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.
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
mesh/docs/plugins/README.md “Default Plugins”; PR 1841)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."mesh-llm-plugin-key-v1:" ‖ node id ‖ plugin name ‖ key ‖ algorithm); the plugin signs its own records; keys are not relayed.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.twin_bracket_id may simply not have been marked. (PR 2300; README.md “Plugin Keys”, “Peer Routing Blocks”)chat_message, logs_request) where the host passes ids only; a page may ask for a primary tab and the operator decides.operator or plugin:<id>), and a plugin may request it only if the operator set allow_peer_blocks for it.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>,
}
| Field | Type | Meaning |
|---|---|---|
| exchange_id | string | Minted when the host admits the request; shared by the exchange's effective-request and terminal events so a plugin can pair them. |
| dispatch_path | enum | typed_frontend, raw_proxy, or remote_mesh: on remote_mesh this node routed the request to a peer and is not the server. |
| phase | enum | effective_request (before dispatch) or terminal (outcome known). |
| request_digest | hex | SHA-256 over the JCS (RFC 8785) form, with floats as decimal strings, of the request body the host actually dispatched; no prompt text. |
| response_digest | hex | Same construction over the response the host served or relayed. |
| served_by_node_id | string | The node that served: this host on a served terminal; on remote_mesh, the peer this node saw deliver. |
| requested_by_node_id | hex | The mesh node that asked, from the authenticated connection's remote id; absent for a request on the local API. |
| model_identity_hash, weights_digest | string | What the host's served-model descriptor says it loaded; omitted when unknown, never zeroed. |
| capsule_id_provenance | enum | self_minted, or peer_asserted for an id read off a peer's response header, which the host never marks as verified. |
| twin_bracket_id | string | Copied 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.
- live Exchange event with digests and serving provenance (PR 1841, PR 1946, merged), naming both nodes (PR 2138, merged), on the paid serving path too (PR 2109, merged), with
twin_bracket_id(PR 2144, merged). - live A plugin page may ask for a primary tab (PR 2130), can turn off the host's page header (PR 2141), and two contribution slots (PR 2140); all merged.
- live Stop routing: operator-local peer blocks a plugin can request, with
allow_peer_blocksopt-in (PR 2142, merged). - live Default-plugin mechanism (PR 2188, merged): in v0.78.1 it downloads a pinned release on first run; the list is empty.
- built, not live Plugin key bound to the node key,
GET /api/plugin-keys(PR 2299, merged 2026-10-08). - built, not live
capabilities.twin_bracket; header stripped before forwarding (PR 2300, merged 2026-10-11). - built, not live
capsules0.1.4 on the default list, installed only from a release's bundled archive; release archives carry none yet, so it is inert (PR 2337, merged 2026-10-19;mesh/docs/plugins/README.md“Default Plugins”). - released (capsules v0.1.4) On the plugin side,
capsulesv0.1.4 subscribes toopenai.exchange.v1, declares a primary-placement Evidence page and both slots, and asks the host for peer blocks; it reads peers' keys fromGET /api/plugin-keyswhere the host announces them, which its INSTALL.md says has not yet run on a stock release. It does not yet announce its own key. (Mesh-LLM/capsules:crates/evidence-plugin/src/main.rs,plugin.package.json,INSTALL.md@b4e24453f572b2d9fe8f2cefbd0f35a5eb54b0ad, tag v0.1.4)
6Specs
- Internet-Draft draft-mih-scitt-agent-action-capsule-05 — the record a plugin seals from each exchange event (next page).
- Internet-Draft draft-mih-agent-bilateral-attestation-02 — two parties' records of one exchange; why the event must name both nodes.
- RFC RFC 8785 — JSON Canonicalization Scheme: the form under
request_digestandresponse_digest. - Code Mesh-LLM/mesh-llm plugins README — “Plugin Keys”, “Peer Routing Blocks”, “Default Plugins”, the web UI slots.
- Code openai-exchange-lifecycle.md — the exchange event contract, including “Client-marked pairs”.
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:
- An event per exchange (or per tool call, per task) with a stable id, digests of what went in and out, and the ids of both parties, taken from an authenticated channel.
- A way for your plugin's signing key to be vouched for by the host's own identity, and seen by peers.
- Capability flags for each seam, so absence of data is never read as absence of support.
- A page and a slot or two next to the host's own rows, where the host passes ids only.
- A local, operator-controlled “stop sending work to this party” that a plugin can request only with the operator's opt-in.
- A way to ship as one removable, pinned plugin rather than as core code.
With the event in hand, the next question is what a record of it should carry.