How do you verify what an AI agent did without trusting it?
Today I am releasing Bellbook, an open-source Rust library for creating tamper-evident, replay-verifiable records of captured AI agent activity.
It started with a simple problem. As agents take real actions - calling tools, changing software, spending money, operating across company systems - we increasingly depend on their own account of what happened.
The agent says it followed the request. The runtime says the tool call succeeded. A dashboard shows green. A log contains a sequence of events. None of those, on their own, give another party a portable way to check the claim.
Can we turn “the agent says it did X” into evidence another party can verify without trusting the agent or the system that produced it?
Bellbook is my attempt at a small, precise answer.
What Bellbook is
Bellbook is an embeddable evidence kernel. It has one durable primitive: a typed record in an append-only log. The host writes down what was requested, what authority existed, what action was attempted, what came back, and what was approved or refused.
Each record is content-addressed: its id is the SHA-256 of its RFC 8785 (JCS) canonical form, so an independent implementation in any language computes the same id. Records reference earlier records by that hash through typed relationships (Cause, Use, Require, Replace), forming a DAG. Every normal record is immediately followed by a deterministic verdict - not a model opinion, but a judgment the verifier derives from the record, the current state, and the configured rules.
You embed it around the activity you choose to capture:
use bellbook::*;
let rules = VerifierRules::new(default_space(), 200)
.with_author_role("human", AuthorType::User)
.with_author_role("agent", AuthorType::Provider);
let mut writer = LogWriter::open(dir, &rules)?;
let mut state = State::default();
// Commit a record; the verifier judges it and appends a paired verdict.
let (id, verdict) = writer.commit(proposal, &rules, &mut state)?;
assert_eq!(verdict.result, VerdictResult::Accept);
// Replay the whole log: recompute ids and times, re-derive every verdict.
let report = verify_log(writer.records(), &rules, None);
assert_eq!(report.result, VerdictResult::Accept);
Replay is where the guarantee lives. verify_log walks from genesis (or a checkpoint) and recomputes every record id, enforces gap-free logical time, requires every record to carry its paired verdict, and re-derives each verdict instead of trusting the stored one. If committed content is modified, removed, inserted, reordered, or paired with a forged verdict, verification fails.
Bellbook is not a logger, a database, an identity provider, or a runtime policy engine. It is a verification layer a host process embeds around the activity it captures.
From logs to receipts
The most important output is not the log. It is a receipt: a portable, self-contained bundle of the records and the rules they were committed under. Another party validates it offline, from genesis, without the original runtime and without trusting the producer. No Rust required - the CLI ships with the crate:
cargo install bellbook
bellbook validate receipt.json # human-readable report
bellbook validate receipt.json --json # same report as JSON
# exit codes: 0 clean, 1 invalid, 2 valid-but-tainted
The validator returns one of three outcomes:
- Clean: the captured history is internally consistent under its embedded rules, with no retracted or tainted claims.
- Tainted: valid, but one or more claims were retracted or depended on something later retracted.
- Invalid: the receipt cannot be parsed under the specification, or it fails replay.
The same receipt moves between an agent runtime, a customer, an auditor, or another system and produces the same result. To watch it catch tampering end to end:
cargo run --example quickstart # commit → verify → tamper → detect
Evidence must not silently become stronger
Very different claims look identical once they are written down. “The verifier computed this” is not “an external system reported this.” A signed statement is not a model inference. An assumption should not become a fact because it was repeated later.
Bellbook makes this explicit with an ordered five-class lattice, strongest to weakest: Deterministic, Verified, Reported, Inferred, Assumed. A derived record inherits the weakest evidence among the sources it declares. Evidence can degrade through derivation but never inflate, and rules can set per-kind minimum thresholds.
This does not prove any real-world statement is true. It preserves how the statement is known, which is what makes a receipt honestly evaluable.
Corrections without rewriting history
What happens when an earlier claim turns out to be wrong? Bellbook uses retraction with taint. A retraction does not delete or edit the original; it appends a new record stating the earlier content was wrong. Bellbook then follows the evidence dependencies and marks downstream records tainted where they relied on the retracted claim.
The log still verifies. The fact that the claim was once made does not disappear, but consumers can now see that the claim, and everything built on it, no longer stands. That is the difference between an invalid history and a valid history that has been corrected.
Authority and signatures are part of the record
Agent activity is not only what happened, but whether the actor had authority to do it. Capabilities, approvals, refusals, and expiries are first-class records. An action must name the exact authority that allowed it. Exact approvals are bound to one actor and one action, and are single-use. Retracted authority stops authorizing.
Bellbook also supports Ed25519 signatures with actor key pinning. When an actor’s keys are pinned and signatures required, an agent cannot author its own approval by simply writing a different role into a record, so it cannot impersonate the user. The honest scope: identity is cryptographic exactly where keys are pinned and signatures enforced; elsewhere it remains a configured claim.
What Bellbook does not prove
The limitations matter as much as the mechanism.
Consistency, not completeness. Bellbook verifies that captured history is intact and rule-conforming. It cannot prove the host captured everything the agent did; a host can omit an action before it ever reaches Bellbook. Completeness depends on how the runtime is instrumented.
Integrity, not confidentiality. Records and receipts carry their payloads in the clear. They must never contain credentials, and sharing a receipt discloses its contents. Encryption, access control, and retention stay with the host.
Tamper-evident, not tamper-proof. Replay detects any change inside committed history, but a storage owner can discard an unanchored log and build a new, internally consistent one from genesis. Key-pinned signatures make protected actors hard to forge; an externally stored head attestation lets another party detect a wholesale rewrite.
Clean is relative to embedded rules. Version 0.2 does not yet define a shared baseline profile that makes Clean comparable across organizations. Consumers compare the receipt’s rules_hash against a rule set they already trust.
These boundaries are stated directly in the specification, because a verification system becomes dangerous when its claims outrun its guarantees.
Open, and independently reproducible
If Bellbook’s value is that you do not have to trust the producer, then you must be able to inspect the rules, reproduce the ids, run the vectors, and challenge the implementation. That is why it is a small open-source library, not a hosted black box. The repository carries the normative specification, canonical and signed test vectors, a working example, the offline validator, and the security model. The code contains no unsafe and is tested on Linux, macOS, and Windows against Rust 1.75.
The specification’s whole point is that a second implementation can reproduce it, so the repository now includes one. A from-scratch Python implementation of the verifier, sharing no code with the Rust crate, recomputes every record id and re-derives every verdict across a language-neutral conformance corpus. It agrees with the Rust reference on every case, including deliberately corrupted and forged inputs, which it rejects exactly where Rust does.
That is the property that matters here: the guarantee does not live inside one codebase. When two independent implementations agree on a receipt, the result is a fact about the specification, not a quirk of one author’s code.
What is available today
Bellbook 0.2.0 is the first public release, implementing specification version 0.2, the first published compatibility epoch. It includes:
- typed, content-addressed records;
- deterministic record and full-log verification;
- author roles, capabilities, approvals, refusals, and lifecycle rules;
- Ed25519 signatures with actor key pinning;
- evidence classes with weakest-link derivation;
- retraction with transitive taint;
- a crash-safe single-writer persistent log;
- portable receipts and offline validation;
- canonical test vectors and a language-neutral conformance corpus.
Not in 0.2: baseline security profiles, profile-aware receipts, selective disclosure, Python bindings, and external policy-decision records.
Try it, inspect it, break it
Bellbook is available under MIT or Apache-2.0:
I would especially value feedback from people building agent runtimes, authorization systems, provenance tools, developer agents, and audit workflows.
The useful questions are concrete ones: Which guarantee is unclear? Which hostile input is not covered? Which integration boundary is wrong? Can an independent implementation in another language reproduce the same identifiers and verdicts? Where does the specification leave room for two validators to disagree?
The goal of this first release is not to declare the trust problem solved. It is to put a precise, testable primitive in public and let others examine whether it holds.