Strands Agents SDK Multi-Agent — Autonomous Agent Collaboration with Swarm
Table of Contents
Introduction
In the introductory series we learned agent basics, and in the practical series we learned output control and behavior monitoring. In Part 5 of the intro series, we used the Agents as Tools pattern to coordinate multiple agents, but that pattern had the orchestrator controlling everything.
Just pass agents to Swarm and they autonomously hand off tasks to each other.
In this article, we'll try:
- Basics — Create and run a Swarm
- How handoffs work — Shared context and autonomous handoffs
- 4-agent Swarm — Autonomous route selection
- Safety mechanisms —
max_handoffsin action
See the official documentation at Swarm.
Setup
Use the same environment from the introductory series. For a fresh setup:
mkdir my_agent && cd my_agent
python -m venv .venv
source .venv/bin/activate
pip install strands-agents strands-agents-toolsAll examples use the same model configuration and can be run as independent .py files. Write the common setup at the top, then add each example's code below it.
from strands import Agent
from strands.models import BedrockModel
from strands.multiagent import Swarm
bedrock_model = BedrockModel(
model_id="us.anthropic.claude-sonnet-4-20250514-v1:0",
region_name="us-east-1",
)Basics — Creating and Running a Swarm
Let's have a researcher and writer collaborate on a task: "research a topic and summarize the findings."
With Agents as Tools, the orchestrator called specialized agents wrapped in @tool. With Swarm, you just pass agents as a list and they hand off to each other autonomously.
researcher = Agent(
name="researcher",
model=bedrock_model,
system_prompt="You are a research specialist. Research the given topic and hand off to the writer when you have enough information.",
callback_handler=None,
)
writer = Agent(
name="writer",
model=bedrock_model,
system_prompt="You are a writing specialist. Write a concise summary based on the research provided. Do not hand off to another agent.",
callback_handler=None,
)
swarm = Swarm(
[researcher, writer],
entry_point=researcher,
max_handoffs=10,
max_iterations=10,
)
result = swarm("What is Amazon Bedrock? Summarize in 2-3 sentences.")The Swarm constructor takes just a list of agents. entry_point specifies which agent runs first. max_handoffs and max_iterations are safety limits to prevent infinite loops. callback_handler=None on each agent disables streaming output to the console — all subsequent examples use the same setting.
Key differences from Agents as Tools:
| Agents as Tools | Swarm | |
|---|---|---|
| Control | Orchestrator decides | Agents decide autonomously |
| Handoff | Via @tool wrapper | handoff_to_agent tool auto-injected |
| Context sharing | Orchestrator relays | Shared context auto-managed |
| Code | @tool wrappers needed | Just pass a list of agents |
01_swarm_basic.py full code (copy-paste)
from strands import Agent
from strands.models import BedrockModel
from strands.multiagent import Swarm
bedrock_model = BedrockModel(
model_id="us.anthropic.claude-sonnet-4-20250514-v1:0",
region_name="us-east-1",
)
researcher = Agent(
name="researcher",
model=bedrock_model,
system_prompt="You are a research specialist. Research the given topic and hand off to the writer when you have enough information.",
callback_handler=None,
)
writer = Agent(
name="writer",
model=bedrock_model,
system_prompt="You are a writing specialist. Write a concise summary based on the research provided. Do not hand off to another agent.",
callback_handler=None,
)
swarm = Swarm(
[researcher, writer],
entry_point=researcher,
max_handoffs=10,
max_iterations=10,
)
result = swarm("What is Amazon Bedrock? Summarize in 2-3 sentences.")
print(f"Status: {result.status}")
print(f"Node history: {[node.node_id for node in result.node_history]}")
print(f"Execution count: {result.execution_count}")
print(f"Execution time: {result.execution_time}ms")
for node_id, node_result in result.results.items():
print(f"\n--- {node_id} ---")
print(f" Status: {node_result.status}")
if node_result.result and node_result.result.message:
for block in node_result.result.message.get('content', []):
if 'text' in block:
print(f" Output: {block['text'][:150]}...")
breakpython -u 01_swarm_basic.pyResult
Status: Status.COMPLETED
Node history: ['researcher', 'writer']
Execution count: 2
Execution time: 12314ms
--- researcher ---
Status: Status.COMPLETED
--- writer ---
Status: Status.COMPLETED
Output: Amazon Bedrock is a fully managed AWS service that provides access to foundation models from leading AI companies...The researcher ran first, and once research was complete, it handed off to the writer. The node_history confirms the execution order.
Note that the researcher's text output is empty. Instead of returning text, it called the handoff_to_agent tool to hand off to the writer. The final text output comes from the writer, which finished without handing off.
How Handoffs Work
Swarm automatically injects a handoff_to_agent tool into each agent. Agents autonomously decide "which agent should handle this next" and call this tool to hand off.
The shared context managed by Swarm includes:
- Original task — The user's input
- Handoff history — Which agents ran in what order
- Shared knowledge — Information accumulated by each agent
- Available agents — Candidates for handoff
Each agent references this context to decide which agent to hand off to next. Developers don't need to write handoff logic.
Key fields in the result object:
| Field | Description |
|---|---|
result.status | Execution status (COMPLETED, FAILED, etc.) |
result.node_history | Ordered history of executed nodes |
result.execution_count | Total execution count |
result.execution_time | Total execution time (milliseconds) |
result.results | Per-node results (node_id → NodeResult) |
The node_history showing ['researcher', 'writer'] confirms the researcher → writer handoff order. With 3+ agents, the handoff order may vary based on LLM judgment.
4-Agent Swarm — Autonomous Route Selection
Let's have 4 agents — researcher → architect → coder → reviewer — collaborate on "designing a REST API." With 2 agents the handoff order was predictable, but with more agents the LLM autonomously chooses the route.
researcher = Agent(
name="researcher", model=bedrock_model,
system_prompt="You are a research specialist. Research the topic and hand off to the most appropriate next agent.",
callback_handler=None,
)
architect = Agent(
name="architect", model=bedrock_model,
system_prompt="You are a system architecture specialist. Design a high-level architecture. Hand off to the coder when done.",
callback_handler=None,
)
coder = Agent(
name="coder", model=bedrock_model,
system_prompt="You are a coding specialist. Write pseudocode based on the architecture. Hand off to the reviewer when done.",
callback_handler=None,
)
reviewer = Agent(
name="reviewer", model=bedrock_model,
system_prompt="You are a code review specialist. Review and provide a final summary. Do not hand off.",
callback_handler=None,
)
swarm = Swarm(
[researcher, architect, coder, reviewer],
entry_point=researcher,
max_handoffs=10,
max_iterations=10,
)
result = swarm("Design a simple REST API for a todo app")02_swarm_team.py full code (copy-paste)
from strands import Agent
from strands.models import BedrockModel
from strands.multiagent import Swarm
bedrock_model = BedrockModel(
model_id="us.anthropic.claude-sonnet-4-20250514-v1:0",
region_name="us-east-1",
)
researcher = Agent(
name="researcher", model=bedrock_model,
system_prompt="You are a research specialist. Research the topic and hand off to the most appropriate next agent.",
callback_handler=None,
)
architect = Agent(
name="architect", model=bedrock_model,
system_prompt="You are a system architecture specialist. Design a high-level architecture. Hand off to the coder when done.",
callback_handler=None,
)
coder = Agent(
name="coder", model=bedrock_model,
system_prompt="You are a coding specialist. Write pseudocode based on the architecture. Hand off to the reviewer when done.",
callback_handler=None,
)
reviewer = Agent(
name="reviewer", model=bedrock_model,
system_prompt="You are a code review specialist. Review and provide a final summary. Do not hand off.",
callback_handler=None,
)
swarm = Swarm(
[researcher, architect, coder, reviewer],
entry_point=researcher,
max_handoffs=10, max_iterations=10,
)
result = swarm("Design a simple REST API for a todo app")
print(f"Status: {result.status}")
print(f"Node history: {[n.node_id for n in result.node_history]}")
print(f"Execution count: {result.execution_count}")
print(f"Execution time: {result.execution_time}ms")python -u 02_swarm_team.pyResult
Status: Status.COMPLETED
Node history: ['researcher', 'architect', 'coder', 'reviewer']
Execution count: 4
Execution time: 83638msThe 4 agents autonomously handed off in the order researcher → architect → coder → reviewer. This order wasn't specified by the developer — each agent decided "who should handle this next" based on the shared context. Depending on the task, the researcher might hand off directly to the coder.
Safety Mechanisms — max_handoffs in Action
Let's see "what happens when max_handoffs is set too low" with the same 4-agent Swarm.
Set max_handoffs=2 to limit handoffs.
swarm = Swarm(
[researcher, architect, coder, reviewer],
entry_point=researcher,
max_handoffs=2,
max_iterations=10,
)
result = swarm("Design a simple REST API for a todo app")03_swarm_limit.py full code (copy-paste)
from strands import Agent
from strands.models import BedrockModel
from strands.multiagent import Swarm
bedrock_model = BedrockModel(
model_id="us.anthropic.claude-sonnet-4-20250514-v1:0",
region_name="us-east-1",
)
researcher = Agent(
name="researcher", model=bedrock_model,
system_prompt="You are a research specialist. Research the topic and hand off to the architect.",
callback_handler=None,
)
architect = Agent(
name="architect", model=bedrock_model,
system_prompt="You are an architecture specialist. Design architecture and hand off to the coder.",
callback_handler=None,
)
coder = Agent(
name="coder", model=bedrock_model,
system_prompt="You are a coding specialist. Write code and hand off to the reviewer.",
callback_handler=None,
)
reviewer = Agent(
name="reviewer", model=bedrock_model,
system_prompt="You are a review specialist. Review and provide final summary. Do not hand off.",
callback_handler=None,
)
swarm = Swarm(
[researcher, architect, coder, reviewer],
entry_point=researcher,
max_handoffs=2, max_iterations=10,
)
result = swarm("Design a simple REST API for a todo app")
print(f"Status: {result.status}")
print(f"Node history: {[n.node_id for n in result.node_history]}")
print(f"Execution count: {result.execution_count}")python -u 03_swarm_limit.pyResult
Status: Status.FAILED
Node history: ['researcher', 'architect']
Execution count: 2With max_handoffs=2, the Swarm stopped after researcher → architect (2 handoffs) with Status.FAILED. The coder and reviewer were never reached.
In production, estimate the number of handoffs needed for your task and set max_handoffs accordingly. Too low and the task won't complete; too high and you risk infinite loops. max_iterations limits total agent executions (handoffs + internal loops within each agent) and is used alongside max_handoffs.
Summary
- Just pass agents to
Swarmfor autonomous collaboration — No@toolwrappers or orchestrator needed. Just a list of agents and anentry_point. - Handoffs are auto-managed via
handoff_to_agenttool — Automatically injected into each agent. Agents autonomously decide handoff targets. No need to write handoff logic. node_historytracks execution order — See which agents ran in what order for debugging and optimization.max_handoffsandmax_iterationsensure safety — Prevent infinite loops. Always set these in production.
