Full walkthrough: Human-in-the-loop for the Vercel AI SDK. Reaching your own end-users on their phones is the Pushary Partner plan.
Human-in-the-loop for the Vercel AI SDK. Give your agent one tool that pauses until a real human approves on their phone, and answers from the lock screen.
Two calls is the whole integration:
enroll(externalId)once per end-user. Show them the link it returns. One tap connects their phone.- Add
createPusharyTools({ externalId })to your agent. Now it can ask that person and block on the answer.
No UI to build, no polling to write, no webhooks required. Requires the Pushary Partner plan.
npm i @pushary/ai-sdk ai zodSet PUSHARY_API_KEY (get it in your dashboard).
import { enroll } from '@pushary/ai-sdk'
const { universalLink } = await enroll({ apiKey: process.env.PUSHARY_API_KEY! }, user.id)
// Show universalLink to the user as a button or QR. One tap turns on approvals.
// Cache the fact that they enrolled, not the link itself (it is single-use).import { generateText, stepCountIs } from 'ai'
import { openai } from '@ai-sdk/openai'
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, // the enrolled person who answers
}),
stopWhen: stepCountIs(10),
prompt: 'Issue the refund only if a human approves it.',
})The agent gets an askHuman tool. When it calls it, the person gets a push notification and approves, declines, picks an option, or types an answer from their phone. The tool blocks until they reply, then hands the model an unambiguous result.
- Fail-closed. A declined, expired, or unanswered
confirmis reported to the model as "not approved, do not proceed." Approval only happens on an explicit yes. - Serverless-safe. Each ask blocks up to 55 seconds by default (
timeoutMs). The decision stays answerable for its full lifetime, so a slow human still resolves it. For waits of minutes or hours, run under a durable workflow (Inngest, Temporal, Vercel Workflow) and use acallbackUrl. - No double-asks on retry. The idempotency key is derived deterministically, so a retried step reuses the same decision instead of paging the human twice.
config: { apiKey, externalId, agentName?, timeoutMs?, baseUrl? }. Returns { askHuman }, a tool you pass to generateText / streamText. Merge it with your own tools:
tools: { ...createPusharyTools({ apiKey, externalId }), ...myOtherTools }config: { apiKey, baseUrl? }. Returns { token, deepLink, universalLink, expiresInSeconds }.
This package is a thin wrapper over @pushary/server (enroll + decisions.ask). Use that directly for any framework, or reach for the Pushary MCP server to wire agents up with no code at all. See the adapters guide.
MIT
A runnable example is in examples/.