Merge branch 'feat/queen-responsibility' into feature/thinking-hook
This commit is contained in:
@@ -1,34 +1,16 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"mcp__agent-builder__create_session",
|
||||
"mcp__agent-builder__set_goal",
|
||||
"mcp__agent-builder__add_node",
|
||||
"mcp__agent-builder__add_edge",
|
||||
"mcp__agent-builder__configure_loop",
|
||||
"mcp__agent-builder__add_mcp_server",
|
||||
"mcp__agent-builder__validate_graph",
|
||||
"mcp__agent-builder__export_graph",
|
||||
"mcp__agent-builder__load_session_by_id",
|
||||
"Bash(git status:*)",
|
||||
"Bash(gh run view:*)",
|
||||
"Bash(uv run:*)",
|
||||
"Bash(env:*)",
|
||||
"mcp__agent-builder__test_node",
|
||||
"mcp__agent-builder__list_mcp_tools",
|
||||
"Bash(python -m py_compile:*)",
|
||||
"Bash(python -m pytest:*)",
|
||||
"Bash(source:*)",
|
||||
"mcp__agent-builder__update_node",
|
||||
"mcp__agent-builder__check_missing_credentials",
|
||||
"mcp__agent-builder__list_stored_credentials",
|
||||
"Bash(find:*)",
|
||||
"mcp__agent-builder__run_tests",
|
||||
"Bash(PYTHONPATH=core:exports:tools/src uv run pytest:*)",
|
||||
"mcp__agent-builder__list_agent_sessions",
|
||||
"mcp__agent-builder__generate_constraint_tests",
|
||||
"mcp__agent-builder__generate_success_tests"
|
||||
"Bash(PYTHONPATH=core:exports:tools/src uv run pytest:*)"
|
||||
]
|
||||
},
|
||||
"enabledMcpjsonServers": ["agent-builder", "tools"]
|
||||
"enabledMcpjsonServers": ["tools"]
|
||||
}
|
||||
|
||||
@@ -67,8 +67,6 @@ temp/
|
||||
|
||||
exports/*
|
||||
|
||||
.agent-builder-sessions/*
|
||||
|
||||
.claude/settings.local.json
|
||||
.claude/skills/ship-it/
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"agent-builder": {
|
||||
"command": "uv",
|
||||
"args": ["run", "-m", "framework.mcp.agent_builder_server"],
|
||||
"cwd": "core"
|
||||
}
|
||||
}
|
||||
"mcpServers": {}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
exports/
|
||||
docs/
|
||||
.agent-builder-sessions/
|
||||
.pytest_cache/
|
||||
**/__pycache__/
|
||||
@@ -1,10 +1,5 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"agent-builder": {
|
||||
"command": "python",
|
||||
"args": ["-m", "framework.mcp.agent_builder_server"],
|
||||
"cwd": "core"
|
||||
},
|
||||
"tools": {
|
||||
"command": "python",
|
||||
"args": ["-m", "aden_tools.mcp_server", "--stdio"],
|
||||
|
||||
+10
-11
@@ -1,17 +1,16 @@
|
||||
# MCP Server Guide - Agent Builder
|
||||
# MCP Server Guide - Agent Building Tools
|
||||
|
||||
This guide covers the MCP (Model Context Protocol) server for building goal-driven agents.
|
||||
> **Note:** The standalone `agent-builder` MCP server (`framework.mcp.agent_builder_server`) has been replaced. Agent building is now done via the `coder-tools` server's `initialize_agent_package` tool, with underlying logic in `framework.builder.package_generator`.
|
||||
|
||||
This guide covers the MCP tools available for building goal-driven agents.
|
||||
|
||||
## Setup
|
||||
|
||||
### Quick Setup
|
||||
|
||||
```bash
|
||||
# Using the setup script (recommended)
|
||||
python setup_mcp.py
|
||||
|
||||
# Or using bash
|
||||
./setup_mcp.sh
|
||||
# Run the quickstart script (recommended)
|
||||
./quickstart.sh
|
||||
```
|
||||
|
||||
### Manual Configuration
|
||||
@@ -21,10 +20,10 @@ Add to your MCP client configuration (e.g., Claude Desktop):
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"agent-builder": {
|
||||
"command": "python",
|
||||
"args": ["-m", "framework.mcp.agent_builder_server"],
|
||||
"cwd": "/path/to/goal-agent"
|
||||
"coder-tools": {
|
||||
"command": "uv",
|
||||
"args": ["run", "coder_tools_server.py", "--stdio"],
|
||||
"cwd": "/path/to/hive/tools"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-58
@@ -17,66 +17,11 @@ Framework provides a runtime framework that captures **decisions**, not just act
|
||||
uv pip install -e .
|
||||
```
|
||||
|
||||
## MCP Server Setup
|
||||
## Agent Building
|
||||
|
||||
The framework includes an MCP (Model Context Protocol) server for building agents. To set up the MCP server:
|
||||
Agent scaffolding is handled by the `coder-tools` MCP server (in `tools/coder_tools_server.py`), which provides the `initialize_agent_package` tool and related utilities. The underlying package generation logic lives in `framework.builder.package_generator`.
|
||||
|
||||
### Automated Setup
|
||||
|
||||
**Using bash (Linux/macOS):**
|
||||
```bash
|
||||
./setup_mcp.sh
|
||||
```
|
||||
|
||||
**Using Python (cross-platform):**
|
||||
```bash
|
||||
python setup_mcp.py
|
||||
```
|
||||
|
||||
The setup script will:
|
||||
1. Install the framework package
|
||||
2. Install MCP dependencies (mcp, fastmcp)
|
||||
3. Create/verify `.mcp.json` configuration
|
||||
4. Test the MCP server module
|
||||
|
||||
### Manual Setup
|
||||
|
||||
If you prefer manual setup:
|
||||
|
||||
```bash
|
||||
# Install framework
|
||||
uv pip install -e .
|
||||
|
||||
# Install MCP dependencies
|
||||
uv pip install mcp fastmcp
|
||||
|
||||
# Test the server
|
||||
uv run python -m framework.mcp.agent_builder_server
|
||||
```
|
||||
|
||||
### Using with MCP Clients
|
||||
|
||||
To use the agent builder with Claude Desktop or other MCP clients, add this to your MCP client configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"agent-builder": {
|
||||
"command": "python",
|
||||
"args": ["-m", "framework.mcp.agent_builder_server"],
|
||||
"cwd": "/path/to/hive/core"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The MCP server provides tools for:
|
||||
- Creating agent building sessions
|
||||
- Defining goals with success criteria
|
||||
- Adding nodes (event_loop only)
|
||||
- Connecting nodes with edges
|
||||
- Validating and exporting agent graphs
|
||||
- Testing nodes and full agent graphs
|
||||
See the [Getting Started Guide](../docs/getting-started.md) for building agents.
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
||||
@@ -95,81 +95,6 @@ async def example_3_config_file():
|
||||
(test_agent_path / "mcp_servers.json").unlink()
|
||||
|
||||
|
||||
async def example_4_custom_agent_with_mcp_tools():
|
||||
"""Example 4: Build custom agent that uses MCP tools"""
|
||||
print("\n=== Example 4: Custom Agent with MCP Tools ===\n")
|
||||
|
||||
from framework.builder.workflow import GraphBuilder
|
||||
|
||||
# Create a workflow builder
|
||||
builder = GraphBuilder()
|
||||
|
||||
# Define goal
|
||||
builder.set_goal(
|
||||
goal_id="web-researcher",
|
||||
name="Web Research Agent",
|
||||
description="Search the web and summarize findings",
|
||||
)
|
||||
|
||||
# Add success criteria
|
||||
builder.add_success_criterion(
|
||||
"search-results", "Successfully retrieve at least 3 web search results"
|
||||
)
|
||||
builder.add_success_criterion("summary", "Provide a clear, concise summary of the findings")
|
||||
|
||||
# Add nodes that will use MCP tools
|
||||
builder.add_node(
|
||||
node_id="web-searcher",
|
||||
name="Web Search",
|
||||
description="Search the web for information",
|
||||
node_type="event_loop",
|
||||
system_prompt="Search for {query} and return the top results. Use the web_search tool.",
|
||||
tools=["web_search"], # This tool comes from tools MCP server
|
||||
input_keys=["query"],
|
||||
output_keys=["search_results"],
|
||||
)
|
||||
|
||||
builder.add_node(
|
||||
node_id="summarizer",
|
||||
name="Summarize Results",
|
||||
description="Summarize the search results",
|
||||
node_type="event_loop",
|
||||
system_prompt="Summarize the following search results in 2-3 sentences: {search_results}",
|
||||
input_keys=["search_results"],
|
||||
output_keys=["summary"],
|
||||
)
|
||||
|
||||
# Connect nodes
|
||||
builder.add_edge("web-searcher", "summarizer")
|
||||
|
||||
# Set entry point
|
||||
builder.set_entry("web-searcher")
|
||||
builder.set_terminal("summarizer")
|
||||
|
||||
# Export the agent
|
||||
export_path = Path("exports/web-research-agent")
|
||||
export_path.mkdir(parents=True, exist_ok=True)
|
||||
builder.export(export_path)
|
||||
|
||||
# Load and register MCP server
|
||||
runner = AgentRunner.load(export_path)
|
||||
runner.register_mcp_server(
|
||||
name="tools",
|
||||
transport="stdio",
|
||||
command="python",
|
||||
args=["-m", "aden_tools.mcp_server", "--stdio"],
|
||||
cwd="../tools",
|
||||
)
|
||||
|
||||
# Run the agent
|
||||
result = await runner.run({"query": "latest AI breakthroughs 2026"})
|
||||
|
||||
print(f"\nAgent completed with result:\n{result}")
|
||||
|
||||
# Cleanup
|
||||
runner.cleanup()
|
||||
|
||||
|
||||
async def main():
|
||||
"""Run all examples"""
|
||||
print("=" * 60)
|
||||
|
||||
@@ -7,7 +7,7 @@ from .nodes import coder_node, queen_node
|
||||
|
||||
# Goal definition
|
||||
goal = Goal(
|
||||
id="agent-builder",
|
||||
id="hive-coder",
|
||||
name="Hive Agent Builder",
|
||||
description=(
|
||||
"Build complete, validated Hive agent packages from natural language "
|
||||
|
||||
@@ -113,7 +113,7 @@ _QUEEN_RUNNING_TOOLS = [
|
||||
# additions.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_agent_builder_knowledge = """\
|
||||
_package_builder_knowledge = """\
|
||||
**A responsible engineer doesn't jump into building. First, \
|
||||
understand the problem and be transparent about what the framework can and cannot do.**
|
||||
|
||||
@@ -880,7 +880,7 @@ coder_node = NodeSpec(
|
||||
system_prompt=(
|
||||
"You are Hive Coder, the best agent-building coding agent. You build "
|
||||
"production-ready Hive agent packages from natural language.\n"
|
||||
+ _agent_builder_knowledge
|
||||
+ _package_builder_knowledge
|
||||
+ _coder_completion
|
||||
+ _appendices
|
||||
),
|
||||
@@ -962,7 +962,7 @@ queen_node = NodeSpec(
|
||||
system_prompt=(
|
||||
_queen_identity_building
|
||||
+ _queen_style
|
||||
+ _agent_builder_knowledge
|
||||
+ _package_builder_knowledge
|
||||
+ _gcu_building_section # GCU as first-class citizen (not appendix)
|
||||
+ _queen_tools_docs
|
||||
+ _queen_behavior
|
||||
@@ -995,7 +995,7 @@ __all__ = [
|
||||
"_queen_behavior_running",
|
||||
"_queen_phase_7",
|
||||
"_queen_style",
|
||||
"_agent_builder_knowledge",
|
||||
"_package_builder_knowledge",
|
||||
"_appendices",
|
||||
"_gcu_building_section",
|
||||
]
|
||||
|
||||
@@ -1,21 +1,7 @@
|
||||
"""Builder interface for analyzing and building agents."""
|
||||
|
||||
from framework.builder.query import BuilderQuery
|
||||
from framework.builder.workflow import (
|
||||
BuildPhase,
|
||||
BuildSession,
|
||||
GraphBuilder,
|
||||
TestCase,
|
||||
TestResult,
|
||||
ValidationResult,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"BuilderQuery",
|
||||
"GraphBuilder",
|
||||
"BuildSession",
|
||||
"BuildPhase",
|
||||
"ValidationResult",
|
||||
"TestCase",
|
||||
"TestResult",
|
||||
]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,832 +0,0 @@
|
||||
"""
|
||||
GraphBuilder Workflow - Enforced incremental building with HITL approval.
|
||||
|
||||
The build process:
|
||||
1. Define Goal → APPROVE
|
||||
2. Add Node → VALIDATE → TEST → APPROVE
|
||||
3. Add Edge → VALIDATE → TEST → APPROVE
|
||||
4. Repeat until graph is complete
|
||||
5. Final integration test → APPROVE
|
||||
6. Export
|
||||
|
||||
Each step requires validation and human approval before proceeding.
|
||||
You cannot skip steps or bypass validation.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime
|
||||
from enum import StrEnum
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from framework.graph.edge import EdgeCondition, EdgeSpec, GraphSpec
|
||||
from framework.graph.goal import Goal
|
||||
from framework.graph.node import NodeSpec
|
||||
|
||||
|
||||
class BuildPhase(StrEnum):
|
||||
"""Current phase of the build process."""
|
||||
|
||||
INIT = "init" # Just started
|
||||
GOAL_DRAFT = "goal_draft" # Drafting goal
|
||||
GOAL_APPROVED = "goal_approved" # Goal approved
|
||||
ADDING_NODES = "adding_nodes" # Adding nodes
|
||||
ADDING_EDGES = "adding_edges" # Adding edges
|
||||
TESTING = "testing" # Running tests
|
||||
APPROVED = "approved" # Fully approved
|
||||
EXPORTED = "exported" # Exported to file
|
||||
|
||||
|
||||
class ValidationResult(BaseModel):
|
||||
"""Result of a validation check."""
|
||||
|
||||
valid: bool
|
||||
errors: list[str] = Field(default_factory=list)
|
||||
warnings: list[str] = Field(default_factory=list)
|
||||
suggestions: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class TestCase(BaseModel):
|
||||
"""A test case for validating agent behavior."""
|
||||
|
||||
id: str
|
||||
description: str
|
||||
input: dict[str, Any]
|
||||
expected_output: Any = None # None means just check it doesn't error
|
||||
expected_contains: str | None = None
|
||||
|
||||
|
||||
class TestResult(BaseModel):
|
||||
"""Result of running a test case."""
|
||||
|
||||
test_id: str
|
||||
passed: bool
|
||||
actual_output: Any = None
|
||||
error: str | None = None
|
||||
execution_path: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class BuildSession(BaseModel):
|
||||
"""
|
||||
Persistent build session state.
|
||||
|
||||
Saved after each approved step so you can resume later.
|
||||
"""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
phase: BuildPhase = BuildPhase.INIT
|
||||
created_at: datetime = Field(default_factory=datetime.now)
|
||||
updated_at: datetime = Field(default_factory=datetime.now)
|
||||
|
||||
# The artifacts being built
|
||||
goal: Goal | None = None
|
||||
nodes: list[NodeSpec] = Field(default_factory=list)
|
||||
edges: list[EdgeSpec] = Field(default_factory=list)
|
||||
|
||||
# Test cases
|
||||
test_cases: list[TestCase] = Field(default_factory=list)
|
||||
test_results: list[TestResult] = Field(default_factory=list)
|
||||
|
||||
# Approval history
|
||||
approvals: list[dict[str, Any]] = Field(default_factory=list)
|
||||
|
||||
# Tools (stored as dicts for serialization)
|
||||
tools: list[dict[str, Any]] = Field(default_factory=list)
|
||||
|
||||
model_config = {"extra": "allow"}
|
||||
|
||||
|
||||
class GraphBuilder:
|
||||
"""
|
||||
Enforced incremental graph building with HITL approval.
|
||||
|
||||
Usage:
|
||||
builder = GraphBuilder("my-agent")
|
||||
|
||||
# Step 1: Define and approve goal
|
||||
builder.set_goal(goal)
|
||||
builder.validate() # Must pass
|
||||
builder.approve("Goal looks good") # Human approval required
|
||||
|
||||
# Step 2: Add nodes one by one
|
||||
builder.add_node(node_spec)
|
||||
builder.validate() # Must pass
|
||||
builder.test(test_case) # Must pass
|
||||
builder.approve("Node works")
|
||||
|
||||
# Step 3: Add edges
|
||||
builder.add_edge(edge_spec)
|
||||
builder.validate()
|
||||
builder.approve("Edge correct")
|
||||
|
||||
# Step 4: Final approval
|
||||
builder.run_all_tests()
|
||||
builder.final_approve("Ready for production")
|
||||
|
||||
# Step 5: Export
|
||||
graph = builder.export()
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
storage_path: Path | str | None = None,
|
||||
session_id: str | None = None,
|
||||
):
|
||||
self.storage_path = Path(storage_path) if storage_path else Path.home() / ".core" / "builds"
|
||||
self.storage_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if session_id:
|
||||
self.session = self._load_session(session_id)
|
||||
else:
|
||||
self.session = BuildSession(
|
||||
id=f"build_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
|
||||
name=name,
|
||||
)
|
||||
|
||||
self._pending_validation: ValidationResult | None = None
|
||||
|
||||
# =========================================================================
|
||||
# PHASE 1: GOAL
|
||||
# =========================================================================
|
||||
|
||||
def set_goal(self, goal: Goal) -> ValidationResult:
|
||||
"""
|
||||
Set the goal for this agent.
|
||||
|
||||
Returns validation result. Must call approve() after validation passes.
|
||||
"""
|
||||
self._require_phase([BuildPhase.INIT, BuildPhase.GOAL_DRAFT])
|
||||
|
||||
self.session.goal = goal
|
||||
self.session.phase = BuildPhase.GOAL_DRAFT
|
||||
|
||||
validation = self._validate_goal(goal)
|
||||
self._pending_validation = validation
|
||||
self._save_session()
|
||||
|
||||
return validation
|
||||
|
||||
def _validate_goal(self, goal: Goal) -> ValidationResult:
|
||||
"""Validate a goal definition."""
|
||||
errors = []
|
||||
warnings = []
|
||||
suggestions = []
|
||||
|
||||
if not goal.id:
|
||||
errors.append("Goal must have an id")
|
||||
if not goal.name:
|
||||
errors.append("Goal must have a name")
|
||||
if not goal.description:
|
||||
errors.append("Goal must have a description")
|
||||
|
||||
if not goal.success_criteria:
|
||||
errors.append("Goal must have at least one success criterion")
|
||||
else:
|
||||
for sc in goal.success_criteria:
|
||||
if not sc.description:
|
||||
errors.append(f"Success criterion '{sc.id}' needs a description")
|
||||
|
||||
if not goal.constraints:
|
||||
warnings.append("Consider adding constraints to define boundaries")
|
||||
|
||||
if not goal.required_capabilities:
|
||||
suggestions.append("Specify required_capabilities (e.g., ['llm', 'tools'])")
|
||||
|
||||
return ValidationResult(
|
||||
valid=len(errors) == 0,
|
||||
errors=errors,
|
||||
warnings=warnings,
|
||||
suggestions=suggestions,
|
||||
)
|
||||
|
||||
# =========================================================================
|
||||
# PHASE 2: NODES
|
||||
# =========================================================================
|
||||
|
||||
def add_node(self, node: NodeSpec) -> ValidationResult:
|
||||
"""
|
||||
Add a node to the graph.
|
||||
|
||||
Returns validation result. Must call approve() after validation passes.
|
||||
"""
|
||||
self._require_phase([BuildPhase.GOAL_APPROVED, BuildPhase.ADDING_NODES])
|
||||
|
||||
# Check for duplicate
|
||||
if any(n.id == node.id for n in self.session.nodes):
|
||||
return ValidationResult(
|
||||
valid=False,
|
||||
errors=[f"Node with id '{node.id}' already exists"],
|
||||
)
|
||||
|
||||
self.session.nodes.append(node)
|
||||
self.session.phase = BuildPhase.ADDING_NODES
|
||||
|
||||
validation = self._validate_node(node)
|
||||
self._pending_validation = validation
|
||||
self._save_session()
|
||||
|
||||
return validation
|
||||
|
||||
def _validate_node(self, node: NodeSpec) -> ValidationResult:
|
||||
"""Validate a node definition."""
|
||||
errors = []
|
||||
warnings = []
|
||||
suggestions = []
|
||||
|
||||
if not node.id:
|
||||
errors.append("Node must have an id")
|
||||
if not node.name:
|
||||
errors.append("Node must have a name")
|
||||
if not node.description:
|
||||
warnings.append(f"Node '{node.id}' should have a description")
|
||||
|
||||
# Type-specific validation
|
||||
if node.node_type == "event_loop":
|
||||
if node.tools and not node.system_prompt:
|
||||
warnings.append(f"Event loop node '{node.id}' should have a system_prompt")
|
||||
|
||||
if node.node_type == "router":
|
||||
if not node.routes:
|
||||
errors.append(f"Router node '{node.id}' must specify routes")
|
||||
|
||||
# Check input/output keys
|
||||
if not node.input_keys:
|
||||
suggestions.append(f"Consider specifying input_keys for '{node.id}'")
|
||||
if not node.output_keys:
|
||||
suggestions.append(f"Consider specifying output_keys for '{node.id}'")
|
||||
|
||||
return ValidationResult(
|
||||
valid=len(errors) == 0,
|
||||
errors=errors,
|
||||
warnings=warnings,
|
||||
suggestions=suggestions,
|
||||
)
|
||||
|
||||
def update_node(self, node_id: str, **updates) -> ValidationResult:
|
||||
"""Update an existing node."""
|
||||
self._require_phase([BuildPhase.ADDING_NODES])
|
||||
|
||||
for i, node in enumerate(self.session.nodes):
|
||||
if node.id == node_id:
|
||||
node_dict = node.model_dump()
|
||||
node_dict.update(updates)
|
||||
updated_node = NodeSpec(**node_dict)
|
||||
self.session.nodes[i] = updated_node
|
||||
|
||||
validation = self._validate_node(updated_node)
|
||||
self._pending_validation = validation
|
||||
self._save_session()
|
||||
return validation
|
||||
|
||||
return ValidationResult(valid=False, errors=[f"Node '{node_id}' not found"])
|
||||
|
||||
def remove_node(self, node_id: str) -> ValidationResult:
|
||||
"""Remove a node (only if no edges reference it)."""
|
||||
self._require_phase([BuildPhase.ADDING_NODES])
|
||||
|
||||
# Check for edge references
|
||||
for edge in self.session.edges:
|
||||
if edge.source == node_id or edge.target == node_id:
|
||||
return ValidationResult(
|
||||
valid=False,
|
||||
errors=[f"Cannot remove node '{node_id}': referenced by edge '{edge.id}'"],
|
||||
)
|
||||
|
||||
self.session.nodes = [n for n in self.session.nodes if n.id != node_id]
|
||||
self._save_session()
|
||||
|
||||
return ValidationResult(valid=True)
|
||||
|
||||
# =========================================================================
|
||||
# PHASE 3: EDGES
|
||||
# =========================================================================
|
||||
|
||||
def add_edge(self, edge: EdgeSpec) -> ValidationResult:
|
||||
"""
|
||||
Add an edge to the graph.
|
||||
|
||||
Returns validation result. Must call approve() after validation passes.
|
||||
"""
|
||||
self._require_phase([BuildPhase.ADDING_NODES, BuildPhase.ADDING_EDGES])
|
||||
|
||||
# Check for duplicate
|
||||
if any(e.id == edge.id for e in self.session.edges):
|
||||
return ValidationResult(
|
||||
valid=False,
|
||||
errors=[f"Edge with id '{edge.id}' already exists"],
|
||||
)
|
||||
|
||||
self.session.edges.append(edge)
|
||||
self.session.phase = BuildPhase.ADDING_EDGES
|
||||
|
||||
validation = self._validate_edge(edge)
|
||||
self._pending_validation = validation
|
||||
self._save_session()
|
||||
|
||||
return validation
|
||||
|
||||
def _validate_edge(self, edge: EdgeSpec) -> ValidationResult:
|
||||
"""Validate an edge definition."""
|
||||
errors = []
|
||||
warnings = []
|
||||
|
||||
if not edge.id:
|
||||
errors.append("Edge must have an id")
|
||||
|
||||
# Check source exists
|
||||
if not any(n.id == edge.source for n in self.session.nodes):
|
||||
errors.append(f"Edge source '{edge.source}' not found in nodes")
|
||||
|
||||
# Check target exists
|
||||
if not any(n.id == edge.target for n in self.session.nodes):
|
||||
errors.append(f"Edge target '{edge.target}' not found in nodes")
|
||||
|
||||
# Warn about conditional edges without expressions
|
||||
if edge.condition == EdgeCondition.CONDITIONAL and not edge.condition_expr:
|
||||
warnings.append(f"Conditional edge '{edge.id}' has no condition_expr")
|
||||
|
||||
return ValidationResult(
|
||||
valid=len(errors) == 0,
|
||||
errors=errors,
|
||||
warnings=warnings,
|
||||
)
|
||||
|
||||
# =========================================================================
|
||||
# VALIDATION & TESTING
|
||||
# =========================================================================
|
||||
|
||||
def validate(self) -> ValidationResult:
|
||||
"""Validate the entire current graph state."""
|
||||
errors = []
|
||||
warnings = []
|
||||
|
||||
# Must have a goal
|
||||
if not self.session.goal:
|
||||
errors.append("No goal defined")
|
||||
return ValidationResult(valid=False, errors=errors)
|
||||
|
||||
# Must have at least one node
|
||||
if not self.session.nodes:
|
||||
errors.append("No nodes defined")
|
||||
|
||||
# Check for entry node
|
||||
entry_candidates = []
|
||||
for node in self.session.nodes:
|
||||
# A node is an entry candidate if no edges point to it
|
||||
if not any(e.target == node.id for e in self.session.edges):
|
||||
entry_candidates.append(node.id)
|
||||
|
||||
if len(entry_candidates) == 0 and self.session.nodes:
|
||||
errors.append("No entry node found (all nodes have incoming edges)")
|
||||
elif len(entry_candidates) > 1:
|
||||
warnings.append(f"Multiple entry candidates: {entry_candidates}. Specify one.")
|
||||
|
||||
# Check for terminal nodes
|
||||
terminal_candidates = []
|
||||
for node in self.session.nodes:
|
||||
if not any(e.source == node.id for e in self.session.edges):
|
||||
terminal_candidates.append(node.id)
|
||||
|
||||
if not terminal_candidates and self.session.nodes:
|
||||
warnings.append("No terminal nodes found (all nodes have outgoing edges)")
|
||||
|
||||
# Check reachability from ALL entry candidates (not just the first one).
|
||||
# Agents with async entry points have multiple nodes with no incoming
|
||||
# edges (e.g., a primary entry node and an event-driven entry node).
|
||||
if entry_candidates and self.session.nodes:
|
||||
reachable = set()
|
||||
for candidate in entry_candidates:
|
||||
reachable |= self._compute_reachable(candidate)
|
||||
unreachable = [n.id for n in self.session.nodes if n.id not in reachable]
|
||||
if unreachable:
|
||||
errors.append(f"Unreachable nodes: {unreachable}")
|
||||
|
||||
validation = ValidationResult(
|
||||
valid=len(errors) == 0,
|
||||
errors=errors,
|
||||
warnings=warnings,
|
||||
)
|
||||
self._pending_validation = validation
|
||||
return validation
|
||||
|
||||
def _compute_reachable(self, start: str) -> set[str]:
|
||||
"""Compute all nodes reachable from start."""
|
||||
reachable = set()
|
||||
to_visit = [start]
|
||||
|
||||
while to_visit:
|
||||
current = to_visit.pop()
|
||||
if current in reachable:
|
||||
continue
|
||||
reachable.add(current)
|
||||
|
||||
for edge in self.session.edges:
|
||||
if edge.source == current:
|
||||
to_visit.append(edge.target)
|
||||
|
||||
# Also follow router routes
|
||||
for node in self.session.nodes:
|
||||
if node.id == current and node.routes:
|
||||
for target in node.routes.values():
|
||||
to_visit.append(target)
|
||||
|
||||
return reachable
|
||||
|
||||
def add_test(self, test: TestCase) -> None:
|
||||
"""Add a test case."""
|
||||
self.session.test_cases.append(test)
|
||||
self._save_session()
|
||||
|
||||
async def run_test_async(
|
||||
self,
|
||||
test: TestCase,
|
||||
executor_factory: Callable,
|
||||
) -> TestResult:
|
||||
"""
|
||||
Run a single test case asynchronously.
|
||||
|
||||
This method is safe to call from async contexts (Jupyter, FastAPI, etc.).
|
||||
executor_factory should return a configured GraphExecutor.
|
||||
"""
|
||||
self._require_phase([BuildPhase.ADDING_NODES, BuildPhase.ADDING_EDGES, BuildPhase.TESTING])
|
||||
self.session.phase = BuildPhase.TESTING
|
||||
|
||||
try:
|
||||
# Build temporary graph for testing
|
||||
graph = self._build_graph()
|
||||
executor = executor_factory()
|
||||
|
||||
# Run the test
|
||||
result = await executor.execute(
|
||||
graph=graph,
|
||||
goal=self.session.goal,
|
||||
input_data=test.input,
|
||||
)
|
||||
|
||||
# Check result
|
||||
passed = result.success
|
||||
if test.expected_output is not None:
|
||||
passed = passed and (result.output.get("result") == test.expected_output)
|
||||
if test.expected_contains:
|
||||
output_str = str(result.output)
|
||||
passed = passed and (test.expected_contains in output_str)
|
||||
|
||||
test_result = TestResult(
|
||||
test_id=test.id,
|
||||
passed=passed,
|
||||
actual_output=result.output,
|
||||
execution_path=result.path,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
test_result = TestResult(
|
||||
test_id=test.id,
|
||||
passed=False,
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
self.session.test_results.append(test_result)
|
||||
self._save_session()
|
||||
|
||||
return test_result
|
||||
|
||||
def run_test(
|
||||
self,
|
||||
test: TestCase,
|
||||
executor_factory: Callable,
|
||||
) -> TestResult:
|
||||
"""
|
||||
Run a single test case.
|
||||
|
||||
This is a synchronous wrapper around run_test_async().
|
||||
If called from an async context (Jupyter, FastAPI, etc.), use run_test_async() instead.
|
||||
|
||||
executor_factory should return a configured GraphExecutor.
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
# Check if an event loop is already running
|
||||
# get_running_loop() returns a loop if one exists, or raises RuntimeError if none exists
|
||||
try:
|
||||
asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
# No event loop running - safe to use asyncio.run()
|
||||
return asyncio.run(self.run_test_async(test, executor_factory))
|
||||
|
||||
# Event loop is running - cannot use asyncio.run()
|
||||
raise RuntimeError(
|
||||
"Cannot call run_test() from an async context. "
|
||||
"An event loop is already running. "
|
||||
"Please use 'await builder.run_test_async(test, executor_factory)' instead."
|
||||
)
|
||||
|
||||
def run_all_tests(self, executor_factory: Callable) -> list[TestResult]:
|
||||
"""Run all test cases."""
|
||||
results = []
|
||||
for test in self.session.test_cases:
|
||||
result = self.run_test(test, executor_factory)
|
||||
results.append(result)
|
||||
return results
|
||||
|
||||
# =========================================================================
|
||||
# APPROVAL
|
||||
# =========================================================================
|
||||
|
||||
def approve(self, comment: str) -> bool:
|
||||
"""
|
||||
Approve the current pending change.
|
||||
|
||||
Must have a passing validation to approve.
|
||||
Returns True if approved, False if validation failed.
|
||||
"""
|
||||
if self._pending_validation is None:
|
||||
raise RuntimeError("Nothing to approve. Run validation first.")
|
||||
|
||||
if not self._pending_validation.valid:
|
||||
return False
|
||||
|
||||
self.session.approvals.append(
|
||||
{
|
||||
"phase": self.session.phase.value,
|
||||
"comment": comment,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"validation": self._pending_validation.model_dump(),
|
||||
}
|
||||
)
|
||||
|
||||
# Advance phase if appropriate
|
||||
if self.session.phase == BuildPhase.GOAL_DRAFT:
|
||||
self.session.phase = BuildPhase.GOAL_APPROVED
|
||||
|
||||
self._pending_validation = None
|
||||
self._save_session()
|
||||
|
||||
return True
|
||||
|
||||
def final_approve(self, comment: str) -> bool:
|
||||
"""
|
||||
Final approval for the complete graph.
|
||||
|
||||
Requires all tests to pass.
|
||||
"""
|
||||
# Run final validation
|
||||
validation = self.validate()
|
||||
if not validation.valid:
|
||||
self._pending_validation = validation
|
||||
return False
|
||||
|
||||
# Check test results
|
||||
if self.session.test_cases:
|
||||
failed_tests = [t for t in self.session.test_results if not t.passed]
|
||||
if failed_tests:
|
||||
self._pending_validation = ValidationResult(
|
||||
valid=False,
|
||||
errors=[f"Failed tests: {[t.test_id for t in failed_tests]}"],
|
||||
)
|
||||
return False
|
||||
|
||||
self.session.phase = BuildPhase.APPROVED
|
||||
self.session.approvals.append(
|
||||
{
|
||||
"phase": "final",
|
||||
"comment": comment,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
}
|
||||
)
|
||||
|
||||
self._save_session()
|
||||
return True
|
||||
|
||||
# =========================================================================
|
||||
# EXPORT
|
||||
# =========================================================================
|
||||
|
||||
def export(self) -> GraphSpec:
|
||||
"""
|
||||
Export the approved graph.
|
||||
|
||||
Requires final approval.
|
||||
"""
|
||||
self._require_phase([BuildPhase.APPROVED])
|
||||
|
||||
graph = self._build_graph()
|
||||
|
||||
self.session.phase = BuildPhase.EXPORTED
|
||||
self._save_session()
|
||||
|
||||
return graph
|
||||
|
||||
def _build_graph(self) -> GraphSpec:
|
||||
"""Build a GraphSpec from current session."""
|
||||
# Determine entry node
|
||||
entry_node = None
|
||||
for node in self.session.nodes:
|
||||
if not any(e.target == node.id for e in self.session.edges):
|
||||
entry_node = node.id
|
||||
break
|
||||
|
||||
# Determine terminal nodes
|
||||
terminal_nodes = []
|
||||
for node in self.session.nodes:
|
||||
if not any(e.source == node.id for e in self.session.edges):
|
||||
terminal_nodes.append(node.id)
|
||||
|
||||
# Collect all memory keys
|
||||
memory_keys = set()
|
||||
for node in self.session.nodes:
|
||||
memory_keys.update(node.input_keys)
|
||||
memory_keys.update(node.output_keys)
|
||||
|
||||
return GraphSpec(
|
||||
id=f"{self.session.name}-graph",
|
||||
goal_id=self.session.goal.id if self.session.goal else "",
|
||||
entry_node=entry_node or "",
|
||||
terminal_nodes=terminal_nodes,
|
||||
nodes=self.session.nodes,
|
||||
edges=self.session.edges,
|
||||
memory_keys=list(memory_keys),
|
||||
)
|
||||
|
||||
def export_to_file(self, path: Path | str) -> None:
|
||||
"""Export the graph to a Python file."""
|
||||
self._require_phase([BuildPhase.APPROVED, BuildPhase.EXPORTED])
|
||||
|
||||
graph = self._build_graph()
|
||||
|
||||
# Generate Python code
|
||||
code = self._generate_code(graph)
|
||||
|
||||
Path(path).write_text(code, encoding="utf-8")
|
||||
self.session.phase = BuildPhase.EXPORTED
|
||||
self._save_session()
|
||||
|
||||
def _generate_code(self, graph: GraphSpec) -> str:
|
||||
"""Generate Python code for the graph."""
|
||||
lines = [
|
||||
'"""',
|
||||
f"Generated agent: {self.session.name}",
|
||||
f"Generated at: {datetime.now().isoformat()}",
|
||||
'"""',
|
||||
"",
|
||||
"from framework.graph import (",
|
||||
" Goal, SuccessCriterion, Constraint,",
|
||||
" NodeSpec, EdgeSpec, EdgeCondition,",
|
||||
")",
|
||||
"from framework.graph.edge import GraphSpec",
|
||||
"from framework.graph.goal import GoalStatus",
|
||||
"",
|
||||
"",
|
||||
"# Goal",
|
||||
]
|
||||
|
||||
if self.session.goal:
|
||||
goal_json = self.session.goal.model_dump_json(indent=4)
|
||||
lines.append("GOAL = Goal.model_validate_json('''")
|
||||
lines.append(goal_json)
|
||||
lines.append("''')")
|
||||
else:
|
||||
lines.append("GOAL = None")
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"",
|
||||
"# Nodes",
|
||||
"NODES = [",
|
||||
]
|
||||
)
|
||||
|
||||
for node in self.session.nodes:
|
||||
node_json = node.model_dump_json(indent=4)
|
||||
lines.append(" NodeSpec.model_validate_json('''")
|
||||
lines.append(node_json)
|
||||
lines.append(" '''),")
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
"]",
|
||||
"",
|
||||
"",
|
||||
"# Edges",
|
||||
"EDGES = [",
|
||||
]
|
||||
)
|
||||
|
||||
for edge in self.session.edges:
|
||||
edge_json = edge.model_dump_json(indent=4)
|
||||
lines.append(" EdgeSpec.model_validate_json('''")
|
||||
lines.append(edge_json)
|
||||
lines.append(" '''),")
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
"]",
|
||||
"",
|
||||
"",
|
||||
"# Graph",
|
||||
]
|
||||
)
|
||||
|
||||
graph_json = graph.model_dump_json(indent=4)
|
||||
lines.append("GRAPH = GraphSpec.model_validate_json('''")
|
||||
lines.append(graph_json)
|
||||
lines.append("''')")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
# =========================================================================
|
||||
# SESSION MANAGEMENT
|
||||
# =========================================================================
|
||||
|
||||
def _require_phase(self, allowed: list[BuildPhase]) -> None:
|
||||
"""Ensure we're in an allowed phase."""
|
||||
if self.session.phase not in allowed:
|
||||
raise RuntimeError(
|
||||
f"Cannot perform this action in phase '{self.session.phase.value}'. "
|
||||
f"Allowed phases: {[p.value for p in allowed]}"
|
||||
)
|
||||
|
||||
def _save_session(self) -> None:
|
||||
"""Save session to disk."""
|
||||
self.session.updated_at = datetime.now()
|
||||
path = self.storage_path / f"{self.session.id}.json"
|
||||
path.write_text(self.session.model_dump_json(indent=2), encoding="utf-8")
|
||||
|
||||
def _load_session(self, session_id: str) -> BuildSession:
|
||||
"""Load session from disk."""
|
||||
path = self.storage_path / f"{session_id}.json"
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"Session not found: {session_id}")
|
||||
return BuildSession.model_validate_json(path.read_text(encoding="utf-8"))
|
||||
|
||||
@classmethod
|
||||
def list_sessions(cls, storage_path: Path | str | None = None) -> list[str]:
|
||||
"""List all saved sessions."""
|
||||
path = Path(storage_path) if storage_path else Path.home() / ".core" / "builds"
|
||||
if not path.exists():
|
||||
return []
|
||||
return [f.stem for f in path.glob("*.json")]
|
||||
|
||||
# =========================================================================
|
||||
# STATUS
|
||||
# =========================================================================
|
||||
|
||||
def status(self) -> dict[str, Any]:
|
||||
"""Get current build status."""
|
||||
return {
|
||||
"session_id": self.session.id,
|
||||
"name": self.session.name,
|
||||
"phase": self.session.phase.value,
|
||||
"goal": self.session.goal.name if self.session.goal else None,
|
||||
"nodes": len(self.session.nodes),
|
||||
"edges": len(self.session.edges),
|
||||
"tests": len(self.session.test_cases),
|
||||
"tests_passed": sum(1 for t in self.session.test_results if t.passed),
|
||||
"approvals": len(self.session.approvals),
|
||||
"pending_validation": self._pending_validation.model_dump()
|
||||
if self._pending_validation
|
||||
else None,
|
||||
}
|
||||
|
||||
def show(self) -> str:
|
||||
"""Show current graph as text."""
|
||||
lines = [
|
||||
f"=== Build: {self.session.name} ===",
|
||||
f"Phase: {self.session.phase.value}",
|
||||
"",
|
||||
]
|
||||
|
||||
if self.session.goal:
|
||||
lines.extend(
|
||||
[
|
||||
f"Goal: {self.session.goal.name}",
|
||||
f" {self.session.goal.description}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
if self.session.nodes:
|
||||
lines.append("Nodes:")
|
||||
for node in self.session.nodes:
|
||||
lines.append(f" [{node.id}] {node.name} ({node.node_type})")
|
||||
lines.append("")
|
||||
|
||||
if self.session.edges:
|
||||
lines.append("Edges:")
|
||||
for edge in self.session.edges:
|
||||
lines.append(f" {edge.source} --{edge.condition.value}--> {edge.target}")
|
||||
lines.append("")
|
||||
|
||||
if self._pending_validation:
|
||||
lines.append("Pending Validation:")
|
||||
lines.append(f" Valid: {self._pending_validation.valid}")
|
||||
for err in self._pending_validation.errors:
|
||||
lines.append(f" ERROR: {err}")
|
||||
for warn in self._pending_validation.warnings:
|
||||
lines.append(f" WARN: {warn}")
|
||||
|
||||
return "\n".join(lines)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -560,7 +560,7 @@ class SessionManager:
|
||||
_QUEEN_BUILDING_TOOLS,
|
||||
_QUEEN_RUNNING_TOOLS,
|
||||
_QUEEN_STAGING_TOOLS,
|
||||
_agent_builder_knowledge,
|
||||
_package_builder_knowledge,
|
||||
_appendices,
|
||||
_queen_behavior_always,
|
||||
_queen_behavior_building,
|
||||
@@ -613,7 +613,7 @@ class SessionManager:
|
||||
+ _queen_tools_building
|
||||
+ _queen_behavior_always
|
||||
+ _queen_behavior_building
|
||||
+ _agent_builder_knowledge
|
||||
+ _package_builder_knowledge
|
||||
+ _queen_phase_7
|
||||
+ _appendices
|
||||
+ worker_identity
|
||||
|
||||
@@ -19,7 +19,7 @@ then run with pytest and debugged with LLM assistance.
|
||||
|
||||
## MCP Tools
|
||||
|
||||
Testing tools are integrated into the main agent_builder_server.py:
|
||||
Testing tools are available via the package generator:
|
||||
- generate_constraint_tests, generate_success_tests (return guidelines)
|
||||
- run_tests, debug_test, list_tests
|
||||
|
||||
|
||||
+10
-46
@@ -7,7 +7,6 @@ This script installs the framework and configures the MCP server.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -75,12 +74,12 @@ def main():
|
||||
|
||||
# Get script directory
|
||||
script_dir = Path(__file__).parent.absolute()
|
||||
os.chdir(script_dir)
|
||||
|
||||
# Step 1: Install framework package
|
||||
log_step("Step 1: Installing framework package...")
|
||||
if not run_command(
|
||||
[sys.executable, "-m", "pip", "install", "-e", "."], "Failed to install framework package"
|
||||
[sys.executable, "-m", "pip", "install", "-e", str(script_dir)],
|
||||
"Failed to install framework package",
|
||||
):
|
||||
sys.exit(1)
|
||||
log_success("Framework package installed")
|
||||
@@ -96,7 +95,7 @@ def main():
|
||||
log_success("MCP dependencies installed")
|
||||
logger.info("")
|
||||
|
||||
# Step 3: Verify/create MCP configuration
|
||||
# Step 3: Verify MCP configuration
|
||||
log_step("Step 3: Verifying MCP server configuration...")
|
||||
mcp_config_path = script_dir / ".mcp.json"
|
||||
|
||||
@@ -107,39 +106,22 @@ def main():
|
||||
config = json.load(f)
|
||||
logger.info(json.dumps(config, indent=2))
|
||||
else:
|
||||
log_error("No .mcp.json found")
|
||||
logger.info("Creating default MCP configuration...")
|
||||
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"agent-builder": {
|
||||
"command": "python",
|
||||
"args": ["-m", "framework.mcp.agent_builder_server"],
|
||||
"cwd": str(script_dir),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with open(mcp_config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
log_success("Created .mcp.json")
|
||||
log_success("No .mcp.json needed (MCP servers configured at repo root)")
|
||||
logger.info("")
|
||||
|
||||
# Step 4: Test MCP server
|
||||
log_step("Step 4: Testing MCP server...")
|
||||
# Step 4: Test framework import
|
||||
log_step("Step 4: Testing framework import...")
|
||||
try:
|
||||
# Try importing the MCP server module
|
||||
subprocess.run(
|
||||
[sys.executable, "-c", "from framework.mcp import agent_builder_server"],
|
||||
[sys.executable, "-c", "import framework; print('OK')"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
log_success("MCP server module verified")
|
||||
log_success("Framework module verified")
|
||||
except subprocess.CalledProcessError as e:
|
||||
log_error("Failed to import MCP server module")
|
||||
log_error("Failed to import framework module")
|
||||
logger.error(f"Error: {e.stderr}")
|
||||
sys.exit(1)
|
||||
logger.info("")
|
||||
@@ -147,29 +129,11 @@ def main():
|
||||
# Success summary
|
||||
logger.info(f"{Colors.GREEN}=== Setup Complete ==={Colors.NC}")
|
||||
logger.info("")
|
||||
logger.info("The MCP server is now ready to use!")
|
||||
logger.info("")
|
||||
logger.info(f"{Colors.BLUE}To start the MCP server manually:{Colors.NC}")
|
||||
logger.info(" uv run python -m framework.mcp.agent_builder_server")
|
||||
logger.info("The framework is now ready to use!")
|
||||
logger.info("")
|
||||
logger.info(f"{Colors.BLUE}MCP Configuration location:{Colors.NC}")
|
||||
logger.info(f" {mcp_config_path}")
|
||||
logger.info("")
|
||||
logger.info(f"{Colors.BLUE}To use with Claude Desktop or other MCP clients,{Colors.NC}")
|
||||
logger.info(f"{Colors.BLUE}add the following to your MCP client configuration:{Colors.NC}")
|
||||
logger.info("")
|
||||
|
||||
example_config = {
|
||||
"mcpServers": {
|
||||
"agent-builder": {
|
||||
"command": "python",
|
||||
"args": ["-m", "framework.mcp.agent_builder_server"],
|
||||
"cwd": str(script_dir),
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info(json.dumps(example_config, indent=2))
|
||||
logger.info("")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+6
-36
@@ -40,52 +40,22 @@ if [ -f ".mcp.json" ]; then
|
||||
echo "Configuration:"
|
||||
cat .mcp.json
|
||||
else
|
||||
echo -e "${RED}✗ No .mcp.json found${NC}"
|
||||
echo "Creating default MCP configuration..."
|
||||
|
||||
cat > .mcp.json <<EOF
|
||||
{
|
||||
"mcpServers": {
|
||||
"agent-builder": {
|
||||
"command": "python",
|
||||
"args": ["-m", "framework.mcp.agent_builder_server"],
|
||||
"cwd": "$SCRIPT_DIR"
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
echo -e "${GREEN}✓ Created .mcp.json${NC}"
|
||||
echo -e "${GREEN}✓ No .mcp.json needed (MCP servers configured at repo root)${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}Step 4: Testing MCP server...${NC}"
|
||||
uv run python -c "from framework.mcp import agent_builder_server; print('✓ MCP server module loads successfully')" || {
|
||||
echo -e "${RED}Failed to import MCP server module${NC}"
|
||||
echo -e "${YELLOW}Step 4: Testing framework import...${NC}"
|
||||
uv run python -c "import framework; print('✓ Framework module loads successfully')" || {
|
||||
echo -e "${RED}Failed to import framework module${NC}"
|
||||
exit 1
|
||||
}
|
||||
echo -e "${GREEN}✓ MCP server module verified${NC}"
|
||||
echo -e "${GREEN}✓ Framework module verified${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${GREEN}=== Setup Complete ===${NC}"
|
||||
echo ""
|
||||
echo "The MCP server is now ready to use!"
|
||||
echo ""
|
||||
echo "To start the MCP server manually:"
|
||||
echo " uv run python -m framework.mcp.agent_builder_server"
|
||||
echo "The framework is now ready to use!"
|
||||
echo ""
|
||||
echo "MCP Configuration location:"
|
||||
echo " $SCRIPT_DIR/.mcp.json"
|
||||
echo ""
|
||||
echo "To use with Claude Desktop or other MCP clients,"
|
||||
echo "add the following to your MCP client configuration:"
|
||||
echo ""
|
||||
echo "{
|
||||
\"mcpServers\": {
|
||||
\"agent-builder\": {
|
||||
\"command\": \"python\",
|
||||
\"args\": [\"-m\", \"framework.mcp.agent_builder_server\"],
|
||||
\"cwd\": \"$SCRIPT_DIR\"
|
||||
}
|
||||
}
|
||||
}"
|
||||
echo ""
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
"""Worker-autonomy guardrails for agent_builder_server."""
|
||||
|
||||
import json
|
||||
|
||||
from framework.graph import Goal, NodeSpec
|
||||
from framework.mcp import agent_builder_server as builder
|
||||
|
||||
|
||||
def _make_session(name: str = "autonomy_test"):
|
||||
session = builder.BuildSession(name=name)
|
||||
session.goal = Goal(
|
||||
id="g1",
|
||||
name="Autonomy Goal",
|
||||
description="Workers stay autonomous.",
|
||||
success_criteria=[],
|
||||
constraints=[],
|
||||
)
|
||||
return session
|
||||
|
||||
|
||||
def test_add_node_rejects_client_facing_event_loop(monkeypatch):
|
||||
session = _make_session()
|
||||
monkeypatch.setattr(builder, "_session", session)
|
||||
monkeypatch.setattr(builder, "_save_session", lambda _: None)
|
||||
|
||||
raw = builder.add_node(
|
||||
node_id="worker",
|
||||
name="Worker",
|
||||
description="Autonomous worker node",
|
||||
node_type="event_loop",
|
||||
input_keys='["task"]',
|
||||
output_keys='["result"]',
|
||||
system_prompt="Do work.",
|
||||
client_facing=True,
|
||||
)
|
||||
data = json.loads(raw)
|
||||
|
||||
assert data["valid"] is False
|
||||
assert any("client_facing=True" in err for err in data["errors"])
|
||||
|
||||
|
||||
def test_validate_graph_rejects_client_facing_event_loop(monkeypatch):
|
||||
session = _make_session()
|
||||
session.nodes = [
|
||||
NodeSpec(
|
||||
id="worker",
|
||||
name="Worker",
|
||||
description="Autonomous worker node",
|
||||
node_type="event_loop",
|
||||
client_facing=True,
|
||||
input_keys=[],
|
||||
output_keys=[],
|
||||
system_prompt="Do work.",
|
||||
)
|
||||
]
|
||||
monkeypatch.setattr(builder, "_session", session)
|
||||
|
||||
data = json.loads(builder.validate_graph())
|
||||
|
||||
assert data["valid"] is False
|
||||
assert any("must not be client_facing" in err for err in data["errors"])
|
||||
@@ -42,40 +42,6 @@ class TestMCPDependencies:
|
||||
assert FastMCP is not None
|
||||
|
||||
|
||||
class TestAgentBuilderServerModule:
|
||||
"""Tests for the agent_builder_server module."""
|
||||
|
||||
def test_module_importable(self):
|
||||
"""Test that framework.mcp.agent_builder_server can be imported."""
|
||||
if not MCP_AVAILABLE:
|
||||
pytest.skip(MCP_SKIP_REASON)
|
||||
|
||||
import framework.mcp.agent_builder_server as module
|
||||
|
||||
assert module is not None
|
||||
|
||||
def test_mcp_object_exported(self):
|
||||
"""Test that the module exports the 'mcp' object (FastMCP instance)."""
|
||||
if not MCP_AVAILABLE:
|
||||
pytest.skip(MCP_SKIP_REASON)
|
||||
|
||||
from mcp.server import FastMCP
|
||||
|
||||
from framework.mcp.agent_builder_server import mcp
|
||||
|
||||
assert mcp is not None
|
||||
assert isinstance(mcp, FastMCP)
|
||||
|
||||
def test_mcp_server_name(self):
|
||||
"""Test that the MCP server has the expected name."""
|
||||
if not MCP_AVAILABLE:
|
||||
pytest.skip(MCP_SKIP_REASON)
|
||||
|
||||
from framework.mcp.agent_builder_server import mcp
|
||||
|
||||
assert mcp.name == "agent-builder"
|
||||
|
||||
|
||||
class TestMCPPackageExports:
|
||||
"""Tests for the framework.mcp package exports."""
|
||||
|
||||
@@ -87,15 +53,3 @@ class TestMCPPackageExports:
|
||||
import framework.mcp
|
||||
|
||||
assert framework.mcp is not None
|
||||
|
||||
def test_agent_builder_server_exported(self):
|
||||
"""Test that agent_builder_server is exported from framework.mcp."""
|
||||
if not MCP_AVAILABLE:
|
||||
pytest.skip(MCP_SKIP_REASON)
|
||||
|
||||
from mcp.server import FastMCP
|
||||
|
||||
from framework.mcp import agent_builder_server
|
||||
|
||||
assert agent_builder_server is not None
|
||||
assert isinstance(agent_builder_server.mcp, FastMCP)
|
||||
|
||||
+9
-60
@@ -101,23 +101,7 @@ def main():
|
||||
else:
|
||||
success("all installed")
|
||||
|
||||
# Check 3: MCP server module
|
||||
check("MCP server module")
|
||||
try:
|
||||
subprocess.run(
|
||||
[sys.executable, "-c", "from framework.mcp import agent_builder_server"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
success("loads successfully")
|
||||
except subprocess.CalledProcessError as e:
|
||||
error("failed to import")
|
||||
logger.error(f" Error: {e.stderr}")
|
||||
all_checks_passed = False
|
||||
|
||||
# Check 4: MCP configuration file
|
||||
# Check 3: MCP configuration file
|
||||
check("MCP configuration file")
|
||||
mcp_config = script_dir / ".mcp.json"
|
||||
if mcp_config.exists():
|
||||
@@ -125,14 +109,14 @@ def main():
|
||||
with open(mcp_config, encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
|
||||
if "mcpServers" in config and "agent-builder" in config["mcpServers"]:
|
||||
server_config = config["mcpServers"]["agent-builder"]
|
||||
if "mcpServers" in config:
|
||||
success("found and valid")
|
||||
logger.info(f" Command: {server_config.get('command')}")
|
||||
logger.info(f" Args: {' '.join(server_config.get('args', []))}")
|
||||
logger.info(f" CWD: {server_config.get('cwd')}")
|
||||
for name, server_config in config.get("mcpServers", {}).items():
|
||||
logger.info(f" Server: {name}")
|
||||
logger.info(f" Command: {server_config.get('command')}")
|
||||
logger.info(f" Args: {' '.join(server_config.get('args', []))}")
|
||||
else:
|
||||
warning("exists but missing agent-builder config")
|
||||
warning("exists but missing mcpServers config")
|
||||
all_checks_passed = False
|
||||
except json.JSONDecodeError:
|
||||
error("invalid JSON format")
|
||||
@@ -140,9 +124,8 @@ def main():
|
||||
else:
|
||||
warning("not found (optional)")
|
||||
logger.info(f" Location would be: {mcp_config}")
|
||||
logger.info(" Run setup_mcp.py to create it")
|
||||
|
||||
# Check 5: Framework modules
|
||||
# Check 4: Framework modules
|
||||
check("core framework modules")
|
||||
modules_to_check = [
|
||||
"framework.runtime.core",
|
||||
@@ -170,46 +153,12 @@ def main():
|
||||
else:
|
||||
success(f"all {len(modules_to_check)} modules OK")
|
||||
|
||||
# Check 6: Test MCP server startup (quick test)
|
||||
check("MCP server startup")
|
||||
try:
|
||||
# Try to import and instantiate the MCP server
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"-c",
|
||||
"from framework.mcp.agent_builder_server import mcp; print('OK')",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
timeout=5,
|
||||
encoding="utf-8",
|
||||
)
|
||||
if "OK" in result.stdout:
|
||||
success("server can start")
|
||||
else:
|
||||
warning("unexpected output")
|
||||
except subprocess.TimeoutExpired:
|
||||
warning("server startup slow (might be OK)")
|
||||
except subprocess.CalledProcessError as e:
|
||||
error("server failed to start")
|
||||
logger.error(f" Error: {e.stderr}")
|
||||
all_checks_passed = False
|
||||
|
||||
logger.info("")
|
||||
logger.info("=" * 40)
|
||||
if all_checks_passed:
|
||||
logger.info(f"{Colors.GREEN}✓ All checks passed!{Colors.NC}")
|
||||
logger.info("")
|
||||
logger.info("Your MCP server is ready to use.")
|
||||
logger.info("")
|
||||
logger.info(f"{Colors.BLUE}To start the server:{Colors.NC}")
|
||||
logger.info(" uv run python -m framework.mcp.agent_builder_server")
|
||||
logger.info("")
|
||||
logger.info(f"{Colors.BLUE}To use with Claude Desktop:{Colors.NC}")
|
||||
logger.info(" Add the configuration from .mcp.json to your")
|
||||
logger.info(" Claude Desktop MCP settings")
|
||||
logger.info("Your framework is ready to use.")
|
||||
else:
|
||||
logger.info(f"{Colors.RED}✗ Some checks failed{Colors.NC}")
|
||||
logger.info("")
|
||||
|
||||
+11
-11
@@ -13,7 +13,7 @@ Use the Hive agent framework (MCP servers and skills) inside [Antigravity IDE](h
|
||||
```bash
|
||||
./scripts/setup-antigravity-mcp.sh
|
||||
```
|
||||
3. **Restart Antigravity IDE.** You should see **agent-builder** and **tools** as available MCP servers.
|
||||
3. **Restart Antigravity IDE.** You should see **coder-tools** and **tools** as available MCP servers.
|
||||
|
||||
> **Important:** Always restart/refresh Antigravity IDE after running the setup script or making any changes to MCP configuration. The IDE only loads MCP servers on startup.
|
||||
|
||||
@@ -23,7 +23,7 @@ Done. For details, prerequisites, and troubleshooting, read on.
|
||||
|
||||
## What you get after setup
|
||||
|
||||
- **agent-builder** – Create and manage agents (goals, nodes, edges).
|
||||
- **coder-tools** – Create and manage agents (scaffolding via `initialize_agent_package`, file I/O, tool discovery).
|
||||
- **tools** – File operations, web search, and other agent tools.
|
||||
- **Documentation** – Guided docs for building and testing agents.
|
||||
|
||||
@@ -68,7 +68,7 @@ The script finds the repo root, writes `~/.gemini/antigravity/mcp_config.json` w
|
||||
|
||||
> **Important:** Always restart/refresh Antigravity IDE after running the setup script. MCP servers are only loaded on IDE startup.
|
||||
|
||||
The **agent-builder** and **tools** servers should show up after restart.
|
||||
The **coder-tools** and **tools** servers should show up after restart.
|
||||
|
||||
**Using Claude Code instead?** Run:
|
||||
|
||||
@@ -82,7 +82,7 @@ That writes `~/.claude/mcp.json` as well.
|
||||
|
||||
### Step 3: Use MCP tools + docs
|
||||
|
||||
Use the `agent-builder` and `tools` MCP servers in Antigravity, and use docs in `docs/` for workflow guidance.
|
||||
Use the `coder-tools` and `tools` MCP servers in Antigravity, and use docs in `docs/` for workflow guidance.
|
||||
|
||||
---
|
||||
|
||||
@@ -90,7 +90,7 @@ Use the `agent-builder` and `tools` MCP servers in Antigravity, and use docs in
|
||||
|
||||
```
|
||||
.agent/
|
||||
├── mcp_config.json # Template for MCP servers (agent-builder, tools)
|
||||
├── mcp_config.json # Template for MCP servers (coder-tools, tools)
|
||||
```
|
||||
|
||||
The **setup script** writes your **user** config (`~/.gemini/antigravity/mcp_config.json`) using paths from **this repo**. The file in `.agent/` is the template; Antigravity itself uses the file in your home directory.
|
||||
@@ -104,7 +104,7 @@ The **setup script** writes your **user** config (`~/.gemini/antigravity/mcp_con
|
||||
- Run the setup script again from the hive repo root: `./scripts/setup-antigravity-mcp.sh`, then restart Antigravity.
|
||||
- Make sure Python and deps are installed: from repo root run `./quickstart.sh`.
|
||||
- Check that the servers can start: from repo root run
|
||||
`cd core && uv run -m framework.mcp.agent_builder_server` (Ctrl+C to stop), and in another terminal
|
||||
`cd tools && uv run coder_tools_server.py --stdio` (Ctrl+C to stop), and in another terminal
|
||||
`cd tools && uv run mcp_server.py --stdio` (Ctrl+C to stop).
|
||||
If those fail, fix the errors first (e.g. install deps with `uv sync`).
|
||||
|
||||
@@ -115,7 +115,7 @@ The **setup script** writes your **user** config (`~/.gemini/antigravity/mcp_con
|
||||
|
||||
**MCP tools don’t show up in the UI**
|
||||
|
||||
- Antigravity may need a restart. Use the files in `docs/` as documentation; the MCP tools (`agent-builder`, `tools`) are the required integration point.
|
||||
- Antigravity may need a restart. Use the files in `docs/` as documentation; the MCP tools (`coder-tools`, `tools`) are the required integration point.
|
||||
|
||||
---
|
||||
|
||||
@@ -126,7 +126,7 @@ Paste this into Antigravity to check that MCP is set up. It doesn’t use your m
|
||||
```
|
||||
Check the Hive + Antigravity integration:
|
||||
|
||||
1. MCP: List available MCP servers/tools. Confirm that "agent-builder" and "tools" (or equivalent) are connected. If not, tell the user to run ./scripts/setup-antigravity-mcp.sh from the hive repo root, then restart Antigravity (see docs/antigravity-setup.md).
|
||||
1. MCP: List available MCP servers/tools. Confirm that "coder-tools" and "tools" (or equivalent) are connected. If not, tell the user to run ./scripts/setup-antigravity-mcp.sh from the hive repo root, then restart Antigravity (see docs/antigravity-setup.md).
|
||||
|
||||
2. Docs: Confirm that the project has `docs/` with setup/developer guides for the workflow.
|
||||
|
||||
@@ -146,9 +146,9 @@ Save as `~/.gemini/antigravity/mcp_config.json` (Antigravity) or `~/.claude/mcp.
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"agent-builder": {
|
||||
"coder-tools": {
|
||||
"command": "uv",
|
||||
"args": ["run", "--directory", "/path/to/hive/core", "-m", "framework.mcp.agent_builder_server"],
|
||||
"args": ["run", "--directory", "/path/to/hive/tools", "coder_tools_server.py", "--stdio"],
|
||||
"disabled": false
|
||||
},
|
||||
"tools": {
|
||||
@@ -184,7 +184,7 @@ python3 -c "import json; json.load(open('.agent/mcp_config.json')); print('OK: v
|
||||
|
||||
```bash
|
||||
# Terminal 1
|
||||
cd core && uv run -m framework.mcp.agent_builder_server
|
||||
cd tools && uv run coder_tools_server.py --stdio
|
||||
|
||||
# Terminal 2
|
||||
cd tools && uv run mcp_server.py --stdio
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
| `core/framework/graph/executor.py` | Remove `FunctionNode` import (~L24). Remove `"function"` from `VALID_NODE_TYPES` (~L1473). Remove `node_type == "function"` branch (~L1529-1533). Remove `register_function()` (~L1975-1977). Add migration error for graphs with `node_type="function"`. |
|
||||
| `core/framework/builder/workflow.py` | Remove `node_type == "function"` validation block (~L258-260). |
|
||||
|
||||
### 1.2 Agent Builder MCP server
|
||||
### 1.2 Builder Package Generator
|
||||
|
||||
| File | What to change |
|
||||
|---|---|
|
||||
| `core/framework/mcp/agent_builder_server.py` | Remove `"function"` from `node_type` description in `add_node` (~L590) and `update_node` (~L841). Remove `node_type == "function"` simulation branch in `test_node` (~L2356-2357). |
|
||||
| `core/framework/builder/package_generator.py` | Remove `"function"` from `node_type` description in `add_node` and `update_node`. Remove `node_type == "function"` simulation branch in `test_node`. |
|
||||
|
||||
### 1.3 Examples & demos
|
||||
|
||||
@@ -56,7 +56,7 @@ Already soft-deprecated with `DeprecationWarning`. No template agent uses them.
|
||||
|---|---|
|
||||
| `core/framework/graph/node.py` | Delete `LLMNode` class (~L660-1689, ~1000 lines). Largest single removal. |
|
||||
| `core/framework/graph/executor.py` | Remove `LLMNode` import. Remove `"llm_tool_use"`/`"llm_generate"` from `VALID_NODE_TYPES`. Remove `DEPRECATED_NODE_TYPES` dict. Remove their branches in `_get_node_implementation` (~L1507-1523). Update `human_input` branch to use `EventLoopNode` instead of `LLMNode`. Add migration error for deprecated types. |
|
||||
| `core/framework/mcp/agent_builder_server.py` | Remove `llm_tool_use`/`llm_generate` validation warnings and branches (~L668-683, L922-937) |
|
||||
| `core/framework/builder/package_generator.py` | Remove `llm_tool_use`/`llm_generate` validation warnings and branches |
|
||||
|
||||
---
|
||||
|
||||
@@ -94,7 +94,7 @@ These tests use `node_type="function"` as convenient scaffolding but actually te
|
||||
|
||||
The entire Planner-Worker-Judge pattern has **zero external consumers**. No template agent, example, demo, or runner references it. It is only consumed by:
|
||||
- Its own internal files (self-referential imports)
|
||||
- The agent-builder MCP server (exposes tools for it)
|
||||
- The builder package generator (exposes tools for it)
|
||||
- Its own dedicated tests
|
||||
|
||||
### 5.1 Delete these files entirely
|
||||
@@ -114,9 +114,9 @@ The entire Planner-Worker-Judge pattern has **zero external consumers**. No temp
|
||||
|
||||
`core/framework/graph/__init__.py` — Remove all planner-worker exports: `FlexibleGraphExecutor`, `ExecutorConfig`, `WorkerNode`, `StepExecutionResult`, `HybridJudge`, `create_default_judge`, `CodeSandbox`, `safe_eval`, `safe_exec`, `Plan`, `PlanStep`, `ActionType`, `ActionSpec`, and all related symbols.
|
||||
|
||||
### 5.3 Remove MCP tools from agent-builder server
|
||||
### 5.3 Remove MCP tools from builder package generator
|
||||
|
||||
`core/framework/mcp/agent_builder_server.py` — Remove these 7 MCP tools:
|
||||
`core/framework/builder/package_generator.py` — Remove these 7 MCP tools:
|
||||
|
||||
| MCP tool | Description |
|
||||
|---|---|
|
||||
@@ -144,7 +144,7 @@ Also remove:
|
||||
4. **Remove `LLMNode` class + deprecated types** (Part 2)
|
||||
5. **Delete Planner-Worker subsystem files** (Part 5.1)
|
||||
6. **Clean up `__init__.py` exports** (Part 5.2)
|
||||
7. **Remove MCP tools** for plans/evaluation from agent-builder server (Part 5.3)
|
||||
7. **Remove MCP tools** for plans/evaluation from builder package generator (Part 5.3)
|
||||
8. **Update examples/demos/docs/skills** (Parts 1.3, 1.4)
|
||||
9. **Run full test suite** to verify
|
||||
|
||||
@@ -157,5 +157,5 @@ Also remove:
|
||||
3. Load any template agent JSON — no errors
|
||||
4. Attempt to load a graph with `node_type="function"` — clear `RuntimeError` with migration guidance
|
||||
5. Attempt to load a graph with `node_type="llm_tool_use"` — clear `RuntimeError` with migration guidance
|
||||
6. Agent builder MCP: `add_node` with `node_type="function"` — rejected with helpful message
|
||||
6. Builder package generator: `add_node` with `node_type="function"` — rejected with helpful message
|
||||
7. Plan/evaluation MCP tools no longer appear in tool list
|
||||
|
||||
@@ -116,10 +116,10 @@ MCP (Model Context Protocol) servers are configured in `.mcp.json` at the projec
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"agent-builder": {
|
||||
"coder-tools": {
|
||||
"command": "uv",
|
||||
"args": ["run", "-m", "framework.mcp.agent_builder_server"],
|
||||
"cwd": "core"
|
||||
"args": ["run", "coder_tools_server.py", "--stdio"],
|
||||
"cwd": "tools"
|
||||
},
|
||||
"tools": {
|
||||
"command": "uv",
|
||||
@@ -130,7 +130,7 @@ MCP (Model Context Protocol) servers are configured in `.mcp.json` at the projec
|
||||
}
|
||||
```
|
||||
|
||||
The tools MCP server exposes tools including web search, PDF reading, CSV processing, and file system operations.
|
||||
The `coder-tools` server provides agent scaffolding via `initialize_agent_package` and related tools. The `tools` MCP server exposes tools including web search, PDF reading, CSV processing, and file system operations.
|
||||
|
||||
## Storage
|
||||
|
||||
|
||||
+10
-10
@@ -101,7 +101,7 @@ Get API keys:
|
||||
./quickstart.sh
|
||||
```
|
||||
|
||||
This sets up agent-builder and tools MCP workflows.
|
||||
This sets up the MCP tools and workflows for building agents.
|
||||
|
||||
### Cursor IDE Support
|
||||
|
||||
@@ -117,7 +117,7 @@ MCP tools are also available in Cursor. To enable:
|
||||
Hive supports [OpenAI Codex CLI](https://github.com/openai/codex) (v0.101.0+).
|
||||
|
||||
Configuration files are tracked in git:
|
||||
- `.codex/config.toml` — MCP server config (`agent-builder`)
|
||||
- `.codex/config.toml` — MCP server config
|
||||
|
||||
To use Codex with Hive:
|
||||
1. Run `codex` in the repo root
|
||||
@@ -136,7 +136,7 @@ To enable Opencode integration:
|
||||
2. Configure MCP servers in `.opencode/mcp.json`
|
||||
3. Restart Opencode to load the MCP servers
|
||||
4. Switch to the Hive agent
|
||||
* **Tools:** Accesses `agent-builder` and standard `tools` via standard MCP protocols over stdio.
|
||||
* **Tools:** Accesses `coder-tools` and standard `tools` via standard MCP protocols over stdio.
|
||||
|
||||
### Verify Setup
|
||||
|
||||
@@ -146,7 +146,7 @@ uv run python -c "import framework; print('✓ framework OK')"
|
||||
uv run python -c "import aden_tools; print('✓ aden_tools OK')"
|
||||
uv run python -c "import litellm; print('✓ litellm OK')"
|
||||
|
||||
# Run an agent (after building one with agent-builder)
|
||||
# Run an agent (after building one with coder-tools)
|
||||
PYTHONPATH=exports uv run python -m your_agent_name validate
|
||||
```
|
||||
|
||||
@@ -206,7 +206,7 @@ hive/ # Repository root
|
||||
│ └── README.md # Tools documentation
|
||||
│
|
||||
├── exports/ # AGENT PACKAGES (user-created, gitignored)
|
||||
│ └── your_agent_name/ # Created via agent-builder workflow
|
||||
│ └── your_agent_name/ # Created via coder-tools workflow
|
||||
│
|
||||
├── examples/ # Example agents
|
||||
│ └── templates/ # Pre-built template agents
|
||||
@@ -235,7 +235,7 @@ hive/ # Repository root
|
||||
|
||||
## Building Agents
|
||||
|
||||
### Using Agent Builder Workflow
|
||||
### Using Coder Tools Workflow
|
||||
|
||||
The fastest way to build agents is with the configured MCP workflow:
|
||||
|
||||
@@ -244,7 +244,7 @@ The fastest way to build agents is with the configured MCP workflow:
|
||||
./quickstart.sh
|
||||
|
||||
# Build a new agent
|
||||
Use the agent-builder MCP tools from your IDE agent chat
|
||||
Use the coder-tools MCP tools from your IDE agent chat (e.g., initialize_agent_package)
|
||||
```
|
||||
|
||||
### Agent Development Workflow
|
||||
@@ -252,7 +252,7 @@ Use the agent-builder MCP tools from your IDE agent chat
|
||||
1. **Define Your Goal**
|
||||
|
||||
```
|
||||
Use the agent-builder workflow
|
||||
Use the coder-tools initialize_agent_package tool
|
||||
Enter goal: "Build an agent that processes customer support tickets"
|
||||
```
|
||||
|
||||
@@ -555,7 +555,7 @@ uv add <package>
|
||||
|
||||
```bash
|
||||
# Option 1: Use Claude Code skill (recommended)
|
||||
Use the agent-builder workflow
|
||||
Use the coder-tools initialize_agent_package tool
|
||||
|
||||
# Option 2: Create manually
|
||||
# Note: exports/ is initially empty (gitignored). Create your agent directory:
|
||||
@@ -563,7 +563,7 @@ mkdir -p exports/my_new_agent
|
||||
cd exports/my_new_agent
|
||||
# Create agent.json, tools.py, README.md (see Agent Package Structure below)
|
||||
|
||||
# Option 3: Use the agent builder MCP tools (advanced)
|
||||
# Option 3: Use the coder-tools MCP tools (advanced)
|
||||
# See core/MCP_BUILDER_TOOLS_GUIDE.md
|
||||
```
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ Build and run an agent using Claude Code CLI with the agent building skills:
|
||||
./quickstart.sh
|
||||
```
|
||||
|
||||
This sets up agent-builder and tools MCP workflows.
|
||||
This sets up the MCP tools and workflows for building agents.
|
||||
|
||||
### Cursor IDE Support
|
||||
|
||||
@@ -180,7 +180,7 @@ MCP tools are also available in Cursor. To enable:
|
||||
|
||||
**Claude Code:**
|
||||
```
|
||||
Use the agent-builder workflow
|
||||
Use the coder-tools initialize_agent_package tool to scaffold a new agent
|
||||
```
|
||||
|
||||
**Codex CLI:**
|
||||
@@ -361,7 +361,7 @@ hive/
|
||||
│ └── pyproject.toml
|
||||
│
|
||||
├── exports/ # Agent packages (user-created, gitignored)
|
||||
│ └── your_agent_name/ # Created via agent-builder workflow
|
||||
│ └── your_agent_name/ # Created via coder-tools workflow
|
||||
│
|
||||
└── examples/
|
||||
└── templates/ # Pre-built template agents
|
||||
@@ -413,10 +413,10 @@ The `.mcp.json` at project root configures MCP servers to run through `uv run` i
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"agent-builder": {
|
||||
"coder-tools": {
|
||||
"command": "uv",
|
||||
"args": ["run", "-m", "framework.mcp.agent_builder_server"],
|
||||
"cwd": "core"
|
||||
"args": ["run", "coder_tools_server.py", "--stdio"],
|
||||
"cwd": "tools"
|
||||
},
|
||||
"tools": {
|
||||
"command": "uv",
|
||||
@@ -453,7 +453,7 @@ This design allows agents in `exports/` to be:
|
||||
### 2. Build Agent (Claude Code)
|
||||
|
||||
```
|
||||
Use the agent-builder workflow
|
||||
Use the coder-tools initialize_agent_package tool
|
||||
Enter goal: "Build an agent that processes customer support tickets"
|
||||
```
|
||||
|
||||
@@ -538,7 +538,7 @@ Run the quickstart script in the root directory:
|
||||
|
||||
[OpenAI Codex CLI](https://github.com/openai/codex) (v0.101.0+) is supported with project-level config:
|
||||
|
||||
- `.codex/config.toml` — MCP server configuration (`agent-builder`)
|
||||
- `.codex/config.toml` — MCP server configuration
|
||||
|
||||
These files are tracked in git and available on clone. To use Codex with Hive:
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ This is the recommended way to create your first agent.
|
||||
# Setup already done via quickstart.sh above
|
||||
|
||||
# Start Claude Code and build an agent
|
||||
Use the agent-builder workflow
|
||||
Use the coder-tools initialize_agent_package tool
|
||||
```
|
||||
|
||||
Follow the interactive prompts to:
|
||||
@@ -115,7 +115,7 @@ hive/
|
||||
│ └── file_system_toolkits/
|
||||
│
|
||||
├── exports/ # Agent Packages (user-generated, not in repo)
|
||||
│ └── your_agent/ # Your agents created via agent-builder workflow
|
||||
│ └── your_agent/ # Your agents created via coder-tools workflow
|
||||
│
|
||||
├── examples/
|
||||
│ └── templates/ # Pre-built template agents
|
||||
@@ -173,7 +173,7 @@ PYTHONPATH=exports uv run python -m my_agent test --type success
|
||||
1. **Dashboard**: Run `hive open` to launch the web dashboard, or `hive tui` for the terminal UI
|
||||
2. **Detailed Setup**: See [environment-setup.md](./environment-setup.md)
|
||||
3. **Developer Guide**: See [developer-guide.md](./developer-guide.md)
|
||||
4. **Build Agents**: Use agent-builder workflow in Claude Code
|
||||
4. **Build Agents**: Use the coder-tools `initialize_agent_package` tool in Claude Code
|
||||
5. **Custom Tools**: Learn to integrate MCP servers
|
||||
6. **Join Community**: [Discord](https://discord.com/invite/MXE49hrKDk)
|
||||
|
||||
@@ -216,4 +216,4 @@ pip uninstall -y framework tools
|
||||
- **Documentation**: Check the `/docs` folder
|
||||
- **Issues**: [github.com/adenhq/hive/issues](https://github.com/adenhq/hive/issues)
|
||||
- **Discord**: [discord.com/invite/MXE49hrKDk](https://discord.com/invite/MXE49hrKDk)
|
||||
- **Build Agents**: Use agent-builder workflow to create agents
|
||||
- **Build Agents**: Use the coder-tools workflow to create agents
|
||||
|
||||
@@ -32,7 +32,7 @@ Scan `exports/` for agent packages and `~/.hive/agents/` for runtime data. Retur
|
||||
|
||||
### 3-7. Session & Checkpoint Inspection
|
||||
|
||||
Ported from `agent_builder_server.py` lines 3484-3856. Pure filesystem reads — JSON + pathlib, zero framework imports.
|
||||
Ported from the former `agent_builder_server.py`. Pure filesystem reads — JSON + pathlib, zero framework imports.
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
@@ -40,13 +40,13 @@ Ported from `agent_builder_server.py` lines 3484-3856. Pure filesystem reads —
|
||||
| `list_agent_checkpoints(agent_name, session_id)` | List checkpoints for debugging |
|
||||
| `get_agent_checkpoint(agent_name, session_id, checkpoint_id?)` | Load a checkpoint's full state |
|
||||
|
||||
**Key difference from agent-builder:** These tools accept `agent_name` (e.g. `"deep_research_agent"`) instead of raw `agent_work_dir` paths. They resolve to `~/.hive/agents/{agent_name}/` internally. Friendlier for the LLM.
|
||||
**Key difference from the old agent-builder server:** These tools accept `agent_name` (e.g. `"deep_research_agent"`) instead of raw `agent_work_dir` paths. They resolve to `~/.hive/agents/{agent_name}/` internally. Friendlier for the LLM.
|
||||
|
||||
### 8. Test Execution
|
||||
|
||||
**`run_agent_tests(agent_name, test_types?, fail_fast?)`**
|
||||
|
||||
Ported from `agent_builder_server.py` lines 2756-2920. Runs pytest on an agent's test suite, sets PYTHONPATH automatically, parses output into structured results (passed/failed/skipped counts, per-test status, failure details).
|
||||
Ported from the former `agent_builder_server.py`. Runs pytest on an agent's test suite, sets PYTHONPATH automatically, parses output into structured results (passed/failed/skipped counts, per-test status, failure details).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ Full rewrite of account listing and configuration:
|
||||
|
||||
---
|
||||
|
||||
### Modified: `core/framework/mcp/agent_builder_server.py`
|
||||
### Modified: `core/framework/builder/package_generator.py`
|
||||
|
||||
- `store_credential(name, value, alias="default", ...)` — added `alias` param; now delegates to `LocalCredentialRegistry.save_account()` with auto health check; returns `status` and `identity`
|
||||
- `list_stored_credentials()` — delegates to `LocalCredentialRegistry.list_accounts()`; returns `credential_id`, `alias`, `status`, `identity`, `last_validated`
|
||||
|
||||
+7
-7
@@ -218,7 +218,7 @@ Implement the Goal Creation Session via the Queen Bee and the dynamic Worker Age
|
||||
- [x] Test case generation
|
||||
- [x] Test case validation for worker agent
|
||||
- [x] **Agent Creation Flow**
|
||||
- [x] Hive Coder reads templates and discovers tools (mcp/agent_builder_server.py)
|
||||
- [x] Hive Coder reads templates and discovers tools (builder/package_generator.py)
|
||||
- [x] Generates agent.py, nodes/__init__.py, config.py
|
||||
- [x] MCP server configuration discovery
|
||||
- [x] Dynamic tool binding
|
||||
@@ -330,7 +330,7 @@ Ship essential framework utilities: Node validation, HITL (Human-in-the-loop pau
|
||||
Port popular tools, and build out the Runtime Log, Audit Trail, Excel, and Email integrations.
|
||||
|
||||
- [x] **File Operations (36+ tools)**
|
||||
- [x] read_file, write_file, edit_file (mcp/agent_builder_server.py)
|
||||
- [x] read_file, write_file, edit_file (builder/package_generator.py)
|
||||
- [x] list_directory, search_files
|
||||
- [x] apply_diff / apply_patch for code modification (tools/file_system_toolkits/)
|
||||
- [x] data_tools (CSV/Excel parsing)
|
||||
@@ -435,7 +435,7 @@ Enforce session-local memory isolation to prevent data bleed between concurrent
|
||||
Implement File I/O support, streaming mode, and allow users to supply custom functions as libraries/nodes.
|
||||
|
||||
- [x] **File I/O**
|
||||
- [x] File read/write operations (mcp/agent_builder_server.py)
|
||||
- [x] File read/write operations (builder/package_generator.py)
|
||||
- [x] File system navigation
|
||||
- [x] Directory listing and search
|
||||
- [x] **Execution Streaming**
|
||||
@@ -443,7 +443,7 @@ Implement File I/O support, streaming mode, and allow users to supply custom fun
|
||||
- [x] Token-by-token output via event bus
|
||||
- [x] Tool call streaming
|
||||
- [x] **Custom Tool Integration**
|
||||
- [x] MCP server discovery (mcp/agent_builder_server.py)
|
||||
- [x] MCP server discovery (builder/package_generator.py)
|
||||
- [x] Dynamic tool binding
|
||||
- [x] Custom tool registration
|
||||
- [ ] **Streaming Mode Enhancements**
|
||||
@@ -461,7 +461,7 @@ Implement File I/O support, streaming mode, and allow users to supply custom fun
|
||||
Add semantic search capabilities and an interactive file system for frontend product integration.
|
||||
|
||||
- [x] **File Search**
|
||||
- [x] search_files tool (mcp/agent_builder_server.py)
|
||||
- [x] search_files tool (builder/package_generator.py)
|
||||
- [x] Directory traversal
|
||||
- [ ] **Semantic Search**
|
||||
- [ ] Semantic indexing of files
|
||||
@@ -735,7 +735,7 @@ Ship ~20 ready-to-use templates including GTM Sales, Marketing, Analytics, Train
|
||||
Build a lightweight local server (e.g., FastAPI or Node) that securely exposes the Hive framework's core Event Bus and Memory Layer to the local browser environment.
|
||||
|
||||
- [x] **MCP Server Foundation**
|
||||
- [x] FastMCP server implementation (mcp/agent_builder_server.py)
|
||||
- [x] FastMCP server implementation (builder/package_generator.py)
|
||||
- [x] Agent builder tools exposed
|
||||
- [x] Port 4001 exposed in Docker
|
||||
- [x] **Event Bus Architecture**
|
||||
@@ -802,7 +802,7 @@ Create a UI component to inspect the Shared Memory and Write-Through Conversatio
|
||||
|
||||
- [x] **Runtime Logs Tool**
|
||||
- [x] Inspect agent session logs (tools/runtime_logs_tool/)
|
||||
- [x] Session state retrieval (mcp/agent_builder_server.py)
|
||||
- [x] Session state retrieval (builder/package_generator.py)
|
||||
- [ ] **Memory Inspector UI**
|
||||
- [ ] Shared Memory visualization
|
||||
- [ ] Conversation memory view (NodeConversation display)
|
||||
|
||||
@@ -22,7 +22,7 @@ template_name/
|
||||
|
||||
### Option 1: Build from template (recommended)
|
||||
|
||||
Use the agent-builder workflow and select "From a template" to interactively pick a template, customize the goal/nodes/graph, and export a new agent.
|
||||
Use the `coder-tools` `initialize_agent_package` tool and select "From a template" to interactively pick a template, customize the goal/nodes/graph, and export a new agent.
|
||||
|
||||
### Option 2: Manual copy
|
||||
|
||||
|
||||
+2
-3
@@ -721,12 +721,11 @@ $importErrors = 0
|
||||
$imports = @(
|
||||
@{ Module = "framework"; Label = "framework"; Required = $true },
|
||||
@{ Module = "aden_tools"; Label = "aden_tools"; Required = $true },
|
||||
@{ Module = "litellm"; Label = "litellm"; Required = $false },
|
||||
@{ Module = "framework.mcp.agent_builder_server"; Label = "MCP server module"; Required = $true }
|
||||
@{ Module = "litellm"; Label = "litellm"; Required = $false }
|
||||
)
|
||||
|
||||
# Batch check all imports in single process (reduces subprocess spawning overhead)
|
||||
$modulesToCheck = @("framework", "aden_tools", "litellm", "framework.mcp.agent_builder_server")
|
||||
$modulesToCheck = @("framework", "aden_tools", "litellm")
|
||||
|
||||
try {
|
||||
$checkOutput = & uv run python scripts/check_requirements.py @modulesToCheck 2>&1 | Out-String
|
||||
|
||||
+2
-3
@@ -321,7 +321,7 @@ echo ""
|
||||
IMPORT_ERRORS=0
|
||||
|
||||
# Batch check all imports in single process (reduces subprocess spawning overhead)
|
||||
CHECK_RESULT=$(uv run python scripts/check_requirements.py framework aden_tools litellm framework.mcp.agent_builder_server 2>/dev/null)
|
||||
CHECK_RESULT=$(uv run python scripts/check_requirements.py framework aden_tools litellm 2>/dev/null)
|
||||
CHECK_EXIT=$?
|
||||
|
||||
# Parse and display results
|
||||
@@ -337,8 +337,7 @@ try:
|
||||
modules = [
|
||||
('framework', 'framework imports OK', True),
|
||||
('aden_tools', 'aden_tools imports OK', True),
|
||||
('litellm', 'litellm imports OK', False),
|
||||
('framework.mcp.agent_builder_server', 'MCP server module OK', True)
|
||||
('litellm', 'litellm imports OK', False)
|
||||
]
|
||||
import_errors = 0
|
||||
for mod, label, required in modules:
|
||||
|
||||
@@ -49,7 +49,7 @@ Write-Host "Using Python: $PythonCmd" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Define modules to check
|
||||
$modules = @("framework", "aden_tools", "litellm", "framework.mcp.agent_builder_server")
|
||||
$modules = @("framework", "aden_tools", "litellm")
|
||||
|
||||
# Benchmark old approach (individual subprocess calls)
|
||||
Write-Host "Testing OLD approach (individual subprocess calls)..." -ForegroundColor Yellow
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# Run from anywhere inside the hive repo. Generates ~/.gemini/antigravity/mcp_config.json
|
||||
# based on .agent/mcp_config.json template, with absolute paths so the IDE can
|
||||
# connect to agent-builder and tools MCP servers without manual path editing.
|
||||
# connect to tools MCP servers without manual path editing.
|
||||
#
|
||||
set -e
|
||||
|
||||
@@ -56,6 +56,6 @@ fi
|
||||
|
||||
echo ""
|
||||
echo "Next: Restart Antigravity IDE so it loads the MCP config."
|
||||
echo " Then open this repo; agent-builder and tools should appear."
|
||||
echo " Then open this repo; tools should appear."
|
||||
echo ""
|
||||
echo "For Claude Code, run: $0 --claude"
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Quick test script for initialize_agent_package."""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "core"))
|
||||
|
||||
from framework.builder.package_generator import (
|
||||
BuildSession,
|
||||
initialize_agent_package,
|
||||
)
|
||||
from framework.graph import EdgeCondition, EdgeSpec, Goal, NodeSpec, SuccessCriterion
|
||||
import framework.builder.package_generator as pg
|
||||
|
||||
# Create a minimal build session
|
||||
session = BuildSession(name="richard_test2")
|
||||
session.goal = Goal(
|
||||
id="goal_1",
|
||||
name="Test Goal",
|
||||
description="A simple test agent",
|
||||
success_criteria=[
|
||||
SuccessCriterion(id="sc_1", description="Completes successfully", metric="llm_judge", target="success")
|
||||
],
|
||||
constraints=[],
|
||||
)
|
||||
session.nodes = [
|
||||
NodeSpec(
|
||||
id="start",
|
||||
name="Start Node",
|
||||
description="Entry point",
|
||||
node_type="event_loop",
|
||||
input_keys=[],
|
||||
output_keys=["result"],
|
||||
system_prompt="You are a helpful assistant.",
|
||||
),
|
||||
]
|
||||
session.edges = []
|
||||
|
||||
# Set as active session (in-memory only, no disk persistence)
|
||||
pg._session = session
|
||||
|
||||
# Now call initialize_agent_package
|
||||
result = initialize_agent_package("richard_test2")
|
||||
print(result)
|
||||
@@ -1347,6 +1347,70 @@ def validate_agent_package(agent_name: str) -> str:
|
||||
)
|
||||
|
||||
|
||||
# ── Meta-agent: Package initialization ─────────────────────────────────────
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def initialize_agent_package(agent_name: str, agent_json_path: str) -> str:
|
||||
"""Generate a full Python agent package from an exported agent.json file.
|
||||
|
||||
Reads the agent.json (goal, nodes, edges), validates the graph, and
|
||||
generates all files needed for a runnable agent in exports/{agent_name}/:
|
||||
config.py, nodes/__init__.py, agent.py, __init__.py, __main__.py,
|
||||
mcp_servers.json, tests/conftest.py, agent.json, README.md.
|
||||
|
||||
Call this INSTEAD of manually writing package files.
|
||||
|
||||
Args:
|
||||
agent_name: Name for the agent package. Must be snake_case (e.g. 'my_agent').
|
||||
agent_json_path: Path to the exported agent.json file to import.
|
||||
|
||||
Returns:
|
||||
JSON with files written, validation warnings, and next steps.
|
||||
"""
|
||||
resolved = _resolve_path(agent_json_path)
|
||||
if not os.path.isfile(resolved):
|
||||
return json.dumps({"success": False, "error": f"File not found: {agent_json_path}"})
|
||||
|
||||
env = os.environ.copy()
|
||||
core_path = os.path.join(PROJECT_ROOT, "core")
|
||||
exports_path = os.path.join(PROJECT_ROOT, "exports")
|
||||
fw_agents_path = os.path.join(PROJECT_ROOT, "core", "framework", "agents")
|
||||
pythonpath = env.get("PYTHONPATH", "")
|
||||
path_parts = [core_path, exports_path, fw_agents_path, PROJECT_ROOT]
|
||||
if pythonpath:
|
||||
path_parts.append(pythonpath)
|
||||
env["PYTHONPATH"] = os.pathsep.join(path_parts)
|
||||
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[
|
||||
"uv", "run", "python", "-m",
|
||||
"framework.builder.package_generator",
|
||||
agent_name,
|
||||
resolved,
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60,
|
||||
env=env,
|
||||
cwd=PROJECT_ROOT,
|
||||
stdin=subprocess.DEVNULL,
|
||||
)
|
||||
if proc.returncode == 0:
|
||||
return proc.stdout.strip() or json.dumps({"success": True})
|
||||
else:
|
||||
return json.dumps({
|
||||
"success": False,
|
||||
"error": proc.stderr.strip()[:3000],
|
||||
"stdout": proc.stdout.strip()[:1000],
|
||||
})
|
||||
except subprocess.TimeoutExpired:
|
||||
return json.dumps({"success": False, "error": "Package generation timed out (60s)"})
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)})
|
||||
|
||||
|
||||
# ── Main ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user