| Internet-Draft | Agent Decision Commitments | October 2026 |
| Milan | Expires 18 April 2027 | [Page] |
Software agents increasingly select and initiate payments on behalf of people and organizations. Existing agent payment protocols record what a principal authorized and that value moved, but not what information the agent relied on when it chose. This document defines the Agent Decision Commitment (ADC): a salted hash commitment to a decision record that identifies the content the agent provided to the model interface (after any agent-side transformation), the model calls made, and the resulting answer and action. The commitment is carried as a single selectively disclosable claim in an existing payment mandate or payment payload and is, by default, registered with a transparency service before the payment is authorized. The decision record and its content remain with the holder and are disclosed only to dispute participants, who can verify them against the mandate and the transparency service and can inspect the content the agent provided to the model. This document defines the data model, the commitment and anchoring procedures, a disclosure and verification procedure that reports attestation levels rather than verdicts, and bindings to three agent payment protocols and to account-to-account credit transfers. It also defines a witnessed reciprocal exchange by which two parties' agents evidence what passed between them: the receiver signs over the committed bytes, independent witnesses fix the time of each step, a failure to answer by a deadline is recorded, and arbiters resolve stalled exchanges.¶
This note is to be removed before publishing as an RFC.¶
This document is an individual submission. Discussion is invited on the DISPATCH mailing list (dispatch@ietf.org), and on the SCITT (scitt@ietf.org) and OAuth (oauth@ietf.org) working group lists, since this document uses the SCITT architecture [RFC9943] and SD-JWT [RFC9901].¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 18 April 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Payment agents increasingly choose rather than merely execute: which bill to pay, which merchant to buy from, whether a request is legitimate. Mandate formats such as Verifiable Intent [VI] and the Agent Payments Protocol [AP2] bind a principal's authorization to an agent key. Merchant-signed checkout objects record what was bought, and payment receipts and settlement record that value moved. None of these records what the agent relied on when it chose.¶
That question matters in two recurring situations. The first is the authorized but wrong purchase: a payment that satisfies the principal's signed constraints but results from a misread invoice or a fraudulent payee that fits the budget. The second is the manipulated agent: content the agent read, including text embedded in images, has been shown to redirect deployed agents [TOB] [BRAVE]. Media is a particular problem because model providers may resize images or sample video frames before the model sees them, and an attack can be constructed to appear only after such resampling [TOB]. A log of original files is therefore not a record of what the model received. This document records the bytes the agent sent to the model interface; transformations applied by a provider after receipt are visible only through provider attestation (Section 6.5).¶
This document defines a minimal mechanism. The holder of the agent's records computes a decision record that identifies, by salted digests, every item provided to the deciding model, together with the model calls, the answer and the action. The holder commits to the record with a salted hash, places the commitment in the payment mandate or payload, and, by default, registers it with a transparency service [RFC9943] before the payment is authorized. In a dispute, the holder discloses the record, the salts and the content to authorized verifiers, who check them against the mandate and the transparency service. Where the agent's decision depends on what a counterparty sent, or a counterparty is to acknowledge what the agent sent, the two parties use the witnessed reciprocal exchange (Section 8), so that what passed between them is evidenced as well.¶
This document specifies the decision record, the context receipt, the commitment, the holder chain and anchoring, outcome and erasure entries, disclosure and verification, bindings, and the witnessed reciprocal exchange between parties. It does not specify how an agent decides, how content is selected for a model, how dispute participants are credentialed, or what consequence follows from a verification result.¶
ADC produces evidence. It does not allocate liability among principals, holders, merchants, issuers, acquirers, networks, or model providers, and it does not define the consequence of a missing, withheld, or failed record. Those consequences are matters for the applicable scheme rules, contracts, and law. Verifiers report the attestation level and step results defined in Section 6.5, not a verdict.¶
ADC establishes the integrity and timing of evidence, not its truth. It does not establish that recorded inputs were authentic or accurate, that the model's answer was correct, that the action reflected the principal's intent beyond what the mandate states, which item caused an outcome, that the context receipt is complete, or that the holder recorded honestly at decision time. ADC imports no facts about the world and therefore relies on no external oracle; facts enter only as recorded content whose stated origin a verifier can assess. Every verification step is a finite computation over released data (hash recomputation, signature verification, and comparison) and terminates. ADC does not evaluate properties of the agent's program or behavior.¶
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 BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
Abbreviations: JCS, JSON Canonicalization Scheme [RFC8785]; NFC, Unicode Normalization Form C [UAX15]; SD-JWT, Selective Disclosure for JWTs [RFC9901]; CDDL, Concise Data Definition Language [RFC8610].¶
| Role | Responsibilities in this document |
|---|---|
| Holder | Records context items, computes receipts, records and commitments, maintains the holder chain, registers anchor and entry statements, stores content for the retention period, authenticates disclosure requests, releases evidence under the principal's authorization policy. |
| Agent | Places the decision context claim in the mandate or payload it signs or submits. |
| Principal | Sets the authorization policy that governs disclosure; may access its own records. |
| Transparency service | Registers signed statements and issues transparency receipts that bound when a commitment existed. |
| Model provider | Optionally attests to model calls (Section 6.5, level A3). |
| Verifier | Requests disclosure under a credential accepted by the holder's profile, runs the procedure in Section 6.4, and reports results. |
| Counterparty | In an exchange, signs held, key_received, received, mismatch, accepted, or refused over exactly the committed bytes (Section 8). |
| Witness | Countersigns exchange object hashes with its own time; sees hashes only. |
| Arbiter | Holds an escrowed share of an exchange key and delivers the key when an exchange stalls; never acknowledges or accepts on the recipient's behalf. |
| Merchant, issuer, acquirer, network | Carry and present the mandate according to the rail's own rules; no new obligations are defined for them. |
Holder/Agent Transparency Rail / Merchant
| Service |
1. record items, compute | |
receipt, record, | |
commitment C | |
2. register anchor(C) ------>| |
|<-- transparency receipt |
3. mandate/payload carrying decision_context(C) -->|
| | |
... later, a dispute ... | |
| | Verifier |
|<-------- 4. disclosure request (credential) |
5. release record, salts, | |
receipt, items ---------------------------------> 6. verify:
| | mandate, C,
|<---------------------- inclusion/time check - receipt,
| | items, level
For each context item i, in the order in which items were provided to the model, the holder constructs an item descriptor D_i and a fresh salt s_i, and computes the item digest:¶
item_digest_i = BASE64URL(SHA-256(s_i || CANON(D_i)))¶
An item descriptor has the following members:¶
The holder MUST list every input to every model call covered by the decision, including instructions, tool definitions, prior turns, retrieved content, and media, and MUST include the raw provider response for each call with role "response". Where the agent transformed an item, the item as provided to the model has role "as_sent" and a "derived_from" member giving the index of its source item. The source item SHOULD also be listed; it has role "original" if it was not itself provided to the model, and role "as_sent" otherwise. Agents SHOULD perform media transformations themselves and stay within provider limits, so that the bytes recorded are the bytes the model received.¶
The context receipt is:¶
receipt_digest = BASE64URL(SHA-256(CANON(receipt))). Because each descriptor is hidden behind its own salt, the receipt reveals neither the content digests nor the origins of the items, and a descriptor and its salt can later be deleted without changing receipt_digest (Section 5.6).¶
One decision record MUST correspond to at most one transaction. Outcomes and erasures are not part of the record; they are separate entries (Section 5.6).¶
C = BASE64URL(SHA-256(salt || CANON(record)))¶
The decision context claim is:¶
{"v":1,"alg":"sha-256","commitment":"<C>","receipt_uri":"<uri>",
"profile":"<uri>"}
¶
"receipt_uri" is an https URI at which disclosure requests for this record are made; its path is an opaque value chosen by the holder. It MUST NOT embed content, digests, or identifiers that link the principal across transactions. "profile" (OPTIONAL) identifies the profile; if it is absent, the verifier obtains the applicable profile out of band, and without one reports no level above A1. Rails that cannot carry the claim object carry only C, as defined in Section 7. In JWT-based mandates, the claim is carried as an ordinary SD-JWT disclosure [RFC9901]: it is not canonicalized, its member order is whatever the issuer serialized, and its digest is computed over the base64url-encoded disclosure string as RFC 9901 specifies. Verifiers MUST use the received disclosure string and MUST NOT re-serialize the claim. On rails that are not JWT-based, the claim is carried as a plain JSON object or reduced to C, as defined in Section 7. The commitment salt is distinct from any SD-JWT disclosure salt and is released only under Section 6.¶
The holder MUST create a decision record for every covered decision, including decisions whose action is "decline", and MUST assign consecutive sequence numbers per chain. The "prev" member links each record to its predecessor, so that a missing record appears as a gap. Hash chains are common in audit-log designs (Section 9); the chain here is kept per chain id, independent of signing keys, and covers declined decisions, so that a verifier can detect a withheld decision, not only a withheld payment.¶
A chain is not tied to a signing key. The key that signs a chain's first anchor is the chain's initial key. To rotate, the holder registers a rotation statement of type "adc-rotation", signed with the current key, that names the chain and the successor key; statements signed with the successor key then continue the same chain id and sequence. Verifiers MUST consider only statements whose signing key is the chain's initial key or a successor established by such rotation statements; statements by any other issuer that use the same "sub" are ignored. The holder MUST NOT start a new chain to replace an existing one except under the profile's rules, and a new chain's first anchor MUST be reported to verifiers on request. A deletion at the tail of a chain, after the last anchor a verifier sees, is not detectable by the chain alone.¶
Unless the profile permits late registration, the holder MUST register an anchor statement with at least one transparency service [RFC9943] before the mandate or payment payload that carries the claim is signed. The anchor is a Signed Statement as defined in [RFC9943]: a COSE_Sign1 message [RFC9052] whose protected header carries a "kid" identifying the signing key (unless "x5t" or "x5chain" is present) and CWT claims [RFC8392] with "iss" identifying the holder and "sub" set to the chain id, and whose payload, of content type application/adc-anchor+json, is:¶
{"type":"adc-anchor","v":1,"commitment":"<C>","chain":"<id>",
"seq":<n>,"prev":"<C(n-1)>","issued_at":"<time>"}
¶
The holder MUST retain the transparency receipt. Level A2 requires that the registration time can be established from the receipt or from the transparency service under the profile; [RFC9943] does not itself require receipts to carry a registration time. Profiles MAY require registration with two transparency services operated by different organizations, SHOULD prefer services that provide consistency proofs, and MAY set the maximum interval between issued_at and registration (300 seconds if unspecified). Where the profile permits late registration, a holder that cannot reach a transparency service registers as soon as it can, and the anchor is reported as late (Section 6.5). Outcome, erasure, and rotation statements (Section 5.6) use the same envelope and "sub" but carry no sequence number. A batch format for anchors is out of scope.¶
Failure handling:¶
After the decision, the holder can register further signed statements that reference the commitment:¶
The following CDDL [RFC8610] describes the objects. JSON encodings apply; "digest" is a base64url string, "timestamp" is an RFC 3339 string, and salts, content, nonce_random, and statements are base64url strings. Each entry of "statements" is a Transparent Statement [RFC9943]: an anchor, entry, or rotation statement with its transparency receipts. The exchange objects of Section 8 are described by adc-exchange, adc-witness, and exchange-receipt.¶
adc-receipt = {
type: "adc-receipt", v: 1, alg: hash-alg,
items: [+ digest], item_count: uint .ge 1,
retention_until: timestamp,
? selector: { name: tstr, version: tstr, ? config_digest: digest },
* tstr => any
}
item-descriptor = {
content_digest: digest, media_type: tstr, role: item-role,
calls: [* uint], ? derived_from: uint, ? transform: tstr,
source: { kind: tstr, ? "uri": tstr, ? fetched_at: timestamp,
* tstr => any },
* tstr => any
}
item-role = "instructions" / "tool_definition" / "prior_turn" /
"as_sent" / "original" / "request" / "response" /
"exchange" / "witness" / tstr
adc-record = {
type: "adc-record", v: 1, alg: hash-alg,
chain: { id: tstr, seq: uint, prev: digest, * tstr => any },
? profile: tstr,
receipt_digest: digest,
question: { name: tstr, kind: tstr, schema_digest: digest,
* tstr => any },
models: [+ model-call],
answer: { * tstr => any },
action: { taken: tstr, * tstr => any },
? transaction: transaction,
issued_at: timestamp,
* tstr => any
}
model-call = {
provider: tstr, id: tstr, request_id: tstr, request_digest: digest,
? params_digest: digest, ? attestation: tstr,
* tstr => any
}
transaction = {
rail: "vi" / "ap2" / "x402" / "iso20022" / tstr,
payee: tstr, amount: tstr, currency: tstr,
? checkout_digest: digest,
* tstr => any
}
decision-context-claim = {
v: 1, alg: hash-alg, commitment: digest, receipt_uri: tstr,
? profile: tstr,
* tstr => any
}
adc-anchor = {
type: "adc-anchor", v: 1, commitment: digest, chain: tstr,
seq: uint, prev: digest, issued_at: timestamp,
* tstr => any
}
adc-outcome = {
type: "adc-outcome", v: 1, commitment: digest, status: tstr,
? reference_digest: digest, issued_at: timestamp,
* tstr => any
}
adc-erasure = {
type: "adc-erasure", v: 1, commitment: digest,
items: [+ uint], erased_at: timestamp,
reason: "principal_request" / "legal_obligation" / "lost" / tstr,
* tstr => any
}
adc-rotation = {
type: "adc-rotation", v: 1, chain: tstr, successor_kid: tstr,
issued_at: timestamp,
* tstr => any
}
adc-request = {
type: "adc-request", v: 1, request_id: tstr, verifier: tstr,
presentation_digest: digest, issued_at: timestamp,
* tstr => any
}
adc-release = {
type: "adc-release", v: 1, request_id: tstr, verifier: tstr,
issued_at: timestamp, record: adc-record,
commitment_salt: tstr, receipt: adc-receipt,
statements: [* tstr],
items: [+ released-item / withheld-item],
? nonce_random: tstr,
* tstr => any
}
released-item = { index: uint, salt: tstr,
descriptor: item-descriptor, content: tstr }
withheld-item = { index: uint, status: "withheld" / "erased",
? erasure_reference: tstr }
adc-error = {
type: "adc-error", v: 1, ? request_id: tstr,
code: "invalid_request" / "invalid_credential" /
"replayed_request" / "not_found" / "policy_denied" /
"too_large" / "temporarily_unavailable" / tstr,
? retry_after: uint, ? description: tstr,
* tstr => any
}
adc-report = {
type: "adc-report", v: 1, verifier: tstr, verified_at: timestamp,
? profile: tstr, level: "A0" / "A1" / "A2" / "A3" / "none",
signer: "agent" / "principal" / "payer" / "none",
steps: [8*8 step-result],
inputs: { presentation: digest, release: digest,
statements: [* digest], attestations: [* digest],
trust_anchors: [* digest], revocation: [* digest] },
? late_anchor_seconds: uint, ? equivocation: bool,
* tstr => any
}
adc-exchange = {
exchange-common, exchange-step,
* tstr => any
}
exchange-common = (
type: "adc-exchange", v: 1, exchange_id: tstr, seq: uint,
prev: digest, issued_at: timestamp
)
exchange-step = (
x-offer // x-held // x-key-release // x-key-received //
x-received // x-mismatch // x-answer // x-arbiter-delivery //
x-non-ack // x-close
)
x-offer = (
step: "offer", intent: "deliver" / "offer",
sender_key: tstr, recipient_key: tstr,
payload_commitment: digest, aead: tstr,
ciphertext_hash: digest, key_hash: digest,
? escrow: adc-escrow, response_window: uint,
expires_at: timestamp
)
x-held = (step: "held", envelope_hash: digest,
? arbiters_accepted: digest)
x-key-release = (
step: "key_release",
hpke: { kem_id: uint, kdf_id: uint, aead_id: uint },
wrapped_key: tstr
)
x-key-received = (step: "key_received", key_release_hash: digest)
x-received = (step: "received", envelope_hash: digest)
x-mismatch = (
step: "mismatch", envelope_hash: digest,
check: "ciphertext_hash" / "unwrap" / "key_hash" / "decrypt" /
"payload_commitment"
)
x-answer = (step: "accepted" / "refused", envelope_hash: digest)
x-arbiter-delivery = (
step: "arbiter_delivery", envelope_hash: digest,
key_matched: bool, commitment_matched: bool,
? delivered_at: timestamp
)
x-non-ack = (
step: "non_ack", envelope_hash: digest, awaited: tstr,
attempts: [* delivery-attempt]
)
x-close = (step: "close", state: exchange-state, reason: tstr)
delivery-attempt = {
transport_digest: digest, witnesses: [+ witness-statement],
* tstr => any
}
adc-escrow = {
scheme: tstr, arbiters: [3* tstr], threshold: uint .ge 2,
shares: [3* tstr], proof: tstr,
* tstr => any
}
exchange-state = "NOT_ACKNOWLEDGED" / "VOID" /
"KEY_UNACKNOWLEDGED" / "DELIVERED_VIA_ARBITER" /
"CONTENT_UNACKNOWLEDGED" / "DELIVERED" /
"ACCEPTED" / "REFUSED" / "NOT_ACCEPTED" /
"FAULT_SENDER" / "FAULT_RECIPIENT" / "CLOSED"
exchange-receipt = {
object: tstr / { payload: tstr, signatures: [+ any] },
witnesses: [+ witness-statement],
* tstr => any
}
witness-statement = {
format: "rfc3161" / "scitt" / tstr, token: tstr,
* tstr => any
}
adc-witness = {
type: "adc-witness", v: 1, object_hash: digest,
* tstr => any
}
step-result = ("pass" / "fail" / "not_applicable" /
"not_checked" / "unsupported")
hash-alg = "sha-256" / tstr
digest = tstr
timestamp = tstr
¶
A verifier sends the holder, at "receipt_uri", the mandate presentation that carries the claim and a credential issued by the party administering the dispute. The credential types, their trust anchors, and the request transport are defined by the profile; this document defines the properties they need. The credential MUST be audience-restricted to the holder, sender-constrained (for example with DPoP [RFC9449]), short-lived, and bound to the transaction, for example by including the "sd_hash" of the presentation, so that a holder or an attacker controlling "receipt_uri" cannot replay it elsewhere. The verifier MUST dereference only https URIs, MUST apply request-forgery protections to "receipt_uri" (for example refusing private and link-local addresses and redirects to other hosts), and MUST NOT dereference "source.uri" values found in released descriptors. The holder MUST authenticate the credential, MUST log the request, and MUST limit release to the records bound to the transaction in the request.¶
The request is an object of type "adc-request" (Section 5.7) carrying "request_id", at least 16 octets from a cryptographically secure random number generator encoded in base64url; "verifier", an identifier of the verifier; "presentation_digest", the digest of the mandate presentation; and "issued_at". The holder MUST reject a request whose request_id it has already accepted within the lifetime of the credential, and a request whose issued_at lies outside the profile's tolerance (60 seconds if unspecified).¶
Release is governed by an authorization policy that the principal sets in advance. When the policy permits, the holder returns a release object of type "adc-release" (Section 5.7) containing the decision record, the commitment salt, the context receipt, the anchor statement and any rotation statements needed to authorize its key, each with its transparency receipts, and, for each item, either the descriptor, its salt, and the item bytes, or a statement that the item is withheld or was erased (with a reference to the erasure entry). The release echoes the request's "request_id" and "verifier" and carries its own "issued_at", so that it cannot be presented as the answer to a different request. For the x402 binding, the release also includes "nonce_random" (Section 7.3). The holder MUST sign the release as a JWS [RFC7515] with a key that the profile associates with the holder, so that what was released, and what was withheld, is attributable. Withheld items remain listed by digest, so the omission is visible. Because the holder is typically the party whose conduct is in question, profiles MAY require the holder to deposit an encrypted copy of the release material with the principal or a custodian at decision time.¶
A release contains personal data and the salts that protect it. It MUST be transferred over TLS 1.3 [RFC8446] or later, following [RFC9325], with the holder authenticated for the host named in "receipt_uri" and the verifier's credential presented within that session. If any party other than the holder and the verifier relays, queues, or stores the release, the release SHOULD additionally be encrypted to the verifier as a JWE [RFC7516]; HPKE-based JWE [I-D.ietf-jose-hpke-encrypt] and post-quantum or hybrid key encapsulation [I-D.ietf-hpke-pq] are suitable as they become available. Salts MUST NOT be sent in any message that is not confidentiality-protected. A verifier MUST NOT retain released content beyond the dispute and any retention obligation that applies to it.¶
| Level | Conditions |
|---|---|
| A0 | The record matches a commitment carried in a field not signed by the agent, the principal, or the payer. |
| A1 | The commitment is covered by the signature of the agent, the principal, or the payer (including the x402 nonce construction), and steps 2 to 5 succeed. The verifier reports which party signed. |
| A2 | A1, and step 6 succeeds: the anchor was registered in time, and the anchored chain values are consistent with the record and with any neighboring anchors obtained. |
| A3 | A2, every model call carries an attestation, and step 7 succeeds for every model call. |
A late or missing anchor caps the level at A1, and the verifier reports the delay; a timely anchor for an A0 commitment is reported alongside A0. No level asserts that the receipt lists every input to the model. At A3, a provider attestation binds what the provider received and returned; if the holder also releases the item with role "request", the verifier can check that the listed items are the parts of that request. The strength of A3 depends on the attestation format; some formats bind hashes of a request and response without proving that the response was produced from that request. Profiles define which levels they accept for which purposes.¶
A holder that does not release returns an error object of type "adc-error" (Section 5.7) with one of the following codes; the profile maps them to its transport.¶
Where distinguishing not_found from policy_denied would reveal that a record exists, the profile MAY require holders to return not_found for both. An error is not evidence about the decision; verifiers report it as received.¶
Verifiers fail closed. Each step in Section 6.4 yields one of "pass", "fail", "not_applicable", "not_checked", or "unsupported". If any of steps 1 to 5 yields "fail" or "unsupported", no level is reported. A step that is not checked never counts as passed, and partial results never raise the level.¶
The verifier records its result as a verification report of type "adc-report" (Section 5.7). The report states the level or its absence, the result of each step, the profile, the verifier, the time of verification, and the digests of every input: the mandate presentation, the signed release, each transparent statement, each attestation, and the trust anchors and revocation information used. The verifier MUST sign the report as a JWS [RFC7515].¶
Given the same inputs, every step except the evaluation of revocation status is a deterministic computation, so any other party holding those inputs can repeat the procedure and must obtain the same result. Revocation status is evaluated as of the recorded time against the recorded sources. A verifier is therefore checked by repetition, not by another authority. Profiles MAY require verification by more than one independent verifier for a given purpose.¶
The bindings below are based on the cited versions of each protocol and will need revision as those protocols change. Verifiable Intent and the Agent Payments Protocol have been contributed to the FIDO Alliance, whose Payments Technical Working Group is developing them further [FIDO-AGENTIC]. AP2 states that other properties may be included in a mandate, based on the mandate type. Verifiable Intent defines no general rule for additional claims in its Layer 3 payloads; it defines an "agent_attestation" extension container whose attestation types are not yet specified and whose unrecognized types are ignored, and a future attestation type for decision evidence could carry the commitment instead of a separate claim. The bindings rely on the JWT rule that claims not understood are ignored unless the application requires otherwise (Section 4 of [RFC7519]), and on verifying parties that choose to process the claim. Selective disclosure does not prevent a presenter from omitting the claim's disclosure; it prevents only alteration of a disclosure that is presented.¶
In Autonomous mode, the agent includes "decision_context" as an additional selectively disclosable claim in both the Layer 3a payment payload and the Layer 3b checkout payload [VI], with the same claim value, so that its disclosure digest appears under the agent's signature in what the network and the merchant each receive. Payee and amount are compared against Layer 3a and checkout_digest against Layer 3b. Verifiable Intent does not profile additional claims of this kind; a holder that presents the payload to a party that does not process the claim can omit its disclosure. The claim MUST NOT be expressed as a constraint, because unknown constraint types in open mandates are rejected. In Immediate mode no agent-signed layer exists and the principal confirms the final values; the claim MAY be placed in the principal-signed Layer 2 mandate only if the signing interface shows the principal that a decision commitment is included, and the verifier reports the principal as signer. checkout_digest is the Verifiable Intent "checkout_hash" value.¶
The agent includes "decision_context" as a selectively disclosable claim of the closed Checkout Mandate ("mandate.checkout.1") [AP2]. The merchant-signed Checkout Receipt's "reference" covers the commitment only if the claim's disclosure is included in the presentation the merchant receives. The claim MUST NOT appear in open mandates or constraints. In human-present mode the principal signs the closed mandate, and the claim SHOULD be included only if the trusted surface shows the principal that a decision commitment is included. The claim MAY also be included in the Payment Mandate, which carries the payee, payment instrument, and amount to the payment processor and issuer; payee, amount, and currency are compared against the Payment Mandate where present and otherwise against the checkout object bound by "checkout_hash". Implementations MUST compute digests over the raw disclosure strings, since some libraries discard unknown members when parsing. checkout_digest is the closed mandate's "checkout_hash".¶
The server advertises the extension "adc" in its payment-required response with an empty "info" object and a schema; the client echoes the extension in its payment payload and appends the claim as "info.decision_context", which the x402 extension rules permit (clients may append to, but not alter, the server's info) [X402]. The payer's EIP-3009 or Permit2 signature does not cover extensions. To bring the commitment under the payer's signature, the client SHOULD set the 32-octet nonce to SHA-256(BASE64URL-DECODE(C) || r), where r is 16 random octets that the holder retains with the record and returns, as "nonce_random", in the release (Section 6.2); the 32-octet value is encoded as the payment scheme requires (for example as a decimal string for Permit2). Without this construction, the binding is level A0. checkout_digest is BASE64URL(SHA-256(CANON(accepted payment requirements))). Payee is the "payTo" value and amount the authorized value in base units; currency is the CAIP-19 identifier [CAIP19] of the asset, lowercased before use. Hexadecimal addresses are compared case-insensitively. Because payment requirements can repeat, reuse of a claim is detected by the nonce construction and the holder chain, not by step 3 alone.¶
For ISO 20022 credit transfers [ISO20022], including instant schemes, the holder places "ADC1:" followed by the lowercase hexadecimal encoding of the 32 octets of the commitment in the unstructured remittance information (69 characters), where the scheme and the payer's provider permit payer-supplied remittance text. Hexadecimal is used because some schemes restrict remittance text to a limited character set. No receipt_uri is carried; the verifier obtains it from the payer's provider or the principal under the profile. Payee is the creditor account identifier, amount the instructed amount in minor units, and currency its ISO 4217 code. checkout_digest is BASE64URL(SHA-256(end-to-end identifier)) when that identifier is known before submission and is otherwise absent. This field is not signed by the agent, so the binding is level A0, anchoring (Section 5.5) provides the time bound, and reuse is detected only through the holder chain.¶
Card authorization and authentication messages do not carry the claim. An issuer can obtain it only through a mandate presented by the rail's agent program. This document defines no binding for authorization messages.¶
A decision record proves what one party's agent relied on. This section defines how two parties, for example an agent and a merchant, two agents, or an agent and a person, evidence what passed between them. The receiver signs over exactly the committed bytes. Independent witnesses, procured and carried by each party's agent, fix the time of every step with their own clocks. A failure to answer by a deadline becomes evidence in its own right, and arbiters resolve stalled exchanges.¶
The result is tamper-evident, not tamper-proof. Fair exchange between two parties without a trusted third party is impossible [PAGNIA], so this section follows the optimistic model [ASOKAN]: witnesses take part in every exchange, and arbiters take part only in stalled ones. The exchange is OPTIONAL; a holder that does not exchange payloads with counterparties under this document need not implement it.¶
| step | Signed by | Members in addition to the common members |
|---|---|---|
| offer | Sender | intent, sender_key, recipient_key, payload_commitment, aead, ciphertext_hash, key_hash, escrow (conditional), response_window, expires_at |
| held | Recipient | envelope_hash, arbiters_accepted (when escrow is present) |
| key_release | Sender | hpke, wrapped_key |
| key_received | Recipient | key_release_hash |
| received | Recipient | envelope_hash |
| mismatch | Recipient | envelope_hash, check |
| accepted, refused | Recipient | envelope_hash; valid only after received for the same envelope, and only when intent is "offer" |
| arbiter_delivery | M arbiters | envelope_hash, key_matched, commitment_matched, delivered_at |
| non_ack | Sender | envelope_hash, awaited, attempts |
| close | Either party | state, reason |
A witness is an independent service that countersigns an object_hash with its own time. Witnesses see hashes only, never content. A witness statement takes one of two forms:¶
{"type":"adc-witness","v":1,"object_hash":"<object_hash>"}
¶
Each party's agent procures witness statements for every object it signs and for every object it receives, and carries them with the object as an exchange receipt: {"object": the signed object, "witnesses": [{"format", "token"}, ...]}. A receipt for a step is therefore three things together: the object, the counterparty's signature on the object that answers it, and the witness statements. Neither party needs to trust the other's clock or storage.¶
At least one witness MUST be independent of both parties. Exchanges that carry a payment or other obligation SHOULD use two witnesses operated by different organizations. The witness time of an object is the earliest time among its valid witness statements from witnesses accepted by the profile. Witness time is authoritative for deadlines and for ordering across parties. The anchor of Section 5.5 is itself a witness statement over the decision commitment.¶
A witness statement proves that an object existed at a time. It does not prove delivery. Delivery is proven by the counterparty's acknowledgement. Without one, a non_ack stands on delivery attempts whose transport receipts were witnessed at the time of each attempt.¶
A verifier determines the state of an exchange by verifying each object's signature, its exchange_id, seq, and prev, and its witness statements, and then applying the transitions below in order of witness time.¶
| State | Expected next | If the deadline passes |
|---|---|---|
| SENT (offer witnessed) | held | NOT_ACKNOWLEDGED. The sender issues non_ack carrying the witnessed delivery attempts. The recipient is bound to nothing, and a later held is void. |
| HELD | key_release | VOID. K was withheld and the offer is void. The recipient MAY close. |
| KEY_RELEASED | key_received | Without escrow: KEY_UNACKNOWLEDGED, and the sender issues non_ack. With escrow: the sender MAY request arbiter delivery (Section 8.5); on arbiter_delivery, DELIVERED_VIA_ARBITER, and any later received, accepted, or refused is void. |
| KEY_RECEIVED | received, or mismatch | CONTENT_UNACKNOWLEDGED. The sender MAY issue non_ack. |
| RECEIVED (intent "offer") | accepted, or refused | NOT_ACCEPTED. The silence is recorded with non_ack. |
| MISMATCH | Recomputation | FAULT_SENDER if the failure is confirmed; FAULT_RECIPIENT if it is not. |
For intent "deliver", received moves the exchange directly to the terminal state DELIVERED. For intent "offer", accepted and refused move it to ACCEPTED and REFUSED. Either party MAY close an exchange in a non-terminal state with a stated reason, which moves it to CLOSED. The terminal states are NOT_ACKNOWLEDGED, VOID, KEY_UNACKNOWLEDGED, DELIVERED_VIA_ARBITER, CONTENT_UNACKNOWLEDGED, DELIVERED, ACCEPTED, REFUSED, NOT_ACCEPTED, FAULT_SENDER, FAULT_RECIPIENT, and CLOSED.¶
An object whose earliest witness time is after the deadline or expiry that governs it, or after the exchange reached a terminal state, is void. A state that results from a passed deadline is established against the objects presented; a party rebuts it by presenting an object of the awaited step whose witness time precedes the deadline.¶
Recomputation: because the sender signed both ciphertext_hash and key_hash, any party given K and the ciphertext can repeat the recipient's checks. If DIGEST(ciphertext) equals ciphertext_hash and DIGEST(K) equals key_hash, but decryption fails or the decrypted payload does not match payload_commitment, the failure is confirmed and the state is FAULT_SENDER. If every check passes, the state is FAULT_RECIPIENT. Where escrow is present, arbiters perform the recomputation with the reconstructed K. A recipient that relies on recomputation discloses K and the ciphertext to the party that performs it, which can then read the payload. If the K or the ciphertext presented does not match the hash the sender signed, recomputation cannot attribute the failure, because HPKE decryption and transport are not publicly verifiable. This covers the checks "ciphertext_hash", "unwrap", and "key_hash"; where escrow is present, the arbiters instead check the reconstructed K as described in Section 8.5. Otherwise the verifier reports the cause as not determinable, and the exchange remains in MISMATCH until it is closed.¶
FAULT_SENDER and FAULT_RECIPIENT identify the party whose signed values are inconsistent with the recomputation. Like every other result of this document, they are evidence and not a verdict (Section 1.2).¶
Implementations MUST present held, received, delivered via arbiter, and accepted to users as distinct states. Treating delivery as agreement is the most likely misuse of this mechanism.¶
Arbiters are named in the offer and bind only once the recipient accepts them through arbiters_accepted in held. Publicly verifiable secret sharing lets the recipient check the encrypted shares before it sends held. A recipient MUST NOT send held with arbiters_accepted unless the proof verifies; where escrow is required and the proof does not verify, the recipient closes the exchange.¶
An arbiter MUST open its share only for an exchange whose chain and witness statements show KEY_RELEASED past its deadline, or MISMATCH, and MUST verify the chain before doing so. Every opening is itself witnessed and registered with a transparency service.¶
When M arbiters have opened their shares, they reconstruct K, check it against key_hash, check that it decrypts the ciphertext to a payload matching payload_commitment, deliver K to the recipient in the manner of key_release, and each sign the same arbiter_delivery body; the signatures are carried in the JWS JSON General Serialization (Section 7.2.1 of [RFC7515]). arbiter_delivery never implies received, acceptance, or refusal. Arbiters never issue received, accepted, or refused on the recipient's behalf.¶
If the reconstructed K does not match key_hash, or does not decrypt the ciphertext to a payload matching payload_commitment, the arbiters sign arbiter_delivery with "key_matched" or "commitment_matched" false and deliver nothing, and the inconsistency is attributed to the sender, who signed both values (FAULT_SENDER).¶
When an agent decides on the basis of a received payload, its context receipt (Section 5.2) lists, as items with role "exchange", the bodies of the exchange objects up to and including received, each with media type application/adc-exchange+json, and, as items with role "witness", their witness statements, in addition to the payload itself. The decision record therefore proves both what arrived and what the agent did with it.¶
A counterparty MAY countersign a decision commitment: the holder sends an exchange with intent "deliver" whose payload is the decision context claim, and the counterparty's received object is the countersignature. In the Agent Payments Protocol, the merchant-signed Checkout Receipt has the same effect when the claim's disclosure is included in the presentation the merchant receives (Section 7.2).¶
Re-submitting recorded items to the named model shows what the model received and how it may respond. It does not establish what caused the original output, whether that output was correct, or that a given item was responsible. Model outputs may vary between calls, and models may be withdrawn. The committed answer is the record of what happened.¶
Salts have a fixed length so that concatenation is unambiguous. The "alg" member allows other digest algorithms in future versions; verifiers reject algorithms they do not support. Item digests and commitments are salted with fixed-length salts, and receipts, records, and statement payloads carry distinct "type" members; an item descriptor carries no "type" but is always hashed with a salt, whereas receipts are not. SD-JWT disclosure digests are computed as specified in [RFC9901].¶
This document does not depend on the quantum resistance of any signature. SHA-256 commitments retain their binding property against known quantum attacks, and the 32-octet salts keep generic quantum search for the hidden content impractical. The signatures on mandates, signed statements, and transparency receipts are another matter: a party able to forge them could fabricate evidence of when a commitment existed. Holders SHOULD support ML-DSA for anchor statements [RFC9964] as transparency services deploy it, MAY use composite signatures [I-D.ietf-jose-pq-composite-sigs], and MAY re-register earlier commitments under a stronger algorithm while retaining the original receipts. Verifiers MUST report the signature algorithm used at each step.¶
This section follows the threat categories of [RFC6973].¶
Data-protection law can apply to holders and verifiers, for example [GDPR] and [LGPD]. Holders will typically need to determine their role as controller or processor, a lawful basis, whether an impact assessment is required, how notice is given to third parties whose data appears in context, how access requests are met, and how cross-border transfers are handled. Under such regimes, digests of personal data can remain personal data. Deleting a descriptor's salt prevents the digest from being linked to the content, but whether that satisfies an erasure obligation is a legal question. Requirements for logging by high-risk AI systems [EUAIA] apply only where a system is classified as high-risk; this document does not by itself satisfy them.¶
IANA is requested to register the following claim in the "JSON Web Token Claims" registry established by [RFC7519]:¶
IANA is requested to register the following media types [RFC6838], all using the "+json" structured syntax suffix [RFC6839]. application/adc-anchor+json is the payload content type of anchor, outcome, erasure, and witness statements (Section 5.5, Section 8.3); application/adc-release+json is the payload of a signed release (Section 6.2); application/adc-exchange+json is the body of an exchange object (Section 8).¶
This section is to be removed before publishing as an RFC.¶
This section records the status of known implementations at the time of writing, following [RFC7942]. Description: the author is developing a prototype holder implementation. Maturity: prototype; not publicly available. Coverage: data model, commitment, and test vectors; the exchange in Section 8 is not yet implemented, and its state table has been checked mechanically for completeness and for the absence of transitions out of terminal states. Contact: the author. No interoperability testing has taken place. The test vectors in Appendix A were reproduced by an independent implementation written from the text of this document alone, and the CDDL in Section 5.7 has been checked with a CDDL validator against the objects in that appendix.¶
All values were computed with SHA-256, NFC normalization, JCS, and base64url without padding. Objects are shown formatted for readability; their canonical forms follow from Section 5.1. Salts are fixed, not random, so that results can be reproduced.¶
Item bytes (as US-ASCII strings) and salts:¶
item 0: "You are a payments assistant. Pay only bills the user
approved." (one line, single space after "user",
no trailing newline)
salt: 32 octets of 0xa0
item 1: "example invoice email body" salt: 32 octets of 0xa1
item 2: "example as-sent image bytes" salt: 32 octets of 0xa2
item 3: "{\"noul\":0.88}" salt: 32 octets of 0xa3
¶
Item descriptors (content_digest values included) and item digests:¶
item 0
salt = oKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKA
descriptor =
{
"calls": [
0
],
"content_digest": "rsu-z89tDjJoX3LK0DRrSIRz_EuYgWJ95Woi06yuRxg",
"media_type": "text/plain",
"role": "instructions",
"source": {
"kind": "system"
}
}
item_digest = 9QV3ZRTxsG60bOe1e6gS5LL0q5XjkTivElxci0uqfqY
item 1
salt = oaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaE
descriptor =
{
"calls": [
0
],
"content_digest": "BlW2UA5Du9SFrwm-NpRxeQsmL5C2lYT2GyJbZW3-BPU",
"media_type": "message/rfc822",
"role": "as_sent",
"source": {
"fetched_at": "2026-10-10T14:02:51Z",
"kind": "email",
"uri": "mid:invoice-0042@supplier.example"
}
}
item_digest = 8m7geGcZw1hWHKHjFFOpEpPe_MQ9IgxwyUaFALTdw4Y
item 2
salt = oqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqI
descriptor =
{
"calls": [
0
],
"content_digest": "2xHGVXsHZv2LW4y8n9Kl7juZ1Q1dfdfdL6s7P114tmQ",
"derived_from": 1,
"media_type": "image/png",
"role": "as_sent",
"source": {
"fetched_at": "2026-10-10T14:02:51Z",
"kind": "email_part",
"uri": "mid:invoice-0042@supplier.example"
},
"transform": "resize/example-lib-1.0/lanczos3/1024"
}
item_digest = nkT1qIfqXLMxVXtuqJvcRQtNaDbLxitRYFq329hcjrI
item 3
salt = o6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6M
descriptor =
{
"calls": [
0
],
"content_digest": "hGYLopth2mQvEvJW8AsgAyAuYcLFyHR5qcILCdeaqRk",
"media_type": "application/json",
"role": "response",
"source": {
"kind": "model",
"uri": "https://provider.example/v1/requests/req-0001"
}
}
item_digest = 2g8OXnhoGOAx3nkSIwTWsKYxkRuD5-sVOJAFUxh7uxA
¶
Context receipt:¶
{
"alg": "sha-256",
"item_count": 4,
"items": [
"9QV3ZRTxsG60bOe1e6gS5LL0q5XjkTivElxci0uqfqY",
"8m7geGcZw1hWHKHjFFOpEpPe_MQ9IgxwyUaFALTdw4Y",
"nkT1qIfqXLMxVXtuqJvcRQtNaDbLxitRYFq329hcjrI",
"2g8OXnhoGOAx3nkSIwTWsKYxkRuD5-sVOJAFUxh7uxA"
],
"retention_until": "2028-04-10T00:00:00Z",
"type": "adc-receipt",
"v": 1
}
¶
receipt_digest = _Ov_XzNXS4z1mVz9sb2CgIRLZbyUlSlN_G2lw080W0s¶
Decision record. Four members use stand-in values so that the vector is self-contained: "prev" stands in for the commitment of record 40 and is DIGEST of the US-ASCII string "previous commitment placeholder"; "checkout_digest" stands in for the AP2 "checkout_hash" and is DIGEST of the US-ASCII string "example checkout_jwt"; "request_digest" is DIGEST of the US-ASCII string "example provider request body". In this appendix, DIGEST of a string means DIGEST of its octets, not of its JSON form. "schema_digest" is DIGEST of the JSON object {"kind":"probability","name":"pay_invoice"}.¶
{
"action": {
"taken": "pay"
},
"alg": "sha-256",
"answer": {
"probability": 0.88
},
"chain": {
"id": "ch-7f3a91",
"prev": "uuwyfPlzoD_kakG59boK4Ga0yJamhmuNq34CaV1WXMA",
"seq": 41
},
"issued_at": "2026-10-10T14:03:22Z",
"models": [
{
"id": "example-model-2026-09",
"provider": "provider.example",
"request_digest": "rLYUCRQ-un2g7mhK3wxADF_ud8Up-2bz66_UsWbCNOE",
"request_id": "req-0001"
}
],
"question": {
"kind": "probability",
"name": "pay_invoice",
"schema_digest": "e64ljzd9rTSr6AXA41WIigpstKOTVV82YIfxgxLidwY"
},
"receipt_digest": "_Ov_XzNXS4z1mVz9sb2CgIRLZbyUlSlN_G2lw080W0s",
"transaction": {
"amount": "48000",
"checkout_digest": "H6AT0BnDXRP7TfoMSOAdI7qs-u2D3Y99JeI7kKsQd2k",
"currency": "BRL",
"payee": "merchant.example",
"rail": "ap2"
},
"type": "adc-record",
"v": 1
}
¶
commitment salt (octets 0x00..0x1f) = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8 C = iJbDuNj4EPEiCI_lVaAeT465fHTJn1qTDyDuBZSIIXA¶
Decision context claim and SD-JWT disclosure. "receipt_uri" in this example ends in an opaque value chosen by the holder. The disclosure salt is the 16 octets 0x20..0x2f ("ICEiIyQlJicoKSorLC0uLw"). The disclosure is the array [salt, "decision_context", claim] serialized without whitespace, with the claim members in the order shown; the disclosure digest is DIGEST of the US-ASCII disclosure string. Line breaks in the encoded value are for display only:¶
{
"v": 1,
"alg": "sha-256",
"commitment": "iJbDuNj4EPEiCI_lVaAeT465fHTJn1qTDyDuBZSIIXA",
"receipt_uri": "https://holder.example/adc/r/q8Zk2LxV"
}
¶
disclosure = WyJJQ0VpSXlRbEppY29LU29yTEMwdUx3IiwiZGVjaXNpb25fY29udGV4dCIseyJ2 IjoxLCJhbGciOiJzaGEtMjU2IiwiY29tbWl0bWVudCI6ImlKYkR1Tmo0RVBFaUNJ X2xWYUFlVDQ2NWZIVEpuMXFURHlEdUJaU0lJWEEiLCJyZWNlaXB0X3VyaSI6Imh0 dHBzOi8vaG9sZGVyLmV4YW1wbGUvYWRjL3IvcThaazJMeFYifV0 disclosure digest = sS4aHqmyXB7SM7eTSevd7HSwzvF2AuHi86hC_VH-BVU¶
x402 nonce, SHA-256(BASE64URL-DECODE(C) || r) in hexadecimal, with r ("nonce_random") = octets 0x30..0x3f:¶
nonce = 0xfeb329a3d9524e30640c71e39301af69
c63b7d54293eb1104a7d67e3765ad304
¶
Negative vector: changing item 1's "fetched_at" to "2026-10-10T14:02:52Z" yields item digest _zVFztwZa20kMSsIdkhpwOq1UeSKLe8yygvC0O5_3Cs and receipt_digest alHUluxRV0hr2pB-N82UZZPIJ5whPUSRm1oZeG2Hsh4, which does not match the record.¶
ISO 20022 remittance information for this commitment:¶
ADC1:8896c3b8d8f810f122088fe555a01e4f8eb97c74c99f5a930f20ee0594882170¶
Normalization: the strings "Cafe" followed by U+0301 and "Caf" followed by U+00E9 produce identical canonical forms after NFC normalization.¶
This appendix is informative. It illustrates which questions in a dispute about an agent-initiated payment the elements of this document can help answer. It does not assert that any scheme accepts them.¶
| Question | Element |
|---|---|
| Was this payment the result of a recorded decision, and when? | Claim in the mandate; anchor; holder chain |
| What did the model receive, including transformed media? | Context receipt and released items |
| Which model decided, and with what confidence? | models, answer; attestation at level A3 |
| Has the evidence changed since the decision? | Commitment and receipt digests |
| Were any decisions omitted? | Sequence gaps in the holder chain |
| Was evidence deleted, and when? | Erasure entries |
The author thanks the reviewers of earlier versions of this document.¶