Give AI autonomy without giving it authority

Version 0.7 September 2026

Human Agency Protocol — Review and Future Directions

This document is the forward ledger: what is open, what it targets, and which deviation rules are on record. What was promoted into v0.7 and why — the vocabulary release, its renames, retirements, and the proposals considered and rejected — is changelog.md. A v0.7 implementation MAY implement any direction below without losing conformance, and MAY skip all of them without losing conformance.

Each open direction carries an explicit target; “undated” means no version commitment exists yet.

Implementation status is not part of this specification. Whether a given implementation meets a requirement is that implementation’s claim, made in its own report where a reader can weigh it (governance.mdReference ConformanceImplementation reports). The reference implementation’s report is published with the conformance suite (hap-e2e/CONFORMANCE.md). Until 2026-09-03 this file carried such status; changelog.mdImplementation status left the specification records why it no longer does.


Owner Signatures — suggested implementation order (specification-led since v0.6)

The normative design is in protocol.mdOwner Signatures; it was promoted specification-led in v0.6 (rule 3). An implementation approaching it will find this order natural, because each phase produces something an outsider can verify and the last is configuration rather than design:

Size
P1Types: the mandate_owners entry, the HAP-mandate-projection and HAP-approval objects, the approvalSignature ticket field, the error codes, deprecation of Subject.owner_signature; a did:key encoder/decodersmall
P2Key-bearing signing DIDs enrolled through a platform authenticator; signing in a locally installed surface (signing_surface: gatekeeper_local); projection verification; owner signatures in any public ticket projection. Decide the curve first — see belowlarge — the real work
P3Approval signatures end-to-end; approvalSignature enforced at ticket issuancemedium
P4Verifier policy surface: minimum binding, DID pinning, out-of-band acquisitionsmall–medium
P5binding: "eudi" — and with it, key rotationconfiguration, not redesign

P5 being configuration rather than redesign is the point of the ordering: build the slot against key material that exists today (passkeys), and a national scheme plugs into it.

The curve is an open decision, and P2 cannot start without it. did:key is defined for several key types; this specification’s vectors and its alg: "EdDSA" examples use Ed25519, and the reference did:key encoder accepts only the Ed25519 multicodec. Platform authenticators on the major desktop and mobile systems commonly sign with ES256 (P-256) rather than Ed25519, so “enrol a passkey as the signing DID” is not automatically implementable. Two ways forward, and the choice belongs to whoever starts P2:

Neither is wrong; leaving it undecided is. binding: "eudi" (P5) raises the same question again, because a national scheme brings its own algorithms — so whichever way P2 goes, the verifier must already be able to say which curve it accepts and refuse the rest.

The enrolment ceremony is not specified. Identity & Authorization rule 6 fixes what must be true — the owner DID in a mandate matches the authenticated identity — and Owner Signatures fixes what a signing DID must be: key-bearing. What sits between them is unwritten: how an account comes to have a signing DID in the first place, what proves possession of the private key at enrolment (a challenge signed with it is the obvious answer), what happens when an account enrols a second key, and how the event is recorded. Key lifecycle covers only the forward path after loss. This is a small specification gap with a large implementation surface, and it is the first thing P2 runs into.


Deviation rules

D1 — Profile in-place mutations (grandfathered, closed)

The immutability rule was violated four times before v0.6 closed the loophole: content_binding was added in place to the published records@0.4 / customers@0.4 (June 2026) and then to email@0.4 / publish@0.4 (July 2026). No version bump, no re-issuance. Tolerable in practice — the authority contract the human signed was untouched, tickets are self-contained, and the fields were OPTIONAL — but wrong: an annotation that changes what future tickets publicly expose is a behavior change, and for email it changed the privacy posture of already-signed mandates without their being re-signed. v0.6 closed the loophole normatively (no annotation exemption). These four mutations are grandfathered; implementations MUST treat them as the last of their kind.

D2 — The field-name fallback for cumulative bounds (closes on a condition)

protocol.mdMigration from v0.6, semantic change 1, permits an enforcement point to keep a field-name fallback for mandates on profile versions that predate appliesTo — and requires its removal once no live, unexpired mandate references such a version. The condition is stated there; whether a given implementation has met it is a matter for its report.


Open directions

Retire the field-name fallback — targets the first release after the D2 condition holds

Step 1 (specify appliesTo) landed in v0.7. Step 2 is deletion of the fallback in every enforcement point, gated on the D2 condition. Procedural lesson carried forward from v0.6: a fix that changes what a profile may declare is a protocol change wearing a bug fix’s clothes, and it belongs in the ledger the day it ships.

A wire binding — undated

The specification defines artifacts, behaviours, and refusals, and deliberately no endpoints, request authentication, response envelope beyond the {approved, errors} shape, proposal object, or approval transport. Two implementations built from the text alone verify each other’s artifacts and cannot talk to each other. Not a conformance gap — a stated limit of interoperability. Direction: a non-normative companion hap-http-binding@0.1 (paths, methods, auth header, envelope, proposal and approval transport, revoke, verify, .well-known key discovery), plus JSON Schemas for the six artifact types, the profile, and the manifest, published beside the vectors.

Sub-mandates and hierarchies of accountability — undated

The protocol’s building blocks — groups, required approvers per profile, review_above_cap with named approvers, multi-owner mandates — already support hierarchies of accountability in place of hierarchies of supervision. What flows down is authority, not instructions; what flows up is exceptions and tickets, not reports. Span widens (a mandate holder can hold dozens of sub-mandates if most activity runs automatically), depth collapses (a layer exists only where a distinct scope of accountability exists), and failure handling becomes surgical (revoke one branch). The organizing rule: each mandate may only grant less than it holds.

What is not yet a protocol object: a sub-mandate cannot cryptographically prove that it nests inside its parent — that its bounds are a subset, its scope narrower, its expiry no later, its commitment mode no looser. Today that relation is organizational configuration on the AS, an honest-operator control. Making it compromise-resistant evidence means a signed reference from child to parent plus a verifiable subset relation over bounds and scope — the second of which needs the per-field commitments below to be checkable without disclosing the parent. Between independent operators the same mechanism forms a mesh — temporary trees per undertaking on a permanent network of sovereign nodes, with a locally operated Authority Server as civic infrastructure. Cross-server federation remains deferred (governance.mdMulti-AS Ecosystem); a discovery layer over ticket histories is out of scope and carries the privacy cost the specification already names under Read Authorization.

Scope values must not travel in the execution context — targets the next version

Found 2026-09-04 while auditing a live Authority Server’s stored data. A ticket request carries executionContext, the per-call values, in plaintext — it has to, because the AS enforces per-transaction and cumulative bounds against those values. Scope, by contrast, reaches the AS only as scope_hash, and the Gatekeeper is its sole enforcer (Enforcement Authority).

Nothing currently keeps the two apart on the way out. A tool-gating manifest maps one connector argument into several execution-context keys, and where a scope dimension is among them — a message’s recipients mapped into the same allowed_recipients key the scope schema declares — those values are sent to the AS and stored in the signed ticket, in the clear. The AS cannot act on them: no boundType names them, and it holds no plaintext allowlist to compare against. So the disclosure buys nothing and costs the privacy the bounds/scope split exists to protect.

The rule to add: a Gatekeeper MUST send only the execution-context fields some boundType.of names, plus actionType and any field a declared content binding requires. Values that exist to satisfy a scope constraint are checked locally and MUST NOT be transmitted. Equivalently: a field’s presence on the wire should follow from a bound that reads it, never from a manifest mapping that happens to produce it.

Why hashing them instead is not the answer. Recipients, domains and environment names are short and enumerable; an unsalted hash of an email address falls to a dictionary. Salting defeats comparison, which was the only reason to send anything. This is the same argument Selective disclosure / per-field commitments already makes, and it applies with more force here, because the values are the counterparties.

If the AS should verify scope, that is a different mechanism, deliberately not proposed here. It would need commitments the AS can compare without learning values — a keyed construction whose key the Gatekeeper holds and the AS does not, with the allowed set committed in the mandate and the per-call value committed in the request. The residual cost is linkability: a stable commitment lets the operator count how often the same unknown counterparty recurs. That may be an acceptable trade for a deployment that wants a second enforcement point; it is not the current design, and adopting it would move scope enforcement from compromise-resistant to shared with an honest-operator control. Recorded so the trade is chosen rather than drifted into.

One execution per ticket — targets the next version

Found 2026-09-04 during a gated release. Cumulative Tracking rule 5 guarantees one ticket per logical execution: a retried ticket request returns the original ticket, and bounded authority is consumed once. That held perfectly — and the action still ran twice. Two triggers inside one Gatekeeper requested a ticket for the same committed proposal; the Authority Server correctly replayed the original to the second; and nothing on the Gatekeeper side knew the tool had already been called. The effect was stopped by the downstream system’s own conflict check, not by the protocol.

The specification is half-stated. The Authority Server’s half is normative. The Gatekeeper’s half — that a ticket authorizes at most one execution — is nowhere, and only the Gatekeeper can supply it, because only it knows whether it went on to act. Direction, as Gatekeeper conformance:

  1. A Gatekeeper MUST keep a durable record of the tickets under which it has executed, written before the downstream call, so an interruption leaves “started, outcome unknown” rather than nothing.
  2. A replayed ticket (idempotent: true, or the review path’s original returned after executed) is a recovery signal for a lost response, not an authorization to act again. With a record of completion the Gatekeeper MUST NOT execute; with a record of intent but no completion it MUST surface the ambiguity to the Mandate Owner rather than resolve it by guessing — unless the profile or manifest declares the action idempotent under its ticket id.
  3. Where several triggers can execute committed proposals, they MUST be serialized per proposal.

This is the missing counterpart of rule 5 and is testable in a conformance suite: concurrent triggers against one committed proposal MUST produce one downstream effect. It does not change the Authority Server, the ticket, or the wire.

Read classification for remote, vendor-controlled connectors — undated

A connector reached through a remote MCP server the operator does not control has a tool list that cannot be enumerated when its manifest is written and can change without notice. Under Tool-Gating Manifests rule 1 such tools are refused until named. Where an operator chooses to name a catch-all action type for them (charge@0.5 registers unclassified for this reason), genuine reads are counted as consequential, consume a write budget, and produce tickets — over-restrictive rather than unsafe, but it means a ticket exists for something no one executed. What the specification could add is a name for the situation: a declared unclassified action class, so a ticket carrying it reads as “this connector could not tell what this was, and treated it as consequential” rather than as a positive claim about the action.

Dual-signed public ticket projection — targets v0.8 (slipped from v0.7; reason in changelog.md)

Security dependency, not only a transparency improvement. Invariant 10 permits a ticket-demanding effector to satisfy Complete Mediation, and such an effector must establish the ticket’s authenticity itself. A public projection that withholds the signature cannot give it that: the effector asks the Authority Server whether the signature is valid and its mediation therefore depends on the AS being both honest and reachable — an honest-operator control where the invariant demands compromise-resistant evidence (protocol.mdEnforcement classes). The dual-signed projection is what closes that.

A public ticket view deliberately redacts private fields and the signature; because one Ed25519 signature covers the whole ticket, the public page cannot be independently re-verified — its “signature valid” is the AS re-verifying itself. Full zero-trust verification is available only to the holder of the complete signed ticket, which for a private action is the party that matters. For a public artifact “everyone is the holder,” and the gap is real.

The fix: the AS separately signs a public-only projection {id, mandateId, timestamp, profileId, actionType, boundsHash, contentHash, contentBinding, identity, issuer} and exposes it with its signature — carrying mandateId and boundsHash (the mandate reference) and, where present, the owner’s signature entry from mandate_owners, because without those the ticket → mandate → owner-signature chain cannot even start from the public view — extended by two requirements that surface when the ticket becomes a bearer proof presented to a third party:

  1. Replay. Idempotency is retry-dedup, not replay defence. A ticket presented twice to an external verifier is a different problem; content binding contains most of it, but an old ticket could force old, vulnerable code back into production. Minimum: a validity window in the projection. Stronger: a spent-ticket record or an issue-time nonce.
  2. Scope fields must be verifiable, not merely disclosed. A machine verifier must check the ticket authorizes this repository, environment, and pipeline. Those live in executionContext, which the projection redacts — so the projection must carry the execution-context fields the profile or mandate declares disclosable (disclose_fields supplies the mechanism).

Related, smaller pieces of the same machine-verification surface: content negotiation on a public ticket URL (JSON for agents), a .well-known discovery document, a structured ticket marker in email headers, and a verifier helper plus ticket types in a core library.

Verifier policy for external relying parties — targets v0.8

v0.6 made verifier policy the primary enforcement tier for owner signatures (“the relying party demands a minimum binding… MUST fail, not warn”) but defined no interface for a relying party who is not running a gateway. An auditor, a counterparty’s system, or a pipeline consuming a bearer ticket has a normative verification procedure but no defined way to express its policy (minimum binding, required owners, pinned DIDs) or a standard failure semantics. Same gap class as the projection above, and the two should land together: a small, declarative verifier-policy document that any conformant verifier evaluates identically.

Deploy binds the artifact digest; the source commit is the reader’s pointer — undated

Found 2026-09-04. A deploy ticket binds the source commit as text, so a released page can name its own approval in a footer a reader can check. Two things then tie that commit to the bytes that go live: the page’s footer, and the deployment host’s report of which commit produced the build. Both are self-asserted strings — a footer says what the build put there; an artifact commit’s message says what its author typed; neither is signed. And reading the footer requires the staged build to be publicly fetchable, which is the wrong dependency for a verification step.

Direction: bind what goes live, not where it goes live, and not what a page says about itself. Where the served artifact is committed, its tree hash is already a content digest of the served bytes; where the host builds, an image or bundle digest is. A future deploy profile binds the digest alongside the source commit (the declared-field binding already on this ledger), the effector verifies the digest of what it is about to activate — independently, per Deployment Security Profile — and the footer becomes a cross-check that may only refuse. The artifact → source link should become a build attestation rather than a commit message. Repository, environment, and pipeline remain covered by the dual-signed projection item above, not by this one.

Subject export of evidence at the AS — undated (custody half promoted)

The Gatekeeper half — custody of the complete signed ticket, mandate, and issuer key — is normative since v0.7. The remaining half: the authenticated subject can export, at any time, the complete signed tickets issued under their mandates and the mandates behind them, in a self-contained format that verifies offline (issuer key included). This converts Retention at the Authority Server’s operator-facing export duty into a right the evidence’s subject can exercise — while an account still exists; it does not reach the already-terminated case, which is why custody is the primary mechanism and export is the recovery path. Deliberately out of scope: custody on an employer-owned device (a returned laptop surrenders the archive with it — a deployment-profile concern, candidate for user-designated escrow), and any subject access after termination, which no protocol text can force on a hostile operator.

Selective disclosure / per-field commitments — undated (activates when a case requires it)

A single hash over a declared subset is all-or-nothing at disclosure time. Proving “the environment was production” without revealing the artifact requires a commitment per field plus a hash over those commitments — and that construction MUST NOT be adopted without a per-field random salt: bound values are frequently short and enumerable, and an unsalted per-field hash is recoverable by enumeration. The sub-mandate direction above is the second case that would need it (proving a subset relation without disclosing the parent).

Resilience to a Compromised Authority Server — remaining items

Owner signatures (v0.6) removed authority fabrication from the compromised-AS surface; the v0.7 enforcement-class table records exactly what remains honest-operator. Remaining:

Output Provenance — profile-bound, undated

Binding a mandate to an observable output location (output_ref in the scope schema, hashed and signed) remains specified and unpromoted: it lands in the deploy-style profile that adopts it, not in HAP Core. Maturity marker: no external integrator.

eudi wallet integration — P5, undated

The binding: "eudi" value and its validation semantics are normative; what remains is the wallet integration itself and its rotation story — tracked here because it is also the answer to key rotation, which no rotation chain or AS-mediated mechanism can provide.

The public layer — not a protocol item, recorded once

The vocabulary is now the specification’s. Websites, product copy, and AI-facing context documents follow the specification, not the other way round; the change is a copy proposal outside this ledger and is mentioned here only so the sequence is on record: spec first, wire second, copy third.