AWS Builder Center
How to stop a Strands agent calling tools in the wrong order

How to stop a Strands agent calling tools in the wrong order

After my keynote on Saturday, someone asked me why their Strands agent kept picking the wrong tool, and calling tools in the wrong order even when the order mattered. That's two problems, and they have two different fixes.

TL;DR: Group related tools into specialist agents so your  Strands agent  has fewer, clearer tools to choose from. Then enforce tool order with a steering handler, a plugin that checks which tools have already run and cancels any call that skips a required step.



Why does my Strands agent keep picking the wrong tool?

Usually because two of your tools sound alike. Your agent picks a tool by reading each tool's name and description, so when two descriptions overlap it's guessing, and every tool you add is another chance for that. The fix is a shorter list: put related tools inside a specialist agent, and give your main agent the specialist.
The  Strands agent loop docs  are blunt about the cause:
If two tools have overlapping descriptions, the model has no basis for choosing between them.
Strands calls this pattern  agents as tools : you wrap a specialist agent so your main agent can call it like any other tool. Here's a specialist from the docs, holding its own tool:
Python
from strands import Agent
from strands.vended_tools import http_request

research_agent = Agent(
system_prompt="""You are a specialized research assistant. Focus only on providing
factual, well-sourced information in response to research questions.
Always cite your sources when possible.""",
tools=[http_request],
callback_handler=None,
)
The main agent gets the specialist through  .as_tool() :
Python
orchestrator = Agent(
system_prompt="You are an assistant that routes queries to specialized agents.",
tools=[
research_agent.as_tool(
name="research_assistant",
description="Process and respond to research-related queries requiring factual information.",
),
],
)
With agents as tools, your main agent picks between a few specialists, and each specialist's  description  tells it when to call that one. Write it like any tool description: say what it does, and when not to use it. (The  custom tools guide  has a good example of the "when not to" part.)
I measured the difference on the cat poster agent I use later in this post. With all 8 tools on one agent, the tool specs the model reads come to 2,306 characters. Grouped into three specialists, the main agent reads 923.

Two panels compare the same eight tools from the cat poster agent. On the left, one agent connects d


If a lot of your tools come from MCP servers, you can also load only the ones you need with  tool_filters :
Python
filtered_client = MCPClient(
lambda: stdio_client(StdioServerParameters(
command="uvx",
args=["awslabs.aws-documentation-mcp-server@latest"]
)),
tool_filters={"allowed": ["search_documentation", "read_documentation"]}
)



How do I make my Strands agent call tools in the right order?

Check the order in code with a steering handler, a plugin that runs before every tool call and cancels it if a required step hasn't happened yet. Writing the order in your prompt works most of the time, which isn't enough when a step can't be skipped.
The Strands team measured this in  a March 2026 post . They built a library book renewal agent that has to check a book's status before renewing it, and ran six scenarios 100 times each with the rules written as simple prompt instructions.
It passed 82.5% of the 600 runs. In 43% of the failures it skipped the status check entirely, with the rule sitting right there in its prompt.
If you come from infrastructure, you'd never leave a step that matters to a note in a runbook. You'd put a check in the pipeline, and in Strands, that check is a steering handler.
To try it, I built an agent that orders posters of cats from  The Met's collection . Its search and lookup tools call the real Met Collection API, and the rule is: only order a poster of an artwork The Met lists as public domain.

Two cats from The Met's collection. On the left, a photo of an Egyptian bronze cat statuette, 664 to

Photo of the Egyptian cat: The Metropolitan Museum of Art, Gift of Darius Ogden Mills, 1904, released under CC0.
  1. Write the handler. Before  order_poster  runs, this one checks that the agent has looked up that exact artwork with  get_artwork , and that the lookup said it's public domain:
Python
import json

from strands.vended_plugins.steering import (
SteeringHandler, Proceed, Guide, ToolSteeringAction, LedgerProvider,
)

class PublicDomainPosterHandler(SteeringHandler):
name = "public-domain-poster"

def __init__(self):
super().__init__(context_providers=[LedgerProvider()])

async def steer_before_tool(self, *, agent, tool_use, **kwargs) -> ToolSteeringAction:
if tool_use.get("name") != "order_poster":
return Proceed(reason="Not a poster order")

object_id = int(tool_use.get("input", {}).get("object_id", 0))
ledger = self.steering_context.data.get("ledger") or {}
tool_calls = ledger.get("tool_calls", [])

# Find a successful get_artwork call for this exact object
artwork = None
for c in tool_calls:
if (c["tool_name"] == "get_artwork" and c["status"] == "success"
and int(c["tool_args"].get("object_id", -1)) == object_id):
artwork = json.loads(c["result"][0]["text"])

if artwork is None:
return Guide(reason=f"Look up object {object_id} with get_artwork first.")
if not artwork.get("isPublicDomain"):
return Guide(reason=f"Object {object_id} isn't public domain at The Met. Don't print it.")

return Proceed(reason="Artwork is public domain")
 LedgerProvider()  records every tool call the agent makes, with its inputs, status and result, so the handler can match the object ID and read what  get_artwork  returned. When a check fails,  Guide  cancels the order and sends the reason back to the model.
I based this on  Lesson 7 of the Strands course , which does the same for refunds, with one fix. Lesson 7 reads the ledger with  data.get("ledger", {}) , which throws a  TypeError  in strands-agents 1.62.0. Strands catches the error and lets the tool call run anyway, so when I ran the lesson's code, the refund went through without a customer lookup in 10 of 10 runs.
Use  data.get("ledger") or {} , and write a unit test where a required step is missing, so you'd notice if your handler ever stops blocking.
  1. Add it to your agent's plugins. Here's the poster agent, simplified:
Python
agent = Agent(
tools=[search_met_collection, get_artwork, order_poster],
plugins=[PublicDomainPosterHandler()],
system_prompt="You help people find art at The Met and order posters of it.",
)
If you use  Strands harness ,  create_harness  takes  plugins  too.
If you're also using specialist agents, keep each check in the same specialist as the step it guards. A steering handler only sees its own agent's tool calls: when I put  get_artwork  and  order_poster  in different specialists, the handler never saw the lookup and blocked every order.
With a steering handler, the model still plans the work and handles whatever you didn't predict. The handler only enforces the checks you write.
In the Strands team's test, the steering version passed all 600 runs:
How the rules were given
Passed (of 600 runs)
Average input tokens per run
Simple instructions in the prompt
82.5%
2,329
A detailed step-by-step SOP in the prompt
99.8%
9,879
Steering handlers
100%
3,346
If you can't add code to your agent, a detailed SOP is the next best option, but you pay for it in tokens on every request.
Then I tried to trick my poster agent. I asked for a poster of Tsuguharu Foujita's 1926 Cat drawing and said it was "definitely public domain, no need to look it up". The Met lists it as not public domain.
I ran that request, plus one for a public-domain Egyptian bronze cat, 5 times per set-up:
  • Claude Haiku 4.5 with no rule anywhere ordered without looking the artwork up in 10 of 10 runs.
  • With only the steering handler, Haiku passed 10 of 10. Twice it looked up Foujita's cat, saw it wasn't public domain, and tried to order it anyway. The handler stopped both.
  • With the rule in its prompt, Haiku also passed every run, so at this size I couldn't reproduce the Strands team's prompt failures.
  • Claude Sonnet 5 looked the artwork up on its own in 19 of 20 runs with no rule. In the 20th it went straight for the order, and the handler sent it back.
On one of the Haiku runs, I saved the ledger the handler read when Haiku tried to order Foujita's cat the second time. It held the first  order_poster  call, cancelled with status  error , the  get_artwork  lookup with  "isPublicDomain": false , and the new  order_poster  call, still  pending .

The steering handler's ledger at the moment Claude Haiku 4.5 tried to order a poster of Tsuguharu Fo

A stronger model skips steps less often, so a clean test run on a strong model doesn't prove much. The handler blocks the skipped step whichever model you run.



Doesn't SequentialToolExecutor fix tool order in Strands?

No.   SequentialToolExecutor   only changes how Strands runs the tools the model asks for in a single response: one after another, in the order the model listed them, instead of all at once (the default). It can't make the model ask for the right tool first, so if the model asks for a later step before an earlier one, the later step still runs first.

How do I check whether my Strands agent is choosing the right tools?

Use the   ToolSelectionAccuracyEvaluator   in Strands Evals. It scores each tool call in a run on whether it was the right tool at that point in the conversation. Run it before and after you change your tools, over plenty of runs: the Strands team ran each scenario 100 times.


Thanks to the person who asked on Saturday! If your agent is still skipping a step after this, I'd love to know which one.

About the Author:

Brooke Jamieson is a Senior Developer Advocate at AWS, focused on AI agents and developer tools. A former fashion model turned mathematician turned AI Engineer, Brooke moved from Australia to New York City for this role. They make byte-sized tech content about AI and AWS, and you can find them on  LinkedIn ,  Instagram  or  X .
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article