/pattern/supervisor/

03 · Multi-AgentManager AgentCoordinator AgentCentral Controller

Supervisor.
One decides. Specialists do.

A central agent dynamically decides which specialized agent or tool works next.

When to reach for it

  • Central control and traceability are essential.
  • Multiple specialists must be coordinated.
  • Tasks are dynamically delegated.

When it backfires

  • Fully decentralized cooperation is needed.
  • The supervisor risks becoming a single point of failure.
  • Delegation is static enough for simple routing.

The tradeoff

Clear operational control is maintained at the cost of a potential coordination bottleneck.

The mental model

A shape you can draw on a napkin.

Every step starts and ends with the supervisor. Watch the dispatch: the supervisor chooses a specialist, the specialist returns, the supervisor decides whether to dispatch again or to answer.

SupervisorSpecialist ASpecialist BSpecialist C
Walk it through

A real run, step by step.

Task"My package didn't arrive, and I want a refund."
1 / 8
Decide"Two issues here: delivery and refund. Let me handle the delivery question first — I need facts before I can resolve money."
Dispatch → Shippingshipping_agent.handle(order_id="A-77192")
Shipping replies"Tracking shows package marked delivered to wrong address on May 12. Customer is correct that it did not arrive."
Decide"Delivery is confirmed lost. Now I can authorise the refund."
Dispatch → Refundsrefunds_agent.handle(order_id="A-77192", reason="lost_in_transit", amount=47.00)
Refunds replies"Refund of €47.00 processed. Reference R-30482. Customer notified by email."
Decide"Both issues resolved. Nothing more to dispatch. Ending the run."
Answer"Your package was delivered to the wrong address. We've refunded €47.00 (ref R-30482) and emailed confirmation."
In code

The loop is already built in.

LangGraphpython
from langgraph.graph import StateGraph, END

def supervisor(state):
    # LLM picks the next agent by name — or "done"
    choice = llm.choose_next(
        history=state["messages"],
        options=["shipping", "refunds", "tech", "done"],
    )
    return {"next": choice}

graph = StateGraph(State)
graph.add_node("supervisor", supervisor)
graph.add_node("shipping", shipping_agent)
graph.add_node("refunds", refunds_agent)
graph.add_node("tech", tech_agent)

graph.add_conditional_edges("supervisor",
    lambda s: s["next"],
    {"shipping": "shipping", "refunds": "refunds",
     "tech": "tech",   "done": END})

# Each specialist returns control to the supervisor:
for spec in ["shipping", "refunds", "tech"]:
    graph.add_edge(spec, "supervisor")

graph.set_entry_point("supervisor")
Pitfalls

Three ways this pattern will hurt you.

God Orchestrator

The supervisor accumulates tools, becomes the place where every prompt change lands, and ends up holding raw user data it doesn't need.

Fix · Keep the supervisor's prompt & toolset minimal. Specialists own their tools. Sensitive data stays inside the specialist's scope.

Ping-pong routing

The supervisor sends to Shipping, gets a partial answer, sends to Shipping again, gets the same partial answer, and the loop never terminates.

Fix · Hard max_iterations. Schema-validate the supervisor's "next" output. Log every routing decision.

Context loss between specialists

Refunds doesn't know what Shipping found because the supervisor summarised away the detail. Decisions are made on stale or wrong assumptions.

Fix · Pass structured state, not text summaries. Pydantic v2 schemas at every handoff. Keep raw facts inspectable.

Framework support

Where Supervisor is native.

LangGraphNative
CrewAIHierarchical ProcessNative
Microsoft Agent FrameworkNative

Search

Search patterns, frameworks, and pages.