How to add human-in-the-loop to a Vercel AI SDK agent
Add a human approval step to a Vercel AI SDK agent with @pushary/ai-sdk: an askHuman tool that reaches a person on their phone and blocks until they decide.
To add human-in-the-loop to a Vercel AI SDK agent, install @pushary/ai-sdk and give your agent an askHuman tool. When the model reaches a step a person should sign off on, it calls the tool, the question lands on that person's phone, and the tool's execute blocks until they approve, decline, or type an answer. The result flows back into the agent loop as a normal tool result, and silence resolves to not approved.
Key takeaways
createPusharyTools({ externalId })returns anaskHumantool you spread into thetoolsofgenerateTextorstreamText. The tool loop itself does the pausing.- The
externalIdis your own id for the user. They complete enrollment in the Pushary app or a supported browser before receiving requests. - A decline, expiry, or no answer is reported as not approved. Your execution code must enforce that result; giving the model an optional tool does not guard other actions.
Why the tool loop is the right seam
The AI SDK's agent loop already knows how to pause. When the model emits a tool call, generateText waits for that tool's execute to resolve before it continues. That means a human approval does not need special orchestration: make the human a tool, and the loop holds the agent for you.
The part the SDK leaves to you is the human side. needsApproval can mark a tool call as requiring approval, and then the pending call is handed back to your code. Nothing is delivered, nobody is notified, and nothing records what was decided. If the approver is away from the keyboard, or the approver is one of your users rather than you, that gap is the entire problem.
The whole integration
npm i @pushary/ai-sdkimport { generateText, stepCountIs } from "ai"
import { openai } from "@ai-sdk/openai"
import { createPusharyTools, enroll } from "@pushary/ai-sdk"
// Create a personal enrollment link; the user must complete setup.
const { universalLink } = await enroll({ apiKey: process.env.PUSHARY_API_KEY! }, user.id)
// Show universalLink as a button or QR; the user consents in the app or a supported browser.
const { text } = await generateText({
model: openai("gpt-4o"),
tools: createPusharyTools({
apiKey: process.env.PUSHARY_API_KEY!,
externalId: user.id,
}),
stopWhen: stepCountIs(10),
prompt: "Issue the refund only if a human approves it.",
})enroll() creates a link for user setup; calling it alone does not connect a phone. After enrollment, createPusharyTools({ externalId }) gives the model an askHuman tool scoped to that person.
When the model calls askHuman, Pushary creates a decision in a durable ledger, sends a push notification to the person's phone through connected app and browser channels, plus configured Slack delivery, and polls the decision until it resolves. The tool result tells the model what the human said, including the option they chose or the text they typed for select and input questions.
An askHuman tool supplies a question the model may choose to ask. To enforce
approval, gate the actual action in your execution code using the
approval adapter. For serverless requests, use the
create-and-resume flow.
Fail-closed, spelled out
For a confirm question, askHuman reports a decline, expiry, or unanswered wait as not approved. A timeout is not a recorded denial: the decision may still be pending. Its row survives a restart, but your application must save the decision ID and proposed action to recover the workflow. Validate select and input answers against your application's rules; arbitrary text is not permission to execute.
Your users, not just you
Choose externalId on your backend from the authenticated session, not from model-generated arguments. It determines which user receives the request. Invited users complete channel setup without a Pushary account or subscription.
Where to go next
The Vercel AI SDK integration page is the condensed version of this post. The human-in-the-loop hub covers the pattern across LangGraph, CrewAI, Mastra, the OpenAI Agents SDK, Eve, Hermes, and MCP. The framework adapters guide has the two-call quickstart for every framework.
Frequently asked questions
How do I add human-in-the-loop to a Vercel AI SDK agent?
Install @pushary/ai-sdk and call createPusharyTools({ externalId }). It returns an askHuman tool you spread into the tools object of generateText or streamText. When the model calls it, the question is delivered to that person's phone as a push notification and the tool blocks until they approve, deny, or type an answer. No reply means not approved.
How is this different from the AI SDK's needsApproval option?
needsApproval pauses the tool call and hands the decision back to your code. It does not deliver anything to a person or wait for them. Pushary supplies that half: the delivery to a phone, the blocking wait, the fail-closed timeout, and the record. The two compose, with needsApproval deciding when to stop and Pushary reaching the human.
Do my end-users need a Pushary account?
No. You reference each user with your own externalId and show them a personal enrollment link. They must complete app or supported browser setup, but need no Pushary account, API key, or paid plan. Native phone approvals require the Pushary app.