Multi-Agentmulti-agentswarmhandoffpeerdecentralised

Swarm / Peer Pattern

Peer agents hand a conversation and its context to whichever agent has the right capability, with no central controller — flexible for routing-heavy dialogues, risky without hop limits.

Interview question
Progress

Mechanism: the hand-off

In a swarm each agent has a normal tool set plus hand-off tools: transfer_to_billing(), transfer_to_refunds(). Calling one does not return a result; it changes which agent runs the next turn. The conversation history (or a filtered slice of it) and a small context object travel with it.

This makes swarms natural for customer-support style flows where the active specialist changes as the conversation reveals what the user needs. Triage hands to billing; billing discovers a refund is required and hands to refunds; refunds resolves and hands back to triage for anything else.

Hand-offs in a support swarm
transfertransfertransferescalateUserHop limit 5TriageBillingRefundsHuman agent
UserLLMAgentToolDataDecisionHumanGuardrail

What travels and what does not

Decide explicitly what a hand-off carries. Full history is simple but leaks irrelevant detail and grows without bound. A context object ({customer_id, order_id, verified: true}) plus the last N turns is usually enough and keeps each agent's prompt small. Tool sets do not travel: refunds has issue_refund, triage does not, which is where the security benefit comes from (Tool Permissions and Least Privilege).

Also decide who may hand to whom. A fully connected graph of 8 agents has 56 possible transfers, most of them nonsensical. Restrict edges to the real routing graph and the model has fewer wrong choices.

A hand-off as a tool that switches the active agent
1class Handoff:
2 def __init__(self, to: str): self.to = to
3
4def transfer_to_refunds(reason: str) -> Handoff:
5 """Hand the conversation to the refunds specialist. Use only after the customer is verified."""
6 return Handoff("refunds")
7
8def run_swarm(agents: dict, start: str, msgs: list, ctx: dict, max_hops: int = 5):
9 active, hops = start, 0
10 while True:
11 out = agents[active].step(msgs, ctx) # returns text or a Handoff
12 if isinstance(out, Handoff):
13 hops += 1
14 if hops > max_hops:
15 return escalate_to_human(msgs, ctx) # termination guard
16 active = out.to
17 continue
18 return out

Risks specific to swarms

There is no single place that knows the plan, so ping-pong hand-offs, dropped tasks and duplicated actions are all easy. Every swarm needs a hop budget, an idempotency key for side effects that may be attempted by two agents (Idempotency), and a trace that records the agent name on every span so you can reconstruct who did what (Trace Inspection: Debugging from a Trace).

Evaluation is also harder: the "right" path through the swarm is not unique. Evaluate on outcomes plus path constraints (e.g. "refund never issued before verification") rather than on exact hand-off sequences.

Key points

  • A hand-off is a tool call that switches the active agent instead of returning data.
  • Carry a small context object and recent turns, not the full history.
  • Tool sets stay with agents — that is the permission boundary.
  • Restrict the hand-off graph; add a hop limit and an escalation exit.
  • Evaluate on outcomes and invariants, not exact paths.

When to use — and when not to

Use it when
  • Conversational flows where the needed specialist changes mid-dialogue.
  • Specialists need very different permission levels.
  • Routing decisions are local and do not need a global plan.
Avoid it when
  • Tasks with a fixed order — a pipeline is simpler and traceable.
  • Work requiring a global plan or merge step — use a supervisor.
  • Side-effect-heavy tasks where duplicated actions are costly.

Failure modes

  • Infinite transfer loops between two agents.
  • Task silently dropped at a hand-off because context did not carry it.
  • Two agents both perform the side effect.
  • Traces without agent identity make incidents unreconstructable.

Tradeoffs

Complexity
low → high
Latency
low → high
Cost
low → high
Reliability
poor → strong
Debuggability
hard → easy