Human-in-the-loop for LangGraph and LangChain
Human-in-the-loop for LangGraph agents, answered on a phone.
A LangGraph node asks a real person and blocks, or parks on a durable interrupt until they answer.
The integration
Add it to your LangGraph agent
Pushary brings customer confirmation, choices and text to the native app. Use a blocking question for a short wait or LangGraph's interrupt with a persistent checkpointer for delayed answers. Your application resolves the authorized reviewer, stores correlation, validates the recorded response and invokes the intended continuation. A callback does not resume the graph by itself. Updated adapter contracts are awaiting a forthcoming release; use the package README matching your installed version.
pip install pushary-langgraphfrom pushary_langgraph import connect, ask_human
# Connect the user's phone once, keyed to your own id.
connect("user_123")
# Ask a human from inside a LangGraph node. Blocks until they answer, fail-closed.
def approve_transfer(state):
d = ask_human("Approve this transfer?", external_id=state["user_id"], node="approval")
return {"approved": d["approved"]}Why you need it
LangGraph pauses the agent. Pushary reaches the human.
LangGraph owns checkpointing and continuation. Pushary reaches the enrolled customer and records their answer. Your application owns response validation, matching the decision to the saved thread and interrupt, and recovery after an uncertain continuation. A stored decision does not preserve a blocking node or guarantee exactly-once business effects. Enforce approval with an explicit branch before any protected operation, including after a resume.
What LangGraph already gives you
interrupt() and the checkpointer
With a configured checkpointer and thread identity, interrupt() pauses the graph and returns control to your application. A later Command(resume=...) supplies the response and re-executes the interrupted node. Persistence, trusted response routing and replay-safe side effects still need application design.
def approval_node(state):
answer = interrupt({"question": "Approve the refund?"})
return {"approved": answer == "yes"}
# somewhere else, eventually:
graph.invoke(Command(resume="yes"), config)Where it stops
- interrupt() does not tell anybody. The graph is parked and no notification goes anywhere.
- Something has to call Command(resume=...). That resume trigger is yours to build, along with whatever surface the human answers on.
- MemorySaver loses the pause when the process dies. A durable checkpointer is required for any real wait.
- The checkpoint records that the graph stopped, not who decided or what they saw.
Before you ship it
Things that bite on LangGraph
- The whole node re-runs on resume
- This is the one that catches people. When the graph resumes, LangGraph re-executes the node from the top, not from the interrupt call. Any side effect before the interrupt happens twice. If you charge a card and then ask for approval in the same node, you charge twice. Keep everything before the interrupt idempotent, or move it to its own node.
- Multiple interrupts in one node resolve positionally
- Two interrupt() calls in the same node are matched to resume values by order, not by name. Add a conditional branch that skips the first one and the second silently receives the wrong answer.
- Pattern A and Pattern B are different tradeoffs
- A blocking question holds the current request open and can time out. An interrupt requires a persistent checkpointer, saved thread identity and application code that handles the answer and resumes the graph. A callback URL alone supplies none of that recovery.
How it works
From ask to resume
Connect the phone once, then ask a human from the tool or step that guards the action. The waiting, the delivery, and the record are handled for you.
- 01
Install the adapter
Add the package: pip install pushary-langgraph for Python, or npm i @pushary/langgraph for TypeScript. Set your Pushary API key in the environment.
- 02
Connect the phone
Create an enrollment link for the authorized reviewer and have them complete it in the native app. Confirm can offer notification actions; select and input open the app. Resolve the recipient from trusted application ownership.
- 03
Ask a human in a node
Use a short blocking question or the interrupt recipe documented for your installed package. For delayed answers, configure persistent checkpoints and application-owned validation and continuation. An optional agent tool is not an enforced gate.
- 04
Continue on the answer
Branch explicitly on the validated outcome before a protected action. A missing approval must keep that action blocked; select and input return data. Preserve rejection, unanswered and expired states rather than treating every unsuccessful wait as a recorded denial.
A decision is a yes or no confirm, a multiple-choice select, or a free-text input. Every one is stored with its outcome and kept in the audit log.
The Partner plan
Your agent asks your users, not just you
Ship the agent to real users and let each of them approve their own decisions from their phone, under your brand.
Your users never see a Pushary login or bill.
Your users answer on their own phones
The externalId is your own id for the end-user. Connect their phone once with a keyless one-tap link that opens the Pushary app, and every approval lands with the right person. No account and no key on their side.
Durable, fail-closed waits
Pushary stores the decision; your application persists its own paused workflow and schedules resumption. Require an explicit, verified approval before acting. Pending, expired and cancelled requests are not approval.
Phone-first delivery
Pushary reaches the person on the device they carry: the app first, because it is the only channel that puts Approve and Deny on a lock screen, then PWA push notifications, then Slack if you route it there. You do not build a delivery pipeline.
Signed webhooks and an audit trail
Every decision is written to a durable ledger you can query and export, and resolved decisions post a signed webhook to your callback URL. You have the record of who approved what and when.
FAQ
Questions, answered
Is there a LangGraph package for Pushary?
Should I use ask_human or pushary_interrupt?
What happens if nobody answers?
Can my own end-users approve on their own phones?
Does this work with plain LangChain agents?
Why is my LangGraph interrupt not working?
Let your LangGraph agent ask a human.
Connect a user with one tap, ask a human with one call, and act on a fail-closed decision. Every decision is saved to a durable ledger you can audit.
Start with PusharyPartner plan, $99 a month. Your users never see a Pushary login or bill.