Skip to content
AI Full-Stack Wiki

Multi-Agent Systems: Orchestrators, Subagents, and When to Use Them

Build an orchestrator that delegates to parallel subagents in TypeScript, write good briefs, control token costs, and learn when multiple agents beat a single one.

Difficulty: Advanced18 min readUpdated
On this page· The orchestrator-subagent architecture

Why this matters

A single agent has one context window. When a task needs a lot of reading in several independent directions (comparing ten vendors, auditing a large codebase, researching a broad question), one context fills up with dead ends, and the agent works through everything one step at a time. A multi-agent system splits the work: a lead agent delegates subtasks to subagents that each work in a fresh context, in parallel, and report back briefly. Done well, it finishes faster and reasons better on broad tasks. Done casually, it multiplies cost and creates new ways to fail.

The orchestrator-subagent architecture#

Orchestrator and subagentsA lead agent plans the task and starts three subagents in parallel, each with its own context window and tools; their condensed findings return to the lead, which writes the final report.questionPARALLELUserLead agentplans, delegatesSubagent ApricingSubagent BfeaturesSubagent CreviewsReportwith sources

Hover or tap a component to see what it does.

Multi-agent orchestration: a lead agent delegates independent subtasks to parallel subagents with their own context windows, then combines their condensed findings into one report.
Show text description

Subagents help when work splits into independent parts that each need a lot of reading. Each returns a short summary, so the lead's context stays small.

  • User
  • Lead agent: Breaks the question into independent subtasks, writes a clear brief for each, and decides when the work is done.
  • Subagent A: Runs its own tool loop in a fresh context window, then returns a condensed summary, not its whole transcript.
  • Subagent B: Works in parallel with the others, so total time is close to the slowest subagent.
  • Subagent C: Isolated context means one subagent's dead ends never clutter another's.
  • Report: The lead agent combines the summaries, resolves conflicts, and cites sources.

Fan out. The lead agent writes a brief for each subagent and starts them at the same time.

Fan in. Each subagent returns a condensed summary, and the lead combines them into the report.

The most reliable multi-agent design is the orchestrator-workers pattern applied to agents:

  1. The lead agent (orchestrator) reads the request, plans independent subtasks, and writes a brief for each.
  2. Subagents run their own tool loops in parallel, each with a fresh context window, and return a condensed summary, not their full transcript.
  3. The lead combines the summaries into the final answer.

The key benefit is context isolation: each subagent can read fifty pages and return one paragraph, so the lead reasons over distilled findings instead of raw material.

Orchestrator-workers

Orchestrator-workers patternAn orchestrator LLM plans subtasks and dispatches them to worker LLM calls; a synthesizer LLM combines their results into the output.InputOrchestratorplans subtasksWorkersubtask 1Workersubtask 2Workersubtask nSynthesizercombines resultsOutput

Tap a step for details.

Orchestrator-workers. An orchestrator LLM breaks the task into subtasks at runtime, delegates them to worker LLM calls, and a synthesizer combines the results.
Show text description

An orchestrator LLM plans subtasks and dispatches them to worker LLM calls; a synthesizer LLM combines their results into the output.

Use it when: You cannot predict the subtasks in advance, for example how many files a code change touches or which sources a question needs.

Example: Plan which files to edit for a feature, have a worker propose each file's change, then merge them into one patch.

When multiple agents help, and when they hurt#

Multi-agent helps when…Stay with one agent when…
The task splits into independent partsSteps depend tightly on each other (most coding tasks)
Each part needs lots of reading (search results, files, pages)The whole task fits comfortably in one context
Parallelism matters for latencyCost matters more than speed
Breadth beats depth: surveys, comparisons, auditsEvery decision needs the full picture

Multi-agent runs typically use several times the tokens of a single-agent run on the same task, because every subagent has its own system prompt, tool definitions, and reading. That cost is worth it only when the task is valuable and genuinely parallel.

Building it#

The example: compare three products for a buyer, using a document store of product pages and reviews. The lead plans, three subagents research in parallel, and the lead writes a cited comparison.

The shared research tools#

lib/corpus.ts
export interface Doc {
  id: string;
  product: string;
  kind: "pricing" | "features" | "review";
  text: string;
}
 
export const DOCS: Doc[] = [
  { id: "d1", product: "Acme CRM", kind: "pricing", text: "Acme CRM: $25 per user per month, annual billing only. Free tier up to 2 users." },
  { id: "d2", product: "Acme CRM", kind: "features", text: "Acme CRM includes email sync, pipelines, and a REST API. No built-in phone dialer." },
  { id: "d3", product: "Acme CRM", kind: "review", text: "Reviewers praise setup speed; several mention slow support responses (2 to 3 days)." },
  { id: "d4", product: "Bolt CRM", kind: "pricing", text: "Bolt CRM: $39 per user per month, monthly or annual. No free tier; 14-day trial." },
  { id: "d5", product: "Bolt CRM", kind: "features", text: "Bolt CRM includes a phone dialer, call recording, pipelines, and Zapier integration." },
  { id: "d6", product: "Bolt CRM", kind: "review", text: "Reviewers like the dialer; some report the mobile app crashes on Android." },
  { id: "d7", product: "Cobalt CRM", kind: "pricing", text: "Cobalt CRM: $15 per user per month; advanced reports cost $10 more per user." },
  { id: "d8", product: "Cobalt CRM", kind: "features", text: "Cobalt CRM has pipelines, basic reports, and CSV import. API access only on the Pro add-on." },
  { id: "d9", product: "Cobalt CRM", kind: "review", text: "Reviewers call it great value for small teams but limited once you pass 20 users." },
];
 
export function searchDocs(query: string, product?: string) {
  const words = query.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
  return DOCS.filter((d) => (!product || d.product === product) && words.some((w) => d.text.toLowerCase().includes(w)));
}

A subagent#

Each subagent is an ordinary agent with a narrow brief, its own tools, and a hard limit. It returns a short summary plus the token usage of its whole run, so the lead can track total cost:

lib/subagent.ts
import Anthropic from "@anthropic-ai/sdk";
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
import { z } from "zod";
import { searchDocs } from "./corpus.ts";
 
const client = new Anthropic();
 
export interface SubagentResult {
  task: string;
  summary: string;
  tokens: number;
}
 
export async function runSubagent(task: string, brief: string): Promise<SubagentResult> {
  const search = betaZodTool({
    name: "search_docs",
    description: "Search product pages and reviews. Returns matching documents with IDs to cite.",
    inputSchema: z.object({ query: z.string(), product: z.string().optional().describe("Exact product name to filter by") }),
    run: async ({ query, product }) => JSON.stringify(searchDocs(query, product)),
  });
 
  const runner = client.beta.messages.toolRunner({
    model: "claude-sonnet-5-5", // subagents do focused reading: a faster model is usually enough
    max_tokens: 8192,
    max_iterations: 6,
    system:
      "You are a research subagent. Complete only the task in your brief. Search thoroughly, then reply with a summary of at most 150 words that cites document IDs like [d4]. State clearly what you could not find.",
    tools: [search],
    messages: [{ role: "user", content: brief }],
  });
 
  let tokens = 0;
  let summary = "";
  for await (const message of runner) {
    tokens += message.usage.input_tokens + message.usage.output_tokens + (message.usage.cache_read_input_tokens ?? 0);
    summary = message.content.flatMap((b) => (b.type === "text" ? [b.text] : [])).join("");
  }
  return { task, summary, tokens };
}

The orchestrator#

lib/orchestrator.ts
import Anthropic from "@anthropic-ai/sdk";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
import { z } from "zod";
import { runSubagent, type SubagentResult } from "./subagent.ts";
 
const client = new Anthropic();
 
const Plan = z.object({
  subtasks: z
    .array(
      z.object({
        name: z.string().describe("Short label, such as 'Bolt CRM'"),
        brief: z.string().describe("Objective, what to look for, expected output, and what is out of scope"),
      }),
    )
    .describe("2 to 5 independent subtasks"),
});
 
// Run async work with at most `limit` tasks in flight.
async function mapLimit<T, R>(items: T[], limit: number, fn: (item: T) => Promise<R>): Promise<R[]> {
  const results: R[] = new Array(items.length);
  let next = 0;
  const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
    while (next < items.length) {
      const i = next++;
      results[i] = await fn(items[i]!);
    }
  });
  await Promise.all(workers);
  return results;
}
 
export async function research(question: string) {
  // 1. Plan: the lead decides how to split the work.
  const planned = await client.messages.parse({
    model: "claude-opus-5-5",
    max_tokens: 4096,
    output_config: { effort: "medium", format: zodOutputFormat(Plan) },
    system: "You are a lead researcher. Split the question into independent subtasks for parallel subagents. Each brief must stand alone: the subagent sees nothing else.",
    messages: [{ role: "user", content: question }],
  });
  const subtasks = (planned.parsed_output?.subtasks ?? []).slice(0, 5);
  if (subtasks.length === 0) throw new Error("The lead agent produced no plan");
 
  // 2. Delegate: subagents run in parallel, a few at a time. One failure does not sink the run.
  const results = await mapLimit(subtasks, 3, async (t): Promise<SubagentResult> => {
    try {
      return await runSubagent(t.name, t.brief);
    } catch (error) {
      return { task: t.name, summary: `FAILED: ${error instanceof Error ? error.message : String(error)}`, tokens: 0 };
    }
  });
 
  // 3. Synthesize from the condensed findings only.
  const final = await client.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 8192,
    system: "You are a lead researcher. Write the final answer from your subagents' findings. Keep their citations. Point out gaps and contradictions honestly.",
    messages: [
      {
        role: "user",
        content: `Question: ${question}\n\n${results.map((r) => `<findings task="${r.task}">\n${r.summary}\n</findings>`).join("\n")}`,
      },
    ],
  });
 
  const answer = final.content.flatMap((b) => (b.type === "text" ? [b.text] : [])).join("");
  const subagentTokens = results.reduce((sum, r) => sum + r.tokens, 0);
  return { answer, subtasks: subtasks.map((t) => t.name), subagentTokens };
}
compare.ts
import { research } from "./lib/orchestrator.ts";
 
const result = await research(
  "We are a 12-person sales team that makes many phone calls. Compare Acme CRM, Bolt CRM, and Cobalt CRM on price, phone features, and reliability, and recommend one.",
);
console.log("subtasks:", result.subtasks.join(", "));
console.log("subagent tokens:", result.subagentTokens);
console.log(result.answer);

Writing briefs that work#

Subagents see only their brief. Most multi-agent failures are vague briefs: two subagents research the same thing, or one wanders off topic. A good brief has four parts:

PartExample
Objective"Find Bolt CRM's pricing and phone features."
What to look for"Per-user price, billing options, trial, dialer, call recording."
Output"At most 150 words, citing document IDs."
Boundaries"Do not research other products. Do not estimate missing prices."

Telling the planner to write briefs like this, as the system prompt above does, is often the single biggest quality improvement.

Controlling cost and failure#

  • Cap everything. Number of subtasks (5 here), iterations per subagent (6), concurrency (3), and output length (150 words).
  • Use cheaper models for subagents when their work is focused reading and summarizing; keep the strongest model for planning and synthesis.
  • Track tokens per subagent. It shows which briefs cause wasteful searching.
  • Isolate failures. A failed subagent becomes a "FAILED" finding; the lead can report the gap instead of the whole run failing.
  • Return summaries, not transcripts. Passing full subagent histories to the lead defeats the purpose of context isolation.

Common mistakes#

  • Multi-agent by default. Try one agent first; split only when context or latency limits are real.
  • Dependent subtasks in parallel. If subtask B needs A's answer, they are not parallel. Run them in sequence or merge them.
  • Agents chatting with each other. Free-form agent-to-agent conversation is expensive and hard to debug. Prefer a lead that delegates and collects.
  • No evaluation of the final answer. Measure the combined output on real questions; more agents does not mean better answers.

Try it

Measure whether multi-agent is worth it

  1. Run compare.ts and record total tokens (planning, subagents, synthesis) and wall-clock time.
  2. Solve the same question with a single agent that has the same search_docs tool and a budget of 12 iterations.
  3. Compare cost, time, and answer quality. Then change the question to one about a single product and compare again.

Check your understanding

What is the main advantage of subagents with separate context windows?
Which task is the worst fit for a multi-agent system?
What should a subagent return to the lead?

Key takeaways

  • Multi-agent systems trade more tokens for parallelism and context isolation; use them for broad, independent, reading-heavy work.
  • The orchestrator plans with structured output, subagents run in parallel with limits, and the lead synthesizes from summaries.
  • Briefs make or break the system: objective, what to look for, output format, and boundaries.
  • Cap subtasks, iterations, and concurrency; track tokens per subagent; isolate failures.

Next steps#

Finished this article? Track it to see your progress on the roadmap.