Policy reference¶
A policy is a JSON document per stream. It is versioned and append-only: ag policy set stores a new
version; old versions stay readable (ag policy history). It is evaluated as a pure function of facts
read in the transaction that commits the integration (how),
so there is no window between "checked" and "committed".
ag policy example # prints an example document to start from
ag policy set policy.json
ag policy show # the policy in force and its version (0 = built-in default)
Documents are parsed strictly: unknown fields are rejected (a typo must not silently weaken a policy),
and policies that could never be satisfied are rejected on load (for example a required validation with no
trusted_issuers).
Start from a preset¶
You do not have to write the first policy. Four presets are built in; each makes an ordinary policy document
(the same one ag policy set would store, parsed by the same parser), which you can edit afterwards.
ag policy preset list # what each is for
ag policy preset show agents # the document it makes, and what it means in plain language
ag policy preset apply agents # store it as the stream's next policy version (needs an admin)
ag init --preset team # or choose one when the repository is made
ag policy explain # the plain-language reading of the policy in force
ag policy explain policy.json # ... or of a file, before you set it
| Preset | For | In short |
|---|---|---|
solo |
one person, with or without agents | your own changes land at once; an agent's change needs your approval; an agent never lands anything itself |
team |
people who review each other | every change needs one approval from somebody else |
agents |
people and agents together | your changes land at once; an agent's change needs a person's approval, except documentation (docs/**, **/*.md, at most 10 files) which it may land itself; agent changes carry signed provenance; .github/**, SECURITY.md, deploy/** and LICENSE need an approval from somebody else |
strict |
code where every change needs two reviewers and a passing check | two approvals (three for the sensitive paths above), a passing check no older than a day, signed provenance for agents, no agent autonomy, at most 200 files per change |
The table is a summary: ag policy explain is the authority. It does not read a hand-written description of the
preset; it reads the policy document and builds every sentence from its fields, so what it says is what the
evaluator enforces (a test runs both on the same cases).
What a preset cannot know it takes as options:
--admin IDENTITY(repeatable): who may change the policy afterwards. The default is you;sololeaves it open to any user.--ci-key NAMEor--issuer IDENTITY=PUBLIC_KEY: the CI issuer trusted to sign the checktests. With one,solo,teamandagentsalso require that check on every change, andstrictcan be built at all (it always requires it).--ci-key ciuses the signing keyciof this repository and creates it if it is missing, soag validate run <proposal> --check tests --key ci -- <command>works straight away;--issuernames a key made elsewhere (ag validate keygen ciprints one).
On a server: ag remote policy main --preset team (your clone's identity and credential), or, with the server stopped,
ag-server admin policy-preset acme/web team --admin user:alice.
Applying the same preset again changes nothing (no new version, and so no attestation is invalidated).
Default (no policy stored)¶
Anyone who is not an agent may integrate; no validation, no approvals, no limits. Agents cannot integrate on
their own (see agent_autonomy), and agent-authored changes need one human approval
(approvals.min_for_agents = 1).
Fields¶
{
"integrators": ["user:*", "service:merge-queue"], // who may run `ag integrate`
"admins": ["user:root"], // who may change the policy / close others' proposals
"allowed_authors": ["user:*", "agent:*"], // who may author proposals to this stream
"trusted_issuers": [ { "identity": "service:ci", "public_key": "<64 hex>" } ],
"required_validations": [
{ "check": "tests", "issuers": ["service:ci"], "environment_digest": "sha256:…", "max_age_secs": 86400 }
],
"approvals": {
"min": 1, // approvals for every integration
"min_for_agents": 1, // …when an agent authored it and it is outside the autonomy envelope
"approvers": ["user:*"], // who counts; empty = any user:… identity
"forbid_self_approval": true
},
"protected_paths": [ { "glob": ".github/**", "min_approvals": 2, "approvers": ["user:admin*"] } ],
"agent_autonomy": { "auto_integrate": true, "allowed_paths": ["docs/**"], "max_files": 10 },
"require_provenance_for_agents": true,
"max_changed_files": 500
}
Identity patterns are globs over the identity string (user:*, agent:docs-*, service:ci); a pattern
matches the full identity or its principal (so agent:bot matches agent:bot#run_1). Path globs use
* within a path component and ** across components.
Who may do what¶
| Field | Empty means | Notes |
|---|---|---|
integrators |
any non-agent | agents are only let in through agent_autonomy (or by being listed explicitly) |
admins |
any user: identity |
changing policy requires being an admin of the current policy version |
allowed_authors |
anyone | otherwise the proposal is Denied (author_not_allowed) |
Validation¶
For each required_validations entry there must be an attestation that is all of:
- a valid Ed25519 signature over its content;
- from an identity and public key listed in
trusted_issuers(and in the entry'sissuers, if given); - about this exact tree (the candidate's tree ID — see Concepts);
- under this policy version (changing the policy invalidates earlier attestations on purpose: the inputs of the check changed; storing the same document again is not a change and keeps the version);
- with the entry's
environment_digestif given, and not older thanmax_age_secsif given; attestations dated more than five minutes in the future are ignored.
A trusted, correctly bound FAIL attestation makes the change Denied even if a PASS also exists:
failure wins. A missing one is only Blocked (missing_validation).
Approvals¶
Reviews attach to the proposal head they were written against; if the head moves, earlier reviews no
longer count. The latest decision per reviewer principal wins. An approval counts if the reviewer matches
approvers (or is a user: when empty), is not the author's principal (when forbid_self_approval), and
— for agent-authored changes outside the envelope — is a human. An eligible request_changes blocks
(changes_requested) until that reviewer approves again.
protected_paths add requirements per matching rule when the integration changes a matching path: at
least min_approvals approvals from that rule's approvers (or the global eligibility if empty).
Agent autonomy¶
A change is inside the envelope if max_files > 0, it changes at most max_files files, every changed
path matches allowed_paths, and it touches no protected path.
- Agent-authored and inside: needs only
approvals.min(often 0). - Agent-authored and outside: needs
max(min, min_for_agents)approvals from humans. - An agent may run the integration only if
auto_integrateis on and the change is inside the envelope; otherwiseactor_not_allowed. require_provenance_for_agentsblocks agent-authored changes without a valid signed provenance record.
auto_integrate requires a non-empty allowed_paths. With max_files left at 0 nothing is ever inside the
envelope.
Limits¶
max_changed_files (0 = unlimited): integrations changing more files are Denied.
Outcome codes¶
ag integrate --json returns outcome.status ∈ integrated, blocked, conflict, denied, invalid,
and for blocked/denied a list of blockers[] with a stable code:
| code | meaning | exit |
|---|---|---|
missing_validation |
no trusted PASS for this exact tree | 3 |
insufficient_approvals |
not enough eligible approvals of the current head (detail.scope: default or a glob) |
3 |
changes_requested |
an eligible reviewer asked for changes | 3 |
missing_provenance |
agent-authored change without signed provenance | 3 |
lease |
an exclusive lease on the scope is held by someone else, or the token is stale/missing | 3 |
failed_validation |
a trusted FAIL exists for this exact tree | 4 |
actor_not_allowed, author_not_allowed |
role/pattern mismatch | 4 |
too_many_changed_files |
over max_changed_files |
4 |
(The exit code is 4 when any blocker is a denial, otherwise 3.)
Recipes¶
Complete documents for these and more (merge queue, release stream, trusted workers, fresh evidence, size limits, and the mistakes the parser rejects), each parsed by the real parser in the tests, are in the policy cookbook.
Solo project, just keep history safe: no policy.
Team with CI: trusted CI issuer + required_validations: [tests] + approvals.min: 1.
Agents on docs, humans on code: the agent_autonomy example above plus require_provenance_for_agents
and a CI validation.
Sensitive paths: protected_paths for .github/**, SECURITY.md, deployment config, each with
min_approvals: 2 and a short approvers list.