This tutorial is a hands-on tour of the Strands Agents hooks system, the composable, type-safe extension point that fires callbacks throughout an agent's request lifecycle. By the end, you will know where every hook fires, how to register one, and how to use writable hook fields (messages, cancel_tool, retry, resume) to control agent behavior at runtime.
| Information | Details |
|---|---|
| Strands Features | Hooks, HookProvider, AgentInitializedEvent, BeforeInvocationEvent, AfterInvocationEvent, BeforeModelCallEvent, AfterModelCallEvent, BeforeToolCallEvent, AfterToolCallEvent, MessageAddedEvent, invocation_state |
| Agent Pattern | Single agent with lifecycle observers and interceptors |
| Tools | Small custom tools used to exercise the tool events |
| Model | Claude Haiku 4.5 on Amazon Bedrock (any Strands-supported model works) |
The tutorial is split into three sequential notebooks, each self-contained and independently runnable:
- 01_hooks_basics.ipynb - Register callbacks for
BeforeModelCallEventandAfterModelCallEventwithagent.add_hook()to measure model latency, useAgentInitializedEventfor one-time setup, and trace the full event lifecycle across model and tool calls. - 02_hooks_intercept.ipynb - Use
BeforeToolCallEvent.cancel_toolto block a tool,AfterToolCallEvent.retryto re-run a failed call,AfterInvocationEvent.resumeto auto-continue the agent, andBeforeInvocationEvent.messagesto redact input before the model sees it. - 03_hooks_packaging.ipynb - Pass per-invocation context between hooks via
invocation_state, package hooks into a reusableHookProviderclass, and combine everything into a single observability and control plugin.
Agent(...) ──► AgentInitializedEvent (once, at construction)
agent("Summarize the weather in Seattle")
│
├─ BeforeInvocationEvent (once per request)
│ │ writable: messages
│ └─ MessageAddedEvent (user input added to history)
│
├─ BeforeModelCallEvent (each time the model is invoked)
│ └─ AfterModelCallEvent writable: retry
│ └─ MessageAddedEvent (model's assistant turn)
│
├─ BeforeToolCallEvent (for every tool the model requests)
│ │ writable: cancel_tool, selected_tool, tool_use
│ └─ AfterToolCallEvent writable: result, retry (reverse order)
│ └─ MessageAddedEvent (tool result appended)
│
├─ (loop: BeforeModelCall → AfterModelCall → tool calls → ... until end_turn)
│
└─ AfterInvocationEvent (once per request, reverse order)
writable: resume
- Python 3.10 or later.
- An AWS account with Amazon Bedrock model access (Claude Haiku 4.5 by default; see the notebook for how to swap models).
- Basic familiarity with Strands Agents (Quickstart Guide).
16-hooks-lifecycle/
├── README.md
├── requirements.txt
├── 01_hooks_basics.ipynb
├── 02_hooks_intercept.ipynb
└── 03_hooks_packaging.ipynb
| File | Description |
|---|---|
| 01_hooks_basics.ipynb | Your first hook and the full event lifecycle. |
| 02_hooks_intercept.ipynb | Writable fields: cancel, retry, resume, and redaction. |
| 03_hooks_packaging.ipynb | Cross-hook data sharing and reusable HookProvider packaging. |
pip install -r requirements.txt- Start with 01_hooks_basics.ipynb and work through the notebooks in order.
- Each notebook is self-contained: run cells sequentially within any single notebook.
- Hook: a typed callback the agent invokes when a specific lifecycle event fires.
- HookEvent: a strongly-typed event object carrying data for that lifecycle stage. Most fields are read-only; a handful are intentionally writable to let hooks control agent behavior.
- HookProvider: a class that bundles multiple callbacks so you can ship hooks as reusable components.
invocation_state: a per-invocation dictionary every hook event exposes. It is the clean way to share data between hooks within a single request.- Reverse ordering:
After*Eventcallbacks fire in reverse registration order so outer hooks see inner hooks' side-effects (thinktry/finally).
- Hook callbacks may be synchronous or
async. Strands will await async callbacks automatically. - Setting
AfterModelCallEvent.retry = TrueorAfterToolCallEvent.retry = Truecauses the call to re-execute with the same inputs. Always pair retries with a counter to avoid infinite loops. AfterInvocationEvent.resumetriggers a brand-new invocation cycle (including a freshBeforeInvocationEvent); guard it with a termination condition held on yourHookProviderinstance, not ininvocation_state(which resets on each new invocation).Agent.structured_output(...)does not fireBeforeModelCallEventorAfterModelCallEvent. UseBeforeInvocationEvent/AfterInvocationEventif you need to hook that code path.- When a retry is requested, intermediate streaming events from the discarded attempt have already been emitted to
stream_asyncconsumers. Only the final attempt's result is added to conversation history.
- Hooks Concept Guide
strands.hooks.eventsAPI referencestrands.hooks.registryAPI reference- Plugins: package hooks plus tools plus config together.
- See
13-human-in-the-loopfor how interrupts compose with hooks (BeforeToolCallEventis_Interruptible). - See
08-observabilityfor production-grade tracing built on the same hook surface. - Explore multi-agent hooks (
BeforeMultiAgentInvocationEvent,BeforeNodeCallEvent,AfterNodeCallEvent, andcancel_node) when moving toGraphorSwarmorchestration.