Local-first, tamper-evident evidence layer for AI agent activity — hash-chained capture sealed into an RFC 6962 Merkle commitment.
Project description
attestroot
Local-first, tamper-evident evidence for AI agent activity.
Captures what an AI agent did, hash-chains it, seals it into an RFC 6962 Merkle commitment, and produces a bundle anyone can verify — on a clean machine, with no dependencies, without our software and without contacting us.
Status: pre-release. M0 and M1 are functionally complete and pass their exit tests. Nothing is published to PyPI yet.
What it claims, and what it does not
It attests that a record existed at a given time and has not been altered since.
It does not prove the record is a truthful account of what the agent did. A party controlling its own capture could record something false, and it would seal and verify perfectly. Integrity and existence-in-time is a real, checkable property — it is simply not the same property as truth.
That distinction is enforced in code, not just in wording: every adapter declares its own blind spots, and evidence lacking independent ground truth is capped at a lower grade with the reason recorded.
Current state
| Component | Status |
|---|---|
| RFC 6962 Merkle tree | ✅ verified against the spec, incl. a path-length guard most implementations omit |
| Ed25519 (RFC 8032) | ✅ pure-Python, all official vectors pass; uses cryptography when present |
| Hash-chained ledger | ✅ 0.47 ms/append, names the exact sequence number where a chain breaks |
| Capture hook | ✅ fail-open, p99 0.68 ms, exits 0 on every hostile input tested |
| Adapter contract | ✅ capability manifest drives grade ceilings mechanically |
| Claude Code adapter | ✅ validated against a real 442-tool-call session |
| Seal / bundle / verify | ✅ end-to-end, 9/9 tamper attacks detected |
| Conformance suite | ✅ 8 cases, all passing — clean, resumed, compacted, crashed, gapped, concurrent, subagent, malformed |
| Grader (D0–D4) | ✅ deterministic, 11/11 fixture bundles match the hand-computed rubric |
| Standalone verifier | ✅ one file, no dependencies, output identical to the CLI |
| Countersignature | ⬜ M3 — nothing is live |
Exit test
The M0 gate was "seal a real 50-turn session; verify in a clean container."
session real Claude Code transcript — 1661 lines, 442 tool calls, 1 compaction
captured 442 events @ 0.75 ms
bundle 2103 leaves, 447 members, 7.7 MB
gaps 0 (every ground-truth tool_use id matched a captured one)
clean room 13/13 checks pass — no dependencies, no signing key, no local state
tampering 9/9 attacks detected
scripts/clean_room_verify.sh runs it. Where Docker is unavailable it builds a
hermetic room with python3 -I -S, which is stricter on the dependency question
than a container: site-packages is unreachable, so cryptography cannot be
imported even when installed, forcing the pure-Python signature path.
Try it
PYTHONPATH=src python3 -m attestroot.cli init
PYTHONPATH=src python3 -m attestroot.cli status
PYTHONPATH=src python3 -m attestroot.cli seal <session-id>
PYTHONPATH=src python3 -m attestroot.cli verify <bundle> --offline-bundle
PYTHONPATH=src python3 -m attestroot.cli grade <bundle>
PYTHONPATH=src python3 -m attestroot.cli conformance
Verifying without installing anything
dist/attestroot_verify.py is a single file with no dependencies. Email it
alongside the bundle:
python3 attestroot_verify.py evidence.arbundle
It produces byte-identical output to attestroot verify, and prints its own
SHA-256 at build time so a recipient can check the copy they were sent against
the one published here.
This exists because "clone the repo and set PYTHONPATH" is already too much friction for someone who was emailed an evidence file and has thirty minutes. Evidence nobody checks is decoration.
Adapters
An adapter is a translator from a source's native events to canonical W Events.
It declares what it can see in a capability manifest, and the grader acts on
that declaration: an adapter reporting has_ground_truth: false caps the grade
at D1 with the reason recorded, because completeness cannot be evaluated without
an independent record to diff against.
Adding one means implementing the contract in adapters/base.py and submitting
conformance fixtures with the change. An adapter is certified only when its
suite passes — including a check that its declared capabilities match what its
output can actually support. Claiming ground truth while producing none fails.
Design constraints
These are load-bearing, not preferences:
- Fail-open capture. The hook never blocks or slows the agent. Every failure path writes a gap record and exits 0. A bug in evidence collection must not stop someone's work.
- No network in the free path. Ever. Not for telemetry, not for time.
- Store raw. Payloads are persisted verbatim and interpreted at read time. Tool schemas change; today's normalisation is tomorrow's lost evidence.
- Zero dependencies for verification. An auditor must be able to verify on a machine where they cannot install anything.
- Gaps are a feature. Missing captures are detected by diffing against the source's own record, never from self-reported gap records — otherwise hiding a gap is as easy as not writing one.
Built on
incident-ledger — the append-only
hash-chained evidence log this extends to agent activity.
pip install incident-ledger
Published standard
- Implementing Agentic-AI Accountability — five evidence principles crosswalked to the IMDA MGF, EU AI Act, NIST AI RMF and ISO/IEC 42001, with the D0–D4 evidentiary maturity lens · CC BY 4.0
- incident-ledger · MIT
Tamper-evident logging, deterministic replay and evidence-maturity grading are cited prior art, not claims of this work. Hash chains and Merkle trees are 1979 and 1991 public cryptography.
Not supported
Windows. Paths, file locking and fsync semantics differ enough that half-support would be worse than none. WSL works.
MIT · attestroot.com · AVATROX TECHNOLOGY PRIVATE LIMITED
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file attestroot-0.1.0.tar.gz.
File metadata
- Download URL: attestroot-0.1.0.tar.gz
- Upload date:
- Size: 57.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
51b0f7e21c859f450f73ca02925e04499bb139ad47663753fcec4305d4880152
|
|
| MD5 |
856fd64ba464e4171a3a961b6349bdb1
|
|
| BLAKE2b-256 |
c97ac6a5d254fb82c8e9cad704ed82444b8888ab2f2197451f5f13e8db624e1c
|
File details
Details for the file attestroot-0.1.0-py3-none-any.whl.
File metadata
- Download URL: attestroot-0.1.0-py3-none-any.whl
- Upload date:
- Size: 62.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90bdf51c2a8b814359cb8223937b938bf08f4ea5da92b6f24373297fdee438a4
|
|
| MD5 |
95e34f6598393256624bb3b9080d5288
|
|
| BLAKE2b-256 |
dbf0ef0a978bcd30500eb40a2601aa58f5040430b162b850007b6a8cab5570a7
|