Edictum
Concepts

Observe Mode

Observe mode lets you test contracts against live traffic without denying any tool calls.

AI Assistance

Right page if: you want to test contracts against live traffic without denying any tool calls -- safe rollout before enforcing. Wrong page if: you want to run two contract versions side-by-side in production -- see observe_alongside in https://docs.edictum.ai/docs/contracts/patterns/compliance. Gotcha: observe mode emits CALL_WOULD_DENY audit events (not CALL_DENIED). Query for that specific action value when reviewing what your contracts would have done.

Observe mode lets you test contracts against live traffic without denying any tool calls. Preconditions that would fire emit CALL_WOULD_DENY audit events instead of denying. The tool call proceeds normally.

This gives you real data on what your contracts would do before you enforce them.

Enforce Mode
Tool Call: read_file(".env")
Precondition: block-dotenv
Contract matches!
args.path contains .env
CALL_DENIED
audit event emitted
Tool NEVER executes
agent receives denial message
Observe Mode
Tool Call: read_file(".env")
Precondition: block-dotenv
Contract matches!
would have denied
CALL_WOULD_DENY
audit event logged
Tool STILL executes
postconditions run on output

The Workflow

1. Deploy contracts in observe mode
        |
2. Review CALL_WOULD_DENY audit events
        |
3. Tune contracts (fix false positives, tighten loose contracts)
        |
4. Switch to enforce mode

Step 1: Deploy in observe mode. Set mode: observe in your contract bundle and deploy to production. Agents run normally -- no tool calls are denied.

Step 2: Review audit events. Every precondition that would have denied a call emits a CALL_WOULD_DENY event. Query your audit sink (stdout, file, OTel) for these events to see which contracts fire and how often.

Step 3: Tune. If a contract fires too often (false positives), narrow its when condition. If it never fires, check that the selectors match your tool arguments. Use edictum check to test specific tool calls against your contracts without running them.

Step 4: Enforce. Change mode: observe to mode: enforce. Contracts now actively deny tool calls.

Enabling Observe Mode

Pipeline-level: all contracts observe

Set the default mode in your contract bundle:

defaults:
  mode: observe

Every contract in the bundle runs in observe mode. No tool calls are denied.

Per-contract: test one contract in observe mode

Leave the bundle default as enforce and set mode: observe on specific contracts:

defaults:
  mode: enforce

contracts:
  - id: block-dotenv
    type: pre
    tool: read_file
    when:
      args.path: { contains: ".env" }
    then:
      effect: deny
      message: "Denied: read of sensitive file {args.path}"

  - id: experimental-api-check
    type: pre
    mode: observe
    tool: call_api
    when:
      args.endpoint: { contains: "/v1/expensive" }
    then:
      effect: deny
      message: "Expensive API call detected (observe mode)."

Here, block-dotenv enforces (denies matching calls) while experimental-api-check observes (logs what it would deny but allows the call).

What Changes in Observe Mode

BehaviorEnforce ModeObserve Mode
Precondition matchesTool call is deniedTool call proceeds
Audit event actionCALL_DENIEDCALL_WOULD_DENY
Tool executesNoYes
Postconditions runN/A (tool didn't run)Yes (tool ran)
Audit trail records the matchYesYes
Session countersAttempt counted, execution notAttempt counted, execution counted

The critical difference: in observe mode, the tool always executes. The audit trail shows you exactly what enforcement would have done, without any impact on the agent.

Postconditions in Observe Mode

Postconditions always produce findings (warnings), never denials. In observe mode, postcondition warnings are prepended with [observe] in the warning string (e.g., "[observe] PII detected in output"). The audit event is still emitted as CALL_EXECUTED or CALL_FAILED -- there is no separate would_warn action. The on_postcondition_warn callback fires in both modes.

Reviewing Observe-Mode Events

Audit events from observe mode include the same fields as enforce-mode events: tool name, arguments, principal, contract ID, policy version, and session counters. The action field distinguishes them:

  • CALL_DENIED -- enforce mode, call was denied
  • CALL_WOULD_DENY -- observe mode, call would have been denied

Filter your audit sink for CALL_WOULD_DENY to see the observed denial report. Group by decision_name (the contract id) to see which contracts fire most often.

Dual-Mode Evaluation with observe_alongside

Observe mode applies to individual contracts or to an entire bundle. But sometimes you need to run two versions of the same contract simultaneously -- the current enforced version and a candidate version that only observes. This is dual-mode evaluation.

The Use Case

You have contracts running in production. A new version is ready but you want to compare its behavior against the current version before promoting it. You need both versions evaluating the same tool calls, with the current version making real decisions and the candidate only logging.

How It Works

Create a second YAML file with observe_alongside: true at the top level:

# candidate.yaml
apiVersion: edictum/v1
kind: ContractBundle
observe_alongside: true

metadata:
  name: candidate-contracts

defaults:
  mode: enforce

contracts:
  - id: block-sensitive-reads
    type: pre
    tool: read_file
    when:
      args.path:
        contains_any: [".env", ".secret", "credentials", ".pem", ".key"]
    then:
      effect: deny
      message: "Denied: read of sensitive file {args.path}"

Load both bundles:

guard = Edictum.from_yaml("contracts/base.yaml", "contracts/candidate.yaml")

The pipeline evaluates both versions on every tool call:

  1. Enforced contracts from base.yaml make real allow/deny decisions
  2. Observed contracts from candidate.yaml evaluate in parallel, producing separate audit events with mode: "observe"

Observed contract IDs are suffixed with :candidate (e.g., block-sensitive-reads:candidate). Observed contracts never block tool calls -- they only produce audit events.

Observed Audit Events

Observed contracts emit the same audit events as regular observe mode:

  • CALL_WOULD_DENY -- the observed contract would have denied this call
  • CALL_ALLOWED -- the observed contract allowed this call

Filter your audit sink for mode: "observe" and decision_name ending in :candidate to see the observed evaluation results.

When to Use

Contract update rollouts. Deploy the candidate in observe mode. Compare its audit trail with the enforced version. If the candidate would have denied calls that should be allowed (false positives), tune it before promoting.

A/B testing contracts. Run a stricter version of a contract in observe mode to measure the impact of tightening a contract.

Composition Report

Use return_report=True to see which contracts were observed:

guard, report = Edictum.from_yaml(
    "contracts/base.yaml",
    "contracts/candidate.yaml",
    return_report=True,
)

for s in report.shadow_contracts:
    print(f"{s.contract_id}: shadow from {s.observed_source}")

See Bundle Composition for full composition reference.

Next Steps

  • Contracts -- writing preconditions, postconditions, and session contracts
  • How it works -- the full pipeline walkthrough
  • Quickstart -- try observe mode in the bonus step
  • YAML reference -- mode field, defaults block, and observe_alongside

Last updated on

On this page