Skip to main content
This example is a support agent built with the OpenAI Agents SDK. It looks orders up with one tool, and its 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

The SDK wheel goes in the bundle’s 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.
The OpenAI Agents SDK can’t run a call with changed arguments, so this adapter offers approve and reject, not edit. Use LangGraph or Agno when reviewers should change the amount.

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

The run is held: the refund waits in the Review queue. Decide it there, or through the Holds API. The item’s id is the model’s tool call id: read it from the hold (GET …/holds/h_12).

Test it before you deploy

The conformance kit builds the bundle and runs it against a mock platform whose model follows call:<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 RunState travels in run state. A value there can be at most 1 MiB, and the RunState grows 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.run returns 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.