AIP-7: GOVERNANCE.md — agentgovernance/v1 (audit, approval & policy primitives)
A filesystem-first format for recording approvals, append-only audit logs, and autonomy policies as workspace files — vendor-neutral, third-party-verifiable.
| Field | Value |
|---|---|
| AIP | 7 |
| Title | GOVERNANCE.md — agentgovernance/v1 (audit, approval & policy primitives) |
| Status | Draft |
| Type | Core |
| Domain | governance.sh |
| Requires | AIP-54 |
| Doctypes | signature (approval event), audit-event (append-only log line), policy (declarative rule), approval-request (pending human decision), governance.workspace/v1 (manifest + view) |
| Composes with | AIP-3 (skills), AIP-6 (companies), AIP-9 (operators), AIP-10 (knowledge), AIP-13 (work) |
| Reference Impl | packages/governance/core |
Abstract
agentgovernance/v1 is an open file-format standard for recording
approvals, audit logs, and autonomy policies as workspace files. It
provides a vendor-neutral, filesystem-first, third-party-verifiable
primitive for any system — human, agentic, or hybrid — that needs
auditable decisions. Three core doctypes: signature (universal approval
event), audit-event (append-only hash-chained log line), policy
(declarative autonomy rule). A fourth, approval-request, records a
pending human decision over an exact pinned payload and specifies which
channels may resolve it.
Motivation
Auditability in agentic systems is typically vendor-locked: each platform keeps approvals and logs in its own database, with proprietary formats and no cross-platform verification. agentgovernance/v1 makes the artifacts themselves portable and verifiable: a workspace receiving these files can validate every doctype and verify the audit chain end-to-end without trusting the originating system.
The standard is domain-agnostic: signature/audit/policy reference no business concepts. Any workflow that needs auditable approvals — clinician overrides, approve-to-publish flows, AI agent action gates, board votes — adopts the spec directly.
The three primitives — signature, audit-event, policy — are individual
records. They answer "did this approval happen?" and "what's the rule?",
but not "what is the posture of THIS workspace, for THIS operator, in
THIS company?". Posture is a registry-of-policies question: which policies
apply, which keys may sign, what autonomy level is the floor, what audit
retention is locked, where does escalation route. Without a manifest that
binds the registry, every consumer (AIP-3 skill,
AIP-6 company, AIP-9 operator) re-derives
posture from prose, which drifts.
governance.workspace/v1 (file = GOVERNANCE.md) closes the gap. It is
the canonical, machine-parseable workspace root that declares the binding:
the autonomy floor, the default approval class, the signing keyring, the
audit retention, the registry of policies and approvers, and the cross-AIP
hooks. The same doctype, used recursively via extends:, expresses
per-context views — an operator, a company, a team, or a skill ships
its own GOVERNANCE.md that adapts the base workspace for its context. It
is the same composability pattern that AIP-10's KNOWLEDGE.md uses for
wikis and AIP-13's WORK.md uses for work tracking; AIP-7 adopts it for
the governance posture.
Specification
Full normative text is in
packages/governance/core/AGENTGOVERNANCE.md. AIP-7 will absorb that text in full as part of moving Draft → Review.
| Doctype | File path | Purpose |
|---|---|---|
governance.workspace/v1 | <scope>/GOVERNANCE.md | Workspace manifest (root or view) — declares posture, registers policies/approvers |
signature | <artifact>/../signatures/<signer>-<isoDate>.signature.json | Universal approval primitive (one signature event per file) |
audit-event | <scope>/audit/audit-log.jsonl (one line per event) | Append-only hash-chained event log |
policy | <scope>/policies/<slug>/POLICY.md | Declarative autonomy rule |
approval-request | <scope>/approvals/<id>/request.json | A pending human decision over a pinned payload; its approve outcome is a signature (see Approval requests) |
Conventions adopted:
- Markdown canonical with YAML frontmatter (for
POLICY.md) - JSON canonical for
signature.jsonandaudit-log.jsonllines - Slug-based references, never database IDs
schema: agentgovernance/v1on every doctype- Vendor-specific extensions under
metadata.<vendor>.* - Git-native workspace layout
The hash-chain protocol for audit-event is published separately and
allows third-party verifiers in any language to validate the chain.
References (AIP-54)
All pointer fields on every governance doctype are typed references under AIP-54 REF. The fields use AIP-54's collection-typed form so consumers get extension-friendly constraints:
| Doctype field | AIP-54 collection | Example values |
|---|---|---|
signature.signer | RefIn<"identity"> | operator:atlas, user:abc123, persona:atlas, email:[email protected] |
signature.artifact | RefIn<"file"> | local:engagements/acme/proposal.md, github:agentik/studio@main:CONTRIBUTING.md |
signature.evidence.target (when applicable) | RefIn<"file"> | typed-name evidence references no target; esign-external evidence MAY carry a Ref<url> to the driver envelope |
audit-event.actor | RefIn<"identity"> (nullable for system events) | same as signer |
audit-event.entity | RefIn<"file"|"identity"> | the artifact or principal the event is about |
audit-event.anchor (emitted by anchor sinks) | RefIn<"anchor"> | ots:local:engagements/.../247.ots, eth_tx:1:0xabc… |
policy.requiredSignatures[].signer | RefIn<"identity"> | same as signer |
Why typed Refs
Earlier drafts encoded these as ad-hoc strings ("operator:atlas",
"engagements/acme/proposal.md"). Each string had its own
parse/validate logic, an ad-hoc escape rule, and no extension story. As
AIP-54 details, this drift is the principal blocker to harmonizing
across the agentproto registry. AIP-7 imports AIP-54 to:
- Replace
kind:slugregex parsing with the canonical AIP-54 parser. Adding a new identity kind (e.g.did,fediverse_handle) becomes "register a kind into theidentitycollection"; AIP-7 needs no change. - Validate paths through
Ref<local>including the path-escape rules and optional#sha256=…fragment. Thesignature.documentHashfield still exists separately as the recorded hash; the optional sha-fragment on thesignature.artifactRef is the expected hash the caller asserted (rejected by the runtime on mismatch with disk). - Open the anchor encoding from a single
AnchorPayloadshape to anyRefin theanchorcollection —ots,eth_tx, future anchor backends — without touching AIP-7.
Migration path
AIP-7 v1 (this draft) ships dual acceptance: the legacy string
forms remain valid wire formats and are preprocessed into AIP-54 Ref
values at parse time. Validators normalize on read. Producers SHOULD
emit AIP-54 object form on write; consumers MUST accept either.
A future AIP-7 v2 will deprecate the legacy string forms and require AIP-54 object form on write. The reference implementation tracks this transition; see the package roadmap.
Workspace root manifest (GOVERNANCE.md)
GOVERNANCE.md is the canonical, machine-parseable manifest that binds the
three primitives — signature, audit-event, policy — to a concrete
workspace and declares its posture. Sibling AIPs use the same pattern:
KNOWLEDGE.md is the workspace root for a wiki,
WORK.md is the workspace root for work tracking, and now
GOVERNANCE.md is the workspace root for the governance surface.
The same doctype, governance.workspace/v1, is used in TWO modes:
- Workspace-root mode —
<scope>/GOVERNANCE.md, noextends. Declares the base posture: autonomy floor, default approval class, signing keyring, audit retention, the registry of policies and approvers. - View mode —
<consumer>/GOVERNANCE.md,extends:set to a parentGOVERNANCE.md. Adapts the base for a specific operator (AIP-9), company (AIP-6), or skill (AIP-3). View mode is the mechanism that lets one base posture be tightened, narrowed, or escalated per consumer without forking.
Why a workspace manifest
Earlier drafts of this AIP shipped only the three primitives. That works for a single team, but breaks the moment two consumers need different postures over the same audit chain. The CFO assistant needs a stricter approval class than the research operator; both write to the same audit log. A junior engineer's operator needs a narrower keyring than the senior engineer's; both share the org-wide policy registry. Without a machine-parseable binding, posture drifts into prose ("the CFO branch is stricter") that no runtime can enforce.
GOVERNANCE.md makes posture a contract. The host loads the manifest, the
manifest declares which policies apply, which keys may sign, what audit
retention is locked, what autonomy ceiling applies. A view tightens the
contract for a specific consumer — never loosens it past the parent's
hardened invariants.
Frontmatter shape
---
schema: governance.workspace/v1
name: <kebab-case-id> # required
title: <human-readable> # required
description: <one-paragraph purpose> # required
version: <semver> # required, the WORKSPACE version
# Composition (view mode)
extends: ../path/to/parent/GOVERNANCE.md # OPTIONAL — relative path
appliesTo: # OPTIONAL — bind this view
- ws://operators/<slug> # AIP-9 operator
- ws://companies/<slug> # AIP-6 company
- ws://skills/<slug> # AIP-3 skill
# Autonomy + approval defaults
autonomy:
level: 0 | 1 | 2 | 3 # AIP-7 0=read-only ... 3=irreversible
defaultApproval: auto | always | on-mutate | policy:<ref>
approvalEscalation: # OPTIONAL — fallover route
from: <approval-class>
to: <approval-class>
# Signing + audit
signing:
algo: ed25519 | ecdsa-p256 | rsa-pss-sha256
keyring: <path> # public-key bundle
required: true | false # MUST every audit event sign?
audit:
retention: forever | days:<n>
hashAlgo: sha256 | sha512 | blake3
appendOnly: true # locked at workspace creation
storage: <vendor-neutral-uri> # OPTIONAL — where logs live
headPointerSign: true | false # publish signed head-pointer?
# Policy registry — what policies apply, with binding
policies: # array; merge-by-id vs parent
- id: <kebab-id> # required, stable
ref: <relative-path-to-policy.md> # required — file ref
appliesTo: <kind> | "*" # AIP-7 action kind or wildcard
severity: error | warn | info
params:
<key>: <value>
# Approver registry — pre-declared approvers
approvers: # array; merge-by-id vs parent
- id: <kebab-id> # required
role: <human-name-or-AIP-9-ref>
canApprove: [<approval-class>, ...]
quorum: 1 | n-of-m
# Cross-AIP refs
executor: ws://operators/<slug> # OPTIONAL — AIP-9 operator
escalateTo: ws://operators/<slug> # OPTIONAL — escalation operator
work: ws://workspaces/<slug>/WORK.md # OPTIONAL — AIP-13 binding
knowledge: ws://wikis/<slug>/KNOWLEDGE.md # OPTIONAL — AIP-10 binding
# Display / UX hints
display:
defaultDashboard: <slug> # OPTIONAL
showRetentionWarnings: true | false
metadata: # vendor extensions, namespaced
<vendor>:
<field>: <value>
---
# <body — purpose, threat model, conventions>Composition semantics
When a host loads a GOVERNANCE.md whose frontmatter declares
extends:, it MUST:
- Walk the parent chain. Recursively load the parent referenced by
extends:, then that parent's parent, until a manifest with noextendsis reached. Maximum chain depth is eight. Hosts MUST detect cycles by tracking visited absolute paths. - Treat depth overflow and cycle detection as warnings, not errors.
A view whose chain is malformed MUST still load — the host falls back
to the local manifest only and surfaces a
governance_extends_cycle(orgovernance_extends_depth_exceeded) warning. - Tolerate a missing parent. If
extends:points to a path that does not exist, the host emitsgovernance_extends_missingas a warning and uses the local manifest only. - Merge bottom-up with child winning on overrides — EXCEPT for the one-way switches enumerated below.
Merge strategy (child wins on override unless a one-way switch fires):
| Field | Strategy | Notes |
|---|---|---|
name, title, description, version | override | Child's identity wins; both are exposed via the resolution chain. |
extends | local-only | Not inherited. |
appliesTo | local-only | Not inherited; each view declares its own scope. |
autonomy.* | leaf-field override | level, defaultApproval, approvalEscalation each override independently. |
signing.algo, signing.keyring | override | Child can rebind. The keyring set MAY narrow but SHOULD NOT widen silently — hosts emit governance_keyring_drift (warn) if a child adds keys not present in the parent. |
signing.required | one-way switch | If parent is true, child MUST NOT downgrade to false. Hard refusal: governance_signing_downgrade. |
audit.retention, audit.hashAlgo, audit.storage, audit.headPointerSign | leaf-field override | Each independent. |
audit.appendOnly | one-way switch | If parent is true, child MUST NOT downgrade to false. Hard refusal: governance_append_only_relaxation. |
policies | merge-by-id | Same id → child replaces parent. New ids → appended. |
approvers | merge-by-id | Same id → child replaces parent. New ids → appended. |
executor, escalateTo, work, knowledge | override | Child can rebind. |
display.* | leaf-field override | |
metadata | deep-merge | Recursive merge; vendor namespaces accumulate. |
One-way switches are HARD errors, not warnings. The append-only
guarantee and the signing-required guarantee are AIP-7's two strongest
invariants. Once a workspace root locks them, no descendant view may
silently relax them. The host MUST refuse to merge such a child and
MUST emit a governance_append_only_relaxation (or
governance_signing_downgrade) error pointing at the offending
manifest. This is the defining safety property of GOVERNANCE.md:
posture can only get stricter down the chain, never looser.
The host MUST expose both the merged effective config AND the resolution chain (ordered list of absolute paths consumed during merge). Consumers use the merged config; tooling uses the chain to explain why a field has the value it does.
Cross-AIP refs
GOVERNANCE.md is the binding surface where governance meets the rest
of the AIP family:
| Field | References | Purpose |
|---|---|---|
executor | AIP-9 operator | Names the operator the host activates to run governance flows (apply policies, collect signatures, append audit). |
escalateTo | AIP-9 operator | When approvalEscalation fires, route to this operator. |
work | AIP-13 WORK.md | Bind audit events to a work-tracking workspace; mutations recorded here also produce work-item updates. |
knowledge | AIP-10 KNOWLEDGE.md | Bind the wiki this workspace governs; schema changes to the wiki flow through THIS workspace's approval gate. |
appliesTo | AIP-3 skill, AIP-6 company, AIP-9 operator | A view declares which consumers it adapts the workspace for. Hosts MUST refuse a view whose appliesTo references a non-existent consumer (governance_appliesto_unresolvable). |
extends | another GOVERNANCE.md | Composition. |
policies[].ref | AIP-7 policy doctype (POLICY.md) | Each entry references a policy file by relative path. |
appliesTo is not inherited. A view binds to its own consumers; a
parent's bindings do not leak into the child. The schema enforces
appliesTo ⇒ extends (a view that binds to a consumer must extend a
parent).
Workspace mode vs view mode — composability table
| Aspect | Workspace-root mode | View mode |
|---|---|---|
| File path | <scope>/GOVERNANCE.md | <consumer>/GOVERNANCE.md |
extends: | absent | required (otherwise it's a workspace, not a view) |
appliesTo: | absent (a workspace has no single consumer) | OPTIONAL but conventional |
| Effective shape | the manifest as written | merge of the chain, child wins (subject to one-way switches) |
audit.appendOnly | declares the lock | MAY NOT relax |
signing.required | declares the lock | MAY NOT downgrade |
| Mutability | edits are themselves governed by the workspace's own policies | local edits adapt the lens, do not affect the workspace |
| Use cases | org-wide posture authors | operator/company/skill teams who want a stricter lens |
| Validation | full schema check | schema check + chain validation + one-way invariant check |
| Lifecycle | versioned with the scope | versioned with the consumer |
The same doctype, the same file name, the same schema. Only the
location, the presence of extends:, and the one-way invariant
direction distinguish.
Approval requests (approval-request)
The signature doctype records that a decision happened. It does not say
who is allowed to trigger one, and it has no notion of a decision that is
still outstanding. A host that lets an agent ask a human for permission
needs both: a durable record of the question, and a rule that only a human
can answer it. The approval-request doctype is that record. It wraps a
signature as its approve outcome and adds nothing to the signature or the
audit chain that a verifier has to trust.
Normative keywords in this section apply to any host that implements approval requests. A host that does not implement them is unaffected.
Record shape
An approval-request is a JSON document with doctype: "approval-request".
Hosts SHOULD store it at <scope>/approvals/<id>/request.json, with the
pinned payload beside it at <scope>/approvals/<id>/payload.json.
| Field | Required | Meaning |
|---|---|---|
id | yes | Opaque unique identifier, stable for the life of the request. |
kind | yes | Free-form class of the thing being approved (for example deploy or send-email). Consumers key their own handling on it. |
title | yes | One-line human-readable summary. |
preview | no | JSON value the requester supplies for a human-facing surface to render. Opaque to the host; it is NOT covered by payloadHash and MUST NOT be treated as what was approved. |
payloadHash | yes | Lowercase hex SHA-256 of the canonical JSON of the pinned payload (see below). |
status | yes | One of pending, approved, denied, expired, consumed. |
requestedBy | yes | The requester: { "sessionId": "<id>" } for an agent session, or { "operator": true } for the operator acting directly. |
channels | yes | The human channels that may decide this request (see Human channels). |
requestedAt | yes | RFC 3339 timestamp. |
expiresAt | no | RFC 3339 timestamp after which a pending request can no longer be decided. |
decision | when decided | { decision, channel, decidedAt, signaturePath? }. decision is approved or denied. signaturePath is present only on an approve. |
consumedAt | when consumed | RFC 3339 timestamp of the consume. |
taskId, appId | no | Correlation ids. The host records them verbatim; this AIP gives them no other meaning. |
Payload pinning and canonicalisation
The payload is the exact thing the human is approving. At request time the
host MUST compute its canonical JSON string, persist those bytes as the
payload artifact, and record payloadHash. The artifact holds the
canonical string itself, UTF-8 encoded, with no trailing newline, so the
SHA-256 of the artifact file equals payloadHash.
Canonical JSON is defined by this recursive rule, which fixes the bytes across implementations:
null, booleans, numbers, and strings serialise as ECMAScriptJSON.stringifywould: no insignificant whitespace, shortest round-trip number form, and JSON string escaping.- An array serialises as
[then its elements, canonicalised in order and joined by,, then]. Array order is preserved. - An object serialises as
{then its members joined by,, then}. Members are sorted by key in ascending order of UTF-16 code units. Each member is the JSON string form of the key, then:, then the canonical form of the value. The sort applies at every depth. - A value that is
undefinedserialises asnull, including as an object member (the key is kept, with valuenull).
For inputs that are pure JSON values this produces the same bytes as RFC 8785
(JSON Canonicalization Scheme). Producers SHOULD restrict payloads to pure
JSON values (no undefined, NaN, or infinities) so that hosts in other
languages compute the same hash without implementing rule 4.
The hash is the lowercase hex SHA-256 of the UTF-8 bytes of the canonical
string. Two deep-equal payloads always hash equal, which is what makes
payloadHash a reliable statement of "this exact payload was approved".
Lifecycle
pending -> approved -> consumed
-> denied
-> expired- A request starts
pending. pending -> approvedandpending -> deniedhappen only through a human channel (see below). Deciding a request that is notpendingMUST be rejected, and MUST NOT alter the existing decision.pending -> expiredhappens whenexpiresAthas passed. Hosts MAY expire lazily, flipping the status the next time the record is read or decided, provided no decision is accepted afterexpiresAt. A request with noexpiresAtnever expires.approved -> consumedhappens once, throughconsume.denied,expired, andconsumedare terminal.
Each transition MUST be applied atomically with respect to the others: the
status check, the status change, and its persistence happen with no
interleaving, so two concurrent decides cannot both succeed and a consume
cannot race a second consume. The record MUST be persisted (write to a
temporary file, then rename) and MUST survive a host restart with its status
intact; a restart never loses a pending request.
Consume
An approval authorises exactly one use of exactly the approved payload. The
party that acts on it (the requester, or the host on its behalf) MUST call
consume with the payload it is about to act on. The host MUST:
- Reject the call unless the caller is the original requester. An operator MAY consume any request that an operator made; a session MAY consume only its own.
- Reject the call unless
statusisapproved. - Recompute the canonical hash of the supplied payload and reject the call
if it differs from
payloadHash. - Otherwise set
statustoconsumedand recordconsumedAt.
Rejections use these stable error codes:
| Code | Meaning |
|---|---|
approval_not_found | No request with that id. |
not_requester | The caller did not make the request. |
approval_already_consumed | The request is already consumed. |
approval_expired | The request is expired. |
approval_not_approved | The request is pending or denied. |
payload_mismatch | The supplied payload does not hash to payloadHash. |
Hosts SHOULD evaluate the checks in the order listed above, so that a consumed or expired request reports that state rather than a mismatch.
Binding to signature and audit-event
An approve MUST produce a signature (see the signature doctype) with:
artifactreferring to the payload artifact (thepayload.jsonabove).documentHashequal to the request'spayloadHash. Because the signature is taken over the payload artifact's own bytes, the two values are equal by construction, not by convention. A host MUST NOT record an approve whosedocumentHashdiffers frompayloadHash.decision: "approve",signerKind: "user", andmethod: "click_through". The signer is the deciding human. AsignerKindofagentis never valid for an approval request.evidencewithkind: "click_through", the deciding client'sipAddressanduserAgentas observed by the host, and asignedUrlTokenthat is unique to this decision. For a card decision (below)signedUrlTokenholds the hash of the one-time ticket, never the ticket itself.
A deny MUST append an audit-event with action approval.denied and no
signature. Its subject is { "kind": "approval", "ref": "<id>" } and its
input carries the approvalId and the deciding channel.
In both cases the decision MUST be appended to the workspace's hash-chained
audit log, so that a third-party verifier can walk the chain and find, for
every approved request, a signature over exactly payloadHash, and for
every denied request an audit event with no signature. The approve's
audit event lists the signature path in its signatures[]. The request's
decision.signaturePath is written only after the signature exists; a host
that observes approved without a signaturePath MUST treat the signature
as still being written, not as an unsigned approval.
Human channels
AIP-7 defines signing methods (typed_name, click_through, and others). It
does not say who may trigger one. For approval requests it does:
A decision MUST come from a declared human channel, and MUST NOT be reachable through any tool a model can call.
A channel is a closed mechanism that the host itself gates. Each request
lists, in channels, the channels allowed to decide it; a decision arriving
on any other channel MUST be rejected. A host MAY register further channels
(for example a phone confirmation or a signed link) without changing the
state machine, but each channel MUST satisfy the rule above. This AIP
defines two channels and their conformance requirements.
web_click: a decision from a browser page a human is looking at.
- The host exposes one decision endpoint, taking
approveordeny. - A request MUST be accepted only if BOTH hold: it carries the host's
bearer credential, AND its
Originheader exactly matches an entry in a configured allowlist of web origins. A missing,null, or unlistedOriginMUST be refused before the credential is even examined. - The allowlist defaults to empty. An empty allowlist turns the channel off:
the host MUST omit
web_clickfrom every new request'schannels, and the endpoint MUST refuse every call. - The signature's
signedUrlTokenis a fresh random nonce per decision.
ui_card: a decision from an interactive card rendered by a
host application.
- The card is a UI resource whose HTML is produced on every read, not once at registration. Each read mints a fresh one-time ticket: at least 32 random bytes, base64url, embedded only in that HTML. The host stores only the ticket's hash, gives it a short TTL (10 minutes RECOMMENDED), and minting a new ticket supersedes any earlier ticket for the same request.
- The card's buttons call a decide action that takes the request id, the
decision, and the ticket. That action MUST be declared app-only (in MCP
Apps terms,
_meta.ui.visibilityof["app"]), so that a host filtering tool lists for its model does not offer it. - The host MUST compare the presented ticket to the stored hash in constant time, and MUST burn the ticket on any attempt, valid or not, before doing anything asynchronous. A wrong guess destroys the real ticket too.
- The ticket MUST NOT appear in any tool result, tool definition, log, signature, or audit event. The one place it exists in cleartext is the card HTML it was minted for.
- The card resource and the decide action MUST be served only on a surface that agent sessions cannot reach. A host MUST serve them on a connection dedicated to human-facing clients and MUST refuse that connection when it carries the identity of an agent session. Marking the action app-only is necessary but NOT sufficient: hosts exist that ignore visibility hints and hand their model both a generic resource-read tool and the ability to call any registered tool by name. On a shared surface such a model reads its own approval's card, extracts the ticket, and decides for itself.
Agents never decide
Every model-callable tool a host ships for approval requests MUST be limited
to four operations: create a request, read one, wait for one to leave
pending, and consume an approved one once. None of them may move a request
out of pending. In particular, the host MUST NOT mount the governance
engine's own signing and audit tools (for example sign_artifact,
request_signatures, record_audit_event) on any surface a session can
reach. The host calls the equivalent functions directly from inside the code
path that handles a human decision, never as a tool.
Tool results for these operations MUST NOT contain card tickets, bearer credentials, or any other secret that would let a caller decide.
Threat model and residual risk
The ui_card channel rests on one assumption: the ticket never appears
anywhere a model can read it. The requirements above defend that assumption
against a model that can call tools on the surface it is connected to,
including one whose host ignores visibility hints. They do not defend
against the following, which implementers MUST treat as out of the
guarantee:
- A host that is not human-facing. The card surface is safe only when the connecting host renders UI resources in a sandbox its model never reads, and does not give its model an unfiltered resource-read or tool-call path to that surface. A deployment SHOULD verify this for each host it points at the card surface, and SHOULD NOT rely on the channel for high-stakes approvals until it has.
- Anything holding the local host credential. The dedicated surface carries no credential of its own beyond whatever already gates the host's tool endpoint (loopback trust, or the host bearer token). A process that holds that credential, or that runs on the host's loopback interface when the host runs without one, can open the card surface directly and decide. The surface split stops an agent session's own connection from reaching the card. It does not stop a process that independently has host-level access. This is the same trust boundary the host's other tools already sit behind; approval requests neither widen nor narrow it.
A deployment that needs a stronger guarantee than these bounds allow SHOULD
add a channel with an independent credential the host does not hold, and
declare it in channels.
Out of scope
This amendment specifies the primitive only. Migrating other approval-like
mechanisms onto it (acknowledgement of a policy, held tool permissions,
workflow approval steps, and similar) is out of scope. Each of those
currently proves nothing about a human having decided, and routing them
through approval requests would close that gap, but that is separate work
for the hosts that carry them. Also out of scope: additional channels,
multi-user signer identity (a host with one local user MAY use a fixed
signer such as user:local, and a multi-user host MUST carry the real
identity of the deciding human), and linking requests to a work board.
Rationale
To be authored. Defend: hash-chained append-only over signed snapshots,
per-event JSONL over batched JSON arrays (streaming-friendly), markdown
for POLICY.md (human-authorable autonomy rules), peer-standard status
to AIP-6 instead of merging into one mega-spec.
Why a workspace manifest (GOVERNANCE.md). Individual policies and
signatures answer "what's the rule?" and "did this approval happen?".
They do not answer "what is the posture of this workspace?" — the
binding question that says which policies apply, which keys may sign,
what autonomy ceiling is enforced, what audit retention is locked.
Without a machine-parseable binding, posture lives in prose and drifts.
The manifest is the registry-of-policies surface: a runtime can load
GOVERNANCE.md, validate it, and know exactly which policies to enforce
on every mutation, without re-reading prose.
Why one doctype for both workspace root and view. The alternative is
two doctypes — governance.workspace/v1 for the root and
governance.workspace.view/v1 for the per-consumer adaptation. Two
doctypes means two schemas, two validators, and an asymmetric merge
surface. One doctype with extends: collapses both into a single mental
model: a workspace IS its own view of itself, and every view IS a
workspace bound to a different consumer. The same schema validates both,
the same merge algorithm applies recursively, and the same authoring
skill walks an agent through both flows. This is the same pattern that
AIP-10 uses for KNOWLEDGE.md and
AIP-13 uses for WORK.md.
Why one-way switches are hard errors, not warnings. Append-only and
signing-required are AIP-7's two strongest invariants — the whole
third-party-verifiability story collapses if either can be silently
relaxed by a descendant manifest. A view that downgrades appendOnly: true → false would let a malicious operator skip the chain entirely;
a view that downgrades signing.required: true → false would let
unsigned events into the chain. Both attacks are detectable at merge
time. Refusing the merge with a hard error keeps the parent's
guarantees intact for every consumer, by construction. The one-way
direction (parent strict → child stricter or equal) is the defining
property: posture can only ratchet up.
Why approval requests, and why a human channel rule. A signature
proves a decision happened; it cannot prove a human made it, because the
doctype says nothing about who is allowed to trigger signing. The moment an
agent can request approval and a runtime can sign on approve, the weak point
is whether the agent can also answer its own question. Making "a decision
MUST come from a declared human channel and MUST NOT be reachable through a
model-callable tool" the rule, and stating the two reference channels as
conformance requirements, turns that from a deployment habit into something a
host can be checked against. Pinning the payload by hash and re-hashing it at
consume time closes the other gap: an approval that could be spent on a
different payload than the one the human saw is not an approval of that
payload. The surface split for the card channel is normative rather than a
recommendation because the failure it prevents was observed, not
hypothesised: agent CLIs that ignore visibility hints can read a card and
call its decide action themselves when both share a surface. Reusing
signature and audit-event as the outcome, instead of inventing a second
receipt format, keeps every approval verifiable by the same third-party
verifier as every other AIP-7 artifact.
Reference Implementation
packages/governance/core —
parser, validator, hash-chain implementation, and policy evaluator. The
approval-request primitive is implemented in the runtime package of the
same repository (agentproto/ts#1553).
Backwards Compatibility
First version of the spec, with one wire-format transition already on
the roadmap: the AIP-54 Ref migration described above. Within this
draft, both the legacy kind:slug string form and the AIP-54 object
form are normative inputs; producers SHOULD emit object form, consumers
MUST accept either.
A future AIP-7 v2 will deprecate the string form. The transition window is at least one minor release of every reference implementation listed in the registry, so operators can update producers and consumers independently.
The approval-request doctype is purely additive. The optional
documentHash, signerKind, method, and evidence fields it relies on
in signature are optional in the schema, so existing signatures remain
valid. A host that does not implement approval requests is unaffected.
Security Considerations
Audit-log integrity is the central invariant. The hash chain is verifiable end-to-end by any third party using the published protocol. Threats:
- Tampering: detected by hash mismatch on any altered prior event.
- Truncation: head-pointer attestations (separate, vendor-specific) guard against silent truncation.
- Replay:
audit-eventincludes monotonic sequence numbers and timestamps; replay attacks are detectable. - Signature spoofing: signers identify themselves with cryptographic keys; the spec is key-format agnostic but RECOMMENDS Ed25519 or Sigstore-style transparency-log signatures.
- Posture relaxation via view shadowing: a malicious or careless
view extends a strict workspace and silently downgrades
audit.appendOnlyorsigning.required. Mitigation: HARD REFUSAL at merge time. The host MUST emitgovernance_append_only_relaxation(orgovernance_signing_downgrade) and refuse to activate the view. Unlike the chain warnings (cycle, depth, missing parent) which degrade gracefully, one-way switches do NOT degrade — a relaxed child fails to load entirely. This is the defining safety property ofGOVERNANCE.md. - Keyring drift via view: a child view rebinds
signing.keyringto a path containing keys not in the parent's keyring, effectively authorising new signers without going through the parent's approval gate. Mitigation: hosts emitgovernance_keyring_drift(warn) and SHOULD route the keyring change itself through the parent's approval gate before merging. Hosts MAY upgradegovernance_keyring_driftto a hard refusal in production deployments via runtime policy. - An agent approving its own request: if any model-callable tool can
move an approval request out of
pending, the approval proves nothing. Mitigation: decisions come only from a declared human channel, and the card channel's ticket and decide action are served only on a surface agent sessions cannot reach (see Approval requests). Residual: anything holding the host's local credential can reach the card surface. - Approving one payload and executing another: mitigated by pinning the
payload with
payloadHashand re-hashing at consume time (payload_mismatch). Thepreviewis display-only and is not covered by the hash. - Approval replay: an approval is consumed once (
approval_already_consumed) and only by its requester. - Ticket leakage: card tickets are one-time, short-lived, stored hashed, burned on any attempt, and never written to a signature or audit event.
- Schema poisoning of
GOVERNANCE.mditself: an attacker rewrites the workspace root to relax policies, narrow the keyring, or unbind the audit chain. Mitigation: edits toGOVERNANCE.mdMUST be subject to the workspace's own approval gate — i.e. mutatingGOVERNANCE.mdis itself a governed action. The reference implementation gatesGOVERNANCE.mdwrites through the policy registered withappliesTo: governance.workspace/v1(or*if absent).
Resources
Supporting artifacts for AIP-7. Links open the file on GitHub — markdown and JSON render natively in GitHub's viewer. Browse the full resource tree →
AIP-6: COMPANY.md — agentcompanies/v1 (company, role & objective primitives)
A filesystem-first, vendor-neutral file format for representing AI companies — their org structure, roles, and objectives — as portable git-native packages.
AIP-8: AGENCY.md — agentagencies/v1 (autonomous agency engine)
A filesystem-first operations format that extends agentcompanies (AIP-6) and agentgovernance (AIP-7) with the doctypes needed to run an autonomous agency — services, procedures, engagements, agreements, deliverables, invoices.