arcaeon-audit
Tamper-evident, audit-ready logs for AI agents. Observability shows you what your agent did. This lets you prove it wasn't altered — the evidence your ISO 42001 / SOC 2 auditor (and, by 2027, the EU AI Act) asks for.
pip install arcaeon-audit
Why this exists
If you ship an AI agent into anything regulated or enterprise-sold, you're already being asked to keep records of what it did and produce them on demand — by your SOC 2 auditor, by ISO/IEC 42001 (Annex A.6.2.8: keep AI event logs across the lifecycle), and by the security/AI-governance questionnaires your enterprise customers send today. The EU AI Act, Article 12 adds a hard tamper-evident-logging mandate for high-risk systems from 2 December 2027 (Digital Omnibus, Reg. (EU) 2026/1744; penalties to €35M or 7% of revenue).
Almost all of that forces you to keep records. Almost none of it is satisfied by a plain log file — anyone with write access can edit, delete, or reorder a past record and nothing shows. What auditors and regulators want is an append-only, integrity-protected record you can hand over and have independently verified. That integrity layer — the provable part — is exactly what this gives you.
A normal log file doesn't meet the bar: anyone with write access can edit, delete, or reorder a past record and nothing shows. The regulators' expectation is an append-only, integrity-protected record — in practice, a hash chain.
arcaeon-audit is the small, boring, correct layer that gives you exactly that,
in two lines and a folder. It wraps arcaeon-ledger
(a hash-chained append-only log, zero heavy deps) with the event vocabulary and
the regulator-ready export that Article 12 asks for.
Use
from arcaeon_audit import AuditLog
log = AuditLog("agent-audit.jsonl", system_id="triage-agent-v3", provider="Acme AI")
log.record(event="system_start", agent="triage-agent-v3")
log.record(event="input", agent="triage-agent-v3", inputs={"patient_msg": "chest pain"})
log.record(event="decision", agent="triage-agent-v3", decision="escalate",
outputs={"routed_to": "ER", "priority": 1}, capability_version="v2")
log.verify().ok # True — any edit to history would make this False
log.export_bundle("audit-export/") # regulator-ready folder
export_bundle() writes a self-verifying folder:
| file | what it is |
|---|---|
records.jsonl |
the full hash-chained audit log, verbatim |
integrity.json |
verdict — chain_ok, truncation_checked, truncation_ok, row count, exact first break if any, and a witness block naming the witness's kind / identifier / independence |
manifest.json |
system id, provider, period covered, counts by event type |
ARTICLE_12_SUMMARY.md |
human-readable mapping to Article 12's requirements |
witness.json |
the external-witness pin + truncation verdict + its nature (only when a witness is consulted) |
The bundle is self-verifying: records.jsonl is hash-chained, so anyone can
re-run arcaeon-ledger's verify_file() and reproduce integrity.json. Tamper
evidence does not depend on trusting this tool or its author — that's the point.
Truncation, and why a chain alone can't catch it
A hash chain proves nobody edited or reordered your records. It provably cannot prove nobody truncated them: delete the most recent rows and the surviving prefix still chains clean. So a log truncated before export would earn a clean pass from chain verification alone — the one gap the integrity check can't close by itself.
The fix is an external witness: an outside record of your log's head
(rows, chain) at a point in time. Pin to it on a cadence; a later truncation has
fewer rows than the witness saw, and is caught. export_bundle() cross-checks
against the witness when one is configured, and the bundle's verdict distinguishes:
PASS— chain intact and the witnessed prefix matches the witness: no truncation up to the witnessed head. The only verdict that claims completeness — but only through the last pin, and only if the witness is controlled independently of whoever can write the log. Records written after the last pin are not covered (pin close to export), and a witness an attacker can also rewrite proves nothing.VERIFIED_MODULO_TRUNCATION— chain intact, but no witness was consulted, so truncation was not checked. Honest non-proof, never a silent clean pass.TRUNCATION_DETECTED/REWRITE_DETECTED— the witness caught missing or re-minted history.
Judge independence yourself. A witness only proves completeness if it is
controlled independently of whoever can write the log. So integrity.json names
what the witness was: a witness block with kind (local_file / remote_url /
opentimestamps / none), its identifier, and an honest independence label
(self_asserted / externally_verifiable / none). The reference WitnessStore is
a local file — same control domain as the log — so it is labelled self_asserted,
not independent: a PASS backed by it is self-attested, and the block says so
plainly. The point is to let a regulator see the independence question, not to claim
an independence we don't have. (Bundles from ≤0.1.3 predate this block; witness_nature_of()
reads them back as kind: unknown.)
from arcaeon_ledger.witness import WitnessStore, publish_head
from arcaeon_ledger import Ledger
store = WitnessStore("witness.jsonl") # or a hosted witness endpoint
publish_head(store, "acme/triage-v3", Ledger("agent-audit.jsonl")) # pin, on a cadence
log.export_bundle("audit-export/", witness=store, witness_namespace="acme/triage-v3")
CLI
arcaeon-audit verify agent-audit.jsonl
arcaeon-audit pin agent-audit.jsonl witness.jsonl acme/triage-v3
arcaeon-audit export agent-audit.jsonl audit-export/ --system-id triage-v3 --provider "Acme AI" \
--witness witness.jsonl --namespace acme/triage-v3
What this is and isn't
Is: an engineering control that produces automatic, tamper-evident, exportable records — the integrity + export primitive Article 12 leans on.
Isn't: legal advice, and not compliance-in-a-box on its own. Article 12 compliance also depends on what you choose to log and your broader obligations under the Act. This tool gives you the hard part (provable integrity + a clean export); the coverage is yours to define.
How it works
Every record is hash-chained: chain = sha256(prev_chain + canonical(row)). Edit,
delete, or reorder any record and every later link breaks; verify() names the
exact row. Records also carry an authority block (principal + capability version)
so "was this edited?" sharpens to "was this edited and was the writer authorized?"
MIT licensed. Built by Arcaeon — the evidence layer for AI.
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 arcaeon_audit-0.1.4.tar.gz.
File metadata
- Download URL: arcaeon_audit-0.1.4.tar.gz
- Upload date:
- Size: 17.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
50619e4b9f3957878580119baec20eefb9169304b4cbaf30eb7c691e77fb3e43
|
|
| MD5 |
6873d3488953ddb339833d3099c66940
|
|
| BLAKE2b-256 |
b52f4ebb7d249d6e131ba1c62b8f3cbfb6220219ca54f6b52c06ef1f4d5eef0a
|
File details
Details for the file arcaeon_audit-0.1.4-py3-none-any.whl.
File metadata
- Download URL: arcaeon_audit-0.1.4-py3-none-any.whl
- Upload date:
- Size: 16.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9b9ea7a521d0d7a7ad9a17aa821fcd2f2fe5ac44c612d87f86865cc71de956e4
|
|
| MD5 |
9832f9bc76889bd97320e3b17dfccd48
|
|
| BLAKE2b-256 |
d20f42d282ed1c427ec561959c0c1f37c265d10a9870a5e73b7b4d215d3f0e42
|