One model split across peers
When one model is split across several Mesh-LLM nodes, each node runs a slice of its layers. The same record pattern then applies one level down: each stage seals what crossed its hand-offs, and the coordinator's record of the request accounts for every stage.
1What it is
Mesh-LLM can split one model across several nodes: a coordinator runs the opening slice of layers and hands activations on to other nodes, each running the next slice.
The stage-record design turns each coordinator-to-stage pair into an exchange of its own: each stage seals a record of its slice and of the exact bytes that crossed its hand-offs, as digests, and the coordinator's record of the request cites its own record of each stage exchange and names each stage's record by digest in its receipt.
A requester holding those records can check, offline and without contacting any stage, whether both ends of every hand-off committed to the same bytes, and see a missing record as a gap rather than as agreement.
2Design choices
split_stage), and its receipt names each stage's record by digest.replayed_at_other_hop_index)gap; the request's terminal_state is always shown beside the hand-offs.stopped_early_agrees_on_truncated_stream)3What it shows / what it does not
Shows
- Which signing key claims which slice of the request: layer range and package id, per request, signed.
- For each hop and lane, whether both ends committed to the same bytes:
agree,break,gap,malformedornot_applicable. - One end misreporting, or a hand-off altered in transit, as a
breakon that hop, without disclosing any activation. - A withheld or late stage record as
absentand agap, never as agreement. - Whether the coordinator's assignment and the stage's own record name the same slice (
disagreeswhen they do not). - Two different records sealed for one stage of one request, as
conflict.
Does not show
- That any stage computed its slice correctly; equal digests show both ends saw the same bytes, not that the output is the right function of the input.
- Collusion: two neighbouring stages can report matching digests for anything, and a stage that alters its own output and reports it consistently is not caught.
- That the stages are independent: a coordinator can place every stage on keys it controls.
- That a stage loaded the weights it names; no per-slice weights digest exists, so package id and layer range are a signed claim.
- Which end is wrong in a
break. - Who operates a stage's key; the requester's check does not yet link a stage key to the assigned node.
4Example
Stage 1's record from the plugin's split-stage fixture, three stages, replies relayed (other envelope fields, the signature, the nonce and the assurance and disposition blocks trimmed). Its upstream.received digest equals the coordinator's downstream.sent, and its downstream.sent equals stage 2's upstream.received: the two forward hand-offs agree.
{
"capsule_id": "ee550164e1c49cf5ba0f91ce1b5acc98d7d41a10c343639d88085ca83243b9cd",
"spec_version": "draft-mih-scitt-agent-action-capsule-05",
"canonicalization_id": "jcs",
"action_id": "mesh-poc/split-stage/stage/node-coordinator-c/mesh-split-1790000000000000000-g2/18446744073709551615/1",
"model_attestation": {
"model_id": "n/a-split-stage",
"provider": "mesh-llm",
"compute_attestation": {
"x-mesh-poc-v1": { "role": "stage" },
"x-mesh-stage-v1": {
"v": 1,
"side": "stage",
"coordinator_node_id": "node-coordinator-c",
"run_id": "mesh-split-1790000000000000000-g2",
"request_id": "18446744073709551615",
"topology_hash": "topology-hash-0001",
"stage_index": 1,
"stage_count": 3,
"layer_start": 16,
"layer_end": 32,
"package_id": "sha256:abab…abab",
"manifest_sha256": "cdcd…cdcd",
"source_model_sha256": "efef…efef",
"return_mode": "relayed",
"upstream": {
"received": { "frames": 4, "digest": "sha256:557e4faf6e4fb73578f5715bb0a7a71b659adb949699d5e0d71702162c3db6d3" },
"replies_sent": { "frames": 3, "digest": "sha256:dee44caeb95e4142b5bc09274cd08252bc5025dfde02842eda5af77e93a85c44" }
},
"downstream": {
"sent": { "frames": 4, "digest": "sha256:292c1d551a824014fbca8353a8ed0dd417286036d3fe7bc27ac97dac81774e51" },
"replies_received": { "frames": 3, "digest": "sha256:b6eb868e31e55d57db915c87158b2b86aece54321a0b3cb306a774581d557161" }
},
"direct_return": null,
"tokens": { "prefill_received": 12, "decode": 4 },
"terminal_state": "completed"
}
}
},
…
"key_id": "e74b97f06c437c8056392be8ab7b7da541a89d69d1871d8b4665513ec6f0d231"
}
| Field | Type | Meaning |
|---|---|---|
| x-mesh-poc-v1.role | string | stage: a record of a coordinator-to-stage exchange; a reader that does not know it reads unknown, never served. |
| side | string | stage for the stage's own record, coordinator for the coordinator's side and its own slice. |
| coordinator_node_id, run_id, request_id | string | The split key; the coordinator id is included because the coordinator mints the run id and nobody checks it. request_id is a decimal string so a u64 survives every JSON reader. |
| stage_index, stage_count | integer | This slice's place in the chain; stage 0 is always the coordinator. |
| layer_start, layer_end | integer | The slice; layer_end is exclusive, so this is layers 16–31. |
| package_id, manifest_sha256, source_model_sha256 | string | The model package, its manifest and the source model the stage says it loaded; all package-wide, none per slice (fixture values here). |
| return_mode | string | relayed (replies hop by hop) or direct (the final stage returns to stage 0); decides which lanes must be non-empty. |
| upstream / downstream | object | Per hop, per lane: frame count and running digest of the bytes this stage read or wrote. Stage 0 has no upstream; the final stage has no downstream. |
| tokens.prefill_received | integer | Tokens received for prefill, including any restored from cache; counts, never content. |
| terminal_state | string | How the request ended at this stage; shown beside the hand-offs, since agreement is not completion. |
| key_id | hex | The stage's own signing key; stage 2's record is signed by a different key (57a88de9…). |
Source: Mesh-LLM/capsules:vectors/split-stage/rust-split-bundle.json (key split_stage_records[0]) @ efcada74026a2e18b9e62d77432888a31bbe9de4; the break case: vectors/split-stage/hop-cases.json, case one_byte_changed, same SHA. The fixture is sealed by the plugin's own generator test, not taken from a live run.
5Status
| Piece | Status | Evidence |
|---|---|---|
| Stage record shape and the hop digest | built, not live | crates/evidence-plugin/src/producer/stage.rs; byte layout pinned in vectors/split-stage/fold-vectors.json. Mesh-LLM/capsules @ efcada74026a2e18b9e62d77432888a31bbe9de4, also in v0.1.4 (b4e24453f572b2d9fe8f2cefbd0f35a5eb54b0ad). |
Stage seals and pushes to the coordinator; coordinator waits (2000 ms default), seals the receipt and split_stage references, carries the stage records to the requester | built, not live | crates/evidence-plugin/src/split_stage.rs @ efcada74026a2e18b9e62d77432888a31bbe9de4, fed by fixture events; its header reads "No host emits skippy.stage.v1 yet", and the channel is not declared in the plugin's manifest. |
| The requester's hop check (agree, gap, break, malformed, not_applicable) | built, not live | crates/evidence-plugin/src/producer/stage_verify.rs and the 16 cases in vectors/split-stage/hop-cases.json @ efcada74026a2e18b9e62d77432888a31bbe9de4. |
| Console stage strip and the "served you through a split" Peers group | built, not live | web-ui/src/features/capsules/components/StageStrip.tsx and LedgerPeersTable.tsx @ efcada74026a2e18b9e62d77432888a31bbe9de4; unit-tested against the split-stage vectors only: no pane serves split rows yet (web-ui/src/features/capsules/api/sidecarTypes.ts, design §12.2). |
| Nesting parent and child rows, role filter chips | designed, not built | docs/DESIGN-split-stage-records.md §12.2 @ efcada74026a2e18b9e62d77432888a31bbe9de4. |
Host event skippy.stage.v1, one per stage per request | planned | Listed as "proposed" in the capsules README's host-changes table @ efcada74026a2e18b9e62d77432888a31bbe9de4. Upstream Mesh-LLM/mesh-llm main @ 60697c61755b7280c22d3ab9d9bcf5b663e8ae3b: the callers of broadcast_channel_message in mesh/crates/mesh-llm-host-runtime/src/ send openai.exchange.v1 (plugin/openai_exchange.rs), payment.lifecycle.v1 (network/payments/lifecycle.rs) and routing.choice.v1 (network/peer_blocks.rs); none sends a stage channel. (Upstream already uses the name skippy.stage.v1 for its stage wire protocol's protobuf package; that is not a plugin channel.) |
| Per-request salt for the digests | designed, not built | Design §4.4 and §8 part B; v0 hashes a zero-length salt (§12), so stage records stay with the coordinator and the requester. |
| A referee re-running one stage's slice as a spot check | planned | Design §9: would compare decoded tensors within a stated tolerance, not digests. |
6Specs
No Internet-Draft defines stage records yet; the record shape and the hop digest are the plugin's design document. The record itself is an ordinary capsule under these specs.
- Internet-Draft draft-mih-scitt-agent-action-capsule-05 — the record each stage seals;
references[]withcitation_purpose(counterparty_halfis defined there;split_stageis the plugin's provisional value); extension points live in the capsule JSON (§Extensibility), where thex-mesh-stage-v1block sits. - Internet-Draft draft-mih-agent-bilateral-attestation-02 — related: each party records only its own side of a cross-party exchange; the coordinator's stage-exchange record cites the stage's own record with
counterparty_half(one way; the stage does not cite back), without this draft's acknowledgment step. - Internet-Draft draft-mih-scitt-checkpointed-local-log-02 — the local log each record is appended to, and the checkpoint and inclusion proof a pushed bundle carries.
- RFC RFC 9052 — COSE; each record's signature is a COSE_Sign1.
- RFC RFC 8785 — JSON Canonicalization Scheme (JCS), the canonical form under each capsule_id.
- Design Mesh-LLM/capsules: DESIGN-split-stage-records.md — the stage record, the hop digest, delivery, the console and the host ask.
- Code Mesh-LLM/capsules — the plugin, its split-stage code and vectors.
- Code Mesh-LLM/mesh-llm — the host and its split runtime; the per-stage event would be added here.