Swarm
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
Section titled “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
Section titled “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
Section titled “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 propagates
Section titled “Multi-tenant identity propagates”Every specialist’s call inherits the orchestrator’s identity automatically. Memory, observability, cost tracking — all scoped per-tenant across the entire handoff chain.
When to use Swarm vs Conditional vs Sequence
Section titled “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
Section titled “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
Section titled “Next steps”- Patterns overview — Reflexion, ToT, Debate, Map-Reduce all run the same way (recipes over the substrate)
- Conditional — single-shot routing alternative