protocol agentproto.shcli cli.agentproto.shpanel /panel
agentproto

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.

FieldValue
AIP7
TitleGOVERNANCE.md — agentgovernance/v1 (audit, approval & policy primitives)
StatusDraft
TypeCore
Domaingovernance.sh
RequiresAIP-54
Doctypessignature (approval event), audit-event (append-only log line), policy (declarative rule), approval-request (pending human decision), governance.workspace/v1 (manifest + view)
Composes withAIP-3 (skills), AIP-6 (companies), AIP-9 (operators), AIP-10 (knowledge), AIP-13 (work)
Reference Implpackages/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.

DoctypeFile pathPurpose
governance.workspace/v1<scope>/GOVERNANCE.mdWorkspace manifest (root or view) — declares posture, registers policies/approvers
signature<artifact>/../signatures/<signer>-<isoDate>.signature.jsonUniversal 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.mdDeclarative autonomy rule
approval-request<scope>/approvals/<id>/request.jsonA 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.json and audit-log.jsonl lines
  • Slug-based references, never database IDs
  • schema: agentgovernance/v1 on 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 fieldAIP-54 collectionExample values
signature.signerRefIn<"identity">operator:atlas, user:abc123, persona:atlas, email:[email protected]
signature.artifactRefIn<"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.actorRefIn<"identity"> (nullable for system events)same as signer
audit-event.entityRefIn<"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[].signerRefIn<"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:

  1. Replace kind:slug regex parsing with the canonical AIP-54 parser. Adding a new identity kind (e.g. did, fediverse_handle) becomes "register a kind into the identity collection"; AIP-7 needs no change.
  2. Validate paths through Ref<local> including the path-escape rules and optional #sha256=… fragment. The signature.documentHash field still exists separately as the recorded hash; the optional sha-fragment on the signature.artifact Ref is the expected hash the caller asserted (rejected by the runtime on mismatch with disk).
  3. Open the anchor encoding from a single AnchorPayload shape to any Ref in the anchor collection — 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, no extends. 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 parent GOVERNANCE.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:

  1. Walk the parent chain. Recursively load the parent referenced by extends:, then that parent's parent, until a manifest with no extends is reached. Maximum chain depth is eight. Hosts MUST detect cycles by tracking visited absolute paths.
  2. 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 (or governance_extends_depth_exceeded) warning.
  3. Tolerate a missing parent. If extends: points to a path that does not exist, the host emits governance_extends_missing as a warning and uses the local manifest only.
  4. 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):

FieldStrategyNotes
name, title, description, versionoverrideChild's identity wins; both are exposed via the resolution chain.
extendslocal-onlyNot inherited.
appliesTolocal-onlyNot inherited; each view declares its own scope.
autonomy.*leaf-field overridelevel, defaultApproval, approvalEscalation each override independently.
signing.algo, signing.keyringoverrideChild 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.requiredone-way switchIf parent is true, child MUST NOT downgrade to false. Hard refusal: governance_signing_downgrade.
audit.retention, audit.hashAlgo, audit.storage, audit.headPointerSignleaf-field overrideEach independent.
audit.appendOnlyone-way switchIf parent is true, child MUST NOT downgrade to false. Hard refusal: governance_append_only_relaxation.
policiesmerge-by-idSame id → child replaces parent. New ids → appended.
approversmerge-by-idSame id → child replaces parent. New ids → appended.
executor, escalateTo, work, knowledgeoverrideChild can rebind.
display.*leaf-field override
metadatadeep-mergeRecursive 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:

FieldReferencesPurpose
executorAIP-9 operatorNames the operator the host activates to run governance flows (apply policies, collect signatures, append audit).
escalateToAIP-9 operatorWhen approvalEscalation fires, route to this operator.
workAIP-13 WORK.mdBind audit events to a work-tracking workspace; mutations recorded here also produce work-item updates.
knowledgeAIP-10 KNOWLEDGE.mdBind the wiki this workspace governs; schema changes to the wiki flow through THIS workspace's approval gate.
appliesToAIP-3 skill, AIP-6 company, AIP-9 operatorA view declares which consumers it adapts the workspace for. Hosts MUST refuse a view whose appliesTo references a non-existent consumer (governance_appliesto_unresolvable).
extendsanother GOVERNANCE.mdComposition.
policies[].refAIP-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

AspectWorkspace-root modeView mode
File path<scope>/GOVERNANCE.md<consumer>/GOVERNANCE.md
extends:absentrequired (otherwise it's a workspace, not a view)
appliesTo:absent (a workspace has no single consumer)OPTIONAL but conventional
Effective shapethe manifest as writtenmerge of the chain, child wins (subject to one-way switches)
audit.appendOnlydeclares the lockMAY NOT relax
signing.requireddeclares the lockMAY NOT downgrade
Mutabilityedits are themselves governed by the workspace's own policieslocal edits adapt the lens, do not affect the workspace
Use casesorg-wide posture authorsoperator/company/skill teams who want a stricter lens
Validationfull schema checkschema check + chain validation + one-way invariant check
Lifecycleversioned with the scopeversioned 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.

FieldRequiredMeaning
idyesOpaque unique identifier, stable for the life of the request.
kindyesFree-form class of the thing being approved (for example deploy or send-email). Consumers key their own handling on it.
titleyesOne-line human-readable summary.
previewnoJSON 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.
payloadHashyesLowercase hex SHA-256 of the canonical JSON of the pinned payload (see below).
statusyesOne of pending, approved, denied, expired, consumed.
requestedByyesThe requester: { "sessionId": "<id>" } for an agent session, or { "operator": true } for the operator acting directly.
channelsyesThe human channels that may decide this request (see Human channels).
requestedAtyesRFC 3339 timestamp.
expiresAtnoRFC 3339 timestamp after which a pending request can no longer be decided.
decisionwhen decided{ decision, channel, decidedAt, signaturePath? }. decision is approved or denied. signaturePath is present only on an approve.
consumedAtwhen consumedRFC 3339 timestamp of the consume.
taskId, appIdnoCorrelation 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:

  1. null, booleans, numbers, and strings serialise as ECMAScript JSON.stringify would: no insignificant whitespace, shortest round-trip number form, and JSON string escaping.
  2. An array serialises as [ then its elements, canonicalised in order and joined by ,, then ]. Array order is preserved.
  3. 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.
  4. A value that is undefined serialises as null, including as an object member (the key is kept, with value null).

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 -> approved and pending -> denied happen only through a human channel (see below). Deciding a request that is not pending MUST be rejected, and MUST NOT alter the existing decision.
  • pending -> expired happens when expiresAt has passed. Hosts MAY expire lazily, flipping the status the next time the record is read or decided, provided no decision is accepted after expiresAt. A request with no expiresAt never expires.
  • approved -> consumed happens once, through consume. denied, expired, and consumed are 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:

  1. 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.
  2. Reject the call unless status is approved.
  3. Recompute the canonical hash of the supplied payload and reject the call if it differs from payloadHash.
  4. Otherwise set status to consumed and record consumedAt.

Rejections use these stable error codes:

CodeMeaning
approval_not_foundNo request with that id.
not_requesterThe caller did not make the request.
approval_already_consumedThe request is already consumed.
approval_expiredThe request is expired.
approval_not_approvedThe request is pending or denied.
payload_mismatchThe 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:

  • artifact referring to the payload artifact (the payload.json above).
  • documentHash equal to the request's payloadHash. 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 whose documentHash differs from payloadHash.
  • decision: "approve", signerKind: "user", and method: "click_through". The signer is the deciding human. A signerKind of agent is never valid for an approval request.
  • evidence with kind: "click_through", the deciding client's ipAddress and userAgent as observed by the host, and a signedUrlToken that is unique to this decision. For a card decision (below) signedUrlToken holds 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 approve or deny.
  • A request MUST be accepted only if BOTH hold: it carries the host's bearer credential, AND its Origin header exactly matches an entry in a configured allowlist of web origins. A missing, null, or unlisted Origin MUST 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_click from every new request's channels, and the endpoint MUST refuse every call.
  • The signature's signedUrlToken is 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.visibility of ["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-event includes 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.appendOnly or signing.required. Mitigation: HARD REFUSAL at merge time. The host MUST emit governance_append_only_relaxation (or governance_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 of GOVERNANCE.md.
  • Keyring drift via view: a child view rebinds signing.keyring to a path containing keys not in the parent's keyring, effectively authorising new signers without going through the parent's approval gate. Mitigation: hosts emit governance_keyring_drift (warn) and SHOULD route the keyring change itself through the parent's approval gate before merging. Hosts MAY upgrade governance_keyring_drift to 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 payloadHash and re-hashing at consume time (payload_mismatch). The preview is 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.md itself: an attacker rewrites the workspace root to relax policies, narrow the keyring, or unbind the audit chain. Mitigation: edits to GOVERNANCE.md MUST be subject to the workspace's own approval gate — i.e. mutating GOVERNANCE.md is itself a governed action. The reference implementation gates GOVERNANCE.md writes through the policy registered with appliesTo: 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 →