By Sagar Shankaran, Founder of CallSphere
Build multi-agent systems in LangGraph using subgraph composition, supervisor routing, and parallel worker execution to create specialized agent teams that collaborate on complex tasks.
Key takeaways
A single agent with many tools quickly hits a ceiling. As you add more tools, the LLM becomes less reliable at selecting the right one. The system prompt grows unwieldy. Different tasks require different model configurations or temperature settings. Multi-agent systems solve this by decomposing complex workflows into specialized agents, each focused on a narrow domain, coordinated by a supervisor.
In the supervisor pattern, one agent acts as a router that decides which specialized worker agent should handle each step:
flowchart TD
USER(["User input"])
SUPER["Supervisor node<br/>routes by state"]
A["Specialist node A<br/>research"]
B["Specialist node B<br/>writing"]
TOOL{"Tool call<br/>needed?"}
EXEC["Tool executor<br/>ToolNode"]
CHK[("Postgres<br/>checkpointer")]
INT{"interrupt for<br/>human approval?"}
HUMAN(["Human reviewer"])
OUT(["Final response"])
USER --> SUPER
SUPER --> A
SUPER --> B
A --> TOOL
B --> TOOL
TOOL -->|Yes| EXEC --> SUPER
TOOL -->|No| INT
INT -->|Yes| HUMAN --> SUPER
INT -->|No| OUT
SUPER <--> CHK
style SUPER fill:#4f46e5,stroke:#4338ca,color:#fff
style CHK fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style OUT fill:#059669,stroke:#047857,color:#fff
style HUMAN fill:#f59e0b,stroke:#d97706,color:#1f2937
from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
class TeamState(TypedDict):
messages: Annotated[list, add_messages]
next_agent: str
supervisor_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
def supervisor(state: TeamState) -> dict:
system = SystemMessage(content="""You are a supervisor routing tasks.
Based on the user request, decide which worker to invoke:
- 'researcher' for information gathering
- 'writer' for content creation
- 'coder' for code generation
- 'FINISH' if the task is complete
Respond with ONLY the worker name.""")
response = supervisor_llm.invoke(
[system] + state["messages"]
)
return {"next_agent": response.content.strip().lower()}
Each worker is a focused agent with its own system prompt and tools:
researcher_llm = ChatOpenAI(model="gpt-4o-mini")
writer_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
coder_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
def researcher(state: TeamState) -> dict:
system = SystemMessage(
content="You are a research assistant. Find and summarize information."
)
response = researcher_llm.invoke(
[system] + state["messages"]
)
return {"messages": [response]}
def writer(state: TeamState) -> dict:
system = SystemMessage(
content="You are a content writer. Create polished, well-structured text."
)
response = writer_llm.invoke(
[system] + state["messages"]
)
return {"messages": [response]}
def coder(state: TeamState) -> dict:
system = SystemMessage(
content="You are a Python developer. Write clean, tested code."
)
response = coder_llm.invoke(
[system] + state["messages"]
)
return {"messages": [response]}
Connect the supervisor to workers with conditional routing:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
def route_to_worker(state: TeamState) -> Literal[
"researcher", "writer", "coder", "__end__"
]:
next_agent = state["next_agent"]
if next_agent == "finish":
return "__end__"
return next_agent
builder = StateGraph(TeamState)
builder.add_node("supervisor", supervisor)
builder.add_node("researcher", researcher)
builder.add_node("writer", writer)
builder.add_node("coder", coder)
builder.add_edge(START, "supervisor")
builder.add_conditional_edges("supervisor", route_to_worker)
# All workers route back to supervisor after completing
builder.add_edge("researcher", "supervisor")
builder.add_edge("writer", "supervisor")
builder.add_edge("coder", "supervisor")
graph = builder.compile()
The supervisor evaluates each response and decides whether to hand off to another worker or finish. This creates a loop where the supervisor orchestrates a multi-step collaboration.
For complex workers that are themselves multi-step graphs, use subgraph composition:
def build_research_subgraph() -> StateGraph:
"""Build a research agent with search and analysis steps."""
class ResearchState(TypedDict):
messages: Annotated[list, add_messages]
def search(state: ResearchState) -> dict:
# Perform web search
return {"messages": [{"role": "assistant", "content": "Search results..."}]}
def analyze(state: ResearchState) -> dict:
# Analyze search results
return {"messages": [{"role": "assistant", "content": "Analysis..."}]}
sub = StateGraph(ResearchState)
sub.add_node("search", search)
sub.add_node("analyze", analyze)
sub.add_edge(START, "search")
sub.add_edge("search", "analyze")
sub.add_edge("analyze", END)
return sub.compile()
research_graph = build_research_subgraph()
# Use the subgraph as a node in the parent graph
builder.add_node("researcher", research_graph)
The parent graph treats the subgraph as a single node. State flows in, the subgraph processes it through its own internal nodes, and the final state flows back to the parent.
LangGraph supports sending work to multiple nodes simultaneously:
from langgraph.graph import Send
def fan_out(state: TeamState) -> list[Send]:
"""Send the task to multiple workers in parallel."""
return [
Send("researcher", state),
Send("writer", state),
]
builder.add_conditional_edges("supervisor", fan_out)
The Send object directs execution to a specific node with a given state. Returning multiple Send objects causes parallel execution, and the results are merged using the state reducers.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
result = graph.invoke({
"messages": [HumanMessage(
content="Research the latest trends in AI agents, "
"then write a blog post about the findings."
)],
"next_agent": "",
})
# The supervisor coordinates: researcher gathers info, writer creates content
for msg in result["messages"]:
print(f"{msg.__class__.__name__}: {msg.content[:80]}...")
The supervisor first routes to the researcher, then after receiving the research results, routes to the writer to produce the final output.
There is no hard limit, but LLM-based routers become less reliable with more than 8-10 options. For larger systems, use a hierarchical pattern with multiple supervisors, each managing a team of 3-5 specialists.
In the standard supervisor pattern, workers communicate through the shared state — they read each other's outputs from the message history. Direct agent-to-agent communication is possible by having workers write to specific state channels that other workers read from.
Add loop counters to state and check them in the supervisor. If a worker has been called more than N times without progress, the supervisor should either try a different worker or terminate with a partial result.
#LangGraph #MultiAgent #SupervisorPattern #Subgraphs #Python #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
How we built a fault-tolerant HVAC emergency triage and tech-dispatch platform on Kubernetes — three-tier CQRS, 11 micro-agents on the OpenAI Agents SDK + LangGraph, NATS JetStream, DTMF/SMS/WebSocket acceptance, circuit breakers, and an evaluation pipeline that catches regressions before they wake a tech at 3 AM.
How to design a multi-agent system using MCP for tools and A2A for cross-vendor coordination, with a CallSphere voice agent as a participating node.
A2A is the open standard for agent-to-agent coordination. Here is how the Agent Card JSON works, how discovery happens, and what to publish.
A2A unlocks cross-vendor agent coordination, but most enterprise voice/chat workloads still ship faster on a single-vendor stack. Here is how to choose.
How short-term (thread-scoped) and long-term (cross-thread) memory actually work in LangGraph, with code, schemas, and the eviction policies that keep cost predictable.
Langgraph multi-agent supervisor handoffs docs: the supervisor pattern in LangGraph for coordinating specialist agents, with full code, an eval pipeline that scores routing accuracy, and the failure modes to watch for.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.