AI Agent Orchestration Patterns
Patterns for building and orchestrating AI agents effectively.
When to Use
- •Building multi-agent systems
- •Designing agent architectures
- •Implementing agent communication patterns
- •Debugging agent workflows
- •Optimizing agent performance and costs
Core Architecture Patterns
Single Agent with Tools
Use when: Task is well-defined, tools are sufficient
code
User Request → Agent → [Tool Selection] → Tool Execution → Response
Implementation:
typescript
const agent = {
systemPrompt: "You are a helpful assistant with access to tools.",
tools: [searchTool, calculatorTool, fileTool],
maxIterations: 10,
};
async function run(input: string) {
let context = { messages: [{ role: 'user', content: input }] };
while (!context.done && context.iterations < agent.maxIterations) {
const response = await llm.chat(context.messages, agent.tools);
if (response.toolCalls) {
for (const call of response.toolCalls) {
const result = await executeTool(call);
context.messages.push({ role: 'tool', content: result });
}
} else {
context.done = true;
return response.content;
}
context.iterations++;
}
}
Supervisor Pattern
Use when: Complex tasks requiring delegation to specialists
code
User Request → Supervisor Agent → [Routing Decision]
↓
┌──────────────────────────────────────┐
↓ ↓ ↓ ↓
Researcher Coder Writer Reviewer
↓ ↓ ↓ ↓
└──────────────────────────────────────┘
↓
Supervisor Synthesis
↓
Response
Key decisions:
- •Supervisor chooses which agent(s) to invoke
- •Can be parallel (all at once) or sequential (one at a time)
- •Supervisor synthesizes results
Pipeline Pattern
Use when: Task has clear sequential stages
code
Input → Stage 1 → Stage 2 → Stage 3 → Output
(Plan) (Execute) (Review)
Example - Code Generation Pipeline:
typescript
const pipeline = [
{ name: 'planner', prompt: 'Create implementation plan' },
{ name: 'coder', prompt: 'Implement the plan' },
{ name: 'reviewer', prompt: 'Review for bugs and improvements' },
{ name: 'tester', prompt: 'Write tests for the implementation' },
];
async function runPipeline(input: string) {
let context = input;
for (const stage of pipeline) {
context = await runAgent(stage.name, stage.prompt, context);
}
return context;
}
Parallel Execution Pattern
Use when: Independent subtasks can be processed simultaneously
typescript
async function parallelAgents(tasks: Task[]) {
const results = await Promise.all(
tasks.map(task => runAgent(task.agent, task.input))
);
return synthesize(results);
}
Reflection Pattern
Use when: Quality is critical, self-improvement needed
code
Initial Response → Critique Agent → Refined Response → [Iterate?]
Implementation:
typescript
async function reflectiveAgent(input: string, maxReflections = 3) {
let response = await generateResponse(input);
for (let i = 0; i < maxReflections; i++) {
const critique = await critiqueResponse(response);
if (critique.satisfactory) break;
response = await improveResponse(response, critique.feedback);
}
return response;
}
Communication Patterns
Message Passing
Agents communicate through structured messages:
typescript
interface AgentMessage {
from: string;
to: string;
type: 'request' | 'response' | 'update';
content: unknown;
metadata: {
timestamp: number;
traceId: string;
};
}
Shared State
Agents share a common context:
typescript
interface SharedContext {
goal: string;
currentState: unknown;
history: Message[];
artifacts: Map<string, unknown>;
}
// Each agent reads/writes to shared context
async function agentStep(agent: Agent, context: SharedContext) {
const result = await agent.run(context);
context.history.push({ agent: agent.name, result });
context.currentState = result.newState;
}
Event-Driven
Agents react to events:
typescript
const eventBus = new EventEmitter();
// Agent subscribes to relevant events
eventBus.on('code:generated', async (code) => {
const review = await reviewerAgent.review(code);
eventBus.emit('code:reviewed', review);
});
eventBus.on('code:reviewed', async (review) => {
if (!review.approved) {
eventBus.emit('code:needsRevision', review.feedback);
}
});
Cost Optimization
Model Routing
Use appropriate models for each task:
typescript
const modelRouter = {
planning: 'claude-3-opus', // Complex reasoning
coding: 'claude-3-sonnet', // Balance of capability/cost
formatting: 'claude-3-haiku', // Simple tasks
review: 'claude-3-sonnet',
};
async function routedCall(task: string, input: string) {
const model = modelRouter[task] ?? 'claude-3-sonnet';
return llm.chat(model, input);
}
Caching
Cache expensive operations:
typescript
const cache = new Map<string, unknown>();
async function cachedAgent(input: string) {
const key = hash(input);
if (cache.has(key)) return cache.get(key);
const result = await runExpensiveAgent(input);
cache.set(key, result);
return result;
}
Early Termination
Stop when goal is achieved:
typescript
async function goalOrientedLoop(goal: string) {
while (true) {
const state = await getState();
const evaluation = await evaluateGoal(goal, state);
if (evaluation.achieved) return state;
if (evaluation.impossible) throw new Error('Goal unreachable');
await takeAction(evaluation.suggestedAction);
}
}
Error Handling
Retry with Backoff
typescript
async function retryAgent(fn: () => Promise<T>, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
await sleep(Math.pow(2, i) * 1000);
}
}
}
Fallback Agents
typescript
const agents = [primaryAgent, fallbackAgent1, fallbackAgent2];
async function resilientRun(input: string) {
for (const agent of agents) {
try {
return await agent.run(input);
} catch (error) {
console.log(`${agent.name} failed, trying next`);
}
}
throw new Error('All agents failed');
}
Debugging
Tracing
typescript
interface Trace {
traceId: string;
spans: Span[];
}
interface Span {
name: string;
startTime: number;
endTime: number;
input: unknown;
output: unknown;
metadata: Record<string, unknown>;
}
Logging
typescript
function instrumentAgent(agent: Agent) {
return {
...agent,
async run(input: string) {
console.log(`[${agent.name}] Input:`, input);
const start = Date.now();
const result = await agent.run(input);
console.log(`[${agent.name}] Output (${Date.now() - start}ms):`, result);
return result;
},
};
}
Anti-Patterns
- •Infinite loops - Always have max iterations
- •No error handling - Agents fail; handle gracefully
- •Tight coupling - Agents should be independent
- •Over-orchestration - Sometimes one agent is enough
- •Ignoring costs - Monitor token usage, use appropriate models