bots-and-co/PLAN.md
2025-11-27 09:02:10 +01:00

27 KiB

Agent Orchestra

A multi-agent system where Claude-powered agents collaborate via Slack, each running in isolated Docker containers with role-based permissions and tools.


Project Overview

Goal: Build a server that orchestrates multiple async Claude agents (using Claude Agent SDK) that communicate through Slack, manage projects together, and can delegate work to each other.

Key Concepts:

  • Agents defined via YAML (roles, tools, permissions, model)
  • Slack as the communication layer (threads, channels, mentions)
  • Each agent runs in its own persistent Docker container
  • Shared git repo with branch-based access control
  • Simple JSON-based project/task management
  • CEO agent as supervisor/orchestrator

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        Host Machine                              │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │                    Orchestrator Service                      ││
│  │  - Slack event listener (Socket Mode)                       ││
│  │  - Agent lifecycle management                               ││
│  │  - Message routing                                          ││
│  │  - MCP server exposure                                      ││
│  └─────────────────────────────────────────────────────────────┘│
│                              │                                   │
│         ┌────────────────────┼────────────────────┐             │
│         ▼                    ▼                    ▼             │
│  ┌─────────────┐     ┌─────────────┐      ┌─────────────┐       │
│  │  CEO Agent  │     │  PM Agent   │      │  Dev Agent  │       │
│  │  Container  │     │  Container  │      │  Container  │       │
│  │             │     │             │      │             │       │
│  │ - memory/   │     │ - memory/   │      │ - memory/   │       │
│  │ - tools     │     │ - tools     │      │ - tools     │       │
│  │ - git clone │     │ - git clone │      │ - git clone │       │
│  └─────────────┘     └─────────────┘      └─────────────┘       │
│         │                    │                    │             │
│         └────────────────────┼────────────────────┘             │
│                              ▼                                   │
│                    ┌─────────────────┐                          │
│                    │  Shared Volume  │                          │
│                    │  - /repos/      │                          │
│                    │  - /projects/   │                          │
│                    │  - /memory/     │                          │
│                    └─────────────────┘                          │
└─────────────────────────────────────────────────────────────────┘

Directory Structure

agent-orchestra/
├── CLAUDE.md                    # Claude Code instructions
├── README.md
├── pyproject.toml
├── docker-compose.yml
├── Dockerfile.agent             # Base image for agent containers
│
├── config/
│   ├── orchestra.yml            # Global config (Slack tokens, git repo, etc.)
│   └── agents/
│       ├── ceo.yml
│       ├── product_manager.yml
│       ├── developer.yml
│       ├── tech_lead.yml
│       └── designer.yml
│
├── src/
│   └── orchestra/
│       ├── __init__.py
│       ├── main.py              # Entry point
│       │
│       ├── core/
│       │   ├── __init__.py
│       │   ├── orchestrator.py  # Main orchestrator service
│       │   ├── agent.py         # Agent class (wraps Claude SDK)
│       │   ├── config.py        # YAML config loading/validation
│       │   └── container.py     # Docker container management
│       │
│       ├── slack/
│       │   ├── __init__.py
│       │   ├── client.py        # Slack API wrapper
│       │   ├── events.py        # Event handlers
│       │   └── formatter.py     # Message formatting
│       │
│       ├── tools/
│       │   ├── __init__.py
│       │   ├── registry.py      # Tool registration & permissions
│       │   ├── filesystem.py    # File operations
│       │   ├── git.py           # Git operations
│       │   ├── slack.py         # Slack tools (create_channel, etc.)
│       │   ├── tasks.py         # Project management
│       │   ├── web_search.py    # Web search
│       │   └── code_exec.py     # Code execution
│       │
│       ├── mcp/
│       │   ├── __init__.py
│       │   ├── server.py        # MCP server implementation
│       │   └── client.py        # MCP client for external servers
│       │
│       └── memory/
│           ├── __init__.py
│           └── manager.py       # Memory file management
│
├── data/                        # Mounted as shared volume
│   ├── repos/                   # Shared git repositories
│   ├── projects/                # Project JSON files
│   │   └── boards.json
│   └── memory/                  # Agent memory folders
│       ├── ceo/
│       │   └── memory.md
│       ├── product_manager/
│       │   └── memory.md
│       └── developer/
│           └── memory.md
│
└── tests/
    ├── __init__.py
    ├── test_agent.py
    ├── test_tools.py
    └── test_slack.py

Configuration Schema

Global Config (config/orchestra.yml)

slack:
  app_token: "${SLACK_APP_TOKEN}"
  bot_token: "${SLACK_BOT_TOKEN}"
  default_channel: "general"

git:
  provider: "gitea"
  base_url: "${GITEA_URL}"          # e.g., https://git.example.com
  api_token: "${GITEA_API_TOKEN}"
  repo_owner: "${GITEA_REPO_OWNER}"
  repo_name: "${GITEA_REPO_NAME}"
  main_branch: "main"
  dev_branch: "dev"

docker:
  network: "orchestra-net"
  base_image: "agent-orchestra:latest"

mcp:
  expose_port: 8080
  external_servers:
    - name: "filesystem"
      url: "stdio://mcp-filesystem"

CEO Agent Config (config/agents/ceo.yml)

id: ceo
name: "CEO Chris"
slack_user_id: "${SLACK_CEO_USER_ID}"

model: "claude-sonnet-4-20250514"
max_tokens: 8192

system_prompt: |
  You are CEO Chris, the supervisor of the agent team.
  You have broad oversight and can step in when needed.
  
  Your responsibilities:
  - Monitor team progress and intervene if blocked
  - Make high-level decisions when there's disagreement
  - Ensure quality standards are maintained
  - Approve major architectural decisions
  - Merge approved changes to main branch
  
  You generally let the team operate autonomously but
  provide guidance when asked or when you notice issues.
  
  You have access to all tools and can perform any operation.

tools:
  filesystem:
    enabled: true
    allowed_paths:
      - "/**"
  
  git:
    enabled: true
    allowed_operations:
      - "*"  # All operations
    denied_operations: []
  
  slack:
    enabled: true
    allowed_operations:
      - "*"
  
  tasks:
    enabled: true
    allowed_operations:
      - "*"
  
  code_exec:
    enabled: true
    allowed_languages:
      - python
      - javascript
      - bash
    timeout_seconds: 600
  
  web_search:
    enabled: true

memory:
  path: "/memory/ceo"
  files:
    - "memory.md"

can_mention:
  - "*"  # Can mention anyone

can_delegate_to:
  - "*"  # Can delegate to anyone

container:
  resources:
    memory: "2g"
    cpus: "1.0"

Tech Lead Config (config/agents/tech_lead.yml)

id: tech_lead
name: "Tech Lead Terry"
slack_user_id: "${SLACK_TECHLEAD_USER_ID}"

model: "claude-sonnet-4-20250514"
max_tokens: 8192

system_prompt: |
  You are Tech Lead Terry, responsible for code quality and architecture.
  
  Your responsibilities:
  - Review PRs from developers
  - Make architectural decisions
  - Ensure code quality and test coverage
  - Mentor developers on best practices
  - Approve PRs for merge to dev
  
  When reviewing code:
  1. Check for correctness and edge cases
  2. Verify tests exist and are meaningful
  3. Look for security issues
  4. Ensure code follows project conventions
  5. Provide constructive feedback

tools:
  filesystem:
    enabled: true
    allowed_paths:
      - "/repos/**"
      - "/workspace/**"
  
  git:
    enabled: true
    allowed_operations:
      - clone
      - checkout
      - pull
      - status
      - diff
      - log
      - list_prs
      - get_pr
      - add_pr_review
      - add_pr_comment
    denied_operations:
      - push_to_main
      - merge_pr  # Only PM merges
      - force_push
  
  slack:
    enabled: true
    allowed_operations:
      - send_message
      - reply_thread
      - add_reaction
      - upload_file
  
  tasks:
    enabled: true
    allowed_operations:
      - view_tasks
      - update_task_status
      - add_comment
      - create_task  # Can create tech debt tasks
  
  code_exec:
    enabled: true
    allowed_languages:
      - python
      - javascript
      - bash
    timeout_seconds: 300
  
  web_search:
    enabled: true

memory:
  path: "/memory/tech_lead"
  files:
    - "memory.md"

can_mention:
  - developer
  - product_manager
  - ceo

Agent Config (config/agents/developer.yml)

id: developer
name: "Dev Dana"
slack_user_id: "${SLACK_DEV_USER_ID}"  # Bot user for this agent

model: "claude-sonnet-4-20250514"
max_tokens: 8192

system_prompt: |
  You are Dev Dana, a skilled software developer working in a team.
  You write clean, well-tested code. You work on feature branches
  and create PRs for review. You communicate progress in Slack.
  
  When assigned a task, you:
  1. Understand requirements fully (ask PM if unclear)
  2. Create a feature branch from dev
  3. Implement the solution with tests
  4. Create a PR and notify tech_lead for review
  
  You have access to the shared repository at /repos/main.

tools:
  filesystem:
    enabled: true
    allowed_paths:
      - "/repos/**"
      - "/workspace/**"
    denied_paths:
      - "/repos/.git/config"
      - "**/.env"
  
  git:
    enabled: true
    allowed_operations:
      - clone
      - checkout
      - branch
      - add
      - commit
      - push
      - pull
      - status
      - diff
      - create_pr
      - list_prs
    denied_operations:
      - push_to_main
      - push_to_dev
      - merge_pr
      - force_push
    branch_pattern: "feature/*"
  
  slack:
    enabled: true
    allowed_operations:
      - send_message
      - reply_thread
      - add_reaction
      - upload_file
    denied_operations:
      - create_channel
      - delete_channel
  
  tasks:
    enabled: true
    allowed_operations:
      - view_tasks
      - update_task_status
      - add_comment
    denied_operations:
      - create_project
      - create_board
      - delete_task
  
  code_exec:
    enabled: true
    allowed_languages:
      - python
      - javascript
      - bash
    timeout_seconds: 300
  
  web_search:
    enabled: true

memory:
  path: "/memory/developer"
  files:
    - "memory.md"

can_mention:
  - tech_lead
  - product_manager
  - ceo

container:
  resources:
    memory: "2g"
    cpus: "1.0"
  environment:
    - "PYTHONPATH=/workspace"

Product Manager Config (config/agents/product_manager.yml)

id: product_manager
name: "PM Paula"
slack_user_id: "${SLACK_PM_USER_ID}"

model: "claude-sonnet-4-20250514"
max_tokens: 8192

system_prompt: |
  You are PM Paula, a product manager coordinating the team.
  You translate requirements into actionable tasks, create project
  channels, and ensure smooth delivery.
  
  When given a new feature request:
  1. Break it down into tasks
  2. Create a project channel if needed
  3. Assign tasks to appropriate team members
  4. Track progress and remove blockers
  
  You can merge approved PRs to the dev branch.

tools:
  filesystem:
    enabled: true
    allowed_paths:
      - "/repos/**"
      - "/workspace/**"
      - "/projects/**"
  
  git:
    enabled: true
    allowed_operations:
      - clone
      - checkout
      - pull
      - merge_to_dev
      - status
      - log
      - list_prs
      - get_pr
      - merge_pr
      - add_pr_review
    denied_operations:
      - push_to_main
      - force_push
  
  slack:
    enabled: true
    allowed_operations:
      - send_message
      - reply_thread
      - create_channel
      - invite_to_channel
      - upload_file
      - add_reaction
  
  tasks:
    enabled: true
    allowed_operations:
      - create_project
      - create_board
      - create_task
      - assign_task
      - update_task_status
      - view_tasks
      - add_comment
  
  web_search:
    enabled: true

memory:
  path: "/memory/product_manager"
  files:
    - "memory.md"

can_mention:
  - developer
  - tech_lead
  - designer
  - ceo

can_delegate_to:
  - developer
  - designer

Core Components

1. Orchestrator (src/orchestra/core/orchestrator.py)

"""
Main orchestrator that:
- Loads agent configs
- Manages agent container lifecycle
- Routes Slack messages to appropriate agents
- Handles agent-to-agent communication
"""

class Orchestrator:
    async def start(self) -> None: ...
    async def stop(self) -> None: ...
    async def route_message(self, event: SlackEvent) -> None: ...
    async def spawn_agent(self, agent_id: str) -> AgentContainer: ...
    async def get_agent(self, agent_id: str) -> Agent: ...

2. Agent (src/orchestra/core/agent.py)

"""
Wraps Claude Agent SDK with:
- Tool permission enforcement
- Memory management
- Slack integration
- Container-aware execution
"""

class Agent:
    id: str
    config: AgentConfig
    claude_agent: ClaudeAgent  # From Agent SDK
    container: AgentContainer
    memory: MemoryManager
    
    async def process_message(self, message: str, context: dict) -> str: ...
    async def invoke_tool(self, tool: str, params: dict) -> Any: ...
    async def update_memory(self, content: str) -> None: ...

3. Container Manager (src/orchestra/core/container.py)

"""
Docker container lifecycle management:
- Build agent images
- Start/stop containers
- Volume mounting
- Network configuration
"""

class ContainerManager:
    async def create_container(self, agent_config: AgentConfig) -> Container: ...
    async def start_container(self, container_id: str) -> None: ...
    async def stop_container(self, container_id: str) -> None: ...
    async def exec_in_container(self, container_id: str, cmd: str) -> str: ...

Tool Implementations

Tasks Tool (src/orchestra/tools/tasks.py)

"""
JSON-based project management.
File: /projects/boards.json
"""

# Schema
{
  "projects": {
    "project-123": {
      "id": "project-123",
      "name": "Company Website",
      "channel": "proj-website",
      "created_by": "product_manager",
      "created_at": "2025-01-15T10:00:00Z"
    }
  },
  "boards": {
    "board-456": {
      "id": "board-456",
      "project_id": "project-123",
      "name": "Sprint 1",
      "columns": ["todo", "in_progress", "review", "done"]
    }
  },
  "tasks": {
    "task-789": {
      "id": "task-789",
      "board_id": "board-456",
      "title": "Implement homepage",
      "description": "Create responsive homepage with hero section",
      "status": "in_progress",
      "assignee": "developer",
      "created_by": "product_manager",
      "branch": "feature/task-789",
      "pr_url": null,
      "comments": [
        {
          "author": "developer",
          "text": "Started working on this",
          "timestamp": "2025-01-15T11:00:00Z"
        }
      ]
    }
  }
}

Git Tool (src/orchestra/tools/git.py)

"""
Git operations with permission checking.
Enforces branch patterns and operation restrictions.
Uses Gitea API for PR operations.
"""

class GitTool:
    # Local git operations
    async def checkout_branch(self, branch: str) -> str: ...
    async def create_branch(self, branch: str, from_ref: str = "dev") -> str: ...
    async def commit(self, message: str, files: list[str]) -> str: ...
    async def push(self, branch: str) -> str: ...
    async def pull(self, branch: str) -> str: ...
    async def status(self) -> str: ...
    async def diff(self, ref: str = "HEAD") -> str: ...
    
    # Gitea API operations
    async def create_pr(self, title: str, body: str, head: str, base: str = "dev") -> dict: ...
    async def list_prs(self, state: str = "open") -> list[dict]: ...
    async def get_pr(self, pr_number: int) -> dict: ...
    async def add_pr_comment(self, pr_number: int, body: str) -> dict: ...
    async def add_pr_review(self, pr_number: int, body: str, approved: bool) -> dict: ...
    async def merge_pr(self, pr_number: int, merge_style: str = "merge") -> dict: ...  # Permission checked


class GiteaClient:
    """
    Async client for Gitea API.
    Docs: https://docs.gitea.com/development/api-usage
    """
    
    def __init__(self, base_url: str, token: str, owner: str, repo: str):
        self.base_url = base_url.rstrip("/")
        self.token = token
        self.owner = owner
        self.repo = repo
    
    async def create_pull_request(
        self, title: str, body: str, head: str, base: str
    ) -> dict:
        """POST /repos/{owner}/{repo}/pulls"""
        ...
    
    async def get_pull_request(self, number: int) -> dict:
        """GET /repos/{owner}/{repo}/pulls/{index}"""
        ...
    
    async def merge_pull_request(
        self, number: int, merge_style: str = "merge"  # merge, rebase, squash
    ) -> dict:
        """POST /repos/{owner}/{repo}/pulls/{index}/merge"""
        ...
    
    async def create_review(
        self, number: int, body: str, event: str  # APPROVE, REQUEST_CHANGES, COMMENT
    ) -> dict:
        """POST /repos/{owner}/{repo}/pulls/{index}/reviews"""
        ...

Slack Integration

Event Flow

Slack Event (message/mention)
         │
         ▼
┌─────────────────┐
│  Event Handler  │  ← Parses mentions, extracts thread context
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│    Router       │  ← Determines which agent(s) should respond
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Agent Queue    │  ← Each agent has async message queue
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Agent Process  │  ← Claude SDK processes, may invoke tools
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Slack Response │  ← Posts reply in thread, may mention others
└─────────────────┘

Message Format

Agents post messages with a consistent format:

🤖 *Dev Dana*
> Working on task-789: Implement homepage
> 
> Created branch `feature/task-789` and started implementation.
> Will have a PR ready for review in ~2 hours.
> 
> @tech_lead heads up, will need your review later

Memory System

Structure

/memory/
├── ceo/
│   └── memory.md           # CEO's persistent memory
├── product_manager/
│   └── memory.md           # PM's persistent memory
├── developer/
│   ├── memory.md           # General memory
│   ├── project-website.md  # Project-specific (future)
│   └── learnings.md        # Technical learnings (future)
└── tech_lead/
    └── memory.md

Memory Format (memory.md)

# Agent Memory - Dev Dana

## Active Context
- Currently working on: Company Website project
- Current branch: feature/task-789
- Pending PR: None

## Project Knowledge
### Company Website
- Tech stack: React, Tailwind, Next.js
- Design system: Uses shadcn/ui components
- PM: Paula, Tech Lead: Terry

## Team Preferences
- Paula prefers detailed task breakdowns
- Terry wants tests for all new features
- Code style: Prefer functional components

## Learnings
- 2025-01-15: This repo uses pnpm, not npm
- 2025-01-14: API endpoints are in /api folder

## Recent Decisions
- Using Next.js App Router (agreed with Terry)

Implementation Phases

Phase 1: Foundation (MVP)

Goal: Single agent responding to Slack messages

  1. Set up project structure with pyproject.toml
  2. Implement config loading (YAML → Pydantic models)
  3. Basic Slack integration (listen to events, post messages)
  4. Wrap Claude Agent SDK in Agent class
  5. Simple filesystem tool
  6. Memory loading/saving
  7. Basic Docker container for one agent

Deliverable: Can @mention one agent in Slack and get responses

Phase 2: Multi-Agent

Goal: Multiple agents running and communicating

  1. Container manager for multiple agents
  2. Message routing based on mentions
  3. Agent-to-agent communication via Slack
  4. Thread context preservation
  5. All agents defined and running

Deliverable: Can have PM delegate to Developer via Slack

Phase 3: Tools & Permissions

Goal: Full toolset with permission enforcement

  1. Tool registry with permission checking
  2. Git tool implementation
  3. Tasks/project management tool
  4. Code execution tool
  5. Web search tool
  6. Slack tools (create_channel, etc.)

Deliverable: Agents can do real work with proper restrictions

Phase 4: Workflow & Polish

Goal: Smooth end-to-end workflows

  1. PR creation and review flow
  2. Task lifecycle (create → assign → progress → complete)
  3. CEO supervision capabilities
  4. Better memory management
  5. Error handling and recovery
  6. MCP server exposure

Deliverable: Full workflow from request → implementation → review → merge

Phase 5: Production Ready

Goal: Deployable and maintainable

  1. Docker Compose setup
  2. Health checks and monitoring
  3. Logging and observability
  4. Configuration validation
  5. Documentation
  6. Integration tests

Docker Compose Structure

version: '3.8'

services:
  orchestrator:
    build:
      context: .
      dockerfile: Dockerfile.orchestrator
    environment:
      - SLACK_APP_TOKEN=${SLACK_APP_TOKEN}
      - SLACK_BOT_TOKEN=${SLACK_BOT_TOKEN}
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
    volumes:
      - ./config:/app/config:ro
      - orchestra-data:/app/data
    networks:
      - orchestra-net
    depends_on:
      - agent-ceo
      - agent-pm
      - agent-dev
      - agent-techlead

  agent-ceo:
    build:
      context: .
      dockerfile: Dockerfile.agent
    environment:
      - AGENT_ID=ceo
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
    volumes:
      - orchestra-data:/data
    networks:
      - orchestra-net

  agent-pm:
    build:
      context: .
      dockerfile: Dockerfile.agent
    environment:
      - AGENT_ID=product_manager
    volumes:
      - orchestra-data:/data
    networks:
      - orchestra-net

  agent-dev:
    build:
      context: .
      dockerfile: Dockerfile.agent
    environment:
      - AGENT_ID=developer
    volumes:
      - orchestra-data:/data
    networks:
      - orchestra-net

  agent-techlead:
    build:
      context: .
      dockerfile: Dockerfile.agent
    environment:
      - AGENT_ID=tech_lead
    volumes:
      - orchestra-data:/data
    networks:
      - orchestra-net

volumes:
  orchestra-data:

networks:
  orchestra-net:
    driver: bridge

CLAUDE.md (for Claude Code)

# Agent Orchestra

Multi-agent system with Claude agents communicating via Slack.

## Quick Start
```bash
uv sync
cp .env.example .env  # Fill in tokens
uv run python -m orchestra.main

Architecture

  • Orchestrator: Routes Slack messages to agents
  • Agents: Run in Docker containers, use Claude Agent SDK
  • Tools: Filesystem, git, Slack, tasks, code execution
  • Memory: Markdown files in /data/memory/{agent_id}/

Key Files

  • config/orchestra.yml - Global config
  • config/agents/*.yml - Agent definitions
  • src/orchestra/core/orchestrator.py - Main service
  • src/orchestra/core/agent.py - Agent wrapper
  • src/orchestra/tools/ - Tool implementations

Agent Config Structure

See config/agents/developer.yml for full example. Key fields: id, name, model, system_prompt, tools, memory, can_mention

Tool Permissions

Each tool has allowed/denied operations per agent. Check src/orchestra/tools/registry.py for enforcement.

Testing

uv run pytest
uv run pytest tests/test_agent.py -v

Common Tasks

  • Add new agent: Create YAML in config/agents/, add to docker-compose
  • Add new tool: Implement in src/orchestra/tools/, register in registry.py
  • Debug agent: Check logs with docker logs agent-{id}

---

## Dependencies

```toml
[project]
name = "agent-orchestra"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
    "anthropic>=0.40.0",
    "anthropic-agent-sdk>=0.1.0",  # Or whatever the actual package name is
    "slack-sdk>=3.27.0",
    "slack-bolt>=1.18.0",
    "pydantic>=2.5.0",
    "pydantic-settings>=2.1.0",
    "pyyaml>=6.0.0",
    "docker>=7.0.0",
    "aiohttp>=3.9.0",
    "httpx>=0.26.0",
    "gitea-api>=0.1.0",            # Gitea API client (or use httpx directly)
    "mcp>=0.1.0",                  # MCP SDK
]

[project.optional-dependencies]
dev = [
    "pytest>=8.0.0",
    "pytest-asyncio>=0.23.0",
    "pytest-mock>=3.12.0",
    "ruff>=0.1.0",
]

Next Steps

  1. Initialize the repository with this structure
  2. Set up Slack app (Bot Token Scopes needed):
    • app_mentions:read
    • channels:history
    • channels:manage
    • channels:read
    • chat:write
    • files:write
    • reactions:write
    • users:read
  3. Create bot users for each agent in Slack
  4. Implement Phase 1 (Foundation)
  5. Iterate through remaining phases

Would you like me to start implementing any specific component?