Attach middleware to every agent invocation — custom or built-in.
- How to write a custom hook (
FingerprintHook) that counts tool calls and prints a summary at the end of each invocation - How to wire it in
config.yamlalongside built-in hooks — no Python glue code needed MaxToolCallsGuard— prevents Agent from infinite loop of tool calling. Sets max allowed calls per invocation.ToolNameSanitizer— some models inject extra tokens into tool names. This hook strips them so the agent can call tools correctly
Hooks are Python classes that implement the strands HookProvider interface. No decorator — just a register_hooks(self, registry) method:
class FingerprintHook(HookProvider):
def __init__(self) -> None:
self._tool_calls = 0
def register_hooks(self, registry: HookRegistry, **kwargs) -> None:
registry.add_callback(AfterToolCallEvent, self._on_after_tool)
registry.add_callback(AfterInvocationEvent, self._on_after_invocation)
def _on_after_tool(self, event: AfterToolCallEvent) -> None:
self._tool_calls += 1
def _on_after_invocation(self, event: AfterInvocationEvent) -> None:
print(f">>> THIS IS YOUR CUSTOM HOOK: Agent used {self._tool_calls} tools <<<")
self._tool_calls = 0 # reset for the next turnIn config.yaml, hooks are listed under the agent. They fire in order:
hooks:
- type: ./hooks.py:FingerprintHook # custom — from local file
- type: strands_compose.hooks:MaxToolCallsGuard
params:
max_calls: 5
- type: strands_compose.hooks:ToolNameSanitizerThe spec format for hooks is always module_or_file:ClassName — the class name is required (no bulk scan).
BeforeToolCallEventandAfterToolCallEventare the most common hook points. See strands docs for the full list.- Multiple hooks on the same agent compose in declaration order. Each fires independently.
MaxToolCallsGuarduses a two-phase approach: the first over-limit call cancels the tool and instructs the LLM to write a final answer (the loop continues so it gets one more turn). If the LLM ignores that and requests another tool, the event loop is hard-stopped. AWARNINGis logged on both phases.ToolNameSanitizeris often necessary — some models append extra tokens to tool names (e.g.search<|x|>instead ofsearch), causing strands to silently fail the tool lookup. The hook strips the artifacts before strands resolves the name.- The tools in
custom_tools.pyare mock stubs that return deterministic fake data so the example works without external APIs.
- AWS credentials configured (
aws configureor environment variables) - Dependencies installed:
uv sync
uv run python examples/05_hooks/main.pyAt the end you'll see our FingerprintHook log:
>>> THIS IS YOUR CUSTOM HOOK: Agent used N tools <<<
Research the impact of electric vehicles on city air quality. Be thorough.Find facts about Python programming and write a short summary.What do we know about climate change and renewable energy?
Strands agents log actions to the console through their default callback_handler.
If you want cleaner example output, set the handler to null in agent_kwargs for any agent:
agents:
my_agent:
agent_kwargs:
callback_handler: null # or ~