LangGraph
Phone approvals for LangGraph agents. Your agent asks, your user taps Approve or Deny.
In LangGraph, the graph decides what runs next. So the way to require a yes is a
node: put an approval node in front of the risky node, and only draw the edge to it
when the answer is yes. @pushary/langgraph (TypeScript) and pushary-langgraph
(Python) give you that approval node. It sends the question to your user's phone
and returns their answer.
Install
npm i @pushary/langgraph @langchain/langgraph @langchain/core zod
export PUSHARY_API_KEY=pk_xxx.sk_xxxGet the key at Partner onboarding.
Connect a user once
import { connect } from '@pushary/langgraph'
const { universalLink } = await connect({ apiKey: process.env.PUSHARY_API_KEY }, user.id)Show the link to that user. They open it on their phone once. They install the free Pushary app and never sign up or pay.
Put an approval node before the risky step
import { Annotation, END, START, StateGraph } from '@langchain/langgraph'
import { pusharyInterrupt } from '@pushary/langgraph'
const config = { apiKey: process.env.PUSHARY_API_KEY }
const Refund = Annotation.Root({
orderId: Annotation<string>,
customerId: Annotation<string>,
approved: Annotation<boolean>,
})
const graph = new StateGraph(Refund)
.addNode('approval', async (state) => {
const answer = await pusharyInterrupt(config, {
externalId: state.customerId,
question: `Refund order ${state.orderId} in full?`,
type: 'confirm',
})
return { approved: answer === 'yes' }
})
.addNode('refund', async (state) => {
await refundOrder(state.orderId)
return {}
})
.addEdge(START, 'approval')
.addConditionalEdges('approval', (state) => (state.approved ? 'refund' : END))
.addEdge('refund', END)
.compile()
await graph.invoke({ orderId: '4471', customerId: user.id, approved: false })The answer is 'yes', 'no', or nothing if nobody answered. Only 'yes' takes the
edge to refund. The model has no way around it, because the graph's edges decide,
not the model. refundOrder is your own function.
As written, the node waits up to 55 seconds for the answer. If your users may take
longer, pass a callbackUrl and run the graph with a checkpointer. Then the node
pauses the graph with LangGraph's own interrupt(), and you resume it when the
answer arrives. The adapter guide shows the resume step.
Let the model ask a question
import { ChatOpenAI } from '@langchain/openai'
import { createReactAgent } from '@langchain/langgraph/prebuilt'
import { createAskHumanTool } from '@pushary/langgraph'
const askHuman = createAskHumanTool({ apiKey: process.env.PUSHARY_API_KEY }, { externalId: user.id })
const agent = createReactAgent({ llm: new ChatOpenAI({ model: 'gpt-4o' }), tools: [askHuman] })
await agent.invoke({
messages: [{ role: 'user', content: 'Find out which order the customer means.' }],
})A tool is the model's choice to call, so use it for questions like "which order". Use the approval node above for anything that must not happen without a yes.
Take the user id from your signed-in user, never from the model's input.
Source and more examples: pushary-langgraph on GitHub. LangGraph reference: interrupts in TypeScript and in Python.