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.
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".
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.
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.
// $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.
- 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.
- 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.
- 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.
- 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?
How do I add human-in-the-loop to an AI agent I already built?
Who decides which person gets asked?
Does this mean more interruptions?
What happens if nobody answers?
Do my end-users need an account or app?
Which frameworks does Pushary support?
How much does it cost?
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.
Human-in-the-loop by framework
By product type
The same two calls work on any framework, listed here or not, anywhere @pushary/server or pip install pushary runs.