Docs › Verifiable runs: prove what your agent did

Verifiable runs: prove what your agent did

When an agent ran unattended, "it worked" is a claim. A proof is better: a record of what ran, when, with which evidence and at what cost, fixed the moment the run ended, chained to every other run of that day, and anchored in the Bitcoin blockchain. Later you, an auditor or a customer can check that the record was not edited afterwards, without asking us. This page explains exactly what is hashed, what is not, and where the guarantee stops.

What is in the record

At POST /v1/runs/end RunVouch builds one JSON object for the run and stores its sha256 (the leaf). The object has these keys and nothing else:

run_id, agent, account_id, started, ended, status, cost, tokens, tool_calls, output_bytes,
evidence (name -> true/false), evidence_ok, source, exit (only when reported), tool_events_hash

tool_events_hash is the sha256 over the ordered list of tool events of the run: tool name, the input hash the client sent, ok flag and timestamp. The leaf is sha256(canonical JSON), canonical meaning sorted keys, separators , and :, UTF-8. It is written once and never updated.

What is not in it: prompts, model outputs, tool inputs, file contents, log lines. RunVouch never receives those, so they cannot be hashed. The proof shows that a run with these numbers and these evidence booleans ended at this time; it does not show what the agent wrote.

The daily chain

Every UTC day, a few minutes after midnight, the server seals the day that just ended:

  1. Take the leaves of all runs that ended that day (all accounts, sorted by run_id).
  2. Build a Merkle tree: pairwise sha256(left + right) over the hex strings, an odd node is paired with itself, an empty day gives sha256(""). The top is the day root.
  3. chain_hash = sha256(prev + ":" + date + ":" + root), where prev is the chain hash of the previous sealed day (64 zeros for the first one).
  4. Write /proof/days/DATE.json with date, root, prev, chain_hash and the list of (run_id, leaf), publicly, without authentication.

The chain is global on purpose: one public object per day for everyone, so changing one leaf of one account would change the root that every other customer can also see. The index of all sealed days is at api.runvouch.com/proof/. Run ids are random hex; the day file does not reveal who ran what.

The Bitcoin anchor

Right after writing the day file the server runs ots stamp DATE.json from the OpenTimestamps client. That submits the file hash to the public OpenTimestamps calendar servers, which aggregate hashes and commit them in a Bitcoin transaction. The resulting DATE.json.ots is served next to the day file. The status you see in ots_status means:

ots_statusmeaning
pendingstamped; the calendar has the hash, the Bitcoin block is not in yet. Usually a few hours.
bitcoin:NNNNNNthe .ots file was upgraded and contains an attestation from Bitcoin block NNNNNN.
ots missing / stamp failed: ...the chain still works, but that day has no Bitcoin anchor. We do not hide this.

Once a day the server runs ots upgrade on pending files of the last two weeks. To check an anchor yourself: pip install opentimestamps-client, download both files, run ots verify DATE.json.ots -f DATE.json. With a local Bitcoin node it verifies against the block header; without one it tells you which block to look up.

Get the proof of a run

rv proof RUN_ID            # prints the proof JSON
rv proof RUN_ID --verify   # recomputes leaf and Merkle path, fetches the public day file, exit 0 or 1
curl -H "X-API-Key: rv_..." https://api.runvouch.com/v1/runs/RUN_ID/proof

The response contains record, leaf_hash, stored_leaf_hash (the one written at run end; a difference would mean the row changed since), merkle_path as a list of (sibling hash, side), root, chain_hash, prev, sealed, ots_status and verify_url. The dashboard has a proof link per run, and the MCP server has runvouch_run_proof, so an agent can fetch its own proof and hand it to whoever asked for the work.

Verify offline, without our code

templates/verify_proof.py is a standalone Python 3 script, standard library only. Save the proof JSON and the day file, then:

python3 verify_proof.py proof.json 2026-08-26.json
PASS leaf hash matches the record
PASS merkle path leads to the root
PASS day file lists this run with this leaf
PASS day file root recomputed from its leaves
PASS chain hash of the day
VERIFIED

Change one byte of the record and the first line reads FAIL and the exit code is 1. The script fetches the day file itself when you leave the second argument out. It checks everything except the Bitcoin attestation; that is the ots verify step above.

Honest limits


Need a key? Get a free key · Stuck? contact