Give AI autonomy without giving it authority

Version 0.8 November 2026

Human Agency Protocol — v0.8 Changelog

This document is the backward-looking record of v0.8: what was promoted into the binding surface, under which rule, on the strength of which recorded review and which implementation evidence, what was retired, and what was found while writing it. The forward ledger — open directions and deviation rules — is review.md. Implementation status is in neither (governance.md → Reference Conformance → Implementation reports).

What v0.8 is

v0.8 is the first version written from use. v0.7 was published on 2026-09-03 and the reference implementation switched to its wire on 2026-10-09. Between the two dates and for five weeks after, fifteen entries were added to the v0.7 ledger — each found in a demo, a gated release, a team setup, or an audit of a live Authority Server’s stored data, each with a stated direction, and twelve of them built end to end before this text was drafted. v0.8 promotes what was built and reviewed, pins the four details the ledger had left open in the shape the implementation chose, and adds two things no one has built, because the public ticket page has been a standing violation since v0.7 and only they close it.

It is a re-grant release (decided 2026-10-30). No field is renamed or retired and every new signed field is optional or conditional, but a v0.8 Authority Server no longer honours v0.7 mandates: every Gatekeeper upgrades and every owner re-grants, because v0.8 changes what a live mandate means — who counts as an approver, what the owner and the approver sign. v0.7 artifacts stay verifiable as evidence (protocol.md → Version negotiation, rule 3).

One sentence summarises the release: the ticket now names the request it answers, admits one execution, and never required the Authority Server to read what the action contained.

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.

None has an external integrator yet (maturity marker: absent, ecosystem-wide). Dates are the day the ledger entry was written and the day the reference implementation exercised the direction end to end; the code is not named here (Implementation status left the specification, v0.7 changelog).

Direction (v0.7 ledger title)Landed asBasis
Scope values must not travel in the execution contextprotocol.md → Ticket Request Schema, rules 7–8; Validation Steps, step 11; Privacy Invariantrule 1 + 2 — ledger 2026-09-04; implemented before the v0.7 wire switch (allow-list of boundType.of fields on every AS call); reviewed in the 2026-10 ledger round. Ledger gap pinned: the ledger’s “plus any field a declared content binding requires” contradicted Content Binding (“the AS receives only the hash, never the content”) and is corrected: a binding contributes the hash and the declaration, never a value
One execution per ticketprotocol.md → One execution per ticket (Gatekeeper conformance); Enforcement classesrule 1 + 2 — ledger 2026-09-04 after a double execution in a gated release; durable execution journal, replay-as-recovery, per-proposal serialization built and tested end to end in September 2026. Ledger gap pinned: the escape hatch “unless the profile or manifest declares the action idempotent under its ticket id” is dropped — no such declaration exists and an undeclared declaration is the hazard the manifest rules forbid
A ticket names the request it answersprotocol.md → Ticket Payload Schema, idempotencyKey + rule 4; Pre-flight Ticket Request; code TICKET_REQUEST_MISMATCHrule 1 + 2 — ledger 2026-09-04; the AS copies the key and the Gatekeeper refuses a mismatch and a stale timestamp since the v0.7 wire switch (2026-10-09); tested end to end
Per-transaction bounds must be able to require their valueprotocol.md → Bounds Schema, rule 8rule 1 + 2 — ledger 2026-09-30; both enforcement points refuse an absent value since early October 2026; adopted by five published profiles. Ledger gap pinned: the ledger did not say where the key goes; every profile placed it inside boundType, beside of — the specification pins that placement and refuses it elsewhere
A profile can restrict the commitment modes of its mandatesprotocol.md → Commitment Modes, rule 8; code COMMITMENT_MODE_NOT_ALLOWEDrule 1 + 2 — ledger 2026-10-05; the AS refuses an unlisted mode and two published profiles declare ["review"]. Two ledger gaps pinned: no error code was named — one is; and review_above_cap “counts as its own mode” left a contradiction with the group mechanism that issues an automatic request as review_above_cap — resolved by the rule that automatic admits it and review alone does not
The owner’s disclosure choice can be standingprotocol.md → Identity Assurance → Standing disclosure choice; governance.md → Invariant 2 wordingrule 1 + 2 — ledger 2026-10-06 after 27 of 29 tickets in a run lost the owner’s name; AS-side setting live since 2026-10-06
A profile can declare a maximum for a boundprotocol.md → Bounds Schema, rule 9rule 1 + 2 — ledger 2026-10-06; AS refusal and the first declaring profile (reporting@0.2, read_max_age_days.maximum: 366) shipped the same week
Group roles: who may grant, who approves above the capprotocol.md → Group roles, approval count, and hard caps; AS Group Configuration; above_cap_approvals in mandate and projection; codes GROUP_CAP_EXCEEDED, APPROVAL_IMPOSSIBLE, PROPOSAL_APPROVERS_INVALIDrule 1 + 2 — ledger 2026-10-14 from a team of ten; grantor gate, approval count, proposer exclusion, hard caps, and the automatic → review_above_cap promotion built mid-October 2026. The specification keeps the ledger’s rule 6 (the count is signed) and adds the rule the review found missing: the decider set comes from the signed mandate, never from the caller
A group can cap reads and capability switchesprotocol.md → Read Authorization → Group ceilings; Bounds Schema, rules 10–11 (enum order, enforcedBy)rule 1 + 2 — ledger 2026-10-14; hard caps on Gatekeeper-enforced and enum bounds, and the Gatekeeper reading up to the signed value in a team, built mid-October 2026. Ledger gap pinned: the ledger’s “sign the group cap into the mandate as a ceiling” would have been a second signed field; the implementation needed none, because the AS refuses to sign above the cap and the signed value is the ceiling. The simpler shape is specified; it also gives enforcedBy: "gatekeeper" — specified since v0.6, though no published profile yet carries it (a new reporting version is the first that should) — its first definition. The enum order is stated as every published profile already has it (most permissive first); the ledger’s gates declaration is not promoted (below)
The Authority Server never sees what an action containsprotocol.md → Proposals — what the Authority Server receives; Privacy Invariant; proposalContentHash in the ticket; codes PROPOSAL_NOT_ENCRYPTED, PROPOSAL_CONTENT_MISMATCH; governance.md → Invariant 4, companion noterule 1 + 2 — ledger 2026-10-12 from a demo run; content encrypted to its readers, fingerprint compared at ticket time, decisions carrying the fingerprint, “encrypted for another device” — built in the second week of October 2026. The fingerprint’s copy into the signed ticket (ledger rule 3 said “bind by fingerprint”) is specified as the rule requires, although the implementation compared without signing; see Found while writing
System data on an approval surfaceprotocol.md → Content Binding → What may be shown next to what is boundrule 1 + 2 — ledger 2026-10-13; three-origin display (sealed / from the system / the AI’s claim), read time, re-read before execution, built the same week
What was shown can be bound to the decision; the request an action answersprotocol.md → Content Binding → The request an action answers, and binding what was shown; Tool-Gating Manifests, rule 5 extendedrule 1 + 2 — ledger 2026-10-14; the agent’s reference labelled as the agent’s, gateway-read history on declared keys, shown_hash per decider, built 2026-10-14 → 10-20. The rule that a reserved key enters neither the content binding nor the Executor’s call is the review’s addition
”Gateway” appears in normative text without a definitioneditorial — ten occurrences replaced by the defined term each stood foreditorial, not a promotion — no requirement changes; ledger 2026-09-10; landed with the vocabulary item below, as the ledger asked
Owner Interface, approval surface, and the local custody domainprotocol.md → Roles (three rows replace Local App); Approval surfacesrule 3 — specification-led: a remote approval surface is optional, and an implementation without one is unaffected; tracked in review.md until one exists. Undated in the ledger; written now because the proposal-content work gave it its first case (a remote surface that must verify before it shows) and because the “gateway” fix needed to know what Local App becomes. Device enrolment stays open
Dual-signed public ticket projectionprotocol.md → Public ticket projection — HAP-ticket-projection; vector ticket-projectionrule 3, specification-led. Targeted v0.7, slipped to v0.8 for a dependency v0.7 then supplied (disclose_fields, issuer). Promoted under rule 3, without an implementation, on the record: it is additive and optional, a vector pins its bytes, and the public ticket page’s “valid” indicator has been a stated v0.7 violation for seven weeks that nothing else closes. Tracked in review.md until an implementation serves it
Verifier policy for external relying partiesprotocol.md → Owner Signatures → Verifier policy — HAP-verifier-policy; code POLICY_UNMETrule 3, specification-led, landing with the projection as the ledger asked. A declarative document with one failure code per line; never sent to the AS

Found by the implementation and not in the ledger — recorded and, where it changes a rule, promoted:

Pinned where the ledger left a detail open

Each of these is a place where the ledger stated a direction, the implementation had to choose a detail to build it, and v0.8 pins the implemented choice because it was reviewed and because a second implementation would otherwise choose differently:

  1. requiredFor lives inside boundType on a bound (beside of) and inside constraint on a scope field — in each case next to the thing it qualifies.
  2. A profile that lists automatic admits review_above_cap; one that lists only review does not. The condition is the actions below the caps.
  3. The group read ceiling is the signed bound value itself; no second field. The AS refuses to sign above the cap and refuses the field’s omission where a cap exists.
  4. A content binding contributes the hash and the declaration to the ticket request, never a field’s value (the ledger sentence that said otherwise is corrected).
  5. The one-execution rule has no idempotent-action escape hatch.
  6. Error codes for every promoted refusal: COMMITMENT_MODE_NOT_ALLOWED, GROUP_CAP_EXCEEDED, APPROVAL_IMPOSSIBLE, PROPOSAL_CONTENT_MISMATCH, PROPOSAL_NOT_ENCRYPTED, PROPOSAL_APPROVERS_INVALID, TICKET_REQUEST_MISMATCH, POLICY_UNMET. The reference implementation emitted several of these conditions under non-canonical names; those are implementation work for its report, not specification text (the v0.7 advisor round made the same ruling).
  7. The read cap applies in every mode. The ledger capped reads at the signed value in group mode only, which needs a signal the Gatekeeper cannot read from the mandate. v0.8 caps at the signed value of an enforcedBy: "gatekeeper" bound in every mode; in personal mode the owner raises it by re-signing (Read Authorization → Group ceilings, rules 2–3).
  8. review in a group carries the above-cap rule. The ledger promoted only automatic requests, so a grantor choosing review approved above the group’s cap alone. v0.8 signs the group’s above-cap fields on a review mandate too (Group roles, rule 7).
  9. Required owners are a third role. Splitting grantors from approvers left the multi-owner coverage rule pointing at a list that no longer existed. v0.8 names required owners — default none beyond the granting owner (Group roles, rule 8).
  10. The proposer never counts, in a group or not (decided 2026-10-29). v0.7 let an owner outside a group name themselves as their own sole above-cap approver, so that above the caps the agent had to ask them. v0.8 keeps one rule for every mandate: such a mandate is refused at signing (ABOVE_CAP_CONFIG_INVALID). An owner who wants to approve their agent’s actions themselves uses review; review_above_cap outside a group needs an approver other than the owner. This is a change in behaviour from v0.7 for that one configuration.

Decided in the first external round (2026-10-29)

  1. The ticket checks run in stages, in a stated order, and a replay is answered first: the same key and the same request — or the same executed proposal and the same request — get the original ticket back, served only to the request context that obtained it. One state table covers every proposal state in every mode; a proposal committed with the above-cap approvals lifts the cap for that action and nothing else.
  2. Values a bound reads are finite and non-negative, for every action type. An action that gives value back is its own action type with its own bound.
  3. The owner signs binding, signing_surface and subjects_disclosed. The binding label stays the owner’s device’s claim, not custody proof; that is said where the label is defined, and custody evidence is an open direction.
  4. Coverage requires identical approval conditions — the same caps, approvers and count — within one group.
  5. An unreachable approval count: outside a group the owner chose it, and the request is refused; in a group the group chose it, and the caps are hard for that owner’s mandate. A grantor who is also one of too few approvers still gets a mandate below the caps.
  6. The review path needs a v0.8 Gatekeeper; a v0.8 AS never accepts clear content to bridge the transition. Gatekeepers upgrade first. (Superseded in scope on 2026-10-30: every mandate is re-granted under v0.8 — Decided before publication, item 3.)
  7. valid_until governs acceptance as authorization, not evidence. A public page or an auditor reads a projection without it; an effector acting on one applies it.
  8. One execution per ticket holds per Gatekeeper, and the text says so; shared records across Gatekeepers and consumption by approval are open directions.
  9. Proposal encryption is pinned byte for byte to the construction the implementation uses — HPKE base mode, empty info and associated data, IV ‖ ciphertext ‖ tag — with a vector. Proposal content is one object, the argument object, and every HAP-approval signs its fingerprint as proposal_content_hash beside content_hash.
  10. The signing input is the JCS bytes, signed directly; RFC 8785 governs the canonicalization, and the vector set now exercises its edge cases.
  11. The v0.6 vocabulary is a structural test, not a publication date: a profile that carries contextSchema or decision_owner is read under the v0.7 names; the published ones are listed (review.md → D4).
  12. Migrating a v0.7 group sets all three roles to the old list with a count of the list minus the proposer; a required owner must be a grantor. The v0.8 default — no required owner beyond the grantor — is weaker than v0.7’s, and is stated as such.

Decided before publication (2026-10-30)

  1. What a decision does. A reject from any signed decider is final; each commit counts once; “send back” is a reject with a note, and a revised action is a new proposal; the proposer may withdraw an undecided proposal; the AS refuses and does not count decisions from non-deciders, departed deciders, repeat deciders, or on decided proposals (DECIDER_INVALID, PROPOSAL_ALREADY_DECIDED, PROPOSAL_WITHDRAWN).
  2. A missing amount fails safe. Under an approval cap an absent value counts as above the cap and needs the approval; under a cumulative_sum bound that governs the action type it is refused, unless the profile excludes the action type through appliesTo. Consequence: a profile whose sum bound has no appliesTo — charge@0.5 among them — refuses its amount-less tools in automatic mode until a new profile version excludes them.
  3. Every mandate is re-granted under v0.8. A v0.8 Authority Server issues no tickets against v0.7 mandates (VERSION_UNSUPPORTED) and keeps verifying v0.7 artifacts as evidence; Gatekeepers upgrade, owners re-grant. This replaces the transition by TTL that v0.7 used, because v0.8 changes what a live mandate means.

Deviation rules

Found while writing

Writing v0.8 against the running implementation found eight places where the implementation and the ledger disagreed. Each is listed in the implementation’s own report in the specification’s terms; none changed the specification, and they are recorded here only as the fact that the review happened and what kind of thing it found. Three of them touched rules with a security consequence — what travels to the Authority Server, where the decider set comes from, and what is signed — and the text of those rules is stated more plainly in v0.8 because of it.

The lesson is the one the v0.7 changelog already recorded for appliesTo, now with a sharper edge: a ledger entry written the day a thing is built is an invitation to build the entry, and the implementation will meet it in its own words. Where the implementation’s words were better — requiredFor inside boundType, no second ceiling field — the specification adopted them. Where they were not, it did not.

Not promoted, with reasons

Retired in v0.8

The role name Local App — replaced by Owner Interface, approval surface, and local custody domain. The word gateway in normative text — ten occurrences replaced. The ledger’s idempotent-action escape hatch on the one-execution rule — dropped, never defined. The v0.7 sentence that a public projection is “a tracked direction, not a v0.7 guarantee” — replaced by the projection.

Considered and rejected

Review record

The recorded review behind this version, per rule 2:

  1. Ledger round (the v0.7 review.md, 2026-09-04 → 2026-10-14, thirteen commits): each promoted direction was carried there with a stated finding, a direction, a conformance test, and a target, and each was built against that entry before promotion. The entries are reproduced in substance in protocol.md and summarised in the table above; the two 2026-10-14 entries (group roles; group caps) were reviewed together and merged on 2026-10-15.
  2. Implementation review (2026-10-27 → 10-28): a read of the reference implementation — Gatekeeper, core library, Authority Server, and every published profile — against v0.7 and the ledger, by a reviewer who had not written either; findings as described under Found while writing; the proposed scope, the pinned details, the deviation rules, and ten questions put to the author. The author’s decisions on all ten are reflected above (one core section; sign the fingerprint; mandateId on the request; the three terms; gates, attachments, and the curve stay open; D3; promote the projection and the policy specification-led).
  3. Author’s adoption (2026-10-28): accepted as proposed.
  4. Draft review (2026-10-28): an independent read of the whole v0.8 draft — text, ledger and vectors — for contradictions, gaps and security consequences of the new text. Three blockers and twenty-three further findings. Resolved in the text: required owners named as a third role; review in a group carries the above-cap rule; the decider set is exactly the signed approvers, never narrowed; HAP-approval names its approver (approver_did) and a ticket carries above-cap approvals as approvalSignatures; a ticket verifier rejects any object carrying typ; one review-path flow; an idempotency key on every request without a proposal; replays exempt from the freshness check; APPROVAL_IMPOSSIBLE defined against approvers who can no longer decide; D2 closed for v0.8 mandates only; scope values removed from every example and vector.
  5. Independent re-derivation of the vectors (2026-10-29): a second implementation, in another language and sharing no code, computed every value from the specification text and the vector inputs with the answers removed. Every value agreed. It also showed that four vector inputs contradicted the text — a synchronous ticket under a review mandate, incomplete limits, a content binding the profile does not declare, the Authority Server’s own key as an above-cap approver — and that several rules were stated only once, or not at all. Fixed in the text: one order of checks at ticket time; the approval count against approvers other than the owner, everywhere; OWNER_NOT_COVERED and COVERAGE_INSUFFICIENT told apart; the short profile identifier in signed and hashed fields; profiles in pre-v0.7 vocabulary read under their v0.7 names; value types and list-valued scope fields in the bounds and scope canonicalization; RFC 8785 governing the signing canonicalization; a malformed commitment_modes as PROFILE_INVALID; every refusal given its code in the checklist and in the error table. No canonicalization result in the vector set changed.
  6. External reviews, first round (2026-10-29): two independent reviews of the draft as it then stood — one adversarial, one from an implementer’s view — each reproducing every vector with its own code, plus a third independent re-derivation over the final text. Twelve blockers between them, merged: the review path had no complete state table and no exit for an approved above-cap proposal; a negative value could return consumed authority; the owner did not sign binding, so min_binding rested on the operator’s word; coverage ignored the owners’ approval conditions; three rules disagreed on an unreachable approval count; the review path was not, as claimed, compatible with v0.7 Gatekeepers; the public projection’s validity window defeated its purpose as evidence; “one execution per ticket” was claimed beyond one Gatekeeper; the encryption of proposal content was not pinned to the byte; “content” had two definitions; the signing input was misstated; execution_context_hash was unchecked. Resolved in the text, with the decisions below (Decided in the first external round), and the should-fix items with them.
  7. Re-derivation over the final text (2026-10-29): a fourth independent implementation — its own RFC 8785 serializer and its own HPKE from RFC 9180 — computed every value of the revised set from the text, the proposal-encryption envelope included; every value agreed. It found three places where the refusal code was not unique (a present non-number under requiredFor, the fields compared on an executed proposal’s replay, mandateId with an unknown boundsHash) and a set of smaller gaps; resolved in the text without changing a vector value.
  8. External reviews, second round (2026-10-29): two fresh reviews — adversarial and implementer — each reproducing every byte-level vector with its own code, the encryption envelope included. Blockers: two refusal rows contradicted the text on a non-numeric value; on the review path the AS did not compare the ticket’s content hash with the approved proposal’s; action_type on the wire contradicted itself; an approval cap could be bypassed by leaving the value out; the decision step had no defined outcomes. The first three and most should-fix items are resolved in the text without changing a byte-level vector; the last two await the author’s decision, and the remaining items are on the ledger with targets (review.md).
  9. External reviews, second round — outside models (2026-10-29): the same brief, given to reviewers of another model family over the package as it stood before item 8’s fixes; each reproduced every byte-level vector. Both found, independently, that an owner-signed approval bound the arguments but not the operation — an AS could show “archive” and issue “delete” for the same record id. HAP-approval now signs action and action_type, and a Gatekeeper verifies the approval signatures a ticket carries. Also resolved: a verifier accepting a ticket as authorization checks its contentBinding against the profile the mandate pins; the absent-appliesTo default stated once; version-2 bindings are jcs only; tickets use the key current at issuance; the privacy invariant is stated as a custody boundary; cumulative bounds are checked over the union of the buckets they govern; an atomic execution claim before every call; the effector’s idempotent exception removed; overclaims in the security summaries narrowed. The approval and review-ticket signatures changed.
  10. Final re-derivation (2026-10-30): a fifth independent implementation re-derived the complete set over the text with the decisions of 2026-10-30 in it; all 868 values agreed. It found five places where a refusal code was not unique — the fields a replay compares, the content binding on the review path, approvals for an absent capped value, the operation a surface shows — resolved in the text without changing a byte-level value.

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