Skip to course content
Free LangGraph course

LangGraph for Agentic Workflows

Unit 07.02: Resuming from an approved decision

Approval should be a field in the state that the acting node checks, not an implicit consequence of someone having clicked resume.

Resume is separate from approve

invoke(None, config) continues the run. That is a mechanical operation and it says nothing about whether anyone approved anything - which is why the approval has to be its own field.

The code pauses, sets approved, resumes, and publishes.

from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver

PUBLISHED = []


class State(TypedDict):
    draft: str
    approved: bool


def publish(state: State):
    if not state["approved"]:
        return {"draft": state["draft"] + "  [blocked: not approved]"}
    PUBLISHED.append(state["draft"])
    return {}


g = StateGraph(State)
g.add_node("write", lambda s: {"draft": "the article"})
g.add_node("publish", publish)
g.add_edge(START, "write")
g.add_edge("write", "publish")
g.add_edge("publish", END)
app = g.compile(checkpointer=InMemorySaver(), interrupt_before=["publish"])

config = {"configurable": {"thread_id": "approve-1"}}
app.invoke({"draft": "", "approved": False}, config)
app.update_state(config, {"approved": True})     # the human decision
print(app.invoke(None, config))
print("published:", PUBLISHED)

# `invoke(None, config)` resumes from the checkpoint. The approval is a field
# in the state, so `publish` can check it -- and would refuse if a resume
# happened without one.

publish checks approved and refuses if it is not set. That check looks redundant when the only way to reach it is through a reviewer, and it is the thing that holds when someone adds a second path to publish six months from now, or when a resume is triggered by a retry rather than by a person.

The guard costs two lines and makes the approval requirement a property of the node rather than a property of the workflow's current shape.

The mistake this prevents

The mistake is treating the resume itself as the approval. Anything can trigger a resume - an operator clearing a queue, a retry job, a bug - and none of those events mean a human read the draft. Approval is data; resuming is control flow.

Takeaway

Store approval as a state field and have the acting node check it. Resume is a mechanical operation, and conflating it with approval means anything that restarts a run has implicitly approved it.