Mastering LangGraph Multi‑Agent Collaboration: The Supervisor Pattern Explained from Theory to Practice
The article examines why single‑agent setups fail, introduces the Supervisor pattern for clear responsibility separation, compares Tool‑Calling and Handoff approaches, provides a complete TypeScript implementation, explores hierarchical supervisors, and outlines five common pitfalls with concrete fixes.
Why Multi‑Agent? The Three Pitfalls of a Single Agent
When a single Agent is equipped with many tools, tool‑selection errors increase sharply—research shows error rates rise noticeably once the tool count exceeds ten. The context window becomes overloaded with all tool‑call histories, causing the LLM to forget core instructions, and debugging is impossible because the workflow is a black box.
Supervisor Pattern Core Principle: Central Controller + Expert Division
The Supervisor pattern enforces responsibility separation: a central Supervisor handles routing, coordination, and final aggregation, while each Worker Agent focuses on a single specialty (e.g., research, math, code).
User request
↓
┌───────────────┐
│ Supervisor │ ← only routes, coordinates, aggregates
└───────┬───────┘
│ tool_call / handoff
┌────┼─────┐
↓ ↓ ↓
[Research] [Math] [Code]
↓ ↓ ↓
Result1 Result2 Result3
└─────┼─────┘
↓
Supervisor aggregates
↓
Final answerThe Supervisor performs three tasks: understand user intent and decompose the task, decide which Agent to call and in what order, and integrate the agents' outputs into the final answer. Workers execute their assigned task without concern for routing.
Two Implementation Approaches: Tool Calling vs Handoff – Which to Choose?
Control: Tool Calling keeps control with the Supervisor; Handoff transfers control to the Worker.
Suitable Scenarios: Tool Calling fits predictable, well‑defined pipelines (e.g., report generation, data analysis). Handoff suits flexible dialogues where the Worker must interact directly with the user (e.g., customer‑service bots).
Context Sharing: Tool Calling uses a unified Supervisor‑managed context; Handoff passes state explicitly.
Debug Difficulty: Tool Calling offers clearer traces; Handoff disperses control flow.
Typical Use Cases: Tool Calling – report generation, data‑analysis pipelines; Handoff – conversational agents, multi‑turn negotiations.
Practical advice: about 90 % of scenarios can use the Tool‑Calling Supervisor; reserve Handoff for cases where the Worker needs direct, multi‑turn interaction with the user, such as medical triage.
Building a Supervisor from Scratch: Full TypeScript Implementation
Step 1 – Define Worker Agents
import { ChatOpenAI } from "@langchain/openai";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
const model = new ChatOpenAI({ model: "gpt-4o-mini" });
// Math tools
const addTool = tool(async ({ a, b }) => `${a + b}`, {
name: "add",
description: "Add two numbers",
schema: z.object({ a: z.number(), b: z.number() })
});
const multiplyTool = tool(async ({ a, b }) => `${a * b}`, {
name: "multiply",
description: "Multiply two numbers",
schema: z.object({ a: z.number(), b: z.number() })
});
// Simplified web‑search tool
const webSearchTool = tool(async ({ query }) => `Search result: latest info about "${query}"...`, {
name: "web_search",
description: "Search the web for information",
schema: z.object({ query: z.string() })
});
const mathAgent = createReactAgent({
llm: model,
tools: [addTool, multiplyTool],
messageModifier: "You are a math expert. Only perform calculations and return the result."
});
const researchAgent = createReactAgent({
llm: model,
tools: [webSearchTool],
messageModifier: "You are a research expert. Only perform web searches and return the results."
});Step 2 – Define Supervisor State and Routing Logic
import { Annotation, StateGraph, END, START } from "@langchain/langgraph";
import { BaseMessage, HumanMessage } from "@langchain/core/messages";
// Supervisor state definition
const SupervisorState = Annotation.Root({
messages: Annotation<BaseMessage[]>({ reducer: (x, y) => x.concat(y) }),
next: Annotation<string>({
reducer: (x, y) => y ?? x,
default: () => "FINISH"
})
});
const members = ["math_agent", "research_agent"] as const;
type Member = typeof members[number] | "FINISH";
// Supervisor decision node – calls LLM to pick next expert
async function supervisorNode(state: typeof SupervisorState.State) {
const systemPrompt = `You are a task coordinator managing experts: ${members.join(", ")}.
Based on the user request and current progress, decide which expert to call next, or reply FINISH if the task is complete.
Rules:
- Need information search → research_agent
- Need mathematical calculation → math_agent
- All done → FINISH
Reply only the expert name or FINISH.`;
const response = await model.invoke([
{ role: "system", content: systemPrompt },
...state.messages
]);
const next = (response.content as string).trim() as Member;
return { next };
}Step 3 – Assemble the Graph
// Helper to wrap a worker agent
async function callAgent(agentGraph, state) {
const result = await agentGraph.invoke({ messages: state.messages });
const lastMessage = result.messages[result.messages.length - 1];
return { messages: [lastMessage] };
}
const workflow = new StateGraph(SupervisorState)
.addNode("supervisor", supervisorNode)
.addNode("math_agent", state => callAgent(mathAgent, state))
.addNode("research_agent", state => callAgent(researchAgent, state))
.addConditionalEdges("supervisor", state => state.next, {
math_agent: "math_agent",
research_agent: "research_agent",
FINISH: END
})
.addEdge("math_agent", "supervisor")
.addEdge("research_agent", "supervisor")
.addEdge(START, "supervisor");
const app = workflow.compile();Step 4 – Run and Observe
async function main() {
const result = await app.invoke({
messages: [new HumanMessage("Search the total number of employees for FAANG companies in 2024 and compute the average per company.")]
});
console.log("Final answer:");
console.log(result.messages[result.messages.length - 1].content);
console.log("
Execution trace:");
result.messages.forEach((msg, i) => {
const name = (msg as any).name || msg.constructor.name;
console.log(`${i}: [${name}]`);
});
}
main();The execution trace shows the Supervisor first invoking research_agent to fetch employee numbers, then math_agent to calculate the average, and finally aggregating the result.
Advanced: Hierarchical Supervisors and Subgraph Architecture
When tasks involve more than a few Workers, a single‑level Supervisor becomes unwieldy. A hierarchical design introduces a top‑level Supervisor that routes to department‑level Supervisors, each managing its own subgraph and isolated state namespace.
User request
↓
┌─────────────┐
│ Top Supervisor │
└───────┬───────┘
│
┌────────────┴─────────────┐
↓ ↓
┌───────────────┐ ┌───────────────┐
│ Research Dept │ │ Analysis Dept │
│ Supervisor │ │ Supervisor │
└───────┬───────┘ └───────┬───────┘
│ │
┌───────┴───────┐ ┌───────┴───────┐
↓ ↓ ↓ ↓
[Web Search] [Academic Search] [Data Analysis] [Visualization]
Agent Agent Agent AgentIn LangGraph each department Supervisor is added as a node via addNode, preserving independent message histories.
Practical Pitfalls: Lessons Learned
Infinite Loop: The Supervisor keeps calling the same Worker because the prompt lacks a clear termination condition. Fix: Add explicit stop rules and set a recursion limit (e.g., 10).
Context Pollution: Workers return full message histories, diluting the Supervisor's context. Fix: Return only the final message and tag it for identification.
Worker Deadlock: Workers call each other, causing a lock. Fix: Explicitly forbid Workers from invoking other Agents in their prompts.
Mis‑routed Calls: Vague routing prompts lead to wrong Agent selection. Fix: Provide a detailed priority list in the routing prompt.
Missing Checkpointer: Without a checkpointer, long tasks lose intermediate state on failure. Fix: Attach a MemorySaver checkpointer to both the main graph and sub‑graphs.
Summary
Single‑Agent setups suffer from tool‑selection errors, context explosion, and debugging difficulty.
The Supervisor pattern separates routing (Supervisor) from execution (Workers), keeping contexts clean.
Tool‑Calling is suitable for 90 % of cases; Handoff is reserved for interactive scenarios.
A complete TypeScript example demonstrates definition, state design, routing, and graph compilation.
Hierarchical Supervisors enable complex, multi‑department workflows with isolated state.
Five common pitfalls are identified with concrete remediation strategies.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
James' Growth Diary
I am James, focusing on AI Agent learning and growth. I continuously update two series: “AI Agent Mastery Path,” which systematically outlines core theories and practices of agents, and “Claude Code Design Philosophy,” which deeply analyzes the design thinking behind top AI tools. Helping you build a solid foundation in the AI era.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
