Edictum
Concepts

How the Pipeline Works

Every tool call your agent makes passes through Edictum's pipeline before it executes.

AI Assistance

Right page if: you want to understand the full evaluation order of a tool call through Edictum's pipeline, or you need to debug why a call was blocked or allowed. Wrong page if: you need the internal architecture details (YAML compilation, adapter pattern, storage model) -- see https://docs.edictum.ai/docs/architecture. Gotcha: the pipeline short-circuits on the first failure in steps 1-5 -- remaining checks are skipped. Postconditions only run if the tool actually executed.

Every tool call your agent makes passes through Edictum's pipeline before it executes. The pipeline evaluates YAML rules against the call's arguments, principal, and session state. If an enforced rule fails, the call is blocked and never reaches the tool. This is a hard boundary -- the LLM cannot reason its way past it.

Agent Calls Tool
Adapter Builds ToolCall
frozen snapshot of tool name, args, principal
pre_execute()
Check Attempt Limits
max_attempts (counts blocked calls too)
Run Before-Hooks
first block short-circuits
Evaluate Preconditions
YAML rules · action: block | ask
Sandbox · Session · Limits
boundaries, cross-turn state, per-tool caps
BLOCK
Block Message
returned to agent
CALL_DENIED
audit event emitted
PRE
DECISION
ASK
Wait for Human
approval_backend.wait_for_decision()
Approved → Execute
Rejected / Timeout → EdictumDenied
ALLOW
Tool Executes
post_execute()
Postconditions
warn · redact · deny (READ/PURE only)
After-Hooks
fire-and-forget observation
CALL_EXECUTED
audit event emitted to all sinks

A Blocked Call: Step by Step

An agent tries to read .env using the read_file tool. Here is what happens:

1. Agent decides to call read_file with {"path": ".env"}.

The framework adapter intercepts the call and builds a ToolCall -- a frozen snapshot of the tool name, arguments, and principal.

2. Edictum evaluates preconditions.

Before preconditions run, the pipeline checks attempt limits and fires any registered before hooks. If the attempt limit is exceeded or a hook blocks, the call is rejected before rulesets are even evaluated.

The pipeline then checks the ruleset. This rule matches:

- id: block-dotenv
  type: pre
  tool: read_file
  when:
    args.path: { contains: ".env" }
  then:
    action: block
    message: "Read of sensitive file blocked: {args.path}"

The args.path value is ".env". The contains operator finds ".env" in the string. The rule fires. (If preconditions pass, the pipeline also evaluates any sandbox rulesets -- allowlist boundaries for file paths, commands, and domains. If the call falls outside a sandbox boundary, it is blocked before reaching session limits.)

3. The call is blocked.

The pipeline returns a PreDecision with action: "block". The tool function never executes. The agent receives the block message: "Read of sensitive file blocked: .env".

4. An audit event is emitted.

An AuditEvent with action: CALL_DENIED is written to all configured sinks (stdout, file, OpenTelemetry). The event records the tool name, arguments, principal, which rule fired, and the policy version hash.

The .env file was never read. The agent sees the block message and can try a different approach. This is intentional -- a well-written block message steers the agent toward safe alternatives instead of leaving it stuck. For example, "Read of sensitive file blocked: .env — use environment variables instead" tells the agent exactly what to do next.

An Approval Gate: Step by Step

An agent calls delete_records with {"table": "users", "query": "WHERE inactive = true"}. A precondition with action: ask is configured.

1. Agent decides to call delete_records.

The adapter intercepts the call and builds a ToolCall.

2. Edictum evaluates preconditions.

The pipeline finds a matching rule with action: ask. Instead of blocking immediately, it returns PreDecision with action: "pending_approval".

3. The pipeline requests human approval.

The approval is routed through Edictum Control Plane, which delivers it to a human reviewer via Telegram, Slack, Discord, or webhook. Telegram is the only interactive channel today; the others send a notification that links back to the hosted control plane. A CALL_APPROVAL_REQUESTED audit event is emitted. The pipeline blocks until a decision arrives or the timeout expires.

4a. If approved: The tool executes normally. A CALL_APPROVAL_GRANTED audit event is emitted and on_allow fires.

4b. If blocked: EdictumDenied is raised. A CALL_APPROVAL_DENIED audit event is emitted and on_block fires.

4c. If timeout: The timeout_action determines the outcome. Default is block (raises EdictumDenied). A CALL_APPROVAL_TIMEOUT audit event is emitted.

An Allowed Call: Step by Step

The same agent calls read_file with {"path": "config.txt"}.

1. Agent decides to call read_file with {"path": "config.txt"}.

The adapter intercepts and builds the envelope.

2. Edictum evaluates preconditions.

The block-dotenv rule checks args.path for ".env". The value is "config.txt" -- no match. All other preconditions pass.

3. Edictum evaluates sandbox rulesets.

Any sandbox rulesets are checked next. If a sandbox defines within: ["/workspace"] for read_file, the pipeline verifies that args.path falls within the allowed boundary. The path "config.txt" is within bounds, so the sandbox passes.

4. The call is allowed.

Session limits are within bounds. The pipeline returns PreDecision with action: "allow". The tool function executes and returns the file contents.

5. Edictum evaluates postconditions.

The pipeline checks the tool's output against postcondition rulesets. For example, a PII detection rule scans the output for SSN patterns. If a pattern matches, the rule produces a finding. For READ/PURE tools, postconditions with action: redact replace matched patterns in the output with [REDACTED], and action: block suppresses the output entirely. For WRITE/IRREVERSIBLE tools, effects fall back to warn because the action already happened. See postcondition effects.

6. An audit event is emitted.

An AuditEvent with action: CALL_EXECUTED is written. It includes the tool name, arguments, whether postconditions passed, any findings, and the policy version hash.

Why This Is Deterministic

Rulesets are code evaluated against arguments. The expression grammar supports string matching, regex, numeric comparisons, and membership checks -- all evaluated by Python at runtime, outside the LLM.

A precondition like args.path: { contains: ".env" } will always block when the path contains .env. It does not matter what the LLM was told in its system prompt, how long the conversation has been, or how creatively the agent argues. The check runs in Python, not in the model.

This is the difference between a prompt instruction ("do not read .env files") and a rule. The prompt is a suggestion. The rule is enforcement.

Dual-Mode Evaluation

When rulesets are composed with observe_alongside: true, the pipeline evaluates observed rulesets alongside enforced rulesets. Observed rulesets produce audit events but never affect allow/block decisions.

After all enforced checks pass, the pipeline evaluates observed preconditions, sandbox rulesets, and session rulesets. Each observed result emits a separate audit event with mode: "observe" -- either CALL_WOULD_DENY or CALL_ALLOWED. This lets you compare the behavior of a candidate ruleset version against the currently enforced version. See observe mode for the full workflow.

What Happens at Each Stage

StageWhenCan Block?Output
Attempt limitsFirst check in the pipelineYesCALL_DENIED if attempt cap exceeded
Before hooksBefore preconditionsYesCALL_DENIED if hook returns block
PreconditionsBefore tool executesYesCALL_DENIED or pass
Approval gateWhen precondition or sandbox has action: askYes (on block/timeout)CALL_APPROVAL_REQUESTED, then GRANTED/DENIED/TIMEOUT
Sandbox rulesetsBefore tool executesYesCALL_DENIED with decision_source: yaml_sandbox
Session rulesetsBefore tool executesYesCALL_DENIED if session limit exceeded
Execution limitsBefore tool executesYesCALL_DENIED if execution cap exceeded
Observed rulesetsAfter enforced checks passNeverAudit events with mode: "observe"
Tool executionOnly if all checks pass--Tool's return value
PostconditionsAfter tool executeswarn: findings only. redact/block: enforced for READ/PURE toolsCALL_EXECUTED with warnings
After hooksAfter postconditionsNeverSide effects only (logging, metrics)
AuditAfter every evaluation--Structured event to all sinks

Next Steps

  • Rulesets -- the four rule types and how to write them
  • Sandbox Rulesets -- allowlist boundaries for file paths, commands, and domains
  • Principals -- attaching identity context to tool calls
  • Observe mode -- testing rulesets in observe mode before enforcement
  • YAML reference -- full rule syntax and ruleset composition

Last updated on

On this page