Give AI autonomy without giving it authority

Version 0.7 September 2026

Human Agency Protocol — v0.7 Changelog

This document is the backward-looking record of v0.7: what was promoted into the binding surface, under which rule, on the strength of which recorded review, what was renamed, and what was retired. The forward ledger — open directions, deviations, implementation status, tracked audits — is review.md.

What v0.7 is

v0.7 is a vocabulary and consolidation release. It changes one invariant’s wording, sharpens one invariant’s boundary (Invariant 2 — an owner is one identifiable human), adds five signed fields the wire break made cheap (issuer, profile_hash, ticket version and mandateId, disclose_fields), and renames every word that failed a single test:

A word wins when it survives being repeated by someone who half-understands it.

The people who retell the protocol are not engineers — a CFO explaining to her board, an employee explaining why she is not afraid of her agent, a works council asking who allowed something, a journalist explaining a scandal. Engineers adopt whatever the config file says; they are not the retellers. By that test attestation, context, commitment mode, review_above_cap and execution die in the first retelling, and receipt nearly passes but carries the wrong tense — in ordinary life a receipt comes after paying, and the protocol’s whole point is before.

The v0.6 glossary had already fixed the right concepts (authority, mandate, capability, execution) and kept the old words alive inside signed identifiers “to avoid invalidating existing artifacts.” v0.7 reverses that trade. One vocabulary for the specification, the wire, and the public — because two vocabularies drift, and a protocol whose spec says one thing, whose JSON says another, and whose sales deck says a third accumulates exactly the terminology debt that kills long-run adoption. The rename is breaking on the wire and done now on purpose: there is one reference implementation, no external integrator, and every artifact has a TTL. It will never be this cheap again, and it would be unthinkable after 1.0.

The promotion rule (unchanged from v0.6)

A direction is promoted when (1) a reference implementation exercises it end-to-end and (2) the design has survived recorded adversarial review; or (3) specification-led, where the design is additive, optional, and its absence changes nothing for existing artifacts — marked as such and tracked in review.md until closed. External-integrator dependence remains a maturity marker (absent, ecosystem-wide). The rule text and its rationale are in the v0.6 changelog.

The vocabulary

Thingv0.6v0.7 (spec, wire, and public)Public synonym
What a person gives an agent — the signed, bounded, revocable permissionattestation (records a mandate)mandate—
What a mandate produces for one action, obtained before it runs, kept as proof afterexecution receiptmandate ticket / ticket—
The enforced limits inside a mandateboundsbounds (kept)limits
The local, signed dimension a mandate applies tocontextscope—
The identifiable human who gives the mandate and answers for itDecision OwnerMandate Ownerthe person in charge
The condition the protocol exists to endstanding authority / standing credentialswithout a mandate / unmandated—
The invariant”No receipt, no execution""No mandate, no ticket. No ticket, no execution.”Nothing runs without a ticket.
The thesis—Access is not a mandate.—

Kept unchanged, each for a stated reason: bounds (survives retelling — “within bounds” is ordinary English — and bounds_hash is the most-signed field in the protocol); profile (neutral, and “template” suggests something one copies and edits, which a published profile is not); execution and Executor (the action / execution distinction is load-bearing and the public never meets the section); Gatekeeper and Authority Server (role names; “the gate” and “the signing service” in speech); the commitment-mode values automatic / review / review_above_cap (wire enums; their human-readable names are runs on its own / asks first / asks above a limit); executionContext (the per-call values — a different thing from scope, and the rename disambiguates them).

None has an external integrator yet (maturity marker: absent, ecosystem-wide).

Direction (v0.6 name)Landed asBasis
Bound↔action pairing: specify appliesToprotocol.md → Bounds Schema, rule 7rule 1 + 2 — all eight published profiles declared it by 2026-08-27; both enforcement points read it. Step 2 (retiring the field-name fallback) stays open in review.md until no live mandate references a pre-declaration profile version
Subject custody of evidence — the Gatekeeper halfprotocol.md → Mandate Tickets → Retention (Gatekeeper custody, MUST, with failure semantics, a retention floor, and scope); governance.md → Retention Enforcementrule 1 + 2 — exercised end-to-end by an implementation since August 2026; challenged in the 0.7 advisor round (best-effort contract, no ceiling, embedded Gatekeepers — all three resolved in the text: archive failure now blocks). The AS-export half remains open
The “enforced” auditprotocol.md → Enforcement classes — every control annotated honest-operator control or compromise-resistant evidence; governance.md → Trust Model consequence closed; AS Accountability annotatedrule 2 — an annotation, not a design; obliged by The Authority Server Cannot Check Itself
Identifiable-human Mandate Ownerprotocol.md → The Mandate Owner; governance.md → Invariant 2rule 2 — sharpens “collective ownership is invalid” into “a role, committee, policy, shared account, or system MUST NOT be enrolled as an owner identity; institutional authority is valid only through one identifiable person who answers for the act” — identifiable, not named: the default owner record is a pseudonymous DID and the name is opt-in
Conformance vectorsgovernance.md → Reference Conformance, plus the published data at content/0.7/vectors/ — canonical bounds and scope hashes, five payload signatures under the RFC 8032 §7.1 test keys (including a co-signed mandate whose case walks the AS-signature → rebuilt-projection → owner-signature chain), and the required-refusal tablesnormative deliverable of the specification — not a rule-3 promotion (a MUST-ship artifact is neither additive nor optional). The files exist as of 2026-09-03; every value was computed and then re-derived by an independently written implementation before publication. No implementation consumes them yet — tracked in review.md
Revocation is permanentprotocol.md → Revocation is permanentrule 1 + ledger record — enforced end-to-end by an implementation since July 2026 (per-ceremony mandate identity); carried as a proposal in the v0.6 ledger and left unchallenged in the 0.7 advisor round; the weaker v0.5 mechanism is retired (below)
Control vs. delegationgovernance.md → Relationship to execution-boundary control models (non-normative)rule 2 — positioning, recorded in the specification so the protocol is not read as a policy engine
Five additive signed fields (from the two 0.7 advisor rounds)issuer on mandate and ticket; profile_hash on the mandate and in the owner-signed projection; version and mandateId on the ticket; disclose_fields on profile and mandate; supported_versions and (co-signed) expires_at on the request; Mandate Request Schema; Version negotiationrule 2 — each closes a hole the review found (no artifact named its issuer or key; tickets had no version; “content-addressed profiles” was claimed and not true; the disclosure declaration had no shape; the transition had no rule). Taken now because this is the release that already breaks the wire

Slipped from v0.7 to v0.8, recorded: the dual-signed public ticket projection (flagged in the v0.6 ledger as a security dependency of Invariant 10) and the verifier-policy document. Both depend on things v0.7 only now supplies — the disclose_fields declaration shape and issuer in the signed payload — and both sit behind owner-signature phase P2 (WebAuthn in the AS), which has not started. Retargeted rather than dropped; the dependency is the reason.

Renames and terminology

Signed payload, profile, and wire (the full old→new map with the unchanged list is protocol.md → Migration from v0.6):

The name of the protocol is unchanged. Changing the expansion of the acronym — keeping HAP, replacing “agency”, which “agentic AI” has captured, with a word that shares a root with the rest of the vocabulary — was considered and withdrawn: the alternative expansion already carries a public, dated third-party definition meaning institutional control at the execution boundary, and adopting it would inherit that definition in every reader’s mind. Domain availability is not evidence either way — companies register brands, not category names. The remaining path is the tagline: the name never travels alone. People stay in charge, AI does the work, and nothing runs without a ticket.

Public layer. Slogans, ranked: Nothing runs without a ticket. — Access is not a mandate. — A key with no name on it, or a ticket with a name on it. — Every ticket has a name on it. — People stay in charge. AI does the work. And everyone can prove it. German: Mandat / Vollmacht; Ticket; Grenzen; ohne Mandat / ohne Vollmacht handeln (already legal language: Vertreter ohne Vertretungsmacht); Ohne Ticket läuft nichts.; Zugang ist keine Vollmacht.; Der Mensch behält das Sagen. Retired from the stage: receipt, attestation, standing authority, execution as a noun for the act, context, profile (say template in speech only), commitment mode and review_above_cap as spoken words, agency as a spoken word, and “No receipt. No execution.” The source documents are in the repository under doc/v07/.

Found while re-checking the advisor review against the draft

Applying a review’s fixes creates its own defects, so the draft was checked against the review a second time. All twenty-seven findings were confirmed applied; six new problems, all introduced by the fixes themselves, were found and closed. One had a security consequence and is recorded here in full:

profile_hash had to be added to the owner-signed projection, not only to the AS-signed payload. The field was added (advisor finding M6) so that “profile bytes are content-addressed” would stop being a claim the specification made without a mechanism. But it landed only in the payload the Authority Server signs, while the owner’s own projection carried profile_id alone. That left the profile tier exactly where M6 found it: a compromised AS could point the mandate at different bytes for the same identifier — bytes with no ownerSignature floor, a wider actionTypes registry, a looser content_binding — and nothing the owner signed would have contradicted it. A control the operator can restate at will is the class of control this whole mechanism exists to leave behind. The projection now carries profile_hash, so the owner commits to which rulebook applied.

The other five were editorial and are fixed without further comment: a normative rule list numbered 6, 8, 7; two request-only fields (profile_hash, supported_versions) added to a request that had no written schema — now Mandate Request Schema, which also states what a request MUST NOT carry; Ticket Verification still telling a verifier to find the issuer key “via DID, DNS, API endpoint, or static config” after issuer had been added to name it; two places still calling a mandate a “grant” or “authorization” in normative text; and one governance list naming the disclosure declaration in prose after it had acquired a field name.

Found in the second advisor review (2026-09-03)

A second external pass re-read the draft after the first review’s fixes and recomputed every vector independently (40 of 40 matched). It found five defects in the signed bytes or their rules, all fixed the same day and all cheap only because this release already breaks the wire:

  1. The owner signed expires_at before it existed. The request MUST NOT carry expires_at (the AS derives it from ttl), yet the projection includes it as the replay defence. Resolved: a co-signed request carries an absolute, owner-chosen expires_at within the profile’s TTL range, and the AS signs exactly that value or refuses; ttl is for unsigned requests. The projection is unchanged.
  2. The projection did not cover the caps. above_cap_caps and above_cap_approvers were AS-signed only, so a compromised AS could keep review_above_cap and raise every cap — a mandate that reads as reviewed and behaves as automatic; the “does prove” paragraph claimed otherwise. The mandate-level disclosure narrowing was likewise uncovered. Both are now conditional projection fields, present exactly when the mandate carries them. New vector mandate-projection-above-cap.
  3. “Per mandate” cumulative state. Two sentences said totals are kept per mandate; the key had no mandate in it. The bucket is shared across every mandate of the same group, profile, and action type, and each request is checked against its own mandate’s ceiling — now stated, with the worked example corrected. No conformant implementation changes.
  4. The ticket could not name its mandate. Under permanent revocation a replacement mandate shares the revoked one’s bounds_hash; the ticket carried only boundsHash and userId. mandateId is now a signed ticket field (audit only — boundsHash stays the lookup key). limits is defined as the mandate’s plaintext bounds. Ticket vector regenerated.
  5. profile_hash over “exact bytes”. Two honest parties provisioning the same profile through different channels would hold different bytes and refuse each other. Defined over the JCS serialization of the parsed profile; new vector set profile-hash.json pins the published charge@0.5.

Also from that pass, and fixed: disclose renamed disclose_fields on profile and mandate, because subjects[].disclose already named the identity-disclosure object (two signed fields, one name); approvalSignature is the signature object itself, never its hash (a hash reverts the check to an AS lookup); ticket version is the mandate’s version, so a ticket never arrives in a version its Gatekeeper cannot read — and a ticket without the field is read under its mandate’s version, not as “0.6” (tickets have existed since v0.4); per-transaction local enforcement is a MUST, since the enforcement-class table already rested a compromise-resistant claim on it; local cumulative enforcement is removed rather than deprecated; every error code now names its emitter and COVERAGE_INSUFFICIENT sits in the ticket table where it fires; BCP 14 boilerplate; a 300-second clock-skew tolerance; nonce uniqueness per (owner_did, nonce); a signing-key publication and rotation rule; a transport-security sentence; a size-limit sentence; the “Planning & analysis” row of the Human-Gated table, dropped by the first rename, restored; and the wording residues — “named” → identifiable in Invariant 1, “grant” → mandate in seven normative sentences, “Execution scope schema” → context in governance, a truncated custody bullet completed, one heading reference repaired, the three Retention headings disambiguated.

Found while fixing a canonicalizer (same day): the canonical form of an absent optional bounds key had never been specified, and an implementation had rendered a placeholder. v0.7 specifies omission and pins it with two vectors. Hash-affecting for any mandate with unset optional bounds under an implementation that did otherwise; the migration is an implementation matter and belongs in its report.

Unrecorded changes from the first round, recorded now: the percent-encoding rule gained “uppercase hex, over the value’s UTF-8 bytes” (hash-affecting for non-ASCII values; pinned by the scope-percent-encoding vector); Invariant 1’s date was corrected from v0.5 to v0.4 and Invariant 7 reworded from “record of execution” to “proof that this execution was authorized before it ran”; issuer-key resolution “via DID, DNS, API endpoint, or static config” was narrowed to issuer-based resolution — a normative narrowing, not an editorial one; and the key-loss event, which had been “of the same class as revocation.superseded”, is now named (owner_key.enrolled) after that class was retired.

Implementation status left the specification (2026-09-03)

Until this day review.md carried two registers describing where the reference implementation stood against the specification — down to file paths and, after the second review, to defects whose fixes had not shipped. That was withdrawn, for a reason that will not change: a specification is read by people who cannot see the implementation, so status written into it is a claim they cannot check — and one component of the reference implementation is proprietary, so the claim also published internals of a closed system. Implementation status now lives in each implementation’s own report (governance.md → Reference Conformance → Implementation reports); the reference implementation’s is hap-e2e/CONFORMANCE.md. review.md keeps what the specification owns: open directions with targets, and deviation rules (such as grandfathering) that bind every implementation. The promotion rule is unchanged — a direction still needs an implementation to have exercised it — but the evidence line in this changelog names the fact and the date, not the code.

Found while building the vectors

Writing the answer key found two things the prose alone had not, which is the argument for building it before a second implementation rather than after:

Corrections to the record

appliesTo was a protocol change wearing a bug fix’s clothes. It entered an implementation on 2026-07-27 as a bug fix — the local cumulative gate was not firing because nothing could say which bound governed which action — and the profiles came to depend on it before any specification text described it. v0.6, written two weeks later “from 0.5 plus the review ledger”, had nothing to pick up, and the audit found the undocumented field five weeks after that. The procedural lesson: a fix that changes what a profile may declare is a specification change, whatever ticket it arrived on.

The v0.6 glossary’s “keep signed identifiers” clause is reversed, not quietly overwritten. It said renaming signed identifiers “would invalidate existing artifacts to gain a synonym.” That is true of artifacts and false of the protocol: existing artifacts keep their version and their field names and stay verifiable; only the vocabulary is invalidated, and it is invalidated on purpose (see What v0.7 is).

Retired in v0.7

Revocation Supersession (protocol.md, since v0.5). Permitted an AS to treat a revocation as superseded when the owner re-issued the same bounds_hash, with attendant MUSTs about audit events and revocation-list output. Never implemented; the reference AS adopted the stricter opposite — a revocation is permanent for its id, and “I want it back” is a new mandate with a new id. A described-but-unbuilt, weaker mechanism invites a later implementer to build it believing it endorsed, and it would weaken the audit story the stricter rule protects. Retired; replaced by Revocation is permanent. The design record remains in the v0.5 and v0.6 archives.

The words attestation, execution receipt, context (for the signed local dimension), and Decision Owner — retired from normative text, from the wire, and from public copy. The slogan “No receipt. No execution.” — retired for tense; replaced by the two-line invariant.

Considered and rejected

Recorded so the next reviewer finds the arguments already made:

Review record

The recorded review behind this version, per rule 2:

  1. Vocabulary rounds (external advisor, extended session, August 2026 — findings in doc/v07/findings.md, proposal in doc/v07/hap-vocabulary-and-narrative.md): the retelling test; the single-root derivation from mandate; the villain as a condition; one object, one name; the acronym expansion left unchanged; the three-part anatomy of a mandate and why the protocol verifies commitment to all three but cannot verify the fit; shield and exposure as one record; why the stakes are the value; institutional authority through named humans; the arrival of execution-boundary control models and what distinguishes a delegation model; provenance as prior art; hierarchies of accountability and the sub-mandate gap. Fourteen findings: twelve landed in this version or in review.md; findings 7 and 8 (shield and exposure as one record; why the stakes are the value) are public-layer material and landed in neither — they belong to docs/essence.md and the website. The ones rejected are listed above.
  2. Adoption round (author, 2026-08-31): the findings recommended aliases beside the spec’s terms; the author decided on replacement — one vocabulary, protocol-first, implementations adapt — and fixed the invariant wording, kept bounds, and chose mandate_owners. The objections raised against replacement (wire churn, the HAP-mandate collision, the v0.6 clause on signed identifiers) and their resolutions (a versioned wire migration while there is one implementation; the projection renamed; the clause reversed on the record) are in What v0.7 is and Corrections to the record.
  3. Advisor review of the draft (external advisor, 2026-08-31, doc/v07/advisor-review-0.7.md — a rename-normalized diff of 0.7 against 0.6 plus targeted greps in the implementation): five blockers, ten major, twelve minor. All accepted except two push-backs, recorded here: (a) appliesTo keeps one name on bounds and on content bindings — the opposite defaults are documented and held by the profile-compliance checks rather than renamed, because two names for one key is the debt this release removes; (b) the reference AS’s non-canonical error codes are implementation work tracked in the plan, not specification text. Findings that caught rename damage the author had introduced: the intent-disclosure verification chain had been re-pointed from the AS signature to the optional owner signature (B1), and MALFORMED_RECEIPT_REQUEST had survived (B2). Findings that changed signed bytes: issuer on mandate and ticket, version on the ticket, profile_hash on the mandate, disclose_fields on the mandate. Findings that corrected the ledger: the reference AS issues version: "0.5" with resolved_domains, emits retired and non-canonical error codes, and the signing vectors were never in the published package — all now stated in review.md. Wording findings: a Mandate Owner is identifiable, not named (the protocol’s default is a pseudonymous DID; the name is opt-in); mandate_owners holds exactly one entry.
  4. Ledger round (the v0.6 review.md, 2026-08-15 → 2026-08-28): the items promoted above were each carried there with a target of v0.7 and a written rationale; the corrections to that ledger (two register-2 entries found conformant on 2026-08-15) are preserved in the v0.6 archive.

Every v0.6 direction appears in exactly one v0.7 bucket: promoted (above), open (review.md), or retired (here).