1444 lines
41 KiB
Markdown
1444 lines
41 KiB
Markdown
# 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.
|
|
|
|
---
|
|
|
|
## Claude Agent SDK Integration Notes
|
|
|
|
### Key SDK Concepts
|
|
|
|
1. **Two APIs**:
|
|
- `query()` - One-shot, creates new session each time
|
|
- `ClaudeSDKClient` - Persistent session, maintains conversation history
|
|
|
|
We use `ClaudeSDKClient` because agents need to remember context across messages.
|
|
|
|
2. **Built-in Tools**: The SDK includes Claude Code's tools:
|
|
- `Read`, `Write`, `Edit` - File operations
|
|
- `Bash` - Shell commands
|
|
- `Glob`, `Grep` - File search
|
|
- `WebSearch`, `WebFetch` - Web access
|
|
- `Task` - Subagent spawning
|
|
- `NotebookEdit` - Jupyter notebooks
|
|
|
|
3. **Custom Tools via SDK MCP Servers**:
|
|
- Use `@tool` decorator and `create_sdk_mcp_server()`
|
|
- Runs in-process (no subprocess overhead)
|
|
- Tools are prefixed: `mcp__<server>__<tool>`
|
|
|
|
4. **Hooks for Permissions**:
|
|
- `PreToolUse` - Check before tool execution, can deny
|
|
- `PostToolUse` - Log/audit after execution
|
|
- Defined via `HookMatcher` in `ClaudeAgentOptions`
|
|
|
|
5. **Permission Modes**:
|
|
- `"default"` - Standard permission behavior
|
|
- `"acceptEdits"` - Auto-accept file edits
|
|
- `"bypassPermissions"` - Skip all checks (use with caution)
|
|
|
|
### Architecture Decision: Orchestrator vs. Per-Container SDK
|
|
|
|
**Option A: Orchestrator runs SDK (chosen)**
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ Orchestrator │
|
|
│ - Runs ClaudeSDKClient per agent │
|
|
│ - Routes Slack messages │
|
|
│ - Agents execute via SDK tools │
|
|
│ (Bash, Write, etc. run in container) │
|
|
└─────────────────────────────────────────┘
|
|
│ docker exec
|
|
▼
|
|
┌─────────────────┐
|
|
│ Agent Container │
|
|
│ - Workspace │
|
|
│ - Git clone │
|
|
│ - No SDK here │
|
|
└─────────────────┘
|
|
```
|
|
|
|
**Option B: SDK runs inside each container**
|
|
```
|
|
┌─────────────────┐ ┌─────────────────┐
|
|
│ Agent Container │ │ Agent Container │
|
|
│ - SDK Client │ │ - SDK Client │
|
|
│ - Claude CLI │ │ - Claude CLI │
|
|
│ - Node.js │ │ - Node.js │
|
|
└─────────────────┘ └─────────────────┘
|
|
```
|
|
|
|
We choose **Option A** because:
|
|
- Simpler container images (no Node.js/CLI required)
|
|
- Centralized orchestration and routing
|
|
- SDK's built-in tools (Bash, Write) can still operate on container filesystems via mounted volumes
|
|
- Easier to manage API keys and rate limits
|
|
|
|
### Setting Up SDK with Agent Options
|
|
|
|
```python
|
|
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, tool, create_sdk_mcp_server
|
|
|
|
# Create custom tools
|
|
@tool("slack_send", "Send Slack message", {"channel": str, "text": str})
|
|
async def slack_send(args):
|
|
await slack_client.chat_postMessage(
|
|
channel=args["channel"],
|
|
text=args["text"]
|
|
)
|
|
return {"content": [{"type": "text", "text": "Sent"}]}
|
|
|
|
# Create MCP server
|
|
tools_server = create_sdk_mcp_server(
|
|
name="agent-tools",
|
|
tools=[slack_send]
|
|
)
|
|
|
|
# Configure agent
|
|
options = ClaudeAgentOptions(
|
|
system_prompt=agent_config.system_prompt,
|
|
model="sonnet",
|
|
|
|
# Built-in tools
|
|
allowed_tools=["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
|
|
disallowed_tools=["Task"], # Don't allow spawning subagents
|
|
|
|
# Custom tools via MCP
|
|
mcp_servers={"tools": tools_server},
|
|
|
|
# Permission mode
|
|
permission_mode="acceptEdits",
|
|
|
|
# Working directory (mapped to container volume)
|
|
cwd="/data/workspaces/developer",
|
|
|
|
# Hooks for fine-grained control
|
|
hooks={
|
|
"PreToolUse": [HookMatcher(hooks=[permission_checker])]
|
|
}
|
|
)
|
|
|
|
# Create persistent client
|
|
async with ClaudeSDKClient(options) as client:
|
|
# Send message (client remembers context)
|
|
await client.query("Create a hello.py file")
|
|
async for msg in client.receive_response():
|
|
handle_message(msg)
|
|
|
|
# Follow-up (same session, remembers previous)
|
|
await client.query("Add a main function")
|
|
async for msg in client.receive_response():
|
|
handle_message(msg)
|
|
```
|
|
|
|
---
|
|
|
|
## 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.orchestrator # Main service (runs SDK)
|
|
├── Dockerfile.workspace # Lightweight workspace containers
|
|
│
|
|
├── config/
|
|
│ ├── orchestra.yml # Global config (Slack, Gitea, Docker)
|
|
│ └── 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 service, Slack listener
|
|
│ │ ├── agent.py # ClaudeSDKClient wrapper
|
|
│ │ ├── config.py # YAML config → Pydantic models
|
|
│ │ ├── container.py # Docker workspace management
|
|
│ │ └── permissions.py # PreToolUse hooks
|
|
│ │
|
|
│ ├── slack/
|
|
│ │ ├── __init__.py
|
|
│ │ ├── client.py # Slack API wrapper
|
|
│ │ ├── events.py # Event handlers, routing
|
|
│ │ └── formatter.py # Message formatting
|
|
│ │
|
|
│ ├── tools/
|
|
│ │ ├── __init__.py
|
|
│ │ ├── server.py # create_sdk_mcp_server setup
|
|
│ │ ├── slack_tools.py # @tool decorated Slack operations
|
|
│ │ ├── task_tools.py # @tool decorated task management
|
|
│ │ ├── gitea_tools.py # @tool decorated Gitea/PR operations
|
|
│ │ └── gitea_client.py # Gitea API client (httpx)
|
|
│ │
|
|
│ └── memory/
|
|
│ ├── __init__.py
|
|
│ └── manager.py # Memory file read/write
|
|
│
|
|
├── data/ # Mounted volumes
|
|
│ ├── workspaces/ # Per-agent working directories
|
|
│ │ ├── developer/
|
|
│ │ ├── product_manager/
|
|
│ │ └── tech_lead/
|
|
│ ├── repos/ # Shared git repository clone
|
|
│ ├── projects/ # Project/task 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_permissions.py
|
|
└── test_slack.py
|
|
```
|
|
|
|
---
|
|
|
|
## Configuration Schema
|
|
|
|
### Global Config (`config/orchestra.yml`)
|
|
|
|
```yaml
|
|
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`)
|
|
|
|
```yaml
|
|
id: ceo
|
|
name: "CEO Chris"
|
|
slack_user_id: "${SLACK_CEO_USER_ID}"
|
|
|
|
model: "sonnet"
|
|
max_turns: 100
|
|
|
|
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.
|
|
|
|
# All tools allowed
|
|
allowed_tools:
|
|
- Read
|
|
- Write
|
|
- Edit
|
|
- Bash
|
|
- Glob
|
|
- Grep
|
|
- WebSearch
|
|
- WebFetch
|
|
- Task
|
|
# All Slack tools
|
|
- mcp__agent__slack_send_message
|
|
- mcp__agent__slack_reply_thread
|
|
- mcp__agent__slack_create_channel
|
|
- mcp__agent__slack_delete_channel
|
|
- mcp__agent__slack_invite_to_channel
|
|
# All task tools
|
|
- mcp__agent__tasks_create_project
|
|
- mcp__agent__tasks_create_board
|
|
- mcp__agent__tasks_create
|
|
- mcp__agent__tasks_assign
|
|
- mcp__agent__tasks_update_status
|
|
- mcp__agent__tasks_view
|
|
- mcp__agent__tasks_delete
|
|
# All git tools
|
|
- mcp__agent__gitea_create_pr
|
|
- mcp__agent__gitea_list_prs
|
|
- mcp__agent__gitea_get_pr
|
|
- mcp__agent__gitea_merge_pr
|
|
- mcp__agent__gitea_add_review
|
|
- mcp__agent__gitea_merge_to_main
|
|
|
|
disallowed_tools: [] # CEO has no restrictions
|
|
|
|
permissions:
|
|
filesystem:
|
|
allowed_paths:
|
|
- "/**"
|
|
git:
|
|
can_push_to:
|
|
- "*"
|
|
can_merge_to:
|
|
- "main"
|
|
- "dev"
|
|
|
|
memory:
|
|
path: "/memory/ceo"
|
|
files:
|
|
- "memory.md"
|
|
|
|
can_mention:
|
|
- "*"
|
|
|
|
can_delegate_to:
|
|
- "*"
|
|
|
|
container:
|
|
resources:
|
|
memory: "2g"
|
|
cpus: "1.0"
|
|
```
|
|
|
|
### Tech Lead Config (`config/agents/tech_lead.yml`)
|
|
|
|
```yaml
|
|
id: tech_lead
|
|
name: "Tech Lead Terry"
|
|
slack_user_id: "${SLACK_TECHLEAD_USER_ID}"
|
|
|
|
model: "sonnet"
|
|
max_turns: 50
|
|
|
|
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
|
|
|
|
allowed_tools:
|
|
- Read
|
|
- Write
|
|
- Edit
|
|
- Bash
|
|
- Glob
|
|
- Grep
|
|
- WebSearch
|
|
- WebFetch
|
|
# Slack tools
|
|
- mcp__agent__slack_send_message
|
|
- mcp__agent__slack_reply_thread
|
|
# Task tools
|
|
- mcp__agent__tasks_view
|
|
- mcp__agent__tasks_update_status
|
|
- mcp__agent__tasks_create # Can create tech debt tasks
|
|
- mcp__agent__tasks_add_comment
|
|
# Git tools - can review but not merge
|
|
- mcp__agent__gitea_list_prs
|
|
- mcp__agent__gitea_get_pr
|
|
- mcp__agent__gitea_add_review
|
|
- mcp__agent__gitea_add_comment
|
|
|
|
disallowed_tools:
|
|
- mcp__agent__gitea_merge_pr
|
|
- mcp__agent__slack_create_channel
|
|
|
|
permissions:
|
|
filesystem:
|
|
allowed_paths:
|
|
- "/repos/**"
|
|
- "/workspace/**"
|
|
git:
|
|
can_push_to: [] # Tech lead reviews, doesn't push
|
|
can_merge_to: [] # Only PM merges
|
|
|
|
memory:
|
|
path: "/memory/tech_lead"
|
|
files:
|
|
- "memory.md"
|
|
|
|
can_mention:
|
|
- developer
|
|
- product_manager
|
|
- ceo
|
|
```
|
|
|
|
### Agent Config (`config/agents/developer.yml`)
|
|
|
|
```yaml
|
|
id: developer
|
|
name: "Dev Dana"
|
|
slack_user_id: "${SLACK_DEV_USER_ID}" # Bot user for this agent
|
|
|
|
# Claude Agent SDK settings
|
|
model: "sonnet" # sonnet, opus, haiku
|
|
max_turns: 50
|
|
|
|
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.
|
|
|
|
Use the Slack tools to communicate with your team.
|
|
Use the task tools to update your task status.
|
|
|
|
# Built-in Claude Code tools (Read, Write, Edit, Bash, Glob, Grep, WebSearch, etc.)
|
|
allowed_tools:
|
|
- Read
|
|
- Write
|
|
- Edit
|
|
- Bash
|
|
- Glob
|
|
- Grep
|
|
- WebSearch
|
|
- WebFetch
|
|
# Custom MCP tools (prefixed with mcp__<server>__)
|
|
- mcp__agent__slack_send_message
|
|
- mcp__agent__slack_reply_thread
|
|
- mcp__agent__tasks_view
|
|
- mcp__agent__tasks_update_status
|
|
- mcp__agent__gitea_create_pr
|
|
- mcp__agent__gitea_list_prs
|
|
|
|
disallowed_tools:
|
|
- mcp__agent__slack_create_channel
|
|
- mcp__agent__slack_delete_channel
|
|
- mcp__agent__tasks_create_project
|
|
- mcp__agent__gitea_merge_pr
|
|
|
|
# Permission hooks configuration
|
|
permissions:
|
|
filesystem:
|
|
allowed_paths:
|
|
- "/repos/**"
|
|
- "/workspace/**"
|
|
denied_paths:
|
|
- "/repos/.git/config"
|
|
- "**/.env"
|
|
- "**/secrets/**"
|
|
|
|
git:
|
|
branch_pattern: "feature/*"
|
|
can_push_to:
|
|
- "feature/*"
|
|
cannot_push_to:
|
|
- "main"
|
|
- "dev"
|
|
|
|
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`)
|
|
|
|
```yaml
|
|
id: product_manager
|
|
name: "PM Paula"
|
|
slack_user_id: "${SLACK_PM_USER_ID}"
|
|
|
|
model: "sonnet"
|
|
max_turns: 50
|
|
|
|
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.
|
|
You cannot merge to main - that requires CEO approval.
|
|
|
|
allowed_tools:
|
|
- Read
|
|
- Write
|
|
- Glob
|
|
- Grep
|
|
- Bash
|
|
- WebSearch
|
|
- WebFetch
|
|
# Slack tools
|
|
- mcp__agent__slack_send_message
|
|
- mcp__agent__slack_reply_thread
|
|
- mcp__agent__slack_create_channel
|
|
- mcp__agent__slack_invite_to_channel
|
|
# Task tools
|
|
- mcp__agent__tasks_create_project
|
|
- mcp__agent__tasks_create_board
|
|
- mcp__agent__tasks_create
|
|
- mcp__agent__tasks_assign
|
|
- mcp__agent__tasks_update_status
|
|
- mcp__agent__tasks_view
|
|
# Git/Gitea tools
|
|
- mcp__agent__gitea_list_prs
|
|
- mcp__agent__gitea_get_pr
|
|
- mcp__agent__gitea_merge_pr
|
|
- mcp__agent__gitea_add_review
|
|
|
|
disallowed_tools:
|
|
- mcp__agent__gitea_push_to_main
|
|
|
|
permissions:
|
|
filesystem:
|
|
allowed_paths:
|
|
- "/repos/**"
|
|
- "/workspace/**"
|
|
- "/projects/**"
|
|
|
|
git:
|
|
can_merge_to:
|
|
- "dev"
|
|
cannot_merge_to:
|
|
- "main"
|
|
|
|
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`)
|
|
|
|
```python
|
|
"""
|
|
Main orchestrator that:
|
|
- Loads agent configs from YAML
|
|
- 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`)
|
|
|
|
Uses `ClaudeSDKClient` from the Claude Agent SDK for persistent conversation sessions.
|
|
Each agent maintains its own client instance for continuous context.
|
|
|
|
```python
|
|
"""
|
|
Wraps Claude Agent SDK with:
|
|
- Persistent conversation via ClaudeSDKClient
|
|
- Custom tools via SDK MCP servers (in-process)
|
|
- Memory management
|
|
- Slack integration
|
|
"""
|
|
from claude_agent_sdk import (
|
|
ClaudeSDKClient,
|
|
ClaudeAgentOptions,
|
|
tool,
|
|
create_sdk_mcp_server,
|
|
AssistantMessage,
|
|
TextBlock,
|
|
ToolUseBlock,
|
|
HookMatcher
|
|
)
|
|
|
|
class Agent:
|
|
id: str
|
|
config: AgentConfig
|
|
client: ClaudeSDKClient # Persistent session
|
|
memory: MemoryManager
|
|
|
|
async def start(self) -> None:
|
|
"""Initialize the ClaudeSDKClient with agent-specific options."""
|
|
# Create in-process MCP server for custom tools
|
|
tools_server = create_sdk_mcp_server(
|
|
name=f"{self.id}-tools",
|
|
version="1.0.0",
|
|
tools=self._build_tools()
|
|
)
|
|
|
|
options = ClaudeAgentOptions(
|
|
system_prompt=self.config.system_prompt,
|
|
model=self.config.model, # e.g., "sonnet", "opus"
|
|
allowed_tools=self.config.allowed_tools,
|
|
disallowed_tools=self.config.disallowed_tools,
|
|
mcp_servers={
|
|
"agent": tools_server,
|
|
# External MCP servers for git, etc.
|
|
"gitea": {
|
|
"type": "stdio",
|
|
"command": "gitea-mcp-server",
|
|
"args": ["--url", self.config.gitea_url]
|
|
}
|
|
},
|
|
permission_mode="acceptEdits", # Auto-accept in containers
|
|
cwd=f"/workspace/{self.id}",
|
|
hooks=self._build_hooks()
|
|
)
|
|
|
|
self.client = ClaudeSDKClient(options)
|
|
await self.client.connect()
|
|
|
|
async def process_message(self, message: str, context: dict) -> str:
|
|
"""Send message and collect response. Client maintains conversation history."""
|
|
await self.client.query(message)
|
|
|
|
response_text = ""
|
|
async for msg in self.client.receive_response():
|
|
if isinstance(msg, AssistantMessage):
|
|
for block in msg.content:
|
|
if isinstance(block, TextBlock):
|
|
response_text += block.text
|
|
|
|
return response_text
|
|
|
|
async def stop(self) -> None:
|
|
"""Disconnect the client."""
|
|
await self.client.disconnect()
|
|
|
|
def _build_tools(self) -> list:
|
|
"""Build custom tools based on agent config permissions."""
|
|
tools = []
|
|
|
|
if "slack" in self.config.tools:
|
|
tools.extend(self._create_slack_tools())
|
|
if "tasks" in self.config.tools:
|
|
tools.extend(self._create_task_tools())
|
|
|
|
return tools
|
|
|
|
def _build_hooks(self) -> dict:
|
|
"""Build permission hooks to enforce tool restrictions."""
|
|
async def check_permissions(input_data, tool_use_id, context):
|
|
tool_name = input_data.get("tool_name")
|
|
tool_input = input_data.get("tool_input", {})
|
|
|
|
# Check git branch restrictions
|
|
if tool_name == "Bash" and "git push" in tool_input.get("command", ""):
|
|
branch = self._extract_branch(tool_input["command"])
|
|
if not self._can_push_to_branch(branch):
|
|
return {
|
|
"hookSpecificOutput": {
|
|
"hookEventName": "PreToolUse",
|
|
"permissionDecision": "deny",
|
|
"permissionDecisionReason": f"Cannot push to {branch}"
|
|
}
|
|
}
|
|
return {}
|
|
|
|
return {
|
|
"PreToolUse": [HookMatcher(hooks=[check_permissions])]
|
|
}
|
|
```
|
|
|
|
### 3. Custom Tools with SDK MCP Server
|
|
|
|
The Claude Agent SDK supports in-process MCP servers, eliminating subprocess overhead.
|
|
|
|
```python
|
|
"""
|
|
Custom tools implemented as SDK MCP servers.
|
|
These run in-process for better performance.
|
|
"""
|
|
from claude_agent_sdk import tool, create_sdk_mcp_server
|
|
from typing import Any
|
|
|
|
# Slack Tools
|
|
@tool("slack_send_message", "Send a message to a Slack channel or thread", {
|
|
"channel": str,
|
|
"text": str,
|
|
"thread_ts": str # Optional, for threading
|
|
})
|
|
async def slack_send_message(args: dict[str, Any]) -> dict[str, Any]:
|
|
# Implementation uses slack_sdk
|
|
channel = args["channel"]
|
|
text = args["text"]
|
|
thread_ts = args.get("thread_ts")
|
|
|
|
result = await slack_client.chat_postMessage(
|
|
channel=channel,
|
|
text=text,
|
|
thread_ts=thread_ts
|
|
)
|
|
|
|
return {
|
|
"content": [{
|
|
"type": "text",
|
|
"text": f"Message sent to {channel}"
|
|
}]
|
|
}
|
|
|
|
@tool("slack_create_channel", "Create a new Slack channel", {
|
|
"name": str,
|
|
"description": str
|
|
})
|
|
async def slack_create_channel(args: dict[str, Any]) -> dict[str, Any]:
|
|
result = await slack_client.conversations_create(
|
|
name=args["name"]
|
|
)
|
|
return {
|
|
"content": [{
|
|
"type": "text",
|
|
"text": f"Created channel #{args['name']}"
|
|
}]
|
|
}
|
|
|
|
# Task Management Tools
|
|
@tool("tasks_create", "Create a new task", {
|
|
"title": str,
|
|
"description": str,
|
|
"assignee": str,
|
|
"board_id": str
|
|
})
|
|
async def tasks_create(args: dict[str, Any]) -> dict[str, Any]:
|
|
task = await task_manager.create_task(
|
|
title=args["title"],
|
|
description=args["description"],
|
|
assignee=args["assignee"],
|
|
board_id=args["board_id"]
|
|
)
|
|
return {
|
|
"content": [{
|
|
"type": "text",
|
|
"text": f"Created task {task['id']}: {task['title']}"
|
|
}]
|
|
}
|
|
|
|
@tool("tasks_list", "List tasks with optional filters", {
|
|
"board_id": str,
|
|
"status": str, # Optional: pending, in_progress, done
|
|
"assignee": str # Optional
|
|
})
|
|
async def tasks_list(args: dict[str, Any]) -> dict[str, Any]:
|
|
tasks = await task_manager.list_tasks(**args)
|
|
return {
|
|
"content": [{
|
|
"type": "text",
|
|
"text": json.dumps(tasks, indent=2)
|
|
}]
|
|
}
|
|
|
|
# Gitea Tools (PR management)
|
|
@tool("gitea_create_pr", "Create a pull request", {
|
|
"title": str,
|
|
"body": str,
|
|
"head": str, # Source branch
|
|
"base": str # Target branch (usually "dev")
|
|
})
|
|
async def gitea_create_pr(args: dict[str, Any]) -> dict[str, Any]:
|
|
pr = await gitea_client.create_pull_request(**args)
|
|
return {
|
|
"content": [{
|
|
"type": "text",
|
|
"text": f"Created PR #{pr['number']}: {pr['title']}"
|
|
}]
|
|
}
|
|
|
|
@tool("gitea_merge_pr", "Merge a pull request", {
|
|
"pr_number": int,
|
|
"merge_style": str # merge, rebase, squash
|
|
})
|
|
async def gitea_merge_pr(args: dict[str, Any]) -> dict[str, Any]:
|
|
result = await gitea_client.merge_pull_request(
|
|
args["pr_number"],
|
|
args["merge_style"]
|
|
)
|
|
return {
|
|
"content": [{
|
|
"type": "text",
|
|
"text": f"Merged PR #{args['pr_number']}"
|
|
}]
|
|
}
|
|
|
|
# Create server with all tools
|
|
def create_agent_tools_server(agent_config: AgentConfig):
|
|
"""Create MCP server with tools filtered by agent permissions."""
|
|
all_tools = {
|
|
"slack_send_message": slack_send_message,
|
|
"slack_create_channel": slack_create_channel,
|
|
"tasks_create": tasks_create,
|
|
"tasks_list": tasks_list,
|
|
"gitea_create_pr": gitea_create_pr,
|
|
"gitea_merge_pr": gitea_merge_pr,
|
|
}
|
|
|
|
# Filter based on agent's allowed operations
|
|
allowed = []
|
|
for tool_name, tool_fn in all_tools.items():
|
|
if agent_config.can_use_tool(tool_name):
|
|
allowed.append(tool_fn)
|
|
|
|
return create_sdk_mcp_server(
|
|
name=f"{agent_config.id}-tools",
|
|
version="1.0.0",
|
|
tools=allowed
|
|
)
|
|
```
|
|
|
|
### 4. Container Manager (`src/orchestra/core/container.py`)
|
|
|
|
```python
|
|
"""
|
|
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`)
|
|
|
|
```python
|
|
"""
|
|
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`)
|
|
|
|
```python
|
|
"""
|
|
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`)
|
|
|
|
```markdown
|
|
# 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
|
|
|
|
```yaml
|
|
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}
|
|
- GITEA_URL=${GITEA_URL}
|
|
- GITEA_API_TOKEN=${GITEA_API_TOKEN}
|
|
volumes:
|
|
- ./config:/app/config:ro
|
|
- ./data:/app/data
|
|
- /var/run/docker.sock:/var/run/docker.sock # For container management
|
|
networks:
|
|
- orchestra-net
|
|
depends_on:
|
|
- workspace-ceo
|
|
- workspace-pm
|
|
- workspace-dev
|
|
- workspace-techlead
|
|
|
|
# Lightweight workspace containers (no SDK, just filesystem)
|
|
workspace-ceo:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.workspace
|
|
volumes:
|
|
- ./data/workspaces/ceo:/workspace
|
|
- ./data/repos:/repos
|
|
- ./data/memory/ceo:/memory
|
|
- ./data/projects:/projects
|
|
networks:
|
|
- orchestra-net
|
|
|
|
workspace-pm:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.workspace
|
|
volumes:
|
|
- ./data/workspaces/product_manager:/workspace
|
|
- ./data/repos:/repos
|
|
- ./data/memory/product_manager:/memory
|
|
- ./data/projects:/projects
|
|
networks:
|
|
- orchestra-net
|
|
|
|
workspace-dev:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.workspace
|
|
volumes:
|
|
- ./data/workspaces/developer:/workspace
|
|
- ./data/repos:/repos
|
|
- ./data/memory/developer:/memory
|
|
networks:
|
|
- orchestra-net
|
|
|
|
workspace-techlead:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.workspace
|
|
volumes:
|
|
- ./data/workspaces/tech_lead:/workspace
|
|
- ./data/repos:/repos:ro # Read-only for tech lead
|
|
- ./data/memory/tech_lead:/memory
|
|
networks:
|
|
- orchestra-net
|
|
|
|
volumes:
|
|
orchestra-data:
|
|
|
|
networks:
|
|
orchestra-net:
|
|
driver: bridge
|
|
```
|
|
|
|
### Dockerfile.orchestrator
|
|
|
|
```dockerfile
|
|
FROM python:3.12-slim
|
|
|
|
# Install Node.js (required for Claude Agent SDK)
|
|
RUN apt-get update && apt-get install -y \
|
|
curl \
|
|
git \
|
|
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
|
&& apt-get install -y nodejs \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Install Claude Code CLI
|
|
RUN npm install -g @anthropic-ai/claude-code
|
|
|
|
# Install Python dependencies
|
|
WORKDIR /app
|
|
COPY pyproject.toml .
|
|
RUN pip install uv && uv sync
|
|
|
|
COPY src/ ./src/
|
|
COPY config/ ./config/
|
|
|
|
CMD ["uv", "run", "python", "-m", "orchestra.main"]
|
|
```
|
|
|
|
### Dockerfile.workspace
|
|
|
|
```dockerfile
|
|
FROM python:3.12-slim
|
|
|
|
# Lightweight container with common dev tools
|
|
RUN apt-get update && apt-get install -y \
|
|
git \
|
|
curl \
|
|
jq \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Create workspace structure
|
|
RUN mkdir -p /workspace /repos /memory
|
|
|
|
WORKDIR /workspace
|
|
|
|
# Keep container running (orchestrator executes commands via docker exec)
|
|
CMD ["tail", "-f", "/dev/null"]
|
|
```
|
|
|
|
---
|
|
|
|
## CLAUDE.md (for Claude Code)
|
|
|
|
```markdown
|
|
# Agent Orchestra
|
|
|
|
Multi-agent system with Claude agents communicating via Slack.
|
|
Uses the Claude Agent SDK (claude-agent-sdk) for agent execution.
|
|
|
|
## Quick Start
|
|
```bash
|
|
# Prerequisites: Node.js, Claude Code CLI
|
|
npm install -g @anthropic-ai/claude-code
|
|
|
|
# Install Python dependencies
|
|
uv sync
|
|
cp .env.example .env # Fill in ANTHROPIC_API_KEY, Slack tokens, Gitea tokens
|
|
uv run python -m orchestra.main
|
|
```
|
|
|
|
## Architecture
|
|
|
|
- **Orchestrator**: Routes Slack messages to agents, manages lifecycle
|
|
- **Agents**: Each agent uses ClaudeSDKClient for persistent conversations
|
|
- **Tools**: Built-in (Read/Write/Bash) + custom MCP tools (Slack/Tasks/Gitea)
|
|
- **Permissions**: PreToolUse hooks enforce agent-specific restrictions
|
|
- **Containers**: Each agent has isolated Docker container for workspace
|
|
|
|
## Claude Agent SDK Usage
|
|
|
|
We use `ClaudeSDKClient` (not `query()`) because agents need persistent
|
|
conversation context. Key patterns:
|
|
|
|
```python
|
|
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
|
|
|
|
async with ClaudeSDKClient(options) as client:
|
|
await client.query(message)
|
|
async for msg in client.receive_response():
|
|
process(msg)
|
|
```
|
|
|
|
Custom tools use `@tool` decorator and `create_sdk_mcp_server()`.
|
|
|
|
## Key Files
|
|
- `config/orchestra.yml` - Global config (Slack, Gitea, Docker)
|
|
- `config/agents/*.yml` - Agent definitions (tools, permissions, prompts)
|
|
- `src/orchestra/core/orchestrator.py` - Message routing, lifecycle
|
|
- `src/orchestra/core/agent.py` - ClaudeSDKClient wrapper
|
|
- `src/orchestra/tools/` - Custom MCP tool implementations
|
|
- `src/orchestra/tools/permissions.py` - PreToolUse hook for restrictions
|
|
|
|
## Agent Permissions
|
|
|
|
Each agent has:
|
|
- `allowed_tools` / `disallowed_tools` - Tool access
|
|
- `permissions.filesystem` - Path restrictions
|
|
- `permissions.git` - Branch push/merge restrictions
|
|
|
|
Enforced via PreToolUse hooks that check before execution.
|
|
|
|
## Testing
|
|
```bash
|
|
uv run pytest
|
|
uv run pytest tests/test_agent.py -v
|
|
```
|
|
|
|
## Common Tasks
|
|
- **Add agent**: Create YAML in config/agents/, add to docker-compose
|
|
- **Add tool**: Use @tool decorator in src/orchestra/tools/, register in server
|
|
- **Debug agent**: Logs in stdout, use `--debug` for SDK verbose output
|
|
```
|
|
|
|
---
|
|
|
|
## Dependencies
|
|
|
|
```toml
|
|
[project]
|
|
name = "agent-orchestra"
|
|
version = "0.1.0"
|
|
requires-python = ">=3.10"
|
|
dependencies = [
|
|
"claude-agent-sdk>=0.1.0", # Official Claude Agent SDK
|
|
"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",
|
|
"anyio>=4.0.0",
|
|
]
|
|
|
|
[project.optional-dependencies]
|
|
dev = [
|
|
"pytest>=8.0.0",
|
|
"pytest-asyncio>=0.23.0",
|
|
"pytest-mock>=3.12.0",
|
|
"ruff>=0.1.0",
|
|
]
|
|
```
|
|
|
|
**Prerequisites:**
|
|
- Python 3.10+
|
|
- Node.js (required by Claude Agent SDK)
|
|
- Claude Code CLI 2.0.0+: `npm install -g @anthropic-ai/claude-code`
|
|
|
|
--- |