🎼 Orchestrate: coordinating a team of agents

Some goals are too big or too varied for one agent. Orchestration puts a coordinator in charge: it plans the work, hands each part to a specialist, keeps track of progress, checks the results and puts everything together.

What kind of thing is this?

Orchestration is a general design pattern, which Anthropic calls orchestrator-workers in Building effective agents. You can build it yourself with the Claude API, as on this page. Some Claude products also offer built-in multi-agent features: subagents in Claude Code and the Claude Agent SDK, and multi-agent sessions in Claude Managed Agents. Anthropic's own Research feature uses an orchestrator that directs several sub-agents. See How we built our multi-agent research system.

What is it?

Agent orchestration means coordinating several tools, skills, agents and workflows to complete one larger goal. A central orchestrator, usually Claude with planning instructions, decides what needs doing, who does each part, and in what order, then checks and combines the results.

Orchestrator (coordinator)
The "project manager" agent that plans, delegates, monitors and combines.
Worker / specialised agent
An agent with one focused role, such as researcher, writer or reviewer, often with its own tools and skills.
Multi-agent system
A system where several agents work together on one goal.
Task dependency
When a task needs another task's result first.
Shared context
Background information that every agent receives, such as product facts or brand voice.
Validation
Checking a result is correct and complete before using it.
Token
A small chunk of text (roughly ΒΎ of a word). AI usage is measured and paid for in tokens.

Single agent and multi-agent systems compared

Single agentMulti-agent (orchestrated)
StructureOne agent with all the tools, working in one loopAn orchestrator plus specialised workers
StrengthsSimple, cheaper, easier to debugHandles big, varied goals, specialists can work in parallel, and each has a smaller, focused context
WeaknessesStruggles when the job is huge or needs very different expertiseUses many more tokens, has more moving parts and is harder to debug
AnalogyA skilled freelancerAn agency with a project manager

Why is it important?

Real-world analogy

The project manager at a marketing agency

A client wants a product launch campaign. The project manager doesn't do everything personally. They break the job into tasks, assign research to the analyst, strategy to the strategist and copy to the writer, and track progress on a board. When the writer's draft is off-brand, the manager sends it back. Finally they assemble the deliverable and get the client's sign-off before anything is published.

How does it work?

  1. Understand the goal: the orchestrator receives the user's goal and shared context.
  2. Plan: it breaks the goal into tasks, picks a specialist for each and notes the dependencies.
  3. Assign: tasks whose dependencies are done are sent to workers, in parallel when they're independent.
  4. Monitor: it tracks status (waiting, running, done or failed) and the budget.
  5. Validate: each result is checked. Weak results are retried with feedback.
  6. Combine: results are merged into the final response.
  7. Approve: anything risky, such as publishing, goes to a human (see Human in the Loop).

Workflow diagram

Simple overview

The orchestrator plans tasks and sends them to specialised agents. Results are validated, and anything that fails goes back for another attempt before the final response is assembled.

Detailed: the marketing workflow (with dependencies)

Here the tasks depend on each other, so they run in order. In other goals, such as researching three competitors, the orchestrator would run the independent tasks in parallel.

Try it: run the orchestrator

Interactive demoA simulation in your browser. No AI is called.
Budget: 0 / 12 calls
TaskAgentNeedsStatus

    Practical example: an AI marketing workflow

    Goal: "Create launch content for our new e-scooter, Glide 2."

    1. Orchestrator plans three tasks: research (no dependencies), strategy (needs research), drafts (needs strategy).
    2. Research agent summarises competitor notes: pricing, messages and gaps.
    3. Strategy agent turns the research into three content angles, for example "longest battery in its class".
    4. Copywriter agent drafts three social posts using the angles and the brand voice from shared context.
    5. Validator checks the facts against the product sheet and the tone against the brand voice. One post claims "fastest scooter ever", which is unsupported, so it goes back to the copywriter with that feedback.
    6. Human approval: the marketing lead reviews the final three posts and approves them. Nothing is published before this.

    Code example

    Runnable but simplified Python for learning, not production-ready. It needs pip install anthropic, the ANTHROPIC_API_KEY environment variable on your computer or server, and a notes.txt file with product facts, brand voice and competitor notes.

    
    # orchestrator.py: plan, delegate, validate, then ask a human. A simplified teaching version.
    import asyncio
    import json
    import pathlib
    import anthropic
    
    client = anthropic.AsyncAnthropic()
    MODEL = "claude-opus-5-5"
    MAX_CALLS = 15      # hard budget: the whole system stops if it would go over
    calls = 0
    
    async def ask(system: str, prompt: str, schema: dict | None = None):
        """One Claude call. If a schema is given, the reply is guaranteed JSON of that shape."""
        global calls
        calls += 1
        if calls > MAX_CALLS:
            raise RuntimeError("Budget exceeded. Stopping to protect costs.")
        extra = {"output_config": {"format": {"type": "json_schema", "schema": schema}}} if schema else {}
        response = await client.messages.create(
            model=MODEL, max_tokens=16000, system=system,
            messages=[{"role": "user", "content": prompt}], **extra,
        )
        text = "".join(b.text for b in response.content if b.type == "text")
        return json.loads(text) if schema else text
    
    # The specialised workers: each is Claude with a focused role (a system prompt).
    SPECIALISTS = {
        "researcher": "You are a market researcher. Use only the shared context. Be factual and brief.",
        "strategist": "You are a content strategist. Turn research into 3 clear content angles.",
        "writer": "You are a copywriter. Friendly, plain language. Max 60 words per post. No unsupported claims.",
    }
    
    PLAN_SCHEMA = {
        "type": "object",
        "properties": {"tasks": {"type": "array", "items": {
            "type": "object",
            "properties": {
                "id": {"type": "string"},
                "agent": {"type": "string", "enum": list(SPECIALISTS)},
                "instructions": {"type": "string"},
                "depends_on": {"type": "array", "items": {"type": "string"}},
            },
            "required": ["id", "agent", "instructions", "depends_on"],
            "additionalProperties": False,
        }}},
        "required": ["tasks"],
        "additionalProperties": False,
    }
    CHECK_SCHEMA = {
        "type": "object",
        "properties": {"ok": {"type": "boolean"}, "problems": {"type": "string"}},
        "required": ["ok", "problems"],
        "additionalProperties": False,
    }
    
    async def run_task(task: dict, results: dict, context: str) -> str:
        """Give one task to its specialist, then validate the result (max 2 tries)."""
        earlier = "\n\n".join(f"## Result of {d}\n{results[d]}" for d in task["depends_on"])
        prompt = f"{task['instructions']}\n\n## Shared context\n{context}\n\n{earlier}"
        for attempt in (1, 2):
            output = await ask(SPECIALISTS[task["agent"]], prompt)
            check = await ask(
                "You are a strict reviewer. Check the output against the task and the shared context.",
                f"Task: {task['instructions']}\n\nShared context:\n{context}\n\nOutput:\n{output}",
                CHECK_SCHEMA,
            )
            if check["ok"]:
                return output
            print(f"  [retry] {task['id']} failed validation: {check['problems']}")
            prompt += f"\n\nA reviewer found these problems. Fix them: {check['problems']}"
        raise RuntimeError(f"Task '{task['id']}' failed validation twice. A human needs to look.")
    
    async def orchestrate(goal: str, context: str) -> dict:
        plan = await ask(
            "You are an orchestrator. Split the goal into 2-4 tasks for these agents: "
            + ", ".join(SPECIALISTS) + ". In depends_on, list the ids of tasks that must finish first.",
            goal, PLAN_SCHEMA,
        )
        tasks = {t["id"]: t for t in plan["tasks"]}
        print("Plan:", [(t["id"], t["agent"], t["depends_on"]) for t in tasks.values()])
    
        results = {}
        while len(results) < len(tasks):
            # A task is ready when everything it depends on has a result.
            ready = [t for t in tasks.values()
                     if t["id"] not in results and all(d in results for d in t["depends_on"])]
            if not ready:
                raise RuntimeError("The plan has a loop or an unknown dependency.")
            outputs = await asyncio.gather(*(run_task(t, results, context) for t in ready))  # parallel
            for t, out in zip(ready, outputs):
                results[t["id"]] = out
                print(f"[done] {t['id']} ({t['agent']})")
        return results
    
    async def main():
        goal = "Create launch content for our new e-scooter 'Glide 2': research competitors, choose angles, draft 3 social posts."
        context = pathlib.Path("notes.txt").read_text()
        results = await orchestrate(goal, context)
        final = list(results.values())[-1]           # the last task to finish (the drafts)
        print("\n=== Final content ===\n" + final)
        print(f"\n(Claude calls used: {calls}/{MAX_CALLS})")
    
        # Human in the loop: nothing is published without approval.
        if input("\nApprove for publishing? Type 'yes' to approve: ").strip().lower() == "yes":
            print("Approved. Handed to the publishing team.")
        else:
            print("Not approved. Nothing was published.")
    
    if __name__ == "__main__":
        asyncio.run(main())
    

    Expected output (wording and plan will vary):

    
    Plan: [('research', 'researcher', []), ('strategy', 'strategist', ['research']), ('drafts', 'writer', ['strategy'])]
    [done] research (researcher)
    [done] strategy (strategist)
      [retry] drafts failed validation: Post 2 claims "fastest scooter ever", which the product sheet does not support.
    [done] drafts (writer)
    
    === Final content ===
    1. Go further on one charge: Glide 2's 60 km battery ...
    
    (Claude calls used: 9/15)
    
    Approve for publishing? Type 'yes' to approve: yes
    Approved. Handed to the publishing team.
    

    Step-by-step explanation

    1. ask() is the single entry point for every Claude call. It counts calls and stops at the budget, which protects you from runaway costs and loops.
    2. SPECIALISTS defines the workers. Here each "agent" is Claude with a different system prompt. Real systems would also give each one its own tools and skills.
    3. PLAN_SCHEMA forces the orchestrator's plan into valid JSON: a list of tasks, each with an agent (only from our list), instructions and depends_on.
    4. run_task() passes the shared context and only the results this task depends on. That is how agents communicate: through the orchestrator, not by chatting with each other.
    5. Each output is checked by a reviewer call using CHECK_SCHEMA (result validation). Failures are retried once with the problems as feedback, then the task stops for a human.
    6. In orchestrate(), the while loop finds every task whose dependencies are done and runs those tasks in parallel with asyncio.gather.
    7. If no task is ever ready, the plan has a loop or a missing task, so the program stops instead of waiting forever.
    8. main() ends with a human approval step. Publishing only happens on an explicit "yes".

    Communication, shared context, dependencies and validation

    πŸ“¨ Communication

    Route all messages through the orchestrator (a "hub"). Workers return results, and the orchestrator decides what each worker sees next. This is easy to log and control.

    πŸ—‚οΈ Shared context

    Give every worker the same essential facts (product sheet, brand voice) but not the whole history. Less context is cheaper and keeps each worker focused.

    πŸ”— Dependencies

    Write down what each task needs. Run tasks once their inputs exist, and run independent ones at the same time.

    βœ… Validation

    Check results with code (format, length) and with a reviewer prompt (accuracy, tone) before anything moves forward.

    Handling failures, loops, conflicts and costs

    ProblemWhat it looks likeHow to handle it
    FailuresA worker errors or times outLimited retries, then stop the task and report it. Don't pretend it succeeded.
    LoopsAgents keep sending work back and forthMaximum retries per task, a maximum number of calls overall, and dependency-loop detection
    Conflicting outputsResearch says $499, strategy says $399The validator checks against the shared facts. Unresolved conflicts go to a human.
    Token usageEvery agent rereads long historiesPass each worker only what it needs. Summarise long results.
    Execution costMany calls per request. Anthropic reported its multi-agent research system used about 15Γ— more tokens than a chatHard budgets, smaller and faster models for simple workers, and orchestration only where it pays off

    When one agent is enough, and when orchestration adds value

    πŸ™‚ One agent is enough

    • The task fits in one conversation
    • It needs one kind of expertise
    • Cost and simplicity matter most
    • A sequential or routing workflow already works

    🎼 Orchestration adds value

    • The goal splits into distinct specialities
    • Parts can run in parallel
    • One agent's context would get overloaded
    • The result is valuable enough to justify the extra cost

    Anthropic's advice is to start with the simplest solution and add complexity only when it clearly improves results.

    Use cases

    Advantages and limitations

    βœ… Advantages

    • Handles big, varied goals
    • Specialists produce better work
    • Parallel parts save time
    • Built-in quality control points

    ⚠️ Limitations

    • Much higher token use and cost
    • More complex to build and debug
    • Errors can pass between agents
    • Plans can be wrong, so they need checking

    🚫 Common mistakes

    • Using many agents for a simple job
    • No budget or loop limits
    • Sending every agent the full history
    • Skipping validation and human approval

    Best practices

    Knowledge check

    1. What is the orchestrator's main job?

    2. When is a single agent usually the better choice?

    3. The strategy task needs the research result. What is this called?

    4. Two agents produce conflicting prices. What should happen?

    5. Why set a maximum number of calls (a budget)?

    Mini project

    A mini trip-planning team.

    1. Adapt orchestrator.py with three specialists: researcher (sights and weather from your notes), budgeter (estimates costs), planner (writes a 2-day itinerary).
    2. Goal: "Plan a 2-day trip to Lisbon for two people under €600." Put your own facts in notes.txt.
    3. Check the printed plan: do researcher and budgeter run in parallel, with planner waiting for both?
    4. Add a validation rule: the itinerary must stay under budget.
    5. End with a human approval prompt: "Book this plan? (yes/no)". Only print what would be booked. Don't really book anything.

    Key takeaways

    Further learning