Skip to content
Original tutorialDocs reviewed

Pause and resume LangGraph approval without duplicate actions

A local rejection/resumption example and explicit durability, identity and deduplication requirements.

Use caseUse interrupt and Command for approval, rejection and resumption. Distinguish thread IDs, in-memory checkpoints and durable storage, with an audit-record checklist.

Source · JevLog editorialIntermediate15 min

Use interrupt and Command for approval, rejection and resumption. Distinguish thread IDs, in-memory checkpoints and durable storage, with an audit-record checklist.

Source · JevLog editorial · Original guide

Source cover · LangChain

Source cover · LangChain

Published 2026-10-02 from current official docs with original exercises. No live service calls; source publication dates not established.

AI workflows need a place to wait for review. A pause is not authorization; applications validate identity, proposal version and permission. Resumption can restart node code, requiring side-effect protection.

  • Install Python and langgraph and download approval-record.json. The example returns state without external business calls.

  • InMemorySaver is a single-process exercise; restart recovery needs a durable checkpointer.

  • Official docs

Original JevLog diagram, not a product screenshot or measured result.

Original JevLog diagram, not a product screenshot or measured result.

Show proposal ID, version, evidence and intended action. Recheck version server-side so viewing v1 cannot authorize v2. Start with a reversible internal draft.

The first invoke pauses; Command resumes the same thread. Only explicit boolean True grants approval. This example rejects; begin a new thread for an approval exercise.

from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import interrupt, Command
class State(TypedDict):
proposal: str
status: str
def review(state: State):
decision = interrupt({"proposal":state["proposal"], "question":"Approve this draft?"})
approved = isinstance(decision, dict) and decision.get("approved") is True
return {"status":"approved" if approved else "rejected"}
builder = StateGraph(State)
builder.add_node("review", review)
builder.add_edge(START, "review")
builder.add_edge("review", END)
graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable":{"thread_id":"demo-proposal-001"}}
print(graph.invoke({"proposal":"Create an internal draft only","status":"pending"}, config))
print(graph.invoke(Command(resume={"approved":False}), config))

3. Inspect rejection and repeated approval

Section titled “3. Inspect rejection and repeated approval”

A rejection terminates as rejected without actions. Completed tasks are not new approvals; duplicates read the existing outcome. Persist proposal version and reviewer server-side.

Resumption restarts the interrupted node. Separate messaging, charging and creation into a later stage with proposal-and-version uniqueness. Do not swallow interruption exceptions.

The in-memory example does not prove restart recovery. Add durable storage and audit identity, time, version, execution and failures. A thread ID locates state, not authorization; confidence cannot substitute for approval.

Practice sample: download approval-record.json for local use; not a live run here.

A local rejection/resumption example and explicit durability, identity and deduplication requirements.

  • Approval inputs can be stale; recheck proposal and version.
  • Rejection blocks action and repeated approval cannot repeat it.
  • Durability and server-side identity/version checks are specified.

Source boundary: compiled from the linked public sources; not reproduced here. Review classifications before acting; they do not run actions automatically.

No. It locates state; the server validates identity, permission, object and version.