Observe Mode
Observe mode lets you test contracts against live traffic without denying any tool calls.
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.
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 modeStep 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: observeEvery 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
| Behavior | Enforce Mode | Observe Mode |
|---|---|---|
| Precondition matches | Tool call is denied | Tool call proceeds |
| Audit event action | CALL_DENIED | CALL_WOULD_DENY |
| Tool executes | No | Yes |
| Postconditions run | N/A (tool didn't run) | Yes (tool ran) |
| Audit trail records the match | Yes | Yes |
| Session counters | Attempt counted, execution not | Attempt 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 deniedCALL_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:
- Enforced contracts from
base.yamlmake real allow/deny decisions - Observed contracts from
candidate.yamlevaluate in parallel, producing separate audit events withmode: "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 callCALL_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 --
modefield,defaultsblock, andobserve_alongside
Last updated on