This page is the task-first guide. For the low-level type and payload inventory, see Hooks reference.
What this guide is for
Use this guide when you want to:- add approval gates
- log or audit agent behavior
- block unsafe tool inputs or outputs
- choose between in-process, command, and HTTP hook runtimes
Hook points
Eight extension points are available:| HookPoint | Classification | When it fires |
|---|---|---|
RunStarted | pre | Start of Agent::run(), before any LLM call |
PreLlmRequest | pre | Before each LLM streaming call |
PreToolExecution | pre | Before each individual tool call is dispatched |
TurnBoundary | pre | Between turns, after all tool results are collected |
PostLlmResponse | post | After the LLM response is received |
PostToolExecution | post | After each tool execution completes |
RunCompleted | post | After a successful run completes |
RunFailed | post | After a run fails with an error |
is_pre() and is_post() methods on HookPoint. Foreground hooks block loop progression; background hooks run asynchronously for observation.
Capabilities
| HookCapability | Purpose |
|---|---|
Observe | Read-only logging and metrics |
Guardrail | Can issue Deny decisions to block execution |
Rewrite capability is deleted entirely; semantic patch authority is removed.
Execution modes
| HookExecutionMode | Behavior |
|---|---|
Foreground | Blocks loop progression. Allow / Deny decisions are handled synchronously. |
Background | Runs asynchronously for observation. |
RuntimeHookResponse rejects unknown fields, so a runtime response carrying any legacy patches field fails closed during deserialization.
How to add a hook
Choose hook point and capability
Decide which
HookPoint to fire at (e.g., PreToolExecution for tool guardrails) and whether the hook needs Observe or Guardrail.Choose a runtime
Pick one of the three runtimes: in-process (Rust closure), command (subprocess), or HTTP (remote endpoint).
Add configuration
Add a
[[hooks.entries]] block to the active realm config.toml (or register programmatically via HooksConfig).Implement the handler
Write the handler that receives a
HookInvocation and returns a RuntimeHookResponse with an optional decision.The docs below show run-scoped hook overrides conceptually, but the current CLI does not expose run-scoped hook override flags in normal builds. For now, use RPC, REST, MCP, or the SDK/embedded surfaces when you want per-run hook overrides.
Runtimes
- In-process
- Command (subprocess)
- HTTP
Calls a registered Rust closure. Config:(
name is accepted as a legacy alias for handler.)The handler is registered at engine construction time via DefaultHookEngine::with_in_process_handler() or register_in_process_handler(). The handler type is:HookEngineError values; they are never converted into warning-only success or a hook-local Deny decision. Pre-point background hooks are still required to be Observe, because they cannot safely block a pre-execution boundary after the loop has moved on.
Decisions and patches
HookDecision
HookDecision
| HookReasonCode | Meaning |
|---|---|
PolicyViolation | Business rule or policy constraint |
SafetyViolation | Content safety check |
SchemaViolation | Schema or format validation failure |
Timeout | Hook timed out (system-generated) |
RuntimeError | Hook execution failed (system-generated) |
Retired patch payloads
Retired patch payloads
The
HookPatch type is deleted entirely. Older payloads such as llm_request, assistant_text, tool_args, tool_result, and run_result fail closed during deserialization instead of being ignored or applied.Hooks can observe typed projections and return Allow or Deny. Canonical provider parameters, assistant text, tool arguments, tool results, and final run text are owned by the runtime and tool execution path.Runtime hook response
All three runtimes return the sameRuntimeHookResponse structure:
decision is optional and is the only field. The struct is declared with deny_unknown_fields, so any legacy patches field — even an empty one — is rejected at deserialization.
Invocation payload
Hooks receive aHookInvocation struct containing contextual data. Fields are populated based on the hook point:
| Field | Type | Populated at |
|---|---|---|
point | HookPoint | Always |
session_id | SessionId | Always |
turn_number | Option<u32> | Most points |
prompt_input | Option<RunInput> | RunStarted |
error_report | Option<AgentErrorReport> | RunFailed |
error_class | Option<AgentErrorClass> | RunFailed |
llm_request | Option<HookLlmRequest> | PreLlmRequest |
llm_response | Option<HookLlmResponse> | PostLlmResponse |
tool_call | Option<HookToolCall> | PreToolExecution |
tool_result | Option<HookToolResult> | PostToolExecution |
prompt and error strings. These are serialize-only projections of prompt_input and error_report; they are never stored fields and are never deserialized back as authority.
Supporting types
Supporting types
HookLlmRequest:max_tokens,temperature,provider_params,message_countHookLlmResponse:assistant_text,tool_call_names,stop_reason,usageHookToolCall:tool_use_id,name,argsHookToolResult:tool_use_id,name,content_blocks,is_error(the wire payload adds a serialize-onlycontenttext projection)
HookToolResult.content_blocks carries the typed tool-result blocks, including image blocks, in original order. The wire payload also serializes a content string — a text projection derived from content_blocks for external command/HTTP hooks; in Rust, use HookToolResult::text_projection(). Both are observational projections and cannot be returned as a rewrite.
Priority ordering and deny short-circuiting
Foreground hooks are sorted bypriority (ascending), then by registration_index (ascending, for determinism when priorities are equal). Lower numeric priority values run first.
When a foreground hook returns Deny:
A priority-1 guardrail that denies will prevent a priority-100 observer from running.
Configuration
Config file example (realm config.toml)
Config file example (realm config.toml)
Hook configuration lives under the
[hooks] table:HookEntryConfig fields
HookEntryConfig fields
| Field | Type | Default | Description |
|---|---|---|---|
id | HookId | "hook" | Unique identifier for the hook |
enabled | bool | true | Whether the hook is active |
point | HookPoint | TurnBoundary | Which hook point to fire at |
mode | HookExecutionMode | Foreground | Foreground or background |
capability | HookCapability | Observe | What the hook can do |
priority | i32 | 100 | Execution order (lower runs first) |
timeout_ms | Option<u64> | None | Override default timeout |
runtime | HookAdapterConfig | in_process/noop | Runtime configuration |
Layered config loading
Layered config loading
Hook loading is runtime-root aware. In workspace-derived realms, global and project hook entries are layered (
~/.rkat/config.toml then .rkat/config.toml) and merged with active realm config hooks. In non-workspace realms, realm config is primary and no project config may be discovered.Per-run overrides
TheHookRunOverrides struct allows per-request hook customization:
- CLI
- RPC / REST / MCP
Run-scoped hook override flags are not currently exposed on the normal
rkat run surface. Use JSON-RPC, REST, MCP, or SDK/embedded surfaces for per-run hook override testing.Agent events
The hook engine emitsAgentEvent variants during execution:
| Event | When |
|---|---|
HookStarted | A hook begins execution |
HookCompleted | A hook finishes successfully |
HookFailed | A hook encounters an error |
HookDenied | A hook returns a Deny decision |
SDK usage
Registering an in-process hook
Registering an in-process hook
Using hook overrides in AgentBuildConfig
Using hook overrides in AgentBuildConfig
