Skip to main content
A LangGraph agent issues refunds from a node. When the policy says a human must approve, the request should surface as the graph’s own interrupt, where your operators already answer, and the yes must not be reusable for a different amount. The adapter buys exactly that and nothing else; @protect alone already covers the node.

The policy

runnable

The code

This is the adapter’s own example. It needs pip install ctrlrun-langgraph and a LangGraph install, which the harness that runs the other recipes does not have; the adapter’s tests run this shape against a real langgraph in this repository’s CI, and the adapter’s README carries the conformance results.

What the agent sees

The €2,500 refund interrupts the graph with a payload naming the action and its arguments. Resuming with approved: True and the arguments the human saw runs it once. Resuming with different arguments is refused with ApprovalMismatch, the approval is left grantable, and nothing runs. Resuming with approved: False refuses and records who said no.

The receipt

The receipt is the same shape as one from ctrlrun approve: approve/committed, approver ada@example.com. The node ran twice, once to ask and once on resume, so the log holds two action_ids and two approval requests for one refund; the action_hash is continuous, which is why the binding is about content and never about an id.

When an AMBIGUOUS appears

If Stripe’s reply is lost inside the resumed node, the effect is AMBIGUOUS and the graph’s retry, or a re-run of the thread, is refused. Resolve it with ctrlrun resolve refund:txn_2 --committed or --failed; the approval was spent on the execution, so a retry after --failed needs a new interrupt.

Next