Orchestrator-Workers
1. What is Orchestrator-Workers?
In the orchestrator-workers pattern, a central LLM acts as a project manager: it analyses the incoming task, decides what subtasks are needed, delegates them to specialised worker LLMs, and then synthesises all results into a final output.
┌─────────────────────────────────────────────┐
│ ORCHESTRATOR │
│ Analyses task → creates dynamic plan │
└────────────┬──────────────────┬─────────────┘
│ delegates │
┌────────────▼──┐ ┌─────▼────────────┐
│ Worker A │ │ Worker B │
│ (specialised) │ │ (specialised) │
└────────────┬──┘ └─────┬────────────┘
│ results │
┌────────────▼──────────────────▼─────────────┐
│ SYNTHESISER │
│ Combines worker outputs │
└─────────────────────────────────────────────┘
│
Output
Key difference from Parallelization: subtasks are not pre-defined. The orchestrator determines them dynamically based on the specific input.
2. When to Use Orchestrator-Workers
Good fit when: - You cannot predict the required subtasks upfront (they depend on the input) - Different subtasks need different specialised prompts/tools - The task is too complex for a single LLM call - You want a human-readable plan before execution
Examples: - Multi-file code changes: The orchestrator reads the codebase, decides which files need changing, delegates each file edit to a worker - Research tasks: Orchestrator decides which sources to search, delegates queries to search workers, synthesises findings - Document generation: Orchestrator creates an outline, assigns each section to a specialised writer worker - Data pipeline building: Orchestrator analyses requirements, assigns schema design / ETL / testing to different workers
Comparison with Parallelization:
| Parallelization | Orchestrator-Workers | |
|---|---|---|
| Subtask definition | Fixed upfront | Dynamically determined |
| Flexibility | Low | High |
| Predictability | High | Lower |
| Best for | Known, independent tasks | Open-ended, complex tasks |
3. Implementation Pattern
import json
from openai import OpenAI
client = OpenAI()
def llm_call(prompt: str, system: str = "", model: str = "gpt-4o") -> str:
messages = []
if system:
messages.append({"role": "system", "content": system})
messages.append({"role": "user", "content": prompt})
response = client.chat.completions.create(model=model, messages=messages)
return response.choices[0].message.content
def orchestrate(task: str) -> str:
"""
Orchestrator-Workers pattern:
1. Orchestrator creates a dynamic plan
2. Workers execute each subtask
3. Synthesiser combines all results
"""
# ── Step 1: Orchestrator plans ─────────────────────────────────────────
plan_json = llm_call(
f"Break down this task into subtasks. Return JSON array of objects with "
f"'subtask_id' (int), 'description' (str), 'worker_type' (str).\n\nTask: {task}",
system="You are a project planning expert. Output only valid JSON."
)
subtasks = json.loads(plan_json)
# ── Step 2: Workers execute ────────────────────────────────────────────
worker_results = []
for subtask in subtasks:
system_prompt = WORKER_SYSTEMS.get(
subtask["worker_type"],
"You are a helpful expert. Complete the task precisely."
)
result = llm_call(subtask["description"], system=system_prompt)
worker_results.append({
"subtask_id": subtask["subtask_id"],
"description": subtask["description"],
"result": result
})
# ── Step 3: Synthesiser combines ───────────────────────────────────────
results_text = "\n\n".join(
f"[Subtask {r['subtask_id']}: {r['description']}]\n{r['result']}"
for r in worker_results
)
final = llm_call(
f"Synthesise these subtask results into a coherent final response for the original task.\n"
f"Original task: {task}\n\nSubtask Results:\n{results_text}",
system="You are a skilled synthesiser. Combine results into a unified, coherent response."
)
return final
WORKER_SYSTEMS = {
"researcher": "You are a research expert. Provide factual, well-sourced information.",
"writer": "You are a skilled technical writer. Write clearly and concisely.",
"analyst": "You are a data analyst. Provide quantitative insights and reasoning.",
"coder": "You are a senior software engineer. Write clean, production-quality code.",
}
# Usage
result = orchestrate(
"Create a technical blog post about RAG pipelines including: "
"an overview, code examples, and a comparison of vector databases."
)4. Managing Junior Engineers — An Analogy
The orchestrator-workers pattern mirrors how a senior engineer manages a team:
| Engineering Team | Orchestrator-Workers |
|---|---|
| Senior engineer analyses requirements | Orchestrator analyses the task |
| Creates a sprint plan with clear tickets | Generates a structured plan with subtasks |
| Assigns tickets to engineers by speciality | Delegates subtasks to specialist workers |
| Reviews and integrates PRs | Synthesiser combines worker outputs |
Key insight: The most successful orchestrator builders have experience managing junior engineers. Give workers clear goals and concrete instructions — not vague open-ended directions.
5. Best Practices
- Output structured plans: Have the orchestrator produce JSON/structured plans that are easy to parse and log.
- Validate the plan before executing: Check that the plan is sensible before spinning up workers (a gate).
- Use worker specialisation: Different system prompts for different worker types dramatically improve quality.
- Parallelise workers when possible: If worker subtasks are independent, run them concurrently (combine with Parallelization pattern).
- Cap the number of subtasks: Add a
max_subtasksguard to prevent runaway planning. - Log each worker’s input/output: Debugging is much harder when you can’t see what each worker received and returned.
- Consider deterministic plans: Pre-define plans for common task types and use the orchestrator only to select between them (more reliable, easier to test).