Ledger Kernel · research-stack
POC · measured 2026-09-16 · every number is from the project's records, listed at the end
seq 1Argumentsynthesis

First the researcher who can read a ledger. Then anyone who has to defend a report.

The kernel's MCP server keeps the old server's tool names, so an existing configuration changes one line. The report is prose you can send; behind every sentence that matters is a page, a byte range, a cost, a verdict — and, where you disagreed, your own recorded decision.

seq 2Runrun.finished · state

Status, plainly.

status · 2026-09-16

Proof of concept. The gate stands at 7 of 8 — check 8 fails on #590, and FIX-590 is packaged against it. The eight-question side-by-side stands at 5 of 8 against a bar of 6. Not yet released: there is no download, no signup and no mailing list, and the repository is not public. What this page describes is how the POC is driven today, from the specification it is built to.

docs/GATE-POC.md · docs/E1-2026-09-16.md · docs/PACKAGES.json FIX-590 · README.md

seq 3Argumentsynthesis

Who it is for.

Claude Code · OpenCode the researcher's agent MCP rsk mcp one config line audit_question deep_research_start / _status / _result deep_research · list_research_jobs research_doctrine · ledger_query ledger.sqlite3 + the report report.md → anyone asked “where did that come from?” ledger_query → which page, which bytes, what it cost, who decided what first: the technical researcher who can read a ledger · next: everyone who has to defend a report

An agent in Claude Code or OpenCode talks to the kernel over MCP. The ledger yields two things: report.md for anyone who is asked “where did that come from?”, and ledger_query for the researcher — which page, which bytes, what it cost, who decided what.docs/SPEC-kernel.md §7 interfaces

seq 4Unitunit.invoked · MCP

Inside Claude Code or OpenCode: rsk mcp.

rsk mcp speaks MCP over stdio and exposes the same tools as the web-research server it replaces, so OpenCode's existing mcp.web-research entry works with only its command changed to ["<path>/rsk", "mcp"]. Legacy parameters (headless, allowed_domains, fresh_profile) are declared and ignored. Tool-level failures come back as results with isError, never as protocol errors.

The eight tools, and one prompt

  • audit_question(question)An audit-only run. Answers {job_id, run_id, question, restated, premise[]{text, load_bearing}, sub_questions[], brief} — the Question events of that run; on a ledger where the same auditor input was already admitted it is a memo hit, no model call.
  • deep_research_start(question, depth)depth = scout | standard | deep | exhaustive → {job_id, status, depth, note}. Spawns a detached worker (rsk research run <job_id>) so the client’s SIGTERM does not kill the run; the server only reads the ledger.
  • deep_research_status(job_id){job_id, question, depth, status: running | done | error, phase, message, elapsed_seconds, events[≤ 40]} — kernel states map onto phase; run.finished{state} onto status; a gap is done with a gap report.
  • deep_research_result(job_id, format)format = markdown | json.
  • deep_research(question, depth, wait_seconds)The blocking form, depth = standard, wait_seconds = 600 (clamped to 1,800). Progress notifications on every state.entered and at least every 30 s; on cancellation it returns {job_id, status: running}.
  • list_research_jobs(limit)limit = 20. Not list_jobs.
  • research_doctrine()No arguments; the doctrine.
  • ledger_query{sql, params?, max_rows?}→ {columns[], rows[][], truncated}. One read-only statement on the reader pool; DML, DDL, PRAGMA, ATTACH and BEGIN are refused; 1,000 rows or 1 MiB by default, hard cap 10,000; BLOBs base64 up to 4 KiB.
  • prompt · research(topic)The one MCP prompt the server declares.

docs/SPEC-kernel.md §7 — MCP (stdio, rmcp 3.3.0)

seq 5Argumentsynthesis · report.md

The report, for anyone.

report.md is a pure function of the ledger: rendered over the Argument view, sections ordered by stable keys, never by time, and it carries no timestamps — the meta line has depth, the plan and doctrine hashes, counts and cost. Wall time, timestamps and cost per second go to report.json. A run that ended in a gap renders its Answer as the single line No answer: …; a halted run as Halted: ….

# {question}
meta · depth · plan hash · doctrine hash · counts · cost
## The question you should be asking      (only when it differs)
## Answer                                  _Confidence: …_
## Findings
## Contested and refuted                   ### {mark} {claim} · **Verdict: …** · supporting · disconfirming
## Adversarial verification                confirmed · unsupported
## What we could not establish
## Next steps
## Assumptions the question was resting on
## Surprises found during orientation
## Sources                                 [host-minus-www](url)

The exact headers, in order. Contested items list their undecided attackers and refuted items their accepted attackers, both read from claim_status; the word contested never appears in a model-written payload.docs/SPEC-kernel.md §7 — Report

seq 6Fetchfetch.done · ledger_query

The ledger, for the researcher.

Everything the report rests on is a row you can read with one SQL statement: a claim's anchors (the fetch it resolved against and the byte range [start, end)), the fetch.done row of that page and its stored body, every model call with its settled micro-dollars, every rejection with its code and path, and the Decision.feedback events the owner wrote. Nothing has to be trusted that can be opened.

The same, from the command line

  • rsk research start [--depth] [--run <id>]An existing run id resumes from the last admitted event.
  • rsk research status | result [--json]
  • rsk research replayRe-executes the plan at concurrency 1 with the model client and the retriever replaced by ledger-backed ones that refuse the network; any miss halts naming both keys.
  • rsk rebuild [--verify] [--out]Replays the events into a fresh file with the triggers firing, then compares view hashes; --verify prints table, rows before, after, equal and never swaps.
  • rsk bench <id>… · bench reportWrites bench.result events into the ledger; bench report recomputes every verdict and exits 1 on any gated FAIL.
  • rsk doctorDB path, node id, HLC skew, WAL size, sqlite version, the OpenRouter key and balance, the Brave key, and the six judged invariants printed pending.
  • rsk decide --scope <s> --text <t>Writes Decision.decided, actor owner.
  • rsk feedback --run … --target <id> --verdict <v> --reason <r>Writes Decision.feedback with refs [run, target], actor owner — the eight side-by-side verdicts were written this way.

Exit codes: 0 done, 2 gap, 3 halted, 1 error. Both Decision commands take the ledger lease before the kernel opens; a held lease is refused at once with nothing written.docs/SPEC-kernel.md §7 — CLI rsk

seq 7Runthe build

The build, as the README has it.

cargo build && cargo test          # offline; nothing in the suite opens a socket
cargo run --bin rsk -- doctor       # --db <path> for the ledger; exit 1 lists what is missing:
                                    # the OpenRouter key file or OPENROUTER_API_KEY, and BRAVE_API_KEY

A Rust workspace: rs (CLI), rsd (daemon: lanes, budget coordinator, fetch), rs-core, rs-store-sqlite, rs-fetch, rs-know and kernel — the ledger kernel with its statechart runner, unit runner, MCP and the rsk CLI. Doctrine, plans, units and benchmark sets are versioned beside it.README.md · docs/ADR-crates.md

back to the front page, or the proof.