inspeximuswe have inspected

EU AI Act Art. 12 · event logging for high-risk systems

What did the agent know when it acted? An audit trail that answers, offline.

Action loggers sign what an agent did. inspeximus also signs a digest of what its memory held at that moment, digests the inputs and outputs without storing them, and lets a reader with a retained transcript check it later: a transcript that differs by one character answers false. The transcript on this page is a real run; ids and hashes change per run, nothing else does.


the run, 2026-09-17, inspeximus 2.40.0

How is one action recorded, and what does the record hold?

One memory, one tool call. The entry is signed and bound to the store's receipt chain; the inputs and output are digested, not stored.

$ inspeximus writer-key --new --out key.txt
secret written to key.txt (0600)
public key: 7c4e59dc326515ba066f820357b96d6e98becead7160e0eb5b7e8a02307e11da

point the server at it: in /home/you/.config/inspeximus/config.json, set {"writer": {"key_file": "/home/you/demo/key.txt"}}
(INSPEXIMUS_WRITER_KEY_FILE cannot name this file: it is inside the project this process runs in, /home/you/demo)
pin this writer as trusted with trust_seeds={'key:7c4e59dc326515ba066f820357b96d6e98becead7160e0eb5b7e8a02307e11da'} (the MCP server: recall.trust_seeds)
KEEP THE SECRET OUT OF GIT. It attests AUTHORSHIP, not truth.

$ inspeximus --path mem.json --receipts --receipt-key-file key.txt remember 'Alice phone is +100' --key alice::phone --source crm/alice
remembered b0520e2bef [key=alice::phone]

$ inspeximus --path mem.json --receipts --receipt-key-file key.txt actions record tool:sms --input '{"to": "+100", "text": "hi"}' --output '{"sent": true}' --actor support-agent
recorded #0 tool:sms  hash 3e42f575d77f  SIGNED

$ inspeximus --path mem.json actions knew 0
{
  "seq": 0,
  "action": "tool:sms",
  "ts": 1789661451.482288,
  "memory_state": {
    "digest": "7fb9dbbd3921c1ca0c0055394b9bf85f9e932f075a961e834ae82d7c4962abb8",
    "records": 1,
    "last_receipt": "5feb104086cfd48edbbaa903706ba82f7909bf740a0d1a1e0668b3df45818356",
    "receipts": 1,
    "recalled": [],
    "recalled_at": null,
    "recall_scope": "this handle",
    "recall_before_previous_entry": false
  },
  "recalled_now": []
}

recalled is empty here because the CLI opens a new handle per command; from Python or the MCP server, a recall made on the same handle before the action is listed in the entry. The digest is a hash over each record's id, status, timestamp, key, tenant and content hash at that moment; it holds no content.

Does a retained transcript match the entry, and what happens when one character changes?

The same inputs answer true on both sides. Change "hi" to "hello" in the retained input and the input side answers false while the output side still matches. The comparison is over JSON values: key order and whitespace never flip it, any value does.

$ cat in.json
{"to": "+100", "text": "hi"}

$ inspeximus --path mem.json actions matches 0 --inputs in.json --output out.json
{
  "seq": 0,
  "action": "tool:sms",
  "inputs": true,
  "output": true
}

$ cat in_edited.json
{"to": "+100", "text": "hello"}

$ inspeximus --path mem.json actions matches 0 --inputs in_edited.json --output out.json
{
  "seq": 0,
  "action": "tool:sms",
  "inputs": false,
  "output": true
}

How does an auditor verify the ledger and take it away in a standard format?

verify recomputes every hash, link and signature, binds each entry to a position in the store's receipt chain, and re-hashes each record against its write receipt, so a record edited on disk under an unchanged chain fails the second line. export-trail writes the IETF draft's record schema and chain; --verify checks the fields and the chain of a file from any producer.

$ inspeximus --path mem.json actions verify --expected-pubkey 7c4e59dc326515ba066f820357b96d6e98becead7160e0eb5b7e8a02307e11da
OK action ledger mem.json.actions.json  (1 entries, bound to mem.json)
OK store records against their write receipts (1 receipt(s))

$ inspeximus --path mem.json actions export-trail --out trail.jsonl --agent-id urn:agent:support.example --agent-version 1.0.0
wrote trail.jsonl: 1 records, draft-sharif-agent-audit-trail-04, session 8e3f4852-f865-4174-9ed0-627f9af83e64, tail ac7cb937eebd

$ inspeximus actions export-trail --verify trail.jsonl
OK trail.jsonl (draft-sharif-agent-audit-trail-04)

The ledger also records human oversight decisions (approve, refuse, override, stop, review; Art. 14), Art. 50 disclosures, Art. 73 incidents with their reporting clock, and lifecycle events. inspeximus compliance prints the evidence labelled by article; the mapping page states the boundary of each row. Timestamps from a third party, and whether that authority was on the EU trusted list on that date, are one command away: inspeximus actions timestamp.

How is this different from an action logger?

An action logger signs what the agent did. This ledger also carries a signed digest of the memory the agent acted on, which is the half an investigator asks for first: what the agent held when it did it, beside what it did. Both sit in one entry, and the memory side is bound to the store's own receipt chain, so a store rewritten by someone without the signing key no longer matches its ledger. An operator who holds the key can rewrite both, and can drop trailing entries; a witnessed anchor (inspeximus actions timestamp, or a co-signed anchor) is what closes that, and the code's own docstrings say so. The comparison page carries the other measurement this library is built around: whether a correction stays corrected.