Edictum
Guides

Python Hooks

Use Python hooks for pre/post pipeline logic that should not live in YAML.

AI Assistance

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:

  • before hooks run before rule evaluation and can block the call
  • after hooks 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

ValueMeaning
HookResult.ALLOWThe hook permits the call
HookResult.DENYThe hook blocks the call

HookResult.DENY is the enum member name. Its current value is "block".

HookDecision

FieldTypeDescription
resultHookResultAllow or block
reasonstr | NoneBlock reason, truncated to 500 chars

Preferred constructors:

HookDecision.allow()
HookDecision.block("reason text")

HookDecision.block("...") still exists as a deprecated alias.

HookRegistration

FieldTypeDescription
phasestr"before" or "after"
toolstrTool name, or "*" for all tools
callbackcallableHook function
whencallable | NoneOptional 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

  1. Attempt limit check
  2. Before hooks
  3. Preconditions
  4. Sandbox rules
  5. Session rules
  6. Execution limits check
  7. Tool executes
  8. Postconditions Postconditions are warn-only by default for Python decorator rules. YAML rulesets add action: redact and action: block for safe tool classes.
  9. After hooks
  10. 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

On this page