Skip to main content

arcaeon-audit

Tamper-evident, audit-ready logs for AI agents. Observability shows you what your agent did. This lets you show it wasn't altered afterward — the integrity layer under the records your ISO 42001 / SOC 2 auditor and your enterprise customers' questionnaires already ask you to keep.

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.

What the law does and does not say, stated carefully because vendors routinely overstate it. The EU AI Act (Article 12) requires automatic recording of events for high-risk systems; it contains no tamper-evidence or integrity requirement, and anyone telling you it mandates tamper-proof logging has not read it. The regulations with teeth on reconstructibility of records are elsewhere: DORA's technical standards (financial entities, in force now) require that ICT-related events be reconstructible after the fact, and the revised Product Liability Directive (applies 9 December 2026) lets courts presume a defect where a defendant fails to produce evidence within its control. Neither mandates this product. What they do is make a records gap expensive in a dispute.

The practical gap is simpler than the law: a plain log file proves nothing, because anyone with write access can edit, delete, or reorder a past record and nothing shows. When a record is challenged, "we logged it" is a self-report. An append-only, hash-chained record whose head is checkable by a party you don't control is the difference between an assertion and evidence.

arcaeon-audit is the small, boring 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 an audit-event vocabulary and an export bundle an auditor can re-verify without trusting you. It is a mechanism, not a certification: it makes no one compliant with anything, and the export's own summary says so in writing.

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; a witness block naming the witness's kind / identifier / independence + independence_source (who decided that label — a self-declaring witness can no longer earn the strongest one by omission) / self_integrity (did the witness prove its OWN record unedited); and how_to_reverify, a two-step recipe whose second step is a prefilled no-credential call against a public witness — the step that does not require trusting us
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. A positive detection by the witness outranks every chain-scope verdict below: what the witness saw is evidence, what the local chain could not scan is not.
  • EMPTY_LOG — the file is genuinely empty: zero rows, nothing recorded yet. An absence of evidence, never an accusation. Only for a truly empty file.
  • UNVERIFIED_SCOPE — the log has records, but the chain could not speak for them (they carry no chain links — the normal state of a log adopted from unchained history). Not a pass, not an accusation, an unanswered question. Exits 2, because an unanswered question must never gate CI green.
  • WITNESS_CHECK_FAILED / UNRECOGNIZED_WITNESS_VERDICT:<v> — the witness consultation could not complete, or returned a verdict this version does not know. Reported verbatim rather than mapped onto the nearest accusation.

Exit codes (verify and export share one contract, so the two subcommands can never disagree about the same file): 0 nothing wrong found · 1 an accusation · 2 the check could not complete.

witness.self_integrity in integrity.json answers a question separate from independence: did the witness prove its own record was unedited? verified only when the witness self-verified its pin chain (arcaeon-ledger 0.5.9+ local store). Every remote/hosted client exposes only .latest() and therefore reads unestablished — the comparison still runs, but a PASS resting on it says so in plain text, because an attacker who can write both the log and the pin can produce exactly that PASS.

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 / undeclared / none).

And it names who decided that label. independence_source is established_by_type (we could tell from the object itself — this outranks any claim), self_declared_by_witness (the witness said so; the note is prefixed SELF-DECLARED and you should verify the identifier yourself), conservative_default, or no_witness. A witness that omits independence now reads undeclared, never the strongest value — an earlier version defaulted the other way, so a shim wrapping a local file in the log owner's own directory could earn externally_verifiable without its URL ever being contacted. independence is the field a regulator weighs a PASS on; it must not be settable by omission. 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.

Release files for arcaeon-audit 0.1.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for arcaeon-audit 0.1.5
File Size Uploaded
arcaeon_audit-0.1.5.tar.gz 27.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for arcaeon-audit 0.1.5
File Interpreter ABI Platform
arcaeon_audit-0.1.5-py3-none-any.whl Python 3 none any Details

Total release size: 53.4 kB

Release files / arcaeon_audit-0.1.5.tar.gz

Download URL arcaeon_audit-0.1.5.tar.gz
Size 27.6 kB
Tags Source
SHA-256 checksum
How to use checksums
d8ed26c5ecc22a0899b905a7136cee7041b649b34caf04b04087119e0337d1bc
BLAKE2b-256 checksum
How to use checksums
9967ee158f51c7bc0958a4b55cbbff644d4bbd5392ee2768ffc2ec444ed3e869
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.3

Release files / arcaeon_audit-0.1.5-py3-none-any.whl

Download URL arcaeon_audit-0.1.5-py3-none-any.whl
Size 25.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2c6b25642bccbf2968c4d326c924ab42b3f1d3aa57fd28e1646a3f23d981d848
BLAKE2b-256 checksum
How to use checksums
cba56b00f67bf1eed68ba69c954023ff5cddb8df7cd7a615a4668cec7db0a010
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.3

Release history Release notifications | RSS feed

0.1.9

1 release file

0.1.8

2 release files

This release

0.1.5 This release

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page