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.39.1

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: 7649bb32db05e344f4d8d6982b92e8886298d9217d0fb4dc93fef155aff37aa3

point the server at it:  INSPEXIMUS_WRITER_KEY_FILE=key.txt
pin this writer as trusted with trust_seeds={'key:7649bb32db05e344f4d8d6982b92e8886298d9217d0fb4dc93fef155aff37aa3'}
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 e1c4d8ccc1 [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 68dfdad73355  SIGNED

$ inspeximus --path mem.json actions knew 0
{
  "seq": 0,
  "action": "tool:sms",
  "ts": 1789648531.390615,
  "memory_state": {
    "digest": "13360a8e1503ce6d7b387b8a9d7eac8823f3bee94c3d55f72f3e3fe9f2c9180a",
    "records": 1,
    "last_receipt": "46a855df52cec14d0f4ea6bed822534a112a66c416248afd8a430aa7f73290f4",
    "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 7649bb32db05e344f4d8d6982b92e8886298d9217d0fb4dc93fef155aff37aa3
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 70b027d0-3796-41a8-aec5-c098a280a32c, tail e97a9b0bcb8c

$ 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.