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.

bash
pip install pushary-langgraph
two calls
from 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.

native
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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

$99/ month, every framework included
Start with Pushary

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?
Yes. Use pushary-langgraph for Python or @pushary/langgraph for TypeScript, following each package's own README and exported names. Updated review contracts depend on forthcoming SDK and adapter releases; do not assume unpublished source examples work with the current registry version.
Should I use ask_human or pushary_interrupt?
Use a blocking ask for a short wait. Use the interrupt recipe for longer waits, with persistent checkpoints and application code to validate a recorded answer and resume the matching thread. Check the supported contract in the installed package README.
What happens if nobody answers?
No answer supplies no approval. Your graph must keep protected actions blocked and distinguish an unanswered request from a recorded rejection. The decision record and the graph checkpoint are separate; neither automatically triggers continuation.
Can my own end-users approve on their own phones?
Yes. Your application selects the authorized reviewer and enrolls their phone in the native Pushary app. They do not need their own API key or paid plan. The action requester is not necessarily the person permitted to approve it.
Does this work with plain LangChain agents?
Yes. ask_human is a normal function, so you can put it in any LangChain tool or callback the same way you put it in a LangGraph node.
Why is my LangGraph interrupt not working?
Three causes cover almost all of it. There is no checkpointer configured, so there is nowhere to park the state and the graph runs straight through. Or you are resuming with a plain input instead of Command(resume=value), which starts a new run rather than continuing the paused one. Or the interrupt sits inside a tool or a subgraph and you are looking for it on the parent graph's output, where it does not appear. If the pause works but side effects happen twice, that is the node re-running from the top on resume, which is expected behaviour rather than a bug.

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 Pushary

Partner plan, $99 a month. Your users never see a Pushary login or bill.