Agent Action Capsule · build your own evidence layer
b2 / 11

The record format

Seam ① Record. Build: one sealed record: digests, the effect, a nonce, a signature over its id. Conform to: draft-mih-scitt-agent-action-capsule-05.

Before you keep a book of records, decide what one record carries: digests of what went in and what came out, the effect it names and who vouches for it, a nonce the store draws itself, and the key that signs the record's identity.

One exchange becomes a signed capsule: bodies hashed, fields filled, identity computed, envelope signed Step 1: two boxes on the left, the request body and the completion body, held by the node. Step 2: each body is hashed with SHA-256 over its JCS form into a digest chip, request_digest and response_digest; a note says the bodies stay on the node. Step 3: the digests flow into a capsule box on the right whose effect record reads type inference_completion, status confirmed, effect_attestation host_served_observed, and the two digests. Step 4: serving facts are added: model_id and model_name_digest, a hash of the model's name; weights_digest, the SHA-256 of the served model file when the host reports it; exchange_id, served_by_node_id and token usage. Step 5: two nonces are added, store_nonce, 256 random bits the store draws for this record alone, and client_nonce, a join key shared by both halves of the exchange; a dashed slot shows a references entry with citation_purpose ran_under citing a hardware-evidence record by digest, marked as specified in the draft with vectors but not sealed by the capsules plugin v0.1.4. Step 6: under the fields, capsule_id is computed as SHA-256 over JCS of the capsule with capsule_id removed. Step 7: a green frame wraps the capsule: a COSE_Sign1 Producer Envelope whose protected header holds alg EdDSA, the content type and kid, the signer's public key, whose payload is the 32 raw bytes of capsule_id, and whose unprotected header is empty; a note on the left says the envelope is stored beside the record as signature and key_id, outside the capsule_id preimage. Request body as received Completion body as returned request_digest 64 hex response_digest 64 hex SHA-256(JCS(body)) bodies stay on the node, never in the record Capsule (JSON payload) effect.type: inference_completion effect.status: confirmed effect_attestation: host_served_observed effect.request_digest: 64 hex effect.response_digest: 64 hex model_id · model_name_digest (of the name) weights_digest: the file, if host reports exchange_id · served_by_node_id · usage store_nonce: 256 bits, the store's own client_nonce: join key of both halves references[]: ran_under, hardware record by digest in draft -05 with vectors; v0.1.4 seals none store_nonce: a guess at a record cannot be checked against its digest capsule_id = SHA-256(JCS(capsule − capsule_id)) recomputed by every verifier COSE_Sign1 Producer Envelope protected: alg EdDSA · content type · kid (public key) payload: capsule_id's 32 raw bytes · unprotected: empty kept beside the record as signature + key_id, outside the capsule_id preimage

1What it is

The record format is the choice of what one record commits to for one action: which facts go in as digests, which go in as plain values, and which never go in at all.

For a model exchange, the Agent Action Capsule Internet-Draft gives the effect type inference_completion, which binds the request and the completion by digest, and a COSE_Sign1 Producer Envelope that signs the record's identity under a key named in its kid.

Mesh-LLM's capsules plugin (release v0.1.4) is the worked example: it fills those fields from the host's exchange event, adds a store nonce, a join nonce and the serving facts, and leaves its hardware-evidence slot empty.

2Design choices

Chosen: commit the request and the completion by digest, SHA-256 over the JCS form of each body, with any floats first written as decimal strings (Draft -05 §2); the bodies stay with the node that saw them.
Rejected: carrying content. A sealed record cannot be retracted, so content that must be checkable later but not disclosed now goes in as a digest only; and a user or session identifier is excluded, not digested, because a digest of a low-entropy value can be reversed by guessing. (Draft -05 §2, §5, §14, §14.1)
Chosen: name the effect. For inference_completion, request_digest covers the request body as received at the serving boundary and response_digest the completion body as returned; status: "confirmed" requires the response digest.
Rejected: a status label with nothing bound to it. When the host forwards no response digest, v0.1.4 seals status: "dispatched" with no response digest rather than claim a completion. (Draft -05 §5.3; capsules crates/evidence-plugin/src/capsule_emit.rs)
Chosen: say who vouches for the effect in effect_attestation: host_served_observed when the plugin only saw the host's report of the exchange, gate_executed when the plugin admitted and served the request itself.
Rejected: grading a host's report as a direct observation. host_served_observed grades equal to runtime_claimed, below gate_executed. (Draft -05 §5.3; both seal paths in capsule_emit.rs)
Chosen: sign the identity, not the JSON: a COSE_Sign1 Producer Envelope over the 32 raw bytes of capsule_id, protected header exactly alg EdDSA, content type application/agent-action-capsule-id and kid (the raw Ed25519 public key); unprotected header empty.
Rejected: making the signature part of the identity, and putting anything in the unprotected header: a witness receipt belongs to a separate registration statement, not the Producer Envelope. Whether the key is authorized for the operator is the caller's policy. (Draft -05 §3.1, §3.2, §3.3)
Chosen: every record carries store_nonce, 256 bits the store draws from a secure random source for that record alone, committed into capsule_id.
Rejected: relying on the client's nonce, a time or a sequence number. Another party knows or can guess those, so the sibling digests in an inclusion proof could be checked against guessed content. (draft-mih-agent-evidence-layer-00 §12.1; capsule-emit 0.0.6 rust/capsule-emit/src/capsule.rs @ 44ad8bd528b029c1abff1937f700423f94e1d6db, tag crates/capsule-emit-v0.0.6)
Chosen: cite hardware evidence by digest: a references entry {type, digest_alg, digest} with citation_purpose: "ran_under". The cited record's grade covers only what that record measured.
Rejected: citing by name or URL, which can later resolve to different bytes; and letting a hardware-measured value lift a self-reported claim inside the same cited record. (Draft -05 §5.5.5; the citation_purpose table and grade rule are printed under §5.5.6)
Chosen: name model facts for what they are: model_name_digest is a hash of the model's name; weights_digest, the SHA-256 of the served file's bytes, is sealed only when the host reports it, with scope: "file". The serving host's name is left out unless the operator opts in.
Rejected: a name hash presented as a model identity, and a default value for any fact the host did not report. (capsules crates/evidence-plugin/src/producer/capsule.rs)

3What it shows / what it does not

Shows

  • That the holder of the key in kid signed this capsule_id, and, once the verifier recomputes the id, that the record's bytes are the ones signed.
  • To anyone who holds the request or completion body: whether it is the body this record committed to.
  • That a confirmed effect carries a digest of the response that was observed, and which grade of voucher the producer states.
  • When the client supplied a nonce or the host forwarded one: which records are the two halves of one exchange, through the shared client_nonce. The marker host-served-no-nonce joins nothing.
  • With store_nonce in every record: the siblings in an inclusion proof cannot be confirmed by guessing a record's content.

Does not show

  • What the request or the completion said.
  • Who holds the key in the real world, or that it was authorized to sign for the operator (Draft -05 §3.2).
  • That the named model ran. model_name_digest hashes a name; weights_digest is the host's report of the file it opened.
  • Anything rooted in hardware. In v0.1.4 the tee_attestation slot is always empty, and binary_attestation is the plugin measuring its own executable (self- or OS-measured); on the host-served path that is not the runtime that served the model.
  • That every exchange was recorded: a record shows this exchange was recorded, not that none was left out.
  • Privacy from client_nonce: it is a join key both sides know, not a secret.

4Example

The format's own conformance vector for an inference_completion exchange, confirmed and attested host_served_observed. A verifier returns ok: true with no findings, and the recomputed capsule_id equals the carried one. The digests are filler values (all 1s, all 2s): the vector tests the record, not real bodies.

{
  "action_id": "v05-inference",
  "action_type": "decide",
  "assurance": {
    "attestation_mode": "self_attested",
    "effect_mode": "confirmed",
    "ledger_mode": "standalone"
  },
  "canonicalization_id": "jcs",
  "capsule_id": "b1f609326c7ba59660b58f813f4fe2ef24e96f7210e3360d62a873b9df90c753",
  …
  "effect": {
    "effect_attestation": "host_served_observed",
    "irreversibility_class": "two_way",
    "request_digest": "1111111111111111111111111111111111111111111111111111111111111111",
    "response_digest": "2222222222222222222222222222222222222222222222222222222222222222",
    "status": "confirmed",
    "type": "inference_completion"
  },
  "format_version": "4",
  …
  "spec_version": "draft-mih-scitt-agent-action-capsule-05",
  "timestamp": "2026-09-26T00:00:00Z"
}
FieldTypeMeaning
capsule_id64 hexSHA-256 over JCS of the record minus this field; recomputed, never trusted.
canonicalization_idstringMust be exactly jcs; checked before any digest is computed.
format_versionstringMust be exactly 4.
effect.typestringRegistered effect type: an inference request whose effect is producing a completion.
effect.statusstringconfirmed: result observed and bound, so response_digest must be present.
effect.effect_attestationstringWho vouches: the serving host's runtime reported the completion.
effect.request_digest64 hexSHA-256 of JCS of the request body as received at the serving boundary, floats first written as decimal strings.
effect.response_digest64 hexSHA-256 of JCS of the completion body as returned.
assuranceobjectClaimed modes; a verifier rederives each from the bytes and reports overclaims.
spec_versionstringThe profile text the record conforms to; never selects an algorithm.

Source: action-state-group/agent-action-capsule:vectors/capsule/pos-v05-effect-inference-completion-host-served/input.json @ 2ef8aeb49e2334e24c404bd62d3fdb161770873c

The hardware citation, from the same repository's vector for ran_under: one references entry, identified by its digest alone (the digest is a filler value).

"references": [
  {
    "citation_purpose": "ran_under",
    "digest": "5555555555555555555555555555555555555555555555555555555555555555",
    "digest_alg": "SHA-256",
    "type": "agent-action-capsule"
  }
]

Source: action-state-group/agent-action-capsule:vectors/capsule/pos-v05-reference-ran-under/input.json @ 2ef8aeb49e2334e24c404bd62d3fdb161770873c

What the Mesh-LLM producer adds, from a record the plugin sealed with its own fixture generator. This record comes from the path where the plugin admitted and served the request itself, so it reads gate_executed; its developer header (trimmed) names an earlier plugin version. For this page its capsule_id was recomputed and its envelope checked against key_id: both hold.

{
  "spec_version": "draft-mih-scitt-agent-action-capsule-05",
  "format_version": "4",
  "capsule_id": "ddd8685c1b578b39c9163a5c748ac7148108663719227f7636b2a113a1dd34dc",
  …
  "model_attestation": {
    "model_id": "m",
    "provider": "mesh-llm",
    "compute_attestation": {
      "agent_input_digest": "6d139f1403cd84518768857efed6c375c0e3fe1af3f250cd3a5c59ea87a0323c",
      "agent_output_digest": "a8461110d26a1ceb91765128a3f8a648e86238810f4bdf27a330deb8681f9d87",
      …
      "x-mesh-poc-v1": {
        "client_nonce": "n",
        "client_nonce_source": "client_supplied",
        "model_name_digest": "62c66a7a5dd70c3146618063c344e531e6d4b59e379808443ce962b3abd63c5a",
        "serving_provenance": {
          …
          "exchange_id": "e-1",
          …
        },
        "role": "served",
        …
        "evidence_refs": {
          …
          "tee_attestation": {
            "type": "tee_attestation",
            "measurement_class": null,
            "digest": null,
            "context": "no TEE quote measured (…); recorded absent, never fabricated"
          },
          …
        }
      },
      "store_nonce": "e372af496774c05c301d3185c83a4a98396b355ca87df2dfb07f8754def62dfe"
    }
  },
  "effect": {
    "status": "confirmed",
    "type": "inference_completion",
    "effect_attestation": "gate_executed",
    "request_digest": "6d139f1403cd84518768857efed6c375c0e3fe1af3f250cd3a5c59ea87a0323c",
    "response_digest": "a8461110d26a1ceb91765128a3f8a648e86238810f4bdf27a330deb8681f9d87"
  },
  …
  "signature": "d284584ca301270378236170706c69636174696f6e2f6167656e742d616374696f6e2d63617073756c652d6964045820…",
  "key_id": "9e4480104975505a70ec6fc913efff2884a93af380a657adf39babf91fbe1fe1"
}
FieldTypeMeaning
agent_input_digest / agent_output_digest64 hexThe same request and completion digests, also kept in the producer's attestation block.
client_noncestringJoin key between the two halves of one exchange. A fixture value here; on the host-observed path it is the nonce the host forwarded, else the marker host-served-no-nonce.
client_nonce_sourcestringWhere the nonce came from (client_supplied, host_forwarded_nonce, …).
model_name_digest64 hexSHA-256 of the model's name string, not of its weights.
serving_provenance.exchange_idstringThe host's per-exchange id, to join this record to the host's own log.
rolestringserved: this node served the exchange (requested on the other half).
evidence_refs.tee_attestationobjectThe hardware-evidence slot: always empty in v0.1.4, with the reason stated.
store_nonce64 hex256 random bits the store drew for this record alone; inside the capsule_id preimage.
effect.effect_attestationstringgate_executed: the plugin admitted and served this request itself.
signaturehexThe COSE_Sign1 Producer Envelope: tag 18, protected header alg -8 (EdDSA), the content type and kid; payload the 32 bytes of capsule_id; empty unprotected header. Outside the id preimage.
key_id64 hexThe signer's raw Ed25519 public key, the same bytes as the envelope's kid. Outside the id preimage.

Source: Mesh-LLM/capsules:vectors/record_push_bundle_rust.json (member capsule) @ b4e24453f572b2d9fe8f2cefbd0f35a5eb54b0ad (tag v0.1.4)

5Status

ItemStatusEvidence
The format: inference_completion, request/response digests, Producer Envelope with kid, references with ran_underlivePublished Internet-Draft -05 and its conformance vectors: action-state-group/agent-action-capsule:spec/draft-mih-scitt-agent-action-capsule-05.md, vectors/capsule/, vectors/producer-envelope/ @ 2ef8aeb49e2334e24c404bd62d3fdb161770873c
One sealed, signed record per exchange half, with digests, store_nonce, signature and key_idliveMesh-LLM/capsules:crates/evidence-plugin/src/capsule_emit.rs @ b4e24453f572b2d9fe8f2cefbd0f35a5eb54b0ad; CHANGELOG.md 0.1.4: four paid exchanges between two nodes built from the release, “each node sealed its own half”
Request and response digests carried on the host's exchange eventbuiltrequest_digest, response_digest in Mesh-LLM/mesh-llm:mesh/crates/mesh-llm-host-runtime/src/plugin/openai_exchange.rs @ 60697c61755b7280c22d3ab9d9bcf5b663e8ae3b (main). First in mesh-llm release v0.77.0 (4ae1ace57dbbe28d0c3d10a05ee542328e8e64e7; set in crates/mesh-llm-host-runtime/src/network/openai/ingress.rs). Without them the plugin seals status: "dispatched" and no response digest.
weights_digest of the served filebuiltSealed only when the host reports it: Mesh-LLM/capsules:crates/evidence-plugin/src/producer/capsule.rs @ b4e24453f572b2d9fe8f2cefbd0f35a5eb54b0ad
Hardware evidence cited by digest (ran_under)designedSpecified in draft -05 §5.5.5 (the citation_purpose table and grade rule are printed under §5.5.6) with vectors (vectors/capsule/pos-v05-reference-ran-under, vectors/interop/ran_under @ 2ef8aeb49e2334e24c404bd62d3fdb161770873c). Not sealed by capsules v0.1.4: its plugin source (crates/) writes no ran_under entry, and its tee_attestation slot is always empty (producer/capsule.rs @ b4e24453f572b2d9fe8f2cefbd0f35a5eb54b0ad).

6Specs

7What you need before the next step