This lesson on Multi-Agent Systems and Supervisor Patterns is hands-on and example-driven. You can design and deploy centralized multi-agent systems using langgraph-supervisor to route complex user queries to specialized sub-agents. You will be able to synchronize conversational state with MessagesState, implement custom sub-agent workflows, and manage context window token consumption using fine-grained output modes.
What You'll Be Able To Do
- Install and configure
langgraph-supervisoralongside specialized ReAct sub-agents. - Instantiate a centralized supervisor agent that routes tasks using dynamic handoff tools.
- Compile custom LangGraph sub-agent workflows using explicit graph naming.
- Format invocation payloads with
MessagesStateschemas to ensure seamless state handoff. - Select appropriate
output_modesettings to balance full context tracking against token bloat.
Detailed Concept Walkthrough
1. Hierarchical Supervisor Architecture
A supervisor architecture acts as a centralized routing hub where a top-level LLM decides which specialized sub-agent handles specific user requests. The supervisor receives the user's prompt, dynamically selects a domain agent, and coordinates the return of the final answer.
- Coordination Mechanism: The supervisor agent uses high-level system prompts to inspect the conversation and match incoming requests to specialized sub-agents.
- Under the Hood: Rather than using manual conditional edges,
create_supervisorinspects the registered sub-agents and dynamically creates tool signatures for delegation. - Best Practice: Keep sub-agents focused on narrow, distinct functional domains (e.g., math, research, weather) to simplify supervisor routing decisions.
from langgraph_supervisor import create_supervisor
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o")
# Define specialized sub-agents
math_agent = create_react_agent(model=model, tools=[add_tool], name="math_expert")
research_agent = create_react_agent(model=model, tools=[lookup_tool], name="research_expert")
# Create the supervisor workflow
workflow = create_supervisor(
agents=[math_agent, research_agent],
model=model,
prompt="You are a team supervisor. Delegate math to math_expert and company lookups to research_expert."
)
Key Takeaway: Centralized supervisor architectures simplify orchestration by replacing complex conditional routing logic with structured LLM-driven delegation.
2. Automated Tool Handoff Mechanics
Agent delegation is implemented under the hood as LLM tool calling, where each sub-agent is converted into an executable transfer tool. When the supervisor decides to delegate, it generates a structured tool call that routes conversation execution to the target agent.
- Execution Flow: The supervisor model emits an empty
AIMessagecontaining a tool call in the formtransfer_to_<agent_name>. - Handoff Execution: LangGraph intercepts the transfer tool call, executes the target sub-agent with the current conversation history, and wraps the sub-agent's return in a
ToolMessage. - Response Synthesis: The supervisor receives the handoff
ToolMessageback from the sub-agent and formats the final synthesized response for the end user.
# Supervisor internally executes handoffs via generated transfer tools
# Example execution trace state flow:
# 1. User: 'What is 42 * 8?'
# 2. Supervisor AIMessage: tool_calls=[{'name': 'transfer_to_math_expert', 'args': {}}]
# 3. Target agent runs tools and completes calculation
# 4. ToolMessage returned to supervisor with sub-agent output
# 5. Supervisor AIMessage: 'The answer is 336.'
Key Takeaway: Handoffs operate directly through native tool-calling mechanisms by binding generated
transfer_to_<agent>schemas to the supervisor model.
3. Custom Sub-Agent Graph Integration
Arbitrary LangGraph workflows can serve as sub-agents within a supervisor system provided they adhere to compatible state schemas and explicit compilation naming. This allows complex multi-step custom graphs to be integrated alongside prebuilt ReAct agents.
- State Schema Matching: Custom sub-agent graphs must use a state schema compatible with
MessagesState, ensuring themessageskey can receive and pass conversational state. - Compilation Requirement: When calling
.compile()on a customStateGraph, you must pass thenameargument (e.g.,.compile(name="weather_agent")). - Tool Binding Generation: The supervisor reads the compiled graph's
nameattribute to construct the matchingtransfer_to_<name>tool for LLM binding.
from langgraph.graph import StateGraph, MessagesState, START, END
# Build custom sub-agent graph
builder = StateGraph(MessagesState)
builder.add_node("fetch_weather", weather_node)
builder.add_edge(START, "fetch_weather")
builder.add_edge("fetch_weather", END)
# CRITICAL: compile with explicit name parameter
custom_weather_agent = builder.compile(name="weather_expert")
# Integrate custom agent into supervisor
app = create_supervisor(
agents=[custom_weather_agent, math_agent],
model=model,
prompt="Delegate weather queries to weather_expert."
).compile()
Key Takeaway: Always supply an explicit
nameduring custom graph compilation so the supervisor can generate valid handoff tool definitions.
4. State Synchronization and Output Modes
Managing conversational history requires choosing between granular step tracking and context window optimization. The output_mode configuration dictates how much intermediate sub-agent execution history is appended to the shared graph state.
- full_history Mode: Returns the complete trace of intermediate sub-agent thoughts, internal tool calls, and execution steps directly into the shared
messageslist. - last_message Mode: Surfaces only the final
AIMessageproduced by the sub-agent, discarding intermediate scratchpad iterations before returning control to the supervisor. - Context Management: Use
last_messagefor production applications with long conversation histories to prevent context window exhaustion and reduce inference costs.
# Option 1: Full intermediate trace preserved (high token consumption)
supervisor_full = create_supervisor(
agents=[research_agent, math_agent],
model=model,
output_mode="full_history"
).compile()
# Option 2: Only final response preserved (optimized for context length)
supervisor_clean = create_supervisor(
agents=[research_agent, math_agent],
model=model,
output_mode="last_message"
).compile()
# Invoking the graph using MessagesState schema
result = supervisor_clean.invoke({
"messages": [{"role": "user", "content": "Find Apple's CEO and multiply his age by 2."}]
})
Key Takeaway: Configure
output_mode="last_message"to protect supervisor token limits unless debugging intermediate sub-agent execution steps.
Topics Covered in Multi-Agent Systems and Supervisor Patterns
- Architecture Overview (0:00 - 0:44) — Introduction to centralized multi-agent orchestration and the supervisor coordination pattern.
- Prebuilt Sub-Agents Setup (0:44 - 2:47) — Configuring specialized ReAct sub-agents with tools and initializing the supervisor graph.
- Graph Invocation Schema (2:47 - 4:09) — Compiling the supervisor graph and executing queries using the MessagesState dictionary structure.
- Handoff Mechanics & Tracing (4:09 - 5:45) — Inspecting tool calls, ToolMessages, and intermediate execution steps across agent handoffs.
- Custom Graph Sub-Agents (5:45 - 6:48) — Integrating arbitrary LangGraph StateGraph agents using explicit compilation naming.
- Output Modes & Context (6:48 - 8:43) — Comparing full_history and last_message output modes to manage token consumption.
- Supervisor Library Internals (8:43 - 10:05) — Analyzing create_supervisor source code, tool name normalization, and model binding mechanisms.
LangChain & LangGraph for AI Agents Cheat Sheet
-
pip install langgraph-supervisor— Installs the official LangGraph supervisor extension packagepip install langgraph-supervisor -
create_supervisor(agents, model, prompt, output_mode)— Initializes a centralized multi-agent supervisor graph workflowworkflow = create_supervisor(agents=[math_agent], model=model, prompt="Route math tasks.") -
create_react_agent(model, tools, name)— Creates a prebuilt ReAct agent with explicit identifiermath_agent = create_react_agent(model=model, tools=[add], name="math_expert") -
builder.compile(name="<agent_name>")— Compiles custom graph with identifier required for handoffsweather_agent = builder.compile(name="weather_expert") -
output_mode="last_message"— Retains only the final sub-agent response in stateapp = create_supervisor(agents=[agent], model=model, output_mode="last_message") -
output_mode="full_history"— Retains all intermediate sub-agent tool calls in stateapp = create_supervisor(agents=[agent], model=model, output_mode="full_history") -
app.invoke({"messages": [...]})— Executes supervisor graph using MessagesState input dictionary formatres = app.invoke({"messages": [{"role": "user", "content": "Hello"}]})
Comparison Table
| Feature / Setting | output_mode='full_history' | output_mode='last_message' |
|---|---|---|
| Intermediate Tool Calls | Appended to shared state | Discarded from state |
| Context Token Usage | High token accumulation | Optimized minimal tokens |
| Primary Use Case | Debugging and step tracing | Production deployments |
| Supervisor Visibility | Sees sub-agent scratchpad | Sees sub-agent final output |
Common Pitfalls
- Mistake: Compiling a custom sub-agent graph without passing the name parameter. Avoid: Always supply an explicit name string inside the compile call so handoff tools can bind.
- Mistake: Using incompatible custom state schemas across supervisor sub-agents. Avoid: Ensure all sub-agent graphs inherit from or match the MessagesState dictionary structure.
- Mistake: Defaulting to full_history output mode in long multi-step workflows. Avoid: Use last_message output mode to prevent context window bloat and unnecessary token spend.
FAQs
- How does the supervisor know which tools belong to which sub-agent?
The supervisor does not see individual sub-agent tools; it only sees high-level
transfer_to_<agent_name>tools generated automatically bycreate_supervisor. - Can I mix prebuilt ReAct agents and custom StateGraph workflows in the same supervisor?
Yes, provided the custom graphs use
MessagesStateand are compiled with anameargument, both agent types can be passed together in theagentslist. - What happens after a sub-agent completes its execution?
The sub-agent's output is wrapped in a
ToolMessageand returned to the supervisor, which either delegates to another agent or synthesizes the final user answer. - Why does the supervisor generate an empty AIMessage during delegation?
The supervisor's
AIMessagecontains tool call definitions undertool_callsrather than textual content, instructing LangGraph runtime to execute the handoff.