Human-in-the-loop for the OpenAI Agents SDK
Pause the tool. Get customer approval on a phone.
Your OpenAI agent stops before a refund. Your user approves it on their phone, and the run carries on.
What OpenAI Agents SDK gives you
needsApproval and run interruptions
Mark a function tool with needsApproval and the SDK stops before running it. The run returns result.interruptions and a RunState you can save. Your code approves or rejects each interruption, then runs the agent again from that state.
Where it stops
- needsApproval decides that a person is needed. Nothing asks anyone; resolving the interruption is your job.
- A long wait means saving RunState and restoring it with the same agent and SDK version.
- It does not know which of your users should answer, or how to reach them.
The fix
Pushary gets your user's answer.
The Agents SDK can pause a tool call, but it tells nobody. Pushary sends each paused call to your user's phone and puts the answer back on the run.
issue_refund needs approval, so the run stops before it executes. resolvePusharyInterruptions asks your user about each paused call, one at a time. Approve runs the refund. Deny, expiry or no answer rejects it, and the model gets the reason. If one of your Pushary rules covers issue_refund, the rule answers first.
npm i @pushary/openai-agents @openai/agents zodimport { Agent, run, tool } from "@openai/agents"
import { z } from "zod"
import { pusharyNeedsApproval, resolvePusharyInterruptions } from "@pushary/openai-agents"
const issueRefund = tool({
name: "issue_refund",
description: "Refund an order",
parameters: z.object({ amount: z.number() }),
needsApproval: pusharyNeedsApproval(),
execute: async ({ amount }) => chargeBack(amount),
})
const agent = new Agent({ name: "Refunds", instructions: "Refund orders when asked.", tools: [issueRefund] })
let result = await run(agent, "Refund order 1234")
while (result.interruptions?.length) {
const outcome = await resolvePusharyInterruptions(
{ externalId: user.id },
{ interruptions: result.interruptions, state: result.state },
)
result = await run(agent, result.state)
if (!outcome.allApproved) break
}- Refund approval tutorial and runnable source
- TypeScript package on npm
- OpenAI's guardrails and approvals guide
Streaming and the Responses API save different things
For a streamed run, read the events and await result.completed before you check interruptions and save the state. The separate Responses API recipe saves the response id and the exact MCP approval request, then sends mcp_approval_response with previous_response_id. The tutorial covers both, with real services.
const result = await runner.run(agent, input, { stream: true });
for await (const event of result) {
// Display appropriate events; output is not permission to act.
}
await result.completed;
// Now inspect result.interruptions and persist result.state.
// The complete recipe binds this state to customer + action.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.
- 01
Install the adapter
Run npm i @pushary/openai-agents @openai/agents zod and put your API key in PUSHARY_API_KEY. For Python, pip install pushary-openai-agents.
- 02
Connect your user once
connect() returns a link. Your user opens it on their phone with the free Pushary app. Keep your API key and their user id on your server.
- 03
Put the approval on the risky tool
Set needsApproval: pusharyNeedsApproval() on the tool. The run stops before it executes, and resolvePusharyInterruptions sends each paused call to your user.
- 04
Act on the answer
On Approve, run the agent again from result.state. On Deny or no answer the call is rejected. For long waits, save the state and resume when the answer arrives.
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 OpenAI Agents SDK
- An ask tool the model can skip is not a gate
- pusharyTool is a tool the model chooses to call. That suits “go ask about this”. For “this must not happen without a yes”, put needsApproval on the risky tool itself. A prompt that says “ask first” does not stop anything.
- A saved answer is not a saved agent
- Save the run state, the exact call, the user and the action version before you ask. Pushary keeps the answer, but it does not rebuild your process or schedule the resume. That runs in your own job system.
- Approval covers the call your user saw
- Read the answer from Pushary again and check it matches the saved call. Only a yes to a yes-or-no question lets the call run. If the draft changes, ask again. Waiting, expired, cancelled and denied requests never count.
- A retry can still run the action twice
- The example runs each resume only once, but your refund API still needs its own operation key so a repeat does nothing. If a network result is unclear, check what happened before you retry. The example handles one paused call per saved operation.
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.
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
Which OpenAI packages are available?
Does the streaming example call a real model or phone?
How does the Responses API MCP example resume?
What happens if the customer never answers?
Does Pushary replace authorization or Codex Auto-review?
Let your OpenAI Agents 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 freePartner plan, $99 a month. Your users never make a Pushary account or pay us.