AI agents · · 5 min read
LangGraph: pause an AI proposal for human approval with Python
Build and test a small approval gate that pauses a proposal, accepts an explicit decision and keeps review separate from sending or executing work.
By Sociologix Editorial

Give the reviewer a real stopping point
An AI worker can draft a project proposal, but a confident draft is not permission to send it. A useful review flow has a visible pending state, the exact content being reviewed and a deliberate decision before the next action.
This AI-assisted editorial guide uses official sources checked October 11, 2026. We ran the synthetic Python example below locally with Python 3.13 and LangGraph 1.2.14: both approval and rejection checks passed. No model, email provider, customer information or production system was connected. These results verify the small gate, not a complete proposal service.
1. Set up an isolated example
LangGraph is a framework for stateful workflows. Its package metadata lists Python 3.10 or newer and an MIT license. The example pins the release verified for this guide rather than depending on whatever version is newest when you try it. [2]
In a new working folder, use the following Windows PowerShell commands. They install the base LangGraph package into a virtual environment; a model-provider package is unnecessary for this demonstration. The official installation guide documents the base package separately from model integrations. [3]
On macOS or Linux, use python3 to create the environment and .venv/bin/python for subsequent commands. Save the next code block as gate_demo.py in the same folder.
python -m venv .venv
.venv/Scripts/python.exe -m pip install "langgraph==1.2.14"2. Pause, then resume with a decision
LangGraph interrupt() exposes a review payload and suspends the run. Command(resume=...) supplies the response when execution continues. The default invoke() API exposes pending interrupts in __interrupt__. Resume using the same thread ID. [1]
This original example keeps one draft and one decision in state. Each acceptance check uses a separate thread. The hard-coded booleans simulate reviewer input; they are test fixtures, not real human authorization.
from typing import TypedDict
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import END, START, StateGraph
from langgraph.types import Command, interrupt
class Review(TypedDict):
proposal: str
decision: str
def review_proposal(state: Review):
answer = interrupt({
"proposal": state["proposal"],
"question": "Approve this exact draft?",
})
if type(answer) is not bool:
raise ValueError("Approval must be a JSON boolean")
return {"decision": "approved" if answer else "rejected"}
builder = StateGraph(Review)
builder.add_node("review", review_proposal)
builder.add_edge(START, "review")
builder.add_edge("review", END)
graph = builder.compile(checkpointer=InMemorySaver())
# Synthetic acceptance checks; these simulate reviewer decisions.
for expected, answer in [("approved", True), ("rejected", False)]:
config = {"configurable": {"thread_id": "demo-" + expected}}
waiting = graph.invoke({
"proposal": "Draft: review a mock website contact form.",
"decision": "pending",
}, config)
assert waiting["decision"] == "pending"
assert waiting["__interrupt__"][0].value["proposal"]
print(expected, "paused before decision")
finished = graph.invoke(Command(resume=answer), config)
assert finished["decision"] == expected
assert not finished.get("__interrupt__")
print(expected, "resumed:", finished["decision"])3. Read what the checks actually prove
Run .venv/Scripts/python.exe gate_demo.py. In our local run, the approved case printed “paused before decision” and then “resumed: approved”; the rejected case printed the corresponding rejected result. The assertions checked a pending decision and an interrupt payload before resumption, followed by the expected final decision and no remaining interrupt.
The function accepts only a Boolean response. A string such as “false” must not accidentally count as approval merely because it is nonempty. Our two passing checks cover True and False; they do not establish malformed-input handling across an application endpoint.
There is no sending node in this graph. An approved result means that this example recorded a decision. It does not mean a message was delivered, an agreement was signed or any project work was authorized outside the demonstration.
4. Keep the checkpoint and the reviewer separate
The example uses InMemorySaver. Its checkpoints live in process memory and disappear when that process restarts. Production review queues need persistent storage; LangGraph documents database-backed checkpointers for that purpose. A checkpointer stores thread state, whereas a store serves data shared across threads. [4][5]
Our implementation recommendation is to bind each proposal thread to its customer, document version and permitted reviewer in your application database. A thread ID identifies saved state; possession of that ID should not grant access to the proposal or the ability to approve it.
On the server, authenticate the reviewer, verify that the proposal is still pending and confirm that its version matches the displayed draft. Record the reviewer, decision and timestamp. If the draft changes materially, request a fresh review rather than carrying the old approval forward. These are application design requirements, not features supplied by this small script.
5. Test the handoff before adding real actions
A resumed node starts again from its beginning, so code before interrupt() can execute again. Keep email delivery or other irreversible actions out of that pre-review path. [1]
Before connecting a real downstream system, use a mock destination and check the cases below. For actual delivery, record the provider result and make retries safe against duplicate actions. Keep “approved,” “delivery attempted” and “delivered” as distinct states so the dashboard reports what really happened.
- Rejection completes without calling the mock delivery function.
- A different customer cannot read or resume another customer’s thread.
- An edited proposal invalidates an earlier decision on the old version.
- A restart preserves a pending review when using the chosen durable checkpointer.
- Repeated approval requests do not create repeated downstream work.
Sources & further reading
Put review controls into your AI workflow
Sociologix can help connect AI-generated drafts to a review queue, a clear approval history and verified downstream actions. Bring one process where your team needs to decide before the automation proceeds.
Talk to Sociologix