When to reach for it
- Subtasks only become apparent at runtime.
- Workers handle highly specialized functions.
- Aggregation must remain centrally controlled.
/pattern/orchestrator-workers/
An orchestrator dynamically decomposes a task and assigns subtasks to specialized workers, managing the aggregation centrally.
When to reach for it
When it backfires
The tradeoff
Highly flexible delegation is gained against significant coordination and integration overhead.
An orchestrator decomposes the job, dispatches to workers in parallel, then folds the results.
from langgraph.graph import StateGraph, END
from langgraph.constants import Send
class State(TypedDict):
task: str
subtasks: list[str]
results: dict[str, str]
answer: str | None
def decompose(state):
return {"subtasks": planner_llm(state["task"])}
def worker(state):
# Each invocation receives one subtask
return {"results": {state["subtask"]: research_llm(state["subtask"])}}
def fold(state):
return {"answer": synthesizer_llm(state["results"])}
g = StateGraph(State)
g.add_node("decompose", decompose)
g.add_node("worker", worker)
g.add_node("fold", fold)
# Fan-out: one Send per subtask
g.add_conditional_edges("decompose", lambda s: [Send("worker", {"subtask": t}) for t in s["subtasks"]])
g.add_edge("worker", "fold")
g.add_edge("fold", END)
g.set_entry_point("decompose")
orchestrator = g.compile()The orchestrator splits the task into 20 micro-tasks. The overhead of dispatch and fold dominates actual work.
Fix · Set a minimum chunk size; merge subtasks that share a context window or a tool call.
Two workers return contradictory data. The fold step averages or randomly selects without surfacing the conflict.
Fix · Require the fold step to emit confidence scores per source, or add an explicit consensus-check node before final answer.
Search patterns, frameworks, and pages.