default: deny) turn the rules into an allowlist. It can never grant a permission
the run’s scope lacks.
policy.yaml
version, default, rules, limits, budgets, freezes, risk, retention, content and
tests. Anything else is an error. Rule and limit ids must be unique.
Matching
match fields (all optional, all must hold): tool, permission, actor, environment, tenant, data_class,
blast_radius (globs: * any run of characters, ? one; each may be a list), and effect (an exact effect class or a
list). data_class and blast_radius come from the tool’s manifest risk; a call to a tool without them does not match a
rule that names them.
when conditions then look at the call arguments with $args.path.to.value:
eq, ne, gt, gte, lt, lte, in, not_in, matches (regular expression), exists, contains.
Numbers and numeric strings both count as numbers, so an upstream that coerces "5000" cannot slip under gt 1000. A
comparison on a missing value is false. For deny and require_approval rules a value that is present but of the wrong
type for the operator (text under gt, a number under matches) counts as a match, so a restriction cannot be dodged by
sending the wrong type. allow rules do not do this.
Tenants
tenant matches the tenant of the run. The server sets it from the tenant on the API key that opens the run. Runs created by
the gateway carry no tenant, so a rule with tenant: never matches there.
Precedence
deny beats require_approval beats allow. Every matching rule is evaluated and reported. With default: deny, a call
that no allow rule matches is denied (no allow rule matches this call (default deny)).
Limits
Sliding windows countedper run, actor, tenant or globally. Only calls that actually started count: a call that was
blocked or is waiting for approval does not use up the window. Counters live in the data store, so every process sharing it
shares the counts.
Budgets
A limit counts calls. A budget can also add up an argument:max is denied or needs approval, and the reason says how much was used. Between
warn_at and max the call goes through and the ledger notes budget daily-charge-total at 85% (...). A call whose
argument is not a number adds 0.
Time windows
A rule applies only inside its window:days is any of sun..sat (default every day), between is HH:MM-HH:MM and may wrap midnight (22:00-06:00), timezone
is an IANA name (default UTC), and outside: true inverts the window. Checked with a fixed clock, the first rule denies on
a Saturday and allows on a Friday; the second asks for approval at 19:00 New York time and not at 11:00.
Freezes
freeze quarter-end: quarter-end freeze (until 2027-01-02T00:00:00Z). Without
match, every call that is not read_only is frozen, which includes tools with no manifest. to must be after from.
Risk scoring
A transparent 0 to 100 score from the call’s effect, the manifest’sblast_radius and data_class, the environment, an
argument and the actor. Every factor is listed, so a reviewer sees why a call scored what it did. Add a risk section to turn
it on:
approve the call needs approval; at or above deny it is refused. approve cannot exceed deny. The score is
clamped to 0..100 and recorded with the step and in the intent record:
risk 60 (effect compensable +35, amount over 100000 +25). A $args.amount call of 200000 by new-agent in prod scores
effect compensable +35, environment prod +10, amount over 100000 +25, actor new-agent +20 = 90, which is denied.
Default weights, used for anything you do not override:
Approvers and quorum
Arequire_approval rule can say who may approve and how many must:
approvers, quorum and approval_timeout are only valid on require_approval rules. When several rules and the manifest’s
own approval settings apply to one call, they are merged: any listed role may approve, the
largest quorum wins, the shortest timeout wins. See approvals.
Retention and content
Two more sections live in the same file.retention says how long run state, approval requests and captured arguments are kept; a run is kept for the longest of the
default, the matching rules and each tool’s manifest audit.retention_days. The ledger itself is never purged. See
retention. content configures the checks in content checks
(secrets, encoded_payloads, injection, pii: flag, require_approval or deny; plus cumulative and
max_string_bytes).
Modes
Policy decisions are the same in every mode; the mode decides what happens to a violation.
The gateway sets the mode with
--mode or mode: in its config. Use observe or advise first, read the
would_block records or reversi report (with no run id, the period report), then switch to enforce. The gateway turns a block into an MCP
error result the agent can read.
More than one policy source
The engine merges the policy file with the gateway’sallow and deny lists, which it compiles into rules
(gateway-deny-0, gateway-allow). Rules, limits, budgets, freezes and tests are concatenated, so ids must stay unique
(a clash is an error). If any source says default: deny, the result is deny: a gateway allow list therefore makes the
merged policy an allowlist. risk, retention and content come from the first source that has one, which is the policy file.
Test it
policy test runs the inline tests: and any extra file of cases, and exits 1 on a failure. Put it in CI. A case may name the
rule that must have fired, which also covers limits, budgets and freezes by id. input takes tool, effect, permission,
actor, environment, tenant, data_class, blast_radius and args; the defaults are actor agent, environment dev and
empty args. Each case starts with fresh counters, so limits and budgets are tested on a single call, and windows and
freezes use the real clock. An extra file is a list of the same cases:
more-cases.yaml
Versioned policies
Pointpolicy: at a folder and each change becomes a version with an author, a note, a checksum and a record of how many of its
own tests passed. Publishing validates the policy and runs its tests; a policy whose tests fail is refused.
--dir defaults to policy: in reversi.config.yaml. The engine loads the active version, and a running engine picks up a new one on reload
(POST /v1/reload; the server and gateway also watch the files unless reload: { watch: false } is set). Each version’s file is checked against its checksum when read, so a policy edited outside the history is refused. Over
HTTP an admin can POST /v1/registry/policy (text, note, activate) and POST /v1/registry/policy/{version}/activate.
The console policy page validates, runs the tests and can
simulate the draft against captured history before you publish.
