tl;dr. a swarm removes the router: agents hand off to each other directly via handoff tools, and "who is active" is just another state field. fewer hops, better momentum on specialist-to-specialist work — paid for with control flow that's distributed across every agent's prompt. use it for peer collaboration; keep the supervisor for dispatch.
delete the hub
// supervisor (lesson 4): swarm (this lesson):
//
// ┌──────┐ ┌─────────┐
// │ hub │ │ billing │ ◄──┐
// ╱ │ ╲ └────┬────┘ │
// ▼ ▼ ▼ ▼ │
// ┌────┐ ┌───┐ ┌────┐ ┌─────────┐ │
// │ a │ │ b │ │ c │ │ tech │ ───┘
// └────┘ └───┘ └────┘ └────┬────┘
// every hop through the ▼
// hub: route, work, ┌─────────┐
// report, route again │ orders │
// └─────────┘
// agents hand off directly;
// "who's active" lives in state the supervisor's cost is structural: every exchange is worker → hub → worker, twice the model calls and a full re-route even when the right next step is obvious to the agent doing the work. billing knows this ticket became a tech problem halfway through the second tool call. a swarm lets billing act on that knowledge — hand the conversation sideways, with context, without asking permission from a router.
a handoff is a tool
import { tool } from "@langchain/core/tools";
import { Command } from "@langchain/langgraph";
import { z } from "zod";
// a handoff is a tool the agent can call — transfer as an action
// the model chooses, not a decision a router makes about it.
function makeHandoff(target: string, when: string) {
return tool(
async ({ context }, config) => {
return new Command({
goto: target, // jump to the peer...
graph: Command.PARENT, // ...in the parent graph
update: {
activeAgent: target,
messages: [toolMsg(config, `→ ${target}: ${context}`)],
},
});
},
{
name: `transfer_to_${target}`,
description: `hand this conversation to ${target}. ${when}`,
schema: z.object({
context: z.string()
.describe("one paragraph: what's done, what's needed next"),
}),
},
);
}
const billing = createAgent({
model: fast,
tools: [
...billingTools,
makeHandoff("tech", "use when the issue is a product bug, not an invoice."),
],
systemPrompt: "you handle invoices. hand off anything that isn't one.",
}); - the model chooses to transfer. a handoff competes with ordinary tools in the same decision. that makes the tool's description your routing policy — the "use when" sentence is doing the job the supervisor prompt did.
- Command.PARENT jumps graphs.
the react agent is a subgraph; the peer it's transferring to lives
in the parent.
graph: Command.PARENTis the escape hatch that makes a tool inside one agent able to route the team. - the context argument is the payload. "what's done, what's needed next" — forced through the schema, so a transfer can't arrive naked. thin payloads are how "the agent got confused" bugs happen; the schema is where you refuse them.
- activeAgent is state. with a checkpointer, a thread that resumes tomorrow resumes with the agent that was handling it — continuity the supervisor pattern has to rebuild by re-routing from scratch.
import { createSwarm, createHandoffTool } from "@langchain/langgraph-swarm";
// the packaged version: agents + a default entry point.
// activeAgent is tracked in state, so a resumed thread
// continues with whoever was talking — not back at triage.
const swarm = createSwarm({
agents: [billing, tech, orders],
defaultActiveAgent: "billing",
}).compile({ checkpointer }); picking between the two
after building both a few times, my dividing line: supervisors dispatch, swarms collaborate. when work arrives as discrete units that need classifying and assigning — tickets, issues, queue items — central routing is a feature: one place to read the policy, one place to fix it. when the work is one continuous thing passing between specialists — a support conversation drifting from invoice to bug, a document moving between writer and fact-checker — the hub is pure overhead, and the swarm's momentum wins. the sdlc pipeline in lesson eight uses both: supervisor-style dispatch at the top, swarm-style peer flow inside a team. they compose; you don't have to convert.
when it breaks
- hot-potato loops. billing thinks it's a tech issue, tech thinks it's a billing issue, and the ticket ping-pongs. nobody's watching, because removing the watcher was the point. cap handoffs in state and escalate to a human gate when the count trips.
- policy smeared across prompts.
routing lives in n handoff-tool descriptions across n agents.
change the team's shape and you're editing prompts everywhere.
generate handoff tools from one declarative table — the
makeHandofffactory above is the start of that. - transfers without context.
a model will happily call
transfer_to_techwith an empty paragraph if you let it. minimum lengths and required fields on the schema aren't pedantry — the receiving agent's first question is the payload.
next: subgraphs and fan-out — teams as black boxes, and the other kind of swarm: many workers at once.