Where it fits
Static agent facts describe who an agent is and what it is capable of — a claim about identity and capability that holds over time. Receipts describe what an agent did — signed after the fact, once an action is complete. The Attested Action Envelope sits between them: it records a decision taken about a specific action, at a moment in time, before the action runs.
Static agent facts say what an agent is. Receipts say what an agent did. The envelope says what an agent may do, and preserves the refusals. Together the three give a system three tenses of accountable evidence — capability, decision, and history — each independently verifiable, none requiring trust in the party that produced it. In the agent-evidence family the envelope sits alongside receipts (which record what an agent did, after the fact) as the record of what an agent may do, decided before the fact.
The eight fields
An envelope is a JSON object with exactly these eight members. A document with any additional
member, any missing member, a malformed action, or an outcome outside the
closed set is not an envelope and MUST fail verification.
| Field | Type | Requirement |
|---|---|---|
| agent_id | string, non-empty | REQUIRED |
| action | object {verb, resource, params} | REQUIRED; exactly these three members |
| policy_id | string | REQUIRED |
| outcome | string, one of authorized | denied | conditional | REQUIRED |
| prev_hash | string (64-char lowercase hex SHA-256) or null | REQUIRED; null only for an agent's first envelope |
| issued_at | string, RFC 3339 timestamp | REQUIRED; supplied by the issuer, validated by parsing |
| sig | string (128-char hex Ed25519 signature) | REQUIRED |
| pubkey | string (64-char hex, raw 32-byte Ed25519 public key) | REQUIRED |
Canonicalization
The signing input is the JSON serialization of the envelope with the sig member
removed, using: keys sorted lexicographically at every level; the compact separators
"," and ":" (no insignificant whitespace); UTF-8 encoding.
params values must be JSON-serializable. A conformant implementation produces
byte-identical canonical bytes for equal envelopes — that determinism is what lets a second,
independent implementation of the eight-field format verify sm-aae's envelopes, and vice versa.
Signature
sig is the Ed25519 signature (RFC 8032) over the canonical bytes, hex-encoded.
pubkey is the raw 32-byte Ed25519 public key, hex-encoded. A verifier recomputes the
canonical bytes and checks sig under pubkey; any mismatch yields failure,
and hostile input never raises. verify_envelope is the reference implementation's
single verification entry point — structural plus signature check, returning a plain boolean.
The envelope hash and the chain
The envelope hash is the lowercase-hex SHA-256 (FIPS 180-4) of the JSON serialization of the
full envelope — including sig — under the same key ordering and
separators. A successor envelope's prev_hash must equal the envelope hash of the
agent's immediately preceding envelope. Because the hash covers sig, the chain commits
to the signed artifacts themselves: swapping a signature anywhere breaks every later link.
envelope {
agent_id
action: { verb, resource, params }
policy_id (the authority the verdict cites)
outcome: authorized | denied | conditional
prev_hash (SHA-256 of this agent's prior envelope, or null)
issued_at (RFC 3339, caller-supplied)
sig (Ed25519 over the canonical body, sig removed)
pubkey (the signer's raw Ed25519 public key)
}
Verifying a chain
A verifier of an agent's chain — an unordered set of that agent's envelopes — recovers the unique
genesis-first ordering, or rejects the set. The reference implementation's order_chain
function returns that ordering, or None if the set does not form exactly one intact
chain. It rejects:
- a gap — a
prev_hashnaming an envelope not present in the set, i.e. a deleted or withheld interior decision; - a fork — two envelopes claiming the same predecessor;
- a foreign-chain splice — a
prev_hashreaching into another agent's chain (more than oneagent_idpresent); - duplicates, or zero/multiple genesis envelopes (more or less than exactly one envelope with
prev_hash = null).
An agent cannot quietly drop the one decision it would rather not have on record — doing so breaks the chain's reconstruction, and the break itself is the signal.
What it does not do
Signs the verdict, not the judgment
sm-aae signs whatever outcome and policy_id the caller supplies. It does not evaluate policy and cannot attest that the verdict was the right one — only that it was decided, by the holder of pubkey, at issued_at, and recorded immutably.
Chain integrity is by hash, not by key
An envelope self-verifies against its own embedded pubkey; order_chain does not check that an agent used the same key throughout. Catching an agent that re-signs its latest envelope under a fresh key requires an external "expected key for this agent" check — an identity layer's responsibility.
A record of a decision, not a clock
Presenting a valid envelope again is not forgery; consumers that derive side effects from envelopes must track which they have already acted on. issued_at is validated as well-formed RFC 3339 but is caller-asserted — sm-aae does not provide a trusted clock.
Out of the primitive's scope
Generation, storage, rotation, and revocation of the signing key, and the issuance of agent identities, are out of scope — the same boundary GOVERNANCE.md draws around the primitive: it owns the envelope, its canonicalization, and chain verification, nothing else.
Out of scope. Whether an action was correct or wise, and whether it actually happened, are both outside the envelope. The first is a policy question the caller owns; the second is the occurrence half of accountability, covered by agency receipts — see agencyreceipts.ai.