← Machine Authority Protocol
Machine Authority Protocol v1.0 Elicitation Loop v1.0 Status: STABLE Published: 2026-05-03

Abstract

The Elicitation Loop is the wire protocol that activates when the Action Authorization Boundary returns a DEFER Decision Envelope. It is a four-message exchange — DeferredActionRequest → ApprovalDecision → (action executes) → ExecutionReceipt — with cryptographic continuity end to end:

  1. The DeferredActionRequest binds the deferred CAR and the AAB's signed envelope to the dispatcher's key via DPoP (RFC 9449).
  2. The ApprovalDecision carries either APPROVE (with an embedded Cryptographic Attestation of Consent) or REJECT (with a reason).
  3. The ExecutionReceipt closes the loop with the dispatcher's outcome and a hash reference back to the CAC.

The Elicitation Loop is the wire-format binding of AARM v1 §R3 (DEFER as a transport-level decision state) and §R5 (signed receipt of the human decision). The normative artifact for validation is schema/elicitation-loop-v1.json.

1. Introduction

MCP 2025-11-25 Elicitation provides a transport primitive that lets a server pause a model loop and ask the user for input, but the protocol terminates inside the model session: the user's response is plain text, unsigned, and unbound to a specific tool call. Google A2A's auth-required TaskState signals that re-authentication is needed but delegates the workflow to implementation-specific hosts. CIBA is the closest existing pattern, but CIBA authenticates a person — not an action — and produces an OAuth access token rather than a verifiable receipt of consent.

The Elicitation Loop occupies the gap. It is transport-flexible (push or poll) but format-rigid (DAR/AD/ER schemas, DPoP-bound resume, JWS-signed ApprovalDecision, embedded CAC). It composes with MCP and A2A: an MCP server MAY surface the DAR through Elicitation; an A2A worker MAY surface it through auth-required. In both cases the resulting ApprovalDecision travels back through the Elicitation Loop, not the host transport.

2. Conventions

The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, NOT RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119.

Canonicalization rules are inherited from CAR §6.

3. Sequence

Dispatcher                       Approver Endpoint
   |                                    |
   |-- (1) DeferredActionRequest -----> |
   |       (POST + DPoP proof)          |
   |                                    |
   |       [Human reviews CAR + envelope, then approves or rejects]
   |                                    |
   |<-- (2) ApprovalDecision ---------- |   push profile (default)
   |                                    |
   |                                    |   - or -
   |                                    |
   |-- GET resume --DPoP--------------->|   poll profile (OPTIONAL in v1.0)
   |<-- ApprovalDecision ---------------|
   |                                    |
   |   [Verify JWS + embedded CAC]      |
   |                                    |
   |   [Execute or permanently DENY]    |
   |                                    |
   |-- (3) ExecutionReceipt ----------> |

3.1 Push profile (REQUIRED in v1.0)

The dispatcher POSTs the DeferredActionRequest to defer_payload.approver_endpoint. The approver returns 202 Accepted and later POSTs the ApprovalDecision back to a dispatcher-supplied callback_url. The callback URL MUST be HTTPS. The approver MUST sign the ApprovalDecision JWS with the approver-identity key and MUST include the embedded CAC when decision = "APPROVE".

3.2 Poll profile (OPTIONAL in v1.0)

The dispatcher polls the approver with GET <approver_endpoint>/<request_id>, carrying a DPoP proof on every request. The approver responds 204 No Content while pending and 200 OK with the ApprovalDecision when available. v1.0 implementations MAY ship the poll profile, but v1.0 conformance only requires the push profile.

3.3 Why the resume is DPoP-bound

defer_payload.resume_token was a bearer token in early drafts. Bearer resume tokens are a known anti-pattern: an attacker who exfiltrates the token (network capture, log scraping, browser-side leakage) can resume the loop as the legitimate dispatcher and receive the human's approval bound to a request the human never saw. v1.0 binds the resume to the dispatcher key via RFC 9449 DPoP:

  • The AAB issued the DEFER envelope with defer_payload.dispatcher_jkt set to the dispatcher's RFC 7638 base64url JWK SHA-256 thumbprint.
  • Every dispatcher → approver request in the loop MUST carry a DPoP HTTP header containing a JWS signed by the dispatcher key whose thumbprint matches dispatcher_jkt.
  • The DPoP proof MUST cover htm (HTTP method), htu (HTTP target URI), iat (issuance time), and ath (RFC 9449 §4.3 base64url SHA-256 of the resume_token).
  • The approver MUST verify that:
    1. The DPoP jwk header thumbprint equals dispatcher_jkt.
    2. The htm and htu claims match the inbound request.
    3. The iat is within the approver's clock-skew tolerance (RECOMMENDED ±60 seconds).
    4. The ath claim equals the SHA-256 of resume_token.
  • Approvers MUST reject a Loop request that omits or fails the DPoP proof. A failed DPoP proof MUST cause the approver to invalidate the pending request and notify the AAB so the original DEFER becomes a permanent DENY.

Replay-protection windows: a previously-seen DPoP jti MUST NOT be honored a second time within defer_payload.expires_at.

4. Schemas

The schemas below are normative and live in schema/elicitation-loop-v1.json.

4.1 DeferredActionRequest (DAR)

DeferredActionRequest:
  loop_version: "1.0"
  request_id: UUIDv4                     // distinct from CAR action_id
  car: <CAR>                              // the deferred Canonical Action Representation
  defer_envelope: <Decision Envelope>     // MUST be the DEFER envelope from the AAB
  callback_url?: https URL                // required for push profile
  created_at: RFC 3339
  expires_at: RFC 3339                    // MUST equal defer_envelope.defer_payload.expires_at

The dispatcher MUST submit car and defer_envelope byte-for-byte as issued. The approver MUST recompute car_hash from car (using the canonicalizer in CAR §6) before presenting the action to the human.

4.2 ApprovalDecision

ApprovalDecision:
  loop_version: "1.0"
  request_id: UUIDv4                     // echoes DAR.request_id
  decision: "APPROVE" | "REJECT"
  approver: { identity: <Identity>, signed_at: RFC 3339 }
  reason?: string                         // required when decision = "REJECT"
  cac?: <CAC>                             // REQUIRED iff decision = "APPROVE"
  approver_signature: <JWS Compact>       // JWS-EdDSA over the canonical body minus this field
  dpop_proof_jkt: <base64url-43>          // MUST equal defer_envelope.defer_payload.dispatcher_jkt

The approver_signature JWS uses alg = EdDSA, typ = MAP-APPROVAL-DECISION-1, b64 = false (RFC 7797 detached payload), and a kid resolvable to the approver-identity public key per CAC §5 verification. Push and poll profiles produce identical signed bodies; only the HTTP framing differs.

dpop_proof_jkt is the RFC 7638 base64url SHA-256 JWK thumbprint of the dispatcher key that presented the resume_token. It MUST equal defer_envelope.defer_payload.dispatcher_jkt; the approver's signature covers this field so the approver attests to the binding.

The human approver loop produces only APPROVE or REJECT. The AAB-only decisions (MODIFY, STEP_UP, REVOKE) are out of scope for the human: those are returned by the AAB on the original CAR submission, not by the approver in the Loop.

4.3 ExecutionReceipt

ExecutionReceipt:
  loop_version: "1.0"
  request_id: UUIDv4
  action_id: UUIDv4                       // echoes CAR.action_id
  outcome: "EXECUTED" | "FAILED" | "ABORTED"
  cac_ref: { car_hash, approver_kid }     // back-reference, not the full CAC
  executed_at: RFC 3339
  result_digest?: hex                     // SHA-256 of result payload, optional
  error?: { code, detail? }               // required for FAILED

The ExecutionReceipt is informational; it is the dispatcher's attestation that it acted on (or aborted) the approval. The Loop is considered complete when either an ExecutionReceipt arrives or expires_at elapses. Approvers MAY ignore the ExecutionReceipt for auditing-only deployments, but conformant approvers MUST surface EXECUTED / FAILED / ABORTED to the human who approved the action.

5. Composition with the CAC

When ApprovalDecision.decision = "APPROVE", the approver MUST embed a fully-formed CAC in cac. The dispatcher persists this CAC alongside its execution log; downstream systems (audit warehouses, transparency logs, regulator exports) consume the CAC, not the ApprovalDecision. This is deliberate: the ApprovalDecision is a Loop-internal artifact; the CAC is the portable, vendor-neutral receipt.

The approver MUST NOT issue a CAC outside an ApprovalDecision. CACs that appear without a containing ApprovalDecision MUST be treated as provenance-less and rejected.

6. Limitations

The following are explicitly out of scope for v1.0 and tracked on the post-v1.0 roadmap. They do not block v1 conformance.

  1. Single approver only. v1.0 Loop produces exactly one ApprovalDecision per DAR. Multi-approver / quorum approval is roadmap.
  2. Push only at conformance level. The poll profile (§3.2) is defined but not required by v1.0 conformance. Push remains REQUIRED for v1.0 implementations.
  3. No approver chaining. An approver MUST NOT forward a DAR to another approver in the same Loop instance. Forwarding is a roadmap feature gated on the multi-approver work.
  4. Time bounds. expires_at MUST be the smaller of the AAB-issued DEFER expiry and the approver's configured maximum. Default approver maximum SHOULD be 15 minutes.
  5. Replay protection scope. Replay protection is per-request_id and per-DPoP jti. A dispatcher that reuses request_id across actions has already broken the spec; approvers MUST NOT attempt to “fix” this by deduplicating on action_id.
  6. No transparency log. v1.0 Loop does not specify or require a transparency log for ApprovalDecisions. Operators who require one SHOULD log the embedded CAC into a Sigstore-style log; the Loop itself is silent on the choice.

6.1 Stability commitment

Breaking changes to the v1 wire format require a 12-month deprecation window and ratification by the founding council before a v2 cutover. Conforming v1 implementations exchanging Loop messages with another v1 implementation MUST interoperate without negotiation.

7. Security considerations

  1. Endpoint substitution. The DAR's defer_envelope.defer_payload.approver_endpoint is signed by the AAB. The dispatcher MUST POST the DAR to that endpoint and refuse redirects to a different origin.
  2. Approver-key compromise. Detection is via the approver-key resolution chain (CAC §5.2) plus key rotation. v1.0 does not solve key revocation in real time; keys outside their validity window MUST fail verification.
  3. Human social engineering. The human approver sees the CAR; nothing in the Loop prevents a human from approving an action they should have rejected. The Loop's job is to make the human's decision non-repudiable, not to substitute for the human's judgement.
  4. Dispatcher-key compromise. A compromised dispatcher key lets the attacker complete the DPoP proof. Dispatchers SHOULD use hardware-backed keys where threat models demand it, and SHOULD rotate dispatcher keys on a schedule shorter than the maximum DEFER expiry they ever expect to issue.

8. References

Normative references