documentation · readproof 0.3.2
Give your agent's context a lockfile.
Readproof records exactly which bytes an agent read — every policy, document, or dataset, by content hash — so any run can be diffed against another, replayed byte for byte without touching the live source, and exported as evidence.
Models are probabilistic, but many context failures are infrastructural. Agent reliability is bounded by context reliability.
# The whole loop. Real output from readproof 0.3.2; ids trimmed. # 1. give the document an identity and a freshness rule $ readproof resource add readproof://demo/policies/refunds \ --source-type filesystem --path policies/refunds.md \ --policy require_fresh # 2. record one agent run, then pin what it read as @prod $ readproof run --id run-a readproof://demo/policies/refunds Mounted …/policies/refunds -> snapshot snap_01M0JED935… (position 0) Committed manifest manifest_01M0JED936… for run run-a (1 entry) $ readproof tag set readproof://demo/policies/refunds prod snap_01M0JED935… # 3. the file changes underneath; a later run records the new bytes $ printf 'Products can be refunded within 14 days.\n' > policies/refunds.md $ readproof run --id run-b readproof://demo/policies/refunds # 4. the diff names the revision that moved, and when it was observed $ readproof diff run-a run-b ~ readproof://demo/policies/refunds why: source revision sha256:c8b0bb212e93 → sha256:8f4b00474456; observed 2026-08-21T15:18:10Z → 2026-08-21T15:18:12Z -Products can be refunded within 30 days. +Products can be refunded within 14 days. # 5. replay rebuilds run-a from the store, never from the file $ readproof replay run-a Products can be refunded within 30 days. Replay verified: SHA256 match for 1/1 entries. # 6. and the whole run exports as one file anyone can check $ readproof evidence export run-a --with-content --out bundle.json $ readproof evidence verify bundle.json evidence verified: 1 entry, merkle root a9b73469f1a6…, embedded content 1/1 re-hashed, replay match 1/1
Readproof in three cards
What it is
A small Go binary (readproof) plus an optional server (readproofd) that sits underneath your retrieval, prompting, and memory stack and answers one question forever: which exact bytes went into this run?
What it isn't
Not a vector DB, not an observability tool, not a prompt registry, not memory. It complements install time lockfiles (APM, skills-lock.json) that pin static config — Readproof pins the runtime documents, per run.
Where it plugs in
MCP server for Claude Code / Desktop / Cursor, OpenTelemetry spans with GenAI attributes, a TypeScript SDK, a LangGraph example, and a plain HTTP API. The CLI behaves identically embedded or against a server.
mechanism
How it works
Six primitives, one loop: resolve → manifest → diff → replay. The picture below is the refunds walkthrough above, drawn as data flow.
run-a still reproduces its exact bytes.Source
Where bytes physically live: filesystem, github, or http. Credentials come from readproofd's environment at fetch time and are never stored.
Resource
The stable logical identity your code uses: readproof://<namespace>/<path>. Survives the bytes changing.
Policy
Freshness rule per resource: require_fresh (verify again every resolve), allow_stale (reuse within --max-age), or pin with a @tag.
Snapshot
An immutable observation: sha256:… content hash, source revision, observed at, provenance (ETag, path, commit…). Identical bytes dedupe to one blob.
Manifest
The ordered, immutable record of everything a run resolved — position, URI, @ref, snapshot, content hash. Order is an invariant: it changes model input.
Evidence bundle
An in-toto Statement whose subject digest is a Merkle root over the manifest; carries entries, redacted resource definitions, and a replay check. Verifiable by anyone you hand it to.
use it
Install & first resolve
Embedded mode needs only Go 1.26+. Everything lives in a local .readproof directory (SQLite + blobs); no services.
# 1. build the one binary
git clone https://github.com/fbzz/readproof.git && cd readproof
go build -o readproof ./cmd/readproof
./readproof version
# -> readproof 0.3.2
# 2. register a document: identity + source + freshness rule
./readproof resource add readproof://demo/policies/refunds \
--source-type filesystem \
--path examples/refund-agent/policies/refunds.md \
--policy require_fresh
# 3. resolve it — this creates the first snapshot
./readproof get readproof://demo/policies/refunds
uri: readproof://demo/policies/refunds snapshot: snap_01M0JED70D1TPFJJNVC4C3ZY9X content_hash: sha256:c8b0bb212e93… freshness: fresh (observed 2026-08-21T15:18:08Z, policy require_fresh) provenance: path=policies/refunds.md source_type=filesystem bytes: 41 content_type: text/markdown --- content --- Products can be refunded within 30 days.
Global flags on every command: --data-dir (embedded; default .readproof or $READPROOF_HOME), or --server http://… / $READPROOF_SERVER_URL to talk to a readproofd instead (plus --api-key / $READPROOF_API_KEY). Output and semantics are identical either way.
export READPROOF_HOME=$HOME/.readproof in your shell profile so the CLI, the MCP server, and your scripts share one store.Sources & freshness policies
A resource is where the bytes come from plus how fresh they must be. Three adapters ship in v0.3.
Filesystem
# a file on the machine that runs readproof (or readproofd)
readproof resource add readproof://acme/policies/refunds \
--source-type filesystem \
--path /srv/policies/refunds.md \
--policy require_fresh
GitHub (file in a repo, at a branch or ref)
# the token is read from the process environment at fetch time
# and is never stored; the commit SHA becomes the source revision
export GITHUB_TOKEN=ghp_…
readproof resource add readproof://acme/runbooks/oncall \
--source-type github \
--owner acme --repo ops --ref main \
--path runbooks/oncall.md \
--policy allow_stale --max-age 1h
The snapshot's source_revision is the commit SHA the file was read at, so readproof diff can later say exactly which commit changed a run's input.
HTTP (any URL; headers may reference env vars)
# ${VAR} inside a header is resolved from the environment at
# fetch time, so the token itself is never written to the store
readproof resource add readproof://acme/pricing/table \
--source-type http \
--url https://pricing.internal/v3/table.json \
--header 'Authorization: Bearer ${PRICING_TOKEN}' \
--policy allow_stale --max-age 15m
${VAR} references are resolved from the server's environment at fetch time. As defense in depth, sensitive header values (Authorization, Cookie, anything matching *token*/*key*/*secret*…) are masked in every API response, in readproof inspect, and in evidence bundles. ETag and Last-Modified are recorded in snapshot provenance when the server sends them.
Choosing a policy
| Policy | Behavior on resolve | Use when |
|---|---|---|
require_fresh (default) | Verifies again against the source on every resolve. Unchanged bytes dedupe to the same content hash (you'll see two snapshot rows, one blob). | Policies, prices, anything where "stale" is a bug. |
allow_stale --max-age 1h | Reuses the current snapshot if it is younger than the TTL; otherwise fetches. --max-age 0 means never refresh once a snapshot exists. | Slow or rate limited sources; reference docs that change rarely. |
@tag pin | Resolving readproof://ns/path@prod delivers exactly the tagged snapshot: no fetch, policy not consulted. | Promotion workflows (@prod, @v3), reproducible evals, incident fixtures. See Tags. |
Inspect what you registered, and the history of what was observed:
# everything registered, with source kind and policy
readproof resource list
# source, policy, tags, and the current snapshot of one resource
readproof inspect readproof://demo/policies/refunds
# every snapshot ever observed, newest first, with a TAGS column
readproof history readproof://demo/policies/refunds
Runs, manifests, diff, replay
A run is the unit of work you want to be able to reproduce — one agent turn, one job, one eval case. Mounting a URI into a run resolves it and records it at the next position; committing freezes the run into a manifest.
Record a run
Single shot form (start + mount + commit) or the three step form if your code mounts across time.
# one shot: start + mount (in the order given) + commit readproof run --id run-a \ readproof://demo/policies/refunds \ readproof://demo/policies/tone # or step by step, when your code mounts across time readproof run start run-a readproof run mount run-a readproof://demo/policies/refunds readproof run mount run-a readproof://demo/policies/tone readproof run commit run-aStarted run run-a Mounted …/policies/refunds -> snapshot snap_01M0JED935… (position 0) Committed manifest manifest_01M0JED936… for run run-a (1 entry)Read the manifest
# takes a run id or a manifest id readproof manifest run-aManifest manifest_01M0JED936… (run run-a), created 2026-08-21T15:18:10Z, 1 entry POS URI SNAPSHOT CONTENT_HASH 0 readproof://demo/policies/refunds snap_01M0JED935… sha256:c8b0bb21…Change the world, run again, diff the inputs
The
why:line is the point: not just that the input changed, but which source revision and when it was observed.# 1. the source changes; nobody tells the agent printf 'Products can be refunded within 14 days.\n' > policies/refunds.md # 2. a later run records the new bytes readproof run --id run-b readproof://demo/policies/refunds # 3. compare what the two runs were given readproof diff run-a run-b--- run-a (manifest_01M0JED936…) +++ run-b (manifest_01M0JEDBE0…) ~ readproof://demo/policies/refunds (snap_01M0JED935… -> snap_01M0JEDBDZ…) why: source revision sha256:c8b0bb212e93 → sha256:8f4b00474456; observed 2026-08-21T15:18:10Z → 2026-08-21T15:18:12Z --- a/readproof://demo/policies/refunds +++ b/readproof://demo/policies/refunds @@ -1,2 +1,2 @@ -Products can be refunded within 30 days. +Products can be refunded within 14 days. 1 resource changed, 0 added, 0 removed, 0 unchanged
Replay — the invariant
replayrebuilds a manifest's bytes from the content addressed store and hashes them again. It never contacts the source, so it works after the file changed, after the API key expired, after the repo was archived. Any mismatch or missing blob exits nonzero.# rebuild run-a's bytes from the store — no source fetch readproof replay run-aReplaying manifest manifest_01M0JED936… (run run-a), 1 entry [0] readproof://demo/policies/refunds materialization: mat_01M0JED936… content_hash (recorded): sha256:c8b0bb212e93… content_hash (replayed): sha256:c8b0bb212e93… match: OK --- content --- Products can be refunded within 30 days. Replay verified: SHA256 match for 1/1 entries.
Evidence bundles
When someone asks "what did the agent actually see for this decision?", hand them a file. readproof evidence export produces an in-toto Statement whose subject digest is a Merkle root over the run's entries; readproof evidence verify checks it — offline if you like.
# 1. export: an in-toto Statement whose subject digest is a
# Merkle root over the run's ordered entries
readproof evidence export run-a --with-content --out bundle.json
# 2. verify: recompute the root, re-hash the embedded content,
# then cross-check the store by replaying the run
readproof evidence verify bundle.json
# 3. offline: root + embedded content only, no store needed
readproof evidence verify bundle.json --offline
evidence bundle written to bundle.json: 1 entry, merkle root a9b73469f1a6e956024399ac407d174cc71e5d74a66e5ce390af3d64102821be evidence verified: 1 entry, merkle root a9b73469f1a6…, embedded content 1/1 re-hashed, replay match 1/1
Shape (trimmed):
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{ "name": "manifest_01M0…",
"digest": { "sha256": "a9b73469f1a6…" } }
],
"predicateType": "urn:readproof:evidence:v0.3",
"predicate": {
"run_id": "run-a",
"manifest_id": "manifest_01M0…",
"merkle": {
"algorithm": "sha256",
"leaf":
"sha256(position_be_uint32 || 0x00 || uri || 0x00 || content_hash)",
"root": "a9b73469f1a6…"
},
"entries": [
{
"position": 0,
"uri": "readproof://demo/policies/refunds",
"ref": "prod",
"snapshot_id": "snap_01M0…",
"content_hash": "sha256:c8b0bb21…",
"source_revision": "sha256:c8b0bb212e93",
"observed_at": "2026-08-21T15:18:10Z",
"content_type": "text/markdown",
"bytes": 41,
"provenance": {
"path": "policies/refunds.md",
"source_type": "filesystem"
},
"content_b64": "UHJvZHVjdHMgY2FuIGJl…"
}
],
"resources": [
{
"uri": "readproof://demo/policies/refunds",
"source": {
"kind": "http",
"config": {
"http": { "headers": { "Authorization": "[REDACTED]" } }
}
},
"policy": { "strategy": "require_fresh" }
}
],
"replay": {
"all_match": true,
"entries": [{ "position": 0, "match": true }]
}
}
}
What it proves
That these bytes (by hash, optionally embedded) were delivered for this run, in this order, under these resource definitions, and that the store can still reproduce them. The root ties the whole run to one digest.
What it doesn't
That the model used them, or that the source was authoritative. Bundles aren't signed yet (roadmap: cosign/in-toto attestation). Tamper with an entry and verify fails; reroot a forged bundle and only the store cross check catches it — so keep --offline for transport, not trust.
Where it fits
EU AI Act Art. 12 automatic logging (Annex III systems, from 2 Aug 2026) and SOC 2 "what data did the agent consider" reviews want exactly this record. Not legal advice — what you owe depends on your system and jurisdiction.
The same bundle can be built client side from the TypeScript SDK (buildEvidence) with a byte identical Merkle root, and exported by an agent through the MCP tool readproof_evidence_export.
integrate
Client/server mode & HTTP API
readproofd is the same engine behind an HTTP API, backed by Postgres + an S3-compatible store (MinIO in dev). Teams, services, and the MCP server share one history.
# 1. the full stack from a clean clone: Postgres, MinIO,
# readproofd, and an OTel collector
cp .env.example .env
# change the dev-only credentials in .env before starting
docker compose up -d --build
curl http://localhost:8080/healthz
# -> ok
# 2. point the CLI at it — every command works identically
export READPROOF_SERVER_URL=http://localhost:8080
readproof resource add readproof://demo/hello-world \
--source-type http \
--url https://raw.githubusercontent.com/octocat/Hello-World/master/README \
--policy require_fresh
readproof get readproof://demo/hello-world
readproofd directly in embedded mode: go build -o readproofd ./cmd/readproofd && ./readproofd --addr :8080 --data-dir ~/.readproof (omit --postgres-dsn).readproofd flags (all also settable as READPROOFD_* env vars)
# metadata in Postgres, blobs in any S3-compatible store
# DSN shape: postgres://user:pass@host:5434/readproof?sslmode=disable
readproofd --addr :8080 \
--postgres-dsn "$READPROOFD_POSTGRES_DSN" \
--s3-endpoint localhost:9000 \
--s3-access-key … --s3-secret-key … \
--s3-bucket readproof-blobs \
--s3-use-ssl \
--api-key "$READPROOFD_API_KEY"
# with --api-key set, every request except /healthz needs
# Authorization: Bearer <key>
Endpoints
| Method · path | What |
|---|---|
POST /v1/resources · GET /v1/resources · GET /v1/resources/get?uri= · GET /v1/resources/history?uri= | Register / list / get one / snapshot history |
GET /v1/snapshots?id= | One snapshot |
PUT /v1/tags · GET /v1/tags?uri= · DELETE /v1/tags?uri=&tag= | Tags (body {uri, tag, snapshot_id}) |
POST /v1/resolve | Resolve a URI (accepts uri@tag) |
POST /v1/runs · POST /v1/runs/mount · POST /v1/runs/commit | Start / mount (accepts @tag) / commit → manifest. Unknown run → 404, already committed → 409 |
GET /v1/manifests?target= · GET /v1/diff?a=&b= · GET /v1/replay?target= | Manifest by id or run · diff with per side provenance · replay |
GET /healthz | Liveness (unauthenticated) |
# resolve one URI (an @tag is honored) and pick three fields out
curl -s -X POST http://localhost:8080/v1/resolve \
-H 'Authorization: Bearer ***' \
-H 'Content-Type: application/json' \
-d '{"uri":"readproof://demo/policies/refunds@prod"}' \
| jq '.resource.ref, .snapshot.content_hash, .freshness.status'
Full request/response schemas: docs/api.md.
TypeScript SDK
@readproof/sdk is a typed, zero dependency client for readproofd (Node 18+ fetch). Every method maps to one endpoint; buildEvidence composes a bundle client side.
# not on a registry yet — build it from the repo and link it
cd sdk/typescript && npm install && npm run build && npm link
import { Readproof, buildEvidence, encodeEvidence } from "@readproof/sdk";
const readproof = new Readproof({
endpoint: "http://localhost:8080",
apiKey: process.env.READPROOF_API_KEY,
});
// one-off resolve
const policy = await readproof.resolve(
"readproof://acme/policies/refunds");
policy.content; // the bytes, decoded to text
policy.snapshot.content_hash; // "sha256:…"
// a run: mount = resolve + append to the manifest (starts lazily)
const run = readproof.run({ id: `turn-${turnId}` });
const refunds = await run.mount(
"readproof://acme/policies/refunds@prod"); // pinned by tag
const customer = await run.mount(
`readproof://acme/customers/${customerId}`);
const manifest = await run.commit();
// store this id next to the model call, or in the checkpoint
manifest.manifest_id;
// tags
await readproof.setTag(
"readproof://acme/policies/refunds", "prod", policy.snapshot.id);
await readproof.listTags("readproof://acme/policies/refunds");
refunds.freshness.status; // "use_tag"
refunds.resource.ref; // "prod"
// diff two runs, with the why
const diff = await readproof.diff("turn-41", "turn-42");
for (const e of diff.entries) if (e.status === "changed")
console.log(e.uri, e.source_revision_a, "->",
e.source_revision_b, e.unified_diff);
// replay and evidence
const replay = await readproof.replay(manifest.manifest_id);
replay.entries.every((e) => e.match); // true
const bundle = await buildEvidence(
readproof, manifest.manifest_id, { withContent: true });
// `readproof evidence verify` accepts this file
await fs.writeFile("bundle.json", encodeEvidence(bundle));
Errors are ReadproofErrors carrying the HTTP status and server message (an unknown tag names both URI and tag). content fields are UTF-8 text; for binary sources prefer the Go exporter for evidence.
MCP: use Readproof from Claude Code, Claude Desktop, Cursor
readproof mcp is a stdio MCP server. Registered documents become readable readproof:// resources (with provenance in _meta), and resolve / runs / diff / replay / tags / evidence become 13 tools the model can call.
Claude Code
# 1. embedded store — one local .readproof directory
claude mcp add readproof -- \
/abs/path/to/readproof mcp --data-dir /abs/path/to/.readproof
# 2. against a running readproofd (API key via env, never argv)
claude mcp add readproof --env READPROOF_API_KEY=sk-… -- \
/abs/path/to/readproof mcp --server https://readproofd.internal
Claude Desktop (claude_desktop_config.json) · Cursor (.cursor/mcp.json)
{
"mcpServers": {
"readproof": {
"command": "/abs/path/to/readproof",
"args": ["mcp", "--server", "https://readproofd.internal"],
"env": { "READPROOF_API_KEY": "sk-…" }
}
}
}
Try it — prompts that exercise the loop
- "Read
readproof://demo/policies/refundsand tell me the refund window." → the model callsresources/read;_metacarries snapshot id, content hash, source revision, observed at, decision. - "Now read the
@prodversion." → same resource,decision: use_tag, the pinned bytes. - "Start run
ticket-1234, mount the refunds and tone policies, commit, and give me the manifest id." →readproof_run_start→readproof_run_mount×2 →readproof_run_commit. - "Diff
ticket-1234againstticket-1200and explain why any input changed." →readproof_diffreturns per side source revision and observation time. - "Export evidence for
ticket-1234." →readproof_evidence_exportreturns the bundle JSON.
| Tool | What it does |
|---|---|
readproof_resources_list | Discovery: every registered document with source kind and policy. |
readproof_resolve | Read one document; bytes + snapshot id, content hash, source revision. May create a snapshot. |
readproof_history | Snapshots newest first, with the tags on each. |
readproof_run_start · readproof_run_mount · readproof_run_commit | Open a run, read and record, freeze into a manifest (returns the manifest id). |
readproof_manifest | Show a manifest by manifest id or run id. |
readproof_diff | Added/removed/changed, unified diff, each side's revision/observed at/ref. |
readproof_replay | Rebuild from storage and hash again; include_content: true returns the bytes. |
readproof_tag_set · readproof_tag_list · readproof_tag_delete | Manage uri@tag pointers. |
readproof_evidence_export | Build the in-toto bundle for a run (with_content optional). |
Resources are served via the template readproof://{namespace}/{+path} (so multi segment paths and @tag both work). Inline content is capped at 1 MiB with a truncation marker and the content hash. Stdio means local trust; --server mode inherits readproofd's API key auth. Details: docs/mcp.md.
LangGraph (and any framework with checkpoints)
The integration pattern is one line of state: mount inside a node, commit, and put the manifest_id in the checkpoint. Whatever can read the checkpoint later can replay the exact bytes of that turn.
// examples/langgraph-ts/src/graph.ts (condensed)
const GraphState = Annotation.Root({
question: Annotation<string>,
// this one line lands in the checkpoint with the rest of state
readproof_manifest_id: Annotation<string>,
readproof_entries: Annotation<MountedEntry[]>({
reducer: (_p, n) => n, default: () => [],
}),
answer: Annotation<string>,
});
async function loadContext(_s: GraphStateType, config: RunnableConfig) {
const run = ctxClient().run({
id: `langgraph-${threadIdOf(config)}`,
});
const entries = [];
// e.g. readproof://demo/policies/refunds, …/tone
for (const uri of CONTEXT_RESOURCES) {
const r = await run.mount(uri);
entries.push({
uri,
snapshot_id: r.snapshot.id,
content_hash: r.snapshot.content_hash,
content: r.content,
});
}
const manifest = await run.commit();
return {
readproof_manifest_id: manifest.manifest_id,
readproof_entries: entries,
};
}
// answer_question prompts the model with exactly
// `readproof_entries[*].content` — never a second read.
# 1. build the SDK
cd sdk/typescript && npm ci && npm run build
# 2. readproofd on the host, so it can read the fixture files
go build -o readproofd ./cmd/readproofd
./readproofd --addr :8080 --data-dir /tmp/readproof-langgraph &
# 3. run the graph — a fake in-memory model unless
# ANTHROPIC_API_KEY is set — and print the manifest id
cd examples/langgraph-ts && npm ci && npm run build
npm run start
# 4. change the source, then replay the checkpointed manifest:
# still "30 days", with the live source flagged CHANGED
printf 'Products can be refunded within 14 days.\n' \
> ../refund-agent/policies/refunds.md
npm run replay
Same shape works for Temporal/Restate activities (put readproof.resolve in an activity, the manifest id in the event history) and for plain request handlers (log the manifest id with the request id).
DeepSeek Harness
DeepSeek Harness (dsh) is DeepSeek's open source agent harness where every capability is a plugin and every context injection is logged. Readproof ships as a native bundle for it — the same 13 tools as the MCP server, registered through the harness's own tool service — plus a zero code MCP overlay.
Install as a bundle
# 1. a reachable readproofd (or let the plugin spawn one)
go build -o readproofd ./cmd/readproofd
# 2. add the bundle to the web profile
dsh plugin --profile web add \
./integrations/deepseek-harness/dsh-plugin-readproof
# 3. boot the profile with the plugin mounted
dsh web
Local dev without installing: dsh web --patch ./integrations/deepseek-harness/readproof-plugin.cordis.yml (set __READPROOF_REPO__ to your absolute repo path first). Zero code alternative: readproof-mcp.cordis.yml runs readproof mcp over stdio through @deepseek-ai/dsh-mcp-client; tools appear as mcp__readproof__readproof_*.
What the plugin does
| Capability | Detail |
|---|---|
| Tools | readproof_resources_list, readproof_resolve, readproof_history, readproof_run_start / _mount / _commit, readproof_manifest, readproof_diff, readproof_replay, readproof_tag_set / _list / _delete, readproof_evidence_export — descriptions written for the model, JSON results, 1 MiB inline cap. |
| Session runs | Every readproof_resolve made on behalf of a DSH session is also mounted into a Readproof run dsh-<sessionId>, committed when the session ends (or lazily on the next commit/manifest/diff/replay/evidence call). A session that keeps reading rolls to -2, -3, … |
| Config | endpoint (default http://127.0.0.1:8080), apiKey (or READPROOF_API_KEY), spawn (start a child readproofd; readproofdPath, dataDir, addr), sessionRuns, toolPrefix, systemPromptSection, maxInlineBytes. |
| Verified | 26 in process tests against a real readproofd through a real Cordis app; booted with @deepseek-ai/dsh@0.1.1-rc.1, all 13 tools registered. Not yet exercised with a live model turn; text content only. |
Source and README: integrations/deepseek-harness/dsh-plugin-readproof/.
Support agent example (open model on Ollama)
A complete, runnable agent in examples/support-agent: a customer support agent answers tickets from three policy documents governed by Readproof, using any chat model Ollama serves (no API key). Every ticket is one run; the manifest id is stored next to the answer.
cd examples/support-agent && npm install
# the whole story against a local model
OLLAMA_MODEL=llama3.2 npm run scenario
# or with no Ollama and no network at all
SUPPORT_FAKE_MODEL=1 npm run scenario
The scenario builds readproof/readproofd, starts a throwaway server, registers the policies (refunds require_fresh, shipping allow_stale 1h, tone pinned @prod), answers a ticket, edits the refund policy from 30 to 14 days, answers again (the decision flips), then diff names the revision that changed, replay returns the 30 day bytes the first answer saw, evidence exports a bundle the Go CLI verifies, and an edit to the house style changes nothing until promote moves the tag. Seven end to end tests run against a real server with a deterministic fake model.
Read the complete guide — architecture, every file and command, the real transcript, tests, environment variables, and how to extend it.
Observability
Every stage is traced; set OTEL_EXPORTER_OTLP_ENDPOINT and spans flow. Unset, instrumentation is a no op. Content is never attached to spans or metrics.
# one mount of a tagged resource, spans indented, attributes below each readproof.run.start readproof.run.id=r readproof.run.mount readproof.run.id=r · readproof.manifest.position=0 ├─ readproof.resolve │ readproof.resource.uri=readproof://demo/policies/refunds │ readproof.resource.ref=prod │ readproof.snapshot.content_hash=sha256:c8b0… │ readproof.snapshot.source_revision=sha256:c8b0bb212e93 │ readproof.policy.strategy=require_fresh │ readproof.policy.decision=use_tag │ gen_ai.data_source.id=readproof://demo │ ├─ readproof.resource.lookup │ ├─ readproof.policy.evaluate │ ├─ readproof.tag.lookup │ ├─ readproof.cache.lookup readproof.cache.hit=true │ └─ readproof.materialize └─ readproof.manifest.append readproof.run.commit readproof.manifest.id=manifest_… readproof.manifest.entries=1 readproof.manifest.merkle_root=a9b73469f1a6…
readproof.manifest.merkle_rooton the commit span equals the evidence bundle's subject digest — a trace and a bundle join on one field.gen_ai.data_source.idfollows the OpenTelemetry GenAI semantic conventions (readproof://<namespace>), so Phoenix, Langfuse, LangSmith, Datadog, Honeycomb can correlate retrieval by source. The proposal to carrycontent_hash/source_revision/uriongen_ai.retrieval.documentsis indocs/observability.md.- Metrics:
readproof_resolve_total/_duration_seconds/_errors_total,readproof_cache_hit_total/_miss_total,readproof_source_fetch_*,readproof_snapshot_created_total,readproof_manifest_created_total,readproof_run_committed_total,readproof_tag_resolve_total. docker compose upalready wiresreadproofdto a collector;docker compose logs -f otel-collectorshows spans arriving.
reference
CLI cheat sheet
| Command | Purpose |
|---|---|
readproof resource add <uri> --source-type filesystem|github|http … --policy require_fresh|allow_stale [--max-age d] | Register a resource |
readproof resource list · readproof inspect <uri>[@tag] · readproof history <uri> | Browse |
readproof get <uri>[@tag] | Resolve and print |
readproof run --id <run> <uri>… · readproof run start|mount|commit | Record a run → manifest |
readproof manifest <manifest|run> · readproof diff <a> <b> · readproof replay <manifest|run> | Inspect, compare (with why:), reproduce (strict) |
readproof tag set <uri> <tag> <snapshot> · tag list · tag rm | Movable pointers |
readproof evidence export <target> [--with-content] [--out f] · readproof evidence verify <bundle> [--offline] | Evidence bundles |
readproof mcp | Stdio MCP server |
readproof version · global --data-dir · --server · --api-key | — |
FAQ & status
Does Readproof replace my RAG pipeline / vector DB?
No. Readproof is about the source documents and what a run actually received. Keep retrieving however you do; mount what you retrieved (or the documents you retrieved from) so the run is reproducible. Derived chunk materializations are on the roadmap; v0.3 delivers raw bytes only.
What happens when the source is unreachable?
require_fresh fails the resolve (no silent staleness). allow_stale serves the cached snapshot if within --max-age. @tag never touches the source. replay never touches the source either.
How is this different from logging tool outputs in my tracing tool?
Traces key on spans; Readproof keys on source identity. You get a freshness policy, a run to run diff of inputs with the source revision that changed, and byte exact replay without fetching again — and the manifest's Merkle root shows up in your trace, so the two complement each other.
Is the store encrypted / multi tenant / signed?
v0.3 is a security baseline: no plaintext credentials at rest (env refs resolved at fetch time, redaction everywhere), optional single API key on readproofd, dev only Compose credentials clearly labeled, dependency scanning clean. Not enterprise IAM; bundles aren't signed yet. See the roadmap.
Status
v0.3.2 — tags/@ref, provenance aware diff, strict replay, evidence bundles, MCP server, OTel GenAI attributes, LangGraph example, support agent example (Ollama), DeepSeek Harness plugin, TypeScript SDK, Postgres/S3 or embedded SQLite storage, CI incl. a Compose integration run. Licensed Apache-2.0; renamed from Ctx to Readproof in 0.3.2; repository public launch pending. Roadmap (in order): public repo; Python SDK; trace context propagation over the HTTP API; MCP HTTP transport; a policy file (allowed sources / SSRF allow list / content scanning); signed + OCI distributed evidence bundles; tag promote; more adapters (S3, Confluence/Notion, generic git); Temporal/Restate helpers; auth beyond one API key; operator UI.
Docs in the repo: README.md · docs/api.md · docs/mcp.md · docs/evidence.md · docs/observability.md · docs/roadmap.md · examples/refund-agent · examples/langgraph-ts · examples/support-agent · integrations/deepseek-harness. Every command output on this page was captured from readproof 0.3.2 on 2026-08-21; ULIDs and hashes are trimmed for width.