State reference
Every field a contract can read, and which of them make a verdict depend on where the work has got to.
State reference
Six namespaces. Every field is a fact rather than a model's opinion, and a rule that reads a name the state does not carry gets nothing rather than an error, so the check fails instead of the gate crashing.
tool
This call, flattened out of the runtime's tool input.
| Field | What it holds |
|---|---|
tool.name | The tool name as the runtime gave it |
tool.normalised | Lowercased, with a functions. prefix and an (Explore) specifier stripped |
tool.family | The family this tool routed to |
tool.command | The shell command, for a command call |
tool.path | The file path, for a read or a write |
tool.url | The URL, for an egress call |
tool.query | The search query |
tool.description | The description, prompt or task text |
tool.subagent_type | Which subagent is being spawned |
Facts the gate has already worked out, so a contract does not re-derive them.
| Field | True when |
|---|---|
tool.is_read tool.is_write tool.is_command | The call is that kind of action |
tool.is_agent tool.is_mcp tool.is_payment | The same |
tool.touches_secret | The path matches a credential file. Environment files, private keys, cloud profiles, keyrings |
tool.is_egress | The call moves data off the machine, by tool or by command |
tool.is_destructive | The command has no safe reading. A recursive delete of the root, a write to a block device, a download piped into a shell, a force push, a cleared history |
tool.destructive_reason | Which of those it was |
tool.path_exists | The target already exists, which separates creating a record from rewriting one |
tool.path_ext tool.path_abs tool.path_size | The extension, the absolute path, and the size on disk |
A payment call carries the offer the agent wants to accept, lifted as facts so a contract compares numbers to numbers.
| Field | What it holds |
|---|---|
tool.amount | The amount, as a number, or nothing if it would not parse |
tool.currency | Uppercased |
tool.payee_address | Who the money goes to |
tool.merchant | Who is being paid |
tool.resource | What is being bought |
tool.network | The chain or network |
tool.offer_id | The offer or nonce |
tool.offer_expires_epoch | How long the offer stands |
tool.is_renewal | Whether this is a renewal |
An amount that is not a finite number arrives as nothing rather than as a string, so a rule comparing it to a limit fails its check instead of comparing a string to a number and passing by accident.
The derived facts are pattern lists, not classifiers. touches_secret matches the
paths that hold credentials, environment files, private keys, cloud profiles and
keyrings. is_destructive matches a fixed list of commands with no safe reading,
and is_egress matches the tools and commands that move data off the machine. A
pattern list misses what it does not name, which is exactly why a contract can add
its own conditions on the raw tool.path and tool.command instead of relying on
the derived flag.
session
What this session already did, before this call.
| Field | Counts |
|---|---|
session.tool_calls | Every governed call |
session.files_read session.files_written | Reads and writes |
session.secrets_read session.secrets_written | Credential files touched |
session.commands_run | Shell commands |
session.destructive_attempts | Destructive commands attempted |
session.egress_calls | Calls that sent data off the machine |
session.subagents_spawned | Subagents started |
session.case_files_read | Files touched inside the case this call resolved to |
session.payments_attempted | Payments attempted |
The counters read the state before the current call, so reading a credential does
not count itself and the egress two calls later is the one that sees it. A
contract about reading a credential and then sending data reads
session.secrets_read for the history and tool.is_egress for now.
A call that was stopped does not advance the counters. A blocked read never happened, so it must not count towards what this session has seen.
case
Live workflow state, read off disk for the case this call touches.
| Field | What it holds |
|---|---|
case.present | Whether any case state could be read at all |
case.id | The case folder's name |
case.root | The resolved case folder |
case.case_root | The root every case lives under |
case.touched_by_this_call | Whether this call's own path resolved the case |
case.<marker> | Whether that marker's files exist |
case.<marker>_count | How many |
case.<marker>_size | The largest, in bytes |
Markers come from the mandate. See The mandate.
case.touched_by_this_call matters more than it looks. An egress call carries no
path and still needs the open case's state, so the gate falls back to the case
folder most recently touched. That fallback must not make an unrelated file read
look like someone opened the customer's file.
platform
Live ledger state supplied by the runtime that embeds the gate. Every field is whatever the platform put in the snapshot, plus one the gate adds.
| Field | What it holds |
|---|---|
platform.present | Whether a snapshot arrived at all |
Missing state is a fact. When no snapshot arrives, platform.present is false and
the contract decides what that means, which keeps an absent provider fail-closed in
the contract rather than quietly allowed in the gate.
The trust boundary is the payload's author. In a Claude Code install that is the harness, and over HTTP it is the platform service calling the gate. It is never the agent whose call is being judged.
env
| Field | What it holds |
|---|---|
env.permission_mode | The permission mode this call is running under |
env.can_ask | Whether that mode can surface a prompt to a person |
env.cwd | The working directory |
env.session_id | This session |
env.agent_id env.agent_type | Which agent, when the runtime says |
env.mandate_id | The mandate governing this call |
env.mode | A mode field on the mandate, when one is set. The gate's operating mode comes from config and sits on every receipt as gate_mode, so read the receipt for which mode actually governed a call |
mandate
Whatever is under limits in your mandate file, read directly.
limits:
max_files_written: 40
max_payment_usd: 50rule: session.files_written < mandate.max_files_writtenWhy the split matters
tool alone is what a stateless gate sees, and a check written against tool
alone can be defeated by splitting one bad action into two allowed ones.
session, case and platform are the parts that make a verdict depend on where
the work actually sits. They are also the parts that can be absent, which is why
every one of them carries a present field or a count you can check first.