Human in the loop and durable agents
An insurance claims agent reads a claim, pulls the policy, checks the hospital bill, and decides the payout is Rs 1.8 lakh. Company rule: anything over Rs 1 lakh needs an adjuster to approve. The adjuster is on leave until Monday. It is Friday evening.
The agent cannot hold a Python process open all weekend waiting for a click, and if the server is redeployed on Saturday, the run must not start again from the beginning and re-read everything. Two capabilities make this work: pausing for a person and durable execution. Every agent that touches money, customers or production eventually needs both.
An agent run pauses for approval and the approver answers three days later. What must have been saved, and where, for the run to continue exactly where it stopped?
Where to put a person#
Not every step needs a human. Put approval where an action is expensive to undo or outward facing:
- money moves (refunds, payouts, purchases)
- something is sent to a customer or published
- data is deleted or permissions change
- the agent is uncertain, or a check failed twice
Everything else runs on its own. An agent that asks for approval on every step trains people to click "approve" without reading, which is worse than no approval at all.
Pause, save, resume#
The same thing in code#
In LangGraph, a node calls interrupt() to pause. With a checkpointer, the state is saved, and a
later call with Command(resume=...) and the same thread id continues from that node.
from langgraph.checkpoint.postgres import PostgresSaver # durable, survives restarts
from langgraph.types import Command, interrupt
def approve_payout(state: ClaimState):
if state["payout"] <= 100_000:
return {"approved_amount": state["payout"]}
decision = interrupt({ # pauses the run here
"action": "payout",
"amount": state["payout"],
"reasons": state["reasons"],
})
return {"approved_amount": decision["amount"] if decision["approved"] else 0}
graph = builder.compile(checkpointer=checkpointer) # e.g. a PostgresSaver
config = {"configurable": {"thread_id": f"claim-{claim_id}"}}
graph.invoke({"claim_id": claim_id}, config) # runs until the interrupt, then returns
# ...days later, from the approval API handler:
graph.invoke(Command(resume={"approved": True, "amount": 150_000}), config)Other stacks have the same shape. The Anthropic SDK's tool runner lets you gate a tool inside its own function or inspect pending tool calls each turn. Hosted agent services expose per tool permission policies such as "always ask". The idea never changes: stop before the risky call, save everything, ask, resume.
Durable execution, beyond approvals#
Pausing for a person is one case of a bigger requirement. An agent run that lasts minutes to hours will meet deploys, crashed workers, rate limits and timeouts. Durable execution means the run's progress is recorded step by step, so any interruption resumes from the last completed step rather than from zero.
Two common ways to get it:
- An agent framework with checkpoints, such as LangGraph with a database backed checkpointer.
- A general durable workflow engine, such as Temporal, where each tool call or model call is an activity whose result is recorded, and the workflow replays from history after a failure. Teams that already run one for payments or orders often put agents on it too.
Two rules make either approach safe:
Make side effects idempotent. A step may run twice: once before a crash and once after resume. Refunds, emails and payouts carry an idempotency key, so the second run is a no-op.
Do not resume on stale facts. If an approval arrives three days later, re-check what might have changed (is the claim still open, is the order already refunded) before acting.
| Choice | What you gain | What you pay | Pick it when |
|---|---|---|---|
| Approve every step | Maximum control. | Approval fatigue: people stop reading and click yes. | Almost never. Only while first trusting a new agent. |
| Approve risky actions only | People look carefully at the few decisions that matter. | Deciding and maintaining what counts as risky. | The default for agents that touch money, customers or production. |
| Checkpointed agent framework | Pauses and resumption built into the agent library. | State storage to run, and checkpoints tied to that framework. | Most agent products with approvals or long runs. |
| Durable workflow engine | Battle tested retries, timers and replay for any long process. | Another system to run and learn, with its own rules about determinism. | Teams already running one, and agents inside critical business processes. |
Checkpoint#
1. What makes it possible to resume an agent run three days after it paused?
2. Why does a payout step need an idempotency key in a durable agent?
3. Where should an agent ask for human approval?
- Approve the irreversible and the outward facing. Not every step.
- Pause = save state, stop, ask. Resume = load state, continue.
- Side effects get idempotency keys. Steps can run twice.
- Re-check facts on resume, and expire approvals that sit too long.
Agents that touch money, customers or production need a person to approve risky actions, and runs that last longer than a request need durable execution. I put approval only where actions are expensive to undo or outward facing, and show the approver the exact action with its reasons, allowing edits. When the agent reaches that step it saves its full state under a run id and stops, with no process waiting. When the decision arrives, possibly days later, any worker loads the state and continues from that step. LangGraph does this with interrupt and a checkpointer, and workflow engines like Temporal do it for any long process. Because steps can run twice, every side effect carries an idempotency key, and I re-check facts that may have changed before acting on a late approval.