| Internet-Draft | Agent Act Receipt | October 2026 |
| Graham | Expires 21 April 2027 | [Page] |
Software agents act for people: they buy, book, cancel, agree, deliver, refuse, tell, ask, read and check. Each payment rail, marketplace and assistant keeps its own record of those acts, held by that party. The person the agent acted for has no record of their own. This document describes the Agent Act Receipt: one entry per act, written by the agent as digests bound to a continuity handle, signed by a running transparency service, placed in a hash chain and covered by dated anchors, with a COSE_Sign1 form of the signature and a signed export (the switch pack) the person can take to another agent. The format is published by GAIP, which runs one such service. This document makes no claim of trust, certification or adoption: a receipt shows that the service's key signed a digest at a stated time, and nothing about the act itself.¶
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 21 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.¶
Every rail that carried an agent's act in 2026 keeps a record of it with a party: the payment network keeps the authorisation, the marketplace keeps the order, the assistant's operator keeps the conversation, and some commerce protocols tell platforms to discard order data once the transaction is complete. No rail keeps the record for the person the agent acted for, and no law requires anyone to. When the person changes assistant, disputes a purchase, or asks what their agents promised in their name last month, the record is scattered across parties who each hold one piece and owe the person nothing.¶
The Agent Act Receipt is a small, neutral record kept on the person's side. An agent writes one entry per act, as digests: what the act was about and what its content was, each hashed by the agent before anything leaves it. The entry is bound to the agent's continuity handle by a keyed hash, so the person's agents can read the record back and nobody else can. A running transparency service signs the entry, appends it to a hash chain and publishes dated anchors over the chain heads. The service never receives the content of the act and never says whether the act happened as described; it records that an agent submitted the entry at the stated time.¶
Three views make the record useful: the entries bound to a handle, read back by that handle; a signed export of the whole record with the service's public keys, which the person can take to another agent ("the switch pack"); and a COSE_Sign1 [RFC9052] form of each entry's signature, so a verifier that reads COSE can check an entry without the service's JSON.¶
This document describes the format GAIP publishes and runs. It is an individual submission. It does not define a standard, assert conformance of any party, or claim that any assistant, rail or regulator reads or accepts these receipts. The words "transparency service" are used in the sense of [RFC9943] only: GAIP operates one instance of such a service for this format and offers it as a running instance for anyone to use or check.¶
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.¶
Something an agent did, promised, checked or was told, on behalf of a person. The kinds this document uses are listed in Section 3.1.¶
Software acting for a person (its principal), which submits entries to the service.¶
An opaque, bearer pair (an identifier and a secret token) a service issues to an agent on a first completed answer, which the agent presents on later calls so the service can link them. The service stores only a hash of the token. A handle names an agent session, not a person.¶
One record of one act, in the form of Section 3, submitted by an agent and completed by the service.¶
The service's Ed25519 signature over an entry's digest, in the JSON form of Section 5 and the COSE form of Section 6, together with the entry's place in the hash chain and the anchors that cover it.¶
The transparency service (in the sense of [RFC9943]) that validates, signs, chains and anchors entries and serves them back to the handle that bound them. GAIP runs one.¶
The signed export of every entry bound to a handle, with the service's public keys and verifier steps, in the form of Section 7.¶
HMAC-SHA256 under a secret the service holds, with a fixed label, over a value the service must be able to recognise again (a handle, a product identifier, an opaque reference) without keeping the value itself.¶
An entry is a JSON object. Its schema is named by the schema_version member, gaip.act.v1. The fields below are
those the GAIP service stores; a service implementing this format MUST NOT add a field that carries the content of the
act or a person's identifier in clear.¶
kind is one of a closed list: BOUGHT, BOOKED, CANCELLED, SUBSCRIBED, PAID_DECLARED, AGREED,
DELIVERED, REFUSED, TOLD, ASKED, READ, CHECKED, REVOKED, NOTICE_MATCHED. A submission with any other
kind MUST be refused. NOTICE_MATCHED is written by the service itself (see Section 4), never by an agent.¶
The SHA-256, as 64 lower-case hex characters, of the agent's own reference for what the act was about. REQUIRED.¶
The SHA-256, as 64 lower-case hex characters, of the content of the act (the order, the message, the terms).
REQUIRED. The content itself MUST NOT be accepted: a submission carrying a content field is refused.¶
OPTIONAL. One of {"handle_hash": <64 hex>} (another agent's handle, by its keyed hash), {"host": <public host
name>} or {"ref": <opaque text>}. A ref is stored only as its keyed hash under the label
gaip.act.counterparty.hmac.v1.¶
OPTIONAL. The SHA-256 of the mandate the agent acted under.¶
OPTIONAL. A decimal string (digits, at most four decimals) and an ISO 4217 code. Recorded as declared, never checked against anything.¶
OPTIONAL. A public product page the service reads once, at submission, through a reader with fixed refusals (no
cart, checkout or account paths; marketplaces the service's rules name are refused; robots.txt is honoured). The
service records what the page showed (see page_read below), never the page.¶
OPTIONAL. {"key_id": ..., "signature": <over entry_sha256>, "host": <optional>}. Recorded as presented, with
key_state KEY_KNOWN when the service's record of published keys lists that key id for the host, else
KEY_NOT_KNOWN. The service does not check the cosigner's signature and says nothing about what two signatures mean
between the parties.¶
OPTIONAL, BOUGHT only. Product identifiers (GTIN, model number or product URL) for the recall watch
(Section 4); the service keeps only their keyed hashes under the label gaip.act.product_id.hmac.v1, and the entry
records only their count.¶
Any field outside this list MUST be refused, so that no plaintext about a person can arrive by another name.¶
gaip.act.v1, ACT_LOG, ACT, and the service's name.¶
The keyed hash (label gaip.act.handle_hash.hmac.v1) of the continuity handle that bound the entry. The handle
itself is never stored in the entry.¶
When the service recorded the submission (ISO 8601, UTC).¶
AGENT for an agent's submission; GAIP (the service's own name) for an entry the service wrote itself.¶
Present when page_url was given: {url, seller_domain, read_at_utc, read_status, values {name, price, currency,
availability, variant}, sha256, sources_read, page_content_retained: false}.¶
A fixed sentence the service places on every entry, answer, page and export, never reworded. GAIP's is: "GAIP records that an agent submitted this entry at the stated time, and, where shown, what a public page showed when GAIP read it. GAIP did not witness the act and does not say it happened as described."¶
false, PSEUDONYMOUS, false, false.¶
The service's corrections address; 0; false; false; whether the submission came from outside the service's
own callers.¶
See Section 5. These sit beside the signed body and are not part of the digest.¶
A BOUGHT entry that carries product_ids with watch set to true asks the service to watch for recall notices
about those products. The service keeps only keyed hashes of the identifiers and a bounded set of words from the
product's name. Once a day it reads the notices that public regulators have published since its last run (in GAIP's
deployment, the GOV.UK product safety alerts and the US Consumer Product Safety Commission recalls) and, when a
notice's identifiers match a watched hash, appends a NOTICE_MATCHED entry to that handle's log carrying the
notice's own title, date and link. A match on name words alone is recorded as a possible match, for the person to
check. The service states only which notice matched and when, quoting the regulator's own words; it MUST NOT
add any judgement of its own about the product.¶
entry_sha256 is the SHA-256, as 64 lower-case hex characters, over the canonical JSON of the entry without the
fields receipt_id, observation_id, entry_sha256, signature, signature_status, cosigner,
handle_hash_retention, content_retention and erasure. Canonical JSON here means: keys sorted, separators ,
and : with no spaces, UTF-8. receipt_id is the string ac- followed by the first twelve characters of the
lower-case base32 encoding of entry_sha256.¶
signature is {alg: "Ed25519", kid, sig, purpose, signed_message}: an Ed25519 [RFC8032] signature, base64url
without padding, over the ASCII bytes of <purpose>:<entry_sha256> where purpose is gaip.act.entry_sha256.v1.
kid is the first sixteen lower-case hex characters of SHA-256 over the raw 32-byte public key. The service publishes
its public keys as a JWK set [GAIP-KEYS] (kty OKP, crv Ed25519, x, alg EdDSA, a status, and for a retired
key when and why). A verifier recomputes entry_sha256, rebuilds the message and checks sig with the key that carries the same
kid.¶
Each signed entry is appended to the service's hash chain as a record {previous_record_sha256, observation} whose
record_sha256 is the SHA-256 of its canonical JSON; observation is the entry. The record is served by its
receipt_id, so anyone can check that the entry sits where the chain says it does. A retention rule may later replace
the entry's content with a tombstone that keeps digests and signatures; an erasure tombstone keeps signatures and key
references and no digest of the content. In both cases previous_record_sha256 and record_sha256 are unchanged, so
the chain still verifies.¶
Once a day the service signs a document holding its chain heads through a public transparency log and timestamps it, and lists the results [GAIP-ANCHORS]. An anchor shows that the listed chain-head digests existed no later than the anchor's own entries; it shows nothing about any act.¶
Each entry's signature is also served as a COSE_Sign1 [RFC9052] so a verifier that reads COSE need not read the
service's JSON. GAIP serves it at GET /v1/free/acts/<receipt_id>/receipt.cose with media type
application/cose; cose-type="cose-sign1".¶
The structure is the tagged COSE_Sign1, tag 18:¶
18([
protected, / bstr: the serialised protected header /
{}, / unprotected header: an empty map /
payload, / bstr: the 64 ASCII hex characters of entry_sha256 /
signature / bstr: 64 bytes, Ed25519 /
])
¶
The protected header is the CBOR [RFC8949] map, in the core deterministic encoding (shortest integers, definite lengths, map keys ordered by their encoded bytes):¶
{
1: -8, / alg: EdDSA, RFC 9053 /
3: "text/plain; charset=utf-8", / content type of the payload /
4: kid / bstr: key id as UTF-8 bytes /
}
¶
alg is EdDSA [RFC9053]. kid is the same key id as the JSON signature's. The payload is the 64 ASCII hex characters
of entry_sha256, not the entry. The signature is Ed25519 over the Sig_structure of [RFC9052] section 4.4,
["Signature1", protected, external_aad, payload], with empty external_aad. It is a second encoding of the same key
over the same digest; the two signatures differ in their signed bytes only because the COSE form signs the
Sig_structure and the JSON form signs <purpose>:<digest>.¶
When the service's signing key is not available, the route answers JSON with status NOT_SIGNED and the fixed
sentence instead of bytes; it never serves an unsigned COSE object.¶
The switch pack is the service's own export of every entry bound to one handle, served only to that handle (a
ticketed read: the request presents the handle, the service verifies it, and a request without one is refused). Its
schema is gaip.switch-pack.v1. It holds:¶
continuity_ref: a one-way reference to the handle, never the handle or its token;¶
entries[]: every entry bound to the handle, newest first: entries of this format with in_chain: true and their
record_sha256, and re_emitted rows, each a view of an earlier receipt the service holds for the same handle, as an
act of its kind, naming source_kind, source_receipt and source_url;¶
counts: entries_total, in_chain, re_emitted, by_kind;¶
keys: the service's public keys as its key set publishes them, with the key id and message rules;¶
verifier: the steps to check the pack, each entry, the COSE form and the chain, and the address of a standalone
verifier;¶
the fixed sentence, what a pack is, and a portability note stating that the pack is the service's own holdings only and binds no other provider;¶
pack_sha256: SHA-256 over the canonical JSON of the pack without its signature fields and without the per-call
fields a runtime adds after signing (the pack names them in hash_rule);¶
signature: Ed25519 over gaip.switch_pack.pack_sha256.v1:<pack_sha256>, with the same key set.¶
The pack is an open, signed JSON document that any agent can read and check offline against the published keys. This document does not say what any other service does with it.¶
[RFC9943] describes an architecture in which Issuers make Signed Statements about Artifacts, a Transparency Service registers them in a verifiable data structure and returns Receipts, and a Transparent Statement is a Signed Statement augmented with its Receipt. In those terms:¶
the agent is an Issuer, and the entry's digests are its Statement about the act (the Artifact is the act's content, which never reaches the service);¶
GAIP's act log is a Transparency Service in the sense that it maintains and extends an append-only hash chain over registered entries, signs each one, and publishes dated anchors over the chain heads;¶
the service's signature in its COSE form, with the entry's chain record and the anchors that cover it, plays the part of the Receipt; an entry served with them is the transparent form of the agent's statement.¶
The correspondence is descriptive. This document does not claim conformance to [RFC9943] or to any SCITT profile, and the receipt format here is not the SCITT Receipt structure; a future revision may describe a mapping. Related individual submissions describe action receipts for AI agents in SCITT or hash-chained forms ([I-D.noa-scitt-ai-agent-receipt], [I-D.sahu-agent-action-receipts], [I-D.mih-scitt-agent-action-capsule], [I-D.cowles-ward]); this document is set apart by keeping the record on the person's side, bound to a continuity handle and exportable as one signed pack. GAIP offers its act log as a running instance of this format, free of charge, without an account, for anyone to write to (once its operator's data protection assessment is complete) and for anyone to check. It makes no claim that any party trusts, certifies or has adopted it.¶
The receipt shows that the service's key signed entry_sha256 at some time, and the chain and anchors show when,
within the anchors' resolution. It does not show that the act happened, that the digests stand for what the agent says
they do, or that any party did anything: an entry is the submission as recorded. A reader MUST treat amount_declared,
counterparty and every agent-supplied digest as the agent's statement.¶
Digests are the agent's: a service cannot tell a digest of real content from a digest of anything else. Two signers on
one digest (cosigner) show that two keys signed the same digest at the stated times; the service records the second
signature as presented and does not check it. Whether that made an agreement is between the parties.¶
The service's private key MUST be held only in the service's runtime and left out of backups; a compromise or rotation
is announced through the key set (a retired key keeps its kid, with when and why). Verifiers SHOULD check the key's
status in the key set, not only its presence.¶
The chain is append-only; a tombstone changes an entry's content and never its chain record, so a reader that sees
CONTENT_DELETED or ERASED_ON_REQUEST SHOULD treat the row as a placeholder whose link still verifies.¶
Submissions and reads are rate-limited per caller, and writes require a handle. A handle is a bearer secret: whoever holds it can write to and read the record it binds. Handles SHOULD be kept as any credential is.¶
The service holds hashes and public page values only. It never receives the content of an act; a content field is
refused. A person's identifier never reaches it: the continuity handle names an agent session, and the entry holds only
a keyed hash of it (actor_handle_hash), under a label and a secret the service holds, so the hash cannot be recomputed
by anyone else from a known handle. An opaque counterparty reference and product identifiers are kept as keyed hashes
in the same way, and a ref or identifier for which no key is available is refused rather than stored in clear.¶
The record is served to the handle that bound it and to nobody else: the entries, the switch pack and the dispute
export answer only a request that presents that handle (or, for the export, the cosigner's own key). One entry is
public by its code as hashes and public page values; it names no handle in clear, but its actor_handle_hash is the
same on every entry of that handle, so holders of several codes can link them.¶
The entry's content is kept for a fixed period, then replaced by digests and signatures; the handle's keyed hash is kept for that period and dropped with the content, and is removed earlier by an erasure tombstone on request. The service's operator publishes a data protection assessment of this processing before accepting outside entries and describes it on its privacy page.¶
A page read records what a public product page showed, never the page, and never a cart, checkout or account page.¶
This document has no IANA actions. A later revision may request registration of the media type parameters and the CBOR tags it uses, if any beyond those already registered.¶
The format page [GAIP-FORMAT] carries a synthetic, unsigned example entry and the diagnostic notation of the protected header. A signed sample made under a test-only key is published with the service's tests; it verifies under that key and under no other.¶
The problem this document names, that every rail keeps its records with a party and the person has none, was drawn from the published specifications of the 2026 agent payment and commerce rails and from the authors of the related individual submissions listed above. GAIP's Legal desk record of 10 October 2026 fixed the sentence and the words this format never uses.¶