Skip to course content
Free LangGraph course

LangGraph for Agentic Workflows

Unit 11.02: Adding the human approval step

The gate goes immediately before the irreversible step, and the irreversible step checks two things rather than one.

Approved, and not already published

publish refuses without approval and refuses if it has already run. Both guards are needed, and they defend against different events.

The code pauses, has a reviewer correct and approve, then resumes twice.

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
    published: bool


def publish(state: State):
    if not state["approved"]:
        return {}                       # never publish without approval
    if state["published"]:
        return {}                       # never publish twice
    PUBLISHED.append(state["draft"])
    return {"published": True}


g = StateGraph(State)
g.add_node("draft", lambda s: {"draft": "refunds take 30 days"})
g.add_node("publish", publish)
g.add_edge(START, "draft")
g.add_edge("draft", "publish")
g.add_edge("publish", END)
app = g.compile(checkpointer=InMemorySaver(), interrupt_before=["publish"])

config = {"configurable": {"thread_id": "capstone-1"}}
app.invoke({"draft": "", "approved": False, "published": False}, config)
print("paused before:", app.get_state(config).next)

app.update_state(config, {"draft": "refunds take 7 days", "approved": True})
app.invoke(None, config)
app.invoke(None, config)               # a duplicate resume
print("published:", PUBLISHED)

The reviewer changed the draft from thirty days to seven and approved it. The graph then resumed twice - simulating a duplicate resume - and published exactly once.

The approval guard defends against a resume that no human triggered. The published guard defends against a second resume by the same human, a retry, or a queue redelivery. Removing either one leaves a hole that only opens under conditions manual testing does not produce.

The mistake this prevents

The mistake is testing the approval flow by clicking through it once. A person doing each thing once will never trigger the duplicate resume, so the missing guard passes every manual test and fails the first time something automated touches the endpoint.

Takeaway

The irreversible node checks both approval and its own guard flag. Test the duplicate resume explicitly - it is the case manual testing structurally cannot produce.