Agentic AI from Scratch, Part 2: Multi-Agent Orchestration
Supervisors, handoffs, and shared state across a team of agents
One agent with ten tools becomes a confused generalist. Past a certain complexity, you get better results by splitting the work across focused agents (each with a tight toolset and a clear job) and coordinating them. This is Part 2 of Agentic AI from Scratch; Part 1 built the single agent we now multiply.
Reach for multi-agent only when a single agent's tool list or prompt has grown unwieldy. More agents means more coordination overhead, don't pay it prematurely.
The Supervisor Pattern#
The most reliable architecture is a supervisor: one router agent decides which specialist handles the current step, the specialist works, control returns to the supervisor, and it repeats until done.
┌─────────────┐
user ──> │ Supervisor │ <──────────────┐
└──────┬──────┘ │
┌──────────┼──────────┐ │
v v v │
┌────────┐ ┌────────┐ ┌─────────┐ │
│Research│ │ Coder │ │ Writer │ ──────┘
└────────┘ └────────┘ └─────────┘Shared State Across Agents#
All agents read and write one state object. The supervisor adds a next field to record its routing decision.
from typing import Annotated, Literal, TypedDict
from langgraph.graph import add_messages
Specialist = Literal["research", "coder", "writer", "FINISH"]
class TeamState(TypedDict):
messages: Annotated[list, add_messages]
next: SpecialistThe Router#
The supervisor is just a model with a structured-output contract: given the conversation, name the next agent.
from pydantic import BaseModel
class Route(BaseModel):
next: Specialist
def supervisor(state: TeamState):
decision = router_model.with_structured_output(Route).invoke([
("system", SUPERVISOR_PROMPT),
*state["messages"],
])
return {"next": decision.next}Forcing structured output (with_structured_output) is what keeps routing reliable, the supervisor can't ramble, it must return a valid agent name.
Wiring the Graph#
Each specialist node does its work and routes back to the supervisor. The supervisor's conditional edge sends control to the chosen agent or ends.
from langgraph.graph import StateGraph, START, END
graph = StateGraph(TeamState)
graph.add_node("supervisor", supervisor)
graph.add_node("research", research_agent)
graph.add_node("coder", coder_agent)
graph.add_node("writer", writer_agent)
graph.add_edge(START, "supervisor")
for name in ["research", "coder", "writer"]:
graph.add_edge(name, "supervisor") # always report back
graph.add_conditional_edges(
"supervisor",
lambda s: s["next"],
{"research": "research", "coder": "coder", "writer": "writer", "FINISH": END},
)
team = graph.compile()Handoffs vs. Supervision#
There are two coordination styles, and they suit different problems:
| Style | How it works | Best for |
|---|---|---|
| Supervisor | Central router delegates each step | Clear task decomposition, auditability |
| Handoff (swarm) | Agents transfer control directly to each other | Fluid, conversational workflows |
Start with a supervisor. It's easier to debug because every decision flows through one place you can log.
Multi-agent systems fail in new ways: infinite ping-pong between two agents, a supervisor that never says FINISH, state that one agent silently overwrites. Bound total steps, log every routing decision, and trace runs from day one.
Observability Is Not Optional#
When three models are talking, "it gave a weird answer" is undebuggable without traces. Instrument every node (inputs, outputs, routing decisions) with a tool like LangSmith. The graph structure makes this natural: each node is a span.
team.invoke(
{"messages": [("user", "Research and summarize the latest on vector DB recall.")]},
config={"configurable": {"thread_id": "run-42"}, "recursion_limit": 25},
)Note the recursion_limit, the multi-agent equivalent of the loop cap from Part 1, and just as essential.
Wrapping the Series#
You've gone from a single tool-using loop to a coordinated team, all as explicit LangGraph state machines. The lesson across both parts: agents are control flow you design, not magic you summon. Keep state explicit, bound your loops, and trace everything, and "agentic" stops being scary and starts being engineering.
Folarin Akinloye is an AI Engineer based in London, UK. He builds production-ready agentic AI systems, multi-agent architectures, and sophisticated RAG implementations, and writes about the engineering decisions behind them.