bellbook (Python)
Python bindings for Bellbook: validate tamper-evident, replay-verifiable records of agent activity offline, from Python.
This is a thin PyO3 wrapper over the Rust bellbook
crate, which stays the single source of truth for canonicalization,
verification, and the conformance vectors. It is not a reimplementation.
(The independent from-scratch validator under conformance/python/ in the
repository is a separate thing: a deliberate second implementation that exists
to cross-check the specification.)
Status
Validation (with the bellbook-core-v1 baseline profile check), reading,
writing, and the read-side query set (issue #13, RFC-0002, RFC-0003).
Validate
validate(bytes) -> Report reaches the same Clean / Tainted / Invalid
decision as the bellbook validate CLI, over the same core.
import bellbook
report = bellbook.validate(open("receipt.json", "rb").read())
print(report.status) # "clean" | "tainted" | "invalid"
print(report.spec_version) # "0.3"
print(report.record_count)
print(report.head_hash) # lowercase hex; compare against an anchored head
print(report.rules_hash) # compare against rules you trust
print(report.retracted, report.tainted)
print(report.standing) # {"compromised": [...], "unsound": [...], "restorations": {...}}
print(report) # the full human-readable report (same as the CLI)
Validation never raises for a bad receipt: an unparseable or failing receipt
returns a Report with status == "invalid" and a problem or reason
set. As with the CLI, Clean is relative to the rules embedded in the
receipt - compare rules_hash against a rule set you trust, or name the
shared baseline:
report = bellbook.validate(data, require_profile="bellbook-core-v1")
p = report.profiles[0]
print(p["status"]) # "Conformant" | "NonConformant" | "Unknown"
print(p["hash"]) # hex of the profile's clause-table hash
for c in p["clauses"]: # [{"id": "B1", "passed": True, "detail": "..."}, ...]
print(c["id"], c["passed"], c["detail"])
require_profile takes one id or a list; results land in report.profiles
in request order, and an unknown id is reported as "Unknown", not raised.
A profile result is a report alongside the verdict: it never changes
status or reason. The profile itself is documented in
docs/profiles/bellbook-core-v1.md.
Read
read(bytes) -> Receipt parses a receipt for inspection. Reading does not
verify - call validate for the decision.
import json
receipt = bellbook.read(open("receipt.json", "rb").read())
print(receipt.spec_version, len(receipt))
for r in receipt.records:
print(r.kind, r.time, r.author_id, r.author_type, r.evidence)
for ref in r.refs:
print(" ", ref["type"], "->", ref["target"])
payload = json.loads(r.payload_json)
A Record exposes id, kind, time, author_id, author_type,
signed, evidence, schema, refs, and payload_json. The enum-valued
fields (kind, author_type, evidence, and each ref's type) are the
record's Rust variant names, e.g. "Candidate", "Provider", "Reported",
"Use". read raises ValueError on bytes that are not a parseable
receipt.
Write
Writer(log_dir, rules) records evolution to a persistent, single-writer log.
It holds the same exclusive lock and runs the same replay-on-commit the Rust
LogWriter does. rules is a JSON string: the verifier rules the log is
committed under, the same object a receipt embeds under rules.
default_rules(authors, max_context=200, admins=None, reaffirmers=None) builds
that string for you - the Python counterpart to bellbook rules init - so you
never hand-author a rules object. authors maps an actor id to a role (user,
provider, system, executor, or verifier, case-insensitive). admins
lists actors allowed to retract records they did not author; reaffirmers,
when given, restricts reaffirming selections to the listed actors. Both must
also appear in authors. Like rules init, the result carries the
bellbook-core-v1 baseline evidence thresholds, so a log committed under it
conforms to the baseline profile out of the box:
import bellbook
rules_json = bellbook.default_rules({"agent": "provider", "evaluator": "provider"})
w = bellbook.Writer("./mylog", rules_json)
c0 = w.candidate(author="agent", git_tree="a1b2...") # a Root candidate
e0 = w.evaluate(author="agent", candidate=c0.id, criterion="builds", passed=True)
s0 = w.select(author="agent", objective="ship it",
consider=[c0.id], choose=[c0.id], uses_eval=[e0.id])
print(c0.id, c0.accepted, c0.reason) # each commit returns a Commit
# Export and verify in the same process:
report = bellbook.validate(w.receipt())
assert report.status == "clean"
Each of candidate, evaluate, select, and retract commits one record
and returns a Commit (id, accepted, result, reason). A record is durably committed
whether accepted or rejected - a rejected record is evidence a proposal was
refused - so accepted may be False without an exception. Statically-knowable
payload violations (an unregistered author, a score scale above 12, an upgrade
whose tree differs from its target) raise ValueError before anything is
written.
candidate(author, git_tree, *, git_commit=None, algo="sha1", note=None, continues=None, parent=None, derives_from=None, upgrades=None, manifest=None)- basis is exactly one of
continues(withparent),derives_from(a list of record ids), orupgrades; omit all three for a Root.manifest(a directory path) binds the source by a canonical manifest hash instead of a reported tree. Aderives_frommember may be a candidate or an evaluation: a repair motivated by an evaluation names it there alongside the candidate it derives from (derives_from=[sound_parent.id, failing_eval.id]), and becauseCausecarries intent, not taint, retracting that evaluation later does not taint the repair.
- basis is exactly one of
evaluate(author, candidate, criterion, *, passed=False, failed=False, score=None, scale=None, procedure=None, uses=None)- exactly one ofpassed,failed, or ascore(withscale, a decimal exponent 0-12).select(author, objective, consider, *, choose=None, uses_eval=None, none=False, replaces=None, rationale=None)- exactly one ofchoose(withuses_eval) ornone=True;replacesreaffirms a prior selection.consider,choose, anduses_evalare lists of record ids.
Retract
retract(author, target, reason) asserts a committed record's content is
wrong. The target stays in the log; its id enters the retracted set, its
epistemic dependents become tainted, and the receipt reports Tainted from
then on - permanently. A later reaffirming selection (one that replaces the
unsound one on surviving evidence) restores the line's standing, but never
turns the receipt Clean again: the episode is part of history, and that is the
point.
r = w.retract(author="evaluator", target=e0.id,
reason="benchmark harness measured the wrong thing")
assert r.accepted
assert bellbook.validate(w.receipt()).status == "tainted"
Retraction is ownership-bound (SPEC section 2): it is accepted only when
author is the target's author, or is listed in the rules'
admin_retraction_actors (set via default_rules(..., admins=[...])). An
Executor may never author a retraction, so Executor-authored records are
retractable only through an admin actor. A Verdict or a Retraction cannot be
retracted. As with the other verbs, a rejected retraction is still durably
committed with accepted == False and the verifier's reason.
The writer is deliberately single-writer (SPEC 5.1): it holds an exclusive lock
for the log directory, so a second Writer on the same directory raises. Other
useful members: w.head (current head, hex), w.records (the committed
records, as Records), len(w), and w.receipt() (portable receipt bytes).
Query
The RFC-0002 named query set - seven deterministic, read-only questions over
lineage, evidence, and standing - is available as methods on both Writer
(over the live log) and the Receipt returned by read (over an exported
receipt). Both return plain dicts/lists in the exact surface JSON shapes the
Rust core and the bellbook query CLI emit, so answers are diffable across
surfaces.
w.selected("best-of-n") # selections under that exact objective, with
# chosen candidates and their evidence
w.descent(c.id) # the line of descent back to its roots
w.descendants(c.id) # everything downstream, in log order
w.siblings(c.id) # the candidate's generation
w.frontier() # unconsidered candidates + winners not continued
w.standing(s.id) # standing, taint, retraction, restorations
w.evidence(c.id) # what a selection or a whole line rests on
r = bellbook.read(w.receipt())
assert r.frontier() == w.frontier() # same answers over the receipt
Queries answer only over verified state: a log or receipt that does not
verify raises ValueError instead of answering, as do a missing or rejected
id and a kind mismatch. Nothing is ranked and nothing is silently filtered -
every reported node carries its standing, taint, and retraction annotations,
and the reader decides.
Build from source
pip install maturin
maturin develop # build and install into the current venv
pytest bindings/python/tests
Prebuilt wheels (Linux, macOS, Windows) are published to PyPI, so
pip install bellbook needs no Rust toolchain; the steps above are for local
development against the working tree.
Licensed under MIT OR Apache-2.0.
Metadata
Release files for bellbook 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bellbook-0.7.0.tar.gz | 30.3 kB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bellbook-0.7.0-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details |
| bellbook-0.7.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| bellbook-0.7.0-cp39-abi3-macosx_11_0_arm64.whl | CPython 3.9 | abi3 | macOS 11.0+ ARM64 | Details |
Total release size: 2.6 MB
Release files / bellbook-0.7.0.tar.gz
| Download URL | bellbook-0.7.0.tar.gz |
|---|---|
| Size | 30.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
09e0c2d1598303724e63cadc2b0072dfe6264d20ca8e62c0a074c579ad02ef09
|
|
BLAKE2b-256 checksum How to use checksums |
c6e3b819eca1d01562f714e29dd8426df26ea3a8fa740a25ee5563ceb3d82086
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.
Transparency logRelease files / bellbook-0.7.0-cp39-abi3-win_amd64.whl
| Download URL | bellbook-0.7.0-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 763.1 kB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
701390edffc4b16663125392c12e94b8f322e4c32aea69ce4e7a5de8768091cf
|
|
BLAKE2b-256 checksum How to use checksums |
1d8cb96c8f0b59636c565fdd6e47f59a99b9780b11a520b7c3dfffd3c14743f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.
Transparency logRelease files / bellbook-0.7.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | bellbook-0.7.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 947.1 kB |
| Tags | CPython 3.9 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
0208c6cbe25d64b9a9d2584f537a83c1b6739a80c4f62ee3f0bd081fb833672c
|
|
BLAKE2b-256 checksum How to use checksums |
8882f907b733a72208e3fb577dcae4fce30a98ad63bde06bfde52e942ee642fd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.
Transparency logRelease files / bellbook-0.7.0-cp39-abi3-macosx_11_0_arm64.whl
| Download URL | bellbook-0.7.0-cp39-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 844.3 kB |
| Tags | CPython 3.9 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
9a8305ec66b1fe26b0951e8b2140cfd239a3751a6676e0b69f22dc43fe19bd10
|
|
BLAKE2b-256 checksum How to use checksums |
373f4dfdd9df1d4c9ca480f820e218c5347f1723c111a80db2f215db5e7c416a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.
Transparency log