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

The seams the host exposes

Seam ① Record. Build: the host hooks a recorder needs: an exchange event naming both sides, a bound plugin key, capability flags, a page, stop routing. Conform to: draft-mih-scitt-agent-action-capsule-05 for the record those events feed, draft-mih-agent-bilateral-attestation-02 for the two sides.

Before an evidence plugin can do its job, the host your agents run on has to hand it a few small, generic things: an event per exchange that names both sides, a way to vouch for the plugin's signing key, flags saying what it supports, a place on screen, and a way to stop routing to a peer.

Five seams on a lean host core, and one evidence plugin attached to them A large box on the left is the Mesh-LLM host core, which seals no records, listing routing and peers, chat and console, logs, payment events and the plugin host. On its right edge are five sockets that light one at a time. Socket 1, green: exchange event naming both nodes, openai.exchange.v1, pull requests 1841, 1946 and 2138, in release v0.78.1. Socket 2, amber dashed: plugin key bound to the node key, pull request 2299, on main but not released. Socket 3, amber dashed: capability flags, plugin_keys.v1 from pull request 2299 and twin_bracket from pull request 2300 on main, and peer_blocks.v1 from pull request 2142, which is in release v0.78.1. Socket 4, green: a plugin page and two slots, pull requests 2130 and 2140. Socket 5, green: stop routing to a peer, peer blocks, pull request 2142, operator opt-in for a plugin. In the last step a box on the right, the evidence plugin, connects to all five sockets (on socket 2 it reads peers' keys but does not yet announce its own); a note says it is one pinned default plugin, pull requests 2188 and 2337, on main and inert until a release bundles it. A legend at the bottom: green means in release v0.78.1, amber dashed means merged to main, not yet in a release. Host core Mesh-LLM, maintained upstream Routing and peers Chat and console Logs Payment events Plugin host core seals no records; only generic hooks 1 exchange event, both node ids openai.exchange.v1 · #1841 #1946 #2138 2 plugin key bound to node key plugin_keys · #2299 3 capability flags #2299 #2300 · peer_blocks.v1 (v0.78.1) 4 a page and two slots #2130 #2140 #2141 5 stop routing to a peer peer blocks #2142 · opt-in Evidence plugin capsules 0.1.4 seals a record per event checks peers' keys reads what host supports Evidence page, chips opt-in stop-routing rule one pinned default plugin #2188 #2337 in mesh-llm release v0.78.1 merged to mesh-llm main, not yet in a release

1What it is

A seam is a small, generic hook the host exposes so that a separate plugin, not the host itself, can keep evidence about what the host did.

An evidence plugin needs the first of them to seal a single record: an event per exchange that names both nodes. The other four let it check peers' records (a key its peers can check), tell what the host supports (flags), show records (a page and slots), and act on what it finds (stop routing to a peer).

If you build agents on someone else's runtime, these are the things to ask that runtime's maintainers for; Mesh-LLM is the worked example, where each seam landed upstream through its own pull requests and the evidence code stayed outside core.

2Design choices

Chosen: the exchange event carries digests and node ids, not the prompt or answer; the default plugin's entry does not allow the exchange hook that can see text.
Rejected: handing an audit plugin the conversation. (mesh-llm mesh/docs/plugins/README.md “Default Plugins”; PR 1841)
Chosen: requested_by_node_id comes from the authenticated mesh connection the request arrived on, so a client cannot set it; a routing node names only the peer it saw deliver.
Rejected: a header or body field the requester fills in, and naming hardware or model details for a peer the node never touched. (PR 2138)
Chosen: the node key signs only a binding ("mesh-llm-plugin-key-v1:" ‖ node id ‖ plugin name ‖ key ‖ algorithm); the plugin signs its own records; keys are not relayed.
Rejected: operators copying each peer's key by hand, and the node key signing plugin data. (PR 2299)
Chosen: the host advertises each seam as a named capability (plugin_keys.v1, peer_blocks.v1, capabilities.twin_bracket); a plugin call against a host without plugin_keys.v1 or peer_blocks.v1 fails at once.
Rejected: inferring support from an absent field: an exchange without twin_bracket_id may simply not have been marked. (PR 2300; README.md “Plugin Keys”, “Peer Routing Blocks”)
Chosen: exactly two named slots (chat_message, logs_request) where the host passes ids only; a page may ask for a primary tab and the operator decides.
Rejected: a general extension-point system, and a plugin promoting itself into the main navigation. (PRs 2140, 2130)
Chosen: stop routing is local to one node, names who asked (operator or plugin:<id>), and a plugin may request it only if the operator set allow_peer_blocks for it.
Rejected: a shared blocklist, which would let any peer get an honest one excluded with made-up reports, and any ranking kept by the host. (PR 2142)
Chosen: evidence ships as one plugin, pinned by version and per-platform SHA-256 on the host's default list, which the operator can turn off.
Rejected: compiling evidence into core, and installing a default by download (PR 2337 replaced the download-on-first-run install of PR 2188).

3What it shows / what it does not

Shows

  • Which node asked and which node served one exchange, as each node observed it, with digests of the request and response, so the two sides' records of one exchange can be matched.
  • That a peer's node key vouched for a given plugin key (where the host lists it).
  • Whether a host supports a seam at all, from its own capability flags.
  • Who stopped routing to a peer, and why, as a change published to local plugins.

Does not show

  • That the serving node ran the model it claims: node ids and digests are the host's observations, used to pair records, not evidence of what ran.
  • Anything about honest serving from the key binding: it says only that the node key vouched for the plugin key (PR 2299).
  • Keys of peers you are not directly connected to: plugin keys are not relayed. A stable identity for client-only nodes is not addressed (PR 2299).
  • Anything to other nodes from a block: it stays on this node and warns no one.
  • Any record at all: the seams carry events; sealing them is the plugin's job (next page).

4Example

The event a Mesh-LLM host publishes on openai.exchange.v1 for each exchange, as its Rust type serializes it (doc comments and some fields trimmed). Every field is optional except the first four; the host leaves out what it did not observe rather than filling it in.

pub struct OpenAiExchangeEnvelope {
    pub exchange_id: String,
    pub dispatch_path: OpenAiExchangeDispatchPath,
    pub phase: OpenAiExchangePhase,
    pub model: String,
    pub status: Option<u16>,
    pub capsule_id: Option<String>,
    pub capsule_id_provenance: Option<CapsuleIdProvenance>,
    pub nonce: Option<String>,
    pub nonce_source: Option<ClientNonceSource>,
    pub serving_provenance: Option<ServingProvenance>,
    pub usage: Option<ExchangeUsage>,
    pub request_digest: Option<String>,
    pub response_digest: Option<String>,
    pub tool_calls_digest: Option<String>,
    pub reasoning_digest: Option<String>,
    pub twin_bracket_id: Option<String>,
}

pub struct ServingProvenance {
    pub served_by_node_id: String,
    …
    pub model_identity_hash: Option<String>,
    …
    pub weights_digest: Option<String>,
    …
    pub requested_by_node_id: Option<String>,
}
FieldTypeMeaning
exchange_idstringMinted when the host admits the request; shared by the exchange's effective-request and terminal events so a plugin can pair them.
dispatch_pathenumtyped_frontend, raw_proxy, or remote_mesh: on remote_mesh this node routed the request to a peer and is not the server.
phaseenumeffective_request (before dispatch) or terminal (outcome known).
request_digesthexSHA-256 over the JCS (RFC 8785) form, with floats as decimal strings, of the request body the host actually dispatched; no prompt text.
response_digesthexSame construction over the response the host served or relayed.
served_by_node_idstringThe node that served: this host on a served terminal; on remote_mesh, the peer this node saw deliver.
requested_by_node_idhexThe mesh node that asked, from the authenticated connection's remote id; absent for a request on the local API.
model_identity_hash, weights_digeststringWhat the host's served-model descriptor says it loaded; omitted when unknown, never zeroed.
capsule_id_provenanceenumself_minted, or peer_asserted for an id read off a peer's response header, which the host never marks as verified.
twin_bracket_idstringCopied unread from a client's x-mesh-twin-bracket header; the host never sends a second request itself.

Source: Mesh-LLM/mesh-llm:mesh/crates/mesh-llm-host-runtime/src/plugin/openai_exchange.rs @ 60697c61755b7280c22d3ab9d9bcf5b663e8ae3b (main, 2026-10-19)

5Status

“Live” here means in Mesh-LLM's latest release, v0.78.1 (bc215ce5b880e21c6c3840b8574ab64f83278821); “built, not live” means merged to main @ 60697c61755b7280c22d3ab9d9bcf5b663e8ae3b but not in a release yet.

6Specs

7What you need before the next step

Write down the seams you will ask your own host for. A builder on any agent runtime can use this list:

With the event in hand, the next question is what a record of it should carry.