Human-in-the-loop for the Vercel AI SDK

Let your Vercel AI SDK agent ask a human on their phone.

Your AI SDK agent asks before a refund or a delete. Your user taps Approve or Deny on their phone.

What Vercel AI SDK gives you

toolApproval, with a policy that answers first

AI SDK 7 checks every tool call against toolApproval on generateText, streamText and ToolLoopAgent before it runs. needsApproval on the tool is now deprecated. @ai-sdk/policy-opa can answer from Open Policy Agent rules: allow, deny, needs approval, or not applicable.

native
import { opaPolicy } from "@ai-sdk/policy-opa"

await generateText({
  model,
  tools,
  toolApproval: opaPolicy({ client, path: "agent/call/decision" }),
})

Where it stops

  • A call that needs approval comes back to your code. Nobody is told, and the wait and resume are yours.
  • The policy is Rego you deploy. Moving a refund limit from $500 to $750 is a pull request.
  • It does not know which of your users owns the action, or how to reach them.
  • Running it means maintaining Rego: a compiled bundle in your app, or an OPA server you operate.

The fix

Pushary gets your user's answer.

The AI SDK can stop a tool call before it runs, but it tells nobody. pusharyApproval sends the question to your user's phone and hands the answer back to the SDK.

Pushary adds human approval to AI SDK 7 through toolApproval. The example gates issueRefund and leaves lookupOrder alone. policy: false requires a person to answer instead of letting a saved Pushary rule decide. Approve runs the refund; deny or no answer leaves it blocked. Keep the API key and the signed-in user ID on your server.

bash
npm i @pushary/ai-sdk ai zod
approval on the risky tool
import { generateText, stepCountIs } from "ai"
import { enroll, pusharyApproval } from "@pushary/ai-sdk"

// Once per user: show this link as a button or QR code.
const { universalLink } = await enroll({ apiKey: process.env.PUSHARY_API_KEY! }, user.id)

const { text } = await generateText({
  model: "openai/gpt-4o",
  tools: { issueRefund, lookupOrder },
  // Only issueRefund asks your user. lookupOrder runs untouched.
  toolApproval: pusharyApproval({ externalId: user.id, tools: ["issueRefund"], policy: false }),
  stopWhen: stepCountIs(5),
  prompt: "Refund order 1234.",
})

Or let the model ask a question

createPusharyTools adds an askHuman tool for a yes or no, a choice like Standard or Express shipping, or a typed answer. The model decides when to call it, so it is optional. A prompt that says “ask first” does not stop a refund. Put pusharyApproval on the refund tool for that.

optional question
import { generateText, stepCountIs } from "ai"
import { createPusharyTools } from "@pushary/ai-sdk"

const { text } = await generateText({
  model: "openai/gpt-4o",
  tools: createPusharyTools({ apiKey: process.env.PUSHARY_API_KEY!, externalId: user.id }),
  stopWhen: stepCountIs(10),
  prompt: "Ask the customer to choose Standard or Express shipping.",
})

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.

  1. 01

    Install the package

    Run npm i @pushary/ai-sdk ai zod. Keep your API key in PUSHARY_API_KEY on your server, never in the browser.

  2. 02

    Connect your user once

    One call returns a link. Show it as a button or QR code. Your user opens it on their phone with the free Pushary app. No account, no payment.

  3. 03

    Put the approval on the risky tool

    Pass pusharyApproval to toolApproval with your authenticated user id and the tools that need a yes. Set policy: false when a person must always decide. Every other tool runs as before.

  4. 04

    Act on the answer

    Approve runs the tool. Deny, expiry or no answer comes back denied, and the tool does not run. A retried call reuses the same request instead of asking twice.

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 Vercel AI SDK

toolApproval needs AI SDK 7
The gate uses toolApproval, which AI SDK 5 and 6 do not have. The askHuman tool works from AI SDK 5 on. Upgrade before you rely on the gate, or your refund tool has nothing in front of it.
Give the model a step to use the answer
With a single step, the askHuman answer comes back as a tool result and the run ends. Allow another step with stopWhen if the model should act on that answer. Neither setting gives the model permission to run a risky tool.
Streaming hides the pause
With streamText the UI keeps rendering while the tool waits. It looks like the agent is still thinking when it is waiting on a person. Show the pending approval in your UI, or users will retry the prompt.

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.

$99/ month, every framework included
Start free

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

Is there a Vercel AI SDK package for Pushary?
Yes. @pushary/ai-sdk is on npm. pusharyApproval and pusharyToolApproval plug into the AI SDK's toolApproval and hold a tool until your user approves. createPusharyTools adds an askHuman tool the model can call for questions. Your users connect once with a link and the free Pushary app.
How does the askHuman tool pause the agent?
It holds the tool call open until your user answers or the wait ends, 55 seconds by default. That does not keep your process alive. The adapter returns only the answer it got within the wait. For longer waits, create the decision with @pushary/server and a callback URL, save the AI SDK's pending approval and messages, and pick the run back up when the signed webhook arrives.
What happens if the person never answers?
The action does not run. An unanswered request counts as no, so pusharyApproval comes back denied. Pushary keeps the request, so a late answer is still saved. It does not restart a tool call that already ended, so your code decides whether to ask again.
Do my end-users need a Pushary account or API key?
No. You refer to each user by your own user id. They install the free Pushary app and open your connect link once. They never sign up with Pushary, pay us, or hold a key. Your API key stays on your server.
Where does the notification get delivered?
To your user's phone, through the free Pushary app. The notification reads “<Your product> needs your approval”. Approve and Deny sit on the lock screen for yes or no questions; a choice or a typed answer opens the app. Your user can also answer in a browser.

Let your Vercel AI SDK 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 free

Partner plan, $99 a month. Your users never make a Pushary account or pay us.