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.

filesystem GitHub HTTP SOURCES (live) resolve policy: require_fresh allow_stale · @tag RESOURCE readproof://demo/policies/refunds fetch snapshotsha256:c8b0bb21… (30 days) snapshotsha256:8f4b0047… (14 days) SNAPSHOTS (content-addressed) after edit @prod movable pointer manifest run-a[0] refunds → c8b0bb21… manifest run-b[0] refunds → 8f4b0047… MANIFESTS (per run, ordered) mount mount replay run-a bytes of c8b0bb21… from the store no fetch · SHA256 re-verified diff run-a run-b why: revision c8b0 → 8f4b observed T1 → T2, ref evidence export in-toto Statement + Merkle root verify anywhere, offline store only — source may be gone
A run's manifest records the hash of every document it delivered. Replay, diff, and evidence read from the content addressed store, never the live source — so the refunds policy can change (or vanish) and 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.

Tip. Put 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

PolicyBehavior on resolveUse 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 1hReuses 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 pinResolving 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.

  1. 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-a
    Started run run-a
    Mounted …/policies/refunds -> snapshot snap_01M0JED935… (position 0)
    Committed manifest manifest_01M0JED936… for run run-a (1 entry)
  2. Read the manifest

    # takes a run id or a manifest id
    readproof manifest run-a
    Manifest 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…
  3. 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
  4. Replay — the invariant

    replay rebuilds 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-a
    Replaying 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.
Design note. Entry order is part of the manifest's identity — two runs that read the same documents in a different order are different runs, because the model saw different input. Readproof commits to the order; it never sorts it away.

Tags: @prod and friends

A tag is a named, movable pointer (resource, tag) → snapshot. It turns "the policy we reviewed on Tuesday" into something code can ask for by name — and something a manifest records.

# 1. point prod at the snapshot run-a was given
readproof tag set readproof://demo/policies/refunds prod \
  snap_01M0JED935NT0FNG7BWT6DG7XP
readproof tag list readproof://demo/policies/refunds
readproof tag rm   readproof://demo/policies/refunds prod

# 2. resolve by tag: no fetch, and the policy is not consulted
readproof get readproof://demo/policies/refunds@prod
readproof run --id run-c readproof://demo/policies/refunds@prod

# 3. the manifest records the ref it was mounted by (REF column)
readproof manifest run-c
# the file on disk now says 14 days — the tag still delivers 30
uri:          readproof://demo/policies/refunds@prod
ref:          prod
snapshot:     snap_01M0JED935NT0FNG7BWT6DG7XP
content_hash: sha256:c8b0bb212e93…
freshness:    tagged (@prod -> snapshot snap_01M0JED935…,
              observed 2026-08-21T15:18:10Z, policy not consulted)

--- content ---
Products can be refunded within 30 days.
  • Tag names match ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$; a tag can only point at a snapshot of its own resource.
  • Manifest entries store the bare URI plus the ref they were mounted by — moving a tag later can never change what a committed manifest replays.
  • Deleting a tag doesn't delete the snapshot; old manifests still replay.
  • Typical flow: every run uses require_fresh in staging; a reviewer runs readproof history, picks a snapshot, tag set … prod; production mounts @prod. Promotion is a pointer move, audited in history's TAGS column.

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
Containerized readproofd can't see your host filesystem. Use GitHub/HTTP sources there, or mount the files. For local on the host work, run 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 · pathWhat
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/resolveResolve a URI (accepts uri@tag)
POST /v1/runs · POST /v1/runs/mount · POST /v1/runs/commitStart / 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 /healthzLiveness (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/refunds and tell me the refund window." → the model calls resources/read; _meta carries snapshot id, content hash, source revision, observed at, decision.
  • "Now read the @prod version." → 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_startreadproof_run_mount ×2 → readproof_run_commit.
  • "Diff ticket-1234 against ticket-1200 and explain why any input changed." → readproof_diff returns per side source revision and observation time.
  • "Export evidence for ticket-1234." → readproof_evidence_export returns the bundle JSON.
ToolWhat it does
readproof_resources_listDiscovery: every registered document with source kind and policy.
readproof_resolveRead one document; bytes + snapshot id, content hash, source revision. May create a snapshot.
readproof_historySnapshots newest first, with the tags on each.
readproof_run_start · readproof_run_mount · readproof_run_commitOpen a run, read and record, freeze into a manifest (returns the manifest id).
readproof_manifestShow a manifest by manifest id or run id.
readproof_diffAdded/removed/changed, unified diff, each side's revision/observed at/ref.
readproof_replayRebuild from storage and hash again; include_content: true returns the bytes.
readproof_tag_set · readproof_tag_list · readproof_tag_deleteManage uri@tag pointers.
readproof_evidence_exportBuild 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

CapabilityDetail
Toolsreadproof_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 runsEvery 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, …
Configendpoint (default http://127.0.0.1:8080), apiKey (or READPROOF_API_KEY), spawn (start a child readproofd; readproofdPath, dataDir, addr), sessionRuns, toolPrefix, systemPromptSection, maxInlineBytes.
Verified26 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_root on the commit span equals the evidence bundle's subject digest — a trace and a bundle join on one field.
  • gen_ai.data_source.id follows the OpenTelemetry GenAI semantic conventions (readproof://<namespace>), so Phoenix, Langfuse, LangSmith, Datadog, Honeycomb can correlate retrieval by source. The proposal to carry content_hash/source_revision/uri on gen_ai.retrieval.documents is in docs/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 up already wires readproofd to a collector; docker compose logs -f otel-collector shows spans arriving.

reference

CLI cheat sheet

CommandPurpose
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|commitRecord 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 rmMovable pointers
readproof evidence export <target> [--with-content] [--out f] · readproof evidence verify <bundle> [--offline]Evidence bundles
readproof mcpStdio 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.