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 you first, on your phone, one tap.

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

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

A human-in-the-loop API is the path an AI agent takes when a decision needs a person. Pushary is that path, and it is also the two answers that avoid it: allow and deny. 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 the call comes back denied. Every verdict is written to a ledger naming the rule or the person that settled 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 the customer 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 of yours names this action and permits it. The agent proceeds, nobody is paged, and the row records which rule decided.
deny
A rule refuses it. The agent stops and gets a reason back in plain text, so the model can act on it instead of retrying the same action.
requires_human
Either a rule of yours asks for a person, or no rule names the action at all. Both land on the phone of the person your code named. An action your rules never mention goes here, so a rule you have not written yet asks rather than 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

One line in front of 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"

const px = createPusharyServer({ apiKey: process.env.PUSHARY_API_KEY! })

// Once per person. One keyless tap, no account of ours.
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()

You always get the same answer back

Three outcomes, one shape. Check whether it was approved and carry on. There is no webhook to catch later.

what comes back
// $42. A rule allowed it. Nobody was paged.
{ 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
A policy evaluator that reads an action's parameters, resolves conflicting rules the same way every time, and treats a parameter it cannot read as a reason to ask rather than a reason to pass.
Changing the answer
Moving a threshold from $500 to $750 should not be a pull request. In a framework the policy is code you ship: a callback, or Rego in an engine you now operate. Here it is a row you edit.

How it works

From call to verdict

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(externalId) with your own id for the user. 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 a person answers is a yes or no confirm, a multiple-choice select, or a free-text input. Every one is stored with its outcome in the decisions ledger.

Questions, answered

What is a human-in-the-loop API?
A human-in-the-loop API is the path an AI agent takes when a decision needs a person. Pushary is that path, and it is also the two answers that avoid it: allow and deny. 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 the call comes back denied. Every verdict is written to a ledger naming the rule or the person that settled it.
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?
The decision fails closed. If nobody answers in the window you set, it comes back denied, and you choose what to do. Because the wait is backed by a durable ledger, a restart does not lose the request.
Do my end-users need an account or app?
No account, no key, no bill. One tap connects their phone: the link opens the Pushary app, which is what carries Approve and Deny on the lock screen. PWA push notifications cover desktop and Android for anyone who will not install it, and on iPhone that channel needs the app anyway. They never see a Pushary login.
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), and CrewAI (pushary-crewai). LangChain, Hermes and OpenClaw call @pushary/server in TypeScript or pip install pushary in Python directly. The Claude Agent SDK and any other MCP client use the hosted MCP server, which is configuration rather than an install.
How much does it cost?
The Partner plan is $99 a month, for companies embedding this in a product their own customers use. It covers 25,000 connected end-users, 100,000 decisions a month, 10 team members and 365 days of decision history. There is a 3-day trial and card details are collected at checkout.

Decide it before it happens.

Write the rules once. Your agent calls authorize() and gets allow, deny, or a real person's answer. Every verdict is on the record with the rule or the name that settled it.