Swarm
Hierarchical routing where one agent's tools are other agents. Triage routes to specialists; specialists may hand off back. The pattern is a Loop over Agent calls — no SwarmAgent class needed.
A user message arrives that could be billing OR technical OR a refund. You don't want one giant prompt that knows everything — that's where agents get vague and hallucinate. You want a triage agent that figures out which specialist should handle this, then hands off. The specialist focuses on its domain. That's Swarm — and it's just a Loop over
Agents with a route function.
What Swarm is
Swarm = a hierarchical agent pattern where one orchestrator routes each turn to a specialist agent. The route function is sync, pure, and returns the id of the next agent (or undefined to halt). Each handoff is one iteration of a Loop:
Loop {
current_agent = route(input)
if current_agent is undefined → exit
result = current_agent.run(input)
input = result + handoff annotation
}In agentfootprint there is no SwarmAgent class. Swarm is a recipe — swarm({ agents, route, maxHandoffs }) constructs a runnable that wraps a Loop. The 4-composition substrate (Sequence/Parallel/Conditional/Loop) is enough; we don't need a 5th primitive.
The recipe
const router = swarm({ agents: [ { id: 'triage', runner: triage }, { id: 'billing', runner: billing }, { id: 'tech', runner: tech }, ], // Route function — pure sync over the current message. First turn goes // to triage, then to billing or tech based on content, then halts. route: (input) => { const msg = input.message.toLowerCase(); if (msg.includes('[billing]')) return undefined; // billing done → halt if (msg.includes('[tech]')) return undefined; // tech done → halt if (msg.includes('refund') || msg.includes('bill')) return 'billing'; if (msg.includes('status') || msg.includes('error')) return 'tech'; return 'triage'; // first turn }, maxHandoffs: 5,});The route function is the orchestrator's brain. It inspects the current input (often the previous handoff's result) and decides which agent to dispatch next. Returning undefined halts the loop — the most recent specialist's output becomes the final answer.
maxHandoffs caps the loop so a buggy route function can't infinite-loop. Production: 3–5 handoffs covers most triage flows.
Why route is sync, not LLM-driven
You CAN make route call an LLM internally — it's just a function. But the standard pattern is a deterministic route based on the previous specialist's output (often via convention markers like [BILLING] or [ESCALATE] in the message). That keeps the routing layer cheap, fast, and debuggable. The intelligence lives in the specialists; the routing is mechanical dispatch.
If you need LLM-driven routing, use Conditional with an LLM-based predicate — different shape, same outcome.
Multi-tenant identity
A swarm runner's input is { message: string } — and that single message is the only thing that flows from one specialist to the next (each agent's string output becomes the next iteration's { message }). swarm() does not carry a MemoryIdentity through the handoff chain for you.
If specialists need per-tenant memory, give each agent its identity directly. Agent.run({ message, identity }) accepts an optional MemoryIdentity on its input; bind the tenant scope when you build/run each specialist (e.g. via a closure that injects it), so memory reads/writes stay isolated even though the swarm only relays the message.
When to use Swarm vs Conditional vs Sequence
| Situation | Use |
|---|---|
| One classifier picks ONE specialist for the whole turn | Conditional |
| Specialists may hand back to triage or to each other | Swarm (this guide) |
| Fixed pipeline: research → write → edit (no branching) | Sequence |
| Multi-perspective fan-out → merge | Parallel |
Anti-patterns
- Don't put expensive logic in the route function. It runs every iteration. Keep it as cheap as text inspection.
- Don't share state between specialists via globals. Use
MemoryIdentity+ memory layer for cross-agent state. - Don't omit
maxHandoffs. A buggy route returning the same agent every time runs forever.
Next steps
- Patterns overview — Reflexion, ToT, Debate, Map-Reduce all run the same way (recipes over the substrate)
- Conditional — single-shot routing alternative
Dynamic ReAct
Context that adapts mid-loop. The on-tool-return trigger fires the iteration after a specific tool ran — recency-first injection without hand-rolled state.
Instructions
Rule-gated context injection. The Instruction primitive activates a prompt when a predicate matches the current iteration's context.
