Orchestrator-Worker Pattern

In the orchestrator-worker pattern, one lead agent (the orchestrator) breaks a task into subtasks, hands each to a worker agent, and combines what the workers return. Anthropic describes this as a workflow in which a central model dynamically decomposes the task, delegates, and synthesizes results, suited to cases where the needed subtasks cannot be listed in advance. Its multi-agent research system uses it: a lead agent plans, spawns subagents to explore aspects in parallel, and synthesizes their findings.

How it works

  1. Plan. The orchestrator reads the request and decides on subtasks. For each it writes a brief with the objective, the output format, which tools or sources to use, and the boundaries of the slice.
  2. Delegate. Each worker starts with a fresh context containing the brief, not the orchestrator’s whole history, and runs its own loop of reasoning and tool calls.
  3. Return. A worker returns a short result, ideally much smaller than the work that produced it.
  4. Synthesize. The orchestrator checks coverage, merges, resolves conflicts, and writes the answer. It may delegate again.

The differences from neighbors: a fixed pipeline has predetermined stages and no model-chosen decomposition; parallelization runs the same predefined split concurrently; a single agent loop keeps everything in one context; peer-to-peer designs let agents talk directly rather than through a hub.

A traced example

The program below is a deterministic stand-in (hypothetical, no model involved) in which the orchestrator splits six documents among three workers, each reporting which documents mention “refund”.

DOCS = {
    "doc0": "Refunds are available within 30 days.",
    "doc1": "A refund needs an order identifier.",
    "doc2": "The refund is sent to the original card.",
    "doc3": "Shipping fees depend on the destination.",
    "doc4": "Ask for a refund through the returns form.",
    "doc5": "Account settings control notifications.",
}
def worker(slice_, fail=False):
    if fail: raise RuntimeError("worker crashed")
    return {n: ("refund" in t.lower()) for n, t in slice_.items()}

def orchestrate(k, fail_worker=None):
    names = list(DOCS); slices = [{n: DOCS[n] for n in names[i::k]} for i in range(k)]
    results, errors = {}, []
    for i, s in enumerate(slices):
        try: results.update(worker(s, fail=(i == fail_worker)))
        except RuntimeError as e: errors.append((i, str(e)))
    missing = sorted(set(DOCS) - set(results))
    return sorted(n for n, v in results.items() if v), missing, errors
print("healthy",orchestrate(3))
print("worker 1 failed",orchestrate(3,fail_worker=1))

healthy (['doc0', 'doc1', 'doc2', 'doc4'], [], [])
worker 1 failed (['doc0', 'doc2'], ['doc1', 'doc4'], [(1, 'worker crashed')])

The two printed calls above produce the following results. With all workers healthy the result is ['doc0','doc1','doc2','doc4'], nothing missing, three worker calls plus two orchestrator steps (plan, synthesis). When worker 1 crashes, the answer would silently become ['doc0','doc2'] if the orchestrator only merged what arrived. Because it compares results with the list of assigned slices, it reports missing = ['doc1','doc4'] and the error, and can retry or say the answer is incomplete. Merging without a coverage check is how a lost slice turns into a confident, wrong answer.

Costs

Each model call reads its prompt, and a loop’s prompt grows with each step. A single agent therefore pays more per step as its context grows, while a worker starts small. Take hypothetical sizes: a 3000-token base prompt, 1500 tokens added per step, a 2000-token shared brief per worker, and 300-token summaries. A 24-step task costs 486,000 prompt tokens in one loop. For the two hub calls, assume the plan prompt contains 3000 base tokens plus 1000 task-description tokens per worker, and the synthesis prompt contains the same plus each 300-token summary. A worker receives its 3000-token base plus the 2000-token brief and then adds 1500 tokens per step. With equal splits, the worker total is \(k[m(3000+2000)+1500m(m-1)/2]\), where \(m=24/k\); the hub adds \(6000+2300k\). Split across workers it costs:

for k in (1,2,4,6):
    steps=24//k
    workers=k*(steps*(3000+2000)+1500*steps*(steps-1)//2)
    # plan: 3000 + 1000*k; synthesis: 3000 + 1000*k + 300*k
    hub=(3000+1000*k)+(3000+1300*k)
    print(k,"workers",workers,"hub",hub,"total",workers+hub)
WorkersPrompt tokens
1542,300
2328,600
4225,200
6193,800

One worker is more expensive than the single loop, because it adds the orchestrator’s calls and the brief without shortening any context. Savings come from short worker contexts. This model ignores prompt caching, parallel latency gains, and output tokens, and it is arithmetic on assumed sizes, not a measurement. Anthropic reports for its research system that agents use roughly 4 times the tokens of chat and multi-agent systems roughly 15 times, so the pattern pays off for tasks whose value justifies that.

Failure modes

  • Poor briefs. Without clear objectives and boundaries, workers duplicate work, leave gaps, or misread the task; Anthropic found this in early versions of its system.
  • Over-delegation. The orchestrator spawns many workers for a simple task; limit worker counts by task difficulty.
  • Lost or lossy results. A crashed worker, or a summary that drops the detail needed to merge, goes unnoticed without a coverage check.
  • Hub as target. The orchestrator holds the plan and all results, so it is the most valuable target, and worker output is input to its own reasoning. A worker that read hostile text may return instruction-shaped output, so treat worker results as untrusted data.
  • Authority creep. Workers should hold only the permissions their slice needs, never more than the orchestrator holds; otherwise the orchestrator becomes a confused deputy.
  • Hard-to-debug runs. Record each delegation, the brief, the identity it ran under and the result, so a wrong answer can be traced to its slice.

When it fits

Use it when subtasks are separable, produce results much smaller than the work behind them, and can be checked by code or a different model. If the subtasks depend heavily on each other’s intermediate state, a single loop or a fixed pipeline is simpler and cheaper.

References


Discover more from Insightful Data Lab

Subscribe to get the latest posts sent to your email.

Similar Posts

Questions, corrections, or additional insights?

This site uses Akismet to reduce spam. Learn how your comment data is processed.