跳转到内容

Pattern: Multi-Agent Coordination

此内容尚不支持你的语言。

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
end
DimensionForkCoordinatorTeam
TopologyStar (parent → children)Hub-spoke (coordinator → workers)Mesh (peer-to-peer)
CommunicationOne-way (result return)Bidirectional (task/report)Multi-directional (mailbox)
Child autonomyFire-and-forgetManaged lifecycleFully autonomous
Parent overheadLowHigh (coordination logic)None (no central control)
Complexity⭐⭐⭐⭐⭐⭐⭐⭐
Best forIndependent subtasksComplex workflowsCollaborative problem-solving
Max agents5-103-72-4
Cache reuse✅ Excellent✅ Good⚠️ Limited
Error isolation✅ Each child independent⚠️ Coordinator is SPOF❌ Errors can cascade

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.

  • 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)
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);
}
// Parent agent identifies changed files, then forks reviewers
const 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 report
const 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')
}`,
}],
});

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.

  • 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
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,
});
}
}
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 result

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.

  • 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
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(),
});
}
}

How do these patterns compare to popular multi-agent frameworks?

FeatureClaude CodeAutoGenCrewAILangGraph
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
CommunicationMailbox + directGroupChatDelegationState 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 agents5-1010+5-7No hard limit
Learning curveMediumHighLowHigh
Token efficiency✅ Best (cache reuse)⚠️ Moderate⚠️ Moderate⚠️ Moderate
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:#c084fc
ScenarioRecommended Pattern
Review 5 files independentlyFork
Implement a feature across multiple filesCoordinator
Debug a complex cross-system issueTeam
Run tests in parallelFork
Refactor with dependency analysisCoordinator
Design review with frontend + backend + security perspectivesTeam
Generate translations for 10 languagesFork
Multi-step data pipelineCoordinator
// ============================================
// 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,
};
}
}
}
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:#c084fc
Anti-PatternProblemBetter Approach
Team for independent tasksUnnecessary communication overheadUse Fork
Fork for dependent tasksRace conditions, missing inputsUse Coordinator
Too many agents (>10)Exponential communication costSplit into sub-groups
No timeout per agentOne stuck agent blocks everythingPer-agent timeout + fallback
Shared mutable stateRace conditionsImmutable messages or isolated worktrees
Coordinator does all the workDefeats the purpose, bottleneckDelegate actual work to workers