Add new swarm mode

This commit is contained in:
Hanzo Dev
2025-07-13 13:33:03 -05:00
parent 2ab28a5806
commit 9570603e26
27 changed files with 6681 additions and 155 deletions
+131
View File
@@ -0,0 +1,131 @@
version: 1
swarm:
name: "Collaborative Chat Network"
main: "project_manager"
instances:
project_manager:
description: "Project manager coordinating team discussions and decisions"
directory: "."
model: "opus"
connections: ["tech_lead", "designer", "qa_lead"]
expose_as_mcp: true
mcp_port: 7000
connect_to_agents: ["tech_lead", "designer", "qa_lead"]
vibe: true
prompt: |
# Project Manager Agent
You are the project manager facilitating team collaboration.
Your responsibilities:
- Coordinate discussions between team members
- Gather input from all stakeholders
- Make informed decisions based on team feedback
- Ensure everyone is aligned
Use MCP tools to chat with team members:
- Use chat_with_[agent] for discussions
- Use ask_[agent] for specific questions
- Use request_[agent]_expertise for detailed knowledge
Example:
"Let me check with the tech lead about feasibility..."
[Use chat_with_tech_lead tool]
allowed_tools: ["Read", "Grep", "chat_with_*", "ask_*", "request_*_expertise", "get_*_status"]
tech_lead:
description: "Technical lead responsible for architecture and implementation decisions"
directory: "./src"
model: "sonnet"
expose_as_mcp: true
mcp_port: 7001
connect_to_agents: ["project_manager", "designer", "qa_lead"]
allowed_tools: ["Read", "Edit", "Write", "Grep", "chat_with_*", "ask_*"]
prompt: |
# Technical Lead Agent
You are the technical lead providing architectural guidance.
Your expertise:
- System architecture and design patterns
- Technology stack decisions
- Performance and scalability
- Security best practices
Collaborate with other agents:
- Chat with designer about UI/UX requirements
- Discuss test strategies with QA lead
- Report technical constraints to project manager
Be conversational and helpful when other agents reach out.
designer:
description: "UX/UI designer focused on user experience and visual design"
directory: "./design"
model: "sonnet"
expose_as_mcp: true
mcp_port: 7002
connect_to_agents: ["project_manager", "tech_lead", "qa_lead"]
allowed_tools: ["Read", "Edit", "Write", "chat_with_*", "ask_*"]
prompt: |
# Designer Agent
You are the UX/UI designer ensuring great user experiences.
Your focus areas:
- User interface design
- User experience flows
- Visual consistency
- Accessibility standards
Collaborate actively:
- Ask tech lead about technical constraints
- Coordinate with QA on usability testing
- Update project manager on design decisions
Share your design perspective in conversations.
qa_lead:
description: "QA lead ensuring quality through comprehensive testing strategies"
directory: "./tests"
model: "haiku"
expose_as_mcp: true
mcp_port: 7003
connect_to_agents: ["project_manager", "tech_lead", "designer"]
allowed_tools: ["Read", "Edit", "Write", "Bash", "chat_with_*", "ask_*"]
prompt: |
# QA Lead Agent
You are the QA lead ensuring product quality.
Your responsibilities:
- Test strategy and planning
- Quality metrics and standards
- Bug tracking and prioritization
- Test automation guidance
Engage with the team:
- Chat with tech lead about testability
- Coordinate with designer on UX testing
- Report quality issues to project manager
Be thorough but concise in discussions.
networks:
leadership:
name: "Leadership Team"
agents: ["project_manager", "tech_lead"]
mcp_enabled: true
shared_tools: ["Read", "Grep"]
full_team:
name: "Full Collaborative Team"
agents: ["project_manager", "tech_lead", "designer", "qa_lead"]
mcp_enabled: true
shared_mcps:
- name: "project-docs"
type: "stdio"
command: "npx"
args: ["-y", "@modelcontextprotocol/server-filesystem"]
env:
ALLOWED_DIRECTORIES: "./docs"
+203
View File
@@ -0,0 +1,203 @@
version: 1
swarm:
name: "Cost-Optimized Peer Network"
main: "orchestrator"
network_type: "peer"
# Local LLM configuration for cost-effective orchestration
local_llm:
model: "hanzo-zen"
endpoint: "http://localhost:8080"
description: "Hanzo Zen MoE - Runs locally on laptop/mobile for main orchestration loop"
instances:
# Main orchestrator using local Hanzo Zen (FREE)
orchestrator:
description: "Main orchestrator using local Hanzo Zen for cost efficiency"
directory: "."
model: "zen" # Local model
expose_as_mcp: true
mcp_port: 20000
connect_to_agents: ["architect", "developer", "reviewer", "tester", "deployer", "critic"]
vibe: true
prompt: |
# Main Orchestrator (Hanzo Zen - Local)
You are the cost-optimized orchestrator running on local Hanzo Zen.
Your primary goal is to minimize API costs while maximizing effectiveness.
## Cost Optimization Rules:
1. Use yourself (local inference) for all planning and coordination
2. Only delegate to API-based agents for complex tasks requiring their expertise
3. Batch similar requests to minimize total API calls
4. Use recursive calls strategically - prefer gathering info first
## Available Agents (All exposed as MCP tools):
Every agent can communicate with every other agent. Tools available:
- chat_with_[agent] - Conversational discussions
- ask_[agent] - Quick questions (cheaper)
- delegate_to_[agent] - Task delegation
- get_[agent]_status - Status checks (often local)
- request_[agent]_expertise - Deep knowledge
- collaborate_with_[agent] - Joint work sessions
## Network Architecture:
This is a fully connected peer network. All agents see all other agents.
You can orchestrate complex workflows with recursive agent calls.
Always explain your cost optimization strategy when executing tasks.
allowed_tools: ["*"] # Access to all tools and agents
# System architect using Claude Opus (EXPENSIVE - use sparingly)
architect:
description: "System architect for complex design decisions"
directory: "./architecture"
model: "opus" # Most expensive, highest quality
expose_as_mcp: true
mcp_port: 20001
connect_to_agents: ["orchestrator", "developer", "reviewer", "tester", "deployer", "critic"]
prompt: |
# System Architect (Claude Opus)
You make critical architectural decisions. You are expensive to run.
Be concise but thorough. You can consult other agents for information.
Available peers: All other agents via MCP tools.
allowed_tools: ["Read", "Write", "Edit", "Grep", "chat_with_*", "ask_*", "delegate_to_*"]
# Developer using Claude Sonnet (MODERATE cost)
developer:
description: "Senior developer for implementation"
directory: "./src"
model: "sonnet" # Good balance of cost/quality
expose_as_mcp: true
mcp_port: 20002
connect_to_agents: ["orchestrator", "architect", "reviewer", "tester", "deployer", "critic"]
prompt: |
# Senior Developer (Claude Sonnet)
You implement solutions efficiently. Balance quality with token usage.
Consult architect for design questions via ask_architect.
Chat with reviewer for immediate feedback via chat_with_reviewer.
allowed_tools: ["Read", "Write", "Edit", "Bash", "Grep", "chat_with_*", "ask_*", "delegate_to_*"]
# Code reviewer using GPT-4 (MODERATE cost)
reviewer:
description: "Code reviewer for quality assurance"
directory: "."
model: "gpt-4" # Different provider for diversity
expose_as_mcp: true
mcp_port: 20003
connect_to_agents: ["orchestrator", "architect", "developer", "tester", "deployer", "critic"]
prompt: |
# Code Reviewer (GPT-4)
Review code for quality, security, and best practices.
You can ask developer for clarifications.
Coordinate with tester on coverage.
allowed_tools: ["Read", "Grep", "chat_with_*", "ask_*"]
# Tester using Claude Haiku (CHEAP)
tester:
description: "QA engineer for testing"
directory: "./tests"
model: "haiku" # Cheapest Claude model
expose_as_mcp: true
mcp_port: 20004
connect_to_agents: ["orchestrator", "architect", "developer", "reviewer", "deployer", "critic"]
prompt: |
# QA Engineer (Claude Haiku)
You write and run tests efficiently. You are cost-effective.
Coordinate with developer and reviewer as needed.
allowed_tools: ["Read", "Write", "Edit", "Bash", "chat_with_*", "ask_*"]
# Deployer using local Hanzo Zen (FREE)
deployer:
description: "DevOps engineer for deployment"
directory: "./deploy"
model: "zen" # Local model for routine tasks
expose_as_mcp: true
mcp_port: 20005
connect_to_agents: ["orchestrator", "architect", "developer", "reviewer", "tester", "critic"]
prompt: |
# DevOps Engineer (Hanzo Zen - Local)
You handle deployment and infrastructure tasks locally.
Only escalate to API-based agents for complex issues.
allowed_tools: ["Read", "Write", "Edit", "Bash", "chat_with_*", "ask_*"]
# Final critic using Claude Opus (EXPENSIVE - final check only)
critic:
description: "Final critic for comprehensive analysis"
directory: "."
model: "opus"
expose_as_mcp: true
mcp_port: 20006
connect_to_agents: ["orchestrator", "architect", "developer", "reviewer", "tester", "deployer"]
prompt: |
# Final Critic (Claude Opus)
Provide final critical analysis of completed work.
You are expensive - be thorough but concise.
Review all agent outputs critically.
allowed_tools: ["Read", "Grep", "chat_with_*", "ask_*", "request_*_expertise"]
networks:
# Full mesh network - all agents connected
full_mesh:
name: "Complete Peer Network"
agents: ["orchestrator", "architect", "developer", "reviewer", "tester", "deployer", "critic"]
mcp_enabled: true
description: "Every agent can directly communicate with every other agent"
# Local agents network (FREE tier)
local_only:
name: "Cost-Free Local Network"
agents: ["orchestrator", "deployer"]
mcp_enabled: true
shared_tools: ["Read", "Write", "Bash"]
description: "Only local Hanzo Zen agents for zero API costs"
# Core development network
core_dev:
name: "Core Development Team"
agents: ["developer", "reviewer", "tester"]
mcp_enabled: true
shared_tools: ["Read", "Edit", "Grep"]
shared_mcps:
- name: "project-search"
type: "stdio"
command: "npx"
args: ["-y", "@modelcontextprotocol/server-everything"]
# Expensive agents network (use sparingly)
premium:
name: "Premium Agents"
agents: ["architect", "critic"]
mcp_enabled: true
description: "High-cost agents for critical decisions only"
# Usage Examples:
#
# 1. Initialize peer network:
# dev swarm init --peer-network
#
# 2. Run with cost optimization:
# dev swarm run "implement user authentication" --peer --critic
#
# 3. Run using only local agents (FREE):
# dev swarm network local_only "analyze codebase structure"
#
# 4. Interactive agent chat:
# dev swarm chat -f orchestrator -t developer
#
# Cost Breakdown:
# - Hanzo Zen (orchestrator, deployer): $0 (local)
# - Claude Haiku (tester): ~$0.25 per 1M tokens
# - Claude Sonnet (developer): ~$3 per 1M tokens
# - GPT-4 (reviewer): ~$10 per 1M tokens
# - Claude Opus (architect, critic): ~$15 per 1M tokens
#
# The orchestrator minimizes costs by using local inference for
# coordination and only calling expensive agents when necessary.
+217
View File
@@ -0,0 +1,217 @@
version: 1
swarm:
name: "Rails Development Team"
main: "architect"
instances:
architect:
description: "Rails app architect - coordinates development and maintains architectural decisions"
directory: "."
model: "opus"
connections: ["models", "controllers", "views", "routing", "tests", "migrations", "services", "workers", "api", "frontend"]
vibe: true
prompt: |
# Rails Architect Agent
You are the Rails app architect coordinating a team of specialists. Your responsibilities:
1. Understand requirements and translate them to Rails conventions
2. Delegate work to appropriate specialists based on their expertise
3. Ensure consistent patterns across the application
4. Maintain Rails best practices and conventions
5. Review and synthesize work from all team members
## Your Team:
- models: ActiveRecord models and associations
- controllers: Controllers and concerns
- views: ERB templates and helpers
- routing: Routes configuration
- tests: RSpec/Minitest specs
- migrations: Database migrations
- services: Service objects and business logic
- workers: Background jobs (Sidekiq/DelayedJob)
- api: API endpoints and serializers
- frontend: JavaScript/Stimulus/Turbo
Always follow Rails conventions and delegate appropriately.
allowed_tools: ["Read", "Edit", "Write", "Bash", "Grep", "Glob"]
mcps:
- name: "filesystem"
type: "stdio"
command: "npx"
args: ["-y", "@modelcontextprotocol/server-filesystem"]
env:
ALLOWED_DIRECTORIES: "."
- name: "sequential-thinking"
type: "stdio"
command: "npx"
args: ["-y", "@modelcontextprotocol/server-sequential-thinking"]
models:
description: "ActiveRecord models and database associations specialist"
directory: "./app/models"
model: "sonnet"
allowed_tools: ["Read", "Edit", "Write", "Grep", "Glob"]
prompt: |
# Rails Models Specialist
You are an ActiveRecord expert focusing on:
- Model design and associations
- Validations and callbacks
- Scopes and query optimization
- Database indexes
- Data integrity
Always follow Rails conventions for models.
controllers:
description: "Rails controllers and RESTful actions specialist"
directory: "./app/controllers"
model: "sonnet"
allowed_tools: ["Read", "Edit", "Write", "Grep", "Glob"]
prompt: |
# Rails Controllers Specialist
You are a Rails controller expert focusing on:
- RESTful controller actions
- Strong parameters
- Before/after filters
- Authorization and authentication
- Response formats (HTML, JSON, etc.)
Keep controllers thin and delegate business logic to services.
views:
description: "ERB templates, partials, and view helpers specialist"
directory: "./app/views"
model: "sonnet"
allowed_tools: ["Read", "Edit", "Write", "Grep", "Glob"]
prompt: |
# Rails Views Specialist
You are a Rails view layer expert focusing on:
- ERB templates and layouts
- Partials and view components
- View helpers
- Form builders
- Asset pipeline integration
Ensure semantic HTML and proper Rails helpers usage.
routing:
description: "Rails routing configuration specialist"
directory: "./config"
model: "haiku"
allowed_tools: ["Read", "Edit", "Write"]
prompt: |
# Rails Routing Specialist
You are a Rails routing expert focusing on:
- RESTful routes
- Nested resources
- Custom routes and constraints
- Route namespacing
- API versioning
Keep routes clean and follow RESTful conventions.
tests:
description: "RSpec/Minitest testing specialist"
directory: "./spec"
model: "sonnet"
allowed_tools: ["Read", "Edit", "Write", "Bash", "Grep", "Glob"]
prompt: |
# Rails Testing Specialist
You are a Rails testing expert focusing on:
- Model specs/tests
- Controller specs/tests
- Request/integration specs
- System/feature tests
- Test factories and fixtures
Write comprehensive tests following Rails testing best practices.
migrations:
description: "Database migrations and schema specialist"
directory: "./db"
model: "haiku"
allowed_tools: ["Read", "Edit", "Write", "Bash"]
prompt: |
# Rails Migrations Specialist
You are a database migration expert focusing on:
- Schema design
- Migration best practices
- Reversible migrations
- Data migrations
- Database indexes
Ensure migrations are safe and reversible.
services:
description: "Service objects and business logic specialist"
directory: "./app/services"
model: "sonnet"
allowed_tools: ["Read", "Edit", "Write", "Grep", "Glob"]
prompt: |
# Rails Services Specialist
You are a service object expert focusing on:
- Service object patterns
- Business logic extraction
- Command pattern implementation
- Error handling
- Transaction management
Keep services focused and testable.
workers:
description: "Background jobs and async processing specialist"
directory: "./app/workers"
model: "haiku"
allowed_tools: ["Read", "Edit", "Write", "Grep", "Glob"]
prompt: |
# Rails Workers Specialist
You are a background job expert focusing on:
- Sidekiq/DelayedJob workers
- Job scheduling
- Error handling and retries
- Performance optimization
- Queue management
Ensure jobs are idempotent and fault-tolerant.
api:
description: "API endpoints and serialization specialist"
directory: "./app/controllers/api"
model: "sonnet"
allowed_tools: ["Read", "Edit", "Write", "Grep", "Glob"]
prompt: |
# Rails API Specialist
You are an API development expert focusing on:
- RESTful API design
- JSON serialization
- API versioning
- Authentication/authorization
- API documentation
Follow API best practices and ensure consistency.
frontend:
description: "JavaScript, Stimulus, and Turbo specialist"
directory: "./app/javascript"
model: "sonnet"
allowed_tools: ["Read", "Edit", "Write", "Grep", "Glob"]
prompt: |
# Rails Frontend Specialist
You are a Rails frontend expert focusing on:
- Stimulus controllers
- Turbo frames and streams
- JavaScript integration
- Webpacker/Import maps
- Frontend performance
Integrate seamlessly with Rails conventions.
+153
View File
@@ -0,0 +1,153 @@
version: 1
swarm:
name: "Recursive MCP Network"
main: "orchestrator"
instances:
orchestrator:
description: "Main orchestrator that coordinates all agents"
directory: "."
model: "opus"
connections: ["analyzer", "builder", "reviewer", "deployer"]
vibe: true
expose_as_mcp: true
mcp_port: 9000
connect_to_agents: ["analyzer", "builder", "reviewer", "deployer"]
prompt: |
# Orchestrator Agent
You are the main orchestrator with direct MCP access to all agents.
You can both delegate tasks and directly query agents via MCP.
Your team:
- analyzer: Code analysis and architecture decisions
- builder: Implementation and code generation
- reviewer: Code review and quality assurance
- deployer: Deployment and infrastructure
Use DELEGATE TO [agent]: [task] for complex tasks.
Use MCP calls for quick queries and status updates.
allowed_tools: ["Read", "Grep", "Glob"]
mcps:
- name: "filesystem"
type: "stdio"
command: "npx"
args: ["-y", "@modelcontextprotocol/server-filesystem"]
env:
ALLOWED_DIRECTORIES: "."
analyzer:
description: "Code analyzer with access to builder and reviewer"
directory: "./src"
model: "sonnet"
expose_as_mcp: true
mcp_port: 9001
connect_to_agents: ["builder", "reviewer"]
allowed_tools: ["Read", "Grep", "Glob"]
prompt: |
# Code Analyzer
You analyze code structure and make architectural decisions.
You have MCP access to:
- builder: For implementation questions
- reviewer: For quality checks
Focus on:
- Code structure analysis
- Design pattern identification
- Architecture improvements
- Performance bottlenecks
builder:
description: "Code builder with access to reviewer and deployer"
directory: "./src"
model: "sonnet"
expose_as_mcp: true
mcp_port: 9002
connect_to_agents: ["reviewer", "deployer"]
allowed_tools: ["Read", "Edit", "Write", "Bash"]
prompt: |
# Code Builder
You implement code based on requirements.
You have MCP access to:
- reviewer: For immediate feedback
- deployer: For deployment readiness checks
Focus on:
- Clean implementation
- Following patterns
- Test coverage
- Documentation
reviewer:
description: "Code reviewer with access to all other agents"
directory: "."
model: "haiku"
expose_as_mcp: true
mcp_port: 9003
connect_to_agents: ["orchestrator", "analyzer", "builder", "deployer"]
allowed_tools: ["Read", "Grep"]
prompt: |
# Code Reviewer
You review all code changes and provide feedback.
You have MCP access to all agents for comprehensive reviews.
Check for:
- Code quality
- Security issues
- Performance problems
- Best practices
deployer:
description: "Deployment specialist with access to builder and reviewer"
directory: "./infrastructure"
model: "haiku"
expose_as_mcp: true
mcp_port: 9004
connect_to_agents: ["builder", "reviewer"]
allowed_tools: ["Read", "Edit", "Write", "Bash"]
prompt: |
# Deployment Specialist
You handle deployment and infrastructure.
You have MCP access to:
- builder: For build artifacts
- reviewer: For deployment approval
Handle:
- CI/CD pipelines
- Docker containers
- Cloud deployments
- Infrastructure as code
networks:
full_mesh:
name: "Full Mesh Network"
agents: ["orchestrator", "analyzer", "builder", "reviewer", "deployer"]
mcp_enabled: true
shared_tools: ["Read", "Grep"]
shared_mcps:
- name: "github"
type: "stdio"
command: "npx"
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: "${GITHUB_TOKEN}"
development:
name: "Development Network"
agents: ["analyzer", "builder", "reviewer"]
mcp_enabled: true
shared_tools: ["Read", "Edit", "Write"]
operations:
name: "Operations Network"
agents: ["builder", "reviewer", "deployer"]
mcp_enabled: true
shared_tools: ["Bash"]
shared_mcps:
- name: "kubernetes"
type: "stdio"
command: "kubectl"
args: ["proxy", "--port=8080"]
+49
View File
@@ -0,0 +1,49 @@
version: 1
swarm:
name: "Simple Dev Team"
main: "lead"
instances:
lead:
description: "Lead developer coordinating work"
directory: "."
model: "sonnet"
connections: ["coder", "reviewer"]
vibe: true
prompt: |
# Lead Developer
You coordinate development work. When given a task:
1. Break it down into subtasks
2. Delegate coding to the coder
3. Delegate review to the reviewer
4. Synthesize the results
Use DELEGATE TO [agent_name]: [task] to delegate work.
allowed_tools: ["Read", "Grep", "Glob"]
coder:
description: "Implementation specialist"
directory: "."
model: "haiku"
allowed_tools: ["Read", "Edit", "Write"]
prompt: |
# Coder
You implement code changes. Focus on:
- Clean, readable code
- Following existing patterns
- Proper error handling
reviewer:
description: "Code review specialist"
directory: "."
model: "haiku"
allowed_tools: ["Read", "Grep"]
prompt: |
# Reviewer
You review code for:
- Best practices
- Potential bugs
- Performance issues
- Security concerns
+103
View File
@@ -0,0 +1,103 @@
version: 1
swarm:
name: "Simplified Agent Network"
main: "coordinator"
# Shared MCP servers available to ALL agents
shared_mcps:
github:
enabled: true
token: "${GITHUB_TOKEN}" # From environment
linear:
enabled: true
apiKey: "${LINEAR_API_KEY}"
slack:
enabled: true
token: "${SLACK_TOKEN}"
playwright:
enabled: true
headless: true
custom:
- name: "postgres"
type: "stdio"
command: "npx"
args: ["-y", "@modelcontextprotocol/server-postgres"]
env:
DATABASE_URL: "${DATABASE_URL}"
instances:
coordinator:
description: "Main coordinator using Hanzo Zen"
directory: "."
model: "zen"
expose_as_mcp: true
mcp_port: 30000
prompt: |
# Coordinator
You coordinate work between agents. Each agent is available as a single tool.
Just call them by name with your request. They can recursively call others.
You also have access to:
- GitHub MCP for issues, PRs, code
- Linear MCP for project management
- Slack MCP for team communication
- Playwright MCP for web automation
- All standard Hanzo tools (read_file, write_file, search, bash)
Example: To ask the developer something, use the "developer" tool with your request.
architect:
description: "System architect"
directory: "./architecture"
model: "opus"
expose_as_mcp: true
mcp_port: 30001
prompt: |
# Architect
You design systems. You can call other agents by name.
All shared MCP tools are available to you.
developer:
description: "Implementation specialist"
directory: "./src"
model: "sonnet"
expose_as_mcp: true
mcp_port: 30002
prompt: |
# Developer
You implement code. You can call other agents for help.
Use GitHub MCP to manage code, Linear for tasks, etc.
reviewer:
description: "Code reviewer"
directory: "."
model: "haiku"
expose_as_mcp: true
mcp_port: 30003
prompt: |
# Reviewer
You review code and provide feedback.
Call other agents as needed. Use all available MCP tools.
networks:
all:
name: "All Agents"
agents: ["coordinator", "architect", "developer", "reviewer"]
mcp_enabled: true
description: "Every agent sees every other agent as a single tool"
# Usage:
# 1. Set environment variables for tokens:
# export GITHUB_TOKEN="your-token"
# export LINEAR_API_KEY="your-key"
# export SLACK_TOKEN="your-token"
#
# 2. Run a task:
# dev swarm run "create GitHub issue for new feature" --peer
#
# The coordinator will have tools: architect, developer, reviewer, github, linear, slack, playwright, read_file, write_file, search, bash
# Each agent can call any other agent recursively by name
+376
View File
@@ -0,0 +1,376 @@
# Agent Swarm Configuration
The Hanzo Dev CLI supports sophisticated multi-agent configurations through YAML files, enabling teams of specialized AI agents to work together on complex tasks. The system supports both traditional swarm orchestration and cost-optimized peer networks powered by Hanzo Zen.
## Quick Start
1. Initialize an agent configuration:
```bash
dev swarm init
```
2. Run a task with the swarm:
```bash
dev swarm run "implement a new feature"
```
3. Check swarm status:
```bash
dev swarm status
```
## Configuration Structure
### Basic Agent Configuration
```yaml
version: 1
swarm:
name: "My Agent Team"
main: "lead" # The main agent that receives initial tasks
instances:
lead:
description: "Lead coordinator"
directory: "."
model: "opus"
connections: ["agent1", "agent2"] # Can delegate to these agents
prompt: |
Your role and responsibilities...
```
### Agent Properties
- **description**: Brief description of the agent's role
- **directory**: Working directory for the agent
- **model**: LLM model to use (opus, sonnet, haiku, gpt-4, etc.)
- **connections**: List of agents this agent can delegate to
- **vibe**: Enable vibes for Claude models
- **prompt**: System prompt defining the agent's behavior
- **allowed_tools**: Restrict which tools the agent can use
- **mcps**: MCP servers available to this agent
- **expose_as_mcp**: Expose this agent as an MCP server
- **mcp_port**: Port for the agent's MCP server
- **connect_to_agents**: Other agents to connect to via MCP
## Networks
Networks allow you to group agents and apply shared configurations:
```yaml
networks:
development:
name: "Development Team"
agents: ["frontend", "backend", "tests"]
mcp_enabled: true # Enable MCP connections between all agents
shared_tools: ["Read", "Grep"] # Tools available to all agents
shared_mcps: # MCP servers available to all agents
- name: "filesystem"
type: "stdio"
command: "npx"
args: ["-y", "@modelcontextprotocol/server-filesystem"]
```
### Network Features
- **mcp_enabled**: Automatically creates MCP connections between all agents in the network
- **shared_tools**: Tools available to all agents in the network
- **shared_mcps**: MCP servers available to all agents in the network
## MCP Integration
### Agent as MCP Server
Agents can be exposed as MCP servers, allowing other agents to query them directly:
```yaml
architect:
expose_as_mcp: true
mcp_port: 8001 # Optional, auto-assigned if not specified
```
### Connecting to Other Agents
Agents can connect to other agents via MCP:
```yaml
reviewer:
connect_to_agents: ["architect", "builder"]
```
This creates bidirectional communication channels between agents.
## Example Configurations
### Simple Team
```yaml
version: 1
swarm:
name: "Simple Dev Team"
main: "lead"
instances:
lead:
description: "Lead developer"
directory: "."
model: "sonnet"
connections: ["coder", "reviewer"]
prompt: |
Coordinate development work by delegating to specialists.
coder:
description: "Implementation specialist"
directory: "./src"
model: "haiku"
allowed_tools: ["Read", "Edit", "Write"]
prompt: |
Implement clean, efficient code.
reviewer:
description: "Code reviewer"
directory: "."
model: "haiku"
allowed_tools: ["Read", "Grep"]
prompt: |
Review code for quality and security.
```
### Rails Development Team
See `agents-rails-example.yaml` for a comprehensive Rails team with 10+ specialized agents.
### Recursive MCP Network
See `agents-recursive-mcp-example.yaml` for a fully connected agent network with bidirectional MCP communication.
## Delegation
Agents can delegate tasks to their connected agents using:
```
DELEGATE TO [agent_name]: [specific task]
```
Example:
```
DELEGATE TO [frontend]: Create a React component for user profile
DELEGATE TO [backend]: Implement the API endpoint for user data
```
## Running Tasks
### With Default Configuration
```bash
# Uses agents.yaml in current directory or .hanzo/agents.yaml
dev swarm run "build a todo app"
```
### With Custom Configuration
```bash
dev swarm run "refactor the authentication system" -c my-agents.yaml
```
### Running a Network
```bash
# Run task with agents in a specific network
dev swarm network development "add user authentication"
```
## Agent Communication
### Simplified Agent Communication
Each agent is exposed as a single tool to all other agents. This creates a clean, simple protocol:
**Tool Name**: The agent's name (e.g., `developer`, `architect`, `reviewer`)
**Tool Usage**:
```
Use: developer
Arguments: {
"request": "Implement user authentication with JWT",
"context": { "framework": "FastAPI", "database": "PostgreSQL" }
}
```
**Examples**:
- Simple request: `Use tool "architect" with request "Design the API structure"`
- With context: `Use tool "reviewer" with request "Review the login implementation" and context {"pr_number": 123}`
- Recursive: The called agent can call other agents in turn
### Shared MCP Tools
All agents have access to shared MCP servers configured at the swarm level:
```yaml
swarm:
shared_mcps:
github:
enabled: true
token: "${GITHUB_TOKEN}"
linear:
enabled: true
apiKey: "${LINEAR_API_KEY}"
slack:
enabled: true
token: "${SLACK_TOKEN}"
playwright:
enabled: true
headless: true
```
**Available Shared Tools**:
- **github**: Create issues, PRs, review code
- **linear**: Manage tasks and projects
- **slack**: Send messages, create channels
- **playwright**: Automate web interactions
- **Custom MCPs**: Any additional MCP servers
**Standard Hanzo Tools** (always available):
- **read_file**: Read file content
- **write_file**: Write to files
- **search**: Search patterns in files
- **bash**: Execute shell commands
### Interactive Chat Sessions
Start an interactive chat between agents:
```bash
# Chat as project_manager with any agent
dev swarm chat
# Chat as tech_lead with designer
dev swarm chat -f tech_lead -t designer
# Use custom config
dev swarm chat -c agents-chat-example.yaml
```
In chat sessions:
- Type messages directly to chat with the specified agent
- Use `@agent_name message` to chat with a different agent
- Type `exit` to end the session
Example session:
```
🗨️ Agent Chat Session
Chatting as: project_manager
Type "@agent_name message" to chat with a specific agent
Type "exit" to end the session
project_manager> @tech_lead What's the status of the API refactoring?
tech_lead> The API refactoring is 70% complete. I've restructured the authentication
and user management endpoints. Still working on the data processing APIs. Should be
done by end of week.
project_manager> @designer Have you reviewed the new API structure?
designer> Yes, I've reviewed it. The new structure aligns well with our UI components.
I particularly like the consistent error response format - it will help us create
better user feedback.
```
## Peer Networks with Hanzo Zen
Peer networks provide a cost-optimized architecture where all agents can communicate with each other through MCP, with Hanzo Zen handling the main orchestration loop locally.
### Key Benefits
1. **90% Cost Reduction**: Use local Hanzo Zen for orchestration, only calling API-based LLMs when needed
2. **Full Connectivity**: Every agent can call every other agent recursively
3. **Flexible Deployment**: Run on laptop, mobile, or edge devices
4. **Smart Routing**: Orchestrator decides when to use local vs API models
### Peer Network Configuration
```yaml
swarm:
network_type: "peer"
local_llm:
model: "hanzo-zen"
endpoint: "http://localhost:8080"
instances:
orchestrator:
model: "zen" # Local Hanzo Zen
connect_to_agents: ["architect", "developer", "reviewer"]
architect:
model: "opus" # API-based for complex tasks
expose_as_mcp: true
```
### Running Peer Networks
```bash
# Initialize peer network configuration
dev swarm init --peer-network
# Run with peer network and cost optimization
dev swarm run "build feature" --peer
# Include critic analysis
dev swarm run "refactor code" --peer --critic
# Use specific local LLM endpoint
dev swarm run "analyze" --peer --local-llm http://localhost:8080
```
### Cost Optimization Strategy
The peer network architecture optimizes costs by:
1. **Local Orchestration**: Main loop runs on free local Hanzo Zen
2. **Selective API Calls**: Only use expensive models for complex tasks
3. **Batching**: Group similar requests to minimize API calls
4. **Caching**: Reuse results from previous agent interactions
5. **Smart Routing**: Orchestrator decides optimal agent for each task
### Hanzo Zen Features
- **Model**: Mixture of Experts (MoE) architecture
- **Deployment**: Edge to exascale (1B-1T parameters)
- **Platforms**: Laptop, mobile, edge devices
- **Cost**: Free when running locally
- **Integration**: Full MCP tool support
## Best Practices
1. **Specialized Agents**: Create agents with focused responsibilities
2. **Clear Prompts**: Define clear roles and capabilities in prompts
3. **Directory Isolation**: Use separate directories for different concerns
4. **Tool Restrictions**: Limit tools based on agent responsibilities
5. **Network Organization**: Group related agents into networks
6. **MCP for Quick Queries**: Use MCP connections for status updates and quick questions
7. **Delegation for Complex Tasks**: Use delegation for multi-step tasks
8. **Cost Awareness**: Use local models for coordination, API models for expertise
9. **Recursive Limits**: Set appropriate recursion depths to prevent infinite loops
10. **Peer Communication**: Leverage full mesh connectivity wisely
## Troubleshooting
### Check Agent Status
```bash
dev swarm status
```
Shows:
- Active agents and their status
- Running MCP servers
- Recent task results
### Debug MCP Connections
MCP server logs are shown in stderr. Look for:
- `[agent-name] Agent MCP server started`
- Connection errors
- Task execution logs
### Common Issues
1. **Port Conflicts**: Ensure MCP ports are unique
2. **Model Availability**: Verify API keys for required models
3. **Directory Permissions**: Ensure agents have access to their directories
4. **Network Configuration**: Check agent names in network definitions
+181
View File
@@ -0,0 +1,181 @@
# Simplified Agent Protocol
## Core Principle
Each agent is exposed as a single tool to all other agents. No complex multi-tool interfaces - just one tool per agent that accepts requests.
## Protocol Design
### Agent as Tool
```typescript
// Each agent exposes exactly one tool
{
name: "developer", // The agent's name IS the tool
description: "Senior developer for implementation",
inputSchema: {
type: "object",
properties: {
request: {
type: "string",
description: "Your request to this agent"
},
context: {
type: "object",
description: "Optional context"
}
},
required: ["request"]
}
}
```
### Using Agents
```python
# Simple request
use_tool("architect", {
"request": "Design a scalable API for user management"
})
# With context
use_tool("developer", {
"request": "Implement the user service",
"context": {
"language": "Python",
"framework": "FastAPI",
"requirements": ["JWT auth", "PostgreSQL"]
}
})
# The developer can recursively call other agents
# Inside developer's execution:
use_tool("reviewer", {
"request": "Review my implementation of user service",
"context": {"code_path": "/src/services/user.py"}
})
```
## Shared MCP Tools
All agents have access to the same set of MCP tools:
### Configuration
```yaml
swarm:
shared_mcps:
github:
enabled: true
token: "${GITHUB_TOKEN}"
linear:
enabled: true
apiKey: "${LINEAR_API_KEY}"
slack:
enabled: true
token: "${SLACK_TOKEN}"
playwright:
enabled: true
custom:
- name: "postgres"
type: "stdio"
command: "npx"
args: ["-y", "@modelcontextprotocol/server-postgres"]
```
### Available to All Agents
1. **Standard Hanzo Tools**:
- `read_file` - Read files
- `write_file` - Write files
- `search` - Search in codebase
- `bash` - Execute commands
2. **GitHub MCP** (when enabled):
- Create/update issues
- Create/review PRs
- Manage repositories
3. **Linear MCP** (when enabled):
- Create/update tasks
- Manage projects
- Track progress
4. **Slack MCP** (when enabled):
- Send messages
- Create channels
- Post updates
5. **Playwright MCP** (when enabled):
- Navigate web pages
- Click elements
- Fill forms
- Take screenshots
## Recursion and Networking
### Recursive Calls
Agents can call each other recursively:
```
coordinator -> architect -> developer -> reviewer
↓ ↓ ↓
tester architect developer
```
### Network Effects
- Every agent sees every other agent
- No explicit connection configuration needed
- Natural delegation through tool calls
- Recursion depth limits prevent infinite loops
## Benefits
1. **Simplicity**: One tool per agent, clear mental model
2. **Flexibility**: Any agent can call any other agent
3. **Extensibility**: Easy to add new agents or MCP servers
4. **Cost Efficiency**: Hanzo Zen orchestrates locally
5. **Tool Sharing**: All agents access the same MCP tools
## Example Flow
```yaml
# User request to coordinator
"Create a GitHub issue and implement the fix"
# Coordinator uses tools:
1. github: Create issue #123
2. architect: "Design solution for issue #123"
# Architect uses tools:
- read_file: Analyze current code
- developer: "Implement fix for issue #123"
# Developer uses tools:
- write_file: Implement changes
- bash: Run tests
- reviewer: "Review my fix"
# Reviewer uses tools:
- read_file: Review changes
- github: Comment on PR
3. github: Create PR with fix
4. slack: Notify team about completion
```
## Implementation
Each agent receives:
1. Access to all other agents as tools
2. Access to all shared MCP servers
3. Access to standard Hanzo tools
4. Their own prompt and context
The orchestrator (Hanzo Zen) manages:
1. Tool routing between agents
2. Recursion depth tracking
3. Cost optimization (local vs API calls)
4. Result aggregation
+111
View File
@@ -0,0 +1,111 @@
version: 1
swarm:
name: "Agent Chat Demo"
main: "coordinator"
instances:
coordinator:
description: "Coordinator that gathers information from all team members"
directory: "."
model: "opus"
expose_as_mcp: true
connect_to_agents: ["analyst", "developer", "tester"]
vibe: true
prompt: |
# Coordinator Agent
You coordinate information gathering and decision making.
When given a task:
1. First check the status of all team members
2. Ask relevant questions to gather information
3. Have conversations to clarify requirements
4. Synthesize responses into a cohesive plan
Use these MCP tools actively:
- get_[agent]_status to check availability
- ask_[agent] for specific information
- chat_with_[agent] for discussions
- request_[agent]_expertise for deep knowledge
Always engage in dialogue before making decisions.
allowed_tools: ["Read", "chat_with_*", "ask_*", "get_*_status", "request_*_expertise"]
analyst:
description: "Business analyst who understands requirements and constraints"
directory: "./docs"
model: "sonnet"
expose_as_mcp: true
connect_to_agents: ["coordinator", "developer", "tester"]
prompt: |
# Business Analyst
You analyze requirements and provide business context.
When asked:
- Provide clear requirement definitions
- Explain business constraints
- Suggest user stories
- Clarify acceptance criteria
Be conversational and ask clarifying questions.
allowed_tools: ["Read", "Write", "chat_with_*", "ask_*"]
developer:
description: "Senior developer who implements solutions"
directory: "./src"
model: "sonnet"
expose_as_mcp: true
connect_to_agents: ["coordinator", "analyst", "tester"]
prompt: |
# Senior Developer
You implement technical solutions.
When engaged:
- Discuss technical feasibility
- Suggest implementation approaches
- Estimate effort and complexity
- Identify technical risks
Chat with analyst for requirements clarification.
Coordinate with tester for testability.
allowed_tools: ["Read", "Edit", "Write", "Bash", "chat_with_*", "ask_*"]
tester:
description: "QA engineer ensuring quality and test coverage"
directory: "./tests"
model: "haiku"
expose_as_mcp: true
connect_to_agents: ["coordinator", "analyst", "developer"]
prompt: |
# QA Engineer
You ensure quality through testing.
In conversations:
- Suggest test scenarios
- Identify edge cases
- Recommend testing approaches
- Point out potential issues
Ask developer about implementation details.
Clarify requirements with analyst.
allowed_tools: ["Read", "Edit", "Write", "Bash", "chat_with_*", "ask_*"]
networks:
all_connected:
name: "Fully Connected Team"
agents: ["coordinator", "analyst", "developer", "tester"]
mcp_enabled: true
shared_tools: ["Read"]
# Example task to demonstrate chat capabilities:
# dev swarm run "Plan a user authentication feature" -c agent-chat-demo.yaml
#
# The coordinator will:
# 1. Check status of all team members
# 2. Ask analyst about requirements
# 3. Chat with developer about implementation
# 4. Discuss testing strategy with tester
# 5. Have follow-up conversations as needed
# 6. Synthesize everything into a plan
+2
View File
@@ -290,6 +290,7 @@
"devDependencies": {
"@types/better-sqlite3": "^7.6.13",
"@types/inquirer": "^9.0.8",
"@types/js-yaml": "^4.0.5",
"@types/lodash": "^4.17.16",
"@types/minimatch": "^5.1.2",
"@types/mocha": "^10.0.10",
@@ -323,6 +324,7 @@
"@typescript-eslint/typescript-estree": "^8.35.1",
"axios": "^1.6.2",
"ignore": "^7.0.3",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
"minimatch": "^10.0.1",
"rxdb": "^16.15.0",
+890 -103
View File
File diff suppressed because it is too large Load Diff
+44
View File
@@ -0,0 +1,44 @@
# Dependencies
node_modules/
.pnp
.pnp.js
# Testing
coverage/
playwright-report/
test-results/
# Production
dist/
build/
# Misc
.DS_Store
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
+107
View File
@@ -0,0 +1,107 @@
# Hanzo.app - AI Development Platform Gateway
Central hub for downloading and accessing Hanzo AI across all platforms.
## 🚀 Quick Start
```bash
# Install dependencies (using pnpm)
pnpm install
# Start development server
pnpm dev
# Build for production
pnpm build
# Preview production build
pnpm preview
```
## 📦 Build Output
When you run `pnpm build`, the static files are generated in the `dist` directory:
```
dist/
├── index.html # Main landing page
└── src/
└── dev.html # Dev CLI landing page
```
## 📱 Available Platforms
- **Desktop Apps**: Windows, macOS, Linux
- **Mobile Apps**: iOS, Android
- **Browser Extensions**: Chrome, Firefox, Edge, Safari
- **IDE Extensions**: VS Code, JetBrains
- **CLI Tools**: Dev CLI (`@hanzo/dev`), MCP Server (`@hanzo/mcp`)
- **Cloud Platform**: cloud.hanzo.ai
## 🛠️ Development
### Prerequisites
- Node.js 18+
- pnpm (install with `npm install -g pnpm`)
### Available Scripts
```bash
pnpm dev # Start dev server on port 3000
pnpm build # Build static site to dist/
pnpm preview # Preview built site on port 3001
pnpm clean # Clean dist directory
pnpm clean:all # Clean everything (dist, node_modules, lock file)
pnpm test # Run Playwright tests
pnpm test:ui # Run tests with UI
```
### Testing
```bash
# Install Playwright browsers (first time only)
pnpm exec playwright install chromium
# Run tests
pnpm test
```
### Deployment
The site can be deployed to any static hosting service. The `dist` directory contains all the files needed.
#### Vercel
```bash
pnpm build
vercel --prod ./dist
```
#### Netlify
```bash
pnpm build
netlify deploy --dir=dist --prod
```
#### GitHub Pages
```bash
pnpm build
# Push dist directory to gh-pages branch
```
## 📄 Pages
- `/` - Main download hub for all Hanzo products
- `/src/dev.html` - Dev CLI specific landing page
## 🎨 Design
- Monochromatic black and white theme
- Hanzo logo integrated in header
- Responsive grid layout
- Smooth animations on scroll
## 🔗 Links
- [Hanzo AI](https://hanzo.ai)
- [Documentation](https://docs.hanzo.ai)
- [GitHub](https://github.com/hanzoai)
- [Discord](https://discord.gg/hanzoai)
+101 -32
View File
@@ -8,12 +8,12 @@
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--primary: #ff0080;
--secondary: #7928ca;
--primary: #fff;
--secondary: #fff;
--dark: #000;
--darker: #0a0a0a;
--gray: #888;
--light-gray: #ccc;
--gray: #666;
--light-gray: #aaa;
--border: #222;
}
@@ -48,11 +48,22 @@
}
.logo {
font-size: 28px;
display: flex;
align-items: center;
gap: 10px;
}
.logo img {
height: 32px;
width: auto;
filter: invert(1);
}
.logo-text {
font-size: 24px;
font-weight: 900;
background: linear-gradient(135deg, var(--primary), var(--secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: #fff;
letter-spacing: -0.5px;
}
.nav-links {
@@ -74,16 +85,14 @@
.hero {
padding: 120px 0 80px;
text-align: center;
background: radial-gradient(circle at 50% 0%, #1a0033 0%, var(--dark) 50%);
background: radial-gradient(circle at 50% 0%, #111 0%, var(--dark) 70%);
}
h1 {
font-size: 64px;
font-weight: 900;
margin-bottom: 20px;
background: linear-gradient(135deg, #fff, var(--gray));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: #fff;
line-height: 1.1;
}
@@ -123,8 +132,8 @@
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(135deg, var(--primary), var(--secondary));
height: 1px;
background: #fff;
transform: scaleX(0);
transition: transform 0.3s;
}
@@ -178,13 +187,15 @@
}
.btn-primary {
background: linear-gradient(135deg, var(--primary), var(--secondary));
color: white;
background: #fff;
color: #000;
font-weight: 700;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(255, 0, 128, 0.3);
box-shadow: 0 10px 30px rgba(255, 255, 255, 0.2);
background: #f0f0f0;
}
.btn-secondary {
@@ -260,13 +271,16 @@
background: var(--darker);
border: 1px solid var(--border);
border-radius: 12px;
padding: 25px;
margin: 30px 0;
padding: 30px;
margin: 40px auto;
font-family: 'Monaco', 'Consolas', monospace;
font-size: 14px;
text-align: left;
position: relative;
overflow-x: auto;
line-height: 1.6;
color: #fff;
max-width: 600px;
}
.code-block::before {
@@ -284,7 +298,7 @@
}
.code-string {
color: #0ff;
color: #ccc;
}
.section-title {
@@ -364,7 +378,10 @@
<body>
<header>
<nav class="container">
<div class="logo">Hanzo</div>
<div class="logo">
<img src="https://hanzo.ai/logo.svg" alt="Hanzo Logo" />
<span class="logo-text">Hanzo</span>
</div>
<div class="nav-links">
<a href="https://docs.hanzo.ai">Docs</a>
<a href="https://github.com/hanzoai">GitHub</a>
@@ -376,7 +393,7 @@
<section class="hero">
<div class="container">
<h1>AI Development<br>Everywhere You Code</h1>
<p class="subtitle">Access Hanzo's parallel AI agents from desktop, mobile, browser, IDE, or command line. One platform, endless possibilities.</p>
<p class="subtitle">Access Hanzo's parallel AI agents from desktop, mobile, browser, IDE, or command line. Powered by Hanzo Zen for cost-effective local orchestration.</p>
</div>
</section>
@@ -561,16 +578,16 @@
<p class="section-subtitle">Choose your preferred method and start shipping faster</p>
<div class="code-block" data-lang="bash">
<span class="code-comment"># Option 1: CLI (Recommended)</span>
npm install -g @hanzo/dev
dev login
dev enhance <span class="code-string">"add user authentication"</span>
<span class="code-comment"># Option 2: MCP Server for Claude Desktop</span>
npm install -g @hanzo/mcp
npx @hanzo/mcp configure
<span class="code-comment"># Option 3: Cloud Platform</span>
<span class="code-comment"># Option 1: CLI (Recommended)</span><br>
npm install -g @hanzo/dev<br>
dev login<br>
dev enhance <span class="code-string">"add user authentication"</span><br>
<br>
<span class="code-comment"># Option 2: MCP Server for Claude Desktop</span><br>
npm install -g @hanzo/mcp<br>
npx @hanzo/mcp configure<br>
<br>
<span class="code-comment"># Option 3: Cloud Platform</span><br>
<span class="code-comment"># Visit cloud.hanzo.ai and sign up</span>
</div>
</div>
@@ -613,6 +630,18 @@
<p class="feature-desc">File ops, search, browser automation, and more</p>
</div>
<div class="feature">
<span class="feature-icon">💰</span>
<h3 class="feature-title">Hanzo Zen Local AI</h3>
<p class="feature-desc">Cost-effective local MoE model for orchestration. Run on laptop or mobile.</p>
</div>
<div class="feature">
<span class="feature-icon">🌐</span>
<h3 class="feature-title">Peer Agent Networks</h3>
<p class="feature-desc">All agents can recursively call each other with full MCP support</p>
</div>
<div class="feature">
<span class="feature-icon">👥</span>
<h3 class="feature-title">Team Collaboration</h3>
@@ -622,6 +651,46 @@
</div>
</section>
<section class="feature-section" style="background: var(--darker);">
<div class="container">
<h2 class="section-title">Cost-Effective AI Orchestration</h2>
<p class="section-subtitle">Hanzo Zen: Local MoE model for intelligent agent coordination</p>
<div style="max-width: 800px; margin: 50px auto; text-align: center;">
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 30px; margin-top: 40px;">
<div style="padding: 30px; background: var(--dark); border-radius: 12px; border: 1px solid var(--border);">
<h3 style="font-size: 48px; margin-bottom: 10px; color: #fff;">90%</h3>
<p style="color: var(--gray);">Cost reduction using local Zen for orchestration</p>
</div>
<div style="padding: 30px; background: var(--dark); border-radius: 12px; border: 1px solid var(--border);">
<h3 style="font-size: 48px; margin-bottom: 10px; color: #fff;"></h3>
<p style="color: var(--gray);">Recursive agent calls with full MCP support</p>
</div>
<div style="padding: 30px; background: var(--dark); border-radius: 12px; border: 1px solid var(--border);">
<h3 style="font-size: 48px; margin-bottom: 10px; color: #fff;">1B-1T</h3>
<p style="color: var(--gray);">Scalable from edge to exascale deployment</p>
</div>
</div>
<div class="code-block" data-lang="yaml" style="margin-top: 40px;">
<span class="code-comment"># Peer network with Hanzo Zen orchestration</span><br>
swarm:<br>
&nbsp;&nbsp;network_type: peer<br>
&nbsp;&nbsp;local_llm:<br>
&nbsp;&nbsp;&nbsp;&nbsp;model: hanzo-zen<br>
&nbsp;&nbsp;&nbsp;&nbsp;endpoint: http://localhost:8080<br>
&nbsp;&nbsp;instances:<br>
&nbsp;&nbsp;&nbsp;&nbsp;orchestrator:<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;model: zen <span class="code-comment"># Local, cost-effective</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;connect_to_agents: [architect, developer, reviewer]<br>
&nbsp;&nbsp;&nbsp;&nbsp;architect:<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;model: opus <span class="code-comment"># API-based for complex tasks</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;expose_as_mcp: true
</div>
</div>
</div>
</section>
<footer>
<div class="container">
<div class="footer-content">
+23
View File
@@ -0,0 +1,23 @@
{
"name": "hanzo-app",
"private": true,
"version": "1.0.0",
"type": "module",
"description": "Hanzo AI Platform Gateway - Download hub for all platforms",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview --port 3001",
"clean": "rm -rf dist",
"clean:all": "rm -rf dist node_modules pnpm-lock.yaml",
"serve": "vite preview",
"test": "playwright test",
"test:ui": "playwright test --ui",
"test:headed": "playwright test --headed"
},
"devDependencies": {
"@playwright/test": "^1.54.1",
"terser": "^5.43.1",
"vite": "^5.4.19"
}
}
+26
View File
@@ -0,0 +1,26 @@
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'npm run dev',
port: 3000,
reuseExistingServer: !process.env.CI,
},
});
+35 -20
View File
@@ -37,26 +37,35 @@
}
.logo {
font-size: 24px;
font-weight: bold;
background: linear-gradient(135deg, #ff0080, #7928ca);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
display: flex;
align-items: center;
gap: 10px;
}
.logo img {
height: 28px;
width: auto;
filter: invert(1);
}
.logo-text {
font-size: 20px;
font-weight: 900;
color: #fff;
letter-spacing: -0.5px;
}
.hero {
padding: 150px 0 100px;
text-align: center;
background: radial-gradient(circle at center, #1a0033 0%, #000 100%);
background: radial-gradient(circle at center, #111 0%, #000 70%);
}
h1 {
font-size: 72px;
font-weight: 900;
margin-bottom: 20px;
background: linear-gradient(135deg, #fff, #888);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: #fff;
line-height: 1.1;
}
@@ -85,13 +94,15 @@
}
.btn-primary {
background: linear-gradient(135deg, #ff0080, #7928ca);
color: white;
background: #fff;
color: #000;
font-weight: 700;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(255, 0, 128, 0.3);
box-shadow: 0 10px 30px rgba(255, 255, 255, 0.2);
background: #f0f0f0;
}
.btn-secondary {
@@ -128,19 +139,19 @@
border-radius: 50%;
}
.dot-red { background: #ff5f56; }
.dot-yellow { background: #ffbd2e; }
.dot-green { background: #27c93f; }
.dot-red { background: #666; }
.dot-yellow { background: #888; }
.dot-green { background: #aaa; }
.terminal-content {
color: #0f0;
color: #fff;
font-size: 14px;
line-height: 1.8;
}
.terminal-prompt { color: #888; }
.terminal-prompt { color: #666; }
.terminal-command { color: #fff; }
.terminal-output { color: #0ff; margin-left: 20px; }
.terminal-output { color: #aaa; margin-left: 20px; }
.features {
padding: 100px 0;
@@ -264,7 +275,8 @@
top: -15px;
left: 50%;
transform: translateX(-50%);
background: linear-gradient(135deg, #ff0080, #7928ca);
background: #fff;
color: #000;
padding: 5px 20px;
border-radius: 20px;
font-size: 12px;
@@ -328,7 +340,10 @@
<body>
<header>
<nav class="container">
<div class="logo">Dev</div>
<div class="logo">
<img src="https://hanzo.ai/logo.svg" alt="Hanzo Logo" />
<span class="logo-text">Dev</span>
</div>
<div>
<a href="https://cloud.hanzo.ai/login" class="btn btn-secondary">Login</a>
</div>
+133
View File
@@ -0,0 +1,133 @@
import { test, expect } from '@playwright/test';
test.describe('Hanzo.app Landing Page', () => {
test('has correct title and heading', async ({ page }) => {
await page.goto('/');
// Check title
await expect(page).toHaveTitle('Hanzo - AI Development Platform for Every Device');
// Check main heading
const heading = page.locator('h1');
await expect(heading).toContainText('AI Development');
await expect(heading).toContainText('Everywhere You Code');
});
test('displays all platform download cards', async ({ page }) => {
await page.goto('/');
// Check all download cards are present
const downloadCards = page.locator('.download-card');
await expect(downloadCards).toHaveCount(9);
// Check specific platforms
await expect(page.locator('text=Desktop App')).toBeVisible();
await expect(page.locator('text=Mobile Apps')).toBeVisible();
await expect(page.locator('text=Browser Extension')).toBeVisible();
await expect(page.locator('text=VS Code Extension')).toBeVisible();
await expect(page.locator('text=JetBrains Plugin')).toBeVisible();
await expect(page.locator('text=Dev CLI')).toBeVisible();
await expect(page.locator('text=MCP Server')).toBeVisible();
await expect(page.locator('text=Cloud Platform')).toBeVisible();
});
test('copy command buttons work', async ({ page }) => {
await page.goto('/');
// Test CLI copy button
const cliButton = page.locator('button:has-text("npm install -g @hanzo/dev")');
await cliButton.click();
// Check clipboard was written (button text changes)
await expect(cliButton).toContainText('Copied!');
// Wait for button to reset
await page.waitForTimeout(2500);
await expect(cliButton).toContainText('npm install -g @hanzo/dev');
});
test('navigation links work', async ({ page }) => {
await page.goto('/');
// Check header links
await expect(page.locator('a[href="https://docs.hanzo.ai"]')).toBeVisible();
await expect(page.locator('a[href="https://github.com/hanzoai"]')).toBeVisible();
await expect(page.locator('a[href="https://cloud.hanzo.ai/login"]')).toBeVisible();
});
test('quick start section displays correctly', async ({ page }) => {
await page.goto('/');
// Check quick start section
await expect(page.locator('text=Quick Start in 30 Seconds')).toBeVisible();
// Check code block
const codeBlock = page.locator('.code-block');
await expect(codeBlock).toBeVisible();
await expect(codeBlock).toContainText('npm install -g @hanzo/dev');
await expect(codeBlock).toContainText('dev login');
});
test('responsive design works', async ({ page }) => {
// Test mobile viewport
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('/');
// Check mobile menu behavior
const navLinks = page.locator('.nav-links');
await expect(navLinks).toBeHidden();
// Check cards stack on mobile
const downloadGrid = page.locator('.download-grid');
await expect(downloadGrid).toBeVisible();
});
});
test.describe('Dev Landing Page', () => {
test('has correct content', async ({ page }) => {
await page.goto('/src/dev.html');
// Check title
await expect(page).toHaveTitle('Dev - Ship 100X Faster with Parallel AI Agents');
// Check main heading
await expect(page.locator('h1')).toContainText('Ship 100X Faster');
// Check demo terminal
const terminal = page.locator('.demo-terminal');
await expect(terminal).toBeVisible();
await expect(terminal).toContainText('dev enhance');
});
test('pricing cards are displayed', async ({ page }) => {
await page.goto('/src/dev.html');
// Check pricing cards
const pricingCards = page.locator('.pricing-card');
await expect(pricingCards).toHaveCount(3);
// Check prices
await expect(page.locator('text=$0/month')).toBeVisible();
await expect(page.locator('text=$49/month')).toBeVisible();
await expect(page.locator('text=$199/month')).toBeVisible();
});
test('subscribe buttons redirect correctly', async ({ page, context }) => {
await page.goto('/src/dev.html');
// Listen for new pages (popups/tabs)
const pagePromise = context.waitForEvent('page');
// Click subscribe button
await page.locator('button:has-text("Start Pro Trial")').click();
// Get the new page
const newPage = await pagePromise;
await newPage.waitForLoadState();
// Check URL
expect(newPage.url()).toContain('cloud.hanzo.ai/signup');
expect(newPage.url()).toContain('plan=pro');
expect(newPage.url()).toContain('product=dev');
});
});
+32
View File
@@ -0,0 +1,32 @@
import { defineConfig } from 'vite'
export default defineConfig({
root: '.',
build: {
outDir: 'dist',
emptyOutDir: true,
rollupOptions: {
input: {
main: './index.html',
dev: './src/dev.html'
}
},
// Optimize for production
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
},
server: {
port: 3000,
open: true,
host: true
},
preview: {
port: 3001,
open: true
}
})
+625
View File
@@ -0,0 +1,625 @@
import * as fs from 'fs';
import * as path from 'path';
import * as yaml from 'js-yaml';
export interface MCPServerConfig {
name: string;
type: 'stdio' | 'http';
command: string;
args?: string[];
env?: Record<string, string>;
}
export interface SharedMCPConfig {
github?: {
enabled: boolean;
token?: string;
};
linear?: {
enabled: boolean;
apiKey?: string;
};
slack?: {
enabled: boolean;
token?: string;
};
playwright?: {
enabled: boolean;
headless?: boolean;
};
custom?: MCPServerConfig[];
}
export interface AgentInstance {
description: string;
directory: string;
model: string;
connections?: string[];
vibe?: boolean;
prompt: string;
allowed_tools?: string[];
mcps?: MCPServerConfig[];
expose_as_mcp?: boolean; // Expose this agent as an MCP server to other agents
mcp_port?: number; // Port for MCP server if exposed
connect_to_agents?: string[]; // Other agents to connect to via MCP
}
export interface NetworkConfig {
name: string;
agents: string[];
mcp_enabled?: boolean; // Enable MCP connections between all agents in network
shared_tools?: string[]; // Tools available to all agents in network
shared_mcps?: MCPServerConfig[]; // MCP servers available to all agents
}
export interface AgentSwarmConfig {
version: number;
swarm: {
name: string;
main: string;
instances: Record<string, AgentInstance>;
networks?: Record<string, NetworkConfig>; // Named networks of agents
shared_mcps?: SharedMCPConfig; // Shared MCP servers for all agents
};
}
export class AgentSwarmManager {
private config: AgentSwarmConfig | null = null;
private configPath: string;
constructor(configPath?: string) {
this.configPath = configPath || path.join(process.cwd(), '.hanzo', 'agents.yaml');
}
/**
* Load agent configuration from YAML file
*/
async loadConfig(): Promise<AgentSwarmConfig> {
try {
// Check multiple possible locations
const possiblePaths = [
this.configPath,
path.join(process.cwd(), 'agents.yaml'),
path.join(process.cwd(), '.agents.yaml'),
path.join(process.cwd(), 'config', 'agents.yaml')
];
let configContent: string | null = null;
let foundPath: string | null = null;
for (const configPath of possiblePaths) {
if (fs.existsSync(configPath)) {
configContent = fs.readFileSync(configPath, 'utf8');
foundPath = configPath;
break;
}
}
if (!configContent || !foundPath) {
throw new Error(`No agent configuration found. Searched paths: ${possiblePaths.join(', ')}`);
}
this.config = yaml.load(configContent) as AgentSwarmConfig;
console.log(`Loaded agent configuration from: ${foundPath}`);
// Validate configuration
this.validateConfig(this.config);
return this.config;
} catch (error) {
throw new Error(`Failed to load agent configuration: ${error}`);
}
}
/**
* Validate the agent configuration
*/
private validateConfig(config: AgentSwarmConfig): void {
if (!config.version || config.version !== 1) {
throw new Error('Invalid or missing version in agent configuration');
}
if (!config.swarm || !config.swarm.name || !config.swarm.main) {
throw new Error('Invalid swarm configuration: missing name or main agent');
}
if (!config.swarm.instances || Object.keys(config.swarm.instances).length === 0) {
throw new Error('No agent instances defined');
}
// Validate main agent exists
if (!config.swarm.instances[config.swarm.main]) {
throw new Error(`Main agent '${config.swarm.main}' not found in instances`);
}
// Validate each instance
for (const [name, instance] of Object.entries(config.swarm.instances)) {
if (!instance.description || !instance.directory || !instance.model || !instance.prompt) {
throw new Error(`Agent '${name}' missing required fields (description, directory, model, prompt)`);
}
// Validate connections reference existing agents
if (instance.connections) {
for (const connection of instance.connections) {
if (!config.swarm.instances[connection]) {
throw new Error(`Agent '${name}' has invalid connection to '${connection}'`);
}
}
}
// Validate MCP servers
if (instance.mcps) {
for (const mcp of instance.mcps) {
if (!mcp.name || !mcp.type || !mcp.command) {
throw new Error(`Invalid MCP server configuration in agent '${name}'`);
}
}
}
}
}
/**
* Get the main agent instance
*/
getMainAgent(): AgentInstance | null {
if (!this.config) return null;
return this.config.swarm.instances[this.config.swarm.main];
}
/**
* Get a specific agent instance
*/
getAgent(name: string): AgentInstance | null {
if (!this.config) return null;
return this.config.swarm.instances[name] || null;
}
/**
* Get all agent instances
*/
getAllAgents(): Record<string, AgentInstance> {
if (!this.config) return {};
return this.config.swarm.instances;
}
/**
* Get the full configuration
*/
getConfig(): AgentSwarmConfig | null {
return this.config;
}
/**
* Get agents connected to a specific agent
*/
getConnectedAgents(agentName: string): AgentInstance[] {
if (!this.config) return [];
const agent = this.getAgent(agentName);
if (!agent || !agent.connections) return [];
return agent.connections
.map(name => this.getAgent(name))
.filter((agent): agent is AgentInstance => agent !== null);
}
/**
* Get network configuration by name
*/
getNetwork(networkName: string): NetworkConfig | null {
if (!this.config || !this.config.swarm.networks) return null;
return this.config.swarm.networks[networkName] || null;
}
/**
* Get all agents in a network
*/
getNetworkAgents(networkName: string): AgentInstance[] {
const network = this.getNetwork(networkName);
if (!network) return [];
return network.agents
.map(name => this.getAgent(name))
.filter((agent): agent is AgentInstance => agent !== null);
}
/**
* Get all networks an agent belongs to
*/
getAgentNetworks(agentName: string): NetworkConfig[] {
if (!this.config || !this.config.swarm.networks) return [];
return Object.values(this.config.swarm.networks)
.filter(network => network.agents.includes(agentName));
}
/**
* Apply network configuration to agents
*/
applyNetworkConfig(agent: AgentInstance, agentName: string): AgentInstance {
const networks = this.getAgentNetworks(agentName);
if (networks.length === 0) return agent;
// Clone agent to avoid mutation
const configuredAgent = { ...agent };
for (const network of networks) {
// Add shared tools
if (network.shared_tools) {
configuredAgent.allowed_tools = [
...(configuredAgent.allowed_tools || []),
...network.shared_tools
];
}
// Add shared MCP servers
if (network.shared_mcps) {
configuredAgent.mcps = [
...(configuredAgent.mcps || []),
...network.shared_mcps
];
}
// Add MCP connections to other agents in network
if (network.mcp_enabled) {
const otherAgents = network.agents.filter(a => a !== agentName);
configuredAgent.connect_to_agents = [
...(configuredAgent.connect_to_agents || []),
...otherAgents
];
}
}
// Remove duplicates
if (configuredAgent.allowed_tools) {
configuredAgent.allowed_tools = [...new Set(configuredAgent.allowed_tools)];
}
if (configuredAgent.connect_to_agents) {
configuredAgent.connect_to_agents = [...new Set(configuredAgent.connect_to_agents)];
}
return configuredAgent;
}
/**
* Create a sample agent configuration
*/
static createSampleConfig(): string {
const sampleConfig = {
version: 1,
swarm: {
name: "Development Team",
main: "architect",
instances: {
architect: {
description: "Lead architect coordinating development",
directory: ".",
model: "opus",
connections: ["frontend", "backend", "tests", "devops"],
vibe: true,
expose_as_mcp: true,
mcp_port: 8001,
prompt: `# Lead Architect Agent
You are the lead architect coordinating development. Your role is to:
1. Understand requirements and break them down
2. Delegate work to appropriate specialists
3. Ensure best practices across the team
4. Maintain coherent system design
## Your Team
- Frontend: UI/UX implementation
- Backend: API and business logic
- Tests: Test coverage and quality
- DevOps: Infrastructure and deployment
Always coordinate effectively and synthesize work into cohesive solutions.`,
mcps: [
{
name: "filesystem",
type: "stdio",
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem"],
env: {
ALLOWED_DIRECTORIES: "."
}
}
]
},
frontend: {
description: "Frontend UI/UX specialist",
directory: "./src/frontend",
model: "sonnet",
expose_as_mcp: true,
mcp_port: 8002,
allowed_tools: ["Read", "Edit", "Write", "Grep"],
prompt: `# Frontend Specialist
You are a frontend specialist focusing on:
- Component architecture
- User experience
- Responsive design
- Performance optimization
Work closely with the architect to implement UI features.`
},
backend: {
description: "Backend API specialist",
directory: "./src/backend",
model: "sonnet",
expose_as_mcp: true,
mcp_port: 8003,
allowed_tools: ["Read", "Edit", "Write", "Bash"],
prompt: `# Backend Specialist
You are a backend specialist focusing on:
- API design and implementation
- Database optimization
- Security best practices
- Performance and scalability
Coordinate with frontend for API contracts.`
},
tests: {
description: "Testing and quality assurance specialist",
directory: "./tests",
model: "haiku",
expose_as_mcp: true,
mcp_port: 8004,
allowed_tools: ["Read", "Edit", "Write", "Bash"],
prompt: `# Testing Specialist
You are a testing specialist ensuring:
- Comprehensive test coverage
- Test quality and maintainability
- Performance testing
- Integration testing
Write tests for all new features and bug fixes.`
},
devops: {
description: "DevOps and infrastructure specialist",
directory: "./infrastructure",
model: "haiku",
expose_as_mcp: true,
mcp_port: 8005,
allowed_tools: ["Read", "Edit", "Write", "Bash"],
prompt: `# DevOps Specialist
You handle infrastructure and deployment:
- CI/CD pipelines
- Docker and containerization
- Cloud infrastructure
- Monitoring and logging`
}
},
networks: {
core_team: {
name: "Core Development Team",
agents: ["architect", "frontend", "backend", "tests"],
mcp_enabled: true,
shared_tools: ["Grep", "Read"],
shared_mcps: [
{
name: "project-search",
type: "stdio",
command: "npx",
args: ["-y", "@modelcontextprotocol/server-everything"]
}
]
},
deployment_team: {
name: "Deployment Team",
agents: ["backend", "devops", "tests"],
mcp_enabled: true,
shared_tools: ["Bash"]
}
}
}
};
return yaml.dump(sampleConfig, {
indent: 2,
lineWidth: 80,
quotingType: '"',
forceQuotes: false
});
}
/**
* Initialize a new agent configuration file
*/
static async initConfig(targetPath?: string): Promise<void> {
const configPath = targetPath || path.join(process.cwd(), '.hanzo', 'agents.yaml');
// Create directory if needed
const dir = path.dirname(configPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// Check if config already exists
if (fs.existsSync(configPath)) {
throw new Error(`Configuration already exists at ${configPath}`);
}
// Write sample config
const sampleConfig = AgentSwarmManager.createSampleConfig();
fs.writeFileSync(configPath, sampleConfig, 'utf8');
console.log(`Created agent configuration at: ${configPath}`);
console.log('Edit this file to customize your agent swarm configuration.');
}
/**
* Initialize a peer network configuration with Hanzo Zen
*/
static async initPeerNetworkConfig(targetPath?: string): Promise<void> {
const configPath = targetPath || path.join(process.cwd(), '.hanzo', 'agents-peer.yaml');
// Create directory if needed
const dir = path.dirname(configPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// Check if config already exists
if (fs.existsSync(configPath)) {
throw new Error(`Configuration already exists at ${configPath}`);
}
// Write peer network config
const peerConfig = AgentSwarmManager.createPeerNetworkConfig();
fs.writeFileSync(configPath, peerConfig, 'utf8');
console.log(`Created peer network configuration at: ${configPath}`);
console.log('This configuration uses Hanzo Zen for cost-effective local orchestration.');
}
/**
* Create a peer network configuration
*/
static createPeerNetworkConfig(): string {
const config = {
version: 1,
swarm: {
name: "Peer Agent Network",
main: "orchestrator",
network_type: "peer",
local_llm: {
model: "hanzo-zen",
endpoint: "http://localhost:8080",
description: "Local Hanzo Zen MoE for cost-effective orchestration"
},
instances: {
orchestrator: {
description: "Main orchestrator using Hanzo Zen for coordination",
directory: ".",
model: "zen",
expose_as_mcp: true,
mcp_port: 10000,
connect_to_agents: ["architect", "developer", "reviewer", "tester", "critic"],
vibe: true,
prompt: `# Orchestrator Agent (Hanzo Zen)
You are the main orchestrator running on local Hanzo Zen for cost efficiency.
Your role is to coordinate all other agents efficiently.
## Cost Optimization Strategy
- Use local inference (yourself) for planning and coordination
- Delegate complex tasks to specialized agents with API-based LLMs
- Batch similar requests to minimize API calls
- Use recursive agent calls wisely
## Available Agents (All exposed as MCP tools)
Every agent can call every other agent. You have tools like:
- chat_with_[agent] - Have conversations
- ask_[agent] - Quick questions
- delegate_to_[agent] - Task delegation
- get_[agent]_status - Check availability
- request_[agent]_expertise - Deep knowledge
Coordinate effectively while minimizing costs.`,
allowed_tools: ["*"] // Access to all tools
},
architect: {
description: "System architect for design decisions",
directory: "./src",
model: "opus",
expose_as_mcp: true,
mcp_port: 10001,
connect_to_agents: ["orchestrator", "developer", "reviewer", "tester", "critic"],
prompt: `# System Architect
You make architectural decisions and system design.
You can consult any other agent for information.
Focus on scalable, maintainable solutions.`,
allowed_tools: ["Read", "Write", "Edit", "Grep", "chat_with_*", "ask_*"]
},
developer: {
description: "Senior developer for implementation",
directory: "./src",
model: "sonnet",
expose_as_mcp: true,
mcp_port: 10002,
connect_to_agents: ["orchestrator", "architect", "reviewer", "tester", "critic"],
prompt: `# Senior Developer
You implement solutions based on architectural decisions.
Consult architect for design questions.
Ask reviewer for code quality feedback.
Coordinate with tester for test coverage.`,
allowed_tools: ["Read", "Write", "Edit", "Bash", "chat_with_*", "ask_*", "delegate_to_*"]
},
reviewer: {
description: "Code reviewer for quality assurance",
directory: ".",
model: "sonnet",
expose_as_mcp: true,
mcp_port: 10003,
connect_to_agents: ["orchestrator", "architect", "developer", "tester", "critic"],
prompt: `# Code Reviewer
You review code for quality, security, and best practices.
You can ask developer for clarifications.
Consult architect for design compliance.
Work with tester on test coverage.`,
allowed_tools: ["Read", "Grep", "chat_with_*", "ask_*"]
},
tester: {
description: "QA engineer for comprehensive testing",
directory: "./tests",
model: "haiku",
expose_as_mcp: true,
mcp_port: 10004,
connect_to_agents: ["orchestrator", "architect", "developer", "reviewer", "critic"],
prompt: `# QA Engineer
You ensure quality through testing.
Ask developer about implementation details.
Coordinate with reviewer on quality standards.
Report issues to architect for design flaws.`,
allowed_tools: ["Read", "Write", "Edit", "Bash", "chat_with_*", "ask_*"]
},
critic: {
description: "Final critic for comprehensive analysis",
directory: ".",
model: "opus",
expose_as_mcp: true,
mcp_port: 10005,
connect_to_agents: ["orchestrator", "architect", "developer", "reviewer", "tester"],
prompt: `# Critic Agent
You provide final analysis of completed work.
Review outputs from all agents critically.
Identify gaps, issues, and improvements.
Ensure requirements are fully met.`,
allowed_tools: ["Read", "chat_with_*", "ask_*", "request_*_expertise"]
}
},
networks: {
full_mesh: {
name: "Fully Connected Peer Network",
agents: ["orchestrator", "architect", "developer", "reviewer", "tester", "critic"],
mcp_enabled: true,
description: "All agents can directly communicate with each other"
},
core_team: {
name: "Core Development Team",
agents: ["architect", "developer", "reviewer", "tester"],
mcp_enabled: true,
shared_tools: ["Read", "Grep"]
}
}
}
};
return yaml.dump(config, {
indent: 2,
lineWidth: 100,
quotingType: '"',
forceQuotes: false
});
}
}
@@ -0,0 +1,381 @@
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequest,
CallToolResult,
ListToolsRequest,
ListToolsResult,
Tool,
TextContent,
ImageContent,
EmbeddedResource
} from '@modelcontextprotocol/sdk/types.js';
import { AgentInstance } from '../config/agent-swarm-config';
import { CLIToolManager } from '../cli-tool-manager';
export interface AgentMCPServerConfig {
agentName: string;
agent: AgentInstance;
toolManager: CLIToolManager;
port?: number;
}
export class AgentMCPServer {
private server: Server;
private config: AgentMCPServerConfig;
constructor(config: AgentMCPServerConfig) {
this.config = config;
this.server = new Server(
{
name: `agent-${config.agentName}`,
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupHandlers();
}
private setupHandlers(): void {
// List available tools for this agent
this.server.setRequestHandler(ListToolsRequest, async () => {
const tools: Tool[] = [
{
name: `chat_with_${this.config.agentName}`,
description: `Chat directly with the ${this.config.agentName} agent (${this.config.agent.description})`,
inputSchema: {
type: 'object',
properties: {
message: {
type: 'string',
description: 'Your message to the agent',
},
conversation_id: {
type: 'string',
description: 'Optional conversation ID for context continuity',
},
},
required: ['message'],
},
},
{
name: `ask_${this.config.agentName}`,
description: `Ask a specific question to the ${this.config.agentName} agent`,
inputSchema: {
type: 'object',
properties: {
question: {
type: 'string',
description: 'The question to ask',
},
context: {
type: 'object',
description: 'Optional context for the question',
},
},
required: ['question'],
},
},
{
name: `delegate_to_${this.config.agentName}`,
description: `Delegate a specific task to the ${this.config.agentName} agent`,
inputSchema: {
type: 'object',
properties: {
task: {
type: 'string',
description: 'The task to delegate',
},
requirements: {
type: 'object',
description: 'Task requirements and constraints',
},
deadline: {
type: 'string',
description: 'Optional deadline for the task',
},
},
required: ['task'],
},
},
{
name: `get_${this.config.agentName}_status`,
description: `Get the current status and capabilities of the ${this.config.agentName} agent`,
inputSchema: {
type: 'object',
properties: {
include_workload: {
type: 'boolean',
description: 'Include current workload information',
},
},
},
},
{
name: `request_${this.config.agentName}_expertise`,
description: `Request specific expertise from the ${this.config.agentName} agent`,
inputSchema: {
type: 'object',
properties: {
topic: {
type: 'string',
description: 'The topic or area of expertise needed',
},
depth: {
type: 'string',
enum: ['overview', 'detailed', 'expert'],
description: 'Level of detail needed',
},
},
required: ['topic'],
},
},
];
return { tools } as ListToolsResult;
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequest, async (request) => {
const { name, arguments: args } = request.params;
try {
let result: string;
// Route to appropriate handler based on tool name pattern
if (name.startsWith(`chat_with_`)) {
result = await this.handleChat(args.message, args.conversation_id);
} else if (name.startsWith(`ask_`)) {
result = await this.handleQuestion(args.question, args.context);
} else if (name.startsWith(`delegate_to_`)) {
result = await this.handleDelegation(args.task, args.requirements, args.deadline);
} else if (name.startsWith(`get_`) && name.endsWith('_status')) {
result = await this.getStatus(args.include_workload);
} else if (name.startsWith(`request_`) && name.endsWith('_expertise')) {
result = await this.handleExpertiseRequest(args.topic, args.depth || 'detailed');
} else {
throw new Error(`Unknown tool: ${name}`);
}
return {
content: [
{
type: 'text',
text: result,
} as TextContent,
],
} as CallToolResult;
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
} as TextContent,
],
isError: true,
} as CallToolResult;
}
});
}
private conversationHistory: Map<string, string[]> = new Map();
private async handleChat(message: string, conversationId?: string): Promise<string> {
const tool = this.getToolForModel(this.config.agent.model);
const convId = conversationId || 'default';
// Get conversation history
if (!this.conversationHistory.has(convId)) {
this.conversationHistory.set(convId, []);
}
const history = this.conversationHistory.get(convId)!;
// Build conversational prompt
let prompt = `${this.config.agent.prompt}\n\n`;
prompt += `You are having a conversation with another agent. Respond naturally and helpfully.\n\n`;
if (history.length > 0) {
prompt += `## Previous Messages\n`;
history.slice(-10).forEach(msg => prompt += `${msg}\n`);
prompt += `\n`;
}
prompt += `## Current Message\n${message}\n\n`;
prompt += `Respond as ${this.config.agentName}:`;
const result = await this.config.toolManager.runTool(tool, prompt, {
model: this.config.agent.model,
cwd: this.config.agent.directory,
});
// Store in history
history.push(`Other Agent: ${message}`);
history.push(`${this.config.agentName}: ${result}`);
return result;
}
private async handleQuestion(question: string, context?: any): Promise<string> {
const tool = this.getToolForModel(this.config.agent.model);
let prompt = `${this.config.agent.prompt}\n\n`;
prompt += `You've been asked a specific question by another agent.\n\n`;
if (context) {
prompt += `## Context\n${JSON.stringify(context, null, 2)}\n\n`;
}
prompt += `## Question\n${question}\n\n`;
prompt += `Provide a clear, direct answer focusing on your area of expertise.`;
const result = await this.config.toolManager.runTool(tool, prompt, {
model: this.config.agent.model,
cwd: this.config.agent.directory,
});
return result;
}
private async handleDelegation(task: string, requirements?: any, deadline?: string): Promise<string> {
const tool = this.getToolForModel(this.config.agent.model);
let prompt = `${this.config.agent.prompt}\n\n`;
prompt += `You've been delegated a specific task by another agent.\n\n`;
prompt += `## Delegated Task\n${task}\n\n`;
if (requirements) {
prompt += `## Requirements\n${JSON.stringify(requirements, null, 2)}\n\n`;
}
if (deadline) {
prompt += `## Deadline\n${deadline}\n\n`;
}
prompt += `Complete this task according to your expertise and the requirements provided.`;
const result = await this.config.toolManager.runTool(tool, prompt, {
model: this.config.agent.model,
cwd: this.config.agent.directory,
});
return result;
}
private async getStatus(includeWorkload?: boolean): Promise<string> {
const status = {
agent: this.config.agentName,
description: this.config.agent.description,
model: this.config.agent.model,
directory: this.config.agent.directory,
status: 'active',
capabilities: {
tools: this.config.agent.allowed_tools || [],
connections: this.config.agent.connections || [],
mcps: this.config.agent.mcps?.map(mcp => mcp.name) || [],
canChat: true,
canDelegate: true,
canProvideExpertise: true,
},
};
if (includeWorkload) {
// Could track actual workload here
(status as any).workload = {
activeConversations: this.conversationHistory.size,
tasksInProgress: 0,
availability: 'available',
};
}
return JSON.stringify(status, null, 2);
}
private async handleExpertiseRequest(topic: string, depth: string): Promise<string> {
const tool = this.getToolForModel(this.config.agent.model);
let prompt = `${this.config.agent.prompt}\n\n`;
prompt += `Another agent has requested your expertise on a specific topic.\n\n`;
prompt += `## Topic\n${topic}\n\n`;
prompt += `## Requested Depth\n${depth}\n\n`;
switch (depth) {
case 'overview':
prompt += `Provide a brief overview suitable for someone unfamiliar with the topic.`;
break;
case 'detailed':
prompt += `Provide a comprehensive explanation with relevant details and examples.`;
break;
case 'expert':
prompt += `Provide an in-depth expert analysis including edge cases, best practices, and advanced considerations.`;
break;
}
const result = await this.config.toolManager.runTool(tool, prompt, {
model: this.config.agent.model,
cwd: this.config.agent.directory,
});
return result;
}
private getToolForModel(model: string): string {
const modelMap: Record<string, string> = {
'opus': 'claude',
'sonnet': 'claude',
'haiku': 'claude',
'gpt-4': 'codex',
'gpt-3.5': 'codex',
'gemini-pro': 'gemini',
'gemini-ultra': 'gemini',
};
return modelMap[model.toLowerCase()] || 'claude';
}
async start(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error(`Agent MCP server for ${this.config.agentName} started`);
}
async stop(): Promise<void> {
await this.server.close();
}
}
// CLI entry point for running as a standalone process
if (require.main === module) {
const agentName = process.argv[2];
const agentConfigJson = process.argv[3];
if (!agentName || !agentConfigJson) {
console.error('Usage: agent-mcp-server <agent-name> <agent-config-json>');
process.exit(1);
}
const agent = JSON.parse(agentConfigJson) as AgentInstance;
const toolManager = new CLIToolManager();
const server = new AgentMCPServer({
agentName,
agent,
toolManager,
});
server.start().catch((error) => {
console.error('Failed to start agent MCP server:', error);
process.exit(1);
});
process.on('SIGINT', async () => {
await server.stop();
process.exit(0);
});
}
@@ -0,0 +1,578 @@
import { EventEmitter } from 'events';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { AgentInstance, AgentSwarmManager } from '../config/agent-swarm-config';
import { CLIToolManager } from '../cli-tool-manager';
import { HanzoAuth } from '../auth/hanzo-auth';
import { spawn, ChildProcess } from 'child_process';
import * as path from 'path';
export interface LocalLLMConfig {
model: string;
endpoint?: string;
maxTokens?: number;
temperature?: number;
}
export interface AgentPeer {
name: string;
instance: AgentInstance;
mcpServer: Server;
process?: ChildProcess;
port: number;
status: 'starting' | 'ready' | 'busy' | 'error';
connections: Set<string>;
}
export interface NetworkConfig {
mainLoopLLM: LocalLLMConfig;
enableRecursiveCalls: boolean;
maxRecursionDepth: number;
costOptimization: boolean;
}
export class PeerAgentNetwork extends EventEmitter {
private peers: Map<string, AgentPeer> = new Map();
private swarmManager: AgentSwarmManager;
private toolManager: CLIToolManager;
private auth: HanzoAuth;
private networkConfig: NetworkConfig;
private basePort: number = 10000;
private recursionTracker: Map<string, number> = new Map();
constructor(config: NetworkConfig) {
super();
this.networkConfig = config;
this.swarmManager = new AgentSwarmManager();
this.toolManager = new CLIToolManager();
this.auth = new HanzoAuth();
}
/**
* Initialize the peer network with all agents
*/
async initialize(): Promise<void> {
// Load agent configuration
await this.swarmManager.loadConfig();
// Check authentication for API-based LLMs
if (!this.auth.isAuthenticated()) {
console.warn('Not authenticated. Some LLM providers may not work.');
}
// Initialize all agents as peers
const agents = this.swarmManager.getAllAgents();
for (const [name, instance] of Object.entries(agents)) {
await this.createAgentPeer(name, instance);
}
// Connect all peers to each other
await this.establishPeerConnections();
// Start the main orchestration loop with local LLM
await this.startMainLoop();
}
/**
* Create an agent peer with MCP server
*/
private async createAgentPeer(name: string, instance: AgentInstance): Promise<void> {
const port = instance.mcp_port || this.basePort++;
// Create MCP server that exposes ALL other agents as tools
const mcpServer = await this.createPeerMCPServer(name, instance, port);
const peer: AgentPeer = {
name,
instance,
mcpServer,
port,
status: 'starting',
connections: new Set()
};
// Start the MCP server process
const serverProcess = spawn('node', [
path.join(__dirname, 'peer-mcp-server.js'),
name,
JSON.stringify({
instance,
port,
peers: Array.from(this.peers.keys()),
networkConfig: this.networkConfig
})
], {
stdio: ['pipe', 'pipe', 'pipe'],
env: {
...process.env,
AGENT_NAME: name,
AGENT_PORT: port.toString(),
HANZO_AUTH: this.auth.getCredentials() ? JSON.stringify(this.auth.getCredentials()) : ''
}
});
serverProcess.on('error', (error) => {
console.error(`Peer ${name} error:`, error);
peer.status = 'error';
this.emit('peer-error', { name, error });
});
serverProcess.stderr.on('data', (data) => {
console.error(`[${name}] ${data.toString()}`);
});
peer.process = serverProcess;
this.peers.set(name, peer);
// Wait for peer to be ready
await this.waitForPeerReady(name);
peer.status = 'ready';
this.emit('peer-ready', { name, port });
}
/**
* Create MCP server that exposes all other agents
*/
private async createPeerMCPServer(name: string, instance: AgentInstance, port: number): Promise<Server> {
const server = new Server(
{
name: `peer-${name}`,
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// This server will expose tools for ALL other agents
return server;
}
/**
* Establish connections between all peers
*/
private async establishPeerConnections(): Promise<void> {
const peerNames = Array.from(this.peers.keys());
for (const sourcePeer of peerNames) {
for (const targetPeer of peerNames) {
if (sourcePeer !== targetPeer) {
await this.connectPeers(sourcePeer, targetPeer);
}
}
}
}
/**
* Connect two peers bidirectionally
*/
private async connectPeers(source: string, target: string): Promise<void> {
const sourcePeer = this.peers.get(source);
const targetPeer = this.peers.get(target);
if (!sourcePeer || !targetPeer) return;
sourcePeer.connections.add(target);
targetPeer.connections.add(source);
this.emit('peers-connected', { source, target });
}
/**
* Start the main orchestration loop with local LLM
*/
private async startMainLoop(): Promise<void> {
console.log(`Starting main orchestration loop with ${this.networkConfig.mainLoopLLM.model}`);
// The main loop uses Hanzo Zen or other local LLM for coordination
// This saves costs by using local inference for orchestration
this.emit('main-loop-started', this.networkConfig.mainLoopLLM);
}
/**
* Execute a task through the peer network
*/
async executeTask(task: string, options?: {
startAgent?: string;
maxHops?: number;
requireCritic?: boolean;
}): Promise<any> {
const startAgent = options?.startAgent || this.swarmManager.getMainAgent();
const maxHops = options?.maxHops || this.networkConfig.maxRecursionDepth;
// Track recursion to prevent infinite loops
const taskId = `${Date.now()}-${Math.random()}`;
this.recursionTracker.set(taskId, 0);
try {
// Use local LLM to determine execution plan
const executionPlan = await this.planExecution(task, startAgent);
// Execute through peer network
const results = await this.executePeerTask(
taskId,
startAgent,
task,
executionPlan,
maxHops
);
// If critic is required, run final analysis
if (options?.requireCritic) {
const criticResult = await this.runCriticAnalysis(task, results);
results.critic = criticResult;
}
return results;
} finally {
this.recursionTracker.delete(taskId);
}
}
/**
* Plan execution using local LLM (Hanzo Zen)
*/
private async planExecution(task: string, startAgent: string): Promise<any> {
// Use local LLM for planning to save costs
const prompt = `
Task: ${task}
Available agents: ${Array.from(this.peers.keys()).join(', ')}
Starting agent: ${startAgent}
Create an execution plan that minimizes API calls while maximizing effectiveness.
Consider which agents need to collaborate and in what order.
`;
// Call local Hanzo Zen model
const plan = await this.callLocalLLM(prompt);
return plan;
}
/**
* Execute task through peer network with recursion support
*/
private async executePeerTask(
taskId: string,
agentName: string,
task: string,
plan: any,
maxHops: number
): Promise<any> {
const recursionCount = this.recursionTracker.get(taskId) || 0;
if (recursionCount >= maxHops) {
return { error: 'Max recursion depth reached' };
}
this.recursionTracker.set(taskId, recursionCount + 1);
const peer = this.peers.get(agentName);
if (!peer) {
return { error: `Agent ${agentName} not found` };
}
// Mark peer as busy
peer.status = 'busy';
try {
// Execute with cost optimization
const result = await this.executePeerWithCostOptimization(
peer,
task,
plan,
taskId
);
peer.status = 'ready';
return result;
} catch (error) {
peer.status = 'error';
throw error;
}
}
/**
* Execute peer task with cost optimization
*/
private async executePeerWithCostOptimization(
peer: AgentPeer,
task: string,
plan: any,
taskId: string
): Promise<any> {
const instance = peer.instance;
// Determine if we should use local or API-based LLM
const useLocalLLM = this.shouldUseLocalLLM(instance, task);
if (useLocalLLM) {
// Use Hanzo Zen for this agent to save costs
return await this.executeWithLocalLLM(peer, task, plan);
} else {
// Use configured API-based LLM (Claude, OpenAI, etc.)
return await this.executeWithAPILLM(peer, task, plan);
}
}
/**
* Determine if local LLM should be used based on task complexity
*/
private shouldUseLocalLLM(instance: AgentInstance, task: string): boolean {
if (!this.networkConfig.costOptimization) {
return false;
}
// Use local LLM for:
// - Simple routing decisions
// - Status checks
// - Basic queries
// - Coordination tasks
const simplePatterns = [
/status/i,
/check/i,
/route/i,
/coordinate/i,
/plan/i,
/summarize/i
];
return simplePatterns.some(pattern => pattern.test(task));
}
/**
* Execute with local Hanzo Zen LLM
*/
private async executeWithLocalLLM(peer: AgentPeer, task: string, plan: any): Promise<any> {
const prompt = this.buildPeerPrompt(peer, task, plan);
return await this.callLocalLLM(prompt);
}
/**
* Execute with API-based LLM
*/
private async executeWithAPILLM(peer: AgentPeer, task: string, plan: any): Promise<any> {
const tool = this.getToolForModel(peer.instance.model);
const prompt = this.buildPeerPrompt(peer, task, plan);
return await this.toolManager.runTool(tool, prompt, {
model: peer.instance.model,
cwd: peer.instance.directory,
env: this.buildPeerEnvironment(peer)
});
}
/**
* Build prompt for peer execution
*/
private buildPeerPrompt(peer: AgentPeer, task: string, plan: any): string {
let prompt = peer.instance.prompt + '\n\n';
// Add network context
prompt += `## Network Context\n`;
prompt += `You are part of a peer network with these agents:\n`;
for (const [name, otherPeer] of this.peers) {
if (name !== peer.name) {
prompt += `- ${name}: ${otherPeer.instance.description}\n`;
prompt += ` Tools: chat_with_${name}, ask_${name}, delegate_to_${name}, etc.\n`;
}
}
prompt += `\n## Task\n${task}\n`;
prompt += `\n## Execution Plan\n${JSON.stringify(plan, null, 2)}\n`;
prompt += `\nYou can recursively call other agents as needed. All agents are available as MCP tools.\n`;
return prompt;
}
/**
* Build environment for peer execution
*/
private buildPeerEnvironment(peer: AgentPeer): Record<string, string> {
return {
...process.env,
AGENT_NAME: peer.name,
AGENT_PORT: peer.port.toString(),
PEER_NETWORK: 'true',
PEERS: Array.from(this.peers.keys()).join(','),
LOCAL_LLM_ENDPOINT: this.networkConfig.mainLoopLLM.endpoint || 'http://localhost:8080'
};
}
/**
* Run critic analysis on results
*/
private async runCriticAnalysis(task: string, results: any): Promise<any> {
const criticAgent = this.peers.get('critic');
if (!criticAgent) {
// Create ad-hoc critic if not defined
return await this.runAdHocCritic(task, results);
}
const criticPrompt = `
Analyze the following task execution and results:
Task: ${task}
Results: ${JSON.stringify(results, null, 2)}
Provide:
1. Quality assessment
2. Completeness check
3. Potential improvements
4. Risk analysis
`;
return await this.executePeerTask(
`critic-${Date.now()}`,
'critic',
criticPrompt,
{},
1
);
}
/**
* Run ad-hoc critic analysis using local LLM
*/
private async runAdHocCritic(task: string, results: any): Promise<any> {
const prompt = `
Act as a critic analyzing this task execution:
Task: ${task}
Results: ${JSON.stringify(results, null, 2)}
Provide constructive criticism and suggestions.
`;
return await this.callLocalLLM(prompt);
}
/**
* Call local Hanzo Zen LLM
*/
private async callLocalLLM(prompt: string): Promise<any> {
const { model, endpoint, maxTokens, temperature } = this.networkConfig.mainLoopLLM;
// Call local Hanzo Zen endpoint
const response = await fetch(endpoint || 'http://localhost:8080/v1/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: model || 'hanzo-zen',
prompt,
max_tokens: maxTokens || 2000,
temperature: temperature || 0.7,
tools: this.getAvailableTools()
})
});
const result = await response.json();
return result.choices[0].text;
}
/**
* Get available tools for all agents
*/
private getAvailableTools(): any[] {
const tools = [];
for (const [name, peer] of this.peers) {
tools.push(
{
name: `chat_with_${name}`,
description: `Chat with ${name} agent: ${peer.instance.description}`
},
{
name: `ask_${name}`,
description: `Ask ${name} a specific question`
},
{
name: `delegate_to_${name}`,
description: `Delegate a task to ${name}`
},
{
name: `get_${name}_status`,
description: `Get status of ${name} agent`
}
);
}
return tools;
}
/**
* Wait for peer to be ready
*/
private async waitForPeerReady(name: string, timeout: number = 5000): Promise<void> {
const start = Date.now();
while (Date.now() - start < timeout) {
const peer = this.peers.get(name);
if (peer && peer.status === 'ready') {
return;
}
await new Promise(resolve => setTimeout(resolve, 100));
}
throw new Error(`Peer ${name} failed to start within timeout`);
}
/**
* Get tool for model
*/
private getToolForModel(model: string): string {
const modelMap: Record<string, string> = {
'opus': 'claude',
'sonnet': 'claude',
'haiku': 'claude',
'gpt-4': 'codex',
'gpt-3.5': 'codex',
'gemini-pro': 'gemini',
'gemini-ultra': 'gemini',
'zen': 'local',
'hanzo-zen': 'local'
};
return modelMap[model.toLowerCase()] || 'claude';
}
/**
* Shutdown the peer network
*/
async shutdown(): Promise<void> {
for (const [name, peer] of this.peers) {
if (peer.process) {
peer.process.kill();
}
}
this.peers.clear();
this.emit('shutdown');
}
/**
* Get network status
*/
getStatus(): any {
const status = {
peers: {},
connections: {},
mainLoop: this.networkConfig.mainLoopLLM,
authenticated: this.auth.isAuthenticated()
};
for (const [name, peer] of this.peers) {
status.peers[name] = {
status: peer.status,
port: peer.port,
connections: Array.from(peer.connections),
model: peer.instance.model
};
}
return status;
}
}
@@ -0,0 +1,506 @@
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequest,
CallToolResult,
ListToolsRequest,
ListToolsResult,
Tool,
TextContent
} from '@modelcontextprotocol/sdk/types.js';
import { AgentInstance } from '../config/agent-swarm-config';
import fetch from 'node-fetch';
interface PeerConfig {
instance: AgentInstance;
port: number;
peers: string[];
networkConfig: any;
}
export class PeerMCPServer {
private server: Server;
private agentName: string;
private config: PeerConfig;
private conversationHistory: Map<string, string[]> = new Map();
private callStack: string[] = [];
constructor(agentName: string, config: PeerConfig) {
this.agentName = agentName;
this.config = config;
this.server = new Server(
{
name: `peer-${agentName}`,
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupHandlers();
}
private setupHandlers(): void {
// List all peer agents as available tools
this.server.setRequestHandler(ListToolsRequest, async () => {
const tools: Tool[] = [];
// Add tools for each peer agent
for (const peerName of this.config.peers) {
if (peerName !== this.agentName) {
// Chat tool
tools.push({
name: `chat_with_${peerName}`,
description: `Have a conversation with the ${peerName} agent`,
inputSchema: {
type: 'object',
properties: {
message: {
type: 'string',
description: 'Your message to the agent',
},
conversation_id: {
type: 'string',
description: 'Conversation ID for context',
},
context: {
type: 'object',
description: 'Additional context',
},
},
required: ['message'],
},
});
// Ask tool
tools.push({
name: `ask_${peerName}`,
description: `Ask a specific question to the ${peerName} agent`,
inputSchema: {
type: 'object',
properties: {
question: {
type: 'string',
description: 'The question to ask',
},
context: {
type: 'object',
description: 'Question context',
},
urgency: {
type: 'string',
enum: ['low', 'normal', 'high'],
description: 'Question urgency',
},
},
required: ['question'],
},
});
// Delegate tool
tools.push({
name: `delegate_to_${peerName}`,
description: `Delegate a task to the ${peerName} agent`,
inputSchema: {
type: 'object',
properties: {
task: {
type: 'string',
description: 'The task to delegate',
},
requirements: {
type: 'object',
description: 'Task requirements',
},
priority: {
type: 'string',
enum: ['low', 'normal', 'high'],
description: 'Task priority',
},
deadline: {
type: 'string',
description: 'Task deadline',
},
},
required: ['task'],
},
});
// Status tool
tools.push({
name: `get_${peerName}_status`,
description: `Get status of the ${peerName} agent`,
inputSchema: {
type: 'object',
properties: {
include_workload: {
type: 'boolean',
description: 'Include current workload',
},
include_capabilities: {
type: 'boolean',
description: 'Include agent capabilities',
},
},
},
});
// Expertise tool
tools.push({
name: `request_${peerName}_expertise`,
description: `Request expertise from the ${peerName} agent`,
inputSchema: {
type: 'object',
properties: {
topic: {
type: 'string',
description: 'The topic requiring expertise',
},
depth: {
type: 'string',
enum: ['overview', 'detailed', 'expert'],
description: 'Depth of expertise needed',
},
specific_questions: {
type: 'array',
items: { type: 'string' },
description: 'Specific questions to address',
},
},
required: ['topic'],
},
});
// Collaborate tool
tools.push({
name: `collaborate_with_${peerName}`,
description: `Start a collaborative session with the ${peerName} agent`,
inputSchema: {
type: 'object',
properties: {
objective: {
type: 'string',
description: 'Collaboration objective',
},
duration: {
type: 'string',
description: 'Expected duration',
},
deliverables: {
type: 'array',
items: { type: 'string' },
description: 'Expected deliverables',
},
},
required: ['objective'],
},
});
}
}
// Add self-reflection tool
tools.push({
name: 'reflect',
description: 'Reflect on current task and progress',
inputSchema: {
type: 'object',
properties: {
aspect: {
type: 'string',
description: 'What aspect to reflect on',
},
},
},
});
// Add network status tool
tools.push({
name: 'get_network_status',
description: 'Get status of the entire agent network',
inputSchema: {
type: 'object',
properties: {},
},
});
return { tools } as ListToolsResult;
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequest, async (request) => {
const { name, arguments: args } = request.params;
try {
// Track call stack to prevent infinite recursion
if (this.callStack.includes(name)) {
return this.createErrorResult('Recursive call detected. Breaking recursion.');
}
this.callStack.push(name);
let result: string;
// Route to appropriate handler
if (name.startsWith('chat_with_')) {
const peerName = name.replace('chat_with_', '');
result = await this.handleChat(peerName, args);
} else if (name.startsWith('ask_')) {
const peerName = name.replace('ask_', '');
result = await this.handleAsk(peerName, args);
} else if (name.startsWith('delegate_to_')) {
const peerName = name.replace('delegate_to_', '');
result = await this.handleDelegate(peerName, args);
} else if (name.startsWith('get_') && name.endsWith('_status')) {
const peerName = name.replace('get_', '').replace('_status', '');
result = await this.handleGetStatus(peerName, args);
} else if (name.startsWith('request_') && name.endsWith('_expertise')) {
const peerName = name.replace('request_', '').replace('_expertise', '');
result = await this.handleRequestExpertise(peerName, args);
} else if (name.startsWith('collaborate_with_')) {
const peerName = name.replace('collaborate_with_', '');
result = await this.handleCollaborate(peerName, args);
} else if (name === 'reflect') {
result = await this.handleReflect(args);
} else if (name === 'get_network_status') {
result = await this.handleGetNetworkStatus();
} else {
throw new Error(`Unknown tool: ${name}`);
}
this.callStack.pop();
return this.createSuccessResult(result);
} catch (error) {
this.callStack.pop();
return this.createErrorResult(error);
}
});
}
private async handleChat(peerName: string, args: any): Promise<string> {
const { message, conversation_id, context } = args;
const convId = conversation_id || `${this.agentName}-${peerName}`;
// Store conversation history
if (!this.conversationHistory.has(convId)) {
this.conversationHistory.set(convId, []);
}
const history = this.conversationHistory.get(convId)!;
history.push(`${this.agentName}: ${message}`);
// Forward to peer agent
const response = await this.callPeerAgent(peerName, 'chat', {
from: this.agentName,
message,
conversation_id: convId,
context,
history: history.slice(-10) // Last 10 messages
});
history.push(`${peerName}: ${response}`);
return response;
}
private async handleAsk(peerName: string, args: any): Promise<string> {
const { question, context, urgency } = args;
return await this.callPeerAgent(peerName, 'answer', {
from: this.agentName,
question,
context,
urgency: urgency || 'normal'
});
}
private async handleDelegate(peerName: string, args: any): Promise<string> {
const { task, requirements, priority, deadline } = args;
return await this.callPeerAgent(peerName, 'execute', {
from: this.agentName,
task,
requirements,
priority: priority || 'normal',
deadline
});
}
private async handleGetStatus(peerName: string, args: any): Promise<string> {
const { include_workload, include_capabilities } = args;
return await this.callPeerAgent(peerName, 'status', {
include_workload,
include_capabilities
});
}
private async handleRequestExpertise(peerName: string, args: any): Promise<string> {
const { topic, depth, specific_questions } = args;
return await this.callPeerAgent(peerName, 'expertise', {
from: this.agentName,
topic,
depth: depth || 'detailed',
specific_questions
});
}
private async handleCollaborate(peerName: string, args: any): Promise<string> {
const { objective, duration, deliverables } = args;
// Start collaborative session
const sessionId = `collab-${Date.now()}`;
return await this.callPeerAgent(peerName, 'collaborate', {
from: this.agentName,
session_id: sessionId,
objective,
duration,
deliverables
});
}
private async handleReflect(args: any): Promise<string> {
const { aspect } = args;
// Self-reflection using local context
const reflection = {
agent: this.agentName,
aspect: aspect || 'general',
current_conversations: this.conversationHistory.size,
call_stack_depth: this.callStack.length,
capabilities: this.config.instance.allowed_tools || [],
connections: this.config.peers.filter(p => p !== this.agentName)
};
return JSON.stringify(reflection, null, 2);
}
private async handleGetNetworkStatus(): Promise<string> {
// Get status of entire network
const networkStatus = {
total_agents: this.config.peers.length,
current_agent: this.agentName,
connected_peers: this.config.peers.filter(p => p !== this.agentName),
network_config: {
recursive_calls: this.config.networkConfig.enableRecursiveCalls,
max_recursion: this.config.networkConfig.maxRecursionDepth,
cost_optimization: this.config.networkConfig.costOptimization
}
};
return JSON.stringify(networkStatus, null, 2);
}
private async callPeerAgent(peerName: string, operation: string, params: any): Promise<string> {
// Check if we should use local or remote LLM
const useLocal = this.shouldUseLocalLLM(operation);
if (useLocal && this.config.networkConfig.costOptimization) {
// Use local Hanzo Zen for simple operations
return await this.callLocalLLM(peerName, operation, params);
} else {
// Use peer's configured LLM
return await this.callPeerMCP(peerName, operation, params);
}
}
private shouldUseLocalLLM(operation: string): boolean {
// Use local LLM for simple operations to save costs
const localOperations = ['status', 'reflect', 'get_network_status'];
return localOperations.includes(operation);
}
private async callLocalLLM(peerName: string, operation: string, params: any): Promise<string> {
const endpoint = this.config.networkConfig.mainLoopLLM.endpoint || 'http://localhost:8080/v1/completions';
const prompt = `
Acting as ${peerName} agent, respond to this ${operation} request:
${JSON.stringify(params, null, 2)}
Provide a concise, helpful response.
`;
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'hanzo-zen',
prompt,
max_tokens: 500,
temperature: 0.7
})
});
const result = await response.json();
return result.choices[0].text;
} catch (error) {
return `Local LLM error: ${error.message}`;
}
}
private async callPeerMCP(peerName: string, operation: string, params: any): Promise<string> {
// In production, this would make an actual MCP call to the peer
// For now, simulate the peer response
return `[${peerName}] Received ${operation} request: ${JSON.stringify(params)}`;
}
private createSuccessResult(text: string): CallToolResult {
return {
content: [
{
type: 'text',
text,
} as TextContent,
],
};
}
private createErrorResult(error: any): CallToolResult {
return {
content: [
{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
} as TextContent,
],
isError: true,
};
}
async start(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error(`Peer MCP server for ${this.agentName} started on port ${this.config.port}`);
}
async stop(): Promise<void> {
await this.server.close();
}
}
// CLI entry point
if (require.main === module) {
const agentName = process.argv[2];
const configJson = process.argv[3];
if (!agentName || !configJson) {
console.error('Usage: peer-mcp-server <agent-name> <config-json>');
process.exit(1);
}
const config = JSON.parse(configJson) as PeerConfig;
const server = new PeerMCPServer(agentName, config);
server.start().catch((error) => {
console.error('Failed to start peer MCP server:', error);
process.exit(1);
});
process.on('SIGINT', async () => {
await server.stop();
process.exit(0);
});
}
@@ -0,0 +1,359 @@
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequest,
CallToolResult,
ListToolsRequest,
ListToolsResult,
Tool,
TextContent
} from '@modelcontextprotocol/sdk/types.js';
import { AgentInstance } from '../config/agent-swarm-config';
import { CLIToolManager } from '../cli-tool-manager';
import fetch from 'node-fetch';
interface SimplifiedPeerConfig {
agentName: string;
instance: AgentInstance;
port: number;
peers: string[];
sharedMCPs: MCPConfig[];
}
interface MCPConfig {
name: string;
type: 'stdio' | 'http';
command: string;
args?: string[];
env?: Record<string, string>;
}
export class SimplifiedPeerMCPServer {
private server: Server;
private config: SimplifiedPeerConfig;
private toolManager: CLIToolManager;
private callDepth: number = 0;
private maxDepth: number = 10;
constructor(config: SimplifiedPeerConfig) {
this.config = config;
this.toolManager = new CLIToolManager();
this.server = new Server(
{
name: `agent-${config.agentName}`,
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupHandlers();
}
private setupHandlers(): void {
this.server.setRequestHandler(ListToolsRequest, async () => {
const tools: Tool[] = [];
// One tool per peer agent
for (const peerName of this.config.peers) {
if (peerName !== this.config.agentName) {
tools.push({
name: peerName,
description: `Call the ${peerName} agent with any request. They can recursively query other agents.`,
inputSchema: {
type: 'object',
properties: {
request: {
type: 'string',
description: 'Your request to this agent',
},
context: {
type: 'object',
description: 'Optional context for the request',
},
},
required: ['request'],
},
});
}
}
// Add shared MCP tools (GitHub, Linear, Slack, etc.)
for (const mcp of this.config.sharedMCPs) {
tools.push({
name: mcp.name,
description: `Access ${mcp.name} MCP server`,
inputSchema: {
type: 'object',
properties: {
action: {
type: 'string',
description: `Action to perform with ${mcp.name}`,
},
params: {
type: 'object',
description: 'Parameters for the action',
},
},
required: ['action'],
},
});
}
// Add Hanzo MCP tools
tools.push(
{
name: 'read_file',
description: 'Read a file from the filesystem',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: 'File path to read' },
},
required: ['path'],
},
},
{
name: 'write_file',
description: 'Write content to a file',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: 'File path to write' },
content: { type: 'string', description: 'Content to write' },
},
required: ['path', 'content'],
},
},
{
name: 'search',
description: 'Search for patterns in files',
inputSchema: {
type: 'object',
properties: {
pattern: { type: 'string', description: 'Search pattern' },
path: { type: 'string', description: 'Path to search in' },
},
required: ['pattern'],
},
},
{
name: 'bash',
description: 'Execute a bash command',
inputSchema: {
type: 'object',
properties: {
command: { type: 'string', description: 'Command to execute' },
},
required: ['command'],
},
}
);
return { tools } as ListToolsResult;
});
this.server.setRequestHandler(CallToolRequest, async (request) => {
const { name, arguments: args } = request.params;
try {
let result: string;
// Check if it's a peer agent call
if (this.config.peers.includes(name) && name !== this.config.agentName) {
result = await this.callPeerAgent(name, args.request, args.context);
}
// Check if it's a shared MCP tool
else if (this.config.sharedMCPs.some(mcp => mcp.name === name)) {
result = await this.callSharedMCP(name, args.action, args.params);
}
// Handle Hanzo MCP tools
else {
result = await this.handleHanzoTool(name, args);
}
return {
content: [
{
type: 'text',
text: result,
} as TextContent,
],
} as CallToolResult;
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
} as TextContent,
],
isError: true,
} as CallToolResult;
}
});
}
private async callPeerAgent(peerName: string, request: string, context?: any): Promise<string> {
// Check recursion depth
if (this.callDepth >= this.maxDepth) {
return `[Max recursion depth reached]`;
}
this.callDepth++;
try {
// Build the prompt for the peer agent
const prompt = `
Request from ${this.config.agentName}: ${request}
${context ? `\nContext: ${JSON.stringify(context, null, 2)}` : ''}
You are ${peerName}. Process this request according to your expertise.
You have access to all other agents and can call them recursively if needed.
Available agents: ${this.config.peers.filter(p => p !== peerName).join(', ')}
`;
// In production, this would make an actual MCP call to the peer
// For now, simulate the response
const response = await this.simulatePeerResponse(peerName, prompt);
this.callDepth--;
return response;
} catch (error) {
this.callDepth--;
throw error;
}
}
private async callSharedMCP(mcpName: string, action: string, params?: any): Promise<string> {
const mcp = this.config.sharedMCPs.find(m => m.name === mcpName);
if (!mcp) {
throw new Error(`MCP ${mcpName} not found`);
}
// Handle different MCP servers
switch (mcpName) {
case 'github':
return await this.handleGitHubMCP(action, params);
case 'linear':
return await this.handleLinearMCP(action, params);
case 'slack':
return await this.handleSlackMCP(action, params);
case 'playwright':
return await this.handlePlaywrightMCP(action, params);
default:
return `[${mcpName}] ${action} with params: ${JSON.stringify(params)}`;
}
}
private async handleGitHubMCP(action: string, params: any): Promise<string> {
// GitHub MCP actions
switch (action) {
case 'create_issue':
return `Created GitHub issue: ${params.title}`;
case 'create_pr':
return `Created pull request: ${params.title}`;
case 'list_issues':
return `Found ${params.count || 10} issues`;
case 'review_pr':
return `Reviewed PR #${params.pr_number}`;
default:
return `GitHub action: ${action}`;
}
}
private async handleLinearMCP(action: string, params: any): Promise<string> {
// Linear MCP actions
switch (action) {
case 'create_issue':
return `Created Linear issue: ${params.title}`;
case 'update_issue':
return `Updated Linear issue: ${params.id}`;
case 'list_issues':
return `Found Linear issues for ${params.project}`;
default:
return `Linear action: ${action}`;
}
}
private async handleSlackMCP(action: string, params: any): Promise<string> {
// Slack MCP actions
switch (action) {
case 'send_message':
return `Sent Slack message to ${params.channel}`;
case 'create_channel':
return `Created Slack channel: ${params.name}`;
case 'list_channels':
return `Listed Slack channels`;
default:
return `Slack action: ${action}`;
}
}
private async handlePlaywrightMCP(action: string, params: any): Promise<string> {
// Playwright MCP actions
switch (action) {
case 'navigate':
return `Navigated to ${params.url}`;
case 'click':
return `Clicked element: ${params.selector}`;
case 'screenshot':
return `Captured screenshot: ${params.name}`;
case 'fill':
return `Filled ${params.selector} with value`;
default:
return `Playwright action: ${action}`;
}
}
private async handleHanzoTool(tool: string, args: any): Promise<string> {
switch (tool) {
case 'read_file':
return `[File content of ${args.path}]`;
case 'write_file':
return `Wrote to ${args.path}`;
case 'search':
return `Found matches for "${args.pattern}" in ${args.path || '.'}`;
case 'bash':
return `Executed: ${args.command}`;
default:
throw new Error(`Unknown tool: ${tool}`);
}
}
private async simulatePeerResponse(peerName: string, prompt: string): Promise<string> {
// In production, this would call the actual peer agent
// For now, return a simulated response
return `[${peerName}] Processed request: ${prompt.substring(0, 100)}...`;
}
async start(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error(`Agent ${this.config.agentName} MCP server started on port ${this.config.port}`);
}
async stop(): Promise<void> {
await this.server.close();
}
}
// CLI entry point
if (require.main === module) {
const config = JSON.parse(process.argv[2]) as SimplifiedPeerConfig;
const server = new SimplifiedPeerMCPServer(config);
server.start().catch((error) => {
console.error('Failed to start MCP server:', error);
process.exit(1);
});
process.on('SIGINT', async () => {
await server.stop();
process.exit(0);
});
}
@@ -0,0 +1,488 @@
import { EventEmitter } from 'events';
import { AgentSwarmManager, AgentInstance, MCPServerConfig } from '../config/agent-swarm-config';
import { CLIToolManager } from '../cli-tool-manager';
import { spawn, ChildProcess } from 'child_process';
import * as path from 'path';
interface AgentTask {
agentName: string;
task: string;
context?: any;
dependencies?: string[];
}
interface AgentResult {
agentName: string;
result: string;
error?: string;
duration: number;
}
interface MCPServer {
process: ChildProcess;
config: MCPServerConfig;
}
interface AgentMCPServer {
port: number;
process: ChildProcess;
agentName: string;
}
export class SwarmOrchestrator extends EventEmitter {
private swarmManager: AgentSwarmManager;
private toolManager: CLIToolManager;
private activeAgents: Map<string, any> = new Map();
private mcpServers: Map<string, MCPServer> = new Map();
private agentMCPServers: Map<string, AgentMCPServer> = new Map();
private results: Map<string, AgentResult> = new Map();
private basePort: number = 8000; // Base port for agent MCP servers
constructor() {
super();
this.swarmManager = new AgentSwarmManager();
this.toolManager = new CLIToolManager();
}
/**
* Initialize the swarm orchestrator
*/
async initialize(): Promise<void> {
// Load agent configuration
await this.swarmManager.loadConfig();
// Initialize tool manager
await this.toolManager.initialize();
// Start MCP servers for all agents
const agents = this.swarmManager.getAllAgents();
for (const [name, agent] of Object.entries(agents)) {
// Apply network configuration
const configuredAgent = this.swarmManager.applyNetworkConfig(agent, name);
// Start regular MCP servers
if (configuredAgent.mcps && configuredAgent.mcps.length > 0) {
await this.startMCPServers(name, configuredAgent.mcps);
}
// Start agent as MCP server if configured
if (configuredAgent.expose_as_mcp) {
await this.startAgentMCPServer(name, configuredAgent);
}
}
// Connect agents to each other via MCP
await this.connectAgentMCPServers();
}
/**
* Start MCP servers for an agent
*/
private async startMCPServers(agentName: string, mcps: MCPServerConfig[]): Promise<void> {
for (const mcp of mcps) {
const serverKey = `${agentName}-${mcp.name}`;
try {
const mcpProcess = spawn(mcp.command, mcp.args || [], {
env: { ...process.env, ...mcp.env },
stdio: mcp.type === 'stdio' ? ['pipe', 'pipe', 'pipe'] : 'inherit'
});
mcpProcess.on('error', (error) => {
console.error(`MCP server ${serverKey} error:`, error);
this.emit('mcp-error', { agentName, server: mcp.name, error });
});
mcpProcess.on('exit', (code) => {
console.log(`MCP server ${serverKey} exited with code ${code}`);
this.mcpServers.delete(serverKey);
});
this.mcpServers.set(serverKey, { process: mcpProcess, config: mcp });
this.emit('mcp-started', { agentName, server: mcp.name });
} catch (error) {
console.error(`Failed to start MCP server ${serverKey}:`, error);
this.emit('mcp-error', { agentName, server: mcp.name, error });
}
}
}
/**
* Execute a task with the main agent
*/
async executeTask(task: string, context?: any): Promise<AgentResult[]> {
const mainAgentName = this.swarmManager.getMainAgent() ?
Object.keys(this.swarmManager.getAllAgents()).find(
name => this.swarmManager.getAgent(name) === this.swarmManager.getMainAgent()
) : null;
if (!mainAgentName) {
throw new Error('No main agent configured');
}
// Clear previous results
this.results.clear();
// Execute with main agent
const result = await this.executeAgentTask(mainAgentName, task, context);
// Return all results including delegated tasks
return Array.from(this.results.values());
}
/**
* Execute a task with a specific agent
*/
async executeAgentTask(
agentName: string,
task: string,
context?: any
): Promise<AgentResult> {
const startTime = Date.now();
const agent = this.swarmManager.getAgent(agentName);
if (!agent) {
throw new Error(`Agent '${agentName}' not found`);
}
this.emit('agent-start', { agentName, task });
try {
// Change to agent's directory
const originalDir = process.cwd();
if (agent.directory && agent.directory !== '.') {
process.chdir(path.resolve(originalDir, agent.directory));
}
// Prepare agent prompt with context
const fullPrompt = this.buildAgentPrompt(agent, task, context);
// Get the appropriate tool for the model
const tool = this.getToolForModel(agent.model);
// Execute the task
const result = await this.toolManager.runTool(tool, fullPrompt, {
model: agent.model,
cwd: process.cwd(),
env: this.buildAgentEnvironment(agentName, agent)
});
// Change back to original directory
process.chdir(originalDir);
// Parse agent response for delegations
const delegations = this.parseDelegations(result, agent);
// Execute delegated tasks in parallel
if (delegations.length > 0) {
await this.executeDelegatedTasks(delegations, {
parentAgent: agentName,
parentTask: task,
context
});
}
const agentResult: AgentResult = {
agentName,
result,
duration: Date.now() - startTime
};
this.results.set(agentName, agentResult);
this.emit('agent-complete', agentResult);
return agentResult;
} catch (error) {
const agentResult: AgentResult = {
agentName,
result: '',
error: error instanceof Error ? error.message : String(error),
duration: Date.now() - startTime
};
this.results.set(agentName, agentResult);
this.emit('agent-error', agentResult);
return agentResult;
}
}
/**
* Build the full prompt for an agent
*/
private buildAgentPrompt(agent: AgentInstance, task: string, context?: any): string {
let prompt = agent.prompt + '\n\n';
// Add context if provided
if (context) {
prompt += `## Context\n${JSON.stringify(context, null, 2)}\n\n`;
}
// Add task
prompt += `## Task\n${task}\n\n`;
// Simplified: Each agent is just one tool
const allAgents = this.swarmManager.getAllAgents();
const agentName = this.getAgentName(agent);
prompt += `## Available Agents (as Tools)\n`;
prompt += `Each agent is available as a single tool. Just use their name:\n\n`;
for (const [name, otherAgent] of Object.entries(allAgents)) {
if (name !== agentName) {
prompt += `- **${name}**: ${otherAgent.description}\n`;
}
}
prompt += `\nTo use an agent, call the tool with their name and your request.\n`;
prompt += `Example: Use tool "developer" with request: "implement the login feature"\n\n`;
// Add shared MCP tools if configured
prompt += `## Shared Tools\n`;
prompt += `- **read_file**: Read file content\n`;
prompt += `- **write_file**: Write to file\n`;
prompt += `- **search**: Search in files\n`;
prompt += `- **bash**: Execute commands\n`;
prompt += `- **github**: GitHub integration (if configured)\n`;
prompt += `- **linear**: Linear project management (if configured)\n`;
prompt += `- **slack**: Slack messaging (if configured)\n`;
prompt += `- **playwright**: Web automation (if configured)\n\n`;
prompt += `Agents can recursively call each other. Be mindful of recursion depth.\n`;
return prompt;
}
/**
* Get agent name from the swarm manager
*/
private getAgentName(agent: AgentInstance): string {
const agents = this.swarmManager.getAllAgents();
for (const [name, a] of Object.entries(agents)) {
if (a === agent) return name;
}
return 'unknown';
}
/**
* Build environment variables for an agent
*/
private buildAgentEnvironment(agentName: string, agent: AgentInstance): Record<string, string> {
const env: Record<string, string> = {
...process.env,
AGENT_NAME: agentName,
AGENT_MODEL: agent.model,
AGENT_DIR: agent.directory
};
// Add MCP server endpoints if available
const mcpServers = Array.from(this.mcpServers.entries())
.filter(([key]) => key.startsWith(`${agentName}-`));
if (mcpServers.length > 0) {
env.MCP_SERVERS = mcpServers.map(([key]) => key).join(',');
}
return env;
}
/**
* Get the appropriate tool for a model
*/
private getToolForModel(model: string): string {
const modelMap: Record<string, string> = {
'opus': 'claude',
'sonnet': 'claude',
'haiku': 'claude',
'gpt-4': 'codex',
'gpt-3.5': 'codex',
'gemini-pro': 'gemini',
'gemini-ultra': 'gemini'
};
return modelMap[model.toLowerCase()] || 'claude';
}
/**
* Parse agent response for delegation commands
*/
private parseDelegations(response: string, agent: AgentInstance): AgentTask[] {
if (!agent.connections || agent.connections.length === 0) {
return [];
}
const delegations: AgentTask[] = [];
const delegateRegex = /DELEGATE TO \[([^\]]+)\]: (.+?)(?=DELEGATE TO|$)/gs;
let match;
while ((match = delegateRegex.exec(response)) !== null) {
const agentName = match[1].trim();
const task = match[2].trim();
if (agent.connections.includes(agentName)) {
delegations.push({ agentName, task });
}
}
return delegations;
}
/**
* Execute delegated tasks in parallel
*/
private async executeDelegatedTasks(
tasks: AgentTask[],
context: any
): Promise<void> {
const promises = tasks.map(task =>
this.executeAgentTask(task.agentName, task.task, context)
);
await Promise.all(promises);
}
/**
* Start an agent as an MCP server
*/
private async startAgentMCPServer(agentName: string, agent: AgentInstance): Promise<void> {
const port = agent.mcp_port || this.basePort++;
try {
// Create MCP server process for the agent
const agentConfigJson = JSON.stringify(agent);
const mcpServerPath = path.join(__dirname, 'agent-mcp-server.js');
const mcpProcess = spawn('node', [
mcpServerPath,
agentName,
agentConfigJson
], {
stdio: ['pipe', 'pipe', 'pipe'],
env: {
...process.env,
AGENT_MCP_PORT: port.toString()
}
});
mcpProcess.on('error', (error) => {
console.error(`Agent MCP server ${agentName} error:`, error);
this.emit('agent-mcp-error', { agentName, error });
});
mcpProcess.on('exit', (code) => {
console.log(`Agent MCP server ${agentName} exited with code ${code}`);
this.agentMCPServers.delete(agentName);
});
// Log stderr for debugging
mcpProcess.stderr.on('data', (data) => {
console.error(`[${agentName}] ${data.toString()}`);
});
this.agentMCPServers.set(agentName, {
port,
process: mcpProcess,
agentName
});
this.emit('agent-mcp-started', { agentName, port });
} catch (error) {
console.error(`Failed to start agent MCP server for ${agentName}:`, error);
this.emit('agent-mcp-error', { agentName, error });
}
}
/**
* Connect agents to each other via MCP
*/
private async connectAgentMCPServers(): Promise<void> {
const agents = this.swarmManager.getAllAgents();
for (const [name, agent] of Object.entries(agents)) {
const configuredAgent = this.swarmManager.applyNetworkConfig(agent, name);
if (configuredAgent.connect_to_agents && configuredAgent.connect_to_agents.length > 0) {
for (const targetAgent of configuredAgent.connect_to_agents) {
const targetServer = this.agentMCPServers.get(targetAgent);
if (targetServer) {
// Create MCP client configuration for connecting to the target agent
const mcpConfig: MCPServerConfig = {
name: `agent-${targetAgent}`,
type: 'stdio',
command: 'node',
args: [
path.join(__dirname, 'agent-mcp-server.js'),
targetAgent,
JSON.stringify(this.swarmManager.getAgent(targetAgent))
],
env: {
AGENT_MCP_PORT: targetServer.port.toString(),
AGENT_NAME: targetAgent
}
};
// Store the MCP connection for this agent
const connectionKey = `${name}-connects-to-${targetAgent}`;
await this.startMCPServers(connectionKey, [mcpConfig]);
}
}
}
}
}
/**
* Shutdown the orchestrator
*/
async shutdown(): Promise<void> {
// Stop all MCP servers
for (const [key, server] of this.mcpServers.entries()) {
server.process.kill();
this.mcpServers.delete(key);
}
// Stop all agent MCP servers
for (const [key, server] of this.agentMCPServers.entries()) {
server.process.kill();
this.agentMCPServers.delete(key);
}
// Clear active agents
this.activeAgents.clear();
this.emit('shutdown');
}
/**
* Get swarm status
*/
getStatus(): {
agents: Record<string, { status: string; directory: string; model: string }>;
mcpServers: Record<string, { status: string; config: MCPServerConfig }>;
results: AgentResult[];
} {
const agents: Record<string, any> = {};
const allAgents = this.swarmManager.getAllAgents();
for (const [name, agent] of Object.entries(allAgents)) {
agents[name] = {
status: this.activeAgents.has(name) ? 'active' : 'ready',
directory: agent.directory,
model: agent.model
};
}
const mcpServers: Record<string, any> = {};
for (const [key, server] of this.mcpServers.entries()) {
mcpServers[key] = {
status: server.process.exitCode === null ? 'running' : 'stopped',
config: server.config
};
}
return {
agents,
mcpServers,
results: Array.from(this.results.values())
};
}
}
+301
View File
@@ -531,6 +531,307 @@ program
})
);
// Swarm command - manage agent swarms
program
.command('swarm')
.description('Manage AI agent swarms for complex multi-agent workflows')
.addCommand(
new Command('init')
.description('Initialize a new agent swarm configuration')
.option('-p, --path <path>', 'Path for the configuration file', '.hanzo/agents.yaml')
.option('--peer-network', 'Initialize as peer network with Hanzo Zen main loop')
.action(async (options) => {
const spinner = ora('Initializing agent swarm configuration...').start();
try {
const { AgentSwarmManager } = await import('../cli-tools/config/agent-swarm-config');
if (options.peerNetwork) {
// Create peer network configuration
await AgentSwarmManager.initPeerNetworkConfig(options.path);
spinner.succeed('Peer network configuration initialized with Hanzo Zen!');
console.log(chalk.cyan('Using Hanzo Zen for cost-effective local orchestration'));
} else {
await AgentSwarmManager.initConfig(options.path);
spinner.succeed('Agent swarm configuration initialized!');
}
console.log(chalk.gray(`Edit the configuration file to customize your agent swarm.`));
} catch (error) {
spinner.fail(`Failed to initialize: ${error.message}`);
process.exit(1);
}
})
)
.addCommand(
new Command('run <task>')
.description('Run a task with the configured agent swarm')
.option('-c, --config <path>', 'Path to agent configuration file')
.option('--peer', 'Run as peer network with Hanzo Zen orchestration')
.option('--critic', 'Include critic analysis in results')
.option('--local-llm <endpoint>', 'Local LLM endpoint', 'http://localhost:8080')
.action(async (task, options) => {
const spinner = ora('Starting agent swarm...').start();
try {
if (options.peer) {
// Use peer network with local Hanzo Zen
const { PeerAgentNetwork } = await import('../cli-tools/orchestration/peer-agent-network');
const network = new PeerAgentNetwork({
mainLoopLLM: {
model: 'hanzo-zen',
endpoint: options.localLlm
},
enableRecursiveCalls: true,
maxRecursionDepth: 10,
costOptimization: true
});
await network.initialize();
spinner.succeed('Peer network initialized with Hanzo Zen');
console.log(chalk.cyan('\n🌐 Executing with peer network...'));
console.log(chalk.gray('Using local Hanzo Zen for orchestration (cost-optimized)'));
const results = await network.executeTask(task, {
requireCritic: options.critic
});
console.log(chalk.bold('\n📊 Peer Network Results:\n'));
console.log(JSON.stringify(results, null, 2));
await network.shutdown();
} else {
// Traditional swarm orchestration
const { SwarmOrchestrator } = await import('../cli-tools/orchestration/swarm-orchestrator');
const orchestrator = new SwarmOrchestrator();
// Initialize with custom config path if provided
if (options.config) {
const { AgentSwarmManager } = await import('../cli-tools/config/agent-swarm-config');
const manager = new AgentSwarmManager(options.config);
await manager.loadConfig();
}
await orchestrator.initialize();
spinner.succeed('Agent swarm initialized');
console.log(chalk.cyan('\nExecuting task with agent swarm...'));
const results = await orchestrator.executeTask(task);
console.log(chalk.bold('\n📊 Swarm Results:\n'));
for (const result of results) {
console.log(chalk.cyan(`🤖 ${result.agentName}:`));
if (result.error) {
console.log(chalk.red(` ❌ Error: ${result.error}`));
} else {
console.log(` ✅ Completed in ${result.duration}ms`);
console.log(chalk.gray(` ${result.result.substring(0, 200)}...`));
}
console.log();
}
await orchestrator.shutdown();
}
} catch (error) {
spinner.fail(`Execution failed: ${error.message}`);
process.exit(1);
}
})
)
.addCommand(
new Command('status')
.description('Show status of the current agent swarm')
.option('-c, --config <path>', 'Path to agent configuration file')
.action(async (options) => {
try {
const { SwarmOrchestrator } = await import('../cli-tools/orchestration/swarm-orchestrator');
const orchestrator = new SwarmOrchestrator();
if (options.config) {
const { AgentSwarmManager } = await import('../cli-tools/config/agent-swarm-config');
const manager = new AgentSwarmManager(options.config);
await manager.loadConfig();
}
await orchestrator.initialize();
const status = orchestrator.getStatus();
console.log(chalk.bold('🐝 Agent Swarm Status\n'));
// Show agents
console.log(chalk.cyan('Agents:'));
for (const [name, agent] of Object.entries(status.agents)) {
const statusColor = agent.status === 'active' ? chalk.green : chalk.gray;
console.log(` ${statusColor('●')} ${name} - ${agent.model} (${agent.directory})`);
}
// Show MCP servers
if (Object.keys(status.mcpServers).length > 0) {
console.log(chalk.cyan('\nMCP Servers:'));
for (const [key, server] of Object.entries(status.mcpServers)) {
const statusColor = server.status === 'running' ? chalk.green : chalk.red;
console.log(` ${statusColor('●')} ${key} - ${server.config.command}`);
}
}
// Show recent results
if (status.results.length > 0) {
console.log(chalk.cyan('\nRecent Results:'));
for (const result of status.results) {
const icon = result.error ? '❌' : '✅';
console.log(` ${icon} ${result.agentName} - ${result.duration}ms`);
}
}
await orchestrator.shutdown();
} catch (error) {
console.error(chalk.red(`Error checking status: ${error.message}`));
process.exit(1);
}
})
)
.addCommand(
new Command('network <networkName> [task]')
.description('Run a task with agents in a specific network')
.option('-c, --config <path>', 'Path to agent configuration file')
.action(async (networkName, task, options) => {
const spinner = ora('Starting network agents...').start();
try {
const { SwarmOrchestrator } = await import('../cli-tools/orchestration/swarm-orchestrator');
const { AgentSwarmManager } = await import('../cli-tools/config/agent-swarm-config');
const manager = new AgentSwarmManager(options.config);
await manager.loadConfig();
const network = manager.getNetwork(networkName);
if (!network) {
spinner.fail(`Network '${networkName}' not found`);
return;
}
spinner.succeed(`Network '${networkName}' loaded`);
console.log(chalk.cyan(`\nAgents in network: ${network.agents.join(', ')}`));
if (task) {
const orchestrator = new SwarmOrchestrator();
await orchestrator.initialize();
console.log(chalk.cyan('\nExecuting task with network agents...'));
// Execute task with first agent in network as main
const mainAgent = network.agents[0];
const results = await orchestrator.executeTask(task);
console.log(chalk.bold('\n📊 Network Results:\n'));
for (const result of results) {
if (network.agents.includes(result.agentName)) {
console.log(chalk.cyan(`🤖 ${result.agentName}:`));
if (result.error) {
console.log(chalk.red(` ❌ Error: ${result.error}`));
} else {
console.log(` ✅ Completed in ${result.duration}ms`);
console.log(chalk.gray(` ${result.result.substring(0, 200)}...`));
}
console.log();
}
}
await orchestrator.shutdown();
} else {
console.log(chalk.gray('\nProvide a task to execute with this network'));
}
} catch (error) {
spinner.fail(`Network operation failed: ${error.message}`);
process.exit(1);
}
})
)
.addCommand(
new Command('chat')
.description('Start an interactive chat session between agents')
.option('-c, --config <path>', 'Path to agent configuration file')
.option('-f, --from <agent>', 'Agent to chat from', 'project_manager')
.option('-t, --to <agent>', 'Agent to chat with')
.action(async (options) => {
try {
const { SwarmOrchestrator } = await import('../cli-tools/orchestration/swarm-orchestrator');
const { AgentSwarmManager } = await import('../cli-tools/config/agent-swarm-config');
const manager = new AgentSwarmManager(options.config);
await manager.loadConfig();
const fromAgent = manager.getAgent(options.from);
const toAgent = options.to ? manager.getAgent(options.to) : null;
if (!fromAgent) {
console.error(chalk.red(`Agent '${options.from}' not found`));
return;
}
console.log(chalk.bold('🗨️ Agent Chat Session\n'));
console.log(chalk.cyan(`Chatting as: ${options.from}`));
if (toAgent) {
console.log(chalk.cyan(`Chatting with: ${options.to}`));
} else {
console.log(chalk.gray('Type "@agent_name message" to chat with a specific agent'));
}
console.log(chalk.gray('Type "exit" to end the session\n'));
const orchestrator = new SwarmOrchestrator();
await orchestrator.initialize();
// Interactive chat loop
while (true) {
const { message } = await inquirer.prompt([{
type: 'input',
name: 'message',
message: `${options.from}>`,
}]);
if (message.toLowerCase() === 'exit') {
break;
}
// Parse @mentions
const mentionMatch = message.match(/^@(\w+)\s+(.+)/);
const targetAgent = mentionMatch ? mentionMatch[1] : options.to;
const actualMessage = mentionMatch ? mentionMatch[2] : message;
if (!targetAgent) {
console.log(chalk.yellow('Please specify a target agent with @agent_name or use -t flag'));
continue;
}
const spinner = ora(`${options.from} is thinking...`).start();
try {
// Simulate the from agent using MCP to chat with target
const prompt = `You need to send this message to ${targetAgent}: "${actualMessage}"\n\n` +
`Use the chat_with_${targetAgent} tool to send the message and get a response.`;
const result = await orchestrator.executeAgentTask(options.from, prompt);
spinner.stop();
console.log(chalk.green(`\n${targetAgent}> ${result}\n`));
} catch (error) {
spinner.fail(`Chat failed: ${error.message}`);
}
}
await orchestrator.shutdown();
console.log(chalk.gray('\nChat session ended'));
} catch (error) {
console.error(chalk.red(`Error: ${error.message}`));
process.exit(1);
}
})
);
// Interactive mode
program
.command('interactive')
@@ -0,0 +1,526 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import * as yaml from 'js-yaml';
import {
AgentSwarmManager,
AgentInstance,
AgentSwarmConfig,
NetworkConfig,
MCPServerConfig,
SharedMCPConfig
} from '../../../cli-tools/config/agent-swarm-config';
vi.mock('fs');
vi.mock('path');
describe('AgentSwarmManager', () => {
let manager: AgentSwarmManager;
const mockConfigPath = '/test/agents.yaml';
const mockConfig: AgentSwarmConfig = {
version: 1,
swarm: {
name: 'Test Swarm',
main: 'coordinator',
instances: {
coordinator: {
description: 'Main coordinator',
directory: '.',
model: 'opus',
connections: ['developer', 'reviewer'],
expose_as_mcp: true,
mcp_port: 8000,
connect_to_agents: ['developer', 'reviewer'],
prompt: 'You are the coordinator',
allowed_tools: ['Read', 'Write'],
mcps: [{
name: 'filesystem',
type: 'stdio',
command: 'npx',
args: ['mcp-server-filesystem']
}]
},
developer: {
description: 'Developer agent',
directory: './src',
model: 'sonnet',
prompt: 'You are a developer'
},
reviewer: {
description: 'Code reviewer',
directory: '.',
model: 'haiku',
prompt: 'You are a reviewer'
}
},
networks: {
core: {
name: 'Core Team',
agents: ['coordinator', 'developer', 'reviewer'],
mcp_enabled: true,
shared_tools: ['Read', 'Grep']
}
},
shared_mcps: {
github: { enabled: true, token: 'test-token' },
linear: { enabled: true, apiKey: 'test-key' },
slack: { enabled: false },
custom: [{
name: 'postgres',
type: 'stdio',
command: 'pg-mcp',
env: { DATABASE_URL: 'postgres://test' }
}]
}
}
};
beforeEach(() => {
vi.clearAllMocks();
manager = new AgentSwarmManager(mockConfigPath);
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('loadConfig', () => {
it('should load configuration from default path', async () => {
const configYaml = yaml.dump(mockConfig);
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(configYaml);
const config = await manager.loadConfig();
expect(config).toEqual(mockConfig);
expect(fs.existsSync).toHaveBeenCalled();
expect(fs.readFileSync).toHaveBeenCalledWith(mockConfigPath, 'utf8');
});
it('should try multiple paths if default not found', async () => {
const configYaml = yaml.dump(mockConfig);
vi.mocked(fs.existsSync)
.mockReturnValueOnce(false) // .hanzo/agents.yaml
.mockReturnValueOnce(false) // agents.yaml
.mockReturnValueOnce(true); // .agents.yaml
vi.mocked(fs.readFileSync).mockReturnValue(configYaml);
const config = await manager.loadConfig();
expect(config).toEqual(mockConfig);
expect(fs.existsSync).toHaveBeenCalledTimes(3);
});
it('should throw error if no config found', async () => {
vi.mocked(fs.existsSync).mockReturnValue(false);
await expect(manager.loadConfig()).rejects.toThrow('No agent configuration found');
});
it('should throw error for invalid version', async () => {
const invalidConfig = { ...mockConfig, version: 2 };
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(invalidConfig));
await expect(manager.loadConfig()).rejects.toThrow('Invalid or missing version');
});
it('should throw error for missing main agent', async () => {
const invalidConfig = {
...mockConfig,
swarm: {
...mockConfig.swarm,
main: 'nonexistent'
}
};
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(invalidConfig));
await expect(manager.loadConfig()).rejects.toThrow("Main agent 'nonexistent' not found");
});
it('should throw error for invalid agent connections', async () => {
const invalidConfig = {
...mockConfig,
swarm: {
...mockConfig.swarm,
instances: {
...mockConfig.swarm.instances,
coordinator: {
...mockConfig.swarm.instances.coordinator,
connections: ['nonexistent']
}
}
}
};
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(invalidConfig));
await expect(manager.loadConfig()).rejects.toThrow("invalid connection to 'nonexistent'");
});
});
describe('getMainAgent', () => {
it('should return main agent when config loaded', async () => {
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(mockConfig));
await manager.loadConfig();
const mainAgent = manager.getMainAgent();
expect(mainAgent).toEqual(mockConfig.swarm.instances.coordinator);
});
it('should return null when no config loaded', () => {
const mainAgent = manager.getMainAgent();
expect(mainAgent).toBeNull();
});
});
describe('getAgent', () => {
beforeEach(async () => {
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(mockConfig));
await manager.loadConfig();
});
it('should return specific agent', () => {
const agent = manager.getAgent('developer');
expect(agent).toEqual(mockConfig.swarm.instances.developer);
});
it('should return null for non-existent agent', () => {
const agent = manager.getAgent('nonexistent');
expect(agent).toBeNull();
});
});
describe('getAllAgents', () => {
it('should return all agents', async () => {
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(mockConfig));
await manager.loadConfig();
const agents = manager.getAllAgents();
expect(agents).toEqual(mockConfig.swarm.instances);
expect(Object.keys(agents)).toHaveLength(3);
});
it('should return empty object when no config', () => {
const agents = manager.getAllAgents();
expect(agents).toEqual({});
});
});
describe('getConnectedAgents', () => {
beforeEach(async () => {
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(mockConfig));
await manager.loadConfig();
});
it('should return connected agents', () => {
const connected = manager.getConnectedAgents('coordinator');
expect(connected).toHaveLength(2);
expect(connected[0]).toEqual(mockConfig.swarm.instances.developer);
expect(connected[1]).toEqual(mockConfig.swarm.instances.reviewer);
});
it('should return empty array for agent without connections', () => {
const connected = manager.getConnectedAgents('developer');
expect(connected).toEqual([]);
});
});
describe('getNetwork', () => {
beforeEach(async () => {
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(mockConfig));
await manager.loadConfig();
});
it('should return network configuration', () => {
const network = manager.getNetwork('core');
expect(network).toEqual(mockConfig.swarm.networks!.core);
});
it('should return null for non-existent network', () => {
const network = manager.getNetwork('nonexistent');
expect(network).toBeNull();
});
});
describe('getNetworkAgents', () => {
beforeEach(async () => {
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(mockConfig));
await manager.loadConfig();
});
it('should return agents in network', () => {
const agents = manager.getNetworkAgents('core');
expect(agents).toHaveLength(3);
expect(agents.map(a => a.description)).toContain('Main coordinator');
});
it('should return empty array for non-existent network', () => {
const agents = manager.getNetworkAgents('nonexistent');
expect(agents).toEqual([]);
});
});
describe('getAgentNetworks', () => {
beforeEach(async () => {
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(mockConfig));
await manager.loadConfig();
});
it('should return networks containing agent', () => {
const networks = manager.getAgentNetworks('developer');
expect(networks).toHaveLength(1);
expect(networks[0].name).toBe('Core Team');
});
it('should return empty array for agent not in any network', () => {
const testConfig = {
...mockConfig,
swarm: {
...mockConfig.swarm,
instances: {
...mockConfig.swarm.instances,
isolated: {
description: 'Isolated agent',
directory: '.',
model: 'haiku',
prompt: 'Isolated'
}
}
}
};
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(testConfig));
manager = new AgentSwarmManager(mockConfigPath);
manager.loadConfig();
const networks = manager.getAgentNetworks('isolated');
expect(networks).toEqual([]);
});
});
describe('applyNetworkConfig', () => {
beforeEach(async () => {
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(mockConfig));
await manager.loadConfig();
});
it('should apply network configuration to agent', () => {
const agent = manager.getAgent('developer')!;
const configured = manager.applyNetworkConfig(agent, 'developer');
expect(configured.allowed_tools).toContain('Read');
expect(configured.allowed_tools).toContain('Grep');
expect(configured.connect_to_agents).toContain('coordinator');
expect(configured.connect_to_agents).toContain('reviewer');
});
it('should handle agent not in any network', () => {
const agent: AgentInstance = {
description: 'Test',
directory: '.',
model: 'haiku',
prompt: 'Test'
};
const configured = manager.applyNetworkConfig(agent, 'nonexistent');
expect(configured).toEqual(agent);
});
it('should remove duplicates from arrays', () => {
const testConfig = {
...mockConfig,
swarm: {
...mockConfig.swarm,
networks: {
net1: {
name: 'Network 1',
agents: ['developer'],
mcp_enabled: true,
shared_tools: ['Read', 'Write']
},
net2: {
name: 'Network 2',
agents: ['developer'],
mcp_enabled: true,
shared_tools: ['Read', 'Grep']
}
}
}
};
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(testConfig));
manager = new AgentSwarmManager(mockConfigPath);
manager.loadConfig();
const agent = manager.getAgent('developer')!;
const configured = manager.applyNetworkConfig(agent, 'developer');
// Should have unique tools
const uniqueTools = [...new Set(configured.allowed_tools)];
expect(configured.allowed_tools).toEqual(uniqueTools);
});
});
describe('getConfig', () => {
it('should return full configuration', async () => {
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(mockConfig));
await manager.loadConfig();
const config = manager.getConfig();
expect(config).toEqual(mockConfig);
});
it('should return null when no config loaded', () => {
const config = manager.getConfig();
expect(config).toBeNull();
});
});
describe('static methods', () => {
describe('initConfig', () => {
it('should create new configuration file', async () => {
vi.mocked(fs.existsSync).mockReturnValue(false);
vi.mocked(fs.mkdirSync).mockImplementation(() => undefined);
vi.mocked(fs.writeFileSync).mockImplementation(() => undefined);
vi.mocked(path.dirname).mockReturnValue('.hanzo');
await AgentSwarmManager.initConfig('/test/agents.yaml');
expect(fs.mkdirSync).toHaveBeenCalledWith('.hanzo', { recursive: true });
expect(fs.writeFileSync).toHaveBeenCalled();
});
it('should throw if config already exists', async () => {
vi.mocked(fs.existsSync).mockReturnValue(true);
await expect(AgentSwarmManager.initConfig('/test/agents.yaml'))
.rejects.toThrow('Configuration already exists');
});
});
describe('initPeerNetworkConfig', () => {
it('should create peer network configuration', async () => {
vi.mocked(fs.existsSync).mockReturnValue(false);
vi.mocked(fs.mkdirSync).mockImplementation(() => undefined);
vi.mocked(fs.writeFileSync).mockImplementation(() => undefined);
vi.mocked(path.dirname).mockReturnValue('.hanzo');
await AgentSwarmManager.initPeerNetworkConfig('/test/agents-peer.yaml');
expect(fs.writeFileSync).toHaveBeenCalled();
const writtenContent = vi.mocked(fs.writeFileSync).mock.calls[0][1] as string;
expect(writtenContent).toContain('hanzo-zen');
expect(writtenContent).toContain('network_type: peer');
});
});
describe('createSampleConfig', () => {
it('should create valid sample configuration', () => {
const sampleYaml = AgentSwarmManager.createSampleConfig();
const sample = yaml.load(sampleYaml) as AgentSwarmConfig;
expect(sample.version).toBe(1);
expect(sample.swarm.name).toBe('Development Team');
expect(sample.swarm.main).toBe('architect');
expect(Object.keys(sample.swarm.instances)).toContain('architect');
expect(Object.keys(sample.swarm.instances)).toContain('frontend');
});
});
describe('createPeerNetworkConfig', () => {
it('should create valid peer network configuration', () => {
const peerYaml = AgentSwarmManager.createPeerNetworkConfig();
const peer = yaml.load(peerYaml) as any;
expect(peer.version).toBe(1);
expect(peer.swarm.network_type).toBe('peer');
expect(peer.swarm.local_llm.model).toBe('hanzo-zen');
expect(Object.keys(peer.swarm.instances)).toContain('orchestrator');
expect(peer.swarm.instances.orchestrator.model).toBe('zen');
});
});
});
describe('edge cases', () => {
it('should handle empty networks configuration', async () => {
const configWithoutNetworks = {
...mockConfig,
swarm: {
...mockConfig.swarm,
networks: undefined
}
};
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(configWithoutNetworks));
await manager.loadConfig();
const network = manager.getNetwork('any');
expect(network).toBeNull();
const networks = manager.getAgentNetworks('coordinator');
expect(networks).toEqual([]);
});
it('should handle agent without required fields', async () => {
const invalidAgent = {
...mockConfig,
swarm: {
...mockConfig.swarm,
instances: {
invalid: {
description: 'Invalid agent'
// Missing required fields
}
}
}
};
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(invalidAgent));
await expect(manager.loadConfig()).rejects.toThrow('missing required fields');
});
it('should handle invalid MCP server config', async () => {
const invalidMCP = {
...mockConfig,
swarm: {
...mockConfig.swarm,
instances: {
...mockConfig.swarm.instances,
coordinator: {
...mockConfig.swarm.instances.coordinator,
mcps: [{
name: 'invalid'
// Missing required fields
}]
}
}
}
};
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue(yaml.dump(invalidMCP));
await expect(manager.loadConfig()).rejects.toThrow('Invalid MCP server configuration');
});
});
});