22
33Provides modular prompting through contextual guidance that appears when relevant,
44rather than front-loading all instructions. Handlers integrate with the Strands hook
5- system to intercept tool calls and provide just-in-time feedback based on local context.
5+ system to intercept actions and provide just-in-time feedback based on local context.
66
77Architecture:
8- BeforeToolCallEvent → Context Callbacks → Update steering_context → steer () → SteeringAction
9- ↓ ↓ ↓ ↓ ↓
10- Hook triggered Populate context Handler evaluates Handler decides Action taken
8+ Hook Event → Context Callbacks → Update steering_context → steer_* () → SteeringAction
9+ ↓ ↓ ↓ ↓ ↓
10+ Hook triggered Populate context Handler evaluates Handler decides Action taken
1111
1212Lifecycle:
1313 1. Context callbacks update handler's steering_context on hook events
14- 2. BeforeToolCallEvent triggers steering evaluation via steer() method
15- 3. Handler accesses self.steering_context for guidance decisions
16- 4. SteeringAction determines tool execution: Proceed/Guide/Interrupt
14+ 2. BeforeToolCallEvent triggers steer_before_tool() for tool steering
15+ 3. AfterModelCallEvent triggers steer_after_model() for model steering
16+ 4. Handler accesses self.steering_context for guidance decisions
17+ 5. SteeringAction determines execution flow
1718
1819Implementation:
19- Subclass SteeringHandler and implement steer() method.
20- Pass context_callbacks in constructor to register context update functions.
20+ Subclass SteeringHandler and override steer_before_tool() and/or steer_after_model().
21+ Both methods have default implementations that return Proceed, so you only need to
22+ override the methods you want to customize.
23+ Pass context_providers in constructor to register context update functions.
2124 Each handler maintains isolated steering_context that persists across calls.
2225
23- SteeringAction handling:
26+ SteeringAction handling for steer_before_tool :
2427 Proceed: Tool executes immediately
2528 Guide: Tool cancelled, agent receives contextual feedback to explore alternatives
2629 Interrupt: Tool execution paused for human input via interrupt system
30+
31+ SteeringAction handling for steer_after_model:
32+ Proceed: Model response accepted without modification
33+ Guide: Discard model response and retry (message is dropped, model is called again)
34+ Interrupt: Model response handling paused for human input via interrupt system
2735"""
2836
2937import logging
30- from abc import ABC , abstractmethod
38+ from abc import ABC
3139from typing import TYPE_CHECKING , Any
3240
33- from ....hooks .events import BeforeToolCallEvent
41+ from ....hooks .events import AfterModelCallEvent , BeforeToolCallEvent
3442from ....hooks .registry import HookProvider , HookRegistry
43+ from ....types .content import Message
44+ from ....types .streaming import StopReason
3545from ....types .tools import ToolUse
36- from .action import Guide , Interrupt , Proceed , SteeringAction
46+ from .action import Guide , Interrupt , ModelSteeringAction , Proceed , ToolSteeringAction
3747from .context import SteeringContext , SteeringContextProvider
3848
3949if TYPE_CHECKING :
@@ -73,24 +83,29 @@ def register_hooks(self, registry: HookRegistry, **kwargs: Any) -> None:
7383 callback .event_type , lambda event , callback = callback : callback (event , self .steering_context )
7484 )
7585
76- # Register steering guidance
77- registry .add_callback (BeforeToolCallEvent , self ._provide_steering_guidance )
86+ # Register tool steering guidance
87+ registry .add_callback (BeforeToolCallEvent , self ._provide_tool_steering_guidance )
88+
89+ # Register model steering guidance
90+ registry .add_callback (AfterModelCallEvent , self ._provide_model_steering_guidance )
7891
79- async def _provide_steering_guidance (self , event : BeforeToolCallEvent ) -> None :
92+ async def _provide_tool_steering_guidance (self , event : BeforeToolCallEvent ) -> None :
8093 """Provide steering guidance for tool call."""
8194 tool_name = event .tool_use ["name" ]
82- logger .debug ("tool_name=<%s> | providing steering guidance" , tool_name )
95+ logger .debug ("tool_name=<%s> | providing tool steering guidance" , tool_name )
8396
8497 try :
85- action = await self .steer ( event .agent , event .tool_use )
98+ action = await self .steer_before_tool ( agent = event .agent , tool_use = event .tool_use )
8699 except Exception as e :
87- logger .debug ("tool_name=<%s>, error=<%s> | steering handler guidance failed" , tool_name , e )
100+ logger .debug ("tool_name=<%s>, error=<%s> | tool steering handler guidance failed" , tool_name , e )
88101 return
89102
90- self ._handle_steering_action (action , event , tool_name )
103+ self ._handle_tool_steering_action (action , event , tool_name )
91104
92- def _handle_steering_action (self , action : SteeringAction , event : BeforeToolCallEvent , tool_name : str ) -> None :
93- """Handle the steering action by modifying tool execution flow.
105+ def _handle_tool_steering_action (
106+ self , action : ToolSteeringAction , event : BeforeToolCallEvent , tool_name : str
107+ ) -> None :
108+ """Handle the steering action for tool calls by modifying tool execution flow.
94109
95110 Proceed: Tool executes normally
96111 Guide: Tool cancelled with contextual feedback for agent to consider alternatives
@@ -114,21 +129,91 @@ def _handle_steering_action(self, action: SteeringAction, event: BeforeToolCallE
114129 else :
115130 logger .debug ("tool_name=<%s> | tool call approved manually" , tool_name )
116131 else :
117- raise ValueError (f"Unknown steering action type: { action } " )
132+ raise ValueError (f"Unknown steering action type for tool call: { action } " )
133+
134+ async def _provide_model_steering_guidance (self , event : AfterModelCallEvent ) -> None :
135+ """Provide steering guidance for model response."""
136+ logger .debug ("providing model steering guidance" )
137+
138+ # Only steer on successful model responses
139+ if event .stop_response is None :
140+ logger .debug ("no stop response available | skipping model steering" )
141+ return
142+
143+ try :
144+ action = await self .steer_after_model (
145+ agent = event .agent , message = event .stop_response .message , stop_reason = event .stop_response .stop_reason
146+ )
147+ except Exception as e :
148+ logger .debug ("error=<%s> | model steering handler guidance failed" , e )
149+ return
150+
151+ await self ._handle_model_steering_action (action , event )
152+
153+ async def _handle_model_steering_action (self , action : ModelSteeringAction , event : AfterModelCallEvent ) -> None :
154+ """Handle the steering action for model responses by modifying response handling flow.
118155
119- @abstractmethod
120- async def steer (self , agent : "Agent" , tool_use : ToolUse , ** kwargs : Any ) -> SteeringAction :
121- """Provide contextual guidance to help agent navigate complex workflows.
156+ Proceed: Model response accepted without modification
157+ Guide: Discard model response and retry with guidance message added to conversation
158+ """
159+ if isinstance (action , Proceed ):
160+ logger .debug ("model response proceeding" )
161+ elif isinstance (action , Guide ):
162+ logger .debug ("model response guided (retrying): %s" , action .reason )
163+ # Set retry flag to discard current response
164+ event .retry = True
165+ # Add guidance message to agent's conversation so model sees it on retry
166+ await event .agent ._append_messages ({"role" : "user" , "content" : [{"text" : action .reason }]})
167+ logger .debug ("added guidance message to conversation for model retry" )
168+ else :
169+ raise ValueError (f"Unknown steering action type for model response: { action } " )
170+
171+ async def steer_before_tool (self , * , agent : "Agent" , tool_use : ToolUse , ** kwargs : Any ) -> ToolSteeringAction :
172+ """Provide contextual guidance before tool execution.
173+
174+ This method is called before a tool is executed, allowing the handler to:
175+ - Proceed: Allow tool execution to continue
176+ - Guide: Cancel tool and provide feedback for alternative approaches
177+ - Interrupt: Pause for human input before tool execution
122178
123179 Args:
124180 agent: The agent instance
125181 tool_use: The tool use object with name and arguments
126182 **kwargs: Additional keyword arguments for guidance evaluation
127183
128184 Returns:
129- SteeringAction indicating how to guide the agent's next action
185+ ToolSteeringAction indicating how to guide the tool execution
186+
187+ Note:
188+ Access steering context via self.steering_context
189+ Default implementation returns Proceed (allow tool execution)
190+ Override this method to implement custom tool steering logic
191+ """
192+ return Proceed (reason = "Default implementation: allowing tool execution" )
193+
194+ async def steer_after_model (
195+ self , * , agent : "Agent" , message : Message , stop_reason : StopReason , ** kwargs : Any
196+ ) -> ModelSteeringAction :
197+ """Provide contextual guidance after model response.
198+
199+ This method is called after the model generates a response, allowing the handler to:
200+ - Proceed: Accept the model response without modification
201+ - Guide: Discard the response and retry (message is dropped, model is called again)
202+
203+ Note: Interrupt is not supported for model steering as the model has already responded.
204+
205+ Args:
206+ agent: The agent instance
207+ message: The model's generated message
208+ stop_reason: The reason the model stopped generating
209+ **kwargs: Additional keyword arguments for guidance evaluation
210+
211+ Returns:
212+ ModelSteeringAction indicating how to handle the model response
130213
131214 Note:
132215 Access steering context via self.steering_context
216+ Default implementation returns Proceed (accept response as-is)
217+ Override this method to implement custom model steering logic
133218 """
134- ...
219+ return Proceed ( reason = "Default implementation: accepting model response" )
0 commit comments