AI Agents
The supervisor pattern separates planning from execution in AI agents. How to implement an orchestrator that routes tasks to specialist workers.
A single agent given a complex goal - "research this market, write a competitive analysis, and identify three acquisition targets" - will attempt to do all of it in sequence, with the same context window, the same tool set, and the same system prompt. The result is a generalist trying to be a specialist in three different domains simultaneously. Output quality degrades, the context fills with irrelevant intermediate results, and debugging becomes difficult because the entire task lives in one undifferentiated loop. The supervisor pattern solves this by separating planning from execution.
The supervisor pattern is an orchestrator agent that receives a high-level goal, decomposes it into subtasks, delegates each subtask to a specialist worker agent, and synthesises the workers' outputs into a final result. The orchestrator does not do domain work itself - it plans, routes, monitors, and assembles. The workers do not plan - they receive a specific, scoped task and execute it with domain-specific tools and context.
```text
User goal
│
▼
┌─────────────────────────────┐
│ Supervisor / Orchestrator │
│ - decomposes the goal │
│ - routes to workers │
│ - handles failures │
│ - synthesises results │
└──────────┬──────────────────┘
│ delegates subtasks
┌──────┼──────┐
▼ ▼ ▼
Worker Worker Worker
(search) (write) (verify)
```
Apply the supervisor pattern when two or more of these are true:
Do not apply the supervisor pattern when the task is sequential and each step depends entirely on the previous one - a simple chain is more predictable and cheaper.
The orchestrator's job is decomposition and routing. It should receive the goal, produce a structured task plan, and invoke workers for each task. Structured output via tool use is the most reliable way to get a machine-readable task plan from the orchestrator:
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const planningTool: Anthropic.Tool = {
name: 'create_task_plan',
description: 'Decompose the user's goal into specific subtasks for worker agents.',
input_schema: {
type: 'object' as const,
properties: {
tasks: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
worker: { type: 'string', enum: ['researcher', 'writer', 'verifier'] },
instruction: { type: 'string', description: 'Specific, self-contained instruction for this worker' },
depends_on: { type: 'array', items: { type: 'string' }, description: 'IDs of tasks that must complete first' },
},
required: ['id', 'worker', 'instruction', 'depends_on'],
},
},
},
required: ['tasks'],
},
};
async function orchestrate(goal: string): Promise {
// Phase 1: planning
const planResponse = await client.messages.create({
model: 'claude-sonnet-5',
max_tokens: 2048,
system: 'You are a task planner. Decompose the user goal into specific subtasks for specialist workers. Each task must be self-contained - the worker receives only its instruction, nothing else.',
tools: [planningTool],
tool_choice: { type: 'any' },
messages: [{ role: 'user', content: goal }],
});
const planBlock = planResponse.content.find(b => b.type === 'tool_use') as Anthropic.ToolUseBlock;
const { tasks } = planBlock.input as { tasks: Task[] };
// Phase 2: execution (respecting dependencies)
const results = await executeTaskPlan(tasks);
// Phase 3: synthesis
const synthesisResponse = await client.messages.create({
model: 'claude-sonnet-5',
max_tokens: 4096,
system: 'Synthesise the worker outputs into a coherent final response to the user's original goal.',
messages: [{
role: 'user',
content: `Original goal: ${goal}
Worker outputs:
${formatResults(results)}`,
}],
});
return synthesisResponse.content[0].type === 'text' ? synthesisResponse.content[0].text : ', ';
}
Workers are purpose-built agents with narrow scope. Each worker gets: a system prompt describing its specialisation, the tools relevant to its domain only, and the specific instruction from the orchestrator - nothing else. No shared state, no awareness of sibling workers:
const workerConfigs: Record = {
researcher: {
model: 'claude-sonnet-5',
system: 'You are a research specialist. Use the search and fetch tools to gather accurate, cited information. Return structured findings with source URLs.',
tools: [webSearchTool, fetchPageTool],
maxIterations: 10,
},
writer: {
model: 'claude-sonnet-5',
system: 'You are a writing specialist. Transform research findings into clear, well-structured prose. Do not add information not present in the provided research.',
tools: [], // writer gets no tools - works from provided context only
maxIterations: 1,
},
verifier: {
model: 'claude-haiku-4-5-20251001', // Fast model for verification tasks
system: 'You are a fact-checker. Verify specific claims against provided sources. Return a structured report of confirmed, unconfirmed, and contradicted claims.',
tools: [fetchPageTool],
maxIterations: 5,
},
};
async function runWorker(workerType: string, instruction: string): Promise {
const config = workerConfigs[workerType];
if (!config) throw new Error(`Unknown worker type: ${workerType}`);
return runAgentLoop(instruction, config.tools, config.maxIterations, config.system, config.model);
}
The most powerful feature of the supervisor pattern is parallelism. Tasks without dependencies can execute simultaneously, collapsing wall-clock time significantly. A plan with three independent tasks that each take 5 seconds executes in 5 seconds with parallelism, not 15:
async function executeTaskPlan(tasks: Task[]): Promise
In production, the supervisor pattern introduces failure surface area at each worker boundary:
The supervisor pattern is not always the right choice. A sequential prompt chain (output of step N is input to step N+1) is simpler and more debuggable when tasks are strictly ordered. A single generalist agent with many tools is sufficient when the task domain is coherent and the context window comfortably fits the work. The supervisor pattern earns its overhead when the task is broad enough that multiple specialisations genuinely improve output quality - typically when the task involves research, transformation, and validation as distinct phases, each of which a generalist does worse than a specialist.
The supervisor pattern handles task decomposition and routing, but individual workers still run the agentic observe-think-act loop internally. For a comparison of the supervisor approach against swarm architectures where agents communicate peer-to-peer, see agent swarm vs pipeline.