Skip to content

Hooks

You use hooks to observe and modify agent behavior at runtime. Hooks fire at defined points in the agent loop: before and after model calls, tool calls, and full agent invocations. They are code, not prompt instructions, so they enforce behavior reliably.

This page covers the mental model for hooks. See the usage guide for registration patterns and the cookbook for ready-made recipes.

Hooks subscribe to lifecycle events. Each event type has a before/after pair (for example, BeforeToolCallEvent and AfterToolCallEvent). When the agent reaches that point in the loop, it fires the event and passes an event object to every registered callback.

Callbacks receive a strongly typed event object carrying context for that stage of the lifecycle. Most event properties are read-only, but specific events expose writable properties that let you modify behavior: cancel a tool call, retry a model call, or rewrite a result.

Here is the simplest case: a callback that prints the tool name before every tool call.

from strands import Agent
from strands.hooks import BeforeToolCallEvent
agent = Agent()
def log_tool_name(event: BeforeToolCallEvent) -> None:
print(f"Calling tool: {event.tool_use['name']}")
agent.add_hook(log_tool_name)

The callback runs in the agent loop. It receives the event, reads (or writes) properties, and returns. Both Python and TypeScript support async callbacks (async def / Promise<void>).

The following diagram shows when hook events fire during a typical agent invocation where tools are called.

flowchart LR
subgraph Start["Request Start Events"]
direction TB
BeforeInvocationEvent["BeforeInvocationEvent"]
StartMessage["MessageAddedEvent"]
BeforeInvocationEvent --> StartMessage
end
subgraph Model["Model Events"]
direction TB
BeforeModelCallEvent["BeforeModelCallEvent"]
AfterModelCallEvent["AfterModelCallEvent"]
ModelMessage["MessageAddedEvent"]
BeforeModelCallEvent --> AfterModelCallEvent
AfterModelCallEvent --> ModelMessage
end
subgraph Tool["Tool Events"]
direction TB
BeforeToolCallEvent["BeforeToolCallEvent"]
AfterToolCallEvent["AfterToolCallEvent"]
ToolMessage["MessageAddedEvent"]
BeforeToolCallEvent --> AfterToolCallEvent
AfterToolCallEvent --> ToolMessage
end
subgraph End["Request End Events"]
direction TB
AfterInvocationEvent["AfterInvocationEvent"]
end
Start --> Model
Model <--> Tool
Tool --> End

The following diagram shows when multi-agent hook events fire during orchestrator execution.

flowchart LR
subgraph Init["Initialization"]
direction TB
MultiAgentInitializedEvent["MultiAgentInitializedEvent"]
end
subgraph Invocation["Invocation Lifecycle"]
direction TB
BeforeMultiAgentInvocationEvent["BeforeMultiAgentInvocationEvent"]
AfterMultiAgentInvocationEvent["AfterMultiAgentInvocationEvent"]
BeforeMultiAgentInvocationEvent --> NodeExecution
NodeExecution --> AfterMultiAgentInvocationEvent
end
subgraph NodeExecution["Node Execution (Repeated)"]
direction TB
BeforeNodeCallEvent["BeforeNodeCallEvent"]
AfterNodeCallEvent["AfterNodeCallEvent"]
BeforeNodeCallEvent --> AfterNodeCallEvent
end
Init --> Invocation

TypeScript includes additional streaming events (ModelStreamUpdateEvent, ContentBlockEvent, ToolStreamUpdateEvent, and others) not present in Python. Features marked Python-only in the cookbook are being added to TypeScript.

EventDescription
AgentInitializedEventFired when the agent finishes construction at the end of its constructor
BeforeInvocationEventFired at the start of a new agent invocation
AfterInvocationEventFired at the end of an invocation, regardless of success or failure. Uses reverse callback ordering
MessageAddedEventFired when a message is added to the agent’s conversation messages
BeforeModelCallEventFired before the model is called for inference
AfterModelCallEventFired after model calling completes. Uses reverse callback ordering
BeforeToolCallEventFired before a tool is called
AfterToolCallEventFired after tool calling completes. Uses reverse callback ordering
EventDescription
MultiAgentInitializedEventFired when the multi-agent orchestrator is initialized
BeforeMultiAgentInvocationEventFired before orchestrator execution starts
AfterMultiAgentInvocationEventFired after orchestrator execution completes. Uses reverse callback ordering
BeforeNodeCallEventFired before individual node execution starts
AfterNodeCallEventFired after individual node execution completes. Uses reverse callback ordering

Most event properties are read-only. The following events expose writable properties that change agent behavior.

EventWritable propertyEffectCookbook recipe
BeforeToolCallEventcancel_toolCancels tool calling with a message returned to the modelLimit tool call counts
BeforeToolCallEventselected_toolReplaces the tool to be called
BeforeToolCallEventtool_useModifies tool parameters before callingFix tool arguments
AfterModelCallEventretryRetries the model callRetry model calls
AfterToolCallEventresultRewrites the tool result
AfterToolCallEventretryRetries the tool callRetry tool calls
AfterInvocationEventresumeTriggers a follow-up invocation with new inputResume after invocation

You can also read AfterToolCallEvent.exception to inspect the original error if the tool raised one.

Hooks run synchronously in the agent loop. Every millisecond a hook spends is a millisecond added to the agent’s response time. Keep this in mind when deciding where to put logic.

Long-running checks add latency. If your hook makes a database lookup or API call, that latency applies to every model call or tool call (depending on the event). For expensive validation, consider these alternatives:

  • Cache results in the hook’s instance state and refresh periodically
  • Move the check into the tool itself, where it runs only when that tool is called
  • Use async patterns (Python hooks support async def callbacks)

Hooks vs. prompts. Prompts suggest behavior to the model. Hooks enforce it. A system prompt saying “never call the delete tool” is guidance the model might ignore under pressure. A BeforeToolCallEvent hook that cancels delete calls is a guarantee. Use prompts for guidance, hooks for guarantees.

Hooks vs. guardrails. Hooks are a mechanism. Guardrails are a policy. You implement guardrails using hooks (among other things). A “PII detection guardrail” is a policy decision; the BeforeToolCallEvent callback that scans inputs and cancels calls containing PII is the hook that enforces it.

Why lifecycle hooks and not middleware chains. We chose lifecycle hooks over middleware chains because hooks compose without ordering dependencies. In a middleware chain, the order you register middleware determines what runs first, and changing the order changes behavior. With hooks, every callback for a given event type runs independently. Before-event callbacks run in registration order; after-event callbacks run in reverse order (unwinding). This makes it safe to combine hooks from multiple plugins without worrying about interference.