refund tool is declared with needs_approval=True. When the model calls it, the run stops, AgentOS asks a person, and the run continues where it stopped with their decision, in whichever container answers the resume.
The SDK’s openai_agents adapter does the work: it keeps the agent’s RunState in run state, asks one hold for every call waiting for approval, and applies the decisions on the resume. See Agent frameworks.
Tested with
openai-agents 0.23.1 and Python 3.13, using the conformance kit: 17 checks passed, including an approved and a rejected refund.The bundle
vendor/ folder and is installed with its openai-agents extra; see SDKs.
How the model is wired
openai_agents.model(inv) is an OpenAIChatCompletionsModel for config.models.main, on an AsyncOpenAI client made from inv.llm.openai_kwargs(asynchronous=True): the gateway’s base URL, the run token in the x-agentos-run-token header, and an HTTP client that never sends the token anywhere else. The model name reaches the gateway exactly as written in agentos.yaml (anthropic/claude-sonnet-4-5). Pass a role for another model: openai_agents.model(inv, "judge").
The agent is built inside the handler, on every invocation, because the token belongs to one invocation. The adapter turns the SDK’s tracing off, since it would send traces to OpenAI.
When a guardrail blocks a model call, the OpenAI client raises BadRequestError. agentos.guardrail_block(error) finds the block in it, and raising that block fails the run with blocked_by_guardrail and the guardrail’s message. See Guardrails.
How the approval works
1
The run stops for approval
The model calls
refund. Because the tool has needs_approval=True, the SDK stops the run with an interruption instead of running it. openai_agents.run keeps the run’s RunState and asks one hold: one item per waiting call, its id the tool call id, its data the call’s arguments, with approve and reject.2
A person decides
The run is held until someone decides in the Review queue or through the Holds API.
3
The run continues
On the resume the adapter restores the
RunState, approves or rejects each call, and continues the run. An approved refund runs; a rejected one returns the reviewer’s comment (or “A person rejected this tool call.”) to the model as the tool’s result, and the model answers the customer.Run it
1
Zip and deploy
Put the SDK wheel in
vendor/ (see SDKs), set config.models.main to a model from your Models page, then zip the folder’s contents and upload them:2
Ask for a refund
GET …/holds/h_12).Test it before you deploy
The conformance kit builds the bundle and runs it against a mock platform whose model followscall:<tool> {arguments} in a message, so these cases drive the tools and both decisions without a real model:
conformance.yaml
Things to know
- Chat Completions, not the Responses API. The adapter’s model uses
/v1/chat/completions, which every provider behind the gateway supports. - The
RunStatetravels in run state. A value there can be at most 1 MiB, and theRunStategrows with the conversation; keep very long conversations short. - A finished run isn’t run again. If the same AgentOS run invokes the handler after the agent finished,
openai_agents.runreturns what it kept (result.final_output) instead of calling the model and the tools again.
Next steps
Human approvals
Everything an ask can carry: several items, details, files, an expiry.
More frameworks
The same agent in other frameworks and languages.

