Python Hooks
Use Python hooks for pre/post pipeline logic that should not live in YAML.
Right page if: you need Python callbacks before or after tool execution -- external policy checks, dynamic allowlists, custom logging, or other hook logic. Wrong page if: your logic fits YAML rulesets -- see https://docs.edictum.ai/docs/guides/writing-rules. Gotcha: hooks are constructor-only in the public API. Use `HookDecision.block()` -- `HookDecision.block()` still works as a deprecated alias.
Hooks are for Python logic that sits around the pipeline:
beforehooks run before rule evaluation and can block the callafterhooks run after postconditions and are side-effect only
Quick Example
from edictum import Edictum, HookDecision, HookRegistration, ToolCall
def block_destructive(tool_call: ToolCall) -> HookDecision:
cmd = tool_call.args.get("command", "")
if "rm -rf" in cmd:
return HookDecision.block("Destructive command blocked")
return HookDecision.allow()
guard = Edictum(
hooks=[
HookRegistration(phase="before", tool="bash", callback=block_destructive),
]
)Core Types
HookResult
| Value | Meaning |
|---|---|
HookResult.ALLOW | The hook permits the call |
HookResult.DENY | The hook blocks the call |
HookResult.DENY is the enum member name. Its current value is "block".
HookDecision
| Field | Type | Description |
|---|---|---|
result | HookResult | Allow or block |
reason | str | None | Block reason, truncated to 500 chars |
Preferred constructors:
HookDecision.allow()
HookDecision.block("reason text")HookDecision.block("...") still exists as a deprecated alias.
HookRegistration
| Field | Type | Description |
|---|---|---|
phase | str | "before" or "after" |
tool | str | Tool name, or "*" for all tools |
callback | callable | Hook function |
when | callable | None | Optional filter: when(tool_call) -> bool |
Before Hooks
Before hooks receive a ToolCall and return a HookDecision.
from edictum import HookDecision, HookRegistration, ToolCall
def check_allowlist(tool_call: ToolCall) -> HookDecision:
allowed_tools = {"read_file", "list_dir", "search"}
if tool_call.tool_name not in allowed_tools:
return HookDecision.block(
f"Tool '{tool_call.tool_name}' is not in the allowlist"
)
return HookDecision.allow()
hook = HookRegistration(phase="before", tool="*", callback=check_allowlist)If a before hook blocks, the tool call stops immediately. Preconditions, sandbox rules, and session rules are skipped.
After Hooks
After hooks receive the ToolCall and the tool response. Their return value is ignored.
from edictum import HookRegistration, ToolCall
def log_tool_result(tool_call: ToolCall, response: object) -> None:
print(f"[audit] {tool_call.tool_name} returned {len(str(response))} chars")
hook = HookRegistration(phase="after", tool="*", callback=log_tool_result)After hooks cannot block tool calls. The tool already executed.
Conditional Hooks
def is_production(tool_call: ToolCall) -> bool:
return tool_call.environment == "production"
hook = HookRegistration(
phase="before",
tool="deploy_service",
callback=require_approval,
when=is_production,
)Async Support
import httpx
from edictum import HookDecision, HookRegistration, ToolCall
async def check_external_policy(tool_call: ToolCall) -> HookDecision:
async with httpx.AsyncClient() as client:
resp = await client.post(
"https://policy.internal/check",
json={"tool": tool_call.tool_name, "args": tool_call.args},
)
if resp.json().get("blocked"):
return HookDecision.block(resp.json()["reason"])
return HookDecision.allow()
hook = HookRegistration(phase="before", tool="*", callback=check_external_policy)Error Handling
If a before hook raises, the pipeline converts that failure into a block:
def risky_hook(tool_call: ToolCall) -> HookDecision:
raise RuntimeError("service unavailable")The resulting reason is Hook error: service unavailable.
If an after hook raises, the error is logged but does not change the tool result.
Pipeline Order
- Attempt limit check
- Before hooks
- Preconditions
- Sandbox rules
- Session rules
- Execution limits check
- Tool executes
- Postconditions
Postconditions are warn-only by default for Python decorator rules. YAML rulesets add
action: redactandaction: blockfor safe tool classes. - After hooks
- Audit event emitted
Registering Hooks
Hooks are passed to the constructor:
from edictum import Edictum, HookDecision, HookRegistration
def audit_hook(tool_call):
print(f"Tool call: {tool_call.tool_name}")
return HookDecision.allow()
def log_result(tool_call, response):
print(f"Result: {response}")
guard = Edictum(
hooks=[
HookRegistration(phase="before", tool="*", callback=audit_hook),
HookRegistration(phase="after", tool="*", callback=log_result),
],
rules=[...],
)Current public API
There is no public Edictum.from_yaml(..., hooks=...) path today. If you need
hooks, build the guard through the Python constructor.
Next Steps
Last updated on