07 Oct LangGraph Multi-Agent Systems: The Supervisor Pattern
One agent with ten tools can get confused. A multi-agent system splits the work: several small agents, each with a narrow job and a few tools, coordinated by a manager. The most common design is the supervisor pattern.
The idea
+-> math_agent ----+
START -> supervisor +-> weather_agent -+-> back to supervisor
+-> responder -> END
- Workers are specialist agents (for example, math and weather)
- The supervisor reads the conversation and decides who works next, or whether the job is done
- Every worker reports back to the supervisor, so it can pick the next step (a loop, like Chapter 7)
- A responder writes the final answer
Why split into agents?
- Focus: a worker with two tools chooses better than one with twenty
- Clear prompts: each worker gets instructions for its own job
- Easy to grow: adding a new skill means adding a worker
- Easy to test: try each worker on its own
The trade-off is cost and speed. Each supervisor decision is an extra model call. If one agent handles your task well, keep it as one agent.
Step 1: Build the worker agents
Each worker is a create_agent agent (Chapter 12) with its own tools:
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain_anthropic import ChatAnthropic
from langchain_core.tools import tool
load_dotenv()
llm = ChatAnthropic(model="claude-sonnet-5-5")
@tool
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
@tool
def get_weather(city: str) -> str:
"""Get the current weather for a city."""
data = {"delhi": "34°C, sunny", "london": "15°C, cloudy"}
return data.get(city.lower(), "Weather data not available")
math_agent = create_agent(
llm,
tools=[multiply],
system_prompt="You are a math specialist. Solve the math part of the request using your tool.",
)
weather_agent = create_agent(
llm,
tools=[get_weather],
system_prompt="You are a weather specialist. Report the weather using your tool.",
)
Step 2: Wrap each worker as a node
A wrapper runs the agent, then sends its answer back to the supervisor:
from typing import Literal
from langchain_core.messages import HumanMessage
from langgraph.graph import MessagesState
from langgraph.types import Command
def as_text(content):
if isinstance(content, str):
return content
return "".join(b.get("text", "") for b in content if isinstance(b, dict))
def call_worker(agent, name):
def node(state: MessagesState) -> Command[Literal["supervisor"]]:
result = agent.invoke({"messages": state["messages"]})
text = as_text(result["messages"][-1].content)
return Command(
goto="supervisor",
update={"messages": [HumanMessage(content=f"[{name}] {text}", name=name)]},
)
return node
What it does:
- Runs the worker on the conversation so far
- Takes only the worker’s final answer (not all its internal tool messages), which keeps the shared history short
- Labels it with the worker’s name, so the supervisor knows who said what
- Returns Command(goto=”supervisor”, update=…), which both updates the state and sends control back
Step 3: The supervisor
The supervisor uses structured output, so its decision is always one of a fixed set of choices:
from pydantic import BaseModel, Field
from langchain_core.messages import SystemMessage
class Route(BaseModel):
next: Literal["math_agent", "weather_agent", "FINISH"] = Field(
description="The next worker to run, or FINISH if the question is fully answered."
)
router_llm = llm.with_structured_output(Route)
SUPERVISOR_PROMPT = """You are a supervisor managing two workers:
- math_agent: does arithmetic
- weather_agent: reports the weather for a city
Read the conversation. Choose the next worker who is still needed.
Choose FINISH only when every part of the user's question has been answered by the messages above."""
def supervisor(state: MessagesState) -> Command[Literal["math_agent", "weather_agent", "responder"]]:
decision = router_llm.invoke([SystemMessage(SUPERVISOR_PROMPT)] + state["messages"])
if decision.next == "FINISH":
return Command(goto="responder")
return Command(goto=decision.next)
The Literal type guarantees the supervisor can’t invent a worker name, and it also tells LangGraph the possible destinations for diagrams.
Step 4: The responder and the graph
from langgraph.graph import StateGraph, START, END
def responder(state: MessagesState):
instruction = SystemMessage(
"Write a short final answer to the user's original question, using the worker results above."
)
reply = llm.invoke([instruction] + state["messages"])
return {"messages": [reply]}
builder = StateGraph(MessagesState)
builder.add_node("supervisor", supervisor)
builder.add_node("math_agent", call_worker(math_agent, "math_agent"))
builder.add_node("weather_agent", call_worker(weather_agent, "weather_agent"))
builder.add_node("responder", responder)
builder.add_edge(START, "supervisor")
builder.add_edge("responder", END)
graph = builder.compile()
Notice how few edges there are. The supervisor and workers return Command(goto=…), so they choose their own next step (Chapter 15 introduced Command).
Run it
result = graph.invoke(
{"messages": [("human", "What's the weather in Delhi, and what is 12 times 12?")]},
{"recursion_limit": 15},
)
print(as_text(result["messages"][-1].content))
Sample output:
Delhi is currently 34°C and sunny, and 12 times 12 is 144.
Watch the teamwork
for step in graph.stream(
{"messages": [("human", "What's the weather in Delhi, and what is 12 times 12?")]},
{"recursion_limit": 15},
stream_mode="updates",
):
print(list(step.keys()))
Sample output (the order may vary):
['supervisor'] ['weather_agent'] ['supervisor'] ['math_agent'] ['supervisor'] ['responder']
Follow the story: the supervisor picks the weather worker, the worker reports back, the supervisor picks the math worker, and once both answers are in, the supervisor says FINISH and the responder writes the reply. If a question needs only one worker, the run is shorter.
Design tips
- Give each worker a narrow job and only the tools it needs
- Write clear worker descriptions in the supervisor prompt. The supervisor chooses based on what you write there.
- Always set a recursion_limit, because a confused supervisor could loop
- Keep worker output short, since the whole history is sent to the supervisor each round, and long histories cost more
- Test workers alone first, then connect them
Other patterns
- Supervisor (this chapter): one manager routes to workers and collects results
- Handoff or swarm: agents pass control directly to each other, with no central manager
- Hierarchical: supervisors managing other supervisors (each team is a subgraph, Chapter 17)
LangChain also publishes helper libraries for these patterns (for example, langgraph-supervisor). Check their current documentation once you understand the version you built by hand here.
Security notes
- Limit each worker’s power: a worker that can delete files or send money needs a human approval step (Chapter 15)
- Treat worker output as untrusted: if a worker reads web pages or user files, text inside them could try to steer the supervisor, so never give the supervisor dangerous tools
- Cap cost: multi-agent runs make many model calls, so use the recursion limit and watch your usage
Common mistakes
- Workers don’t return to the supervisor: without goto=”supervisor”, the run ends early
- Passing every internal tool message back: return just the final answer to keep history small
- No FINISH option: the supervisor could route forever
- Forgetting recursion_limit: a routing mistake can run up costs
- Vague worker descriptions: the supervisor can’t pick the right specialist
- Too many agents for a simple task: one agent with a few tools is often better
- A worker name in Route that doesn’t match a node name: they must match exactly
Summary
- A multi-agent system splits work across specialist agents
- In the supervisor pattern, a manager routes to workers, and workers report back
- Command(goto=…, update=…) lets nodes route themselves and update state at the same time
- Structured output with Literal keeps the supervisor’s choices valid
- Add a recursion limit, keep worker outputs short, and use multiple agents only when one isn’t enough
Practice
- Add a third worker, text_agent, with the word_count tool from Chapter 10, and update the supervisor prompt and Route
- Ask a question that needs only one worker and confirm the supervisor skips the other
- Stream a question needing all three workers and write down the order they ran
- Give the supervisor a bad instruction (for example, “never choose FINISH”) and see the recursion limit stop it
If you liked the tutorial, spread the word and share the link and our website, Studyopedia, with others.
For Videos, Join Our YouTube Channel:Â Join Now
Read More:
- Generative AI Tutorial
- AI Ethics
- Machine Learning Tutorial
- Deep Learning Tutorial
- Ollama Tutorial
- Retrieval Augmented Generation (RAG) Tutorial
- ChatGPT Tutorial
- Microsoft Copilot Tutorial
No Comments