Orchestrator-Worker Patterns for Coordinating Specialist Agents
Orchestrator-worker is the multi-agent topology where a central controller owns the user goal and dispatches bounded work to specialist workers. Workers run their own loops with narrower tools; they return structured results; the orchestrator merges, retries, re-routes, or escalates.
This page is a practical recipe for that pattern inside one product runtime (in-process or service mesh with a single control plane).
Summary
Implement an orchestrator that classifies the next subgoal, builds a handoff packet, runs a worker under a local budget, validates the return contract, then merges into shared task state until a global stop condition fires.
Recipe
- Define the role graph: one orchestrator, N workers, optional human node.
- Document tools per worker (least privilege) and tools the orchestrator may use (usually few or none beyond routing helpers).
- Choose a routing policy: rules, classifier, or LLM tool-call named after agents.
- Standardize packet and return schemas for every worker.
- Implement dispatch + collect with per-worker
max_turnsand a global turn/cost/time cap. - Define failure policy: retry same worker, different worker, replan, or
needs_human. - Add trace fields:
run_id,agent_id, handoff id, status, budgets remaining. - Eval routes and return-schema validity separately from final answer quality.
Working Example
from dataclasses import dataclass, field
from typing import Any, Callable
@dataclass
class Packet:
subgoal: str
constraints: list[str]
inputs: dict[str, Any]
success_fields: list[str]
max_turns: int = 6
@dataclass
class WorkerResult:
status: str # ok | failed | needs_human
summary: str
artifacts: dict[str, Any] = field(default_factory=dict)
errors: list[dict] = field(default_factory=list)
Worker = Callable[[Packet], WorkerResult]
WORKERS: dict[str, Worker] = {} # register specialists at startup
def route(goal: str) -> str:
g = goal.lower()
if "refund" in g or "billing" in g:
return "billing"
if "stack trace" in g or "exception" in g:
return "coder"
return "researcher"
def dispatch(goal: str, task: dict, workers: dict[str, Worker], global_left: int) -> WorkerResult:
name = route(goal)
worker = workers[name]
packet = Packet(
subgoal=goal,
constraints=task.get("constraints", []),
inputs={"artifacts": task.get("artifacts", {})},
success_fields=["summary"],
max_turns=min(6, global_left),
)
result = worker(packet)
task.setdefault("history", []).append({"worker": name, "status": result.status})
if result.status == "ok":
task.setdefault("artifacts", {}).update(result.artifacts)
return result
def run_orchestrator(user_goal: str, workers: dict[str, Worker], max_global_turns: int = 16):
task = {"goal": user_goal, "artifacts": {}, "history": [], "constraints": []}
left = max_global_turns
# One-shot specialist for the basics; production often loops on subgoals
result = dispatch(user_goal, task, workers, left)
if result.status == "ok":
return {"status": "ok", "answer": result.summary, "task": task}
if result.status == "needs_human":
return {"status": "needs_human", "detail": result, "task": task}
return {"status": "failed", "detail": result, "task": task}Wire real workers by implementing Worker callables that run their own model/tool loops and always emit WorkerResult.
Deep Dive
Topology variants
| Variant | Flow | Best fit |
|---|---|---|
| One-shot dispatch | Orchestrator picks one worker, returns to user | Simple routing products |
| Sequential pipeline | Fixed worker order (research → draft → review) | Stable business processes |
| Plan then assign | Orchestrator (or planner) emits steps with owners | Multi-step jobs |
| Fan-out / fan-in | Parallel workers, orchestrator merges | Independent research slices |
| Hierarchical | Manager workers own sub-orchestrators | Large multi-skill jobs |
Start with one-shot or short sequential pipelines. Add fan-out only when merge rules are explicit.
Routing policy choices
- Deterministic rules - keywords, ticket type, URL path. Cheap and testable.
- Embedding / classifier - maps utterance to worker id with confidence threshold.
- LLM router - model emits
transfer_to_billingstyle tool calls. - User pin - UI forces a specialist; orchestrator still enforces tools and budgets.
Prefer rules for high-volume product paths. Use LLM routers for open-ended intents, with a safe default worker.
What the orchestrator should not do
- Run every hard subgoal itself while workers idle.
- Grant temporary superuser tools "because the user asked."
- Accept free-form worker dumps without schema validation.
- Retry forever without a replan counter or human path.
Budgets and fairness
- Local budget: worker max turns, tool-call cap, wall time.
- Global budget: sum of worker spend, total wall time, total dollars.
- Fairness: prevent one recursive worker from consuming the global budget alone by allocating slices per dispatch.
Framework mappings (verify at build)
- LangGraph: supervisor node + worker nodes, conditional edges, shared state.
- CrewAI: hierarchical process with manager agent.
- OpenAI Agents SDK: handoffs from a triage agent.
- Microsoft Agent Framework: workflow graphs with coordinator roles.
Gotchas
- God orchestrator - reimplements all skills; workers become dead code.
- Shared superuser tool belt - multi-agent costume with monolith risk.
- Missing return validation - orchestrator trusts prose and invents success.
- Unbounded retries - same worker fails 20 times with the same packet.
- Silent context drop - worker never receives the ticket id the orchestrator assumed was obvious.
- Fan-out without merge rules - conflicting artifacts overwrite each other.
- No global stop - each worker is bounded but the orchestrator loops forever across workers.
Alternatives
| Strategy | When it wins | When it loses |
|---|---|---|
| Orchestrator-worker (this recipe) | Clear ownership, product UX | Orchestrator becomes bottleneck |
| Single agent + tools | Narrow tasks | Prompt/tool bloat |
| Peer-to-peer | Research swarms, open collab | Harder budgets and invariants |
| Pure pipeline (no LLM orchestrator) | Fixed stages | Weak on open-ended intents |
| A2A cross-runtime | Agents in different orgs/clouds | Heavier protocol and ops |
FAQs
What is the orchestrator's primary job?
Own the user goal, choose the next worker, enforce global budgets and policy, and merge or escalate structured results.
Should the orchestrator call tools?
Prefer few or none. If it needs tools, keep them read-only routing aids. Side-effect tools belong on least-privilege workers with explicit gates.
How do I pick between sequential pipeline and dynamic routing?
Use a pipeline when stages are stable and always required. Use dynamic routing when only one of several specialists should run per request.
How should fan-out merges work?
Define a merge function: concatenate sources with dedupe keys, vote on classifications, or pick highest-confidence artifact. Never "last writer wins" by accident.
What if two workers need the same artifact?
Store it in shared task state under a stable key and pass references in packets. Do not attach full copies to every handoff if size will explode.
How many workers should I start with?
Two is enough to learn the pattern (orchestrator + one specialist). Add a third only when a new tool/policy boundary is real.
How do humans fit the pattern?
Model human approval as a blocking worker with a return contract (approved, edited, rejected). The orchestrator continues only after a valid return.
Can workers call the orchestrator back?
Prefer workers only returning results. Callbacks create hidden cycles. If you need peer calls, read the peer-to-peer page and add cycle guards.
What metrics prove the pattern works?
Route accuracy, return-schema validity rate, handoff depth, budget-hit rate, time-to-escalation, and final task success - not only user thumbs-up.
Is a non-LLM router still "multi-agent"?
Yes. Multi-agent is about multiple agent loops and control transfer. The router can be deterministic code.
Related
- Multi-Agent Architecture Basics - two-agent skeleton
- Why Split One Agent Into Many Specialized Agents - when decomposition pays
- Context Handoff: Passing State Between Agents Cleanly - packet fields
- Peer-to-Peer Agent Collaboration Without a Central Orchestrator - no central controller
- Architecture Guide: Designing a Multi-Agent System's Communication Contract - formal messages
- Multi-Agent Architecture Best Practices - practices list
- Multi-Agent Handoff: Specialist Agents Passing Work to Each Other - architecture vocabulary
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.