dumbtv
dumbtv takes the ads, screen tracking and update nags out of your Fire TV over ADB. No root, none of your apps touched, every step can be undone. Free and open source.
Your coding agent is one command away from deleting prod. Provenrail blocks the destructive call at the tool boundary, and signs every decision into a record your client or auditor can verify without
SecurityPython · HTMLGitHub @pofkyOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Guardrails for AI agents, with a receipt. Blocks destructive tool calls before they run and signs every decision into a hash-chained record anyone can verify with an open-source tool.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
A hard limit on what your coding agent may spawn, spend and touch. The control with no
equivalent anywhere else is the fan-out cap: Claude Code refuses a subagent at depth 3 of 3,
nothing limits how many subagents one session may start, and a runaway spawns dozens, each a
fresh context billed and metered on top of the one it came from. Provenrail counts them and asks
you before the twenty-first. Every decision is also signed into a hash-chained record anyone can
verify, trusting neither the agent nor the sink.
Start here: pr report --fanout, how wide your own sessions spreadRead the full README ↗The supervisor for coding agents. Undo what an agent did to your working tree, including what a shell command did. Stop every agent on the machine with one command, or from your phone. Get asked before a risky call runs when you are not at the keyboard. One hook, with adapters for Claude Code, Codex, Gemini CLI, Copilot and Cursor. Only the Claude Code adapter has been driven end to end; the other four are written from each vendor's published hook contract and are not yet driven against a live install. Nothing leaves your machine unless you set up the phone remote or anchor a record.
> Run exactly this shell command: rm -rf src
Deleted `src` (untracked, so not recoverable from git).
$ pr undo
#1 2s ago 0ebfd150 Bash rm -rf 2 files (-2)
$ pr undo last
Restored checkpoint #1: 2 file(s) written back, 0 removed.
~ src/app.py
~ src/wip.py
Verified: the working tree now matches the checkpoint exactly.
Changed your mind? `pr undo 2` puts it back.
That is a real Claude Code session and the real output. The files were never committed, so git had nothing to give back, and the agent said so.
/plugin marketplace add pofky/provenrail
/plugin install provenrail-guard@provenrail
It works from the next tool call, on whatever python3 the machine has (3.9 or later), with
no account and no config file. For the pr command line and the other hosts:
uv tool install git+https://github.com/pofky/provenrail # 0.6.1; PyPI still serves 0.4.3
pr guard install # Claude Code, in this project
pr guard install --host codex # or gemini, copilot, cursor
| Command | In the plugin | |
|---|---|---|
| Undo. A checkpoint of the working tree before every action that can change a file. | pr undo, pr undo last, pr undo <n> --diff | /guard-undo |
| Stop. Every tool call from every agent running the hook on this machine is refused until you resume. | pr stop, pr resume | /guard-stop |
| Loop breaker. Asks you after the same call, or the same short cycle, runs 8 times back to back. | on by default | on by default |
| File lanes. Asks before one session edits a file another session wrote in the last 15 minutes. | on by default | on by default |
| Phone remote. Approve, refuse, stop and resume from Telegram or ntfy. No server of ours. | pr remote setup, pr away, pr back | needs the CLI |
| Rules and caps. 45 rules that screen where a command points, a spend cap, a subagent cap. | pr guard status, pr guard budget 25 | /guard-status, /guard-budget |
| Scan. What in a cloned repository runs or steers an agent without asking. | pr scan [path] | /guard-scan |
| Brief. What the agent changed, from recorded facts, for whoever reviews it. | pr brief | needs the CLI |
Undo, exactly. The checkpoint lives in a second git object store under ~/.provenrail,
outside your repository. Your real .git is never opened: no stash, no commit, no ref,
nothing in git status. A restore snapshots the current state first, so an undo can itself be
undone, and a full restore is checked by tree hash before it says "Verified". On a 381-file
repository the first snapshot took 1.04 s and each one after it 0.03 s.
What it does not cover: files your .gitignore excludes (apart from root-level .env files),
any file over 50 MiB (skipped, and listed), commits and branches in your real repository,
databases, and anything outside the repository. It runs inside git repositories only.
Checkpoints are kept for 14 days. A nested repository or submodule is recorded as a pointer
and a restore never touches it. If a snapshot cannot finish in 10 seconds, checkpoints pause
for that project and pr undo says so; the call still runs. It is a way back from a mistake,
not a backup.
The loop breaker's threshold is measured. tools/measure_loops.py replays your own
transcripts. On the machine this was written on, across 3,186 sessions and 147,356 tool calls,
97.1% of sessions never repeated a block at all, and 0.47% would have been asked once.
The phone remote never fails open. No answer, no network, an unpaired chat, an ended evaluation: each leaves the verdict exactly what it was. It also never waits longer than the hook timeout the host was given, because a host that kills a hook treats it as having had no opinion. On a host with no approval prompt of its own (Codex, Gemini CLI), a rule that needs a human becomes a real question on your phone instead of a flat refusal (by design; those two adapters are not yet driven against a live install).
pr remote setup ntfy # no account: a secret topic in the ntfy app
pr remote setup telegram # a bot you create, answers only from you
pr away # questions go to the phone until `pr back`
What it is a defence against: a trusted agent that makes mistakes, while you are away. It is not a defence against a hostile program running in your own account, which can read the same files this does. Telegram is the stronger channel of the two.
The text of a question (the rule, the tool, and by default the command) goes from your machine
to the channel you chose. pr remote config --detail shape sends the verb and flags only, and
a command longer than 600 characters is cut with a line saying how much is not shown. The remote is free for 14 days from setup with no account or card; after that questions stay
on your machine unless you buy the $9 a month plan, which also covers timestamp anchoring of
the signed record. Everything else in the table is MIT licensed and free.
Tuning. All of it is on by default and each part has a switch in .provenrail.json:
{
"undo": {"enabled": true, "include": [".env", ".env.*"]},
"watch": {"enabled": true, "repeats": 8},
"lanes": {"enabled": true, "minutes": 15},
"remote": {"detail": "shape"}
}
That file is in your working tree, where an agent can write it and a cloned repository can ship it. So it may switch a feature off only out loud (you are told once a day), and for the remote it may only make things more private or the wait shorter. When the phone is asked, how much of a command it is sent, and how long it waits are yours alone:
pr remote config --ask auto --detail full --wait-s 300
Every decision can also be signed into a hash-chained record anyone can verify, trusting neither the agent nor the sink. That part is described under "The record underneath".
pr report --fanout: how wide your own sessions already spreadYou already have the data. Claude Code writes a transcript of every session it runs, and they
pile up in ~/.claude/projects whether or not you ever look at them. One command reads them:
uv tool install provenrail # or: pip install provenrail
pr report --fanout
No setup, no config file, no account, and no network call. It reads transcripts that are already
on your disk, so it tells you something the first time you run it, before you have changed
anything about how you work. On the machine this was written on it read 2,163 transcripts,
3.79 GB across 23 active projects, in 128 seconds. Trimmed, and run with --share so the
project names below are hashes:
PROVENRAIL REPORT
942 sessions across 23 projects, over 31 days, ran 79,883 tool calls
and spent an estimated $7,863.51.
517 of those would have been stopped: 23 refused, 494 sent to you to approve.
2,163 transcripts, 3.79 GB, 128.0s
COST
estimated total $7,863.51
model calls priced 69,573
by model
claude-opus-5 $6,646.54 41,554 calls
claude-sonnet-5 $600.45 15,412 calls
claude-sonnet-4-6 $471.85 11,305 calls
Claude Code recorded its own total for 49 of these sessions: $5,704.12,
against $5,015.01 estimated here for the same ones.
Spend is estimated at API list price, and notional on Pro and Max flat-rate
plans.
ACTIVITY
sessions 942
first and last 2026-08-17 to 2026-09-16
tool calls 79,883
bash commands 51,494
files written or edited 7,027 distinct
RISK your own commands, replayed offline through the shipped rules
tool calls screened 79,883
would have been refused 23 0.03%
would have been sent to you 494 0.62%
rules that would have fired
138 ask production.deploy-command
119 ask access.disarm-the-guard
104 ask destructive.force-remove
Those are one machine's numbers, not a benchmark. Run it on yours and the numbers are yours.
Four flags. --since 2026-09-01 limits the window and skips whole files on their mtime.
--project foo narrows to project names containing foo. --json prints a versioned
machine-readable document. --share produces the version that is safe to post: it drops the
root path, replaces every project name with a short hash, and reduces each command to its verb
and flags so no operand can carry a path, a hostname or a key.
Two honest notes about the dollar figure. It is estimated at API list price, and notional on Pro and Max flat-rate plans, where there is no per-token charge at all. And it is a floor: over the 49 sessions where Claude Code recorded its own total, the estimate came out about 12 per cent low. Two known causes are unmodelled, because neither is visible anywhere in a transcript: fast mode bills Claude Opus 5 at $10/$50 per million tokens against the standard $5/$25, and web search bills $10 per 1,000 searches. No multiplier is guessed at to close the gap, so the number misses low rather than high.
pr report reads Claude Code transcripts. No other agent host is read.
Inside Claude Code:
/plugin marketplace add pofky/provenrail
/plugin install provenrail-guard@provenrail
That is the whole setup. No pip install, no account, no server, nothing leaves your
machine, and the next tool call your agent makes is already being checked. 45 rules are
armed with no config file, because installing a plugin called "guard" is the opt-in and a
guard that waits for configuration protects nobody.
Read the public data-loss reports filed against coding agents and the command is almost
never rm -rf. It is git reset --hard, git checkout -- ., git clean -fd,
git stash drop, prisma migrate reset, a provider volume delete. Those are stopped, and
they are stopped by what they would actually destroy: the git rules run git status and
check for unpushed commits, so on a clean, pushed tree they say nothing, because there
nothing is at stake.
The same idea runs the rest. rm -rf .next inside your project is allowed outright;
rm -rf ~/, a whole disk, a system path or an unset variable followed by a slash is
refused; a delete that merely leaves the project asks. dd of=/dev/...,
terraform destroy, a force push, DROP TABLE, DELETE with no WHERE,
kubectl delete namespace, chmod 777 and committed API keys are denied. Actions that
need a human (reading .env, deploying, migrating, a cloud teardown) become a permission
prompt, and the approval is recorded as human oversight.
Measured on one frozen corpus of 36,977 Bash commands from 1,247 real agent sessions,
replayed with the directory each ran in: 0.3.1 interrupted 923 and refused 305; 0.4.0
interrupts 222 and refuses 16, and fifteen of the sixteen are this repository's own
adversarial test fixtures. On 40 commands taken from public data-loss reports, 0.3.1
allowed all 40 and 0.4.0 allows none. Run it on your own transcripts with
python tools/measure_guard.py, which makes no network call.
/guard-status shows what is armed and what it has actually stopped. /guard-card prints
a summary safe to post: every command is reduced to its verb and flags, and every operand
is dropped, so it cannot carry a path, a hostname or a key. /guard-rules lists every
rule, including the packs that are off by default. To change or disable it, write a
.provenrail.json at your repo root; it wins over the defaults completely, including an
explicit {"policy": {"use": []}}, which arms nothing and says so once a day.
Installing the CLI upgrades the same local history in place, in the same directory, and signs it:
uv tool install provenrail # or: pip install provenrail
pr guard receipt # a signed, hash-chained export of what was blocked
pr verify guard-receipt.json
Full guide: https://provenrail.com/claude-code-guardrails
pr attest --blame # signed AI authorship attestation over the history
pr attest-anchor ai-attestation.json # RFC 3161 timestamp from an independent authority
pr attest-verify ai-attestation.json --receipt attest-receipt.json
Software contracts have started asking suppliers to disclose AI authorship. Every other way
of answering, a Co-authored-by: trailer or a git note, is written by the party being asked
and can be edited afterwards. Signing binds the answer to a set of commit ids, which are
themselves content hashes, so the document cannot be pointed at a different tree or quietly
rewritten once a dispute begins, and pr attest-verify re-derives the findings from the
reader's own clone rather than reading the summary back to them.
It does not prove the findings are true, and says so in its own limits field: a commit
whose author stripped the trailer reads as human-authored, and unlisted tools understate AI
involvement rather than overstate it. A commit authored inside a recorded Provenrail session
carries a higher evidence grade, because a signed record is not something the committer
typed. Full detail: https://provenrail.com/ai-code-attribution
In CI, so the document exists at the moment the commits landed rather than being written months later by someone with a reason to shade it:
- uses: actions/checkout@v4
with: {fetch-depth: 0} # a shallow clone would attest to a stump; the action refuses one
- uses: pofky/[email protected]
with:
blame: "true"
signing-key: ${{ secrets.PROVENRAIL_SIGNING_KEY }} # optional; without it the signature is anonymous
licence-key: ${{ secrets.PROVENRAIL_LICENCE_KEY }} # optional; adds an independent RFC 3161 timestamp
- uses: actions/upload-artifact@v4
with: {name: ai-attestation, path: ai-attestation.json}
pr guard hook --use destructive,secrets arms exactly those packs for a single hook
invocation, without a .provenrail.json, which is what you want when the hook command is
generated by another tool. A pack name that does not exist refuses to enforce and says so on
stderr, rather than silently arming nothing, and so does a --use that names no pack at all.
pr --version prints the installed version.
pr runs the wrong programpr is also a POSIX utility, the coreutils paginator. On most systems the install directory
comes first on PATH and ours wins, but on Windows under Git Bash it does not: pr --version
prints pr (GNU coreutils) 8.32 and pr verify --pin ... fails with unknown option. If you
see that, use the module entry point, which nothing can shadow:
python -m provenrail verify bundle.json --pin pin.json
Every subcommand is identical.
pr verify (and pr-verify) exits 0 when the record verifies. It exits 1 when it reached
a verdict that is not a pass: TAMPERING DETECTED, NOT CONFIRMED, NO RECORDS, or
NOT A PROVENRAIL BUNDLE (valid JSON, but not a format this verifier recognizes). It exits 2
when it never reached a verdict at all, because the file could not be read as JSON (not UTF-8,
empty, truncated) or the arguments were wrong; that case prints RESULT: NOT A BUNDLE. Gate
your job on 1 and treat 2 as "check the path you passed", or a typo will page someone about
a tampering incident that never happened. pr verify --help restates this.
pr guard hook always exits 0: a hook that exits non-zero would block your agent over our
own error, so a denial is carried on stdout instead. pr guard receipt exits 0 when it wrote
the receipt, whether or not the receipt contains denials, since finding them is why you ran it.
To gate a job on "did this run try anything it was not allowed to?", use
pr risk guard-receipt.json, which exits 1 when there are denials.
Install the SDK, capture model calls and tool calls, and every record is pushed to an
append-only sink where it cannot be silently altered, reordered, deleted, or back-dated.
Anyone can verify the record with the open-source pr-verify tool.
This is the self-serve Provenrail SKU. It is a forensic black box, not a compliance certifier. Witnessed anchors and regime evidence reports ship today. Cross-account WORM storage and TEE-backed keys do not exist yet; they are on the roadmap and are not for sale, here or anywhere, until they do.
Read this before trusting it. Honesty about the boundary is the product.
| Property | Guaranteed? | Mechanism |
|---|---|---|
| A flushed record cannot be altered without detection | Yes | client Ed25519 signature + hash chain; server re-chains on receipt |
| Records cannot be reordered or deleted mid-stream without detection | Yes | dual chain (client prev_hash links + server receipt chain) |
| Records existed no later than a trusted time | Yes, when anchored | RFC 3161 trusted timestamp over a Merkle root of the receipt chain |
| The sink cannot fabricate agent actions | Yes | only the device key can sign; the sink does not hold it |
| Completeness (a hostile agent cannot skip recording) | No, never claimed | an in-process logger can refuse to emit; the out-of-process sidecar narrows this (see below) but only egress control makes capture mandatory |
| The sink cannot equivocate (show two anchor histories) or drop published anchors | Yes, when witnessed | per-account RFC 6962 transparency log + independent witness cosignatures (SPEC.md section 9) |
| Tail records after the last anchor cannot be truncated | Bounded | anchor frequency for the unanchored tail, plus client pin; published anchors are witness-protected |
| Protection against the machine owner (B2C single host) | No | the owner holds the key and can choose not to flush |
The honest one-line claim: "Anything your agent did, once it reaches the sink, is immutable and verifiable. Completeness is never claimed."
The adversary is the agent (or a prompt-injection of it). The agent can refuse to log,
but it cannot forge, alter, reorder, or delete anything that already reached the sink, nor
back-date it past a trusted timestamp, without pr-verify reporting it. Defense against a
malicious sink operator (who could truncate the tail or show different histories to
different parties) is the transparency / witness log: a per-account append-only RFC 6962
Merkle log over the anchor receipts, with inclusion and consistency proofs an auditor checks
offline and independent witness cosignatures on each head. A witness will not cosign a fork or
a rollback, so a sink cannot equivocate without compromising a quorum of witnesses. Honest
boundary: witnessing proves append-only-ness and anti-equivocation, never completeness; an
un-cosigned checkpoint is reported amber, not green (SPEC.md section 9.8). The client pin still
covers the unanchored tail between the last cosigned checkpoint and now.
Capture topology and the completeness gap. In-process capture (the SDK and the drop-in
instrumenters) is the easiest to adopt but shares the agent's address space, so a hostile agent
can skip the logger. The capture sidecar (pr sidecar) is a recording reverse proxy: the
agent points its model client at the proxy, which forwards to the real provider and records each
call from a separate process the agent does not control. To dodge recording the agent must then
avoid the proxy entirely, which means getting no model response when egress is locked to the
proxy. The sidecar does not by itself prove completeness; its strength equals the egress control
around it. The deployment contract is "force all model egress through the sidecar" (network
policy / firewall). With that, capture is mandatory; without it, the sidecar is a strong default,
not a proof. We never overstate this.
agent process off-box sink auditor
------------- ------------ -------
SDK (capture) ingest API (append-only) pr-verify
model calls ---> server receipt chain ---> recompute every
tool dispatch (re-chains on arrival) hash, signature,
hash-not-content RFC 3161 anchor over chain link, Merkle
sign + chain Merkle(receipt heads) root, timestamp
Two independent chains: the client signs and links each record (prev_hash), and the
server independently re-chains records in arrival order. A coherent re-chain by a malicious
sink is still caught by the client chain; a client edit is caught by the server's stored
bytes. Anchoring binds the whole thing to a trusted external time.
Repeated from the top for readers who arrived here from the table of contents. The
Claude Code plugin needs nothing else installed:
/plugin marketplace add pofky/provenrail, then
/plugin install provenrail-guard@provenrail. It blocks, asks and keeps a local history on
its own, and installing the CLI later signs that history in place rather than starting a new
one.
To wire the hooks directly instead, or to record as well as block:
uv tool install provenrail
pr quickstart # local sink, no account, nothing leaves your machine
pr guard install # arms destructive + secrets + production + access, installs the hooks
From your next Claude Code session in that folder:
rm -rf, terraform destroy, git push --force, DROP TABLE, chmod 777 and leaked
API keys are blocked before they run, with the rule that fired shown to the agent..env, a deploy, a migration) becomes a permission
prompt, and your approval is recorded as human oversight rather than being blocked outright.pr guard status # what is armed, and what it blocked
pr guard receipt # export the proof, then verify it yourself
The verdict is computed offline, before anything touches the network, so a sink that is down can never turn a deny into an allow. If a decision cannot be recorded it is journalled locally and reported as unsigned and pending, never as proof.
Honest scope: this covers tool calls Claude Code routes through its hooks. It cannot constrain a process that never calls them. Other agent hosts are not claimed until their hook contract has been read and tested.
Simplest possible: one command to set up, two lines in your code.
pip install provenrail
pr quickstart # starts a local sink and writes .provenrail.json (no tokens to copy)
import provenrail as fr
with fr.record("my-agent"):
... # your agent runs; model and tool calls are captured automatically
That is the whole setup. fr.record(...) provisions a stream, builds the recorder, opens a
signed session, and seals + drains it off-box on exit. Connection details come from
.provenrail.json (or env vars, or fr.configure(...)), so your code carries no URLs or
tokens. A decorator form exists too: @fr.recorded("nightly-job"). Stop the local sink with
pr quickstart --stop. Point at your own or a hosted sink with pr quickstart --url <URL>.
Run your agent as many times as you like: each run becomes its own sealed session on the same
stream, and one export verifies them all. The first run creates .provenrail.key, the device
signing key reused by every later run; keep it out of version control (gitignore it next to
.provenrail.json). Losing it only means future runs sign under a new identity.
Budgets keep their running totals in .provenrail-spend.json in the project root, with a
.provenrail-spend.json.lock beside it so concurrent agents cannot lose each other's writes.
Both are local state, not records: gitignore them. PROVENRAIL_SPEND_LEDGER moves the pair
somewhere else if you would rather share one budget across several checkouts.
The same capture is available for TypeScript agents (Node 20+), in sdk-js/:
npm install provenrail
import { record } from "provenrail";
await record("my-agent", async (pr) => {
await pr.recordModelCall("openai", "gpt-5", { prompt }, out, { usage });
});
A run recorded in TypeScript is byte-for-byte compatible with one recorded in Python: the same sink
accepts it and the same two verifiers (pr verify and the in-browser verify.js) prove it. This is
enforced by a cross-language test (tests/test_js_sdk.py): records signed by the JS SDK are verified
by the Python verifier and the JavaScript verifier, and tampering is caught by both.
Other entry points:
pr demo # self-contained: records a session, anchors it, writes bundle.json + pin.json
pr verify bundle.json --pin pin.json # verify, trusting neither agent nor sink
pr report --regime eu-ai-act bundle.json --md # map the record to a regime
pr diff run-a.json run-b.json # diff two runs with provable fidelity
pr serve --anchor rfc3161 # run the sink yourself (real trusted timestamps)
pr sidecar --upstream https://api.openai.com # out-of-process capture proxy (point base_url here)
pr witness --log <origin>=<pubkey> # run an independent witness on separate infrastructure
# or: docker compose up
Running the sink in account mode (the default, without --open) with paid plans in front of it
needs one environment variable:
PROVENRAIL_BILLING_SECRET=<the secret your payment provider's webhook sends> pr serve
Raising an account's plan requires that secret in an X-Provenrail-Billing-Secret header, so
only the payment provider can do it. Leave it unset and no upgrade can be applied over the API
at all, which is the right default for a self-hosted sink with no billing in front of it.
Lowering a plan never needs it: nobody defrauds anyone by asking for less.
Two responses worth knowing if you write your own ingest client rather than using the SDK:
409 means a record reused a client seq that this session already holds with different
content, and nothing in the batch was stored (accepting it would break the chain permanently).
422 on ingest means the record cannot be canonicalized, usually a float or an integer outside
the JS-safe range, so it could never be hashed or verified.
pr sidecar --upstream https://api.openai.com --port 8788
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8788") # the only change in your code
Every model call now flows through a recorder in a separate process. Lock outbound model egress
to the sidecar (firewall / network policy) and capture becomes mandatory rather than a default.
Add --fail-closed to refuse any call that cannot be recorded.
from provenrail.integrations import instrument_openai, instrument_anthropic, instrument_mcp
instrument_openai(openai_client, fr) # every model call now captured automatically
instrument_anthropic(anthropic_client, fr)
instrument_mcp(mcp_session, fr) # every MCP call_tool captured as an mcp_call
from provenrail.ingest_client import provision_stream
from provenrail.sdk import FlightRecorder
prov = provision_stream("http://127.0.0.1:8000", label="my-agent")
fr = FlightRecorder("http://127.0.0.1:8000", prov["write_token"], prov["stream_id"])
@fr.tool("search")
def search(q): ...
with fr.session({"agent": "demo"}):
fr.record_model_call("anthropic", "claude", request="...", response="...")
search("hello") # captured at the tool-dispatch boundary
fr.record_decision("ship it")
Share a read-only proof: GET /share/<share_token>. Export + verify:
curl -H "Authorization: Bearer $READ_TOKEN" \
http://127.0.0.1:8000/v1/streams/$STREAM/export > bundle.json
pr verify bundle.json
Privacy default is store-hash-not-content: prompts and outputs are SHA-256 hashed, not
stored, unless you pass capture_content=True. Tokens are scoped strictly: write can only
append, read can only read, share is public read-only, and no delete scope exists.
You can run every part of this yourself, and most people should. But there is one thing self-hosting cannot give you, by construction: you cannot be your own independent party. A timestamp you produced yourself proves your chain is internally consistent. It does not prove the chain existed before the dispute that made anyone ask.
So send the root, not the records.
# Your sink, your machine, your data. Export as usual.
curl -H "Authorization: Bearer $READ_TOKEN" \
http://127.0.0.1:8000/v1/streams/$STREAM/export > bundle.json
# 32 bytes leave this machine: the Merkle root over your record hashes, and how far it reaches.
pr anchor-push bundle.json --url "$ANCHOR_URL" --key "$ACCOUNT_KEY" \
--receipt-out receipt.json
These two commands arrived in 0.2.31; on an older install they exit with invalid choice.
$ANCHOR_URL is any Provenrail server reachable by both you and whoever checks your evidence.
Two of them exist. pr serve on a second machine works end to end and costs nothing, but note
what it does not buy you: a service you run yourself gives you the append-only history and the
receipt, and cannot make you independent of yourself. The other is
https://provenrail.com, which we operate, where the credential is the licence key from a paid
plan and every anchor carries an RFC 3161 timestamp from a public authority rather than one we
asserted. Switching between them is a change of URL and nothing else.
The anchor service stores the root, the coverage and a timestamp, and publishes them at a URL anyone can read without an account. It has no field a record could arrive in, so it cannot receive your prompts or outputs even if you tried to send them.
Your client, your auditor, or anyone else then checks the two files against each other, offline, without asking us anything:
pr anchor-verify bundle.json receipt.json
# RESULT: VERIFIED. These records existed in this order at that time.
anchor-verify checks three things, and all three are needed. The receipt is a real, unforged
commitment to a root, checked with the same code pr verify uses. The bundle hashes to that root,
which ties the receipt to these records and not to some other set the same signer once anchored.
And the bundle's own chain verifies, because the root commits to record hashes, not to record
contents: without that check, editing a payload while leaving its hash alone would slip through.
Drop the tail and the receipt says how many records it covered, so the gap shows. Neither you nor we can make a rewritten history match a root that was already published.
Coverage is monotonic per stream. If a stream is anchored to 1000 records, an anchor covering 400 is refused, and so is a second anchor at 1000 with a different root. Refusing to sign a shorter or forked history is the service, not a limitation of it.
Integrity core
@tool
decorator for the local tool-dispatch boundary a network proxy never sees, and a
non-blocking async transport (background batched flush with retry and an ordered drain
on session close, so capture never slows the agent).call_tool capture so tool use through MCP is recorded at the
dispatch boundary.POST /v1/anchors takes a Merkle root and a coverage count and
nothing else, enforces monotonic coverage per stream, and publishes the result at a public,
unauthenticated URL an auditor can read without an account. pr anchor-push sends a local
bundle's root; pr anchor-verify checks a receipt against a bundle offline, without calling
the service that issued it.pr verify --pin detects a
malicious sink truncating the tail.pr verify
checks the CMS signature and certificate chain against a bundled TSA root, trusting
neither the agent nor the sink.Daily-use surface (the dashboard makes the integrity worth opening every day)
/app: an authenticated dashboard over every stream, session, and event,
with live integrity verdicts, token and estimated-cost analytics rolled up from captured
usage, an expandable per-event timeline, and a live-session indicator. Mobile-first./v1/webhooks, or the Alerts tab) and get an
HMAC-signed POST the moment a stream stops verifying (integrity.tampered), recovers, or
lands its first trusted anchor.Trust and compliance surface
/verify: anyone can drop a bundle and re-run verification with no
install and no account. It verifies FULLY CLIENT-SIDE using a second, independent
JavaScript verifier (web/verify.js, WebCrypto), so you trust not even our server; it is
conformance-tested to agree byte-for-byte with pr verify. The lockstep covers enforcement
as well as integrity: the browser verifier replays the committed policy, including spend
caps, and its cost arithmetic is parity-tested against pricing.py so a budget can never
replay differently in the browser than on the CLI. One check is deliberately CLI-only: RFC
3161 timestamp validation needs ASN.1/PKCS#7 parsing, so the browser reports such an anchor
as present but unvalidated rather than pretending to have checked it.<img src=".../badge/<share_token>.svg"> re-verifies on every load
and turns amber or red if the record ever stops verifying.pr pack, or /v1/streams/{id}/evidence): a self-contained,
reproducible ZIP with the bundle, a regime evidence report (EU AI Act Art. 12 / HIPAA
164.312(b) / generic), a verification guide, and a SHA-256 manifest, for an auditor./v1/streams/{id}/export.ndjson): one flattened, hash-linked record per line
for Splunk / Elastic / Datadog, content-hashed by default so it does not leak prompts.pr CLI (serve / demo / verify / report / pack), Dockerfile, CI, ruff-clean source, a
marketing site in web/, and a formal SPEC.md so anyone can build an independent verifier.The evidence format and the exact verification algorithm are in SPEC.md. A clean
pr verify recomputes every hash, signature, chain link, Merkle root, and timestamp from
scratch; it never trusts a derived field in the bundle.
The test suite covers hermetic real-RFC-3161 verification with tamper cases
(content edit, deletion, coherent malicious re-chain, backdating, tail truncation),
accounts/auth, token revocation and expiry, rate limiting, auto-anchoring, async transport,
Unicode NFC canonicalization, the Run Explorer dashboard and analytics, cost estimation,
integrity alerting, the hosted verifier and embeddable badge, evidence packs, SIEM NDJSON
export, MCP capture, and a JavaScript verifier conformance-tested against the Python one.
Plus committed-policy verification (the active guardrails are signed into the session and
re-checked offline), richer guardrail rules (content gates and per-session limits), and heuristic
coherence signals in both the verifier and the dashboard. The public transparency / witness
log is shipped: per-account RFC 6962 Merkle log over
the anchor receipts, inclusion and consistency proofs, C2SP checkpoint / signed-note /
cosignature wire formats, a reference witness plus a C2SP witness HTTP client with split-view
and rollback alarms, public /v1/tlog/{account}/checkpoint|inclusion|consistency endpoints,
verifier steps 7 to 8 in both Python and JavaScript (conformance-tested on witnessed,
unwitnessed, and forged-proof bundles), and a green-witnessed / amber-proofs badge axis.
Enterprise and forward-looking layers are shipped too: RBAC (org members, least-privilege
owner/admin/member/viewer roles, per-member keys), OIDC single sign-on (server/sso.py, strict
RS256/EdDSA ID-token validation with JIT provisioning), a hash-chained tamper-evident access log
(audit-of-the-audit), an agent identity / KYA registry (the verifier confirms the device key is
the one registered for the agent), an active policy / guardrail layer (declarative deny /
require-oversight rules and a per-session spend cap enforced at the dispatch boundary, with the
decision recorded in the signed chain), and run replay/diff with provable fidelity (pr diff,
diff computed over verified bundles). Dashboard and pages verified end to end in a real browser
(dark / light / mobile), including fully client-side verification.
Selective-disclosure redaction reconciles an immutable trail with the right to erasure: a
sensitive field is recorded as a salted commitment (never cleartext at the sink), the opening is
held only by the operator, and you can later disclose it to an auditor (any verifier confirms it
opens the commitment) or erase it permanently (destroy the opening; the field becomes
unrecoverable while the record stays tamper-evident and verifiable). Wrap a value with
provenrail.redactable(...); check disclosures with pr verify --openings or pr disclose.
Both the Python and JavaScript verifiers recompute the commitment identically (conformance-tested).
See SPEC section 17.
Independently-verifiable credibility, auditor artifacts, and trust robustness are deeper too:
frozen conformance vectors (tests/vectors/, with a documented manifest) let anyone validate
their own verifier against the same golden bundles; a full verification report
(pr report --html, also folded into the evidence pack as report.html) renders the full verdict,
findings, trusted-time and witness status, committed-policy result, coherence signals, and a
chronological timeline as a printable document; and TSA trust is now operator-extensible
(pr verify --tsa-root host=cert.pem, trust.add_root(...)) with a MultiTSAAnchor that fails
over across timestampers for availability.
Guardrails are configurable without touching agent code and alert in real time: enable
prebuilt rule packs by name ({"policy": {"use": ["destructive", "secrets", "money"]}},
eight packs (35 rules) covering destructive tools, leaked credentials, money movement, production
changes, privilege escalation, exfiltration shapes, and per-session blast-radius caps; list
them with pr rules, check which would match your actual tool names with
pr rules --check bundle.json), or declare a
policy block in .provenrail.json (deny / require_oversight / limit rules with glob and regex
matching, plus spend budgets) and the deployment owner sets the rules even if someone else
wrote the agent. A malformed policy is rejected loudly rather than ignored, because a typo that
silently disables a guardrail leaves you believing you are protected when you are not. When a rule
blocks an action, a policy.denied webhook fires at ingest, in the same moment the record
lands, not on the anchor schedule; delivery is HMAC-signed and runs off the request path so a slow
endpoint of yours can never slow the agent. Review afterwards with pr risk bundle.json (exits
non-zero if anything was blocked, so CI can gate on it), the dashboard's Blocked tile and red
timeline rows, or the NDJSON SIEM export. Nothing is suspicious by default: there is no built-in
threat library and no anomaly detection, so with no policy declared nothing is ever blocked.
Spend budgets stop the failure everyone actually has: an agent that runs all night across hundreds of short sessions and is discovered when the invoice arrives. A budget denies the model call that would cross it, at one of three scopes:
{"policy": {"budgets": [
{"scope": "session", "limit_usd": 5, "warn_at": 0.8},
{"scope": "day", "limit_usd": 50},
{"scope": "total", "limit_usd": 500}
]}}
Where a budget binds, and where it does not. A budget is evaluated on the model call,
which is the only place cost is created, so it fires on calls made through the SDK
(record_model_call, or the drop-in instrumentation). pr guard alone is not enough: Claude
Code hooks fire on tool calls such as Bash, Edit and Write, and a model call never passes
through one, so a policy installed by pr guard install alone gives you tool guardrails and
spend reporting but not spend enforcement. Instrument the model client as well to get
both. pr guard status shows which of the two you have.
day and total survive process exit via a local ledger, so they bind across runs where a
session cap cannot. The ledger is local and editable: it is a convenience that makes a cap
work across processes, never evidence, and an operator who needs a cap the agent's own host
cannot lift enforces it at the sink. Crossing warn_at does not block; it writes a warning into the signed chain
and fires a budget.warning webhook while the run can still be stopped, with budget.exceeded
when a cap actually bites. The budgets are part of the committed policy hash, so an auditor can
prove which cap was in force. Check spend with pr spend (per run from a bundle, per agent from
the ledger) and headroom with pr guard status.
Finance rollups answer the question a budget owner actually asks. GET /v1/spend?group_by=
groups estimated spend by agent, project, team, model, day, stream, or session
over any date window, with &format=csv for the spreadsheet the conversation happens in. The
grouping keys are your own session metadata, which lives inside the signed record, so a finance
report is derived from the same evidence as everything else instead of a side table someone
could edit. Spend with no project set rolls up as (unattributed) rather than vanishing: a
report whose rows do not sum to the total makes an underspend look real.
Reconcile the estimate against the bill. pr reconcile run.json --invoice usage.csv aligns
recorded spend with a provider usage export and reports the drift per model, plus the finding
that matters most: spend on the invoice that no recorded run explains, which means calls
happened outside the recorder. It exits non-zero when it finds any, so a finance cron can gate
on it. The invoice is an untrusted input used to interpret the record, never to amend it.
Costs are estimates from reported token usage and a dated public price table, never a
substitute for your provider's invoice, and they are labelled that way everywhere. The estimator
handles what naive ones get wrong: cached tokens priced at the cache rate and not double-counted
(providers disagree about whether the prompt total already includes them), and reasoning tokens
reported but never billed twice. Negotiated or committed-use rates go in
.provenrail-prices.json and override the list price. A model with no verified rate is reported
as unpriced rather than silently counted as free.
Approvals for agents with no human at the keyboard. Inside Claude Code a
require_oversight rule becomes the permission prompt, and the person answering it is the
oversight. A headless agent has no prompt, so the same rule could only deny. Set
approval_timeout and the agent instead pauses, opens a request, and an approval.requested
webhook delivers a one-click approve and a separate deny link to whoever is on call. An
approval is recorded as a human_oversight event in the agent's own signed chain, and the
policy is then re-evaluated: the approval flows through the guardrail rather than around it.
It fails closed in every other case, including an unanswered request, an expired link, and an
unreachable sink. Opening a link only ever shows the pending action; the decision happens
when the button on that page is submitted, because mail gateways and chat unfurlers fetch
every URL they see and a link that approved on open would be approved by a robot. The two
links are unrelated single-use secrets stored only as hashes, so a leaked database cannot
approve anything. An approval unlocks the one rule it answered, not every oversight-gated rule
in the session. Only require_oversight is ever offered to a human: a hard deny and a blown
budget are decisions you already made.
Honest boundary: the sink cannot forge an approval, because it does not hold the agent's signing key. It can still deceive one, by telling the waiting agent a request was approved when nobody clicked. Approvals are as trustworthy as the sink you point the agent at.
Replay scrubber. The session view steps through a run one action at a time (arrow keys, a
scrub track, a detail pane) and will compare it against any other run of the same agent,
marking the steps that diverge and jumping to the first one. That is where debugging actually
starts: the run that broke only means something next to the run that worked. The in-browser
alignment is a display aid; pr diff is the version that runs over verified bundles.
The active policy / guardrail layer is deeper now: the policy is committed into the signed
session-start record (so a verifier proves which guardrails were in force and that no executed
action violates a re-verifiable deny / limit / spend rule, SPEC section 13), with content gates
(arg_contains regex over argument text, for secret / PII blocking) and per-session call limits
(max_per_session). The verifier and dashboard also surface heuristic coherence signals
(clock skew, time gaps, missing usage, no-governance runs, self-inconsistent seal) that point a
reviewer at structural oddities without ever changing the cryptographic verdict (SPEC section 14).
Hosted-readiness and out-of-process layers are shipped too: a persisted server identity (the
transparency-log and KYA-registry keys live in the DB so a sink reboot keeps the same public
keys an auditor pinned), usage metering with per-account monthly plan quotas and a GET /v1/usage
billing surface (server/plans.py), a standalone out-of-process witness (pr witness,
server/witness.py) that speaks the C2SP add-checkpoint protocol so a hosted sink can earn the
witnessed-green path against a witness on independent infrastructure, and the capture sidecar
(pr sidecar, sidecar.py) that records model calls from a process the agent does not control.
Run pytest -q for the current count; a number written into prose here goes stale within a
day and two stale numbers disagreeing with each other is worse than none.
Not yet shipped: cross-account WORM storage, a Go verifier (no toolchain here yet), SAML and a
browser SSO redirect flow, multi-TSA trust roots, and the actual cloud deployment plus payment
integration (the metering and plan plumbing is in place; wiring a real provider and infra is an
ops step, not code shippable here). Roadmap in STRATEGY.md; design in
../DESIGN-agent-audit-trail.md section 11.
Open-core, dual-licensed (see LICENSING.md):
pr-verify verifier, the bundle format and SPEC.md, and the
web verifier. Verify any Provenrail record with a permissively-licensed tool, no strings.src/provenrail/server/). Self-host freely; a
commercial license is available for proprietary or closed-source-SaaS use
([email protected]).Provenrail is evidence tooling, not legal advice or a compliance guarantee; completeness is never
claimed. See DISCLAIMER.md. Business model in BUSINESS.md, moat analysis in MOAT.md,
regulatory mapping in COMPLIANCE.md.
Claude Code, Codex, Gemini CLI, GitHub Copilot, Cursor, Telegram and ntfy are names of their respective owners. Provenrail is independent and is not affiliated with, endorsed by or sponsored by any of them.