Pausing and Resuming LangGraph Agents with interrupt() and Command
How LangGraph's interrupt() and Command build on checkpoints and threads to pause an agent for human approval, edits or input, then resume it safely later.
This article is published in English.
An agent that can move money, delete resources or deploy to production should not always be allowed to finish what it started. Sometimes the graph must stop on purpose, wait hours for a person, and then continue exactly where it left off. This article explains how LangGraph does that with interrupt() and Command, why it depends on checkpoints and threads, and which decisions you still own before running human-in-the-loop (HITL) agents in production.
The building blocks underneath
HITL sits on three layers. First, graph execution: state enters a node, and an edge picks the next node.
STATE
↓
NODE
↓
EDGE
↓
NEXT NODE
Second, state management: nodes return updates, and reducers merge them into the new state.
NODE
↓
STATE UPDATE
↓
REDUCER
↓
NEW STATE
Third, persistence: a checkpointer saves state as checkpoints grouped under a thread.
STATE
↓
CHECKPOINT
↓
CHECKPOINTER
↓
THREAD
↓
PERSISTENCE
Together these let an agent run, remember and survive restarts. What is missing is a way to decide whether it should continue at all.
When an agent should not simply continue
A user tells a payments assistant, "Transfer ₹50,000 to this account." The agent prepares the transaction, and nothing technically stops it from executing.
User
↓
Agent
↓
Prepare Transfer
↓
Transfer ₹50,000
↓
Execute
For an action this consequential, a person should review the proposal first, so the flow branches on a human decision.
User
↓
Agent
↓
Prepare Transfer
↓
┌─────────────────────┐
│ HUMAN APPROVAL │
└──────────┬──────────┘
│
┌───┴───┐
↓ ↓
Approve Reject
│ │
↓ ↓
Execute Cancel
The graph now has to halt deliberately: not on an error or crash, but because the process requires human input here.
What human-in-the-loop means
HITL inserts human decisions into an automated flow. Instead of going straight from decision to action,
Agent
↓
Decision
↓
Action
the agent proposes, pauses and waits for review.
Agent
↓
Proposed Action
↓
PAUSE
↓
Human Review
↓
Decision
↓
Continue
The reviewer may approve, reject, edit the proposal, supply missing data, correct the agent or choose between alternatives. That suits any step with real consequences.
Financial transactions
Sending emails
Deleting resources
Production deployments
Database modifications
Legal workflows
Publishing content
Access approvals
Sensitive tool calls
What pausing a graph really requires
Take a chain with an approval step before the last node.
Node A
↓
Node B
↓
Human Approval
↓
Node C
If the reviewer answers six hours later, blocking the process is the wrong answer: it wastes a worker and dies on the first restart.
sleep(21600)
What you want instead is to save state and stop,
Node B
↓
SAVE STATE
↓
PAUSE EXECUTION
↓
then restore it later, possibly in another process, and continue at the next node.
Application can stop──────── 6 HOURS LATER ────────Human responds
↓
RESTORE STATE
↓
CONTINUE EXECUTION
↓
Node C
Persistence already solves half of this. Checkpoints, the checkpointer, threads and thread_id are exactly what a long pause needs.
Checkpoint
Checkpointer
Thread
thread_id
Persistence
How interrupt() stops execution
interrupt() pauses a graph and asks the outside world for a value. You call it inside a node with a JSON-serialisable payload describing what the reviewer should see. The snippet is Python, despite its original label.
from langgraph.types import interrupt
def approval_node(state):
decision = interrupt({
"question": "Do you approve this transaction?",
"amount": state["amount"]
})
return {
"decision": decision
}
When execution reaches the call,
interrupt(...)
the node does not reach its return statement. The run is suspended and the payload goes back to the caller for display.
approval_node
│
↓
interrupt()
│
↓
┌─────────────────┐
│ EXECUTION PAUSED│
└─────────────────┘
Two details matter. interrupt() requires a graph compiled with a checkpointer, since the paused state must be stored somewhere. And on resume, LangGraph re-runs the interrupted node from the top, with interrupt() now returning the human's value, so code before the call runs twice. Keep side effects out of that part of the node, and confirm the behaviour for your version in the LangGraph docs.
What is saved during an interrupt
Consider this graph.
START
↓
Prepare Transfer
↓
Human Approval
↓
Execute Transfer
↓
END
The preparation node updates state, a checkpoint is written, and the approval node calls interrupt().
START
↓
Prepare Transfer
↓
State Updated
↓
Checkpoint
↓
Approval Node
↓
interrupt()
The current state is captured and written to storage.
Current State
│
↓
Checkpoint
│
↓
Persistent Storage
The thread now holds the transfer details, a pending status and the reason for stopping.
THREAD: transaction-123
Checkpoint
────────────────────
amount ₹50,000
recipient Account X
status pending
────────────────────
Execution:
PAUSED
Reason:
Human approval required
That is everything needed to continue later. Without persistence, the process would simply vanish after the interrupt.
Agent
↓
interrupt
↓
process disappears
↓
???
When the reviewer says "Approved" the next day, nobody could tell what was approved, where the graph stopped or which node comes next. With checkpoints, the state is loaded from the thread and restored before resuming.
INTERRUPT
│
↓
CHECKPOINT
│
↓
THREAD
│
↓
PERSISTENT STORAGE
...time passes...
HUMAN INPUT
│
↓
THREAD
│
↓
LOAD CHECKPOINT
│
↓
RESTORE STATE
│
↓
RESUME
Resuming with Command
The application renders the interrupt payload as a question
Approve transfer of ₹50,000?
with two buttons,
[ APPROVE ] [ REJECT ]
and the reviewer picks one.
APPROVE
The answer goes back through Command, which tells the graph how to proceed. To resume, invoke the graph with Command(resume=...) instead of normal input.
from langgraph.types import Command
graph.invoke(
Command(resume="approved"),
config=config
)
The config must carry the same thread as the paused run,
thread_id = transaction-123
because the thread ID is how LangGraph knows which suspended execution you mean.
The lifecycle step by step
A run starts with an ordinary invocation,
graph.invoke()
reads its thread from the config,
thread_id = transaction-123
enters the preparation node,
START
↓
Prepare Transfer
and writes the transfer into state.
amount = ₹50,000
recipient = Account X
status = pending
The approval node calls interrupt(),
Approval Node
↓
interrupt()
and the run is suspended.
EXECUTION PAUSED
The latest checkpoint stays safe under the thread.
Thread
↓
Checkpoint
↓
Persistent Storage
A person sees the request
Approve ₹50,000 transfer?
and answers.
YES
The application sends a resume command
Command(resume="approved")
to the same thread.
transaction-123
LangGraph restores the run, hands the decision to the approval node and continues to the end.
Restore Execution
↓
Human Decision = approved
↓
Continue Graph
↓
Execute Transfer
↓
END
Every pattern below is a variation on this lifecycle.
Human decisions beyond yes or no
A reviewer's answer need not be binary.
YES / NO
Approval executes the proposal.
Agent Proposal
↓
Human
↓
APPROVE
↓
Execute
Rejection cancels it.
Agent Proposal
↓
Human
↓
REJECT
↓
Cancel
Editing changes part of it, such as the amount, before the graph proceeds.
Agent:
Send ₹50,000
↓
Human:
Change to ₹40,000
↓
Graph continues with
updated information
Supplying missing information turns the interrupt into a question.
Agent
↓
Missing Account Number
↓
INTERRUPT
↓
Human provides account
↓
RESUME
Choosing lets the agent offer alternatives and leave the pick to a person.
Agent generates:
Option A
Option B
Option C
↓
Human chooses B
↓
Graph continues
HITL is a general way to inject human input into a running graph, not just an approval dialog.
Command as data flow and control flow
Command can also update state and choose the next destination.
Resume Execution
Update State
Choose Next Destination
A single command can set a status and send execution to a specific node; in LangGraph this form is typically returned from a node.
Command(
update={
"status": "approved"
},
goto="execute"
)
That joins two concerns,
DATA FLOW
+
CONTROL FLOW
since routing used to belong only to edges.
Edges control where we go.
Now one object can carry both the state change and the next step.
Command can participate in
state updates + control decisions.
Correcting state before continuing
Suppose state holds this amount,
amount = 50000
and the reviewer approves with a correction.
Approve, but change amount to 40000.
The flow goes from the original value, through review, to an updated value, and only then resumes (the second snippet arrives flattened onto one line).
BEFORE
amount = 50000 ↓Human Review ↓UPDATEamount = 40000 ↓RESUME
The question changes from "may I continue?" to "inspect, fix if needed, then continue."
Gating sensitive tool calls
HITL is most practical in front of risky tools.
search_web()
read_database()
send_email()
delete_resource()
transfer_money()
A web search can run automatically,
search_web()
↓
Execute Automatically
while deleting a resource should wait for approval.
delete_resource()
↓
INTERRUPT
↓
Human Approval
Each tool call passes a sensitivity check: safe calls run, sensitive ones interrupt for approval or cancellation.
Agent
│
↓
Tool Call
│
↓
Sensitive Tool?
/ \
NO YES
│ │
↓ ↓
Execute INTERRUPT
│ │
│ ↓
│ Human
│ / \
│ Approve Reject
│ │ │
└─────────┘ ↓
Cancel
For a fuller implementation backed by a store, see approval-gated agents in LangGraph.
Review as a deliberate control boundary
Human review is not a patch for an agent that is not smart enough. It is a boundary you place on purpose. Low-risk work runs automatically,
LOW RISK
Search
Read
Summarize
↓
Automatic
and high-risk work goes through a person.
HIGH RISKSend
Delete
Purchase
Transfer
Deploy
↓
Human Approval
The aim is to decide where autonomy is appropriate, not to reduce it everywhere.
Handling rejection and silence
Rejection deserves real design.
Agent
↓
Prepare Action
↓
Interrupt
↓
Human
↓
REJECT
Record the outcome in state,
status = "rejected"
then route on it with a conditional edge.
Decision
/ \
approved rejected
│ │
↓ ↓
Execute Cancel
│ │
↓ ↓
END END
Every earlier concept participates, and human input is just more information shaping execution.
State
Reducers
Conditional Routing
Checkpoints
Threads
Commands
Reviewers may also never answer.
Interrupt
↓
Waiting for approval
↓
1 hour
↓
1 day
↓
7 days
A paused thread can wait indefinitely, so your application needs policies.
Expiration
Timeout
Cancellation
Reminder
Escalation
A common one is a time-boxed wait, usually enforced by a scheduler that resumes stale threads with a timeout value.
Approval Requested
│
↓
Wait up to 24 hours
│
┌───┴────┐
↓ ↓
Response Timeout
│ │
↓ ↓
Resume Cancel
LangGraph supplies primitives; the business rules are yours.
Multiple gates and streaming
A deployment agent may ask for approval of its plan and again before production.
START
↓
Analyze Code
↓
Generate Deployment Plan
↓
Human Approval #1
↓
Deploy to Staging
↓
Run Tests
↓
Human Approval #2
↓
Deploy Production
↓
END
Each gate pauses and resumes the same thread, leaving a history of checkpoints and interrupts.
Thread
│
├── CP1
│
├── CP2
│
├── INTERRUPT
│
├── RESUME
│
├── CP3
│
├── INTERRUPT
│
├── RESUME
│
└── END
That is a durable workflow shared by people and an agent. Until now every example used a blocking call,
graph.invoke(...)
which returns only when the run ends or pauses.
START → A → B → C → END
↓
RESULT
Interactive apps want progress as it happens, up to the approval request.
START
↓
Planner Update ─────────→ UI
↓
Tool Call ──────────────→ UI
↓
Tool Result ────────────→ UI
↓
Approval Required ──────→ UI
Streaming delivers those events so the UI can show completed steps and the pending decision.
┌──────────────────────────────────┐
│ Deployment Agent │
├──────────────────────────────────┤
│ │
│ ✓ Analyzed repository │
│ │
│ ✓ Generated deployment plan │
│ │
│ ✓ Staging tests passed │
│ │
│ ⚠ Production deployment ready │
│ │
│ Human approval required │
│ │
│ [ APPROVE ] [ REJECT ] │
│ │
└──────────────────────────────────┘
Behind that screen, events flow to the frontend and the decision returns through Command(resume=...).
LangGraph
│
↓
Streaming
│
↓
Frontend
│
↓
Human Decision
│
↓
Command(resume=...)
│
↓
LangGraph
The complete execution model
In one diagram: the user invokes the graph on a thread, state moves through nodes and reducers, checkpoints are written, routing picks the next step, and sensitive actions divert to an interrupt that a human resolves.
USER
│
↓
graph.invoke()
│
↓
THREAD
│
↓
CHECKPOINTER
│
↓
STATE
│
↓
NODE
│
↓
STATE UPDATE
│
↓
REDUCER
│
↓
NEW STATE
│
↓
CHECKPOINT
│
↓
EDGE / ROUTING
│
↓
SENSITIVE ACTION?
/ \
NO YES
│ │
↓ ↓
NEXT NODE interrupt()
│
↓
PAUSED
│
↓
HUMAN
/ | \
/ | \
Approve Edit Reject
│ │ │
└────────┼────────┘
↓
Command(resume)
│
↓
SAME thread_id
│
↓
RESTORE CHECKPOINT
│
↓
CONTINUE GRAPH
│
↓
END
As a story, a request arrives
User Input
and execution starts.
START
Nodes read state and return updates, reducers merge them, checkpoints save them under a thread, and edges choose what runs next. The graph may branch, loop or call tools.
BRANCH
LOOP
CALL TOOLS
When a human is needed, an interrupt suspends the run.
interrupt()
The person responds,
APPROVE
REJECT
EDIT
PROVIDE INPUT
a resume command continues from the persisted thread,
Command(resume=...)
and the graph finishes.
END
Most projects start from a single model call,
LLM
↓
Response
and end with a graph combining LLM and tool nodes, reducers, checkpoints, routing, interrupts and commands.
USER
│
↓
START
│
↓
STATE
│
↓
NODE
│
┌──────┴──────┐
│ │
LLM TOOL
│ │
└──────┬──────┘
↓
STATE UPDATE
│
↓
REDUCER
│
↓
NEW STATE
│
↓
CHECKPOINT
│
↓
THREAD
│
↓
ROUTING
/ | \
/ | \
NODE LOOP INTERRUPT
│
↓
HUMAN
/ | \
Approve Edit Reject
\ | /
\ | /
↓
COMMAND
│
↓
RESUME
│
↓
NEXT NODE
│
↓
END
Production checklist
Keep nodes focused
Small single-purpose nodes are easier to test, retry and interrupt safely,
Plan Node
Search Node
Validate Node
Execute Node
unlike one node that does everything.
do_everything_agent()
Design state and reducers deliberately
Keep only what nodes need, since everything is checkpointed and may be shown to reviewers. Pick reducers carefully for fields that accumulate.
messages
events
results
observations
Make side effects idempotent
Irreversible operations need care.
Send Email
Charge Card
Delete Resource
Deploy
Transfer Money
Retries and resumes must never repeat them. Because an interrupted node restarts on resume, put irreversible calls after the approval point or in a separate node, and use idempotency keys where APIs support them.
Use durable persistence
In-memory checkpointers suit learning; production needs durable storage matched to your reliability requirements.
Write down boundaries and limits
Decide these explicitly for each workflow,
What can AI do automatically?
What requires approval?
What can humans modify?
When does approval expire?
and give loops termination conditions.
Maximum attempts
Timeouts
Validation
Failure paths
Escalation
When HITL is worth it
Human review pays off in specific situations.
Cost of wrong action is high
Action is irreversible
Policy requires approval
Agent confidence is insufficient
Sensitive information is involved
Human judgment adds value
Sprinkled everywhere, it defeats automation and trains reviewers to click through without reading.
Agent
↓
Ask Human
↓
Agent
↓
Ask Human
↓
Agent
↓
Ask Human
A better design gives the agent an autonomous zone for planning and reasoning, with one boundary before the consequential action.
AUTONOMOUS ZONE
┌───────────────────────┐
│ │
│ Plan → Search → Think │
│ │
└───────────┬───────────┘
│
↓
CONTROL BOUNDARY
│
↓
HUMAN
│
↓
APPROVED ACTION
How the layers fit together
Graph execution covers nodes, edges and routing;
Graph
Nodes
Edges
START
END
Loops
Conditional Routing
state management covers updates and reducers;
State
State Updates
Reducers
Messages
State Evolution
persistence covers checkpoints, threads and recovery;
Checkpoints
Checkpointer
Threads
thread_id
State History
Recovery
and HITL adds interrupts, commands and review patterns.
interrupt()
Command
Pause
Resume
Approve
Reject
Edit
Human Control
Stacked, they form one pipeline.
GRAPH
↓
STATE
↓
NODE
↓
UPDATE
↓
REDUCER
↓
CHECKPOINT
↓
THREAD
↓
ROUTING
↓
INTERRUPT
↓
HUMAN
↓
COMMAND
↓
RESUME
↓
END
Key takeaways
The vocabulary can look overwhelming,
StateGraph
MessagesState
Reducers
Checkpointers
Threads
Commands
Interrupts
but each piece solves one part of a single goal: an agent that knows, acts, updates, decides, saves progress, stops when it should, asks a person and resumes safely.
Remember what it knows
↓
Perform some work
↓
Update what it knows
↓
Decide what to do next
↓
Persist its progress
↓
Pause when necessary
↓
Ask a human
↓
Resume safely
↓
Continue until complete
- State is what the agent knows, nodes what it does, edges where it goes, reducers how state changes.
- Checkpoints record where it was; threads say which run a history belongs to.
interrupt()marks where to stop and ask;Commandsays how to continue, always on the same thread.- Interrupted nodes restart on resume, so keep irreversible side effects after the approval point.
- Place review at genuine control boundaries, with explicit timeout and escalation rules.