The act log by GAIP · the Internet-Draft
The Agent Act Receipt: a signed, chained record of an agent's act
The text of draft-gaip-act-receipt-00 in kramdown-rfc markdown: the entry, the receipt, the COSE
form, the switch pack and GAIP's offer of a running transparency service in the sense of RFC 9943. It describes GAIP's
own format and claims no trust, certification or adoption by anyone. An Internet-Draft is a public working document
under the IETF's Note Well; this one is written for GAIP's founder to file from his own datatracker account.
Download the markdown · Your agents' record ·
The format.
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.
---
title: "The Agent Act Receipt: a signed, chained record of an agent's act"
abbrev: "Agent Act Receipt"
docname: draft-gaip-act-receipt-00
category: info
ipr: trust200902
area: Security
workgroup: Individual Submission
keyword:
- agent
- receipt
- transparency
- COSE
- SCITT
stand_alone: yes
pi:
toc: yes
sortrefs: yes
symrefs: yes
author:
-
ins: T. Graham
name: Toby Graham
organization: GAIP
email: agents@gaipagents.com
uri: https://www.gaipagents.com
normative:
RFC2119:
RFC8032:
RFC8174:
RFC8949:
RFC9052:
RFC9053:
informative:
RFC9943:
I-D.noa-scitt-ai-agent-receipt:
I-D.sahu-agent-action-receipts:
I-D.mih-scitt-agent-action-capsule:
I-D.cowles-ward:
GAIP-FORMAT:
title: "The act log: the format"
target: https://www.gaipagents.com/acts/format
author:
org: GAIP
date: 2026-10
GAIP-KEYS:
title: "GAIP receipt signing keys (gaip.receipt-keys.v1)"
target: https://www.gaipagents.com/.well-known/gaip-receipt-keys.json
author:
org: GAIP
date: 2026-10
GAIP-ANCHORS:
title: "GAIP dated chain-head anchors"
target: https://www.gaipagents.com/v1/free/observatory/anchors
author:
org: GAIP
date: 2026-10
--- abstract
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.
--- middle
# Introduction
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.
## Conventions and Definitions
{::boilerplate bcp14-tagged}
# Terminology
Act:
: Something an agent did, promised, checked or was told, on behalf of a person. The kinds this document uses are
listed in {{kinds}}.
Agent:
: Software acting for a person (its principal), which submits entries to the service.
Continuity handle:
: 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.
Entry:
: One record of one act, in the form of {{entry}}, submitted by an agent and completed by the service.
Receipt:
: The service's Ed25519 signature over an entry's digest, in the JSON form of {{receipt}} and the COSE form of
{{cose}}, together with the entry's place in the hash chain and the anchors that cover it.
Service:
: 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.
Switch pack:
: The signed export of every entry bound to a handle, with the service's public keys and verifier steps, in the form of
{{pack}}.
Keyed hash:
: 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.
# The Entry {#entry}
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.
## Kinds {#kinds}
`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 {{watch}}), never by an agent.
## Fields the agent submits
subject_sha256:
: The SHA-256, as 64 lower-case hex characters, of the agent's own reference for what the act was about. REQUIRED.
content_sha256:
: 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.
counterparty:
: 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`.
mandate_sha256:
: OPTIONAL. The SHA-256 of the mandate the agent acted under.
amount_declared, currency:
: OPTIONAL. A decimal string (digits, at most four decimals) and an ISO 4217 code. Recorded as declared, never
checked against anything.
page_url:
: 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.
cosigner:
: 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.
product_ids, watch:
: OPTIONAL, `BOUGHT` only. Product identifiers (GTIN, model number or product URL) for the recall watch
({{watch}}); 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.
## Fields the service adds
schema_version, service, evidence_class, brand:
: `gaip.act.v1`, `ACT_LOG`, `ACT`, and the service's name.
actor_handle_hash:
: 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.
recorded_at_utc, observed_at_utc:
: When the service recorded the submission (ISO 8601, UTC).
submitted_by:
: `AGENT` for an agent's submission; `GAIP` (the service's own name) for an entry the service wrote itself.
page_read:
: 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}`.
sentence, what_this_is:
: 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."
published, data_classification, content_retained, page_content_retained:
: `false`, `PSEUDONYMOUS`, `false`, `false`.
correction_route, price_gbp, affiliate_links, external_effect, outside_write:
: The service's corrections address; `0`; `false`; `false`; whether the submission came from outside the service's
own callers.
receipt_id, observation_id, entry_sha256, signature, signature_status:
: See {{receipt}}. These sit beside the signed body and are not part of the digest.
# The Receipt {#receipt}
## The digest
`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`.
## The signature
`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`.
## The hash chain
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.
## Daily anchors
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.
# The COSE Form {#cose}
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: the 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 {#pack}
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.
# Relationship to SCITT {#scitt}
{{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.
# Security Considerations
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.
# Privacy Considerations
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.
# IANA Considerations
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.
--- back
# A worked example {#example}
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.
# Acknowledgements
{:numbered="false"}
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.