Second in a series on building agents that survive regulatory review. The first was about deleting your approval rule to see whether a test goes red. This one is about the gap that can’t be closed retroactively.
We build systems; your counsel interprets the regulation. The obligations named below are ones your compliance team already tracks.
Two questions that look similar
“Where were we?” A process died mid-run. You need to resume without redoing completed work. This is durable execution, it’s a hard engineering problem, and modern agent frameworks solve it properly. LangGraph checkpoints graph state at each superstep and offers three durability modes — exit, async, sync — trading write frequency against latency. That is real, and it works.
“Who decided this?” A supervisor is looking at one output your system produced three months ago. They want to know what the system had in front of it, which alternatives were available, what rule permitted the action, who had authority to stop it, and whether that person saw enough to exercise the authority.
The first question is about the state. The second is about the transition.
A checkpoint stores the state. That’s not an oversight — it’s the correct design for the problem it solves. You don’t need to know why the graph reached step 7 in order to resume from step 7.
But it means the artefact you have and the artefact you’re asked for are different things.
The asymmetry
This is the part worth internalising, because it changes when you have to decide.
A system built to continue can always be taught to continue better. Add a checkpointer, tighten the durability mode, move the store to Postgres. These are upgrades. You can do them next quarter.
A system built to continue cannot be taught to answer retroactively. In March you saved the state. The reasoning, the rejected alternatives, the permission check, the identity of whoever approved — none of it was written, because nothing asked for it. In December a supervisor asks. There is no migration that recovers information you didn’t record.
So this is not a decision you can defer, which is unusual for an architectural question, and it’s why it’s worth a whole article.
What a decision record contains that a snapshot doesn’t
Concretely, for one gated action:
| Snapshot | Decision record | |
|---|---|---|
| What the system had in front of it | the state, if it happens to be in state | the exact messages sent to the model |
| What it chose | the resulting state | the chosen action and what else was available |
| Why it was permitted | — | the rule that allowed it, by name |
| Who could stop it | — | who was asked, what they were shown |
| Whether they did | approved/denied, if modelled | who, when, on what basis, in their own words |
| What happened next | the next state | the result, and whether it was retried |
The right-hand column isn’t more data for its own sake. Every row maps to a question somebody will ask: record-keeping, human oversight, third-party risk. Your compliance team has the article numbers; the engineering point is that each row has to be written at the moment it’s true, because none of them can be reconstructed later.
The inversion that makes this cheap
The instinct is to add audit logging alongside your state. That’s the expensive version, and it drifts: two sources of truth, and the log is the one nobody reads until it matters, which is when you find out it’s been wrong for two months.
The cheaper arrangement inverts the dependency.
Make the log the state. Append-only. Every event — the model’s turn, the permission decision, the tool result, the approval, the cancellation — is a row. Then derive the current state by reading the log.
Three things follow, and the third is the one that surprises people.
Auditability stops being a feature. You don’t build it, maintain it, or forget to update it. The log is what the agent runs on, so it cannot be stale without the agent being broken.
Recovery stops being a special case. Resuming is replaying. There’s no separate checkpoint format that can disagree with the record.
You can prove the fast path hasn’t drifted. Reading the whole log every step is slow, so you’ll build a projection — a cached view of “is this cancelled, is there an approval pending”. Now you have two representations again. But this time you can write a function that derives state both ways and asserts they agree, and run it as a test. We have one called verify. It’s the difference between believing your cache is right and demonstrating it.
What this costs
Honest accounting, because a post that only lists upsides is one this audience discounts.
Storage. An append-only log of everything is larger than a snapshot of the current state, by a factor that depends on run length. For agent runs this is small in absolute terms — text, not media. For high-frequency systems it isn’t, and you’d want compaction with the raw events archived rather than dropped.
Read cost. Deriving state by replay is O(events). You’ll need the projection above, and now you need the equality test, which is work.
Retention becomes a real question. An append-only log keeps everything verbatim, which collides with data minimisation the moment a customer message contains personal data. You need field-level redaction at write time and a retention policy that can delete without breaking replay. This is genuinely hard and we haven’t finished it — more on that below.
Discipline. The model is not permitted to write to the log directly, only to propose. If an agent can append its own justification, the record is a transcript of what it said, not evidence of what happened.
Where LangGraph actually sits
Being precise here, because the cheap version of this argument is wrong and this audience will catch it.
LangGraph is a low-level orchestration framework for long-running stateful agents. It provides durable execution, streaming, human-in-the-loop pausing, and time-travel debugging. Time travel in particular gets close to what’s needed — you can inspect state at a past superstep and fork from it.
What it optimises for is developer iteration: build a graph fast, resume runs reliably, inspect state when something breaks. Those are the right goals for the overwhelming majority of its users, and it meets them.
Compliance optimises for something in tension with that: a third party’s ability to reconstruct a specific decision, months later, without access to the developers. Every abstraction that hides mechanism helps the first goal and hurts the second. A graph of channels and reducers is a good way to express a workflow and a bad thing to hand a reviewer who asks what permitted a particular write.
There’s a market signal worth noting rather than an argument: Temporal shipped a LangGraph plugin, on the reasoning that a run living in a single process isn’t durable execution. You can read their framing as vendor positioning — the sync durability mode does persist per step — but the plugin existing at all tells you where people hit the edge.
None of which means don’t use LangGraph. It means the evidentiary properties aren’t inherited from your framework choice. They’re built deliberately, as a thin layer you can point at, in whatever you’re already using. If your reviewer has to understand Pregel semantics before they can understand your audit trail, you’ve added a dependency to your compliance argument.
The exercise
Take one decision your system made last month. A declined transaction, a flagged account, an automated response to a customer.
Then, without opening a terminal, answer:
- What exactly did the model have in front of it at that moment?
- What else could it have done?
- Which rule permitted the action it took?
- Who had authority to stop it, and what were they shown?
- Did they act, and on what basis?
If the answer to any of these is “we’d have to reconstruct that”, you have a snapshot. If it’s “we’d need the developer who built it”, you have a snapshot and a person-shaped dependency.
Five for five is the bar, and it’s lower than it sounds — these are the questions, not a supervisor’s full list.
What we haven’t solved
Three, stated plainly, because a series that only describes what works is marketing.
Retention and redaction. Our log keeps everything verbatim, for ever. That’s the correct default for evidence and the wrong one for personal data. Field-level redaction at write time with replay that still works afterwards is the piece we’re building; today it’s a policy decision at ingestion, which is weaker.
Cost accounting. Our budgets are enforced against a per-call cost supplied by the caller. Nobody computes it from a price list. So “this run cost $1.40” is a number we accept rather than derive. If you’re using spend as a control, that matters.
The sandbox is defence in depth, not a security boundary. Code the agent writes runs in a locked-down subprocess with resource limits and an import blocklist. That stops accidents, not a determined adversary. A real boundary is a VM with a threat model, and that’s an infrastructure decision rather than a library one. We say so because a vendor who calls their subprocess “secure” is telling you what they don’t know.
What we do have is a record of what changed. Last month a generated test set caught four real bugs in our signal pipeline, including one where a filter deleted valid records silently — precision stayed at 100% while recall sat at 54%, and nothing in the output looked wrong. Each is a named test now. That’s what “we improve continuously” should look like when it’s true: a changelog with dates, not an intention.