Naive waiting
Bad:
while not approved():
time.sleep(30)
Problems:
- a worker remains occupied;
- the process can restart;
- a deploy destroys the sleeping call stack;
- horizontal workers complicate ownership;
- waiting for days becomes absurd.
Model the wait as state
At the approval boundary, persist something like:
{
"run_id": "run_123",
"status": "awaiting_review",
"review_round": 1,
"draft_report_id": "draft_19",
"review_requested_at": "...",
"review_version": 4
}
Then stop executing that run.
No function has to remain asleep.
What LangGraph interrupt means
Conceptually:
def await_review(state):
decision = interrupt({
"vendor": state["vendor_name"],
"draft": state["draft_report"],
"unknowns": state["unknowns"],
})
return {"review_decision": decision}
Execution timeline:
worker enters await_review
↓
interrupt produced
↓
workflow state/checkpoint persists
↓
worker returns to other work
↓
three hours pass
↓
reviewer submits HTTP request
↓
same durable run/thread is resumed
↓
a worker continues execution
Now the learner has earned the concise rule:
Waiting is state, not compute.
Review payload
Do not ask only:
Approve? Yes / No
Provide enough information to make a responsible decision:
vendor
coverage
unknowns
contradictions
high-risk claims
evidence links
cost so far
what action follows approval
Human review without evidence is theater.
Structured decision
class ReviewDecision(TypedDict):
action: Literal[
"approve",
"reject",
"request_more_research",
]
reviewer_id: str
note: str | None
requirements: list[str]
review_version: int
Stale approval
Important production scenario:
draft version 4 sent for review
↓
more research occurs
↓
draft version 5 exists
↓
reviewer clicks old approval for v4
An approval of version 4 must not silently authorize version 5.
Bind review decisions to a version or immutable artifact hash.
approval.review_version == current_review_version
Otherwise reject the stale decision and request a new review.
Authentication versus authorization
Authentication:
Who is this person?
Authorization:
May this person approve this action for this run?
Do not equate possession of a review URL with authorization.
Possible roles:
viewer
reviewer
admin
Publish may require all of:
user role permits approval
run belongs to user's tenant
run state == awaiting_review
review version matches
decision == approve
Timeouts and escalation
Long human waits need explicit policy:
review_due_at
remind_at
escalate_at
expire_at
Possible behavior:
send reminder
route to backup reviewer
cancel action
continue only under pre-authorized policy
Do not ask the model to invent governance policy.
Durable steering
Human control can include:
cancel
pause
add a constraint
change priority
request specific evidence
reduce budget
Represent these as durable control events, not ephemeral chat instructions.
FAILURE LAB 07: Three-Hour Approval
- Run until
awaiting_review. - Restart the service twice.
- Resume from a new process.
- Verify no worker was occupied during the wait.
- Verify the review payload is unchanged.
- Approve and confirm one publish.
- Try unauthorized approval.
- Try approval of an old review version.
Invalid approvals must not advance state.
Check your understanding
Answer before moving on. If one is fuzzy, the relevant section is a scroll away.
- Why is
sleep()not durable waiting? - What is persisted when a workflow pauses for review?
- Why should approval bind to a specific draft/review version?
- What is authentication versus authorization?
- What happens to the worker while the human is away?
Exit criteria
Observable conditions, not “I understand it”. Check them off; progress is saved in your browser.
- A waiting run holds no worker, no thread, no connection
- Approve, reject, and request-more-research all work, and the research loop is bounded
- The review endpoint is idempotent
- The approval payload shows coverage, unknowns, and evidence counts, not a bare yes/no
- Reviewer edits are folded into state and survive context rebuilds
Primary sources
- LangGraph interrupts: the exact semantics of
interrupt()andCommand(resume=...), including what is replayed on resume. Worth reading before the lab, because the replay behavior is the part that surprises people. - Temporal’s human-in-the-loop example: the same durable-wait pattern in a workflow engine, useful to see that “waiting is state” is an industry invariant and not a LangGraph quirk.