Skip to main content

Contract Cookbook

Every replay contract field explained with examples. Grouped by the problem each field solves.


Basic structure

Every tool contract is a YAML file with the tool name matching the function name in your tool definitions.

tool: my_tool_name
side_effect: read # read | write | destructive
evidence_class: local_transaction
commit_requirement: acknowledged

timeouts: { total_ms: 30000 }
retries: { max_attempts: 1, retry_on: [] }
rate_limits: { on_429: { respect_retry_after: true, max_sleep_seconds: 60 } }

assertions:
input_invariants: []
output_invariants: []

golden_cases: []
allowed_errors: []

The fields below are additive — add only what you need.


Classify risk: side_effect

Tells the enforcement pipeline how risky this tool is.

side_effect: read          # Safe — just reading data
side_effect: write # Modifies state — creates/updates records
side_effect: destructive # Irreversible — deletes data, sends money
side_effect: admin # Administrative/governance actions
side_effect: financial # Financial transactions (payments, transfers)

Combined with risk_defaults in session.yaml, this controls whether unmatched tools are allowed:

# session.yaml
risk_defaults:
read: allow
write: block # write tools blocked unless their contract has gate: allow
destructive: block
admin: block
financial: block

Override risk defaults: gate

Override the risk_defaults behavior for a specific tool.

tool: archive_records
side_effect: write
gate: allow # Allow this write tool even though risk_defaults blocks writes
tool: read_audit_log
side_effect: read
gate: block # Block this read tool even though risk_defaults allows reads

Control phase visibility: transitions

Restrict when a tool is available and which phase it advances to.

transitions:
valid_in_phases: [eligibility_checked] # Only visible in this phase
advances_to: refund_issued # After execution, move to this phase

Tools without transitions are available in all phases (no phase restriction).

See Phases & Transitions for full phase machine design.


Enforce ordering: preconditions

Require that specific tools were called before this one.

Basic ordering

preconditions:
- requires_prior_tool: lookup_customer

issue_refund is blocked unless lookup_customer was called earlier in the session.

Require specific output

preconditions:
- requires_prior_tool: check_eligibility
with_output:
- path: "$.eligible"
equals: true

Not only must check_eligibility have been called — its output must have eligible: true.

Same-entity enforcement (resource binding)

preconditions:
- requires_prior_tool: check_eligibility
resource:
bind_from: arguments
path: "$.order_id"
with_output:
- path: "$.eligible"
equals: true

The check_eligibility call must have been for the same order_id as this issue_refund call. Prevents "check order A, refund order B" bypasses.

Minimum step count

preconditions:
- requires_step_count:
gte: 2
description: "At least 2 prior steps before this tool"

See Preconditions & Ordering for detailed examples.


Prevent double execution: forbids_after

After this tool executes, block specific tools for the rest of the session.

tool: issue_refund
forbids_after:
- issue_refund # Can't refund twice
- void_order # Can't void after refund

Once issue_refund runs successfully, both issue_refund and void_order are removed from the tool list permanently. The model can't see them or call them.


Validate arguments at runtime: argument_value_invariants

Check the actual values in tool call arguments — not just types.

argument_value_invariants:
- path: "$.amount"
gte: 0
lte: 10000 # Max $10,000 refund

- path: "$.currency"
one_of: ["USD", "EUR", "GBP"]

- path: "$.customer_email"
regex: "^.+@.+"

- path: "$.query"
exact_match: 'status:"active"' # Catches provider quote-stripping

Available operators for argument_value_invariants:

OperatorWhat it checks
exact_matchString exact match
typeValue type (string, number, boolean, object, array)
regexString matches regex pattern
one_ofValue is in list
gteGreater than or equal
lteLess than or equal

Note: assertions (input/output invariants) support a broader set of operators — see Contract YAML Reference.


Validate response metadata: response_format_invariants

Check the LLM response envelope itself — not just the tool call arguments.

response_format_invariants:
finish_reason: "tool_calls" # Expect tool calls, not text
content_when_tool_calls: "empty" # No text content alongside tool calls
tool_calls_present: true # Actually has tool calls (catches phantom announcements)

When this matters:

  • Models sometimes return finish_reason: "stop" instead of "tool_calls" — 500/500 responses in research
  • Some models include text content alongside tool calls — underdocumented behavior
  • Models sometimes say "I'll call the tool" but produce no tool_calls — phantom announcements

Define evidence requirements: evidence_class and commit_requirement

Control how the enforcement pipeline treats execution evidence.

evidence_class: ack_only               # Evidence pattern
commit_requirement: acknowledged # Minimum evidence for authoritative commit
evidence_classMeaning
local_transactionTool execution happens locally with observable results
ack_onlyExecution is acknowledged but not independently verified
unverifiableNo way to verify execution happened
commit_requirementWhen state advances
acknowledgedAfter execution receipt is recorded
noneTool is governed and recorded but doesn't advance state

Restriction: ack_only is not allowed with high-risk side effects

Tools with side_effect of destructive, admin, or financial cannot use evidence_class: ack_only. The compiler rejects this combination with error code ACK_ONLY_ON_HIGH_RISK because high-risk operations require stronger evidence than simple acknowledgment.

Use local_transaction (observable results) or unverifiable (fire-and-forget) instead:

# High-risk tool — use local_transaction, not ack_only
tool: process_payment
side_effect: financial
evidence_class: local_transaction
commit_requirement: acknowledged
side_effectlocal_transactionack_onlyunverifiable
readOKOKOK
writeOKOKOK
destructiveOKBlockedOK
adminOKBlockedOK
financialOKBlockedOK

Pre-execution argument validation: execution_constraints

Validate tool arguments before the real tool function runs. Requires wrapping your tool executor.

tool: delete_file
side_effect: destructive

execution_constraints:
arguments:
- path: "$.file_path"
regex: "^/tmp/scratch/" # Only allow files in scratch directory
- path: "$.recursive"
equals: false # Never allow recursive delete

Important: These are argument validation rules, not security capabilities. They check values before your function runs, but they don't reduce the function's actual permissions.

Requires passing tool executors to replay():

const session = replay(client, {
tools: {
delete_file: myDeleteFunction, // Required when execution_constraints declared
},
});

Authorization: policy

Control which callers can use this tool based on a principal identity.

tool: process_refund
side_effect: destructive

policy:
allow:
- principal:
path: "$.department"
equals: "finance"
deny:
- principal:
path: "$.type"
equals: "readonly-auditor"

Requires passing a principal to replay():

const session = replay(client, {
principal: {
department: "finance",
type: "agent",
tier: "L2",
},
});

Rules:

  • Deny rules are checked first and are absolute
  • Session-level policy (in session.yaml) overrides per-tool policy
  • If any contract has a policy block and no principal is supplied, replay() fails with a config error

Provider constraints: provider_constraints (session.yaml)

Block known API incompatibilities or warn about soft issues.

# session.yaml
provider_constraints:
anthropic:
block_incompatible:
- "tool_choice.type=any + thinking" # Hard API error
openai:
warn_incompatible:
- "parallel_tool_calls=false + gpt-4o-mini" # Silently ignored
warn_floating_alias: true # Recommend pinned model version

block_incompatible entries throw before the LLM call. warn_incompatible entries fire a diagnostic callback and proceed.


Complete example

A production-grade contract for a payment tool:

tool: process_payment
side_effect: financial
evidence_class: local_transaction
commit_requirement: acknowledged
gate: block

timeouts: { total_ms: 30000 }
retries: { max_attempts: 1, retry_on: [] }
rate_limits: { on_429: { respect_retry_after: true, max_sleep_seconds: 60 } }

assertions:
input_invariants: []
output_invariants: []

golden_cases: []
allowed_errors: []

transitions:
valid_in_phases: [payment_approved]
advances_to: payment_processed

preconditions:
- requires_prior_tool: verify_identity
- requires_prior_tool: approve_payment
resource:
bind_from: arguments
path: "$.order_id"
with_output:
- path: "$.approved"
equals: true

forbids_after:
- process_payment
- void_order

argument_value_invariants:
- path: "$.amount"
gte: 0.01
lte: 50000
- path: "$.currency"
one_of: ["USD", "EUR", "GBP", "JPY"]

execution_constraints:
arguments:
- path: "$.amount"
lte: 50000

response_format_invariants:
finish_reason: "tool_calls"
tool_calls_present: true

Next steps