LangGraph Supervisor Tutorial: Build a Multi-Agent System
A LangGraph supervisor tutorial should begin with a simple idea: a useful multi-agent system is not a group of bots talking at random. It is a workflow with a clear decision-maker. One component owns the conversation, chooses the next piece of work, and decides when the answer is ready. In this tutorial, you will build that decision-maker—a supervisor—for a small research-and-writing assistant.
This is the second article in the LangGraph multi-agent systems series. Part 1 introduced stateful conversational agents. Here, we add specialist workers without losing control of the overall task.
What a LangGraph supervisor tutorial teaches
A supervisor is the agent the user talks to. It delegates bounded tasks to specialists, receives concise results, and remains accountable for the final response. The specialists do not independently take over the conversation.
This pattern is useful when a request crosses genuinely separate domains, when a worker needs focused instructions or tools, or when independent work can run in parallel. It is not the default answer to every complicated prompt: if one agent with a few well-described tools can do the job, keep the design simpler.
The architecture we are building
Our example has three roles:
- Supervisor: understands the request, selects a worker, and synthesises the final answer.
- Research worker: gathers factual material and returns a short, source-aware brief.
- Writing worker: turns an approved brief into a structured draft.
The key boundary is deliberate: workers receive a task, not the entire conversation by default, and they return a useful result, not their full message history. That keeps the supervisor’s context focused and makes each worker easier to test.

Prerequisites
- Python 3.10 or later
- A tool-calling chat model configured for LangChain
langchain, a model integration such aslangchain-openai, andlanggraph
pip install -U langchain langchain-openai langgraph
The example uses LangChain’s current create_agent helper for agent loops. It produces a LangGraph graph underneath, so the system can later add checkpoints, interrupts, or more explicit graph control.
1. Define narrow workers
Start by making workers boringly specific. Their prompts should say what they own, what they must return, and what they must not do. The workers below have no direct user-facing role.
from langchain.agents import create_agent
from langchain.tools import tool
MODEL = "openai:gpt-5" # Replace with a tool-calling model available to you.
research_agent = create_agent(
model=MODEL,
tools=[],
system_prompt=(
"You are a research specialist. Return a concise factual brief. "
"State important uncertainty. Do not write the final response."
),
)
writer_agent = create_agent(
model=MODEL,
tools=[],
system_prompt=(
"You are a technical writing specialist. Turn the supplied brief "
"into a clear draft. Do not claim facts that are not in the brief."
),
)
In production, the research worker would receive carefully chosen search, documentation, or database tools. The point is not the number of workers. It is the contract between each worker and the supervisor.
2. Expose workers as supervisor tools
The supervisor calls workers as tools. Each wrapper creates a clean task for the worker and returns only its final message. This prevents intermediate worker turns from expanding the main conversation.
@tool("research_topic")
def research_topic(query: str) -> str:
"""Research a topic and return a concise factual brief."""
result = research_agent.invoke({
"messages": [{"role": "user", "content": query}]
})
return result["messages"][-1].content
@tool("draft_from_brief")
def draft_from_brief(brief: str) -> str:
"""Write a technical draft from an approved research brief."""
result = writer_agent.invoke({
"messages": [{"role": "user", "content": brief}]
})
return result["messages"][-1].content
Tool names and descriptions matter. They are the supervisor’s working vocabulary. A vague tool called do_work makes delegation ambiguous; research_topic states the capability and expected input.
3. Create the supervisor
Give one main agent the worker tools. The instruction makes control flow explicit: research before drafting, ask for clarification when needed, and never expose internal delegation as a substitute for an answer.
supervisor = create_agent(
model=MODEL,
tools=[research_topic, draft_from_brief],
system_prompt=(
"You are the supervisor for a technical research assistant. "
"You own the conversation and final answer. "
"For requests requiring factual research, call research_topic first. "
"Call draft_from_brief only after you have a sufficient brief. "
"Keep worker outputs concise, check missing assumptions, "
"and give the user a direct final response."
),
)
result = supervisor.invoke({
"messages": [{
"role": "user",
"content": "Explain when a team should use a supervisor pattern."
}]
})
print(result["messages"][-1].content)
A supervisor is different from a simple router. A router usually classifies a request once and dispatches it. A supervisor can use the evolving conversation and worker results to decide what should happen next across several steps.
Control what crosses the boundary
Context engineering is the practical heart of this LangGraph supervisor tutorial. Passing the full chat history to every worker is easy, but erodes the benefit of specialisation. Pass the smallest context that lets the worker succeed.
| Worker | Give it | Return |
|---|---|---|
| Research | Question, scope, source rules | Findings, sources, uncertainty |
| Writing | Approved brief, audience, format | A draft, not research history |
| Supervisor | User conversation and worker summaries | The final answer and next action |
Guardrails to add before this becomes a product
- Bound the loop: set a maximum number of worker calls per request.
- Validate inputs: use typed tool parameters and reject unknown worker names.
- Make failure visible: return an error state or request clarification; do not invent a worker result.
- Trace delegation: record which worker ran, the task it received, duration, and its summary.
- Start synchronously: use background jobs only when work is independent and the user does not need it immediately.
Common mistakes
Making every worker a generalist. If all workers have the same prompt and tools, the system adds coordination cost without useful separation.
Letting workers answer the user directly. That is a handoff pattern, not a supervisor pattern. Use it only when the active agent should genuinely change.
Returning full histories. It creates context bloat. Return a compact, structured summary unless the supervisor needs more.
Using an LLM for rules code can enforce. Budget limits, permissions, retries, and stop conditions should be deterministic.
Where the series goes next
You now have a coordinator and two bounded workers. Next, we will make the data flow explicit: shared state, worker-specific context, and the difference between passing a summary and sharing mutable conversation history.