Summary
Multi-agent systems require orchestration to coordinate work across specialized agents. Three fundamental patterns address different coordination needs: the Coordinator pattern uses a central orchestrator to delegate tasks to specialists and synthesize results; the Swarm pattern deploys multiple agents in parallel for breadth or redundancy; the Pipeline pattern chains agents sequentially where each stage transforms output for the next. Choosing the right pattern depends on task structure, latency requirements, and quality goals.
The Problem
When tasks exceed what a single agent handles well, you need multiple agents working together. But coordination introduces new challenges:
- Delegation decisions: How does work get assigned to specialists?
- Result synthesis: How do outputs from multiple agents combine?
- Error propagation: What happens when one agent fails mid-workflow?
- Context isolation: How do agents share relevant information without pollution?
- Latency vs quality: When is parallel execution worth the coordination overhead?
Without clear orchestration patterns, multi-agent systems become tangled webs of ad-hoc communication.
The Three Patterns
Pattern 1: Coordinator (Hub and Spoke)
A central orchestrator receives tasks, delegates to specialists, and synthesizes final output.
┌───────────────────┐
│ ORCHESTRATOR │
│ (Coordinator) │
└─────────┬─────────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Backend │ │ Frontend │ │ QA │
│ Engineer │ │ Engineer │ │ Engineer │
└───────────────┘ └───────────────┘ └───────────────┘
│ │ │
└─────────────────────┼─────────────────────┘
│
▼
┌───────────────────┐
│ Final Result │
└───────────────────┘
How it works:
- Orchestrator receives the high-level task
- Orchestrator analyzes task and identifies required specialists
- Orchestrator delegates sub-tasks with appropriate context
- Specialists complete work and return results
- Orchestrator synthesizes results into cohesive output
Implementation:
interface CoordinatorConfig {
specialists: Map<string, AgentConfig>;
synthesisStrategy: 'merge' | 'sequence' | 'vote';
}
async function coordinatorPattern(
task: string,
config: CoordinatorConfig
): Promise<Result<string>> {
// Step 1: Analyze task
const analysis = await analyzeTask(task);
const requiredSpecialists = analysis.identifySpecialists();
// Step 2: Delegate to specialists
const delegations = requiredSpecialists.map(specialist => ({
agent: config.specialists.get(specialist.role),
subTask: specialist.extractedTask,
context: specialist.relevantContext,
}));
// Step 3: Execute (can be parallel or sequential)
const results = await Promise.all(
delegations.map(d => executeAgent(d.agent, d.subTask, d.context))
);
// Step 4: Synthesize
return synthesizeResults(results, config.synthesisStrategy);
}
When to use:
- Tasks require multiple distinct specialties
- Final output needs coherent synthesis
- You want centralized error handling and retry logic
- Task decomposition benefits from high-level understanding
Example: Feature implementation
// User request: "Add user authentication with OAuth"
const coordinator = async (task: string) => {
// Delegate to specialists
const backendResult = await spawnAgent('backend-engineer', {
task: 'Implement OAuth routes and token management',
context: loadContext(['root', 'backend', 'auth']),
});
const frontendResult = await spawnAgent('frontend-engineer', {
task: 'Create login/logout UI components',
context: loadContext(['root', 'frontend', 'ui']),
});
const qaResult = await spawnAgent('qa-engineer', {
task: 'Write authentication tests',
context: loadContext(['root', 'testing', 'auth']),
dependencies: [backendResult, frontendResult],
});
// Synthesize into final report
return synthesize([backendResult, frontendResult, qaResult]);
};
Pattern 2: Swarm (Parallel Execution)
Multiple agents work simultaneously on related aspects of a task, providing breadth or redundancy.
┌────────────────────────────────────┐
│ TASK DISPATCHER │
└──────────────┬─────────────────────┘
│
┌───────────┬───────────┼───────────┬───────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Agent 1 │ │ Agent 2 │ │ Agent 3 │ │ Agent 4 │ │ Agent 5 │
│ (API) │ │ (Auth) │ │ (DB) │ │ (UI) │ │ (Tests) │
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
│ │ │ │ │
└───────────┴───────────┼───────────┴───────────┘
│
▼
┌───────────────────┐
│ AGGREGATOR │
│ (Merge Results) │
└───────────────────┘
How it works:
- Dispatcher divides work into parallel chunks
- Multiple agents execute simultaneously
- Aggregator collects and merges results
- Conflicts are resolved by voting, priority, or synthesis
Two swarm variants:
Variant A: Many perspectives (different agents, same task)
// Get multiple perspectives on code review
async function swarmReview(code: string): Promise<ReviewResult> {
const agents = [
{ role: 'security-auditor', focus: 'vulnerabilities' },
{ role: 'performance-reviewer', focus: 'efficiency' },
{ role: 'maintainability-reviewer', focus: 'readability' },
{ role: 'architecture-reviewer', focus: 'patterns' },
];
const reviews = await Promise.all(
agents.map(agent =>
spawnAgent(agent.role, {
task: `Review this code for ${agent.focus}`,
code,
})
)
);
// Aggregate unique findings
return aggregateReviews(reviews);
}
Variant B: Same perspective, multiple times (redundancy for reliability)
// Run same analysis 3 times, take majority vote
async function reliableAnalysis(task: string): Promise<AnalysisResult> {
const results = await Promise.all([
spawnAgent('analyzer', { task, temperature: 0.1 }),
spawnAgent('analyzer', { task, temperature: 0.3 }),
spawnAgent('analyzer', { task, temperature: 0.5 }),
]);
// Majority vote on conclusions
return majorityVote(results);
}
When to use:
- Task benefits from multiple perspectives
- You need confidence through redundancy
- Work is naturally parallelizable (no dependencies)
- Latency is critical and subtasks are independent
Pattern 3: Pipeline (Sequential Stages)
Agents process work in sequence, each stage transforming output for the next.
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Stage 1 │────▶│ Stage 2 │────▶│ Stage 3 │────▶│ Stage 4 │
│ (Plan) │ │ (Code) │ │ (Test) │ │ (Review)│
└─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │
▼ ▼ ▼ ▼
[Plan] [Code] [Tested] [Approved]
How it works:
- Task enters first stage
- Each stage transforms input and passes output to next
- Later stages can reject and send work back
- Final stage produces finished output
Implementation:
interface PipelineStage {
agent: AgentConfig;
validate: (output: unknown) => boolean;
canReject: boolean;
}
async function pipelinePattern(
input: string,
stages: PipelineStage[]
): Promise<Result<string>> {
let current = input;
for (const stage of stages) {
const result = await executeAgent(stage.agent, current);
if (!result.success) {
return result; // Pipeline fails
}
if (!stage.validate(result.data)) {
if (stage.canReject) {
// Send back to previous stage with feedback
return pipelinePattern(
addFeedback(current, result.feedback),
stages
);
}
return { success: false, error: 'Validation failed' };
}
current = result.data;
}
return { success: true, data: current };
}
Classic pipeline: Actor-Critic
// Writer produces, Critic reviews, iterate until approved
async function actorCriticPipeline(task: string): Promise<string> {
let draft = await spawnAgent('writer', { task });
let approved = false;
let iterations = 0;
const maxIterations = 5;
while (!approved && iterations < maxIterations) {
const critique = await spawnAgent('critic', {
task: 'Review this code for issues',
code: draft,
readOnly: true, // Critic cannot modify, only report
});
if (critique.issues.length === 0) {
approved = true;
} else {
// Writer addresses critique
draft = await spawnAgent('writer', {
task: 'Fix these issues',
code: draft,
issues: critique.issues,
});
}
iterations++;
}
return draft;
}
When to use:
- Work has natural sequential dependencies
- Later stages need earlier outputs as input
- You want quality gates between stages
- Feedback loops improve output quality
Combining Patterns
Real systems often combine patterns:
// Coordinator delegates to specialists (some run as pipelines, some as swarms)
async function hybridOrchestration(feature: string): Promise<Result<string>> {
// Coordinator decomposes task
const plan = await planFeature(feature);
// Backend: Pipeline (code -> test -> review)
const backendResult = await pipelinePattern(plan.backend, [
{ agent: 'backend-coder', validate: compiles },
{ agent: 'backend-tester', validate: testsPass },
{ agent: 'backend-reviewer', validate: noIssues, canReject: true },
]);
// Security: Swarm (multiple auditors in parallel)
const securityResult = await swarmPattern(backendResult.data, [
'owasp-auditor',
'injection-auditor',
'auth-auditor',
]);
// Coordinator synthesizes
return synthesize([backendResult, securityResult]);
}
Pattern Selection Guide
| Criterion | Coordinator | Swarm | Pipeline |
|---|---|---|---|
| Task structure | Multiple specialties | Parallelizable | Sequential stages |
| Dependencies | Mixed | None | Strong |
| Latency priority | Medium | High | Low |
| Quality priority | Medium | High (redundancy) | High (gates) |
| Error handling | Centralized | Per-agent | Stage-by-stage |
| Context needs | High (synthesis) | Low (isolated) | Medium (chained) |
Common Pitfalls
Pitfall 1: Over-Coordination
// BAD: Coordinator for simple task
const overkill = async (task: string) => {
const coordinator = spawnAgent('orchestrator', task);
// Simple task doesn't need delegation overhead
};
// GOOD: Direct execution for simple tasks
const appropriate = async (task: string) => {
if (isSimpleTask(task)) {
return spawnAgent('generalist', task);
}
return coordinatorPattern(task);
};
Pitfall 2: Swarm Without Aggregation Strategy
// BAD: No conflict resolution
const results = await Promise.all(agents.map(a => execute(a)));
return results; // What if they disagree?
// GOOD: Explicit aggregation
const results = await Promise.all(agents.map(a => execute(a)));
return aggregateWithVoting(results, { threshold: 0.6 });
Pitfall 3: Pipeline Without Exit Conditions
// BAD: Infinite loop risk
while (!approved) {
draft = await improve(draft);
approved = await review(draft);
}
// GOOD: Bounded iterations
let iterations = 0;
while (!approved && iterations < MAX_ITERATIONS) {
draft = await improve(draft);
approved = await review(draft);
iterations++;
}
Related
- Sub-Agent Architecture – Team structure these patterns coordinate
- Sub-Agent Context Hierarchy – Context isolation for orchestrated agents
- Tool Access Control – Permission boundaries for coordinated agents
- Actor-Critic Adversarial Coding – Pipeline pattern for quality
- Agent Swarm Patterns for Thoroughness – Swarm implementation details
- Parallel Agents for Monorepos – Swarm pattern at scale
- Clean Slate Trajectory Recovery – Error recovery in pipelines
References
- Anthropic: Multi-Agent Coordination – Official guidance
- Sub-Agent Architecture – Foundational team patterns
- Actor-Critic Adversarial Coding – Pipeline quality pattern

