multi-agent-patterns
Multi-Agent Systems: orchestration vs choreography, tool routing, state management, agent handoffs, parallelization (fan-out/fan-in), error handling in multi-agent workflows, Claude SDK patterns (Agent/Tool/Handoff), and observability with OpenTelemetry.
Best use case
multi-agent-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Multi-Agent Systems: orchestration vs choreography, tool routing, state management, agent handoffs, parallelization (fan-out/fan-in), error handling in multi-agent workflows, Claude SDK patterns (Agent/Tool/Handoff), and observability with OpenTelemetry.
Teams using multi-agent-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/multi-agent-patterns/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How multi-agent-patterns Compares
| Feature / Agent | multi-agent-patterns | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Multi-Agent Systems: orchestration vs choreography, tool routing, state management, agent handoffs, parallelization (fan-out/fan-in), error handling in multi-agent workflows, Claude SDK patterns (Agent/Tool/Handoff), and observability with OpenTelemetry.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
Related Guides
SKILL.md Source
# Multi-Agent Patterns
Patterns for building reliable, scalable multi-agent systems with Claude.
## When to Activate
- Designing a system where multiple Claude agents collaborate
- Implementing task decomposition with parallel sub-agents
- Routing tasks between specialized agents (router/dispatcher patterns)
- Managing state across agent invocations
- Building reliable agent-to-agent handoffs
- Adding observability (tracing, latency) across agent boundaries
- Selecting the right coordination pattern (fan-out, pipeline, split-role)
---
## Orchestration vs. Choreography
```
Orchestration (Central Control) Choreography (Decentralized Events)
───────────────────────────────── ─────────────────────────────────────
Orchestrator Agent A ──event──▶ Agent B
/ | \ Agent B ──event──▶ Agent C
Agent A Agent B Agent C (no central coordinator)
WHEN: Clear workflow, sequential steps WHEN: Loose coupling, event-driven, scaling
easy to debug and reason about independent microservices
```
**Choose Orchestration when:**
- Workflow steps are known in advance
- You need a single audit trail
- Failure handling requires central coordination
- Order of execution matters
**Choose Choreography when:**
- Services must remain independent
- New consumers can subscribe without modifying producers
- Eventual consistency is acceptable
- Scale requires horizontal distribution
---
## Tool Routing
The orchestrator decides which agent/tool handles a task.
### Intent Classification Router
```typescript
// Claude as router — classify then dispatch
const AGENT_REGISTRY = {
'code-review': codeReviewAgent,
'security-scan': securityAgent,
'test-generation': tddAgent,
'documentation': docAgent,
};
async function route(task: string, context: string): Promise<AgentResult> {
const classification = await claude.messages.create({
model: 'claude-haiku-latest', // Haiku for lightweight routing
system: `Classify the task into one of: ${Object.keys(AGENT_REGISTRY).join(', ')}.
Reply with ONLY the category name.`,
messages: [{ role: 'user', content: task }],
max_tokens: 10,
});
const category = classification.content[0].text.trim();
const agent = AGENT_REGISTRY[category];
if (!agent) throw new Error(`No agent for category: ${category}`);
return agent.run(task, context);
}
```
---
## State Management
Where does state live between agent calls?
### In-Memory (Short-Lived Workflows)
```typescript
interface WorkflowState {
taskId: string;
input: string;
steps: StepResult[];
metadata: Record<string, unknown>;
}
class WorkflowContext {
private state: WorkflowState;
constructor(taskId: string, input: string) {
this.state = { taskId, input, steps: [], metadata: {} };
}
addStep(name: string, result: unknown): void {
this.state.steps.push({ name, result, timestamp: Date.now() });
}
getLastResult(): unknown {
return this.state.steps.at(-1)?.result;
}
toHandoffSummary(): string {
// Compress context for sub-agent handoffs
return `Task: ${this.state.input}\n` +
`Completed: ${this.state.steps.map(s => s.name).join(', ')}\n` +
`Last result: ${JSON.stringify(this.getLastResult())}`;
}
}
```
> For durable state (Redis, DynamoDB event log) and task decomposition handoffs, see `multi-agent-patterns-advanced`.
---
## Handoff Patterns
### Full Context Handoff
Pass the complete conversation history to the next agent — use when the sub-agent needs full context.
```typescript
async function handoffWithFullContext(
conversation: Message[],
nextAgentSystem: string
): Promise<string> {
const response = await claude.messages.create({
model: 'claude-sonnet-latest',
system: nextAgentSystem,
messages: conversation, // Full history passed through
max_tokens: 4096,
});
return response.content[0].text;
}
```
### Summary Handoff
Compress context before handoff — use for long workflows or to save tokens.
```typescript
async function summarizeForHandoff(
context: WorkflowContext,
maxTokens = 500
): Promise<string> {
const summary = await claude.messages.create({
model: 'claude-haiku-latest', // Haiku for cheap summarization
system: 'Summarize the key findings and decisions. Be concise.',
messages: [{
role: 'user',
content: `Summarize this workflow progress for handoff to next agent:\n${context.toHandoffSummary()}`,
}],
max_tokens: maxTokens,
});
return summary.content[0].text;
}
```
---
## Parallelization (Fan-Out / Fan-In)
```typescript
async function parallelReview(codeFiles: string[]): Promise<ReviewResult[]> {
// Fan-out: launch all reviews concurrently
const reviewPromises = codeFiles.map(file =>
reviewAgent.run(file).catch(err => ({
file,
error: err.message,
issues: [],
}))
);
// Fan-in: collect results
const results = await Promise.allSettled(reviewPromises);
return results.map((result, i) => {
if (result.status === 'fulfilled') return result.value;
return { file: codeFiles[i], error: result.reason.message, issues: [] };
});
}
// Controlled parallelism (avoid rate limits)
async function parallelWithConcurrencyLimit<T>(
tasks: (() => Promise<T>)[],
concurrency = 5
): Promise<T[]> {
const results: T[] = [];
const chunks = [];
for (let i = 0; i < tasks.length; i += concurrency) {
chunks.push(tasks.slice(i, i + concurrency));
}
for (const chunk of chunks) {
const chunkResults = await Promise.all(chunk.map(t => t()));
results.push(...chunkResults);
}
return results;
}
```
---
## Error Handling in Multi-Agent Systems
### Retry with Fallback Agent
```typescript
async function runWithFallback<T>(
primary: () => Promise<T>,
fallback: () => Promise<T>,
maxRetries = 2
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await primary();
} catch (err) {
if (attempt === maxRetries) {
console.warn(`Primary agent failed after ${maxRetries} retries, using fallback`);
return fallback();
}
await backoff(attempt);
}
}
throw new Error('Unreachable');
}
// Partial results: continue with what succeeded
async function collectPartialResults<T>(
tasks: Promise<T>[],
minRequired: number
): Promise<T[]> {
const results = await Promise.allSettled(tasks);
const successes = results
.filter((r): r is PromiseFulfilledResult<T> => r.status === 'fulfilled')
.map(r => r.value);
if (successes.length < minRequired) {
throw new Error(`Only ${successes.length}/${tasks.length} tasks succeeded (need ${minRequired})`);
}
return successes;
}
```
---
## Claude Agent SDK Patterns
The core agentic loop: call the model, handle `tool_use` stop reason by executing tools and appending results, repeat until `end_turn`.
```typescript
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
async function orchestratorLoop(goal: string): Promise<string> {
const messages: Anthropic.MessageParam[] = [{ role: 'user', content: goal }];
while (true) {
const response = await client.messages.create({
model: 'claude-sonnet-latest',
system: ORCHESTRATOR_SYSTEM_PROMPT,
tools: AVAILABLE_TOOLS,
messages,
max_tokens: 4096,
});
if (response.stop_reason === 'end_turn') {
return response.content.filter(b => b.type === 'text').map(b => b.text).join('');
}
// Handle tool calls: execute all in parallel, append results
messages.push({ role: 'assistant', content: response.content });
const toolResults = await Promise.all(
response.content
.filter((b): b is Anthropic.ToolUseBlock => b.type === 'tool_use')
.map(async (t) => ({
type: 'tool_result' as const,
tool_use_id: t.id,
content: await executeTool(t.name, t.input),
}))
);
messages.push({ role: 'user', content: toolResults });
}
}
```
---
## Observability
```typescript
import { trace, context, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('multi-agent-system');
async function tracedAgentCall<T>(
agentName: string,
task: string,
fn: () => Promise<T>
): Promise<T> {
return tracer.startActiveSpan(`agent.${agentName}`, async (span) => {
span.setAttributes({
'agent.name': agentName,
'agent.task.length': task.length,
'agent.task.preview': task.slice(0, 100),
});
try {
const result = await fn();
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (err) {
span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });
span.recordException(err as Error);
throw err;
} finally {
span.end();
}
});
}
// Log key agent metrics
function logAgentCall(event: {
agent: string;
inputTokens: number;
outputTokens: number;
latencyMs: number;
toolCalls: number;
success: boolean;
}): void {
console.log(JSON.stringify({
type: 'agent_call',
...event,
timestamp: new Date().toISOString(),
}));
}
```
---
## Anti-Patterns
### Running Independent Sub-Agents Sequentially Instead of in Parallel
**Wrong:**
```typescript
// Runs one at a time — wastes latency when tasks are independent
const reviewResult = await codeReviewAgent.run(code)
const securityResult = await securityAgent.run(code)
const docsResult = await docAgent.run(code)
```
**Correct:**
```typescript
// Fan-out: all three run concurrently
const [reviewResult, securityResult, docsResult] = await Promise.all([
codeReviewAgent.run(code),
securityAgent.run(code),
docAgent.run(code),
])
```
**Why:** Sequential execution of independent agents multiplies latency unnecessarily — fan-out with `Promise.all` reduces wall-clock time to the slowest single agent.
---
### Passing the Full Conversation History to Every Sub-Agent
**Wrong:**
```typescript
// Sub-agent receives 50 turns of unrelated orchestrator history
async function handoff(fullConversation: Message[], nextSystem: string) {
return claude.messages.create({
system: nextSystem,
messages: fullConversation, // bloats context, raises cost, degrades focus
max_tokens: 4096,
})
}
```
**Correct:**
```typescript
// Compress to only what the next agent needs
const summary = await summarizeForHandoff(context, 500)
return claude.messages.create({
system: nextSystem,
messages: [{ role: 'user', content: summary }],
max_tokens: 4096,
})
```
**Why:** Passing irrelevant history inflates token costs, risks hitting context limits, and distracts sub-agents with information they don't need.
---
### Using Opus for Lightweight Routing Decisions
**Wrong:**
```typescript
const classification = await claude.messages.create({
model: 'claude-opus-latest', // overkill for a one-word classification
system: 'Classify as: code-review | security-scan | documentation.',
messages: [{ role: 'user', content: task }],
max_tokens: 10,
})
```
**Correct:**
```typescript
const classification = await claude.messages.create({
model: 'claude-haiku-latest', // fast and cheap for routing
system: 'Classify as: code-review | security-scan | documentation. Reply with the category only.',
messages: [{ role: 'user', content: task }],
max_tokens: 10,
})
```
**Why:** Routing is a lightweight classification task; using a heavyweight model wastes cost and latency on a decision that requires no deep reasoning.
> For advanced patterns — capability registry, durable state, task decomposition, testing multi-agent systems, pattern quick-selection guide, and failure handling — see `multi-agent-patterns-advanced`.Related Skills
zero-trust-patterns
Zero-Trust security patterns — mTLS between microservices (Istio/SPIFFE), SPIRE workload identity, OPA/Envoy authorization, NetworkPolicy default-deny-all, short-lived credentials, service mesh security, and Kubernetes RBAC hardening.
webrtc-patterns
WebRTC patterns — peer connection setup, ICE/STUN/TURN configuration, signaling server design, SFU vs mesh topology, screen sharing, media track management, and reconnect/ICE restart handling.
webhook-patterns
Webhook patterns for receiving, verifying (HMAC), and idempotently processing third-party events. Covers Stripe, GitHub, and generic webhook patterns, delivery guarantees, retry handling, and testing.
wasm-patterns
WebAssembly patterns: wasm-pack, wasm-bindgen (JS↔Wasm interop), WASI, Component Model, wasm-opt, Rust-to-WASM compilation, JS integration (web workers, streaming instantiation), and production deployment (CDN, Content-Type headers).
ux-micro-patterns
UX micro-patterns for every product state: Empty States, Loading States (skeleton screens, spinners, optimistic UI), Error States, Success States, Confirmation Dialogs, Onboarding Flows, and Progressive Disclosure. These patterns apply to every feature — done wrong, they're the biggest source of user confusion.
typescript-patterns
TypeScript patterns — type system best practices, strict mode, utility types, generics, discriminated unions, error handling with Result types, and module organization. Core patterns for production TypeScript.
typescript-patterns-advanced
Advanced TypeScript — mapped types, template literal types, conditional types, infer, type guards, decorators, async patterns, testing with Vitest/Jest, and performance. Extends typescript-patterns.
typescript-monorepo-patterns
TypeScript monorepo patterns with Turborepo + pnpm workspaces. Covers package structure, shared configs, task pipeline caching, build orchestration, and publishing strategy.
terraform-patterns
Infrastructure as Code with Terraform — project structure, remote state, modules, workspace strategy, AWS/GCP patterns, CI/CD integration, and security hardening. The standard for managing production infrastructure.
swiftui-patterns
SwiftUI architecture patterns, state management with @Observable, view composition, navigation, performance optimization, and modern iOS/macOS UI best practices.
swift-patterns
Core Swift patterns — value vs reference types, protocols, generics, optionals, Result, error handling, Codable, and module organization. Foundation for all Swift development.
swift-patterns-advanced
Advanced Swift patterns — property wrappers, result builders, Combine basics, opaque & existential types, macro system, advanced generics, and performance optimization. Extends swift-patterns.