Why Multi-Agent?
A single LangGraph agent has a limited context window, a fixed set of tools, and must handle every subtask sequentially. Multi-agent systems break complex workflows into specialized roles so each agent stays focused, context stays small, and work can run in parallel.
Specialization
One agent searches the web, another writes code, another checks facts — each excels at its role.
Parallelism
Independent sub-tasks run concurrently, cutting total latency significantly.
Scalability
Add or swap agents without rewriting the whole system — just update the graph edges.
Core Patterns
Multi-agent architectures fall into a few common topologies. Choose based on whether subtasks are sequential, parallel, or dynamic.
| Pattern | Description | Best For |
|---|---|---|
| Supervisor | One orchestrator delegates to worker agents | Dynamic task routing |
| Parallel Fan-Out | Multiple agents run the same task on different inputs | Batch processing, map-reduce |
| Sequential Pipeline | Output of one agent feeds the next | Refinement workflows |
| Hierarchical | Supervisors manage supervisors | Very large, nested tasks |
| Peer-to-Peer | Agents hand off directly without a supervisor | Fixed, well-defined flows |
Supervisor Pattern with LangGraph
The supervisor is an LLM node that reads the conversation and decides which worker to call next, or when to finish. Workers return their output and the supervisor re-evaluates.
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
from typing import TypedDict, Literal
llm = ChatOpenAI(model="gpt-4o-mini")
# --- Workers ---
researcher = create_react_agent(llm, tools=[search_tool], name="researcher")
writer = create_react_agent(llm, tools=[draft_tool], name="writer")
reviewer = create_react_agent(llm, tools=[check_tool], name="reviewer")
# --- State ---
class State(TypedDict):
messages: list
next: str
# --- Supervisor node ---
MEMBERS = ["researcher", "writer", "reviewer"]
SYSTEM = (
"You are a supervisor. Given the conversation, decide which worker "
"should act next or respond FINISH.\n"
f"Workers: {MEMBERS}"
)
def supervisor_node(state: State) -> State:
response = llm.invoke([{"role": "system", "content": SYSTEM}] + state["messages"])
next_worker = response.content.strip()
return {"next": next_worker if next_worker in MEMBERS else END}
# --- Graph ---
graph = StateGraph(State)
graph.add_node("supervisor", supervisor_node)
graph.add_node("researcher", researcher)
graph.add_node("writer", writer)
graph.add_node("reviewer", reviewer)
graph.set_entry_point("supervisor")
for member in MEMBERS:
graph.add_edge(member, "supervisor") # always return to supervisor
graph.add_conditional_edges(
"supervisor",
lambda s: s["next"],
{m: m for m in MEMBERS} | {END: END},
)
app = graph.compile()
result = app.invoke({"messages": [{"role": "user", "content": "Research and write a blog post about LangGraph."}]})
Parallel Fan-Out
LangGraph supports sending to multiple nodes at once via Send — each runs concurrently in the same graph step.
from langgraph.constants import Send
def split_topics(state):
# Fan out: one agent call per topic
return [Send("research_agent", {"topic": t}) for t in state["topics"]]
def research_agent(state):
result = llm.invoke(f"Research: {state['topic']}")
return {"results": [result.content]}
def merge_results(state):
combined = "\n\n".join(state["results"])
summary = llm.invoke(f"Summarize:\n{combined}")
return {"final": summary.content}
graph = StateGraph(OverallState)
graph.add_node("research_agent", research_agent)
graph.add_node("merge_results", merge_results)
graph.set_entry_point("split_topics")
graph.add_conditional_edges("split_topics", split_topics, ["research_agent"])
graph.add_edge("research_agent", "merge_results")
graph.add_edge("merge_results", END)
app = graph.compile()
result = app.invoke({"topics": ["LangChain", "LangGraph", "LangSmith"]})
Performance tip: Three parallel research agents finish in roughly the same time as one, so fan-out is ideal for any map-reduce pattern where sub-tasks are independent.
Agent Handoffs
Agents can hand off control directly using transfer_to_agent tool calls — no supervisor required. The caller agent emits a tool call whose name matches the target agent.
from langgraph.prebuilt import create_react_agent
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.types import Command
from langchain_core.tools import tool
# Define handoff tools
@tool
def transfer_to_billing() -> Command:
"""Transfer to the billing specialist agent."""
return Command(goto="billing_agent")
@tool
def transfer_to_tech_support() -> Command:
"""Transfer to technical support agent."""
return Command(goto="tech_agent")
# Create agents
triage_agent = create_react_agent(
llm,
tools=[transfer_to_billing, transfer_to_tech_support],
name="triage",
)
billing_agent = create_react_agent(llm, tools=[lookup_invoice], name="billing")
tech_agent = create_react_agent(llm, tools=[run_diagnostic], name="tech_support")
# Build graph with native LangGraph handoffs
graph = StateGraph(MessagesState)
graph.add_node("triage_agent", triage_agent)
graph.add_node("billing_agent", billing_agent)
graph.add_node("tech_agent", tech_agent)
graph.add_edge(START, "triage_agent")
# Agents emit Command(goto=...) to hand off directly
app = graph.compile()
Shared State & Memory
All agents in a graph share a single state object. Use annotated fields with reducers to safely merge concurrent writes.
from typing import Annotated
from operator import add
from langgraph.graph import StateGraph
class SharedState(TypedDict):
messages: Annotated[list, add] # append-only — safe for concurrent writes
results: Annotated[list, add] # each agent appends its result
final: str # last-write wins
# Persist state across sessions with a checkpointer
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)
# Thread ID isolates state per conversation
config = {"configurable": {"thread_id": "user-42"}}
app.invoke({"messages": [...]}, config=config)
For production, replace MemorySaver with PostgresSaver or RedisSaver so state persists across restarts and can be shared across instances. Pair shared state with a human-in-the-loop checkpoint when an agent needs approval before acting.
Best Practices
Keep agents small
Give each agent 3–5 tools maximum. Overloaded agents make poor decisions.
Set recursion limits
Use graph.compile(recursion_limit=25) to prevent infinite supervisor loops.
Name agents clearly
The supervisor uses agent names to route — descriptive names reduce routing errors.
Trace everything
Multi-agent graphs are hard to debug without LangSmith traces. Enable tracing from day one.
Multi-Agent Systems FAQ
What is a multi-agent system in LangGraph?
A multi-agent system splits a complex workflow across several specialized agents that each own a narrow role, such as research, writing, or review. In LangGraph these agents are nodes in a shared graph that pass control and a common state object between one another, so each agent keeps a small context window while the system as a whole tackles a large task.
What is the difference between a supervisor and a swarm architecture?
In a supervisor architecture, one orchestrator LLM reads the conversation and decides which worker agent runs next, and workers always return control to the supervisor. In a swarm or peer-to-peer architecture there is no central router: agents hand off directly to each other. Supervisors are best for dynamic routing, while swarms suit fixed, well-defined flows where the path between agents is known in advance.
How do agents hand off control to each other in LangGraph?
Agents hand off by emitting a Command(goto=...) from a handoff tool, or by routing through conditional edges in the graph. A triage agent can expose tools like transfer_to_billing that return Command(goto='billing_agent'), which moves execution to that node directly without a supervisor in the middle. The shared state travels with the handoff so the receiving agent has full context.
When should I use multiple agents instead of one?
Use a single agent when the task fits one prompt and a small tool set. Reach for multiple agents when subtasks need different tools or expertise, when work can run in parallel to cut latency, or when one agent's tool list grows past roughly five tools and its decisions get worse. Multi-agent systems add coordination overhead, so only split when specialization or parallelism clearly pays for it.
How do agents share state in a LangGraph multi-agent system?
All agents in a LangGraph graph read and write a single shared state object. Fields that several agents update concurrently should use annotated reducers, such as Annotated[list, add], so writes append safely instead of overwriting each other. A checkpointer like MemorySaver in development or PostgresSaver in production persists that state across steps and sessions, keyed by a thread_id.
How do I prevent infinite loops in a multi-agent graph?
Set a recursion limit when compiling the graph, for example graph.compile(recursion_limit=25), so the supervisor cannot route between workers forever. Give the supervisor a clear FINISH option, keep each agent's tool set small so routing stays predictable, and enable LangSmith tracing to spot loops where two agents keep handing work back and forth.