Choosing a Contract Type
Four contract types for different enforcement needs. Pick the right one for your use case.
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
| Type | When it runs | What it checks | Effects | Use when |
|---|---|---|---|---|
| Precondition | Before tool executes | Tool name, arguments, principal | deny, approve | Block dangerous calls, require approval |
| Sandbox | Before tool executes (after preconditions) | File paths, commands, domains | deny, approve | Restrict WHERE the agent can operate |
| Session | Before tool executes (after sandbox) | Call counts across turns | deny | Cap tool usage per session |
| Postcondition | After tool executes | Tool output | warn, redact, deny | Detect PII, secrets, or violations in results |
Pipeline Order
Contracts evaluate in this order. If any step denies, the pipeline stops:
- Attempt limits — hard cap on total attempts (including denied calls)
- Before hooks — custom Python callbacks
- Preconditions —
type: precontracts - Sandbox contracts —
type: sandboxcontracts - Session contracts —
type: sessioncontracts - Execution limits — hard cap on successful executions
- Tool executes
- Postconditions —
type: postcontracts - 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 immediatelyapprove— pause and request human approval (withtimeoutandtimeout_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:
| Boundary | Applies to | Example |
|---|---|---|
within / not_within | File paths | Restrict reads to /workspace |
allows.commands | Shell commands | Allow only ls, cat, git |
allows.domains | HTTP URLs | Allow only github.com, *.googleapis.com |
not_allows.domains | HTTP URLs | Block 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:
| Limit | What it counts |
|---|---|
max_attempts | Total PreToolUse events (including denied calls) |
max_tool_calls | Successful PostToolUse executions |
max_calls_per_tool | Per-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:
| Effect | PURE / READ tools | WRITE / IRREVERSIBLE tools |
|---|---|---|
warn | Warning logged, callback invoked | Warning logged, callback invoked |
redact | Matched patterns replaced with [REDACTED] | Falls back to warn |
deny | Output 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:
-
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
-
Do you need to limit how many times a tool can be called?
- Yes → Session contract
-
Do you need to inspect what a tool returned?
- Yes → Postcondition
-
Not sure?
- Start with a precondition. You can always add sandbox or session contracts later.
Next Steps
- Preconditions reference — full syntax and examples
- Postconditions reference — output scanning patterns
- Session contracts reference — limit configuration
- Sandbox contracts — boundary enforcement
- YAML reference — complete schema
Last updated on