Multi-agent: supervisors and handoffs

LangGraph · Advanced · 15 min

Splitting a big agent into specialists keeps each one's prompt and tools focused, if you pick the right pattern.

One agent with fifty tools and a huge prompt tends to choose badly. Multi-agent designs give each specialist a narrow prompt and toolset, coordinated by the graph. Try a single agent first; split only when it struggles.

Supervisor (subagents) pattern. A central agent decides which specialist to call next. Two common shapes:

  • Each specialist is wrapped as a tool of the supervisor. The supervisor calls it, gets its final answer back, and keeps control of the conversation. Simple and good for parallel work.
  • The supervisor is a node that returns Command(goto="billing_agent"); specialists route back to it when done.

Handoffs. Agents transfer control to each other directly, often with tools like transfer_to_sales that return Command(goto="sales_agent", update={...}, graph=Command.PARENT). A state key such as active_agent remembers who is speaking, so the next user message goes to the right specialist. Good for customer-facing flows where the specialist talks to the user.

Router. A first step classifies the request and sends it to one or several agents, then combines answers. Cheap and predictable.

Whatever the pattern, decide deliberately what context each agent sees: the full message history, only its task, or a summary. Passing everything is simple but costs tokens and can confuse specialists.

The old langgraph-supervisor and langgraph-swarm helper packages are legacy; current docs build these patterns with LangChain's create_agent and plain LangGraph, and point to Deep Agents (a higher-level LangChain harness) for built-in subagents.

Example

from typing import Literal
from langchain_core.messages import AIMessage
from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.types import Command


class State(MessagesState):
    next_agent: str


def supervisor(state: State) -> Command[Literal["billing", "tech", "__end__"]]:
    last = state["messages"][-1]
    if isinstance(last, AIMessage):                  # a specialist answered: done
        return Command(goto=END)
    text = last.content.lower()                     # real code: model + structured output
    return Command(goto="billing" if "invoice" in text else "tech")


def billing(state: State) -> Command[Literal["supervisor"]]:
    return Command(goto="supervisor", update={"messages": [AIMessage("Billing: invoice resent.")]})


def tech(state: State) -> Command[Literal["supervisor"]]:
    return Command(goto="supervisor", update={"messages": [AIMessage("Tech: try a restart.")]})


b = StateGraph(State)
for fn in (supervisor, billing, tech):
    b.add_node(fn)
b.add_edge(START, "supervisor")
graph = b.compile()
print(graph.invoke({"messages": [{"role": "user", "content": "Where is my invoice?"}]})["messages"][-1].content)
# Billing: invoice resent.

Remember

  • Supervisor: one coordinator routes to specialists (as tools or via Command)
  • Handoffs: agents pass control with Command, tracking the active agent
  • Router: classify once, dispatch, combine
  • Choose what context each agent sees; start with one agent

Watch out for

  • Splitting into many agents before a single well-prompted agent has been tried and measured.

Check yourself

  1. In a customer chat, the sales specialist should keep talking to the user after taking over. Which pattern fits best?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.langchain.com/oss/python/langchain/multi-agent/index

Builds on

Unlocks

Nothing builds on this yet.