Human-in-the-loop for LangChain
Human-in-the-loop for LangChain agents, on a phone.
Put one call inside your risky LangChain tool, and it waits until your user approves on their phone.
What LangChain gives you
Middleware that interrupts before a tool
LangChain 1.0 handles approvals in middleware. You list the tools that need approval, and the agent interrupts before those calls with a pending request your code approves, edits or rejects. It runs on LangGraph's interrupt underneath.
agent = create_agent(
model=model,
tools=[issue_refund],
middleware=[
HumanInTheLoopMiddleware(
interrupt_on={"issue_refund": True},
),
],
checkpointer=checkpointer,
)Where it stops
- It needs a checkpointer, so you run the graph runtime whether you wanted it or not.
- The interrupt reaches your code, not a person. Getting it to someone is on you.
- Nothing is written down. There is no record of what the middleware stopped.
The fix
Pushary gets your user's answer.
The middleware stops the tool and hands your code a pending request. decisions.ask() puts the question on your user's phone, waits for Approve or Deny, and saves the answer.
The ask_human tool calls decisions.ask(), which sends the question to your user's phone and waits up to 55 seconds for the answer. It returns approved only on an explicit yes. Use pushary in Python or @pushary/server in TypeScript; there is no wrapper to install.
pip install pusharyimport os
from langchain_core.tools import tool
from pushary import PusharyServer
px = PusharyServer(api_key=os.environ["PUSHARY_API_KEY"])
# Once per user: show them this link to connect their phone.
link = px.enroll("user_123")["universalLink"]
@tool
def ask_human(question: str) -> str:
"""Ask a real person to approve before acting."""
d = px.decisions.ask(question=question, external_id="user_123", type="confirm")
return "approved" if d["approved"] else f"not approved ({d['status']})"
# agent = create_agent(model, tools=[ask_human])How it works
From install to the first answer
Connect your user once. Put the approval on the risky step. Pushary sends the question, waits for the answer and saves it.
- 01
Install the SDK
Run pip install pushary for Python, or npm i @pushary/server for TypeScript. Put your API key in PUSHARY_API_KEY.
- 02
Connect your user once
One SDK call per user returns a connect link. Your user opens it once on their phone with the free Pushary app. No account, no payment.
- 03
Wrap decisions.ask in an @tool
Define an @tool whose body calls px.decisions.ask() and add it to your agent's tools. The docstring tells the model when to use it.
- 04
Act on the answer
The tool returns approved only on an explicit yes. A no, an expired request or no answer means the agent does not take the action.
A question can be a yes or no, a choice from a list, or a typed answer. Every answer is saved.
Before you ship
Things that bite on LangChain
- A tool the model can skip is not a gate
- ask_human is a tool the model chooses to call, so it is optional. A prompt that says “ask first” does not stop a refund. Call decisions.ask() inside the refund tool itself, or gate it with the middleware's interrupt_on.
- No checkpointer means no interrupt
- Without a checkpointer the middleware does nothing useful, because there is nowhere to park the state. Tools run straight through and it looks as if the config was ignored.
- Edit lets a person rewrite tool arguments
- Approving is simple. Editing means a person changes the arguments before the tool runs, which is a direct route into your tool with typed input. Check edited arguments the same way you check model output.
- Tool name keys are exact strings
- interrupt_on is keyed by tool name. Rename the tool and the gate silently stops applying, with no error and no warning. Write a test that checks the guarded tool really interrupts.
The Partner plan
Your agent asks your users, not just you
Ship your agent to your users. Each of them approves its risky actions from their phone, under your brand.
Your users never make a Pushary account or pay us.
Your users answer on their own phones
Connect each user once with a link. They install the free Pushary app and answer from the lock screen, with no account of their own.
No answer means no
Run the action only after an explicit Approve. A request that is still waiting, expired or cancelled never counts as a yes. Pushary keeps every request, so your code can pick it up again after a restart.
Reaches them on their phone
The app comes first, because only the app puts Approve and Deny on the lock screen. Browser notifications come next. You build no delivery system.
Every answer saved
Every answer is saved with who answered and when. You can search and export it, and with a callback URL, each answer is also sent to your server as a signed webhook.
FAQ
Questions, answered
Do I need a special package for LangChain?
Does this work with create_agent and older agent runtimes?
What happens when nobody responds?
Can my end-users answer instead of me?
Is there a TypeScript path?
How does this compare to the LangChain human-in-the-loop middleware?
Let your LangChain agent ask before it acts.
Connect a user once, ask with one call, and run the action only on a yes. Every answer is saved.
Start freePartner plan, $99 a month. Your users never make a Pushary account or pay us.