Agents that
work together
Section 4. One agent with tools goes a long way. When a job is too big, too varied or too slow for one agent, you build a team: a supervisor with workers, agents used as tools, swarms that hand work around, and graphs that route it along fixed paths.
From one agent to a team
When a software team takes on a hard problem, nobody does everything alone. Architects, developers and testers each do what they're best at. A hospital works the same way, with doctors, nurses, specialists and administrators. The same idea applies to AI agents.
In Sections 1 to 3 you built single agents with tools and memory. For most use cases, one well-equipped agent is enough. But what if the problem is too big or too varied for one agent to handle well?
In this section you move from one agent to several. You'll see why one agent isn't always enough, then build the main ways of coordinating a team: a supervisor delegating to workers, agents reused as tools, swarms that hand work to each other, and graphs that route work along fixed paths. Along the way you'll learn when each pattern makes sense, and when it doesn't.
✦Think of it like this
A wedding in India. One person can't book the hall, manage the caterer, handle the decorators, coordinate the mehendi artist and greet 500 guests. So there's a wedding planner (the coordinator) and specialists who each own one job. The planner doesn't cook; the caterer doesn't decorate. Multi-agent systems are built on exactly this division of labour.
What you'll learn here
- When one agent isn't enough
- What multi-agent patterns are
- How agents communicate: sequential, parallel and collaborative
- Common orchestration patterns: supervisor–worker, agent as tool, swarm and graph
- When multi-agent is not the answer
Before you start
- 1
A working Strands setup from Sections 1 to 3
Virtual environment, AWS credentials with Amazon Bedrock access, and
check_setup.pypassing. - 2
Basic Python functions and classes
- 3
An AWS account with Bedrock permissions
Every notebook calls Claude Sonnet 4.6 on Bedrock. Nothing else is created in your account, so there's nothing to clean up.
- 4
REST APIs and JSON (helpful, not required)
- 5
An IDE
VS Code, JupyterLab or Kiro, whichever you've been using.
- 6
A free Tavily API key for notebook 1
The news and sentiment agents search the web with Tavily. An Alpha Vantage key is optional; without it, financial data comes from Yahoo Finance, which needs no key.
| Notebook | Pattern | Example | On this page |
|---|---|---|---|
01_supervisor_worker.ipynb | Supervisor–worker | Market research: news, financial and sentiment workers coordinated by a supervisor | 4.3 |
02_agent_as_tool.ipynb | Agent as tool | Tutoring: a teacher delegates to math, writing and science tutors | 4.4 |
03_graph_pattern.ipynb | Graph | E-commerce orders routed through risk, fraud, inventory, shipping and refund steps | 4.7 |
04_swarm_pattern.ipynb | Swarm | Game level design: four specialists hand work to each other | 4.7 |
pip install -r requirements.txt
export TAVILY_API_KEY="tvly-..." # notebook 1 (free key at tavily.com)
export ALPHA_VANTAGE_API_KEY="..." # optional
jupyter labiWhat's in requirements.txt
strands-agents and strands-agents-tools (the agents, plus the ready-made Tavily search tool), yfinance (free Yahoo Finance data), alpha_vantage (optional market data), pandas and boto3.
When one agent isn't enough
Before the patterns, the why: when should several agents work together instead of one super-agent doing everything?
The problem with single agents
Imagine asking one agent to research the latest AI trends, analyse competitors' pricing, write a technical specification, generate the code, review that code for security issues, and build a presentation for executives. That's a lot to ask. These are the warning signs that one agent is carrying too much:
Context overload
It tries to track too many different tasks at once and loses focus, the you met in Section 3.
Jack of all trades, master of none
Results are mediocre across every task instead of excellent and accurate in specific areas.
No parallelisation
Everything happens one step after another, even when tasks could run at the same time.
Hard to debug
When something goes wrong, it's hard to pinpoint which part of the process failed.
The multi-agent solution
Picture a busy restaurant. One person could take orders, cook, serve and wash up, but it would be slow. Instead there's a host, servers, chefs and dishwashers, each focused on what they do best. Together they deliver a smooth experience.
Multi-agent systems work the same way. Instead of one very complex agent, you build several specialised agents that cooperate. Under the hood, a coordinator agent receives the request, breaks it into subtasks, and delegates each one to the right specialist. The specialists do their work and report back, and the coordinator stitches the results into one response.
Benefits of the multi-agent approach
- 1
Specialisation
Each agent becomes an expert in its domain, with a focused prompt and the right tools.
- 2
Parallel processing
Independent tasks run at the same time, saving time.
- 3
Better debugging
You can isolate and fix problems in one specific agent.
- 4
Scalability
Add new agents as the system grows, without rewriting everything.
The same six jobs, one agent vs a team
Total time
Context the busiest agent carries
What are multi-agent patterns?
A is a structured way of organising several AI agents to work towards a common goal.
Think of an orchestra: strings, brass, woodwind and percussion each play their part, together, under a conductor. And just as music can be organised in different ways (a small chamber group, a large symphony, a jazz band that improvises), there are different patterns for organising agents. Each solves a particular kind of problem, and choosing the right one makes your system faster and easier to build.
Every multi-agent design answers four questions:
| Aspect | The question | Two ends of the spectrum |
|---|---|---|
| Orchestration | How will information flow between agents? | A central coordinator (a conductor) ↔ agents talking directly (a jazz group) |
| Specialisation | What is each agent's focused role and toolset? | One narrow job per agent ↔ broader generalists |
| Collaboration | How do agents share information and build on each other's work? | Output of one becomes input of the next ↔ ongoing discussion |
| Coordination | Who decides the order of work? | A workflow you define (a classical score) ↔ agents figure it out (improvisation) |
These four aspects underpin every pattern ahead. We start with the simplest and most common one.
Lab: the supervisor–worker pattern
Your first multi-agent pattern, and the most common one in production. One agent coordinates; specialist agents do the work.
In the pattern, one agent acts as the coordinator. It receives the request, decides which specialists to call, delegates the work, and combines their results into one response. The workers focus on their tasks and report back to the supervisor.
A story in three versions
You're building a market-research assistant. An investor asks: "Should I invest in electric vehicle companies?"
- 1
Version 1: one agent
It scans recent news, analyses stock performance, checks social media sentiment, reads analyst reports and compares competitors. About five minutes per question. Fine for a few questions a day. Then your manager says it must handle hundreds of questions an hour.
- 2
Version 2: three agents in parallel
A news agent scans EV news, a financial agent analyses stock performance, a sentiment agent checks social media. The tasks are independent, so they run at the same time: ideally about three times faster. It works! But the investor now gets three separate answers:
News agent
"Several large automakers announced new battery plants. One EV start-up reported production delays."
Financial agent
"EV stocks are up this quarter on average. Valuations of pure-play EV makers are high; one is burning cash fast."
Sentiment agent
"Social media sentiment is mixed. Retail investors are bullish; charging infrastructure is a common worry."
The investor stares at this wall of unconnected facts and asks: "So… should I invest or not?" Each agent did its job perfectly, but nobody synthesised the information into a recommendation.
- 3
Version 3: add a supervisor
The supervisor receives the question and thinks: "This person needs news context, financial analysis and market sentiment. I have three workers for exactly those." It calls all three at once, rephrasing the question for each, then weaves their answers into one recommendation.
All three workers run at the same time. Two minutes later the supervisor has every perspective and answers with one recommendation, something like: "The EV sector shows strong growth, but valuations are high and some companies face production problems. Established makers look steadier than newer entrants. With a high risk tolerance the sector looks promising; if you prefer stability, wait for valuations to settle." One coherent answer, and the investor can decide.
Workers stay independent
The news agent doesn't care what the financial agent is doing. Each focuses on its expertise and reports back.
Debugging is straightforward
Wrong financial data? Check the financial agent. Old news? Check the news agent. The supervisor handles synthesis, not domain logic, so bugs stay isolated.
New skills are easy to add
Need regulatory analysis? Create a regulatory agent and tell the supervisor about it. Existing workers don't change.
Workers on their own vs with a supervisor
Run the notebook: 01_supervisor_worker.ipynb
The notebook builds exactly this assistant with real data: Tavily web search for news and sentiment, and Yahoo Finance (through yfinance) for prices and fundamentals. Cell numbers on this page count code cells only, so they skip the notebook's text cells.
%pip install -q -r requirements.txtimport os
from strands.models.bedrock import BedrockModel
MODEL_ID = os.getenv("BEDROCK_MODEL_ID", "global.anthropic.claude-sonnet-4-6")
REGION = os.getenv("AWS_REGION", os.getenv("AWS_DEFAULT_REGION", "us-east-1"))
os.environ.setdefault("AWS_REGION", REGION)
model = BedrockModel(model_id=MODEL_ID, region_name=REGION)
print(f"Using {MODEL_ID} in {REGION}")One BedrockModel object, shared by every agent in the notebook. All four notebooks start with this same cell.
Configure API keys
Set them in your shell before starting Jupyter (recommended), or uncomment the two lines in this cell:
# os.environ["TAVILY_API_KEY"] = "tvly-..."
# os.environ["ALPHA_VANTAGE_API_KEY"] = "..."
if not os.getenv("TAVILY_API_KEY"):
print("⚠️ TAVILY_API_KEY not set — the news and sentiment agents cannot search the web.")
else:
print("✓ TAVILY_API_KEY is configured")
if not os.getenv("ALPHA_VANTAGE_API_KEY"):
print("ℹ️ ALPHA_VANTAGE_API_KEY not set — financial data comes from yfinance (free, no key).")
else:
print("✓ ALPHA_VANTAGE_API_KEY is configured")!Keep keys out of notebooks you share
An API key pasted into a cell travels with the notebook file. Prefer export in your shell, so the key lives only in your environment.
import yfinance as yf
from strands import Agent, tool
from strands_tools.tavily import tavily_searchtavily_search is a ready-made tool from strands-agents-tools. It reads TAVILY_API_KEY from the environment and supports options such as time_range ("day", "week", "month", "year") and max_results.
Custom financial tools (Yahoo Finance)
def _num(value, money=False, pct=False):
"""Format numbers safely — Yahoo sometimes leaves fields empty."""
if not isinstance(value, (int, float)):
return "N/A"
if pct:
return f"{value * 100:.2f}%"
return f"${value:,.2f}" if money else f"{value:,.2f}"
@tool
def get_stock_price(symbol: str) -> str:
"""Get the current stock price and key metrics using Yahoo Finance.
Args:
symbol: Stock ticker symbol (e.g., 'AAPL', 'TSLA', 'MSFT')
"""
try:
info = yf.Ticker(symbol).info
return (
f"Stock: {symbol}\n"
f"Current Price: {_num(info.get('currentPrice') or info.get('regularMarketPrice'), money=True)}\n"
f"Market Cap: {_num(info.get('marketCap'), money=True)}\n"
f"P/E Ratio: {_num(info.get('trailingPE'))}\n"
f"52 Week High: {_num(info.get('fiftyTwoWeekHigh'), money=True)}\n"
f"52 Week Low: {_num(info.get('fiftyTwoWeekLow'), money=True)}\n"
f"Average Volume: {_num(info.get('averageVolume'))}\n"
f"Sector: {info.get('sector', 'N/A')}\n"
f"Industry: {info.get('industry', 'N/A')}"
)
except Exception as e:
return f"Error fetching stock data for {symbol}: {e}"
@tool
def get_stock_history(symbol: str, period: str = "1mo") -> str:
"""Get historical stock price data.
Args:
symbol: Stock ticker symbol (e.g., 'AAPL', 'TSLA')
period: One of 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max
"""
try:
hist = yf.Ticker(symbol).history(period=period)
if hist.empty:
return f"No historical data available for {symbol}"
start_price = hist["Close"].iloc[0]
end_price = hist["Close"].iloc[-1]
change_pct = (end_price - start_price) / start_price * 100
return (
f"Historical Data for {symbol} ({period}):\n"
f"Starting Price: ${start_price:.2f}\n"
f"Ending Price: ${end_price:.2f}\n"
f"Change: {change_pct:+.2f}%\n"
f"Highest: ${hist['High'].max():.2f}\n"
f"Lowest: ${hist['Low'].min():.2f}\n"
f"Average Volume: {hist['Volume'].mean():,.0f}\n\n"
f"Recent 5-day trend:\n{hist[['Close', 'Volume']].tail().to_string()}"
)
except Exception as e:
return f"Error fetching historical data for {symbol}: {e}"
@tool
def get_company_financials(symbol: str) -> str:
"""Get company financial metrics: revenue, margins, growth and leverage.
Args:
symbol: Stock ticker symbol (e.g., 'AAPL', 'TSLA')
"""
try:
info = yf.Ticker(symbol).info
return (
f"Financial Metrics for {symbol}:\n"
f"Revenue: {_num(info.get('totalRevenue'), money=True)}\n"
f"Revenue Growth: {_num(info.get('revenueGrowth'), pct=True)}\n"
f"Gross Profit: {_num(info.get('grossProfits'), money=True)}\n"
f"Profit Margin: {_num(info.get('profitMargins'), pct=True)}\n"
f"Operating Margin: {_num(info.get('operatingMargins'), pct=True)}\n"
f"EBITDA: {_num(info.get('ebitda'), money=True)}\n"
f"Earnings Growth: {_num(info.get('earningsGrowth'), pct=True)}\n"
f"Return on Equity: {_num(info.get('returnOnEquity'), pct=True)}\n"
f"Debt to Equity: {_num(info.get('debtToEquity'))}\n"
f"Free Cash Flow: {_num(info.get('freeCashflow'), money=True)}"
)
except Exception as e:
return f"Error fetching financial data for {symbol}: {e}"
print(get_stock_price(symbol="AAPL")) # tools are still plain functions you can call- a
_num(...): a defensive formatterYahoo Finance sometimes leaves fields empty. Instead of crashing on
None, the tool prints "N/A". The leading underscore marks it as a private helper, not a tool. - b
Three focused
@toolfunctionsCurrent price and key metrics, price history over a period, and company fundamentals. Each returns a plain-text summary the model can read, and each catches errors and returns them as text so one failed lookup doesn't break the whole run.
- c
Test before you give it to an agent
The last line calls a tool directly. A decorated tool is still a normal Python function, so you can check its output first.
Stock: AAPL Current Price: $… Market Cap: $… P/E Ratio: … 52 Week High: $… 52 Week Low: $… Average Volume: … Sector: Technology Industry: Consumer Electronics
Optional: an Alpha Vantage tool
from alpha_vantage.timeseries import TimeSeries
@tool
def get_alpha_vantage_quote(symbol: str) -> str:
"""Get the latest stock quote from the Alpha Vantage API.
Args:
symbol: Stock ticker symbol (e.g., 'AAPL')
"""
key = os.getenv("ALPHA_VANTAGE_API_KEY")
if not key:
return "ALPHA_VANTAGE_API_KEY is not set."
ts = TimeSeries(key=key, output_format="pandas")
data, _ = ts.get_quote_endpoint(symbol=symbol)
return data.to_string()
financial_tools = [get_stock_price, get_stock_history, get_company_financials, tavily_search]
if os.getenv("ALPHA_VANTAGE_API_KEY"):
financial_tools.append(get_alpha_vantage_quote)Use this instead of, or alongside, Yahoo Finance if you have a key. Other data providers worth knowing include Polygon.io, Finnhub and Financial Modeling Prep. The financial agent's toolbox is built conditionally: the Alpha Vantage tool is only added when the key exists, so the agent never sees a tool that can't work.
The worker agents
news_agent = Agent(
name="news_agent",
description="Researches recent news and developments about companies and industries.",
model=model,
system_prompt="""You are a news research specialist. Search for recent news and developments about companies and industries.
When searching:
- Focus on recent news (use time_range='week' or 'month')
- Prefer reliable sources such as major news outlets
- Summarize key developments and trends
- Include specific facts, dates, and figures when available""",
tools=[tavily_search],
callback_handler=None,
)
financial_agent = Agent(
name="financial_agent",
description="Analyzes stock prices, price history, and company financial metrics.",
model=model,
system_prompt="""You are a financial analysis specialist. Analyze financial metrics, stock performance, and company valuations.
When analyzing:
- Use get_stock_price for current prices and key metrics
- Use get_stock_history for price trends
- Use get_company_financials for revenue, earnings, and growth
- Compare companies within the same sector and give specific numbers""",
tools=financial_tools,
callback_handler=None,
)
sentiment_agent = Agent(
name="sentiment_agent",
description="Analyzes market and investor sentiment from forums, social media, and commentary.",
model=model,
system_prompt="""You are a market sentiment analyst. Analyze market sentiment from social media, forums, and investor discussions.
When analyzing sentiment:
- Look for both bullish and bearish opinions
- Identify common concerns and excitement points
- Distinguish retail from institutional sentiment
- Summarize the overall market mood""",
tools=[tavily_search],
callback_handler=None,
)
print("✓ Worker agents created")| Argument | What it does here |
|---|---|
name | Becomes the tool name the supervisor sees: letters, digits, underscores or hyphens, like news_agent. |
description | Becomes the tool description: how the supervisor knows when to call this worker. |
system_prompt | The worker's job description, with concrete instructions about which tools to use and how. |
tools | Each worker gets only the tools for its speciality. News and sentiment share Tavily search; finance gets the Yahoo Finance tools. |
callback_handler=None | Silences the worker's own streaming output, so you only see the supervisor's answer instead of four voices talking at once. |
The supervisor agent
supervisor = Agent(
name="research_supervisor",
model=model,
system_prompt="""You are a market research coordinator. When asked about investment opportunities:
1. Work out what information is needed
2. Delegate to the specialist agents:
- news_agent: recent developments and industry news
- financial_agent: financial metrics and performance data
- sentiment_agent: market sentiment and investor opinions
3. Synthesize their responses into one balanced recommendation
This is educational analysis, not financial advice — say so briefly at the end.""",
tools=[news_agent.as_tool(), financial_agent.as_tool(), sentiment_agent.as_tool()],
)
print("✓ Supervisor created")agent.as_tool() wraps a worker so the supervisor can call it like any other tool. (Passing the agents directly in tools=[...] does the same thing; you'll see that in the next lab.) By default each call starts the worker from a clean slate, so one question's research doesn't leak into the next. The supervisor's prompt tells it how to delegate and how to synthesise, and it ends with a guardrail: this is education, not financial advice.
Run the multi-agent system
question = "Should I invest in electric vehicle companies?"
print(f"Question: {question}\n")
response = supervisor(question)Question: Should I invest in electric vehicle companies? I'll gather recent news, financial data and market sentiment, then combine them. Tool #1: news_agent Tool #2: financial_agent Tool #3: sentiment_agent
EV investment: a balanced view
News: large automakers keep expanding EV and battery capacity; some newer EV makers face production delays.
Financials: sector revenue is growing, but valuations of pure-play EV makers are high and several are not yet profitable.
Sentiment: retail investors are mostly bullish; institutional investors are cautious about valuations.
Recommendation: established manufacturers look steadier than newer entrants. With a high risk tolerance the sector looks promising; if you prefer stability, wait for valuations to normalise.
This is educational analysis, not financial advice.
Notice the three tool calls. Claude usually asks for all three workers in a single turn, and Strands runs tool calls from the same turn by default, so the three workers really do research in parallel. Your run will quote real, current figures from the live searches.
Try different questions
questions = [
"What's the outlook for renewable energy stocks?",
"Is the semiconductor industry a good investment right now?",
]
for q in questions:
print(f"\n{'=' * 80}\nQuestion: {q}\n{'=' * 80}")
supervisor(q)Same supervisor, same workers, different sectors. Nothing about the team is specific to EVs; the supervisor works out what each question needs.
Specialisation
Each worker has one job and the right tools for it.
Coordination
The supervisor decides whom to ask and combines the answers.
Modularity
Add a new worker without touching the others.
</>Making it production-ready
Add retries and caching for API calls, respect rate limits, run asynchronously, and add tracing so you can see every call (Section 7).
The trade-offs of supervisor–worker
- 1
The supervisor is a
Every decision flows through one agent. In production you address this with (run several supervisor instances), caching (repeated questions return instantly) and . For high volume, add load balancing and asynchronous processing.
- 2
It's a
If the supervisor crashes, the system stops. You need health checks, automatic restarts, and checkpoint management so the supervisor resumes from its last known state. For critical systems, run redundant supervisors with a mechanism.
- 3
Workers can't collaborate directly
If the sentiment agent discovers something the financial agent should know, it has to go through the supervisor. That's fine for simple workflows; for real negotiation you need collaborative communication, covered in 4.6.
iWhy it's still the favourite
Most multi-agent systems don't need complex negotiation. They need clear accountability, simple communication and easy debugging. That's why supervisor–worker is the most common pattern in production.
Lab: make agents reusable with "agent as tool"
Build a specialist once, give it a clear description, and any other agent can discover and call it like an ordinary tool.
Supervisor–worker works well, but if every specialist is built from scratch for one particular supervisor, you rebuild it again and again. The pattern fixes that: you wrap an agent with a description of what it does, and other agents can call it just like a regular tool. Treating components as modular, reusable tools lets a system grow without rigid dependencies between its parts.
When an agent receives a task, it reads the available tool descriptions and picks the right one, exactly as you saw with tools in Section 2. The only difference is that these tools are agents.
✦Think of it like this
A hospital's specialist doctors. The cardiologist isn't hired by one GP; any doctor in the hospital can refer a patient to them. The referral desk only needs to know "cardiologist: heart problems". Build the specialist once, and every department can use them.
The example: a tutoring assistant
Students ask about different subjects, and you have specialist tutors for math, writing and science. Instead of writing routing logic like "if the question contains 'equation', call the math tutor", you wrap each specialist as a tool and let the teacher agent decide which one to use.
Run the notebook: 02_agent_as_tool.ipynb
Cells 1 and 2 are the same install and model cells as notebook 1. Then:
from strands import AgentSpecialist agents
Each specialist gets a name and a description. When an agent is used as a tool, the name becomes the tool name and the description tells the teacher when to call it.
math_tutor = Agent(
name="math_tutor",
description="Solves mathematical equations and explains math concepts step by step",
model=model,
system_prompt="You are a math tutor. Solve math problems and explain your solutions clearly, step by step.",
callback_handler=None,
)
writing_coach = Agent(
name="writing_coach",
description="Helps with writing, grammar, and essay structure",
model=model,
system_prompt="You are a writing coach. Help with writing, grammar, and essay structure. Provide constructive feedback.",
callback_handler=None,
)
science_helper = Agent(
name="science_helper",
description="Explains scientific concepts and principles in simple terms",
model=model,
system_prompt="You are a science tutor. Explain scientific concepts clearly using simple language and real-world examples.",
callback_handler=None,
)The teacher ()
Pass the specialist agents straight into tools=[...]; Strands wraps each one as a callable tool for you.
teacher = Agent(
model=model,
system_prompt="""You are a teacher who helps students by delegating to specialist tutors.
Never solve problems yourself. Always delegate to the appropriate specialist:
- math_tutor: for math problems and equations
- writing_coach: for writing, grammar, and essays
- science_helper: for science questions
After receiving the specialist's response, present it to the student in a friendly way.""",
tools=[math_tutor, writing_coach, science_helper],
)?Two ways to say the same thing
Notebook 1 wrote tools=[news_agent.as_tool(), …]. Here it's tools=[math_tutor, …]. When Strands finds an agent in the tools list, it calls .as_tool() for you with the default settings. Use .as_tool(name=..., description=...) explicitly when you want to override the name or description.
Test with different questions
# Math → math_tutor
response = teacher("Can you help me solve this quadratic equation: x^2 + 5x + 6 = 0?")I'll help you with that quadratic equation! Let me connect you with our math specialist.
Tool #1: math_tutor
Great! Here's what our math tutor explained:
To solve x² + 5x + 6 = 0, we can use factoring.
Step 1: Look for two numbers that multiply to 6 and add to 5.
Those numbers are 2 and 3 (since 2 × 3 = 6 and 2 + 3 = 5).
Step 2: Factor the equation: (x + 2)(x + 3) = 0
Step 3: Solve each factor:
If x + 2 = 0, then x = −2
If x + 3 = 0, then x = −3
Solution: x = −2 or x = −3
You can check both answers by substituting them back into the original equation; both make it equal zero!
Would you like help with another problem?
The teacher has no hard-coded rule like "if math question, call the math tutor". It saw three tools, read their descriptions, and chose math_tutor because the question matched "solves mathematical equations". Then, as its prompt asks, it presented the tutor's answer in a friendly way.
# Writing → writing_coach
response = teacher("How do I write a strong thesis statement for my essay about climate change?")# Science → science_helper
response = teacher("Why does ice float on water?")Expect Tool #1: writing_coach for the thesis question and Tool #1: science_helper for the ice question (ice is less dense than liquid water because its molecules lock into an open crystal structure).
Which tutor does the teacher call?
Why this is so flexible
Want a history tutor? Create a history_tutor agent and add it to the teacher's tools. The teacher automatically knows it exists. No routing logic to update, no new if statements.
And it goes deeper. Specialists can have their own tools, including other agents, creating layers of expertise. Your math_tutor might use a calculator_agent and a graph_plotter_agent. And the same math_tutor can be reused by a teacher agent, a tutoring-assistant agent and a homework-grader agent:
iSupervisor–worker vs agent as tool
They look alike, and in Strands both use agents as tools. The difference is intent. Supervisor–worker is about coordination: fan out, then synthesise one answer. Agent as tool is about reuse: a self-contained specialist that any orchestrator can pick up, while the orchestrator stays in control of the conversation.
How data flows through a team of agents
You've built two patterns. Now zoom out and follow one request as it moves through a multi-agent system, step by step.
The request: "What's the best laptop for video editing under ₹1,80,000?"
Follow the laptop question
- 1
The request arrives
The question enters the system as raw text. Nothing has happened yet.
- 2
The router makes decisions
It reads the question and thinks: "This person needs product recommendations, price comparisons and technical specs. I know which agents can help." It sends work to the specs agent (knows what video editing needs), the price agent (searches current prices across retailers) and the review agent (analyses customer feedback). It doesn't forward the question blindly; it rephrases it for each. The specs agent gets "What CPU and GPU are needed for 4K video editing?"; the price agent gets "Find laptops under ₹1,80,000 with these specs."
- 3
The agents do their thing
All three work at the same time: one works out requirements, one searches retailer listings, one reads thousands of reviews. Each agent can keep its own memory of the conversation (in Strands, wrap it with
as_tool(preserve_context=True); the default starts each call fresh), so if you mentioned your editing software earlier, the specs agent remembers. - 4
Agents talk to each other (sometimes)
The price agent finds a laptop at ₹1,65,000, but the specs agent realises it doesn't have enough RAM for 4K. The price agent asks: "If we stretch the budget to ₹1,90,000, can we get something that works?" Agents negotiate and refine based on what the others find. (In a strict supervisor–worker setup this exchange goes through the supervisor; in collaborative setups, covered next, agents talk directly.)
- 5
Results are combined
An aggregator (often the supervisor) collects: specs agent: "At least 32 GB RAM, a recent mid-range dedicated GPU and a fast multi-core CPU." Price agent: "Three laptops match: ₹1,72,000, ₹1,79,000 and ₹1,86,000." Review agent: "The ₹1,79,000 one is rated 4.5 stars, with praise for rendering speed."
- 6
You get your answer
"I recommend Laptop B at ₹1,79,000. It meets the specs for 4K editing, fits your budget, and users specifically praise its rendering performance." It feels like one agent answered, but three specialists collaborated behind the scenes.
A single agent would have to know video-editing requirements, search several retailers, analyse reviews and synthesise everything itself: slower, more expensive and more likely to miss something. With several agents, each is an expert in its area, and they work in parallel. That raises the obvious next questions: how do agents share information, and how do you decide whether they work in sequence, in parallel or together?
How agents talk to each other
Agents working in isolation aren't a system; they're just separate programs. Communication is what turns them into a team.
Agents communicate in three main ways. Each solves a different problem and fits a different situation.
Sequential communication
Think of writing a research paper. You can't write the conclusion before the research, and you can't edit what hasn't been written. Some things must happen in order. In communication, one agent finishes and hands its results to the next.
The research agent might spend 10 minutes gathering information, the writing agent 15 minutes drafting, and the editing agent 5 minutes polishing: 30 minutes in total. The writing agent literally cannot start without the research, and the editor needs a complete draft.
+Use it when
There are real dependencies: step B needs step A's output. Bonus: only one agent calls the LLM at a time, which helps if your account has API .
−The cost
Time adds up with no overlap. And if the writing agent fails after 10 minutes of research, that time is wasted: everything downstream stops and the pipeline has to restart.
Parallel communication
Now picture planning a conference: one person books the venue, another arranges catering, a third handles marketing. None depends on the others, so they happen at the same time. In communication, several agents work simultaneously on different parts of a problem, and their results are combined when all have finished.
Say you're analysing a market before a product launch and need three kinds of analysis. One agent scrapes a competitor's website and builds a pricing matrix (5 minutes). Another reads thousands of customer reviews of similar products, finding patterns in complaints and praise (5 minutes). A third analyses search trends, social media mentions and industry reports (5 minutes). Run them one after another and you wait 15 minutes; run them in parallel and you wait 5, the time of the slowest agent.
Parallel works best when tasks are independent: agent 1 doesn't need agent 2's results. It's also useful for accuracy, with several agents checking the same thing from different angles.
Sequential vs parallel timing
Sequential
Parallel
Parallel for accuracy: let agents vote
Imagine a content-moderation system. Three agents independently check whether a post breaks community guidelines. Agent 1 says safe, agent 2 says safe, agent 3 says unsafe. Two of three vote safe, so the post is approved.
Agents make mistakes, but if they're independent they don't all make the same mistakes. If two of three agree, you can trust the result more than any single opinion; if all three flag something, you can be very confident it needs review. The maths backs this up: if each agent is wrong 5% of the time, independently, a majority vote of five agents is wrong only about 0.12% of the time, because at least three would have to be wrong together.
| Voting rule | How it decides | Fits |
|---|---|---|
| Simple majority | More than half agree | Content moderation |
| Unanimous | All agents must agree | High-stakes calls such as a medical-diagnosis assistant |
| Weighted | Some votes count more | Legal document review, where the specialist agent's vote weighs more |
!Voting only works with independent agents
If every agent uses the same model, the same data source and the same reasoning, they'll make the same mistakes and voting adds nothing. You need diversity: different models, different data sources, different approaches.
How much does voting help?
Number of voting agents
One agent alone is wrong
Majority vote is wrong
Collaborative communication
Think about designing a building. The architect proposes a design; the structural engineer says it won't bear the load; the architect adjusts; the interior designer suggests changes for better flow. They go back and forth until the design satisfies every constraint. In communication, agents talk to each other, share insights, negotiate trade-offs and refine their work together.
Here three agents review code: a developer agent wrote a function that processes user data, a security agent checks for vulnerabilities, and a performance agent makes sure it's fast enough. Step through their negotiation:
A code review negotiation
Read the whole conversation
- Developer: I've written a function that processes user data quickly.
- Security: This is vulnerable to SQL injection. You need to sanitise inputs.
- Developer: If I add sanitisation, performance drops by 40%.
- Performance: 40% slower is unacceptable. We have strict latency requirements.
- Security: Then use parameterised queries. They're secure and fast.
- Developer: Good idea. Let me refactor.
- Performance: Wait, you're loading the entire dataset into memory. Stream it instead.
- Developer: Done. All three concerns addressed.
- All agents review the updated code.
- Security: Secure now. Performance: Meets latency requirements. Developer: Code is ready.
This back-and-forth doesn't happen in sequential or parallel setups. Sequential passes information one way; parallel agents work without talking. Collaboration is a real conversation that continues until the solution satisfies every agent's requirements, here balancing security, performance and maintainability. And it does work better on hard problems: experiments with multi-agent discussion show that iterative debate catches errors and explores alternatives a single agent would miss.
+Use it when
Agents must balance trade-offs with no single right answer: security vs performance, cost vs quality, speed vs accuracy. Decisions where one agent's recommendation affects another's.
−The cost
Several rounds of discussion means several rounds of LLM calls. One agent might answer in 10 seconds; three collaborating might take 30 to 40. It's slower, costlier, and harder to troubleshoot: finding which round introduced a bad decision takes more work than debugging a simple pipeline.
| Technique | Information flows | Time | Best for |
|---|---|---|---|
| Sequential | One direction, A → B → C | Sum of all steps | Real dependencies; tight rate limits |
| Parallel | Out to everyone, then combined | The slowest step | Independent tasks; accuracy through voting |
| Collaborative | Back and forth, in rounds | Several rounds of calls | Trade-offs with no single right answer |
Sequential, parallel and collaborative are the building blocks. Next you'll see how whole systems are organised around them.
Common orchestration patterns
Supervisor–worker is the pattern most organisations use, but it isn't the only one. Two more solve very different problems: swarms and graphs.
Each uses the communication building blocks you just learned, arranged for a specific kind of problem. A swarm lets agents decide who goes next; a graph makes you decide, in code, ahead of time. We cover the swarm (notebook 04) before the graph (notebook 03) because the graph is easiest to understand by contrast; you can run the two notebooks in either order.
Swarm: agents hand work to each other
A builds a whole system out of collaborative communication. The hard part of collaboration is coordination: who talks to whom, when, and how do you stop agents talking in circles? A swarm answers with : agents explicitly pass control to each other.
✦Think of it like this
A game-design team building a stealth mission in an abandoned space station. The level designer creates the floor plan and a rough 3D . In playtesting, the game designer finds players get lost, so it goes back to the level designer, who adds landmarks and lighting. Then the narrative designer objects: bright lighting contradicts the story of a creepy, abandoned station. The level designer switches to dim emergency lights and environmental storytelling. Now the difficulty balancer finds the level too easy with clearer navigation, so back to the level designer for tougher enemy placement, without removing the visual cues. The path loops and branches based on what each specialist finds. Nobody could have predicted it up front.
In a swarm you'd have four agents, level_designer, game_designer, narrative_designer and difficulty_balancer, each with its own expertise. There's no supervisor and no fixed sequence. When an agent finishes its part, it decides who should go next and hands off the task with context about what's been done.
Run the notebook: 04_swarm_pattern.ipynb
After the usual install and model cells:
import logging
from strands import Agent
from strands.multiagent import Swarm
# Show each handoff as it happens
logging.getLogger("strands.multiagent").setLevel(logging.DEBUG)
logging.basicConfig(format="%(levelname)s | %(name)s | %(message)s", handlers=[logging.StreamHandler()])The two logging lines switch on Strands' multi-agent debug messages, so you can watch every handoff live.
level_designer = Agent(
name="level_designer",
model=model,
callback_handler=None,
system_prompt="""You are a Level Designer in a game design swarm.
Your role is to create level layouts, environments, and visual elements.
Focus ONLY on layout and visual design. Do NOT write narrative or balance difficulty.
After completing your layout, hand off to narrative_designer for story elements
and difficulty_balancer for gameplay tuning.
When receiving feedback from other agents, revise your design accordingly.""",
)
game_designer = Agent(
name="game_designer",
model=model,
callback_handler=None,
system_prompt="""You are a Game Designer in a game design swarm.
Your role is to playtest designs for fun, usability, and player confusion.
Focus ONLY on gameplay feel and player experience. Do NOT design layouts or write narrative.
When you find issues, hand off to the appropriate specialist to fix them.
When the design is ready, give the final summary and do not hand off.""",
)
narrative_designer = Agent(
name="narrative_designer",
model=model,
callback_handler=None,
system_prompt="""You are a Narrative Designer in a game design swarm.
Your role is to create story elements, character backstories, and atmospheric details.
Focus ONLY on narrative. Do NOT redesign layouts or adjust difficulty.
After adding narrative elements, hand off to difficulty_balancer for balance review
or game_designer for playtesting feedback.""",
)
difficulty_balancer = Agent(
name="difficulty_balancer",
model=model,
callback_handler=None,
system_prompt="""You are a Difficulty Balancer in a game design swarm.
Your role is to evaluate and tune difficulty progression, enemy placement, and pacing.
Focus ONLY on balance. Do NOT redesign layouts or write narrative.
When you find layout or narrative issues, hand off to the appropriate agent.
After balancing, hand off to game_designer for a final playtest review.""",
)Read the prompts closely: each one says what the agent does, what it must not do ("Do NOT write narrative"), and whom to hand off to. The game designer's prompt contains the exit: "When the design is ready, give the final summary and do not hand off." A swarm finishes when an agent completes its turn without handing off.
Create the swarm
The swarm gives every agent a handoff_to_agent tool and a shared view of the work so far. For example, the game designer might call:
handoff_to_agent(
agent_name="difficulty_balancer",
message="Patrol patterns too predictable - need more variation",
)swarm = Swarm(
[level_designer, game_designer, narrative_designer, difficulty_balancer],
entry_point=level_designer,
max_handoffs=20,
max_iterations=20,
execution_timeout=1200.0,
node_timeout=300.0,
repetitive_handoff_detection_window=8,
repetitive_handoff_min_unique_agents=3,
)
print("✓ Swarm created")| Setting | Value | What it protects against |
|---|---|---|
entry_point | level_designer | Which agent starts. A layout has to exist before anyone can review it. |
max_handoffs | 20 | Caps how many times control can be passed. |
max_iterations | 20 | Caps the total number of agent turns. Without these limits, agents could loop forever. (In the current Strands version both are counted as completed agent turns, so they behave alike; setting both is belt and braces.) |
execution_timeout | 1200.0 s | Wall-clock cap for the whole swarm (20 minutes). |
node_timeout | 300.0 s | Wall-clock cap for any single agent's turn (5 minutes). |
repetitive_handoff_detection_window | 8 | : two agents bouncing the task back and forth without progress. The swarm looks at the last 8 turns; if fewer than 3 different agents appear, it stops. So if the level and narrative designers trade the task 8 times without anyone else, the loop is detected and the swarm ends. |
repetitive_handoff_min_unique_agents | 3 |
Run the swarm (takes a few minutes)
result = swarm("""Design a stealth mission level in an abandoned space station.
The level needs to support multiple playstyles (aggressive stealth, patient stealth, and ghost runs).
Include at least 3 distinct zones with different difficulty levels,
narrative elements that explain why the station was abandoned,
and ensure the difficulty curve is appropriate for mid-game players.""")
print("=" * 80)
print("HANDOFF CHAIN:")
for node in result.node_history:
print(f" → {node.node_id}")
print(f"\nStatus: {result.status} | iterations: {result.execution_count} | time: {result.execution_time / 1000:.0f}s")DEBUG | strands.multiagent.swarm | current_node=<level_designer>, iteration=<1> | executing node
DEBUG | strands.multiagent.swarm | from_node=<level_designer>, to_node=<narrative_designer> | handoff detected
DEBUG | strands.multiagent.swarm | current_node=<narrative_designer>, iteration=<2> | executing node
DEBUG | strands.multiagent.swarm | from_node=<narrative_designer>, to_node=<difficulty_balancer> | handoff detected
DEBUG | strands.multiagent.swarm | current_node=<difficulty_balancer>, iteration=<3> | executing node
DEBUG | strands.multiagent.swarm | from_node=<difficulty_balancer>, to_node=<game_designer> | handoff detected
DEBUG | strands.multiagent.swarm | current_node=<game_designer>, iteration=<4> | executing node
DEBUG | strands.multiagent.swarm | from_node=<game_designer>, to_node=<level_designer> | handoff detected
...
DEBUG | strands.multiagent.swarm | node=<game_designer> | no handoff occurred, marking swarm as complete
DEBUG | strands.multiagent.swarm | status=<Status.COMPLETED> | swarm execution completed
DEBUG | strands.multiagent.swarm | node_history_length=<11>, time=<486.46>s | metrics
================================================================================
HANDOFF CHAIN:
→ level_designer
→ narrative_designer
→ difficulty_balancer
→ game_designer
→ level_designer
→ game_designer
→ …
→ game_designer
Status: Status.COMPLETED | iterations: 11 | time: 486sRead the chain as a story. The level designer drew the layout and handed off to the narrative designer for story elements, who added the backstory and passed it to the difficulty balancer for tuning, who handed off to the game designer for playtesting. The game designer found navigation problems in zone 2 and sent it back to the level designer. That back-and-forth continued for 11 turns until the game designer approved the final design. Nobody programmed this sequence. The agents worked it out from what needed to happen next.
from IPython.display import Markdown, display
last_node = result.node_history[-1].node_id
display(Markdown(str(result.results[last_node].result)))result.node_history lists every turn in order; the last entry is the agent that finished (usually the game designer with its final summary). result.results maps each agent's name to its last output, which this cell renders as Markdown.
How a handoff works under the hood
Handoffs happen through tool calls. When you create a Swarm, every agent automatically gets a handoff_to_agent tool, and the swarm keeps a shared record of the run. Say the game designer finishes playtesting and finds enemy patrols are too predictable. That's a difficulty problem, not a playtesting problem, so it needs someone who can tune the challenge. When its turn began, the swarm gave the game designer a formatted briefing that includes:
- 1
The handoff message
What the previous agent said it needs, for example "Add landmarks in zone 2".
- 2
The original task
- 3
The agents who have worked on it so far
For example
level_designer → game_designer. - 4
What previous agents contributed
As "shared knowledge from previous agents": whatever notes an agent chose to pass in the handoff's optional
contextargument. - 5
The other available agents
By name, plus each one's
descriptionif you gave it one. The game designer reads: the level designer creates layouts and visuals; the narrative designer creates story and atmosphere; the difficulty balancer tunes difficulty, enemy placement and pacing.
The patrol problem matches the difficulty balancer, so the game designer calls handoff_to_agent(agent_name="difficulty_balancer", message="Patrol patterns too predictable - need more variation"). Now the difficulty balancer is in control and receives the same formatted context, including what the game designer found. The LLM makes each routing decision by matching the problem to what each agent does.
!Write handoffs like a good shift-change note
Each agent's own conversation is reset before its turn, so a returning level designer doesn't automatically see its earlier layout. Knowledge travels in the handoff message (and optional context). Prompting agents to summarise what they did and what's needed when they hand off makes swarms work much better.
iTip: give swarm agents a description
The lab's agents rely on their names and prompts. Adding description="Tunes difficulty, enemy placement and pacing" to each Agent(...) puts that text in every teammate's list of available agents, which makes handoff choices more reliable.
Run a swarm, one turn at a time
Swarm log
Briefing the next agent receives
Last 8 turns: unique agents
+Use a swarm for
Creative and iterative work where you can't predict the path up front. Sometimes the layout passes every reviewer first time; sometimes it bounces around as new problems appear. You define the agents and their strengths; they work out who handles each issue.
−Don't use one when
You need a fixed sequence where certain checks always happen in a specific order. That's the graph pattern's job.
Graph: you define the paths
The is built on a : data flows along defined paths from one node to the next. There are two flavours:
"Isn't that what a swarm does?" The key difference: in a swarm, agents decide the path; in a graph, you define the paths up front. Before the example, the building blocks:
Nodes
Anything that processes data. In Strands a node can be a single agent (like a fraud-detection agent), a swarm of agents, another graph (graphs can be nested), or custom business logic: your own deterministic code wrapped as a node.
Edges
The connections. An edge says "when node A finishes, pass its output to node B". Edges can carry , for example "only follow this edge if the order is over $1,000".
Dependencies
If node B depends on node A, A must finish before B starts. The graph manages this and runs nodes in the right order. When several arrows lead into one node (like inventory check below), it runs as soon as one incoming arrow fires.
The example: processing e-commerce orders
Orders arrive all day. Some are small purchases from regular customers; some are big orders from brand-new accounts; some ship somewhere that doesn't match the billing address. You can't treat them all the same way.
You could use supervisor–worker: the supervisor reads each order and decides where to send it. It works, until your payment processor asks, "How do you guarantee high-value orders always get fraud review?" With a supervisor, the routing happens inside the LLM's reasoning. You can log it, but the decision logic isn't explicit in your code.
A graph makes the routing explicit. The risk analyser runs first; high-value orders go to manual review; suspicious ones go to fraud detection; clean, low-risk orders go straight to the inventory check. The decision tree is visible in your code, and anyone can see exactly what happens to each kind of order.
- 1
High-value orders go to manual review first
- 2
Suspicious orders go to advanced fraud detection
Red flags such as a billing and shipping mismatch, or a brand-new account placing a large order.
- 3
Low-risk orders from trusted customers skip the checks
They go straight to inventory.
- 4
All three paths meet at the inventory check
In stock: shipping generates a label and the package goes out. Out of stock: refund processing notifies the customer and returns the payment. Every order ends in one of these two states.
Directed means arrows point one way: from the risk analyser to fraud check, never backwards. Acyclic means no loops: you can't go risk analyser → fraud check → inventory → back to risk analyser, which could cycle forever. That structure guarantees every order eventually reaches an end state, shipped or refunded.
That's why graphs are great for compliance and auditability. When your payment processor asks about fraud prevention, you point at the graph. The logic lives in code, not hidden in LLM reasoning. A large order from a new customer always follows the same path; it doesn't vary with how an LLM reads the situation that day.
?So where does the LLM fit?
The graph defines the flow; each node is still an agent that uses an LLM for its task. The risk analyser understands the order in natural language, the fraud agent reasons about suspicious patterns, the inventory agent interprets stock data. You define the routing; the LLMs do the reasoning at each step.
Run the notebook: 03_graph_pattern.ipynb
After the usual install and model cells:
from strands import Agent
from strands.multiagent import GraphBuilderThe two "decision" agents end their answer with a fixed line (RISK_CATEGORY: ..., STOCK_STATUS: ...). The routing functions read only that line, so an explanation that merely mentions another category can't send the order down the wrong path.
risk_analyzer = Agent(
name="risk_analyzer",
model=model,
callback_handler=None,
system_prompt="""Analyze the order's risk level and pick exactly ONE category:
- HIGH_VALUE: orders over $1,000
- SUSPICIOUS: mismatched billing/shipping country, brand-new account with a large order, or known fraud patterns
- LOW_RISK: regular orders from trusted customers
If more than one applies, SUSPICIOUS wins over HIGH_VALUE.
Give a one-paragraph explanation, then finish with a final line in exactly this format:
RISK_CATEGORY: <HIGH_VALUE|SUSPICIOUS|LOW_RISK>""",
)
manual_review_agent = Agent(
name="manual_review_agent",
model=model,
callback_handler=None,
system_prompt="Review high-value orders. Verify customer identity, check order history, and approve or flag for further review.",
)
fraud_detection_agent = Agent(
name="fraud_detection_agent",
model=model,
callback_handler=None,
system_prompt="Run fraud detection on suspicious orders: known fraud patterns, velocity checks, and device fingerprinting. Decide whether to proceed.",
)
inventory_agent = Agent(
name="inventory_agent",
model=model,
callback_handler=None,
system_prompt="""Check inventory for the order items. Assume laptops, books and phones are in stock and
anything described as 'limited edition' or 'pre-order' is out of stock.
Explain briefly, then finish with a final line in exactly this format:
STOCK_STATUS: <IN_STOCK|OUT_OF_STOCK>""",
)
shipping_agent = Agent(
name="shipping_agent",
model=model,
callback_handler=None,
system_prompt="Generate a shipping label and process the shipment. Confirm a tracking number and estimated delivery date.",
)
refund_agent = Agent(
name="refund_agent",
model=model,
callback_handler=None,
system_prompt="Process a refund for out-of-stock items. Notify the customer and initiate the payment reversal.",
)
print("✓ All agents created")iTwo lab conveniences
The risk rules use dollars ($1,000) to match the sample orders. And the inventory agent pretends to check stock using a simple rule (laptops, books and phones in stock; "limited edition" and "pre-order" out of stock). In a real system that node would call your inventory database as a tool.
Routing conditions
Each condition reads the previous node's output and returns True or False. The logic is plain Python, not hidden inside model reasoning.
import re
def decision(state, node_id: str, label: str) -> str:
"""Return the value of the final 'LABEL: VALUE' line written by a node."""
text = str(state.results.get(node_id, ""))
matches = re.findall(rf"{label}\s*:\s*([A-Z_]+)", text.upper())
return matches[-1] if matches else ""
def is_high_value(state):
return decision(state, "risk_analysis", "RISK_CATEGORY") == "HIGH_VALUE"
def is_suspicious(state):
return decision(state, "risk_analysis", "RISK_CATEGORY") == "SUSPICIOUS"
def is_low_risk(state):
return decision(state, "risk_analysis", "RISK_CATEGORY") == "LOW_RISK"
def is_in_stock(state):
return decision(state, "inventory_check", "STOCK_STATUS") == "IN_STOCK"
def is_out_of_stock(state):
return decision(state, "inventory_check", "STOCK_STATUS") == "OUT_OF_STOCK"
print("✓ Routing conditions defined")- a
state.resultsThe graph's record of every node's output so far, keyed by node ID.
str(...)turns a node's result into its text. - b
The
RISK_CATEGORY\s*:\s*([A-Z_]+)finds "RISK_CATEGORY", optional spaces, a colon, optional spaces, then captures a word in capitals and underscores..upper()makes it case-insensitive, andmatches[-1]takes the last match, the required final line, so earlier mentions in the explanation are ignored. - c
Five tiny conditions
One per edge that needs a decision. Each is a one-line, testable Python function.
Build the graph
builder = GraphBuilder()
# Nodes
builder.add_node(risk_analyzer, "risk_analysis")
builder.add_node(manual_review_agent, "manual_review")
builder.add_node(fraud_detection_agent, "fraud_check")
builder.add_node(inventory_agent, "inventory_check")
builder.add_node(shipping_agent, "shipping")
builder.add_node(refund_agent, "refund")
# Conditional edges out of risk analysis
builder.add_edge("risk_analysis", "manual_review", condition=is_high_value)
builder.add_edge("risk_analysis", "fraud_check", condition=is_suspicious)
builder.add_edge("risk_analysis", "inventory_check", condition=is_low_risk)
# Review / fraud check → inventory
builder.add_edge("manual_review", "inventory_check")
builder.add_edge("fraud_check", "inventory_check")
# Inventory → ship or refund
builder.add_edge("inventory_check", "shipping", condition=is_in_stock)
builder.add_edge("inventory_check", "refund", condition=is_out_of_stock)
builder.set_entry_point("risk_analysis")
graph = builder.build()
print("✓ Graph built")
def show_path(result):
path = " → ".join(node.node_id for node in result.execution_order)
print(f"Status: {result.status}\nPath: {path}")
last = result.execution_order[-1].node_id
print(f"\nFinal step ({last}):\n{result.results[last].result}")UserWarning: … no max_node_executions or execution_timeout set …
✓ Graph builtThe warning is expected. Strands is reminding you that this graph has no overall execution limit; for a forward-only graph like this one that's fine, because every run ends at shipping or refund. For cyclic graphs, set limits with builder.set_max_node_executions(...) or an execution timeout.
add_node(agent, "node_id") registers each agent under a short ID. add_edge(from, to, condition=...) draws an arrow; with a condition, the arrow is only followed when the function returns True. Edges without a condition are always followed. set_entry_point says where every run starts, and build() validates the setup (at least one node, a valid entry point) and returns a runnable graph. show_path prints the route an order took, using result.execution_order, and the last node's output.
Process orders: each one takes a different path
# High value → manual review → inventory → shipping
result = graph("Order #12345: $1,500 laptop from an existing customer with 3 previous orders; billing and shipping address match")
show_path(result)Status: Status.COMPLETED Path: risk_analysis → manual_review → inventory_check → shipping Final step (shipping): Shipping label generated for Order #12345 (1 × laptop). Tracking number: … Estimated delivery: 3–5 business days.
# Low risk → inventory → shipping
result = graph("Order #67890: $45 book from a returning customer with 50+ previous orders")
show_path(result)Status: Status.COMPLETED Path: risk_analysis → inventory_check → shipping
# Suspicious → fraud check → inventory → refund (limited edition is out of stock)
result = graph(
"Order #11111: $800 limited edition headphones, account created 5 minutes ago, "
"shipping to a different country than the billing address"
)
show_path(result)Status: Status.COMPLETED Path: risk_analysis → fraud_check → inventory_check → refund Final step (refund): Refund initiated for Order #11111: limited edition headphones are out of stock. The customer has been notified …
Same graph, three orders, three different paths, and each path is fully explained by the edges you wrote. Run the same order again and it takes the same path.
Send an order through the graph
Use graphs wherever you have an established procedure: e-commerce order processing, compliance workflows, approval chains, content moderation, or anywhere you must prove that certain checks always happen in a certain order. Don't use one when the path can't be predicted up front; that's a swarm's job.
Choosing a pattern
| Pattern | Who decides the path? | Best for | Trade-off |
|---|---|---|---|
| Supervisor–worker | The supervisor agent | Centralised coordination with one synthesised answer; parallel research | Supervisor is a bottleneck and a single point of failure |
| Agent as tool | The orchestrator picks from available tools, and stays in control | Reusable agents across different systems; simple delegation | Agents only communicate through the orchestrator; nested calls add latency |
| Swarm | The agents, through handoffs | Creative or iterative work where the path can't be predicted | Harder to debug; the order of work varies between runs |
| Graph (DAG) | You, in code (conditional edges) | Fixed, auditable procedures and compliance workflows | Every possible path must be defined up front |
</>Patterns combine
Most production systems mix them. A supervisor might coordinate workers that run in parallel; a graph node might itself be a swarm; a specialist used as a tool might be a small graph. Match the pattern to your constraints, one layer at a time.
When multi-agent is not the answer
You now know supervisor–worker, swarms and graphs. Here's what doesn't get said often enough: most problems don't need multiple agents.
It's a common story. Someone learns about multi-agent systems and immediately rebuilds a working single-agent app as five agents coordinating through a graph. The demo looks impressive. Then it goes live.
Debugging becomes a nightmare. Which agent failed? Was it agent three's input or agent two's output? The logs show five execution paths and you're tracing which one ran. The single agent used to answer in 3 seconds; the multi-agent version takes 12 because of coordination overhead. And the error rate went up, because there are now five points of failure instead of one.
✦Think of it like this
You don't hire a committee to fix a leaking tap. A good plumber with the right toolkit is faster, cheaper and easier to hold accountable. You call in a team for building a house, not for every job.
A single agent with good tools beats a poorly designed multi-agent system. Before you design something complex, try giving one agent the right tools. Can it call your APIs? Query your databases? Run code? Read your documentation? If you structure the tools well, most problems fit in one agent's context window.
The patterns in this section solve real problems, but they aren't default architectures. You'll know you need multiple agents when you hit actual limits:
- 1
The context window keeps filling up
You're processing thousands of documents and the agent loses track.
- 2
You need true parallelism
Waiting for three sequential 30-second API calls is too slow for your users.
- 3
The expertise genuinely doesn't fit in one prompt
The roles need such different instructions and tools that one prompt becomes a muddle.
iThe rule of thumb
Start with one agent, give it tools, and test it. Only add agents when one agent clearly can't cope. Three agents that work reliably beat ten that don't.
Do you need a team? Which one?
Recap and self-check
You can now build teams of agents, and, just as importantly, decide when not to.
You started with the basic question of when several agents beat one. You built a supervisor that fans questions out to news, financial and sentiment workers and synthesises one answer, and a teacher that reuses specialist tutors as tools. You followed data through a multi-agent system and learned the three ways agents communicate: sequential for dependencies, parallel for speed (and voting for accuracy), and collaborative for negotiating trade-offs. Then you organised whole systems: a swarm that hands work around when the path can't be predicted, and a graph with explicit, auditable routes. Most production systems combine these patterns; the skill is matching the pattern to your constraints.
You should now be able to explain
| Idea | In one line |
|---|---|
| Signs you need a team | Context overload, mediocre results everywhere, no parallelism, hard to debug |
| Four aspects | Orchestration, specialisation, collaboration, coordination |
| Supervisor–worker | A coordinator fans out to specialists, who never talk to each other, then synthesises one answer |
agent.as_tool() | Turns an agent into a tool; its name and description tell the caller when to use it |
| Agent as tool | Build a specialist once, reuse it anywhere; the orchestrator stays in control |
| Sequential / parallel / collaborative | Sum of times / slowest time / several rounds of calls |
| Voting | Independent agents outvote each other's mistakes; correlated agents don't |
| Swarm | Agents hand off with handoff_to_agent, share context, and stop when nobody hands off; limits prevent loops |
| Graph | Nodes, edges with conditions, dependencies; routing lives in code, so it's auditable |
| When not to | One agent with good tools first; add agents only at a real limit |
When something goes wrong
| What you see | What it usually means and how to fix it |
|---|---|
| "TAVILY_API_KEY not set", or the news agent says it can't search | Export the key before starting Jupyter, or set it in the commented lines of the keys cell, then re-run the cells that create the agents. |
| Financial numbers show "N/A" | Yahoo Finance left that field empty for this ticker. _num handles it; the agent works with what's available. |
ThrottlingException from Bedrock | Parallel workers and swarms make many calls quickly. Wait and retry, run fewer questions at once, or ask for a higher quota. |
| The teacher answers the question itself | Its prompt says "Never solve problems yourself". Check the prompt ran unchanged and the tutors are in tools=[...]. |
A graph run stops after risk_analysis | No condition matched: the risk analyser didn't end with a valid RISK_CATEGORY: line. Print result.results["risk_analysis"].result to see what it wrote. |
The swarm stops early (Status.FAILED) with a repetitive-handoff, max-handoffs or timeout reason | The safety limits did their job. Make the prompts clearer about who to hand off to and when to finish, or raise the limits slightly. |
| The swarm takes several minutes | Normal: 10 or more full agent turns. Each costs model calls, so don't run it in a loop. |
iNothing to clean up
These notebooks only call Bedrock (and the Tavily and Yahoo Finance APIs). They create no AWS resources, so there's nothing to delete. The cost is model usage, which is highest for the swarm.
Self-check
Ten quick questions. Pick an answer to see why it's right or wrong.
Score: 0 of 10 answered
→Up next: Section 5, MCP and A2A
So far your agents have lived in one notebook. Next you'll connect them across processes with two open protocols: MCP, which connects an agent to tools and data served by other programs, and A2A, which lets independent agents discover and call each other. Folder: section-05-mcp-and-a2a.
Glossary
Every underlined word on this page, in one place.