Edictum

Choosing a Contract Type

Four contract types for different enforcement needs. Pick the right one for your use case.

AI Assistance

Right page if: you need to decide between preconditions, postconditions, session contracts, and sandbox contracts for a specific enforcement goal. Wrong page if: you already know which type and need the full syntax reference -- see https://docs.edictum.ai/docs/contracts/preconditions, https://docs.edictum.ai/docs/contracts/postconditions, or https://docs.edictum.ai/docs/contracts/yaml-reference. Gotcha: preconditions cover 80% of use cases. Only reach for sandbox when you need path/command/domain allowlists, session contracts when you need cross-turn limits, or postconditions when you need to inspect tool output.

Edictum has four contract types. Each runs at a different point in the pipeline and solves a different problem.

At a Glance

TypeWhen it runsWhat it checksEffectsUse when
PreconditionBefore tool executesTool name, arguments, principaldeny, approveBlock dangerous calls, require approval
SandboxBefore tool executes (after preconditions)File paths, commands, domainsdeny, approveRestrict WHERE the agent can operate
SessionBefore tool executes (after sandbox)Call counts across turnsdenyCap tool usage per session
PostconditionAfter tool executesTool outputwarn, redact, denyDetect PII, secrets, or violations in results

Pipeline Order

Contracts evaluate in this order. If any step denies, the pipeline stops:

  1. Attempt limits — hard cap on total attempts (including denied calls)
  2. Before hooks — custom Python callbacks
  3. Preconditionstype: pre contracts
  4. Sandbox contractstype: sandbox contracts
  5. Session contractstype: session contracts
  6. Execution limits — hard cap on successful executions
  7. Tool executes
  8. Postconditionstype: post contracts
  9. After hooks — custom Python callbacks

Preconditions

The default choice. Covers ~80% of enforcement needs.

Use preconditions when you need to block or approve a tool call based on who is calling, what they're calling, or what arguments they're passing.

- id: deny-secret-reads
  type: pre
  tool: read_file
  when:
    args.file_path:
      matches_any: ['\.env$', '\.pem$', '\.key$']
  then:
    effect: deny
    message: "This file may contain secrets."

Available checks: args.*, principal.*, tool.name, env.*, boolean logic (all, any, not), 15 operators (contains, matches, gt, lt, exists, etc.)

Effects:

  • deny — block the call immediately
  • approve — pause and request human approval (with timeout and timeout_effect)

Sandbox Contracts

Use when you need allowlists — restrict where the agent can read, write, execute, or connect.

Sandbox contracts define boundaries. Anything outside the boundary is denied (or requires approval). This is the inverse of preconditions: instead of listing what's bad, you list what's allowed.

- id: workspace-boundary
  type: sandbox
  tools: [read_file, write_file, edit_file]
  within: [/workspace, /tmp]
  not_within: [/workspace/.git, /workspace/.env]
  outside: deny
  message: "File access restricted to /workspace and /tmp."

Boundary types:

BoundaryApplies toExample
within / not_withinFile pathsRestrict reads to /workspace
allows.commandsShell commandsAllow only ls, cat, git
allows.domainsHTTP URLsAllow only github.com, *.googleapis.com
not_allows.domainsHTTP URLsBlock evil.com

Path resolution: All paths resolved via os.path.realpath() before comparison. Handles .. traversals and symlinks.

Sandbox vs precondition: Use preconditions for "deny this specific bad thing." Use sandbox for "only allow these specific good things."


Session Contracts

Use when you need cross-turn limits. Preconditions and sandbox contracts evaluate each call independently. Session contracts track state across calls.

- id: session-limits
  type: session
  limits:
    max_attempts: 200
    max_tool_calls: 100
    max_calls_per_tool:
      exec: 20
      web_fetch: 50
  then:
    effect: deny
    message: "Session limit reached. Summarize progress and stop."

Available limits:

LimitWhat it counts
max_attemptsTotal PreToolUse events (including denied calls)
max_tool_callsSuccessful PostToolUse executions
max_calls_per_toolPer-tool execution cap (e.g., exec: 20)

State is stored via StorageBackend — in-memory by default, or Redis/Postgres for distributed agents.


Postconditions

Use when you need to inspect tool output. Postconditions run after the tool executes and can scan the result for PII, secrets, or contract violations.

- id: pii-in-output
  type: post
  tool: "*"
  when:
    output.text:
      matches_any:
        - '\b\d{3}-\d{2}-\d{4}\b'    # SSN
        - '\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'  # Email
  then:
    effect: redact
    message: "PII pattern detected and redacted."

Effects depend on tool classification:

EffectPURE / READ toolsWRITE / IRREVERSIBLE tools
warnWarning logged, callback invokedWarning logged, callback invoked
redactMatched patterns replaced with [REDACTED]Falls back to warn
denyOutput replaced with [OUTPUT SUPPRESSED] {message}Falls back to warn

The fallback exists because WRITE/IRREVERSIBLE actions have already completed — suppressing the output cannot undo the side effect. Use preconditions to deny dangerous writes before execution.


Decision Tree

Start here:

  1. Do you need to block or approve a call before it runs?

    • Yes, based on arguments, role, or tool name → Precondition
    • Yes, based on file path / command / domain boundaries → Sandbox
  2. Do you need to limit how many times a tool can be called?

    • Yes → Session contract
  3. Do you need to inspect what a tool returned?

    • Yes → Postcondition
  4. Not sure?

    • Start with a precondition. You can always add sandbox or session contracts later.

Next Steps

Last updated on

On this page