Repo docs

Per-repo documentation — each repo's docs/ and README, ingested and associated with the repo. Rendered through the <<<RepoDocs>>> tag.

← code__chain docs · README.md

Conductor Blockchain Ledger

A local, tamper-evident spine for Conductor. Where telemetry.db is fast-and-local and notes_db is durable-and-shell-readable, the ledger is immutable-and-ordered: once a meaningful Conductor moment is anchored it cannot be silently altered or removed, and the whole history is a single hash-chained log you can verify with one call.

It runs on a local Anvil node (Foundry) — no real network, no gas cost, no keys to protect. It is purely a local content-addressed + ordered attestation layer, the on-chain twin of the signals jazz already produces.

  ./ledger.sh setup && ./ledger.sh deploy      # stand it all up
  ./ledger.sh up                               # anvil+build+test+deploy+selftest
  ./ledger.sh mcp                              # start the MCP server

---

Why a ledger (what it buys you)

The Conductor data-plane already content-addresses every volume snapshot (volctl serialize → sha256 + IPFS CID) and the control-plane already signals every dispatch (agent_status) and every confirmation (auth_gate). Those records are mutable (sqlite, notes_db). The ledger adds the one property they lack: immutability + global ordering. After the fact you can prove:

- these exact bytes existed and were attested at block N (provenance), - this dispatch session opened, ran, and ended with this outcome (sessions), - this human approved this scoped decision, at this time (gates),

and that none of it was edited after the fact — the hash chain breaks if any entry is changed.

---

Architecture

                         ┌──────────────────────────────────────────┐
   data-plane            │            ~/code/chain (Foundry)         │
   volctl serialize ─┐   │                                           │
   (sha256 + CID)    │   │   Provenance.sol   ── content anchors     │
                     │   │   Sessions.sol     ── dispatch lifecycle  │
   control-plane     ├──▶│   ConductorLedger  ── hash-chained log +  │
   coordinator       │   │                       registry (knows the │
   worker.spawn/     │   │                       other two addresses)│
   worker.finish     │   │                                           │
                     │   │            local Anvil :8545              │
   auth_gate         │   └──────────────────────────────────────────┘
   answer_gate ──────┘                    ▲          ▲
                                          │          │
            jazz_telemetry.ledger  ───────┘          │   web3.py, fire-and-forget
            (anchor_*, read_*, verify)                │   degrades to a telemetry row
                                                      │
            ledger_mcp (FastMCP)  ────────────────────┘   thin client over the lib

The contracts (src/)

| Contract | Anchors | Key | |---|---|---| | Provenance.sol | volctl artifacts: sha256, IPFS cid, volume, mode, size | bytes32 sha256 (append-only set; duplicate sha rejected) | | Sessions.sol | coordinator dispatch: task, model, agent, outcome | bytes32 session id; open() once → close() once | | ConductorLedger.sol | the spine: a hash-chained append-only log + a registry of the other two contracts; also where auth-gate decisions land | seq (each entry links prevHashentryHash) |

ConductorLedger is the single address every consumer needs: it exposes provenance() and sessions() so the rest is discoverable, and verifyChain() walks the log and returns the first broken link (or a sentinel if intact).

The Python lib (jazz_telemetry/ledger/)

Lives inside jazz_telemetry so every module that already does from jazz_telemetry import telemetry gets the ledger for free.

- client.py — lazy, cached web3 connection; ABI loading (prefers the Foundry out/ build, falls back to bundled ledger/abi/*.json); zero-config address discovery from chain/deployments/<chainid>.json. - anchor.py — the fire-and-forget write path. Every anchor drops a local telemetry row first, then tries the chain; any failure (no web3, node down, revert) is swallowed and recorded as a *.fail telemetry row. Never blocks, never raises — exactly like agent_status / auth_gate. - read.py — the read / verification path. These do raise LedgerError on an explicit problem (a reader that silently returns empty hides failures).

Public API (re-exported from jazz_telemetry):

from jazz_telemetry import (
    anchor_provenance, record_session_open, record_session_close,
    anchor_gate, log_entry,                       # writes (fire-and-forget)
    is_anchored, get_artifact, get_session,        # reads
    ledger_count, ledger_entry, recent_entries, verify_chain,
    chain_status, get_addresses,                   # status / discovery
)

The MCP server (~/code/ledger_mcp/)

A thin FastMCP server — same shape as notes_db_mcp (thin client, returns JSON strings). Tools: status, addresses, anchor_provenance, is_anchored, get_artifact, record_session_open, record_session_close, get_session, anchor_gate, log, log_count, recent, log_entry, verify_chain.

Start it: ./ledger.sh mcp (or python -m ledger_mcp with JAZZ_LEDGER_* env).

---

Integration points (the hooks already wired)

All three are fire-and-forget and individually killable — none can break the host operation, and each respects an env switch.

1. volctl → provenance. volctl/core.py::serialize() calls _anchor_provenance_safe() right after it emits the volume.serialize telemetry row, anchoring the same (sha256, cid). Disable: VOLCTL_LEDGER=0.

2. coordinator → sessions. coordinator/dispatch.py calls _ledger_session_open() at the worker.spawn moment and _ledger_session_close() at worker.finish, keyed by <pkg>:<token.id>. Disable: COORDINATOR_LEDGER=0.

3. auth_gate → gate decisions. auth_gate.py::answer_gate() anchors the resolved decision (gate id, type, agent, response) on the ledger log the moment a human answers. Disable: JAZZ_GATE_LEDGER=0.

Because the lib degrades to a telemetry row when the chain is absent, these hooks are safe to ship before the chain is even deployed — they simply no-op until ./ledger.sh deploy has run.

---

Configuration (env)

| Var | Default | Meaning | |---|---|---| | JAZZ_LEDGER | 1 | 0 makes all anchors no-ops | | JAZZ_LEDGER_RPC | http://127.0.0.1:8545 | Anvil RPC | | JAZZ_LEDGER_KEY | Anvil acct #0 | signing key (local dev only) | | JAZZ_LEDGER_DIR | ~/code/chain | where deployments/<id>.json lives | | JAZZ_LEDGER_ADDRESSES | (discovery) | explicit address-book path override |

The default key is Anvil's well-known deterministic account #0 — it is a public test key, only ever used against the local node. Do not point this at a real network.

---

ledger.sh commands

| Command | Does | |---|---| | setup | install Foundry, vendor forge-std, pip install web3 into .venv | | anvil | start a background Anvil node on :8545 (pid+log under run/) | | build | forge build | | test | forge test -vv (Solidity unit tests in test/Ledger.t.sol) | | deploy | start Anvil, deploy all three contracts, write the address book | | mcp | run the ledger_mcp FastMCP server (stdio) | | selftest | live Python E2E: anchor → read → verify the hash chain | | status | Anvil + deployment + chain_status() | | stop | stop the background Anvil node | | up | anvil + build + test + deploy + selftest (everything) |

Verify it works

./ledger.sh setup        # one-time: foundry + forge-std + web3
./ledger.sh deploy       # starts anvil, deploys, writes deployments/31337.json
./ledger.sh selftest     # anchors live, reads back, verifies the chain → PASSED
./ledger.sh test         # the Solidity unit tests

forge test proves the contracts in isolation; selftest proves the whole Python → web3 → chain → read-back path against the deployed node.