Multi-Agent Orchestration Patterns
Day 22 — Multi-Agent Orchestration Patterns
When a single LLM call isn't enough, you need multiple agents coordinating. The orchestration pattern you choose — supervisor, pipeline, peer-to-peer, or marketplace — determines your system's reliability, latency, and cost profile. Getting this wrong creates systems that are expensive, fragile, and impossible to debug.
1. The Four Orchestration Patterns
// Pattern 1: Supervisor — one agent delegates to others
class SupervisorOrchestrator {
private agents: Map<string, Agent>;
private supervisor: Agent;
async execute(task: Task): Promise<Result> {
// Supervisor plans and delegates
const plan = await this.supervisor.plan(task);
const results: StepResult[] = [];
for (const step of plan.steps) {
const agent = this.agents.get(step.agentId);
if (!agent) throw new Error(`Unknown agent: ${step.agentId}`);
const result = await agent.execute(step.input, {
context: results, // previous step results
budget: step.tokenBudget,
});
results.push(result);
// Supervisor reviews and decides next step
const review = await this.supervisor.review(result, plan);
if (review.action === 'retry') continue;
if (review.action === 'abort') break;
}
return this.supervisor.synthesize(results);
}
}
// Pattern 2: Pipeline — fixed sequence, each agent hands off to next
class PipelineOrchestrator {
constructor(private stages: Agent[]) {}
async execute(input: unknown): Promise<unknown> {
let current = input;
for (const stage of this.stages) {
current = await stage.execute(current);
}
return current;
}
}
// Pattern 3: Peer-to-peer — agents communicate directly
class PeerToPeerOrchestrator {
private agents: Map<string, Agent>;
private messagebus: EventEmitter;
constructor() {
this.messagebus = new EventEmitter();
}
register(agent: Agent): void {
this.agents.set(agent.id, agent);
// Agent can send messages to other agents
agent.onMessage = (target: string, message: AgentMessage) => {
this.messagebus.emit(`agent:${target}`, message);
};
// Agent receives messages
this.messagebus.on(`agent:${agent.id}`, (msg) => agent.handleMessage(msg));
}
}
// Pattern 4: Marketplace — agents bid on tasks
class MarketplaceOrchestrator {
private agents: Agent[];
async execute(task: Task): Promise<Result> {
// Broadcast task, collect bids
const bids = await Promise.all(
this.agents.map(async (agent) => ({
agent,
bid: await agent.bid(task), // confidence, estimated cost, time
}))
);
// Select best agent
const winner = bids
.filter(b => b.bid.canHandle)
.sort((a, b) => b.bid.confidence - a.bid.confidence)[0];
return winner.agent.execute(task);
}
}
2. When to Use Each Pattern
const patternGuide = {
supervisor: {
useWhen: [
'Tasks require dynamic planning (steps not known upfront)',
'Need quality control between steps',
'Complex multi-domain tasks',
],
avoid: 'Simple, predictable workflows — supervisor adds latency and cost',
example: 'Research agent that plans, delegates research to specialists, synthesizes',
},
pipeline: {
useWhen: [
'Fixed, well-defined sequence of transformations',
'Each step has a single responsibility',
'Output format of each step is stable',
],
avoid: 'Dynamic tasks where step order or count varies',
example: 'Document processing: extract → classify → enrich → store',
},
peerToPeer: {
useWhen: [
'Agents need to collaborate iteratively',
'No single agent has authority/oversight',
'Problem requires negotiation or debate',
],
avoid: 'Most production systems — hard to debug and control',
example: 'Code review: author agent and reviewer agent iterate on feedback',
},
marketplace: {
useWhen: [
'Multiple agents can handle the same task differently',
'Want to route to the best agent dynamically',
'Task specialization varies',
],
avoid: 'When only one agent can handle the task',
example: 'Customer support: route to specialist agent based on ticket type',
},
};
3. Agent Lifecycle Management
Agents need explicit lifecycle management — spawning, monitoring, and cleanup:
class AgentManager {
private active = new Map<string, AgentInstance>();
private maxConcurrent = 10;
async spawn(config: AgentConfig): Promise<AgentInstance> {
if (this.active.size >= this.maxConcurrent) {
// Evict least recently active agent
const lru = [...this.active.values()]
.sort((a, b) => a.lastActive - b.lastActive)[0];
await this.terminate(lru.id);
}
const instance: AgentInstance = {
id: crypto.randomUUID(),
config,
state: 'initializing',
startedAt: Date.now(),
lastActive: Date.now(),
tokensBurned: 0,
maxTokenBudget: config.tokenBudget || 50000,
};
this.active.set(instance.id, instance);
instance.state = 'running';
return instance;
}
async terminate(agentId: string): Promise<void> {
const instance = this.active.get(agentId);
if (!instance) return;
instance.state = 'terminated';
// Save state for potential resume
await this.saveCheckpoint(instance);
this.active.delete(agentId);
}
// Monitor for stuck or expensive agents
async healthCheck(): Promise<AgentHealthReport[]> {
return [...this.active.values()].map(instance => ({
id: instance.id,
runtime: Date.now() - instance.startedAt,
tokensBurned: instance.tokensBurned,
budgetRemaining: instance.maxTokenBudget - instance.tokensBurned,
isStuck: Date.now() - instance.lastActive > 60_000, // no activity for 60s
isOverBudget: instance.tokensBurned > instance.maxTokenBudget,
}));
}
}
4. Deadlock and Livelock in Multi-Agent Systems
Multi-agent systems can deadlock (agents waiting for each other) or livelock (agents endlessly retrying without progress):
class DeadlockDetector {
private waitGraph = new Map<string, Set<string>>(); // agent → waiting-for agents
registerWait(agentId: string, waitingFor: string): void {
if (!this.waitGraph.has(agentId)) this.waitGraph.set(agentId, new Set());
this.waitGraph.get(agentId)!.add(waitingFor);
if (this.hasCycle(agentId)) {
throw new DeadlockError(`Deadlock detected involving agent ${agentId}`);
}
}
private hasCycle(start: string, visited = new Set<string>()): boolean {
if (visited.has(start)) return true;
visited.add(start);
for (const dep of this.waitGraph.get(start) || []) {
if (this.hasCycle(dep, new Set(visited))) return true;
}
return false;
}
}
// Livelock prevention: detect repeated state oscillation
class LivelockDetector {
private stateHistory = new Map<string, string[]>();
recordState(agentId: string, state: string): void {
const history = this.stateHistory.get(agentId) || [];
history.push(state);
if (history.length > 20) history.shift();
this.stateHistory.set(agentId, history);
if (this.detectOscillation(history)) {
throw new LivelockError(`Agent ${agentId} is oscillating: ${history.slice(-6).join(' → ')}`);
}
}
private detectOscillation(history: string[]): boolean {
if (history.length < 6) return false;
const last6 = history.slice(-6);
// A-B-A-B-A-B pattern
return last6[0] === last6[2] && last6[2] === last6[4] &&
last6[1] === last6[3] && last6[3] === last6[5];
}
}
Try This Today
Implement a supervisor orchestrator for a multi-step task: the supervisor plans 3 steps, delegates each to a worker agent, reviews the output, and decides whether to continue or retry. Add a token budget and deadlock timeout (kill if no progress in 30s). Test with a real task: "research X, analyze findings, write summary."
Resources
- AutoGen: Multi-Agent Conversation Framework — Microsoft's framework for multi-agent orchestration patterns
- CrewAI Documentation — Production multi-agent framework with role-based orchestration