For the full guide, see Hooks.
Observer hook (background)
A post_tool_execution background hook that logs every tool call to a file. Background hooks run asynchronously and never block the agent loop.
CLI
JSON-RPC
REST
MCP
Python
TypeScript
rkat run "Summarize this repo" --hooks-override-json '{
"entries": [{
"id": "audit-log",
"point": "post_tool_execution",
"mode": "background",
"command": ["python3", "hooks/log_tool.py"],
"timeout_ms": 3000
}]
}'
{
"jsonrpc": "2.0", "id": 1,
"method": "session/create",
"params": {
"prompt": "Summarize this repo",
"hooks_override": {
"entries": [{
"id": "audit-log",
"point": "post_tool_execution",
"mode": "background",
"command": ["python3", "hooks/log_tool.py"],
"timeout_ms": 3000
}]
}
}
}
curl -X POST http://127.0.0.1:8080/sessions \
-H "Content-Type: application/json" \
-d '{
"prompt": "Summarize this repo",
"hooks_override": {
"entries": [{
"id": "audit-log",
"point": "post_tool_execution",
"mode": "background",
"command": ["python3", "hooks/log_tool.py"],
"timeout_ms": 3000
}]
}
}'
{
"name": "meerkat_run",
"arguments": {
"prompt": "Summarize this repo",
"enable_builtins": true,
"hooks_override": {
"entries": [{
"id": "audit-log",
"point": "post_tool_execution",
"mode": "background",
"command": ["python3", "hooks/log_tool.py"],
"timeout_ms": 3000
}]
}
}
}
result = client.create_session(
"Summarize this repo",
hooks_override={
"entries": [{
"id": "audit-log",
"point": "post_tool_execution",
"mode": "background",
"command": ["python3", "hooks/log_tool.py"],
"timeout_ms": 3000,
}]
},
)
const result = await client.createSession({
prompt: "Summarize this repo",
hooksOverride: {
entries: [{
id: "audit-log",
point: "post_tool_execution",
mode: "background",
command: ["python3", "hooks/log_tool.py"],
timeoutMs: 3000,
}],
},
});
Guardrail hook (blocking)
A pre_tool_execution blocking hook that must approve each tool call before it runs. If the hook returns deny, the tool call is skipped.
CLI
JSON-RPC
REST
MCP
Python
TypeScript
rkat run "Delete old logs" --hooks-override-json '{
"entries": [{
"id": "safety-gate",
"point": "pre_tool_execution",
"mode": "blocking",
"command": ["python3", "hooks/approve_tool.py"],
"timeout_ms": 5000
}]
}'
{
"jsonrpc": "2.0", "id": 1,
"method": "session/create",
"params": {
"prompt": "Delete old logs",
"hooks_override": {
"entries": [{
"id": "safety-gate",
"point": "pre_tool_execution",
"mode": "blocking",
"command": ["python3", "hooks/approve_tool.py"],
"timeout_ms": 5000
}]
}
}
}
curl -X POST http://127.0.0.1:8080/sessions \
-H "Content-Type: application/json" \
-d '{
"prompt": "Delete old logs",
"hooks_override": {
"entries": [{
"id": "safety-gate",
"point": "pre_tool_execution",
"mode": "blocking",
"command": ["python3", "hooks/approve_tool.py"],
"timeout_ms": 5000
}]
}
}'
{
"name": "meerkat_run",
"arguments": {
"prompt": "Delete old logs",
"enable_builtins": true,
"hooks_override": {
"entries": [{
"id": "safety-gate",
"point": "pre_tool_execution",
"mode": "blocking",
"command": ["python3", "hooks/approve_tool.py"],
"timeout_ms": 5000
}]
}
}
}
result = client.create_session(
"Delete old logs",
hooks_override={
"entries": [{
"id": "safety-gate",
"point": "pre_tool_execution",
"mode": "blocking",
"command": ["python3", "hooks/approve_tool.py"],
"timeout_ms": 5000,
}]
},
)
const result = await client.createSession({
prompt: "Delete old logs",
hooksOverride: {
entries: [{
id: "safety-gate",
point: "pre_tool_execution",
mode: "blocking",
command: ["python3", "hooks/approve_tool.py"],
timeoutMs: 5000,
}],
},
});
In-process hook (Rust only)
Register a Rust closure directly — no subprocess, no serialization overhead. Only available in the Rust SDK.
let engine = DefaultHookEngine::new(hooks_config)
.with_in_process_handler(
"pii-scrub",
Arc::new(|invocation| Box::pin(async move {
let text = invocation.tool_result.as_ref()
.map(|r| r.content.as_str()).unwrap_or("");
let scrubbed = redact_pii(text);
Ok(RuntimeHookResponse {
patches: vec![HookPatch::ToolResult {
content: scrubbed,
is_error: None,
}],
..Default::default()
})
})),
);
HTTP hook
A hook that POSTs the invocation payload to a remote URL. Useful for centralized policy servers.
CLI
JSON-RPC
REST
MCP
Python
TypeScript
rkat run "Analyze quarterly data" --hooks-override-json '{
"entries": [{
"id": "policy-server",
"point": "pre_llm_request",
"mode": "blocking",
"url": "https://hooks.example.com/policy",
"timeout_ms": 10000
}]
}'
{
"jsonrpc": "2.0", "id": 1,
"method": "session/create",
"params": {
"prompt": "Analyze quarterly data",
"hooks_override": {
"entries": [{
"id": "policy-server",
"point": "pre_llm_request",
"mode": "blocking",
"url": "https://hooks.example.com/policy",
"timeout_ms": 10000
}]
}
}
}
curl -X POST http://127.0.0.1:8080/sessions \
-H "Content-Type: application/json" \
-d '{
"prompt": "Analyze quarterly data",
"hooks_override": {
"entries": [{
"id": "policy-server",
"point": "pre_llm_request",
"mode": "blocking",
"url": "https://hooks.example.com/policy",
"timeout_ms": 10000
}]
}
}'
{
"name": "meerkat_run",
"arguments": {
"prompt": "Analyze quarterly data",
"enable_builtins": true,
"hooks_override": {
"entries": [{
"id": "policy-server",
"point": "pre_llm_request",
"mode": "blocking",
"url": "https://hooks.example.com/policy",
"timeout_ms": 10000
}]
}
}
}
result = client.create_session(
"Analyze quarterly data",
hooks_override={
"entries": [{
"id": "policy-server",
"point": "pre_llm_request",
"mode": "blocking",
"url": "https://hooks.example.com/policy",
"timeout_ms": 10000,
}]
},
)
const result = await client.createSession({
prompt: "Analyze quarterly data",
hooksOverride: {
entries: [{
id: "policy-server",
point: "pre_llm_request",
mode: "blocking",
url: "https://hooks.example.com/policy",
timeoutMs: 10000,
}],
},
});
All 8 hook points
| Hook point | Classification | When it fires |
|---|
run_started | pre | Start of Agent::run(), before any LLM call |
pre_llm_request | pre | Before each LLM streaming call |
pre_tool_execution | pre | Before each individual tool call is dispatched |
turn_boundary | pre | Between turns, after all tool results are collected |
post_llm_response | post | After the LLM response is received |
post_tool_execution | post | After each tool execution completes |
run_completed | post | After a successful run completes |
run_failed | post | After a run fails with an error |
Pre-point hooks in blocking mode halt the agent loop until they return. Pre-point hooks in background mode are limited to observe-only capability.
Disable a hook
Use the disable list to turn off hooks defined in the realm config without removing them.
CLI
JSON-RPC
REST
MCP
Python
TypeScript
rkat run "Quick test" --hooks-override-json '{
"disable": ["safety-gate", "audit-log"]
}'
{
"jsonrpc": "2.0", "id": 1,
"method": "session/create",
"params": {
"prompt": "Quick test",
"hooks_override": {
"disable": ["safety-gate", "audit-log"]
}
}
}
curl -X POST http://127.0.0.1:8080/sessions \
-H "Content-Type: application/json" \
-d '{
"prompt": "Quick test",
"hooks_override": {
"disable": ["safety-gate", "audit-log"]
}
}'
{
"name": "meerkat_run",
"arguments": {
"prompt": "Quick test",
"enable_builtins": true,
"hooks_override": {
"disable": ["safety-gate", "audit-log"]
}
}
}
result = client.create_session(
"Quick test",
hooks_override={
"disable": ["safety-gate", "audit-log"]
},
)
const result = await client.createSession({
prompt: "Quick test",
hooksOverride: {
disable: ["safety-gate", "audit-log"],
},
});
Hook events
Hooks emit events into the session event stream. Subscribe to these to build dashboards, alerting, or audit trails.
// HookStarted -- emitted when a hook begins execution
{"type": "hook_started", "hook_id": "safety-gate", "point": "pre_tool_execution"}
// HookCompleted -- emitted when a hook finishes successfully
{"type": "hook_completed", "hook_id": "safety-gate", "point": "pre_tool_execution", "duration_ms": 42}
// HookDenied -- emitted when a hook blocks execution
{"type": "hook_denied", "hook_id": "safety-gate", "point": "pre_tool_execution", "reason_code": "policy_violation", "message": "rm -rf not allowed"}
// HookFailed -- emitted when a hook errors or times out
{"type": "hook_failed", "hook_id": "audit-log", "point": "post_tool_execution", "error": "process exited with code 1"}
// HookRewriteApplied -- emitted when a hook applies a patch
{"type": "hook_rewrite_applied", "hook_id": "pii-scrub", "point": "post_tool_execution", "patch": {"ToolResult": {"content": "***", "is_error": null}}}