Manav.id
Developer · 4 min read

Why database pause states fail as human-in-the-loop evidence

Why database pause states fail as human-in-the-loop evidence

Framework interrupt primitives solved a real engineering problem: how to suspend a graph mid-execution and resume it later. They were not designed to produce evidence, and they are being used as though they were.

Why is a framework interrupt not evidence of human approval?

Because it is a checkpointing mechanism. LangGraph-style interrupts persist graph state so execution can resume; the resume is an API call carrying a value, authenticated by whatever authenticates the application. Nothing in that path records which human decided, or what they were shown.

Key takeaways
  • An interrupt is a checkpointing mechanism. It persists graph state so execution can resume; it makes no claim about who approved the resumption.
  • The resume path is typically an API call carrying a value, authenticated by whatever authenticates the application. The human is upstream and unrecorded.
  • Binding the approval to the proposed action's canonical form gives the framework something to verify before resuming, with minimal integration surface.

What an interrupt actually does

Checkpointer rowthe graph was resumedApplication endpointan authenticated session called resumeSessionsomeone logged in earlierThe humannothing connects them to this decision
Each layer does its job. The chain simply does not reach a human.

Modern agent frameworks provide a way to suspend execution at a node, persist the graph state to a checkpointer, and resume later with a value supplied by the caller. Implementations differ; the shape is consistent.

# Conceptual shape, not any specific framework's API
value = interrupt({"proposed_action": action})   # execution suspends
# ... state written to the checkpointer, process may exit ...
# later:
resume(thread_id, {"approved": True})            # execution continues

This is good engineering. Durable suspension across process boundaries is genuinely useful and not trivial to implement.

It is also, precisely, a resumption mechanism. Nothing in it concerns who decided to resume.

Tracing the approval backwards

Follow the chain from the resume call outward and see where it terminates.

LayerWhat it knows
Checkpointer rowThe graph was resumed with this value
Application endpointAn authenticated session called resume
SessionA user logged in at some earlier time
The humanNothing recorded connects them to this specific decision

Each layer is doing its job. The chain simply does not extend to a person and a specific proposed action, and it terminates in a row that the application wrote about itself.

Three failure modes that follow

  1. Session-level approval. A stolen or replayed session resumes the graph. No login occurs, no MFA is invoked, and the resume is indistinguishable from a legitimate one.
  2. Proposal mutation. The action shown to the human and the action resumed are different objects. If the payload is reconstructed after approval, or the state is modified between suspension and resumption, the approval covers something that no longer exists.
  3. Unrendered content. The approval UI shows a summary. The action carries fields the summary omitted. The human approved what they saw.

Why a checkpointer cannot fix this

It is tempting to strengthen the checkpointer — append-only storage, immutable rows, cryptographic hashing of state.

That helps against post-hoc tampering and does not address the core issue, which is that the checkpointer records what the application told it. An application that resumes on a forged session writes a perfectly immutable record of a resumption that no human authorised.

Integrity of the record and authenticity of the decision are different properties. The first is achievable inside the system; the second requires something from outside it.

The integration, kept small

The design goal is to change the resume path and nothing else.

  1. At suspension, canonicalise the proposed action and compute a digest. Carry it in the interrupt payload.
  2. Render the action to the human from its canonical form — every field that will execute, not a model-written summary.
  3. The human signs a statement containing the digest, using a credential under their sole control.
  4. The resume call carries the receipt rather than a boolean.
  5. Before resuming, verify: the signature is valid, and the digest matches the action as it stands now. A mismatch halts rather than proceeding.

Step five is what closes proposal mutation. Verification happens against current state at resume time, not against what was stored at suspension.

Which interrupts warrant this

Not all of them. Many human-in-the-loop points are clarifications — which of these three options, is this the right customer — where a boolean is entirely appropriate and a signature would be ceremony.

Reserve it for interrupts guarding irreversible effects. In most production graphs that is a small number of nodes, and identifying them is a half-day exercise that most teams have not done.

What this gives a compliance function

An artefact they can hand to an auditor that does not require the auditor to trust the application's database. Under any oversight regime asking whether a human reviewed an automated decision, the difference between a row and a signed statement is the difference between an assertion and evidence.

Three ways this fails in production

Failure modes of a row-based resume
FailureWhat happens
Session-level approvalA stolen or replayed session resumes the graph. No login, no MFA, indistinguishable from a legitimate resume.
Proposal mutationThe action shown and the action resumed are different objects, because the payload was rebuilt or state changed in between.
Unrendered contentThe interface showed a summary; the action carries fields the summary omitted. The human approved what they saw.

Strengthening the checkpointer does not help. Append-only storage and hashed state protect against post-hoc tampering, and an application that resumes on a forged session writes a perfectly immutable record of a resumption no human authorised. Integrity of the record and authenticity of the decision are different properties.

Objections and honest limits

“This is a criticism of agent frameworks.” It is not. Durable suspension across process boundaries is genuinely hard and these primitives solve it well. Producing non-repudiable approval evidence is outside their scope, which is the point.

“Every interrupt would need a signature.” No. Clarification interrupts — which of these three, is this the right customer — are fine with a boolean. Reserve signatures for interrupts guarding irreversible effects, which in most graphs is a handful of nodes.

Closing the resume path

  1. Canonicalise the proposed action at suspension. Compute a digest and carry it in the interrupt payload.
  2. Render from the canonical form. Every field that will execute, not a model-written summary.
  3. Sign the digest, not a boolean. With a credential under the human's sole control and user verification.
  4. Carry the receipt on the resume call. In place of the approval value.
  5. Recompute before resuming. Against current state, not against what was stored. A mismatch halts rather than proceeding.

Terms used here

Interrupt
A framework primitive that suspends graph execution and persists state so it can be resumed later, possibly in a different process.
Checkpointer
The store holding suspended graph state. It records what the application told it, which is the limit of what it can evidence.
Proposal mutation
The gap between the action displayed for approval and the action that executes, when nothing binds the two together.

Frequently asked questions

Is this a criticism of agent frameworks? No. Interrupt and checkpoint primitives solve durable suspension well. Producing non-repudiable approval evidence is outside their scope, and the point is that enterprises need it from somewhere.

Would an immutable checkpointer be enough? No. Immutability protects the record's integrity. It does not establish that a human authorised the resumption, because the record reflects what the application asserted.

Does every interrupt need a signature? No. Clarification interrupts are fine with a boolean. Reserve signatures for interrupts guarding irreversible effects, which are a small minority of nodes.

How much integration work is this? A digest in the interrupt payload, a rendering change, and a verification step before resume. The graph structure is untouched.

What does a compliance function get from it? An artefact that does not require the auditor to trust the application's database — the difference between an assertion and evidence.

Where this fits in Manav

Manav slots into the resume path: the interrupt payload carries a canonical digest, the human signs it on their own device, and the graph refuses to resume unless the signature verifies and the digest still matches the action as it now stands.

See the approval gate →

Sources and further reading