Skip to content

Lesson 7: Improve Agent Reliability with Strands Steering

Play

Watch on YouTube

Code for this lesson: samples/07-steering

Instructions in prompts aren’t always reliably followed, especially as context grows. Steering lets you inspect and influence agent behavior at runtime. A steering handler looks at what the agent is about to do and returns one of three outcomes: Proceed, Guide (send feedback to the model and let it try again), or Interrupt (pause for a human).

In a published benchmark, Strands steering hooks achieved a 100% pass rate across 600 evaluation runs, compared to 82.5% for prompt-based instructions and 80.8% for graph-based workflows.

TypeHow It WorksUse Case
Deterministic (SteeringHandler)Python logic inspects the events ledgerEnforcing workflow ordering, validating parameters
LLM-based (LLMSteeringHandler)A second agent judges the outputTone/policy compliance, nuanced quality checks

Deterministic Steering: Enforce the Refund Workflow

Section titled “Deterministic Steering: Enforce the Refund Workflow”

Our customer service agent should never process a refund without first looking up the customer and checking their order history. Rather than hoping the prompt gets followed, a SteeringHandler checks the ledger of previous tool calls before process_refund is allowed to run:

from strands.vended_plugins.steering import (
SteeringHandler, Proceed, Guide, ToolSteeringAction, LedgerProvider,
)
class RefundWorkflowHandler(SteeringHandler):
name = "refund-workflow"
def __init__(self):
super().__init__(context_providers=[LedgerProvider()])
async def steer_before_tool(self, *, agent, tool_use, **kwargs) -> ToolSteeringAction:
if tool_use.get("name") != "process_refund":
return Proceed(reason="Not a refund operation")
ledger = self.steering_context.data.get("ledger", {})
tool_calls = ledger.get("tool_calls", [])
# Must look up customer first
customer_verified = any(
c["tool_name"] == "lookup_customer" and c["status"] == "success"
for c in tool_calls
)
if not customer_verified:
return Guide(reason="You must look up the customer first.")
# Must check order history
order_checked = any(
c["tool_name"] == "get_order_history" and c["status"] == "success"
for c in tool_calls
)
if not order_checked:
return Guide(reason="You must check order history first.")
return Proceed(reason="Refund workflow validated")

When the handler returns Guide, the tool call is blocked and the reason is fed back to the model, which then does the missing step and tries again.

Some rules can’t be expressed as Python conditionals. “Don’t overpromise timelines” requires judgment. An LLMSteeringHandler uses a second model to evaluate the response against a policy and guide the agent if it falls short:

from strands.vended_plugins.steering import LLMSteeringHandler
class ToneGuardrailHandler(LLMSteeringHandler):
name = "tone-guardrail"
def __init__(self):
super().__init__(
system_prompt="""Evaluate the customer service response against these policies:
- Don't overpromise timelines
- Acknowledge customer frustration
- Don't offer unauthorized compensation
- Keep responses concise
If violated, provide specific guidance on what to fix."""
)

Steering handlers are plugins, so they stack alongside skills and any other plugin:

from strands import Agent
agent = Agent(
tools=[lookup_customer, get_order_history, process_refund],
plugins=[
skills_plugin, # On-demand workflow instructions
RefundWorkflowHandler(), # Deterministic: enforce refund steps
tone_handler, # LLM-based: enforce communication quality
],
system_prompt=SYSTEM_PROMPT,
)

📂 customer_service_steering.py · steering_handlers.py