Human-in-the-loop API for AI agents

Decide what your AI agent is allowed to do.

Your agent asks before it acts. A $42 refund just runs. A $4,200 refund asks the right person first, on their phone.

Start free

@pushary/server · @pushary/ai-sdk · @pushary/eve · pip install pushary

What is a human-in-the-loop API?

Sometimes an AI agent needs a person to say yes before it acts. A human-in-the-loop API asks that person and sends the answer back. Pushary is that API, and your rules can also allow or deny on their own. Your agent calls authorize() before a consequential action, and your rules answer three ways: allow it, deny it, or send it to a named person and wait for their tap. Refunds under $100 run on their own. Refunds at or over $1,000 go to the person whose money it is. Anything your rules do not name goes to a person, so an empty policy fails safe. If nobody answers inside your window, your code gets approved: false and the action does not run. Every answer is saved, with the rule or the person that gave it.

You write the rules, not the if-statements

A rule can read the amount, so you get "refunds over $1,000 need approval" instead of "all refunds need approval".

your rules
refund.create   amount < 100       allow
refund.create   amount >= 1000     require_approval
refund.create   amount >= 10000    deny

$42 runs on its own. $4,200 asks your user first. $40,000 never happens. Anything you forgot to write a rule for asks a person, so a gap is never a yes.

allow
A rule allows this action. The agent carries on, nobody is asked, and the record shows which rule allowed it.
deny
A rule blocks it. The agent stops and gets a plain reason, so it does not just try the same thing again.
requires_human
A rule asks for a person, or no rule covers the action. Either way, the person your code named gets it on their phone. A missing rule asks. It never allows.

The strictest rule wins, so the order you write them in never changes the answer. The rule decides whether to ask. You decide who gets asked.

The call

Connect once. Then one call before the action.

One call sits in front of the refund, the deploy, the delete. Your rules answer it. A phone only gets involved when they cannot.

npm i @pushary/server
import { createPusharyServer } from "@pushary/server"

// Rules need a server key from Settings > API keys.
// The key from onboarding can ask a person, but it cannot read your rules.
const px = createPusharyServer({ apiKey: process.env.PUSHARY_API_KEY! })

// Once per person: show them this link to connect. No account needed.
const { universalLink } = await px.enroll(user.id)

// Rules answer first. This one is over your $1,000 threshold.
const decision = await px.authorize({
  toolName: "refund.create",
  toolTarget: "order_4471",
  externalId: user.id,
  parameters: { amount: 4200 },
  question: "Approve a $4,200 refund for order #4471?",
})

if (!decision.approved) throw new Error(decision.reason)
await issueRefund()

Every call returns the same shape

Three outcomes, one shape. Check approved and carry on. Keep the decision id if you need to pick the run back up later.

what comes back
// $42. A rule allowed it. Nobody was asked.
{ approved: true,  resolvedBy: "policy", reason: "Allowed by policy rule refund.create." }

// $4,200. Your >= 1000 rule sent it to a person. They tapped Approve.
{ approved: true,  resolvedBy: "human",  reason: "A person approved it." }

// $4,200, and nobody answered inside the window.
{ approved: false, resolvedBy: "human",  reason: "Nobody answered, so this was not approved." }

If nobody answers in time, the answer is no. Your agent gets a plain no with a reason it can log, and the refund does not happen. An action your rules never named comes back the same way, settled by a person instead of a rule.

After the pause

Your framework stops the agent. It does not decide anything.

LangGraph interrupts. The Vercel AI SDK marks a tool call as needing approval. The OpenAI Agents SDK records an approval item. Every one of them hands you a paused run and leaves the rest to you.

Deciding
Something has to read the action, like the refund amount, and pick the right rule every time. When it cannot read the amount, it should ask, not let the action through.
Changing the answer
Moving a limit from $500 to $750 should not need a code change. In a framework, the rule lives in your code. Here you change one rule, with no deploy.

How it works

From call to answer

Install it, connect the person who answers, write the rules, and put one call in front of the action. The deciding, the waiting, the delivery and the record are handled for you.

  1. 01

    Install the SDK

    npm i @pushary/server in TypeScript, or pip install pushary in Python. Published adapters cover the Vercel AI SDK, LangGraph, Mastra, the OpenAI Agents SDK, CrewAI and Eve.

  2. 02

    Connect the person who answers

    Call enroll() with your own user id. They tap the link once, it opens the Pushary app, and Approve and Deny land on their lock screen. No account and no key on their side.

  3. 03

    Write the rules

    A rule is an action, a condition on its parameters, and an effect. Under $100 allow. At or over $1,000 require approval. At or over $10,000 deny. Most restrictive wins, so the answer never depends on the order you wrote them in.

  4. 04

    Call authorize() before the action

    Your rules answer first, and the question reaches a person only when no rule can settle it. If nobody answers in your window it comes back denied, so a timeout never becomes an unapproved action.

A question can be a yes or no, a choice from a list, or a typed answer. Every answer is saved.

Questions, answered

How do I add human-in-the-loop to an AI agent I already built?
Install @pushary/server in TypeScript or pip install pushary in Python, write your rules once, then add one call. authorize() goes inside the tool or step that guards the risky action: your rules answer it first, and it blocks on a person only when no rule can settle it. enroll(externalId) connects the phone of whoever answers. Use decisions.ask() instead when you mean to ask a person every time. Nothing about your agent's control flow has to change.
Who decides which person gets asked?
You do, by the externalId you pass to authorize(). A rule decides whether to ask. Your code decides who. The same integration can ask the customer about a refund and an on-call engineer about a deploy.
Does this mean more interruptions?
It takes them away. Every case a person approved twice is a case a rule can settle on its own, and the rule takes it off their phone permanently. Shrinking what reaches a person is the point, because that is what lets you run the agent on more actions than you would dare to without it.
What happens if nobody answers?
Your code gets approved: false when the wait ends, so the action does not run. That is not a recorded denial. The decision stays open until someone answers it, it is cancelled, or it expires. Save the decision id if you want to pick it up later from a verified callback or by polling. Never run the action from a timeout.
Do my end-users need an account or app?
No Pushary account, no API key and no bill. To answer from the lock screen they install the free Pushary app, then open your connect link once. Someone who will not install it can get the question in a supported browser instead. On iPhone that means adding a Home Screen web app. Your product's name leads every notification, and the phone shows the Pushary app icon above it.
Which frameworks does Pushary support?
Published adapters cover the Vercel AI SDK (@pushary/ai-sdk), Eve (@pushary/eve), LangGraph (@pushary/langgraph, or pushary-langgraph in Python), Mastra (@pushary/mastra), the OpenAI Agents SDK (@pushary/openai-agents, or pushary-openai-agents), CrewAI (pushary-crewai), Pydantic AI (pushary-pydantic-ai), Haystack (pushary-haystack) and the Claude Agent SDK (@pushary/claude-agent-sdk). LangChain, Hermes and OpenClaw call @pushary/server in TypeScript or pip install pushary in Python directly. n8n has a community node, n8n-nodes-pushary. Any other MCP client uses the hosted MCP server, which is configuration rather than an install.
How much does it cost?
Partner Launch is $99 a month for up to 5,000 of your users and 25,000 approval requests a month. The 3-day trial covers up to 100 users and 100 approval requests. You add a card at checkout. There are no automatic overage charges.

Decide it before it happens.

Write the rules once. Your agent asks before it acts and gets a yes, a no, or a real person's answer. Every answer is saved, with the rule or the person that gave it.

Start free

Human-in-the-loop by framework

By product type

More

The four levels of oversight

Oversight of an AI agent stacks in four layers. The first reports an action once it has already happened. The second holds an action open until a person answers. The third resolves the routine cases from rules, so a person is asked only when it matters. The fourth keeps the record of what ran and who cleared it. Each layer is worth less without the one beneath it, which is why a notification on its own is something you watch rather than something you control.

LevelQuestion it answersWhen it acts
1. NotifyWhat is the agent doing?After the fact, no decision
2. ApproveShould this run?In the moment, blocks
3. PolicyWhich cases need me at all?Before the moment, automatically
4. AuditWhat happened, and who decided?After, durable record
Notify
Notify is awareness with no decision: the agent acts, then tells you what happened. It is parity now that Claude Code, Codex and Cursor all ship native notifications, so the only thing left to compete on is whether the message actually reaches you. Notifications across every agent
Approve
Approve is a gate in the moment: the agent stops before the action and does not move until a human answers yes or no. A gate only counts if it really holds the agent, which takes a hook sitting in front of the call itself. Anything that merely announces an approval is wanted, without the power to stop what comes next, is notification wearing a different label.
Policy
Policy is rules that settle the routine cases without you, turning a call into an allow, an ask or a deny before any person sees it. The version worth having reads the parameters a call carries rather than only its name, so a harmless status check can clear itself while a push to a shared remote still stops for you. Permission policies
Audit
Audit is the durable record after the fact: every decision, whatever made it, and what changed as a result. It has to include the calls nobody was ever shown, since anything a rule cleared on its own still belongs in the record together with the rule that cleared it. Keep only the moments somebody was interrupted and the record omits nearly everything the agent was free to do. The audit trail

The full argument for why the levels stack, and why notify stopped being the product, is in The four levels of AI agent oversight: notify, approve, policy, audit.

Controlling agents you run yourself

The same two calls work on any framework, listed here or not, anywhere @pushary/server or pip install pushary runs.

Using AI agents yourself? Get their questions on your phone or your Mac, and answer from either one. See the agent plans.