The local evidence log
Seam ① Record. Build: an append-only local log with signed checkpoints and inclusion proofs. Conform to: draft-mih-scitt-checkpointed-local-log-02 (reference implementation: checkpointed-local-log; any conforming log).
Once you can seal a record, keep every one you seal in an append-only log on your own disk, sign a small checkpoint over it every N records or T minutes, and hand out a record with its inclusion proof and checkpoint so anyone can check it offline.
1What it is
A local evidence log is a producer's own append-only log of the records it seals: each record's id goes in as a leaf of a Merkle Mountain Range (MMR) on the producer's disk, and nothing leaves unless the producer sends it.
Every N records or T minutes the producer signs a small checkpoint over the whole log. Any record can then be shown to sit at a position under that checkpoint (an inclusion proof), and any later checkpoint can be shown to extend it (a consistency proof).
What you export is one record plus its inclusion proof plus the checkpoint: a file or a push that another party checks offline against the producer's key. The producer can also register its checkpoints with a transparency service it does not run.
The reference implementation is checkpointed-local-log (CLL), in Python, Go, TypeScript and Rust. Any log that meets the CLL draft's checks is acceptable: a verifier checks the proofs and the signed checkpoint, not which library produced them.
Where the library sits today
The Mesh-LLM plugin is a worked example with four layers, each in its own repository. They are listed bottom-up, with the versions pinned in the plugin's v0.1.4 Cargo.lock and the current release of each.
| Layer | Package | What it does |
|---|---|---|
| log | checkpointed-local-log | The MMR, leaf and node hashing, signed checkpoints, and inclusion and consistency proofs, in Python, Go, TypeScript and Rust. v0.1.4 pins crate 0.2.1; the latest crate tag is 0.3.1. (action-state-group/checkpointed-local-log) |
| book | any conforming book | Record semantics over a log it embeds and never exposes: append, query, bundle, request and respond. The Evidence Layer draft (§8) says what a book must supply and names no implementation. v0.1.4 has no separate book layer of its own; capsule-emit 0.0.6 supplies the log wrapper, canonicalization and padding records. |
| producer | capsule-emit | Seals and signs each capsule, writes capsules.jsonl, and decides when to cut a checkpoint and where to send it. v0.1.4 pins crate 0.0.6. (action-state-group/capsule-emit) |
| host plugin | capsules | The timer that drives the checkpoints, push to the other side, receipt of pushed records, and the Evidence page. (Mesh-LLM/capsules v0.1.4) |
That layering is moving. capsule-emit's main already builds its checkpoints on checkpointed-local-log directly (capsule-emit#326, merged); the plugin still pins capsule-emit 0.0.6. The checkpoint timer stays in the plugin. Section 5 gives the status of each piece.
2Design choices
capsule_id), and its leaf value is SHA-256(0x00 ‖ entry).checkpoint_cadence.rs)CAPSULES_CHECKPOINT_PAD_BUCKET)CAPSULES_CHECKPOINT_WITNESS_URLS), and an unreachable service never blocks local checkpointing.checkpoint_cadence.rs, CAPSULES_CHECKPOINT_WITNESS_URLS; CLL §4.4 item 3)3What it shows / what it does not
Shows
- The producer signed every checkpoint and chose what to present, so the next three points hold only as far as the verifier trusts the producer (CLL §5.3):
- A record with a given
capsule_idis in the log at a given position under a checkpoint the producer's key signed (an inclusion proof, as in the example below). - A later checkpoint extends an earlier one without rewriting it (a consistency proof, CLL §4.3 step 8).
- A presented range of records is complete under one checkpoint (CLL §6).
- Once checkpoints are registered under a Consistency Registration Policy at a service independent of the producer, each record existed no later than the first registered checkpoint covering it, and the registered history was not rewritten (CLL §7).
Does not show
- That any record's content is true (CLL §7).
- That the producer appended everything it did, or that this is its only log (CLL §7).
- When a record was made, if no checkpoint was registered. The checkpoint's time is the producer's own claim.
- Anything beyond the producer's own word for records appended after the last registered checkpoint (CLL §4.4 item 4).
- The exact number of records: padding hides it only to within the bucket size, and checkpoint times still show when the producer is active (Evidence Layer -00 §12.1).
- In v0.1.4, a gap in checkpoints as something a verifier can detect. The plugin cuts no checkpoint while nothing new is appended. The -02 draft asks a producer that publishes checkpoints to declare a cadence and keep to it even when idle (CLL §4.4 item 1).
4Example
This is a bundle as the plugin pushes it to the other side of an exchange: a served half, its inclusion proof, and the checkpoint that proof resolves against. It comes from the plugin's record-push test corpus. The corpus was built from public material, its keys were discarded, and the request and response digests are placeholders. The test's expected answer is that the receiver keeps the half with status received. A copy whose root was changed after signing gets a signed refusal, inclusion_unverified.
{
"capsule": {
"action_id": "serve_exchange/c79b9e3c-75b1-4ddf-b917-d38da457a94b",
…
"capsule_id": "d804c0bfc4df59def6edce207cefea1d6e4656792da7a1aeb3586017f2c977d5",
"effect": { … "type": "inference_completion" },
"key_id": "1d2ffc84c4a0bbeee8bae343e01feff0419e9f9f989cc5dc6ed3bdac3e40cbb9",
"signature": "d284584ca3…",
"spec_version": "draft-mih-scitt-agent-action-capsule-05",
…
},
"checkpoint": {
"key_id": "1d2ffc84c4a0bbeee8bae343e01feff0419e9f9f989cc5dc6ed3bdac3e40cbb9",
"kind": "mmr_checkpoint",
"log_id": "sender-log",
"mmr_size": 10,
"prev_root": "",
"prev_size": 0,
"root": "6473683c244f0220be10b3ee131a978bafbc29097a5563645a3258352aea0047",
"signature": "e5783347bc7400e44902dd43d3b98c809d42226d1a5d3b0ae67996f726e424e449f957753953f00c3619425a505562310700e9fd7d80da1bea4768fb174af806",
"timestamp": "2026-09-27T00:00:10Z",
"v": 1
},
"inclusion": {
"leaf_index": 4,
"proof": {
"kind": "inclusion",
"leaf_index": 4,
"peaks_left": ["d1b17708d8ce4bab928477715734afe2e841f174f51bbab7ce097287bc3480b1"],
"peaks_right": [],
"size": 10,
"v": 1,
"witness": ["c93642d9c7be9e0de1344d8c4002b1233519e95033ff67d7d3e38d7444d39254"]
}
},
"record_push_bundle": 1
}
| Field | Type | Meaning |
|---|---|---|
| record_push_bundle | uint | Bundle format version: 1. A receiver refuses any other value. |
| capsule.capsule_id | hex | SHA-256 of the JCS bytes of the record without capsule_id, signature and key_id (draft-mih-scitt-agent-action-capsule-05); it is also the log entry for this record. |
| capsule.key_id | hex | The sender's raw Ed25519 public key, which signed the record. |
| inclusion.leaf_index | uint | The record's index among leaves: 4, the fifth record. That is MMR position 7. |
| proof.size | uint | The MMR size the proof is for. It must equal the checkpoint's mmr_size. |
| proof.witness | hex[] | Sibling node values from the leaf up to its peak: node 8. |
| proof.peaks_left / peaks_right | hex[] | The other peaks, left and right of the one the path reaches: node 6 on the left, none on the right. |
| checkpoint.kind | string | mmr_checkpoint: the log implementation's internal JSON checkpoint record (a single bagged root), signed separately from the COSE wire form. |
| checkpoint.log_id | string | Names the log. A real node uses capsules/<first 16 hex of SHA-256(public key)> by default (CAPSULES_LOG_ID overrides it), unique per node, because a witness keeps one history per log id. |
| checkpoint.mmr_size | uint | Node count of the MMR committed to: 10, which is six records. |
| checkpoint.prev_size / prev_root | uint / hex | The previous checkpoint's size and root. Here 0 and empty: this is the log's earliest checkpoint. |
| checkpoint.root | hex | The peaks folded right to left into one value: SHA-256(node 9 ‖ node 6). |
| checkpoint.signature | hex | Ed25519 by key_id over the hex SHA-256 of the sorted, compact JSON of the other fields (2f422cdd…). |
| checkpoint.timestamp | string | When the checkpoint was cut, as the producer states it. |
Recompute it
- leaf 7 =
SHA-256(0x00 ‖ d804c0bf…), over the 32 bytes ofcapsule_id. - node 9 =
SHA-256(be64(10) ‖ leaf 7 ‖ c93642d9…): the parent's position plus one, then left child, then right child. - root =
SHA-256(node 9 ‖ d1b17708…)=6473683c…, the checkpoint'sroot. Then check the checkpoint's signature underkey_id.
Under CLL -02 the same log at size 10 would carry commitment = node 6 ‖ node 9 (64 bytes, tallest peak first), and a bagged root "MUST NOT be carried" (CLL §4.1). The explain series' "Checkpointed local log and MMR" page walks through a -02 checkpoint.
Source: Mesh-LLM/capsules:vectors/parity/corpus/record_push.json (case bundle_valid) and vectors/parity/golden/record_push.json @ b4e24453f572b2d9fe8f2cefbd0f35a5eb54b0ad (tag v0.1.4)
5Status
Tags: live in a tagged release; built-not-live merged but not released; designed written in a draft, not yet produced; planned an open issue or PR.
- live The plugin seals every exchange into
<data dir>/ledger/capsules.jsonland checkpoints it intocheckpoints.jsonl. The checkpoint timer is on by default and local only (100 records or 300 s; padding to 32).Mesh-LLM/capsules:crates/evidence-plugin/src/checkpoint_cadence.rs,INSTALL.md@b4e24453f572b2d9fe8f2cefbd0f35a5eb54b0ad; defaults inaction-state-group/capsule-emit:rust/capsule-emit/src/checkpoint.rs@44ad8bd528b029c1abff1937f700423f94e1d6db(crate 0.0.6). - live A pushed bundle (record, inclusion proof, checkpoint) is checked in-process and kept only if every check passes. Record push is off by default.
Mesh-LLM/capsules:README.md"What works today" and the record-push parity corpus @b4e24453f572b2d9fe8f2cefbd0f35a5eb54b0ad. - live capsule-emit crate 0.0.8 builds its checkpoints on checkpointed-local-log directly (capsule-emit#326, merged; tag
crates/capsule-emit-v0.0.8→fd4e619ab87e920ea60614b030f9371ae0f606d9). The plugin does not use it yet: v0.1.4 pins capsule-emit 0.0.6. - designed The -02 checkpoint wire form: peaks concatenated, the consistency proof inside the signed payload, and one fixed
subper log. Per the draft's Implementation Status, all four checkpointed-local-log implementations still emit the previous COSE wire form (a CBOR array of peaks, a per-checkpointsub). Separately, the v0.1.4 bundle above carries the internal JSON record with a baggedroot. CLL -02 §8; vectors inaction-state-group/checkpointed-local-log:cll-02-vectors/@40dabd74508d0f8649c8754873e316badef1057d. - planned The plugin moves into the mesh-llm workspace: Mesh-LLM/mesh-llm#2459 (open). The checkpoint timer stays in the plugin.
6Specs
- Internet-Draft draft-mih-scitt-checkpointed-local-log-02 — the log discipline (§2), the leaf (§3), the checkpoint and its checks (§4, §4.3), required cadence (§4.4), registration (§4.5), and what it does and does not establish (§7).
- Internet-Draft draft-bryce-cose-receipts-mmr-profile-03 — the MMR, its node hashing, and inclusion and consistency proofs.
- Internet-Draft draft-mih-agent-evidence-layer-00 — what a book must supply (§8) and what a checkpoint reveals, including padding (§12.1).
- Internet-Draft draft-mih-scitt-agent-action-capsule-05 — the record whose
capsule_idis each entry. - RFC RFC 9943 — SCITT architecture: registering a checkpoint as a Signed Statement.
- RFC RFC 9942 — COSE Receipts: what a transparency service returns for a registered checkpoint.
- RFC RFC 8785 — JSON Canonicalization Scheme: the bytes
capsule_idcovers. - Code action-state-group/checkpointed-local-log · action-state-group/capsule-emit · Mesh-LLM/capsules v0.1.4
7What you need before the next step
- A local log that records every exchange you take part in, as requester or provider, signed with a key the other side can check.
- A checkpoint cadence you can state (N records or T seconds), and padding to a bucket size before any checkpoint leaves.
- An export the other side can check offline: the record, its inclusion proof and the checkpoint.
- A way to send your half to the other side and keep theirs. The next step compares two halves of one exchange and needs both.