Lesson 5: Control Your Agent With Hooks
Code for this lesson: samples/05-hooks
Rules That Don’t Depend on the Model
Section titled “Rules That Don’t Depend on the Model”Hooks inject code at lifecycle events (before/after tool calls, before/after the agent loop) without changing the agent’s logic. Unlike tools, which the model decides to use, hooks fire automatically every time regardless of what the model reasons. That’s the key distinction: a prompt is a request, a hook is a guarantee.
A runaway loop could call the same tool dozens of times. A model might attempt a destructive operation without asking. Prompting asks the model to behave. Hooks guarantee it.
Hook Architecture
Section titled “Hook Architecture”- Subclass
HookProviderto define hooks - Register callbacks for lifecycle events in
register_hooks() - Multiple hooks can listen to the same event (stackable)
event.interrupt()pauses the loop for human inputevent.cancel_toolblocks tool execution and feeds a message back to the model explaining why
Example: Human Approval for Deletions
Section titled “Example: Human Approval for Deletions”from strands import Agent, toolfrom strands.hooks import BeforeToolCallEvent, HookProvider, HookRegistry
class DeleteApprovalHook(HookProvider): """Intercepts delete operations for human approval."""
def register_hooks(self, registry: HookRegistry) -> None: registry.add_callback(BeforeToolCallEvent, self.check_delete)
def check_delete(self, event: BeforeToolCallEvent) -> None: if event.tool_use["name"] != "delete_file": return
approval = event.interrupt( "delete-approval", reason={"path": event.tool_use["input"]["path"]} )
if approval.lower() != "y": event.cancel_tool = "User denied file deletion"
agent = Agent( tools=[list_files, read_file, write_file, delete_file], hooks=[DeleteApprovalHook()],)event.interrupt() pauses agent execution and returns control to the caller. That’s what makes approval workflows possible: the agent doesn’t proceed until a human answers.
Example: Rate Limiting Tool Calls
Section titled “Example: Rate Limiting Tool Calls”from strands.hooks import BeforeInvocationEvent, BeforeToolCallEvent, HookProvider, HookRegistry
class LimitToolCounts(HookProvider): def __init__(self, max_calls: int = 3): self.max_calls = max_calls self.counts: dict[str, int] = {}
def register_hooks(self, registry: HookRegistry) -> None: registry.add_callback(BeforeInvocationEvent, self.reset) registry.add_callback(BeforeToolCallEvent, self.check)
def reset(self, event: BeforeInvocationEvent) -> None: self.counts = {}
def check(self, event: BeforeToolCallEvent) -> None: name = event.tool_use["name"] self.counts[name] = self.counts.get(name, 0) + 1 if self.counts[name] > self.max_calls: event.cancel_tool = ( f"'{name}' hit the {self.max_calls}-call limit. " "Do NOT call this tool again." )
agent = Agent(tools=[get_weather], hooks=[LimitToolCounts(max_calls=3)])Notice the counter resets on BeforeInvocationEvent. Rate limits are per request, not for the agent’s lifetime. And because cancel_tool sends a message back to the model, the model learns why the call was blocked and stops retrying.
When to Use Hooks
Section titled “When to Use Hooks”- Rate limit tool usage to prevent runaway loops
- Require human approval before destructive operations
- Log every tool call for audit trails
- Validate tool inputs and outputs against business rules
- Enforce access control on sensitive tools
Hooks don’t touch tools or prompts. They’re a separate, reusable layer you can drop onto any agent.