By Deepti Reddy | April 2026
This is Part 4 of an 8-part series covering every multi-agent strategy in AgentSpan. Today: the swarm strategy agents hand off to each other using transfer tools. No manager and no classifier. Peer-to-peer.
— -
In Part 1, we built a sequential pipeline, agents in a fixed order. In Part 2, we built a parallel code review agents running simultaneously. In Part 3, we built a handoff triage bot where the parent LLM decides the route.
Every strategy so far has been top-down. A parent agent, a classifier, or a fixed pipeline decides which agent runs. The agents themselves never talk to each other. The bug handler in Part 3 never said “actually, this should go to the docs handler.”
That is the swarm strategy. There is no parent deciding. Instead, every agent gets auto-generated transfer_to_ tools. The refund specialist can transfer to tech support. Tech support can transfer back to the refund specialist. They decide among themselves — peer-to-peer.
What we are building
A customer support system with three agents that can transfer between each other:
Front-line Support: triages incoming requests, looks up orders
Refund Specialist: checks eligibility, processes refunds
Tech Support: checks warranty, creates warranty claims
The front-line agent gets the initial message. When it recognizes the issue, it transfers to the right specialist. But here is the key difference from handoff: the specialists can transfer to each other. If a customer asks for a refund but the issue is actually a product defect, the refund specialist transfers to tech support — no need to go back to the front-line agent first.
Customer → [Front-line] → transfer_to_refund_specialist()
↓
[Refund Specialist] → “this is a defect, not a return”
↓
transfer_to_tech_support()
↓
[Tech Support] → warranty claim created
How is this different from handoff and router?
In handoff (Part 3), the parent agent’s LLM decides everything. Sub-agents are tools the parent calls. Once the bug handler runs, it cannot say “actually, send this to the docs handler”, it finishes and control returns to the parent.
In router, a dedicated classifier picks one specialist. Same limitation: once the specialist runs, it cannot redirect.
In swarm, every agent has transfer tools for every other agent. The LLM inside each agent can decide to transfer at any point during its own execution. There is no parent in the loop.
Setup
Same as Parts 1 through 3:
pip install agentspan
agentspan server start
Defining the specialists
Each specialist has tools for its domain:
from agentspan.agents import Agent, AgentRuntime, Strategy, tool
from agentspan.agents.handoff import OnTextMention
@tool
def lookup_order(order_id: str) -> dict:
“””Look up order details and status.”””
return {
“order_id”: order_id,
“status”: “delivered”,
“item”: “Wireless Headphones (Model WH-1000)”,
“amount”: 249.99,
}
@tool
def check_refund_eligibility(order_id: str) -> dict:
“””Check if an order is eligible for a refund.”””
return {
“order_id”: order_id,
“eligible”: True,
“refund_amount”: 249.99,
“processing_time”: “3–5 business days”,
}
@tool
def process_refund(order_id: str, amount: float, reason: str) -> dict:
“””Process a refund for an order.”””
return {
“refund_id”: “RF-88431”,
“amount”: amount,
“status”: “processed”,
}
@tool
def check_warranty(order_id: str) -> dict:
“””Check warranty status for a product.”””
return {
“order_id”: order_id,
“warranty_status”: “active”,
“warranty_expiry”: “2027–04–10”,
“claim_options”: [“replacement”, “repair”],
}
@tool
def create_warranty_claim(order_id: str, issue: str) -> dict:
“””Create a warranty claim.”””
return {
“claim_id”: “WC-55102”,
“next_step”: “Prepaid shipping label via email within 24 hours”,
}
Now the agents. Notice the instructions — each specialist knows when to transfer to the other:
refund_specialist = Agent(
name=”refund_specialist”,
model=”openai/gpt-4o”,
instructions=(
“You are a refund specialist. Handle refund requests.\n\n”
“1. Use check_refund_eligibility to verify the order qualifies.\n”
“2. If eligible, use process_refund to issue the refund.\n”
“3. Confirm the refund amount, method, and timeline.\n\n”
“If the customer’s issue is actually a product defect (not a return), “
“transfer to tech_support — they handle warranty claims.”
),
tools=[check_refund_eligibility, process_refund],
)
tech_support = Agent(
name=”tech_support”,
model=”openai/gpt-4o”,
instructions=(
“You are technical support. Handle product issues and warranty claims.\n\n”
“1. Use check_warranty to verify warranty status.\n”
“2. If under warranty, use create_warranty_claim.\n”
“3. Explain next steps to the customer.\n\n”
“If the customer just wants their money back (not a replacement/repair), “
“transfer to refund_specialist.”
),
tools=[check_warranty, create_warranty_claim],
)
The refund specialist knows: “if this is a defect, transfer to tech_support.” Tech support knows: “if they just want money back, transfer to refund_specialist.” They can bounce the customer between themselves — no parent agent involved.
The swarm strategy
support = Agent(
name=”support”,
model=”openai/gpt-4o”,
instructions=(
“You are front-line customer support. Triage the request.\n”
“- Refund or return → transfer to refund_specialist\n”
“- Product issue or defect → transfer to tech_support\n”
“Do NOT handle refunds or technical issues yourself.”
),
agents=[refund_specialist, tech_support],
strategy=Strategy.SWARM,
tools=[lookup_order],
handoffs=[
OnTextMention(text=”refund”, target=”refund_specialist”),
OnTextMention(text=”defect”, target=”tech_support”),
],
max_turns=5,
)
Two things are happening here:
Auto-generated transfer tools (primary mechanism). When you set strategy=Strategy.SWARM, AgentSpan automatically creates transfer_to_refund_specialist() and transfer_to_tech_support() as callable tools on the front-line agent. The LLM sees these tools and decides when to call them. Each specialist also gets transfer tools for every other agent, so refund_specialist gets transfer_to_tech_support() and vice versa.
OnTextMention fallback conditions (optional safety net). The handoffs= list provides fallback rules: if the agent’s response mentions “refund” but it did not call a transfer tool, the condition triggers and forces the handoff. These are optional — you can omit handoffs= entirely and rely purely on the auto-generated transfer tools.
The transfer tools are the core mechanism. The OnTextMention conditions are belt-and-suspenders.
Compare all five strategies:
# Sequential — fixed order, all run
pipeline = a >> b >> c
# Parallel — all run at once
team = Agent(agents=[a, b, c], strategy=Strategy.PARALLEL)
# Handoff — parent LLM picks one
triage = Agent(agents=[a, b, c], strategy=Strategy.HANDOFF)
# Router — separate classifier picks one
triage = Agent(agents=[a, b, c], strategy=Strategy.ROUTER, router=classifier)
# Swarm — agents transfer between each other
team = Agent(agents=[a, b, c], strategy=Strategy.SWARM)
Same Agent class. Different strategy. Different behavior.
Running it
with AgentRuntime() as runtime:
result = runtime.run(
support,
“I bought wireless headphones (order ORD-7821) last week and “
“the left ear cup stopped working after 3 days. I want my money back.”,
)
result.print_result()
This message is interesting because it has two intents: a product defect (left ear cup stopped working) AND a refund request (I want my money back). Watch what happens.
The flow
Front-line reads the message, sees “money back” → calls transfer_to_refund_specialist()
Refund specialist reads the full context, sees “stopped working after 3 days” → recognizes this is a defect, not a simple return → calls transfer_to_tech_support()
Tech support checks warranty (active until 2027), creates a warranty claim, offers replacement or repair options
That re-routing refund specialist deciding “actually, this is a tech issue” and transferring — is the thing that handoff and router cannot do. In those strategies, once a specialist runs, it finishes. In a swarm, agents redirect mid-conversation.
The transfer tool mechanism
When you set strategy=Strategy.SWARM, AgentSpan auto-generates transfer tools. You do not define them. They appear as regular tools that the LLM can call.
For the front-line agent:
Available tools:
— lookup_order(order_id)
— transfer_to_refund_specialist() ← auto-generated
— transfer_to_tech_support() ← auto-generated
For the refund specialist:
Available tools:
— check_refund_eligibility(order_id)
— process_refund(order_id, amount, reason)
— transfer_to_tech_support() ← auto-generated
— transfer_to_support() ← auto-generated (back to front-line)
Every agent can reach every other agent. The LLM decides when — based on its instructions and the conversation context.
OnTextMention: the fallback
The handoffs= conditions are optional. They exist as a safety net:
handoffs=[
OnTextMention(text=”refund”, target=”refund_specialist”),
OnTextMention(text=”defect”, target=”tech_support”),
]
The evaluation order is:
Did the LLM call a transfer tool? → Yes → use that transfer. Done.
No transfer tool called? → Check OnTextMention conditions against the agent’s response text.
Condition matches? → Force the handoff.
Nothing matches? → No transfer. Agent’s turn ends.
The transfer tools are priority 1. The text conditions are priority 2. You can omit handoffs= entirely and the swarm still works — the LLM-driven transfers are the primary mechanism.
When to use swarm vs handoff vs router
Handoff
Use when: You want a parent agent to delegate.
Pattern: Top-down. Parent picks a child, child runs, control returns.
Example: Issue triage — read once, route once.
Router
Use when: Classification is straightforward and you want cost control.
Pattern: Top-down. Classifier picks, specialist runs.
Example: Ticket categorization at scale — cheap model classifies.
Swarm
Use when: Conversations evolve and agents need to redirect.
Pattern: Peer-to-peer. Any agent can transfer to any other.
Example: Support conversations where the initial classification is wrong.
The mental model: handoff and router are dispatchers, they pick once. Swarm is a team conversation as agents pass the baton as the situation evolves.
How durability works
Same as every previous part. The swarm compiles into a durable server-side workflow. If your process crashes after the refund specialist transfers to tech support but before tech support finishes:
The transfer decision is persisted on the server.
You restart your script.
Tech support resumes from where it was, no re-triage, no re-transfer.
Every transfer is a durable state transition on the server.
Composability
Swarm composes with other strategies:
# Swarm handles the conversation, then a summarizer wraps up
team = Agent(
agents=[support, refund_specialist, tech_support],
strategy=Strategy.SWARM,
)
pipeline = team >> summarizer
Or use a swarm inside a larger handoff:
# Handoff decides: simple question → single agent, complex issue → swarm team
support_swarm = Agent(
agents=[refund_specialist, tech_support],
strategy=Strategy.SWARM,
)
router = Agent(
agents=[faq_agent, support_swarm],
strategy=Strategy.HANDOFF,
)
Same Agent class. Strategies nest naturally.
Try it
pip install agentspan
agentspan server start
python 05_support_swarm.py
Blog examples: github.com/agentspan-ai/agentspan/tree/main/sdk/python/examples/blog_and_videos/swarm
Docs: agentspan.ai/docs
What’s next
Part 5: Round Robin — Agents take turns in a fixed rotation. No routing decision at all, just A, B, C, A, B, C. Useful for debates, iterative refinement, and multi-perspective review. Same Agent class, different strategy.



Top comments (0)