---
title: Swarm
description: 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.
---

import CodeFile from '@/components/CodeFile.astro';

> 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 `Agent`s 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

<CodeFile path="examples/patterns/06-swarm.ts" region="swarm-recipe" title="examples/patterns/06-swarm.ts (region: swarm-recipe)" />

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](/agentfootprint/getting-started/key-concepts/#3-conditional--branch-on-a-decider) 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](/agentfootprint/getting-started/key-concepts/#patterns--named-compositions) — Reflexion, ToT, Debate, Map-Reduce all run the same way (recipes over the substrate)
- [Conditional](/agentfootprint/getting-started/key-concepts/#3-conditional--branch-on-a-decider) — single-shot routing alternative
