Skip to main content
Hooks intercept agent actions at lifecycle points so you can log, modify, or block them without changing agent code.
The user sends a request; hooks intercept tools and lifecycle steps without changing agent code.

Quick Start

1

Simple Usage

Register a hook with add_hook and any agent picks it up automatically:
Hook return values:
  • None or no return → Allow
  • False → Deny
  • "reason" → Deny with custom message
2

With HooksConfig

Attach a HooksConfig to a specific agent for scoped hooks:

Which Hook Point Should I Use?

Pick the lifecycle event that matches what you want to observe or block.

How It Works

Available Hook Events

The events most agents will ever need are the agent / tool / LLM / error / session ones — start here.
This is the core lifecycle subset. The SDK ships ~40 events in total — including plugin/system hooks (on_init, on_shutdown), tool-result persistence (tool_result_persist), message-level bot hooks (before_message, after_message, message_received, message_sending, message_sent), gateway hooks (gateway_start, gateway_stop), compaction hooks (before_compaction, after_compaction), permission/config/auth hooks (on_permission_ask, on_config, on_auth), schedule hooks (schedule_add, schedule_remove, schedule_trigger), background-job hooks (job_completed), kanban task hooks, and Claude-Code-parity events (user_prompt_submit, notification, subagent_stop, setup).See Hook Events for the complete reference with input dataclasses and examples for each.
on_retry is emitted once per retryable error, just before the back-off sleep. It runs whether the LLM call is agent.chat(...) (sync) or await agent.achat(...) (async). Sync-registered callbacks on the async path are run in a thread executor — they cannot block the event loop.
Two lookalike fields. Function-style hooks return HookResult and set modified_input to rewrite the payload. Shell-command hooks parse into HookOutput and use modified_data for the same purpose. When you write a hook in Python, always use HookResult(decision="allow", modified_input={...}) — the internal Agent code reads .modified_input on hook results.

Configuration Options

HooksConfig SDK Reference

Full parameter reference for HooksConfig

Common Patterns

Security Filtering

Audit Logging

Tool Matching with HookRegistry

HookEvent.BEFORE_TOOL and HookEvent.AFTER_TOOL now fire on both the sync (agent.chat(...)) and async (await agent.achat(...)) tool-execution paths. When a BEFORE_TOOL hook blocks a call, the tool returns "Execution of {tool_name} was blocked by security policy." on either path. AFTER_TOOL results aggregate context onto the tool output (string concat, or the _additional_context key on a dict result). The check costs nothing when no hooks are registered.

Best Practices

Hooks run synchronously before/after each operation. Avoid network calls or heavy computation inside hook functions — use async queues for heavy processing.
The simplest hook contract: return nothing (or None) to allow, return a string with a reason to block. This keeps hooks readable.
add_hook registers hooks globally — all agents in the process obey them. Use HooksConfig when you need different rules per agent.
By default, hook exceptions are logged as warnings and execution continues. Set agent._strict_hooks = True in tests so hook failures surface immediately as errors.
before_agent, after_agent, and BEFORE_TOOL_DEFINITIONS cost nothing when no hook is registered — the runtime checks has_hooks() before building the input (including os.getcwd(), the tools list, and any deep-copy of tool definitions) on both sync (chat) and async (achat) paths. Register these hooks in production without a per-turn overhead concern.

Hook Events

Complete list of ~40 events with input dataclasses and examples

Guardrails

Validate agent output quality with automatic retry

Callbacks

Observe agent events for UI and logging purposes