Contract YAML Reference
Every field in per-tool contracts, session.yaml, and workflow.yaml.
Per-tool contract
Each tool gets a YAML file named {tool_name}.yaml in your contracts directory.
Required fields
| Field | Type | Description |
|---|---|---|
tool | string | Tool name (must match function name in tool definitions) |
side_effect | "read" | "write" | "destructive" | "admin" | "financial" | Risk classification |
evidence_class | "local_transaction" | "ack_only" | "unverifiable" | Evidence pattern (restrictions) |
commit_requirement | "acknowledged" | "none" | When authoritative state advances |
timeouts | { total_ms: number } | Call timeout |
retries | { max_attempts: number, retry_on: string[] } | Retry policy |
rate_limits | { on_429: { respect_retry_after: boolean, max_sleep_seconds: number } } | Rate limit handling |
assertions | { input_invariants: Invariant[], output_invariants: Invariant[] } | Structural assertions |
golden_cases | GoldenCase[] | Reference test cases |
allowed_errors | AllowedError[] | Expected error patterns |
Optional fields
| Field | Type | Description |
|---|---|---|
gate | "allow" | "block" | Override risk_defaults for this tool |
transitions | { valid_in_phases: string[], advances_to: string } | Phase restrictions |
preconditions | Precondition[] | Cross-step ordering requirements |
forbids_after | string[] | Tools blocked after this one executes |
argument_value_invariants | ValueInvariant[] | Runtime argument value checks |
response_format_invariants | ResponseFormatInvariants | Response envelope checks |
execution_constraints | { arguments: Invariant[] } | Pre-execution argument validation |
policy | PolicyBlock | Per-tool authorization rules |
binds | BindEntry[] | Captures argument/output values into named session-scoped slots |
schema_derived | boolean | Enable/disable auto-derived invariants from tool schema (default: true) |
schema_derived_exclude | string[] | JSONPaths excluded from schema-derived invariant synthesis |
checkpoint | CheckpointDefinition | Human approval gate for high-stakes calls |
evidence_class restrictions
ack_only cannot be used with high-risk side effects. The compiler rejects the combination with error code ACK_ONLY_ON_HIGH_RISK.
side_effect | local_transaction | ack_only | unverifiable |
|---|---|---|---|
read | OK | OK | OK |
write | OK | OK | OK |
destructive | OK | Blocked | OK |
admin | OK | Blocked | OK |
financial | OK | Blocked | OK |
Use local_transaction (observable results) or unverifiable (fire-and-forget) for high-risk tools instead.
transitions
transitions:
valid_in_phases: [phase_a, phase_b] # Phases where this tool is visible
advances_to: phase_c # Phase after successful execution
preconditions
preconditions:
# Basic ordering
- requires_prior_tool: tool_name
# With output check
- requires_prior_tool: tool_name
with_output:
- path: "$.field"
equals: "value"
# Same-entity enforcement
- requires_prior_tool: tool_name
resource:
bind_from: arguments
path: "$.entity_id"
with_output:
- path: "$.approved"
equals: true
# Minimum step count
- requires_step_count:
gte: 3
argument_value_invariants
Runtime argument value checks support a subset of operators:
argument_value_invariants:
- path: "$.field_name"
exact_match: "string_value" # String exact match
- path: "$.field_name"
type: string # Type check
- path: "$.field_name"
regex: "^pattern$" # Regex match
- path: "$.field_name"
one_of: ["a", "b", "c"] # Enumeration
- path: "$.field_name"
gte: 0 # Greater than or equal
- path: "$.field_name"
lte: 10000 # Less than or equal
response_format_invariants
response_format_invariants:
finish_reason: "tool_calls" # Expected finish reason
content_when_tool_calls: "empty" # "empty" | "allowed"
tool_calls_present: true # Must have tool calls
execution_constraints
execution_constraints:
arguments:
- path: "$.file_path"
regex: "^/tmp/scratch/"
- path: "$.recursive"
equals: false
policy
policy:
allow:
- principal:
path: "$.department"
equals: "finance"
deny:
- principal:
path: "$.type"
equals: "readonly-auditor"
binds
binds:
- name: approved_shares
source: arguments # "arguments" (default) or "output"
path: $.shares
- name: approval_id
source: output
path: $.approval_id
Captures values into named session-scoped slots. Consumer tools reference them with ref in argument_value_invariants.
| Field | Type | Required | Description |
|---|---|---|---|
binds[].name | string | yes | Unique slot name |
binds[].source | "arguments" | "output" | no (default: "arguments") | Where to extract the value |
binds[].path | string | yes | JSONPath into the source object |
ref operator (in argument_value_invariants)
argument_value_invariants:
- path: $.shares
ref: approved_shares # Must equal bound value
- path: $.notional_value
ref: approved_notional
tolerance: 0.01 # Relative tolerance for floats
| Field | Type | Required | Description |
|---|---|---|---|
ref | string | no | Name of a previously bound slot. Asserts equality. |
tolerance | number | no | Relative tolerance for numeric comparisons. Only valid with ref. |
schema_derived / schema_derived_exclude
# Disable all schema-derived invariants for this tool
schema_derived: false
# Or exclude specific fields
schema_derived_exclude:
- $.shares
- $.metadata.custom_field
When enabled (default), the compiler extracts JSON Schema keywords (minimum, maximum, pattern, enum, minLength, maxLength, const) from tool definitions and synthesizes corresponding argument_value_invariants automatically. Manual invariants take precedence on the same path.
checkpoint
checkpoint:
when:
- path: $.notional_value
gte: 1000000
- path: $.order_type
equals: "market"
match: any # "any" (default) or "all"
timeout_seconds: 300
on_timeout: deny # "deny" (default) or "allow"
context:
- path: $.shares
label: "Shares"
- path: $.notional_value
label: "Notional Value"
Pauses the session and emits an approval request when conditions are met. See Govern Mode for server-side enforcement.
| Field | Type | Required | Description |
|---|---|---|---|
when | condition[] | "always" | yes | Conditions that trigger the checkpoint |
when[].path | string | conditional | JSONPath into tool arguments |
when[].gte / lte / equals / one_of | varies | conditional | Condition operator |
when_side_effect | string | no | Trigger based on tool's side_effect classification |
match | "any" | "all" | no (default: "any") | Whether any or all conditions must be met |
timeout_seconds | number | yes | How long to wait for approval (minimum 30) |
on_timeout | "deny" | "allow" | no (default: "deny") | What happens if no decision within timeout |
context[] | array | no | Fields to include in the approval request |
session.yaml
One per contracts directory. Defines session-level configuration.
schema_version: "1.0"
agent: my-agent-name
# Phase machine
phases:
- name: start
initial: true
- name: processing
- name: done
terminal: true
transitions:
start: [processing]
processing: [done]
# Session limits
session_limits:
max_steps: 20
max_tool_calls: 50
max_tool_calls_mode: block # "block" (default) | "narrow"
max_cost_per_session: 10.00
max_calls_per_tool:
dangerous_tool: 1
loop_detection:
window: 5
threshold: 3
circuit_breaker:
consecutive_blocks: 5
consecutive_errors: 3
# Risk classification defaults
risk_defaults:
read: allow
write: block
destructive: block
admin: block
financial: block
# Session-level policy
policy:
default_deny: false
rules:
- deny:
principal: { path: "$.type", equals: "readonly" }
- allow:
principal: { path: "$.tier", one_of: ["L2", "L3"] }
tools: ["specific_tool"]
# Provider constraints
provider_constraints:
anthropic:
block_incompatible:
- "tool_choice.type=any + thinking"
openai:
warn_incompatible:
- "parallel_tool_calls=false + gpt-4o-mini"
warn_floating_alias: true
# Resource definitions (for workflow governance)
resources:
order:
type: order
extract_from:
path: "$.order_id"
# Session aggregates (Layer 3)
aggregates:
- name: total_shares
metric: sum
tool: submit_live_order
path: $.shares
lte: 100000
reason: "Total shares must not exceed risk limit"
# Value envelopes (Layer 3)
envelopes:
- name: risk_envelope
stages:
- tool: approve_risk_check
path: $.shares
role: ceiling
- tool: submit_live_order
path: $.shares
role: constrained
constraint: lte_ceiling
reason: "Submitted shares must not exceed approved shares"
# Session checkpoints (Layer 4)
checkpoints:
- name: high_value_trade
tool: submit_live_order
when:
- path: $.notional_value
gte: 1000000
timeout_seconds: 300
on_timeout: deny
# Label gates
policy:
label_gates:
- when_label: mnpi
deny_tools: [submit_live_order, place_trade]
reason: "Trading blocked when MNPI in context"
# Schema-derived invariants (default: true)
schema_derived: true
# Contract graph analysis suppression
graph_analysis:
suppress:
- check: dead_tool
tool: sentinel_tool
reason: "Intentional sentinel"
Phase rules
- Exactly one phase must have
initial: true - At least one phase must have
terminal: true - All phases in
transitionsmust be declared inphases - Every non-terminal phase must be reachable from the initial phase
- Per-tool
valid_in_phasesandadvances_tomust reference declared phases
aggregates
Session-wide computed constraints. Checked speculatively before each tool call is committed.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Unique aggregate identifier |
metric | "sum" | "count" | "max" | "min" | "count_distinct" | yes | Aggregation function |
tool | string | string[] | "*" | yes | Tool(s) to aggregate over |
path | string | conditional | JSONPath into arguments. Required for all metrics except count. |
gte / lte | number | at least one | Bounds on the aggregate value |
reason | string | yes | Human-readable reason (appears in block decisions) |
when | condition[] | no | Conditional bound overrides (see below) |
Conditional bounds: when
Aggregate bounds can adapt to runtime conditions via session bindings. Each when entry references a binding and overrides the base gte/lte when the condition matches. First match wins.
aggregates:
- name: hedge_calls
metric: count
tool: hedge_position
lte: 3
reason: "Hedge call limit"
when:
- binding: latest_var
gte: 0.10
then_lte: 10
Default: max 3 hedge calls. When the latest_var binding is >= 0.10: max 10.
| Field | Type | Required | Description |
|---|---|---|---|
binding | string | yes | Session binding name to check |
gte / lte / equals | number or value | at least one | Condition on the binding value |
then_gte / then_lte | number | at least one | Overridden bounds when condition matches |
The binding must be captured via a contract's binds entry before the aggregate is evaluated. Missing bindings fall through to base bounds.
envelopes
Directional flow constraints across tool calls.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Unique envelope identifier |
stages[] | array | yes | Ordered value capture/check points (min 2) |
stages[].tool | string | yes | Tool name |
stages[].path | string | yes | JSONPath to the value |
stages[].role | "ceiling" | "floor" | "anchor" | "initial" | "constrained" | yes | Role in the envelope |
constraint | "lte_ceiling" | "gte_floor" | "within_band" | "monotonic_decrease" | "monotonic_increase" | "bounded" | yes | Constraint type |
band | number | conditional | Band width for within_band (fraction, e.g., 0.05 = 5%) |
reason | string | yes | Human-readable reason |
checkpoints (session-level)
Session-level checkpoint definitions (alternative to per-tool checkpoint blocks).
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Checkpoint name |
tool | string | "*" | yes | Tool to gate |
when | condition[] | "always" | yes | Trigger conditions |
when_side_effect | string | no | Trigger based on side_effect classification |
timeout_seconds | number | yes | Approval timeout (minimum 30) |
on_timeout | "deny" | "allow" | no (default: "deny") | Timeout behavior |
label_gates (in policy)
Context-aware tool gating based on session labels.
| Field | Type | Required | Description |
|---|---|---|---|
when_label | string | yes | Label that activates this gate |
deny_tools | string[] | yes | Tools denied when label is active |
reason | string | yes | Human-readable reason |
Labels follow taint semantics: they can be added but never removed within a session.
schema_derived (session-level)
schema_derived: false # Disables schema-derived invariants globally
Default: true. When enabled, JSON Schema keywords from tool definitions are automatically synthesized into argument_value_invariants.
graph_analysis
graph_analysis:
suppress:
- check: dead_tool
tool: sentinel_tool
reason: "Intentional sentinel"
Suppress specific contract graph analysis diagnostics. Suppressions require a reason field.
Provider constraints
Block known API incompatibilities or warn about soft issues per provider.
| Field | Type | Description |
|---|---|---|
provider_constraints | object | Top-level key, keyed by provider name (openai, anthropic) |
block_incompatible | string[] | Patterns that throw before the LLM call |
warn_incompatible | string[] | Patterns that fire a diagnostic callback and proceed |
warn_floating_alias | boolean | Recommend pinned model version instead of alias |
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.
workflow.yaml
Defines multi-session coordination above individual sessions.
schema_version: "1.0"
workflow: my-workflow-name
roles:
- name: orchestrator
session_contract: packs/orchestrator/session.yaml
- name: worker
session_contract: packs/worker/session.yaml
handoffs:
- from: orchestrator
to: worker
requires_artifacts: ["task_summary"]
parent_close_policy: request_cancel
workflow_limits:
max_sessions: 8
max_active_sessions: 4
max_total_steps: 100
max_total_cost: 25.00
max_open_handoffs: 6
shared_resources:
- alias: resource_name
mode: single_writer # single_writer | exclusive_pending | serial_only
cancellation:
subtree_kill: true
workflow_kill: operator_only
parent_close_policy:
default: request_cancel # request_cancel | terminate | abandon
Shared resource modes
| Mode | Behavior |
|---|---|
exclusive_pending | While one session has a pending step on the resource, no other session can start one |
single_writer | At most one session can be the mutating owner |
serial_only | Multiple sessions may act, but only one commits at a time |
Kill scopes
| Scope | Effect |
|---|---|
session | Kills one session |
subtree | Kills session + all descendants |
workflow | Kills all sessions, rejects future claims |
Invariant operators
Full operator set (assertions, execution_constraints, preconditions.with_output)
| Operator | Type | Description |
|---|---|---|
equals | any | Exact value match |
exists | boolean | Field exists (not null/undefined) |
type | string | Value type check (string, number, integer, boolean, object, array) |
contains | string | String/array contains |
one_of | array | Value in enumeration |
regex | string | Regex pattern match |
gte | number | Greater than or equal |
lte | number | Less than or equal |
length_gte | number | Minimum length |
length_lte | number | Maximum length |
equals_env | string | Equals environment variable value |
Argument value invariants (subset)
argument_value_invariants support a narrower set of operators:
| Operator | Type | Description |
|---|---|---|
exact_match | string | String exact match |
type | string | Value type check |
regex | string | Regex pattern match |
one_of | array | Value in enumeration |
gte | number | Greater than or equal |
lte | number | Less than or equal |
ref | string | Assert equality with a previously bound session slot |
tolerance | number | Relative tolerance for numeric ref comparisons |
Next steps
- Contract Cookbook — fields explained with examples
- API Reference — SDK API details
- Troubleshooting — common issues