n4nAI

LangGraph supervisor pattern: coordinating multiple agents

Practical guide to the LangGraph supervisor pattern for multi-agent systems: architecture, code, pitfalls, and tradeoffs for production orchestration.

n4n Team3 min read761 words

Audio narration

Coming soon — every post will get a voice note here.

The LangGraph supervisor pattern multi-agent architecture solves a specific coordination problem: a central node delegates subtasks to specialized workers while keeping authority over state transitions and termination. If you’re building a system where multiple LLM agents need to collaborate without descending into chaos, this pattern gives you a single point of control. It is not a silver bullet, but it is the most debuggable starting point for production multi-agent workflows.

When to reach for a supervisor

Use the supervisor pattern when you have clear role separation—e.g., a researcher, a coder, and a critic—and need deterministic orchestration. If agents must negotiate peer-to-peer or dynamically spawn unknown tools, a decentralized graph or a swarm topology fits better.

The supervisor owns the decision of “who acts next” and “are we done.” That centralization simplifies logging, replay, and guardrail enforcement. You trade concurrency for observability.

Core graph shape

A supervisor graph is a cyclic state machine. The supervisor node inspects shared state, picks a worker, and routes there. Each worker executes and returns control to the supervisor. The loop ends when the supervisor routes to END.

State contract

Define a typed state. Keep it minimal: messages, next actor, and a scratchpad. LangGraph uses reducer annotations to merge updates.

from typing import TypedDict, Annotated, List
from langgraph.graph import START, END, StateGraph

class AgentState(TypedDict):
    messages: Annotated[List[dict], "append"]
    next: str  # name of next node or "FINISH"
    iteration: int

Supervisor node

The supervisor is a function that calls an LLM with a strict output format. Use structured output or function calling to force a decision.

from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4o-mini").with_structured_output({
    "type": "object",
    "properties": {"actor": {"enum": ["researcher", "coder", "FINISH"]}}
})

def supervisor(state: AgentState):
    decision = model.invoke([
        {"role": "system", "content": "Pick the next actor or FINISH."},
        *state["messages"]
    ])
    return {"next": decision["actor"], "iteration": state["iteration"] + 1}

Worker nodes

Each worker reads messages, acts, and appends results. It never decides control flow.

def researcher(state: AgentState):
    out = "mock research result"
    return {"messages": [{"role": "tool", "content": out}]}

def coder(state: AgentState):
    out = "mock code diff"
    return {"messages": [{"role": "tool", "content": out}]}

Implementing the pattern

Wire nodes with conditional edges from the supervisor. The route function reads next and guards against infinite loops.

builder = StateGraph(AgentState)
builder.add_node("supervisor", supervisor)
builder.add_node("researcher", researcher)
builder.add_node("coder", coder)

builder.add_edge(START, "supervisor")

def route(state: AgentState):
    if state["next"] == "FINISH" or state["iteration"] > 10:
        return END
    return state["next"]

builder.add_conditional_edges("supervisor", route)
builder.add_edge("researcher", "supervisor")
builder.add_edge("coder", "supervisor")

graph = builder.compile()

This is the minimal langgraph supervisor pattern multi-agent skeleton. Every worker returns to the supervisor; the supervisor breaks the loop.

Writing the supervisor prompt

The supervisor’s system prompt must enumerate workers and output a constrained label. Do not ask the model to “think step by step” here—keep it routing-only.

You are an orchestrator. Available actors: researcher, coder.
Return JSON {"actor": "researcher"|"coder"|"FINISH"}.
Finish when both research and code are present in messages.

Pitfall: if the LLM emits a typo, route will raise. Validate and default to FINISH on unknown labels.

def route(state: AgentState):
    nxt = state.get("next", "FINISH")
    if nxt not in {"researcher", "coder", "FINISH"}:
        return END
    if nxt == "FINISH" or state["iteration"] > 10:
        return END
    return nxt

Termination and loop control

Unbounded loops are the most common production failure. Cap iterations in state and force FINISH after N steps. Also enforce a token budget: if messages exceed a threshold, truncate or summarize before the next supervisor call.

A simple heuristic: count messages length; if > 20, replace older tool messages with a summary entry. The supervisor does not need full history to route.

Common pitfalls

Over-centralizing reasoning

Engineers often stuff all planning into the supervisor prompt. That bloats context and creates a single point of latency. Push execution context into workers; keep supervisor prompts to routing only.

State mutation races

LangGraph passes state copies between nodes, but if you use shared mutable objects inside tools, you’ll get non-deterministic runs. Never mutate state in place; return deltas.

Token blowup

Each supervisor call sees full messages. With three workers and 10 iterations, you can multiply prompt size by 30. Use summarization edges or scoped state slices per worker.

Ignoring provider limits

If each worker calls a different model provider, rate limits will surface as intermittent failures. The supervisor should treat model errors as transient and retry or reroute.

Tradeoffs vs. other topologies

The supervisor pattern is easier to test than a peer-to-peer graph because control flow is explicit. But it adds a sequential bottleneck: workers never run concurrently. If your tasks are independent, use a fan-out graph and aggregate later.

For deeply nested planning, a hierarchical supervisor (supervisor of supervisors) works, but debuggability drops fast. Start flat.

Production model routing

When each worker targets a different model provider, you inherit provider rate limits. An OpenAI-compatible gateway such as n4n.ai addresses 240+ models behind one endpoint, applies automatic fallback when a provider degrades, and meters per-token usage—while honoring your routing directives and provider cache-control hints. That lets the supervisor treat model selection as a config change, not a code change.

Testing the supervisor

Write property tests: given a state with next="coder", the graph steps to coder and returns to supervisor. Use graph.invoke with frozen state fixtures.

def test_routes_to_coder():
    res = graph.invoke({"messages": [], "next": "coder", "iteration": 0})
    assert res["next"] in {"researcher", "coder", "FINISH"}

Record traces to confirm the supervisor never skips a worker when the task requires it.

Observability

Emit a span per node. Tag supervisor decisions with the chosen actor. This turns “why did the agent loop 9 times” into a one-line query instead of a forensic exercise.

Closing checklist

  • Define strict state schema with reducers.
  • Constrain supervisor output via structured calls.
  • Cap iterations and tokens.
  • Validate routing labels defensively.
  • Isolate worker side effects.
  • Monitor loop counts in prod.
  • Treat model selection as configuration.

The langgraph supervisor pattern multi-agent design is boring on purpose. Boring orchestration is what survives contact with real traffic.

Tagslanggraphsupervisor-patternmulti-agentorchestration

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All langgraph multi-agent workflows posts →