Overview
The orchestrator-worker pattern uses a central LLM — the orchestrator — to analyze a task, break it into subtasks dynamically, delegate each subtask to a worker LLM, and then synthesize the workers’ outputs into a final result. Unlike parallelization, where the subtasks are defined at design time, the orchestrator determines the decomposition at runtime based on the specific input. This makes the pattern well-suited for complex, open-ended tasks where the number and nature of subtasks cannot be predicted in advance.
How It Works
- The orchestrator receives the task. The central LLM is given the high-level objective along with a system prompt that instructs it to plan a decomposition.
- The orchestrator produces a plan. It analyzes the task and outputs a structured list of subtasks — each with a clear description, the information it needs, and any constraints. The plan is typically returned as structured data (JSON or a similar format).
- Workers execute the subtasks. Each subtask is dispatched to a worker LLM call. Workers can run in parallel if the subtasks are independent, or sequentially if there are dependencies. Workers can use different prompts, tools, or even different models depending on the subtask requirements.
- Results are returned to the orchestrator. The worker outputs are collected and fed back to the orchestrator.
- The orchestrator synthesizes the final output. It reviews all worker results, resolves conflicts or gaps, and produces the unified deliverable.
When to Use
- The task is complex and open-ended, and you cannot predict at design time how many subtasks it will require or what they will be.
- Subtasks may require different strategies, tools, or expertise.
- You need a system that can adapt its approach based on the specific input (e.g., a coding agent that decides which files to edit based on the change request).
- The final output requires coherent synthesis across multiple independently-produced parts.
When Not to Use
- The subtask decomposition is predictable and fixed — use Parallelization or Prompt Chaining instead, as they are simpler and cheaper.
- The task is small enough that a single LLM call handles it well.
- You need deterministic, repeatable behavior — the orchestrator may decompose the same task differently across runs.
- Latency and cost budgets are tight, since the pattern requires at minimum two LLM calls (planning + synthesis) on top of the worker calls.
Example
# Orchestrator-Worker: A coding agent that edits multiple files based on a feature request.
import json
def orchestrate(feature_request: str, codebase_summary: str) -> str:
"""Orchestrator: Analyze the request and produce a plan."""
plan_response = llm.call(
system=(
"You are a senior software architect. Given a feature request and codebase summary, "
"produce a JSON list of subtasks. Each subtask has: file_path, action (create|modify), "
"and description of the change."
),
prompt=f"Feature request: {feature_request}\n\nCodebase:\n{codebase_summary}"
)
return json.loads(plan_response.text)
def worker_edit(subtask: dict, current_file_content: str) -> str:
"""Worker: Execute a single file edit subtask."""
response = llm.call(
system="You are an expert programmer. Apply the requested change to the file.",
prompt=(
f"File: {subtask['file_path']}\n"
f"Action: {subtask['action']}\n"
f"Change: {subtask['description']}\n\n"
f"Current content:\n{current_file_content}"
)
)
return response.text
def synthesize(feature_request: str, edits: list[dict]) -> str:
"""Orchestrator: Review all edits and produce a summary."""
edits_text = "\n\n".join(
f"--- {e['file_path']} ---\n{e['new_content']}" for e in edits
)
response = llm.call(
system="Review the following code edits for consistency and correctness. Provide a summary.",
prompt=f"Feature: {feature_request}\n\nEdits:\n{edits_text}"
)
return response.text
# Run the orchestrator-worker flow
plan = orchestrate("Add user avatar upload to the profile page", codebase_summary)
edits = []
for subtask in plan:
content = read_file(subtask["file_path"])
new_content = worker_edit(subtask, content)
edits.append({"file_path": subtask["file_path"], "new_content": new_content})
summary = synthesize("Add user avatar upload to the profile page", edits)
Related Patterns
- Parallelization — A simpler alternative when the subtask decomposition is known at design time.
- Prompt Chaining — Preferable when the sequence of steps is fixed and predictable.
- Evaluator-Optimizer — Can be layered on top: after the orchestrator synthesizes results, an evaluator checks quality and triggers another iteration if needed.