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
- react to committed input and peer-delivery outcomes
- update application UI from typed interaction completions
- log or audit agent behavior
- block unsafe tool inputs or outputs
- choose between in-process, command, and HTTP hook runtimes
Hook points
Fourteen extension points are available:
Classification is determined by the
is_pre() and is_post() methods on HookPoint. Foreground hooks block loop progression; background hooks run asynchronously for observation.
The six post-commit points are intrinsically observe-only. Their session-scoped
dispatcher owns spawned tasks, reaps completed work, and aborts remaining work
when the dispatcher is dropped. A returned denial, timeout, or hook runtime
failure cannot change or delay the committed fact.
Capabilities
The former
Rewrite capability is deleted entirely; semantic patch authority is removed.
Execution modes
Semantic patch publication is retired.
RuntimeHookResponse rejects unknown fields, so a runtime response carrying any legacy patches field fails closed during deserialization.
How to add a hook
1
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.2
Choose a runtime
Pick one of the three runtimes: in-process (Rust closure), command (subprocess), or HTTP (remote endpoint).
3
Add configuration
Add a
[[hooks.entries]] block to the active realm config.toml (or register programmatically via HooksConfig).4
Implement the handler
Write the handler that receives a
HookInvocation and returns a RuntimeHookResponse with an optional decision.5
Test with overrides
Use
HookRunOverrides to test your hook in isolation through RPC, REST, MCP, or embedded builders before committing the config.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.
Every background hook is required to be Observe, because asynchronous work
cannot safely publish a guardrail decision after loop progression.
Decisions and patches
HookDecision
HookDecision
These are reason codes a hook response can supply with
Deny, not automatic
engine-denial outcomes. Engine timeouts and execution failures remain typed
HookEngineError values, mapped to HookFailureReason::Timeout or
HookFailureReason::ExecutionFailed in foreground HookFailed events.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:
For command and HTTP hooks, the serialized wire payload additionally carries
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,usage,server_tool_content(typedServerToolKindprojection of provider-native server-tool blocks in the response, e.g. web search — empty when none; lets a foreground hook classify provider-native content ingestion synchronously)HookToolCall:tool_use_id,name,args,provenance(typedToolProvenanceof the called tool — kind + source id, e.g. an MCP server name — projected from the tool’sToolDef; absent for tools without declared provenance)HookToolResult:tool_use_id,name,content_blocks,is_error,provenance(as onHookToolCall; the wire payload adds a serialize-onlycontenttext projection)HookObservation: a tagged typed union for accepted, rejected, or deduplicated runtime input; committed peer ingress or egress; and completed interaction facts
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.
Hooks versus event streams
Use hooks as the default in-process application reaction API. Event streams remain the canonical ordered transport for remote observers, replay-aware pipelines, transcript/audit projection, and resynchronization after stream truncation. Command receipts remain the direct result for the caller that initiated an action; the matching committed outcome is also projected through the hook surface. Post-commit hooks are process-local best-effort reactions, not durable delivery. A crash after the canonical event is published may lose a hook invocation, and recovery of a finalized outbox may replay one. Use event streams for durable or replay-aware consumption; hook handlers with external side effects should deduplicate by canonical ids such asinteraction_id.
Post-commit hooks are not synchronous policy seams. They cannot close an
in-turn race or gate a same-turn write. Keep using synchronous points such as
PostToolExecution when policy must join the agent loop before it advances.
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:
1
Record denial
The denial is recorded as the merged decision.
2
Skip remaining foreground hooks
All remaining foreground hooks are skipped (short-circuit).
3
Skip background hooks
All background hooks are skipped (they only fire when no foreground
Deny occurred).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
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
For ordinary agent-loop hook points, the session event stream can include:
Ordinary background work does not promise a terminal
HookCompleted or
HookFailed session event. Later failures and capacity skips are observable
through DefaultHookEngine::background_dispatch_ledger() and tracing; a
capacity-skipped hook has not started and does not earn HookStarted.
Post-commit reactions use a separate isolated dispatch path and do not promise
these normal lifecycle session events.
SDK usage
Registering an in-process hook
Registering an in-process hook
Using hook overrides in AgentBuildConfig
Using hook overrides in AgentBuildConfig
