Pattern: Multi-Agent Coordination
Overview
Section titled “Overview”As AI tasks grow in complexity, a single agent often isn’t enough. Claude Code implements three distinct multi-agent coordination patterns, each suited to different scenarios. Understanding when to use each is critical to building effective agent systems.
graph TB subgraph "Pattern 1: Fork" FP["Parent Agent"] FC1["Child 1"] FC2["Child 2"] FC3["Child 3"] FP --> FC1 FP --> FC2 FP --> FC3 FC1 -.->|result| FP FC2 -.->|result| FP FC3 -.->|result| FP end
subgraph "Pattern 2: Coordinator" CO["Coordinator"] CW1["Worker 1"] CW2["Worker 2"] CW3["Worker 3"] CO -->|task| CW1 CO -->|task| CW2 CO -->|task| CW3 CW1 -.->|report| CO CW2 -.->|report| CO CW3 -.->|report| CO CO -->|more tasks| CW1 end
subgraph "Pattern 3: Team" TA["Agent A"] TB["Agent B"] TC["Agent C"] MB["Shared Mailbox"] TA <-->|message| MB TB <-->|message| MB TC <-->|message| MB endPattern Comparison
Section titled “Pattern Comparison”| Dimension | Fork | Coordinator | Team |
|---|---|---|---|
| Topology | Star (parent → children) | Hub-spoke (coordinator → workers) | Mesh (peer-to-peer) |
| Communication | One-way (result return) | Bidirectional (task/report) | Multi-directional (mailbox) |
| Child autonomy | Fire-and-forget | Managed lifecycle | Fully autonomous |
| Parent overhead | Low | High (coordination logic) | None (no central control) |
| Complexity | ⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Best for | Independent subtasks | Complex workflows | Collaborative problem-solving |
| Max agents | 5-10 | 3-7 | 2-4 |
| Cache reuse | ✅ Excellent | ✅ Good | ⚠️ Limited |
| Error isolation | ✅ Each child independent | ⚠️ Coordinator is SPOF | ❌ Errors can cascade |
Pattern 1: Fork
Section titled “Pattern 1: Fork”Essence
Section titled “Essence”The simplest multi-agent pattern. A parent agent spawns independent child agents, each handling a separate subtask. Children run in parallel, return results, and the parent synthesizes them.
When to Use
Section titled “When to Use”- Tasks are embarrassingly parallel (no dependencies between subtasks)
- Each subtask is self-contained with clear input/output
- You want maximum cache reuse (all forks share the parent’s prefix)
Implementation
Section titled “Implementation”interface ForkResult { taskId: string; result: string; tokenUsage: number;}
async function forkPattern( parentContext: AgentContext, tasks: string[],): Promise<ForkResult[]> { // All forks share the parent's system prompt and context const sharedPrefix = { system: parentContext.systemPrompt, messages: parentContext.messages, // Cache-friendly shared prefix };
// Launch all forks in parallel const forkPromises = tasks.map((task, i) => runSubAgent({ ...sharedPrefix, messages: [ ...sharedPrefix.messages, { role: 'user', content: `Sub-task ${i + 1}: ${task}\n\nComplete this specific sub-task and return your findings.`, }, ], maxTurns: 10, }).then(result => ({ taskId: `fork-${i}`, result: result.finalMessage, tokenUsage: result.tokensUsed, })) );
return Promise.all(forkPromises);}Example: Multi-File Code Review
Section titled “Example: Multi-File Code Review”// Parent agent identifies changed files, then forks reviewersconst changedFiles = ['src/auth.ts', 'src/api.ts', 'src/utils.ts', 'tests/auth.test.ts'];
const reviews = await forkPattern(parentContext, changedFiles.map(file => `Review ${file} for: 1. Security vulnerabilities 2. Performance issues 3. Code style violations Return a structured review with severity ratings.`));
// Parent synthesizes all reviews into a unified reportconst synthesis = await callLLM({ messages: [{ role: 'user', content: `Synthesize these code reviews into a unified PR report:\n${ reviews.map(r => `### ${r.taskId}\n${r.result}`).join('\n\n') }`, }],});Pattern 2: Coordinator
Section titled “Pattern 2: Coordinator”Essence
Section titled “Essence”A central coordinator agent manages the workflow, assigns tasks to worker agents, monitors progress, and reassigns work as needed. Unlike Fork, the coordinator can make dynamic decisions based on intermediate results.
When to Use
Section titled “When to Use”- Tasks have dependencies (Task B needs Task A’s output)
- The workflow needs adaptive planning (next task depends on results)
- You need centralized error handling and retries
Implementation
Section titled “Implementation”interface WorkerTask { id: string; description: string; dependencies: string[]; // IDs of tasks that must complete first assignedTo?: string; status: 'pending' | 'running' | 'complete' | 'failed'; result?: string;}
class CoordinatorPattern { private tasks: WorkerTask[] = []; private workers = new Map<string, SubAgent>(); private results = new Map<string, string>();
constructor( private readonly coordinatorLLM: LLMClient, private readonly maxWorkers: number = 3, ) {}
async execute(goal: string): Promise<string> { // Phase 1: Plan tasks this.tasks = await this.planTasks(goal);
// Phase 2: Execute with dynamic coordination while (this.hasPendingTasks()) { // Find tasks whose dependencies are satisfied const ready = this.tasks.filter(t => t.status === 'pending' && t.dependencies.every(d => this.results.has(d)) );
// Launch workers for ready tasks (up to concurrency limit) const toRun = ready.slice(0, this.maxWorkers - this.workers.size);
for (const task of toRun) { task.status = 'running'; const worker = this.launchWorker(task); this.workers.set(task.id, worker); }
// Wait for any worker to complete const completed = await Promise.race( [...this.workers.entries()].map(async ([id, worker]) => { const result = await worker.waitForCompletion(); return { id, result }; }) );
// Process completion this.results.set(completed.id, completed.result); this.workers.delete(completed.id); this.tasks.find(t => t.id === completed.id)!.status = 'complete';
// Phase 3: Replan if needed const shouldReplan = await this.evaluateProgress(); if (shouldReplan) { const newTasks = await this.replanRemainingTasks(); this.tasks.push(...newTasks); } }
// Phase 4: Synthesize final result return this.synthesizeResults(); }
private async planTasks(goal: string): Promise<WorkerTask[]> { const plan = await this.coordinatorLLM.complete({ messages: [{ role: 'user', content: `Break this goal into concrete tasks with dependencies: Goal: ${goal} Return JSON: [{id, description, dependencies: []}]`, }], }); return JSON.parse(plan.content); }
private launchWorker(task: WorkerTask): SubAgent { // Inject dependency results into the worker's context const dependencyContext = task.dependencies .map(d => `Result of "${d}": ${this.results.get(d)}`) .join('\n');
return createSubAgent({ task: `${task.description}\n\nContext from prior tasks:\n${dependencyContext}`, maxTurns: 15, }); }}Coordinator Workflow Visualization
Section titled “Coordinator Workflow Visualization”sequenceDiagram participant C as Coordinator participant W1 as Worker 1 participant W2 as Worker 2 participant W3 as Worker 3
C->>C: Plan tasks (A, B, C, D) Note over C: A, B have no deps; C depends on A; D depends on B, C
par Independent tasks C->>W1: Task A (analyze codebase) C->>W2: Task B (read requirements) end
W1-->>C: Result A C->>W3: Task C (design solution) [depends on A]
W2-->>C: Result B Note over C: D waiting for B and C
W3-->>C: Result C C->>W1: Task D (implement) [depends on B, C]
W1-->>C: Result D C->>C: Synthesize final resultPattern 3: Team (Swarm)
Section titled “Pattern 3: Team (Swarm)”Essence
Section titled “Essence”Multiple agents operate as peers with no central authority. They communicate through a shared mailbox (message bus), each agent deciding independently what to work on based on messages from others.
When to Use
Section titled “When to Use”- The problem requires diverse expertise (each agent specializes in a domain)
- Agents need to iterate collaboratively (back-and-forth discussion)
- No single agent can see the whole picture
Implementation
Section titled “Implementation”interface MailboxMessage { from: string; to: string | 'all'; type: 'request' | 'response' | 'update' | 'done'; content: string; timestamp: number;}
class TeamPattern { private mailbox: MailboxMessage[] = []; private agents = new Map<string, TeamAgent>();
addAgent(id: string, role: string, specialization: string) { this.agents.set(id, new TeamAgent(id, role, specialization, this.mailbox)); }
async execute(goal: string, maxRounds: number = 10): Promise<string> { // Broadcast the goal to all agents this.broadcast({ from: 'system', to: 'all', type: 'request', content: goal, timestamp: Date.now() });
for (let round = 0; round < maxRounds; round++) { // Each agent processes unread messages and responds const roundPromises = [...this.agents.values()].map(agent => agent.processMailbox() );
await Promise.all(roundPromises);
// Check if any agent signals completion const doneMessages = this.mailbox.filter(m => m.type === 'done'); if (doneMessages.length >= this.agents.size * 0.5) { break; // Majority consensus: done } }
return this.synthesizeMailbox(); }
private broadcast(message: MailboxMessage) { this.mailbox.push(message); }
private synthesizeMailbox(): string { return this.mailbox .filter(m => m.type === 'response' || m.type === 'done') .map(m => `[${m.from}]: ${m.content}`) .join('\n\n'); }}
class TeamAgent { private lastReadIndex = 0;
constructor( private id: string, private role: string, private specialization: string, private mailbox: MailboxMessage[], ) {}
async processMailbox(): Promise<void> { // Read unread messages const unread = this.mailbox.slice(this.lastReadIndex); this.lastReadIndex = this.mailbox.length;
if (unread.length === 0) return;
// Generate response using LLM with role-specific system prompt const response = await callLLM({ system: `You are ${this.role}, specializing in ${this.specialization}. Read the team messages and contribute your expertise. Reply to relevant messages or add new insights.`, messages: [{ role: 'user', content: unread.map(m => `[${m.from}] (${m.type}): ${m.content}`).join('\n'), }], });
// Post response to mailbox this.mailbox.push({ from: this.id, to: 'all', type: 'response', content: response.content, timestamp: Date.now(), }); }}Cross-Framework Comparison
Section titled “Cross-Framework Comparison”How do these patterns compare to popular multi-agent frameworks?
| Feature | Claude Code | AutoGen | CrewAI | LangGraph |
|---|---|---|---|---|
| Fork (parallel) | ✅ Native | ⚠️ Via GroupChat | ❌ Sequential only | ✅ Via branches |
| Coordinator | ✅ Native | ✅ Via AssistantAgent | ✅ Via Manager | ✅ Via state graph |
| Team/Swarm | ✅ Native | ✅ GroupChat | ✅ Crew roles | ⚠️ Complex setup |
| Communication | Mailbox + direct | GroupChat | Delegation | State passing |
| Cache reuse | ✅ Prompt Cache | ❌ No | ❌ No | ❌ No |
| Isolation | ✅ Git worktrees | ❌ Shared memory | ❌ Shared state | ⚠️ Per-node state |
| Dynamic replanning | ✅ Coordinator replan | ✅ Nested chats | ⚠️ Manual | ✅ Conditional edges |
| Max practical agents | 5-10 | 10+ | 5-7 | No hard limit |
| Learning curve | Medium | High | Low | High |
| Token efficiency | ✅ Best (cache reuse) | ⚠️ Moderate | ⚠️ Moderate | ⚠️ Moderate |
Decision Tree: Which Pattern to Use
Section titled “Decision Tree: Which Pattern to Use”graph TD START["What's the task structure?"] --> Q1{"Are subtasks<br/>independent?"}
Q1 -->|"Yes, completely"| FORK["✅ Use Fork<br/>Maximum parallelism<br/>Best cache reuse"]
Q1 -->|"Some dependencies"| Q2{"Need dynamic<br/>replanning?"}
Q2 -->|"No, fixed plan"| Q3{"How many<br/>dependency levels?"} Q3 -->|"1 level"| FORK2["✅ Use Fork<br/>with sequenced groups"] Q3 -->|"2+ levels"| COORD["✅ Use Coordinator<br/>Manages dependency DAG"]
Q2 -->|"Yes"| COORD
Q1 -->|"Deeply intertwined"| Q4{"Need diverse<br/>expertise?"} Q4 -->|"Yes"| TEAM["✅ Use Team<br/>Peer collaboration"] Q4 -->|"No"| COORD2["✅ Use Coordinator<br/>with iteration"]
style FORK fill:#4ade80 style FORK2 fill:#4ade80 style COORD fill:#60a5fa style COORD2 fill:#60a5fa style TEAM fill:#c084fcQuick Reference
Section titled “Quick Reference”| Scenario | Recommended Pattern |
|---|---|
| Review 5 files independently | Fork |
| Implement a feature across multiple files | Coordinator |
| Debug a complex cross-system issue | Team |
| Run tests in parallel | Fork |
| Refactor with dependency analysis | Coordinator |
| Design review with frontend + backend + security perspectives | Team |
| Generate translations for 10 languages | Fork |
| Multi-step data pipeline | Coordinator |
Reusable Agent Orchestration Template
Section titled “Reusable Agent Orchestration Template”// ============================================// Universal Agent Orchestrator// ============================================
type OrchestrationType = 'fork' | 'coordinator' | 'team';
interface OrchestrationConfig { type: OrchestrationType; maxAgents: number; maxRounds: number; timeout: number; cacheReuse: boolean;}
interface OrchestratorResult { type: OrchestrationType; results: AgentResult[]; totalTokens: number; wallTime: number;}
async function orchestrate( goal: string, context: AgentContext, config: OrchestrationConfig,): Promise<OrchestratorResult> { const start = performance.now();
switch (config.type) { case 'fork': { // Step 1: Decompose into independent tasks const tasks = await decomposeTasks(goal, context);
// Step 2: Run all in parallel const results = await Promise.all( tasks.slice(0, config.maxAgents).map(task => runSubAgent({ system: context.systemPrompt, messages: [...context.messages, { role: 'user', content: task }], maxTurns: config.maxRounds, }) ) );
return { type: 'fork', results, totalTokens: results.reduce((sum, r) => sum + r.tokensUsed, 0), wallTime: performance.now() - start, }; }
case 'coordinator': { const coordinator = new CoordinatorPattern(context.llm, config.maxAgents); const result = await coordinator.execute(goal); return { type: 'coordinator', results: [{ finalMessage: result, tokensUsed: 0 }], totalTokens: 0, // tracked internally wallTime: performance.now() - start, }; }
case 'team': { const team = new TeamPattern(); // Add specialized agents based on the goal const roles = await identifyNeededRoles(goal, context); roles.forEach(role => team.addAgent(role.id, role.name, role.specialization));
const result = await team.execute(goal, config.maxRounds); return { type: 'team', results: [{ finalMessage: result, tokensUsed: 0 }], totalTokens: 0, wallTime: performance.now() - start, }; } }}Evolution Path: Start Simple, Scale Up
Section titled “Evolution Path: Start Simple, Scale Up”graph LR S1["Single Agent<br/>1 agent, sequential"] -->|"Task too large"| S2["Fork<br/>N agents, parallel"] S2 -->|"Need coordination"| S3["Coordinator<br/>1 manager + N workers"] S3 -->|"Need collaboration"| S4["Team<br/>N peers + mailbox"]
style S1 fill:#94a3b8 style S2 fill:#4ade80 style S3 fill:#60a5fa style S4 fill:#c084fcAnti-Patterns
Section titled “Anti-Patterns”| Anti-Pattern | Problem | Better Approach |
|---|---|---|
| Team for independent tasks | Unnecessary communication overhead | Use Fork |
| Fork for dependent tasks | Race conditions, missing inputs | Use Coordinator |
| Too many agents (>10) | Exponential communication cost | Split into sub-groups |
| No timeout per agent | One stuck agent blocks everything | Per-agent timeout + fallback |
| Shared mutable state | Race conditions | Immutable messages or isolated worktrees |
| Coordinator does all the work | Defeats the purpose, bottleneck | Delegate actual work to workers |