Give AI autonomy without giving it authority
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
| Thing | v0.6 | v0.7 (spec, wire, and public) | Public synonym |
|---|---|---|---|
| What a person gives an agent — the signed, bounded, revocable permission | attestation (records a mandate) | mandate | — |
| What a mandate produces for one action, obtained before it runs, kept as proof after | execution receipt | mandate ticket / ticket | — |
| The enforced limits inside a mandate | bounds | bounds (kept) | limits |
| The local, signed dimension a mandate applies to | context | scope | — |
| The identifiable human who gives the mandate and answers for it | Decision Owner | Mandate Owner | the person in charge |
| The condition the protocol exists to end | standing authority / standing credentials | without 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).
Promoted into v0.7
None has an external integrator yet (maturity marker: absent, ecosystem-wide).
| Direction (v0.6 name) | Landed as | Basis |
|---|---|---|
Bound↔action pairing: specify appliesTo | protocol.md → Bounds Schema, rule 7 | rule 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 half | protocol.md → Mandate Tickets → Retention (Gatekeeper custody, MUST, with failure semantics, a retention floor, and scope); governance.md → Retention Enforcement | rule 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” audit | protocol.md → Enforcement classes — every control annotated honest-operator control or compromise-resistant evidence; governance.md → Trust Model consequence closed; AS Accountability annotated | rule 2 — an annotation, not a design; obliged by The Authority Server Cannot Check Itself |
| Identifiable-human Mandate Owner | protocol.md → The Mandate Owner; governance.md → Invariant 2 | rule 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 vectors | governance.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 tables | normative 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 permanent | protocol.md → Revocation is permanent | rule 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. delegation | governance.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 negotiation | rule 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):
HAP-attestation→HAP-mandate;attestation_id→mandate_id;context_hash→scope_hash;contextSchema→scopeSchema.resolved_owners(a DID list) andowner_mandates(a signature list) →mandate_owners, one entry per owner:{ did }, plus the signature fields where the owner co-signed. One owner, one entry, one name. The person is the mandate’s owner; their signature goes on the mandate — it was never a second “mandate” object.- The owner-signed projection
typ: "HAP-mandate"→"HAP-mandate-projection", freeingHAP-mandatefor the thing it names. ownerMandate(profile floor) →ownerSignature;receipt_lookup→ticket_lookup; gatedecision_owner→mandate_owner;MALFORMED_RECEIPT_REQUEST→MALFORMED_TICKET_REQUEST.- Additive:
issuer(mandate, ticket),profile_hash(mandate, projection),versionandmandateId(ticket),disclose_fields(profile, mandate, projection),above_cap_caps/above_cap_approvers(projection),supported_versionsand co-signedexpires_at(request); codesPROFILE_INVALID,PROFILE_HASH_MISMATCH,VERSION_UNSUPPORTED; an emitter column on both error tables. - Error codes:
ATTESTATION_*→MANDATE_*;MALFORMED_ATTESTATION→MALFORMED_MANDATE;PROPOSAL_ATTESTATION_MISMATCH→PROPOSAL_MANDATE_MISMATCH;CONTEXT_*→SCOPE_*;MANDATE_SIGNATURE_*→OWNER_SIGNATURE_*;SCOPE_INSUFFICIENT→COVERAGE_INSUFFICIENTandOWNER_SCOPE_MISMATCH→OWNER_NOT_APPROVER(so scope has one meaning). - Section titles: Attestations → Mandates; Execution Receipts → Mandate Tickets; Owner Mandate Signatures → Owner Signatures; Decision Ownership → Mandate Ownership; the axis mandate assurance → signature assurance.
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:
- The owner signed
expires_atbefore it existed. The request MUST NOT carryexpires_at(the AS derives it fromttl), yet the projection includes it as the replay defence. Resolved: a co-signed request carries an absolute, owner-chosenexpires_atwithin the profile’s TTL range, and the AS signs exactly that value or refuses;ttlis for unsigned requests. The projection is unchanged. - The projection did not cover the caps.
above_cap_capsandabove_cap_approverswere AS-signed only, so a compromised AS could keepreview_above_capand 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 vectormandate-projection-above-cap. - “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.
- The ticket could not name its mandate. Under permanent revocation a replacement mandate shares the revoked one’s
bounds_hash; the ticket carried onlyboundsHashanduserId.mandateIdis now a signed ticket field (audit only —boundsHashstays the lookup key).limitsis defined as the mandate’s plaintext bounds. Ticket vector regenerated. profile_hashover “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 setprofile-hash.jsonpins the publishedcharge@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:
- A contradiction in the escape rules. Canonicalization Rules refused raw newlines in a value and then listed
\nand\ramong the bytes to percent-encode — permission to encode what the previous line rejects. v0.7 removes them from the encode list. No hash changes: no conformant implementation could reach that branch. - The escaping rules had never been exercised. No published profile carries a value with
=,%, a non-ASCII byte, or a newline, so an implementation could pass every live case without implementing the rules at all — which is luck rather than enforcement. The vectors now pin both rules, so the gap fails a check instead of waiting for a second implementation to find it.
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:
- A ticket/receipt split — ticket going in, receipt coming out. The artifact does not change, so its name should not either; a concert ticket after the show is still a ticket. Two names for one thing solved a technical audience’s worry by creating a vocabulary problem.
- All-access pass, blank check, master key as the villain. Each describes too much access — the 2024 problem, which scoped tokens and agent identity are solving. In two years the typical agent will have carefully limited access and the fog will be exactly as thick, because limited access still has no human’s name on each act. The villain is a condition, not a thing: without a mandate.
- A bet, a degree, a peacock’s tail as analogies for costly proof — luck; cost without exposure; trait without decision. The credential era is the better frame (see
doc/v07/power-through-accountability.md). - A new expansion of the acronym — see Renames.
- “Takes longer to judge” as the distinction between limits and direction — true, but requires the reader to think sideways.
limitsas the specification term — bounds kept; “limits” is its public synonym. Spec prose saying “limits” over a wire sayingbounds_hashis the spec≠wire debt the release exists to remove.templatefor profile;actionreplacingexecution— kept, reasons under The vocabulary.mandate_owner(singular) as the field name — in v0.7 the array holds exactly one entry (Identity rule 6 forbids any other DID), so the singular would be accurate today; kept plural because the array form is reserved for a multi-signer ceremony not yet specified, and a singular would have to be renamed the day it lands. Stated plainly in Owner Authority so the plural is not read as a promise.- “No mandate, no execution” alone as the invariant — true but only half: it is what every permission system promises, and only for access. The two-line form keeps the half only HAP promises — a name on each act.
- The why / what / how anatomy in normative text (the human keeps the why and the what-at-most and the when-to-ask-me; the agent owns the how). Right for teaching and mission-command framing; kept out of the protocol, which already separates Direction State from Authorization State and verifies commitment, not fit.
Review record
The recorded review behind this version, per rule 2:
- Vocabulary rounds (external advisor, extended session, August 2026 — findings in
doc/v07/findings.md, proposal indoc/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 inreview.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 todocs/essence.mdand the website. The ones rejected are listed above. - 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, theHAP-mandatecollision, 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. - 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)appliesTokeeps 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), andMALFORMED_RECEIPT_REQUESThad survived (B2). Findings that changed signed bytes:issueron mandate and ticket,versionon the ticket,profile_hashon the mandate,disclose_fieldson the mandate. Findings that corrected the ledger: the reference AS issuesversion: "0.5"withresolved_domains, emits retired and non-canonical error codes, and the signing vectors were never in the published package — all now stated inreview.md. Wording findings: a Mandate Owner is identifiable, not named (the protocol’s default is a pseudonymous DID; the name is opt-in);mandate_ownersholds exactly one entry. - 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).