Everyday Data Science
Latest
Agentic workflows now power a third of surveyed enterprise automationAfrica's AI startup ecosystem posts record funding yearNew benchmark results reshape the coding-agent leaderboardNigeria launches national AI strategy with major investment planRwanda's sovereign AI cloud enters public betaThe future of AI agents: from tools to teammates
Agentic AITutorial

Building Multi-Agent Pipelines with LangGraph: A Practical Guide

LangChain now recommends against the supervisor library most tutorials teach, here's what to build instead, and the benchmarks for choosing

IDIbrahim Denis FofanahData Scientist & AI Researcher6 min read·Code-Along · LangGraph · Multi-Agent
Part 4 of 5Building Agentic AI SystemsView path →

If you learned to build multi-agent systems in LangGraph from a tutorial, you were probably taught create_supervisor from the langgraph-supervisor library. Go and read that library's README today and you'll find this at the very top:

We now recommend using the supervisor pattern directly via tools rather than this library for most use cases.

That's not a random blog opinion. That's LangChain, on their own repo, steering you somewhere else. Here's what changed, what to build instead, and, the part almost nobody covers, the published numbers on which pattern actually costs less.

Why the supervisor library fell out of favour

The old approach handed you a black box. You passed in a list of agents, it generated handoff tools, and it wired the graph for you. Convenient, until you needed to control what each agent actually sees.

And that turns out to be the whole game. LangChain's guidance is blunt about it:

At the center of multi-agent design is context engineering, deciding what information each agent sees.

A generated handoff tool passes the full message history to the next agent by default. On a long conversation that's a lot of tokens, most of them irrelevant to the specialist you're calling. The tool-calling approach gives you that control back, and it's just Python, no framework magic to fight.

The five patterns

Multi-agent isn't one thing. LangChain now documents five distinct architectures:

Pattern How it works
Subagents A main agent calls subagents as tools. All routing goes through it. This is the supervisor.
Handoffs Agents transfer control to each other. The active agent persists across turns.
Skills One agent stays in control, loading specialized prompts and knowledge on demand.
Router A classification step dispatches to specialists, then synthesizes the results.
Custom workflow Bespoke graphs mixing deterministic logic with agentic steps.

Most people reach for a supervisor because it's the one they've seen. It's often not the cheapest.

Building the supervisor properly

The mechanism is almost disappointingly simple: a subagent is just an agent, wrapped in a tool.

from langchain.tools import tool
from langchain.agents import create_agent
 
# 1. A specialist agent
research_agent = create_agent(model="...", tools=[web_search])
 
# 2. Wrap it as a tool the main agent can call
@tool("research", description="Research a topic and return findings")
def call_research_agent(query: str):
    result = research_agent.invoke({"messages": [{"role": "user", "content": query}]})
    return result["messages"][-1].content
 
# 3. The supervisor is just an agent whose tools happen to be agents
main_agent = create_agent(model="...", tools=[call_research_agent])

That's it. No supervisor class, no handoff machinery. The main agent sees a tool called research, decides when it's relevant, calls it, and gets a string back.

Because the subagent runs in its own context window, the main conversation never fills up with the specialist's intermediate reasoning. That isolation is the real prize, and it's why this pattern wins on token cost when you have multiple domains.

Scaling past a handful of agents

One tool per agent gets unwieldy fast. The alternative is a single dispatch tool over a registry:

SUBAGENTS = {
    "research": research_agent,
    "writer": writer_agent,
}
 
@tool
def task(agent_name: str, description: str) -> str:
    """Launch an ephemeral subagent for a task.
 
    Available agents:
    - research: Research and fact-finding
    - writer: Content creation and editing
    """
    agent = SUBAGENTS[agent_name]
    result = agent.invoke({"messages": [{"role": "user", "content": description}]})
    return result["messages"][-1].content
 
main_agent = create_agent(
    model="...",
    tools=[task],
    system_prompt=(
        "You coordinate specialized sub-agents. "
        "Available: research (fact-finding), writer (content creation). "
        "Use the task tool to delegate work."
    ),
)

Now adding an agent means adding a dictionary entry. Different teams can ship agents independently without touching the coordinator.

The three decisions that actually determine whether it works

Everything above is plumbing. These three are where systems succeed or quietly fail.

1. Names and descriptions are your routing logic. The main agent has nothing else to go on. research_agent, "researches topics using web search; use for current events, not math" routes correctly. agent_2, "does stuff" does not. These are prompt engineering, not naming conventions.

2. Inputs decide what the specialist knows. By default your subagent gets only the query string. If it needs the conversation so far, pass it explicitly, and pass only what helps. This is the control the old library took away from you.

3. Outputs decide whether the supervisor can act.

Fix it in the prompt: tell the subagent explicitly that its final message is the only thing the supervisor receives, so the answer must be in it.

Which pattern is actually cheapest?

This is the part almost no tutorial gives you. LangChain publishes real comparisons:

Pattern One-shot Repeat request Multi-domain (3 topics)
Subagents 4 calls 8 calls 5 calls · ~9K tokens
Handoffs 3 calls 5 calls 7+ calls · ~14K tokens
Skills 3 calls 5 calls 3 calls · ~15K tokens
Router 3 calls 6 calls 5 calls · ~9K tokens

Three things fall out of that table:

Subagents cost one extra call on simple tasks. Results flow back through the main agent. You're paying that call for centralized control, fine, as long as you know you're paying it.

Stateful patterns win on repetition. Handoffs and Skills save 40–50% of calls on a repeated request, because the right agent (or context) is already active. Subagents are stateless by design and re-run the whole flow every time.

But context isolation wins on breadth. Ask about three domains at once and Skills balloons to ~15K tokens, every subsequent call re-processes all that loaded documentation. Subagents handles it in ~9K, roughly 67% fewer tokens, because each specialist works in a clean window. Handoffs is worst here: it runs sequentially and can't fan out in parallel at all.

So: few domains, repeated interactions → Handoffs or Skills. Many domains, parallel work, large contexts → Subagents or Router.

app.invoke(inputs, config={"recursion_limit": 10})

Two lines. Do it now, not after the invoice.

Related on Everyday Data Science: The agent revolution is here, and most organizations are not ready, on what separates the teams that get agents into production.

Key takeaways

  1. Don't reach for create_supervisor. LangChain now recommends the tool-based approach, a subagent is just an agent wrapped in a @tool.
  2. Context engineering is the design. Names, inputs, and outputs decide whether the system works; the graph wiring rarely does.
  3. Tell subagents their final message is all the supervisor sees. This one line of prompt prevents the most common silent failure.
  4. Choose the pattern from your access shape, repeated single-domain work and broad multi-domain work want opposite architectures.
  5. Cap the recursion. Always.

Which multi-agent pattern are you running in production, and did you pick it deliberately, or because it was the one in the tutorial? Hit the comments 👇


Sources: LangChain multi-agent docs, Subagents pattern, langgraph-supervisor README. Benchmarks are LangChain's published figures; your numbers will vary by model and workload.

Share

Found this useful? Passing it on to someone who builds is the best way to help the publication grow.