In April, an AI coding agent took out a company's production database. One GraphQL mutation against the Railway API, nine seconds, and the production volume was gone. The backups attached to it went too. The founder told the story himself; the agent, reports said, then wrote a confession.
The confession made the rounds as a punchline. The detail that should have kept you up at night was quieter: the most complete witness to what happened was the agent itself, narrating its own actions in a transcript nobody outside the company could verify. And nobody doubts the story. But nobody doubting it isn't a property of the evidence. It's a property of the narrator's mood that day.
Two dev.to posts framed this summer's version of the debate. Prompt Injection Is the New SQL Injection is the one everybody quoted, and it ends where these conversations always end: defenses that shrink the blast radius from "catastrophic" to "survivable", plus a call to audit what your agent reads against what it's allowed to do. Its companion, Who's Accountable When the AI Was Just Following Instructions?, doesn't name a culprit. It asks for one thing instead: "a record of who authorized what — an unforgeable one, not logs the acting system can quietly rewrite."
That sentence is the whole problem. Neither thread shows how to build the thing it asks for. And both conversations live in the first gap. The unforgeable record is second-gap territory, and the law landed there two months ago.
Quick map, because I'll lean on it. Any AI system has two gaps. What it reads versus what it's allowed to do: the exploit gap. What it did versus what its logs say: the cover-up gap. This post lives in the second one.
Two months ago, the clock started
And while everyone argued about blame, the law moved. On August 2 the EU AI Act passed its general application date: it now applies everywhere its text doesn't schedule otherwise. The big "otherwise" is the high-risk block of Annex III, which lands on December 2, 2027. Article 12 sits in that block, and it says high-risk systems must technically allow for the automatic recording of events (logs) over their lifetime.
Fourteen months is nothing when the thing being retrofitted is logging that has to survive an auditor. Most agent stacks will check that box with a database table the operator owns. A log you control is a log you can edit, and on the day an authority asks to see it, "we have logs" and "we can prove these logs were never touched" are two very different sentences.
What the text actually asks for
Article 12 stops being abstract once you read it against an agent stack. The system must record events automatically over its lifetime, with enough detail to trace how it functioned, including the situations that pose risk, and to flag substantial modifications. The deployer keeps those logs for at least six months. On request, they go to the competent authority.
In agent terms:
- every tool call, with arguments, results, and which model decision produced it
- every LLM call. Including the transcript the model was fed — yes, that transcript
- config changes and substantial modifications, versioned
- timestamps that don't depend on the honesty of the machine writing them
- and retention you can prove, not retention you describe in a policy nobody reads
Nothing in that list scares anyone. Every product in this space logs it today. The verb that hurts is the last one in the paragraph: provide. Whatever you hand over has to survive someone whose job is to distrust you.
"Automatic" is doing the heavy lifting
Automatic recording is satisfied by the agent writing its own log. Nobody types it, so it's automatic. But a log written by the system it describes, on infrastructure owned by the operator, is a confession written by the suspect.
Append-only flags stop the sloppy mistakes and nobody else. Hash chains stop outsiders. Neither stops the key holder: they can regenerate the entire history, fabricated events, recomputed hashes, re-signed seals, and end up with an internally consistent journal that verifies against its own key with flying colors. Which proves exactly one thing — that somebody with the key built it. Not when. And when is the whole game. A seal created after the fact tells you nothing about what existed before it.
A witness you can build from parts you already have
The fix is a timestamp from someone outside your trust boundary. The pieces are standard, and the core fits on one screen. The chain first:
import hashlib, json
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
key = Ed25519PrivateKey.generate()
def seal(prev_head: bytes, event: dict) -> dict:
payload = json.dumps(event, sort_keys=True).encode()
head = hashlib.sha256(prev_head + payload).digest()
return {
"event": event,
"head": head.hex(),
"prev": prev_head.hex(),
"sig": key.sign(head).hex(),
}
GENESIS = bytes(32) # head of an empty journal
journal = [seal(GENESIS, {"kind": "tool_call", "tool": "db.delete", "ok": True})]
Change one event and every head after it changes. Forge the whole history and every head changes. At this point the chain only verifies against its own key, which proves nothing about when. So you take the head, all 32 bytes of it, and hand it to witnesses that don't work for you:
pip install opentimestamps-client # the only install this post asks for
echo "<last head, hex>" | xxd -r -p > head.bin
ots stamp head.bin # lands in a bitcoin block header
ots upgrade head.bin.ots && ots verify head.bin.ots
openssl ts -query -data head.bin -sha256 -cert -out head.tsq
curl -s -H "Content-Type: application/timestamp-query" \
--data-binary @head.tsq https://freetsa.org/tsr > head.tsr
openssl ts -verify -data head.bin -in head.tsr -CAfile tsa.crt
Two different failure modes for a forger. OpenTimestamps buries your 32-byte head in a bitcoin Merkle tree, and redoing a network's proof of work is not a plan. The RFC 3161 receipt is the legal tier: a qualified timestamp authority carries a presumption of correctness under eIDAS, which is the difference between a technical measure and a measure a lawyer accepts. Notice what neither witness ever sees: an event. Only an opaque digest. The GDPR conversation stays short.
What the auditor gets is almost boring. Three files: the journal export, the anchoring receipts, and a verifier of about sixty lines that recomputes the chain from genesis, checks every signature against the public key you published, and checks each receipt against its external root. Exit 0 or the history is rejected, no matter when the tampering happened — any rewrite produces a different head than the one the witnesses recorded. Next to those files goes the logging description Annexe IV §2(f) asks for, written once, against a journal that can't quietly drift.
Two honest limits
Because a log is the easiest place to oversell compliance.
First: tamper-evidence is not truth-at-write. Anchoring proves the journal wasn't altered after the fact. If the agent records a false event and it gets sealed, you have now proven, with bitcoin's help, that a falsehood existed. Garbage in, sealed forever.
Second, this documents. It doesn't prevent. Article 12 lives in the cover-up gap and leaves the exploit gap exactly where it found it. An agent that shouldn't touch production will still touch production. It'll just do it on the record. That's worth something, but it's not the same thing, and I'd rather not blur them.
If you've been through an AI Act readiness review this summer, yours or a client's, I'm curious what they actually asked about your logs. Comments are open.


Top comments (6)
The operational snag with external TSAs on agent runs is query latency. Running an RFC 3161 request on every individual tool call adds 200 to 400ms of network overhead to tight loops. Batching to turn boundaries or session completion keeps the runtime fast, but an unexpected kill signal leaves the tail unanchored until the recovery pass seals the dangling head.
Right instinct on the latency math — a design that pays a 300ms round-trip per tool call deserves to die. But the witness never sees a tool call. It sees a 32-byte head. The per-event cost is one SHA-256 and one Ed25519 signature, offline — 0.21ms per sealed event, measured. The 200-400ms belongs to the anchor step, and an anchor commits every event since the previous one, so it amortizes over whatever batch it covers. The cadence is a policy knob, not an architectural tax.
The dangling head is the sharper half of your comment, and it splits into two guarantees with different owners. Integrity never lapses: the chain and signatures on disk verify from genesis, and recovery chains from the last flushed head — nothing gets repaired or rewritten. What's missing is only the independent when. And a kill signal doesn't hand the key to anyone new — the adversary that tier exists for is the key holder, and SIGKILL grants them no fresh powers.
The honest limit stands, though: inside the anchor window, the key holder can rewrite the unanchored tail and no external witness would notice. Two operational habits bound that window to something you can defend in an audit. Anchor on a wall-clock timer, not just at turn boundaries — a killed session's blind spot is then minutes, not "whenever recovery runs." And anchor before consequential actions: the agent requests a timestamp, then runs the destructive tool. The tail before anything dangerous then starts at an anchored head, and the cost lands exactly where the stakes are. Tiering helps too — a co-located TSA for fast cadence, the qualified external one for the legal receipts; every receipt is itself a journaled event, so the tiers chain.
What cadence does your stack actually run — turn boundaries, or something tighter?
Reading this after your Firebase piece, and the pairing does something neither post does alone. This one builds the witness. That one shows what happens when nobody has.
Your two honest limits are where I want to spend the comment, because limit one is the whole thing. Tamper-evidence is not truth-at-write. Seal a false event and you have proven a falsehood existed, with a receipt. Everything above that line is solved and everything below it is not.
There is a second move that works on limit one, and it is structural rather than cryptographic. Do not seal your own log. Do not hold it.
UK payment initiation is built that way, under PSD2 rather than the AI Act, and it has been running for years. We initiate. The authoritative record of whether the payment happened lives at the bank. We cannot write to it. The bank has no stake in our claim, and the FCA can ask either of us for our copy. There is no anchoring anywhere in that design. What makes the record trustworthy is custody, not a Merkle tree.
The reason that beats anchoring on your limit one is specific. An anchored journal proves the agent's story has not changed since Tuesday. The bank's record is not the agent's story. It is a second party's record of what it was asked to do and what it did, written by someone with a reason to disagree.
And the reason it does not generalise, which I think is the honest position: it needs a counterparty. A payment cannot happen without a bank acting, so a second record exists whether or not anyone designed for one. An agent that deletes a production volume has no second party. There is nothing to reconcile against, and that is the case where you are back to your design, because anchoring is what you do when custody transfer is not available.
So I read Article 12 as asking for the weaker thing on purpose. Automatic recording and provide are both satisfiable by an operator-held journal with anchoring. The text could not have asked for custody transfer, because for most high-risk systems there is nobody to transfer to. Fourteen months is the right worry, and I think the thing teams will get wrong is not the crypto. It is reading tamper-evident as truth-evident on the day an authority asks, which is the distinction @glenallen drew on @james_anderson_h's post this week.
@reidmarlow's latency point is real and I have nothing better than his batching. Per-tool-call RFC 3161 is not survivable in a tight loop. Batching at turn boundaries leaves the window between the last anchor and the incident unanchored, and that window is where the interesting events are.
"Custody, not a Merkle tree" is the better half of this design space, and the PSD2 reading is the sharpest thing anyone's brought to either thread: the bank's record wins not because it's tamper-evident but because it's written by someone with a reason to disagree, and the mechanism is the requirement that the two records reconcile. An interested counterparty beats a disinterested timestamp authority every time — the TSA vouches for when and is structurally indifferent to what; the bank vouches for both, because it has skin in the outcome. I'll concede the taxonomy: anchoring is what you build when custody transfer is unavailable, and it's the weaker instrument.
Where I'd sharpen the boundary: a second party exists more often than it looks, because most consequential agent actions cross a trust boundary — the storage layer logged the delete, the API holds the request, the provider has the audit trail. The agent that wipes a production volume has a counterparty; it's just unconsulted, and too often it's the same operator on both sides — which is exactly the Firebase piece: Google held the knowable number and published an adjective, because custody without obligation is a number nobody demands. So the rule your comment implies: map each consequential action to its trust boundary. Counterparty exists → demand their stream and reconcile. First-party end to end → anchoring is the honest floor, and Article 12 asking for the weaker thing on purpose is the right reading — the regulation can't mandate a counterparty that doesn't exist. The disclosure that matters on the day an authority asks is which grade of evidence they're holding.
On the anchor window — two bounds on it upthread from the reid exchange: wall-clock cadence (a killed session's blind spot is minutes, not "whenever recovery runs"), and anchoring before consequential actions, so the tail before anything destructive starts at an anchored head. But your last sentence is the one for the wall: the window between the last anchor and the incident is where the interesting events are — the same sentence as "controls stop being tested as controls," pointed at time instead of code.
@slabb — my no second party line was too strong and your delete example is the right correction. The storage layer did record it. I was treating counterparty as something a design has or lacks, when usually it is there and unconsulted.
But your rule needs a third grade, and your own sentence is what tells me so. You said the TSA vouches for when and is structurally indifferent to what, while the bank vouches for both because it has skin in the outcome. Apply that to the storage provider. It will tell you the delete call arrived, authenticated, at that second. It will never tell you the delete should not have happened. It has no position on that and will not acquire one, because nothing it owns moved.
So a provider audit trail is a second custodian rather than a second party. It sits much closer to your timestamp authority than to the bank, and it inherits the TSA's limit exactly: independent on when, silent on whether. That still beats anchoring your own journal, because the record is held by someone you cannot write to. It does not do the thing the bank's record does.
Three grades rather than two, then. An adverse counterparty disputes the substance, because its own money or its own licence is in the transaction. A disinterested custodian confirms the event and is indifferent to its propriety. Self-anchored is you plus a clock. Your reconcile-with-the-counterparty rule is right for grade one and half right for grade two, and the half that does not carry over is the only part I was arguing for: a record written by someone with a reason to disagree.
The practical consequence is in the disclosure you end on. On the day an authority asks, we hold counterparty records reads the same for grade one and grade two, and they are not the same evidence. The provider log cannot be the reason you believe the delete was authorised. Nothing in it is about authorisation.
Where I am genuinely unsure: whether grade two beats a good anchored journal or is just differently shaped. The provider's record is independent of you, which is the whole case for it. It is also produced by a party that can change its retention, its scope and its export format without telling you, and I have no way to price that against a Merkle tree I control.
The three-grade taxonomy is right, and it replaces mine — adverse counterparty, disinterested custodian, anchored self, sorted by whether the record-holder has skin in the substance. Your custodian definition is the precise one: independent on when, silent on whether. The provider log is evidence that an authenticated delete arrived; nothing in it is about whether the delete was authorized — and "we hold counterparty records" hiding a grade-2 behind a grade-1 sentence is exactly the disclosure failure to design out. Three grades, three labels, per event class.
On your pricing question: I'd argue they're not competitors — they're rails that watch each other, because they fail in opposite directions. The custodian's record protects against you: the operator rewriting its own history. Your anchored journal protects against the custodian: retention changed, scope narrowed, export format swapped — the party you correctly note can alter all three without telling you. Custody alone means a silent scope change surfaces at audit time, as one word against an export. Anchor alone means trusting your own security with your own evidence. Run both and the divergence becomes the control: reconcile the custodian stream against your anchored chain on a schedule, and a retention change stops being invisible — it's a finding with a date. Grade 2 plus a grade-3 journal doesn't double the evidence; it makes either side going dark detectable.
ADR 022 gets rewritten around your three grades, lineage included — this thread has quietly become the definitive text on evidence grades, and you're in it whether you intended to be or not.