Skip to main content
The OpenAI Agents API is in public beta. Model IDs and event names may change before general availability.
The OpenAI Agents API runs the agent model in OpenAI’s cloud and connects it to compute you own. On Runloop, that compute is a devbox: your application creates an Agents API session, boots a devbox, starts the Codex executor inside it pointed at the session’s environment, and streams the turn. Files the agent writes land on the devbox filesystem, where your application reads them back. Every step below is a stage of the complete script at the end of this page, followed by the operational rules that keep sessions and devboxes from leaking.

Which provisioning mode you are in

Pick one mode per session. Do not attach a provisioning webhook handler to sessions your application already provisions. Application-managed is the right mode when your application already has a place to run: it needs no public endpoint, no webhook signature verification, and no separately deployed service. Choose webhook-managed when sessions are created by something that cannot provision compute itself — for example a hosted product surface that only talks to OpenAI. Deleting an Agents API session does not stop the devbox. Whichever mode you choose, teardown of Runloop compute is your application’s job.

What you need

  • A Runloop API key from the Runloop dashboard
  • An OpenAI account with access to the Agents API
  • Two OpenAI API keys with the same owner, organization, and project:
    • OPENAI_API_KEY — used by your application to create and drive the session
    • OPENAI_EXECUTOR_API_KEY — a separate, restricted key that is the only OpenAI credential permitted inside the devbox
  • Python 3.11 or newer and uv
Never inject OPENAI_API_KEY into the devbox. The executor key is scoped for exactly this use and is the only one that should cross the sandbox boundary; a leak from inside the devbox should not expose your application’s key.

Environment variables

The example passes the executor key to the devbox through environment_variables at create time. For anything beyond a local run, store it as a Runloop account secret and map it in instead — see Account Secrets.

Run it

Copy the complete script to openai-agents-api-runloop.py and run it with uv:
uv run installs the dependencies declared in the script’s inline metadata: the official openai SDK (3.13.0 or newer, which is where client.beta.agents lives) and runloop-api-client. A successful run prints the session ID, the devbox ID, the streamed agent output, and finally the contents of plan.md, then shuts the devbox down and deletes the session.

How the run works

1

Create the Agents API session

The session declares a self-hosted environment and the workspace directory the agent will treat as its working tree. The returned session.environment carries the two values the executor needs later: remote_url and id.
The assert is not decoration: it narrows the environment union so remote_url and id are typed, and it fails fast if the session came back OpenAI-hosted.
2

Create the devbox

Create the devbox through runloop.api.devboxes.create(), retain its handle, then wait for it to reach running. Keeping the handle before waiting lets cleanup shut it down if boot times out. The executor key goes in as an environment variable named CODEX_API_KEY, and keep_alive_time_seconds caps the devbox lifetime if the application crashes.
Naming the devbox after the session ID is what makes an orphan traceable back to the session that created it. Keep the convention.
3

Install the executor

The devbox side of the connection is codex exec-server, installed into the devbox at runtime. Check the exit code — a failed install otherwise surfaces much later as a session that never reaches a completed turn.
The alpha tag is intentional: the Agents API beta relies on the exec-server command, which ships on that tag. OpenAI’s Cookbook sandbox examples install the same tag for every self-hosted provider.Installing on every run spends devbox time on every session and depends on npm being reachable from the devbox. For repeated runs, bake @openai/codex into a blueprint and create devboxes from that instead.
4

Seed the workspace

Anything the agent should read has to exist in the workspace before the turn starts. The example writes a one-line brief.
5

Start the executor against the session

exec_async starts the server and returns immediately; the process stays up for the life of the devbox. --environment-id is what binds this devbox to the session created in step 1. Pass session.environment.remote_url through unchanged rather than hardcoding an endpoint: it is a per-session connect URL, not a fixed API address.
6

Stream the turn

Failure arrives as an event type, not an exception. Treat the five failure events as terminal, and treat a stream that ends without a completed turn as a failure too. stream() subscribes before it submits the input, so no events are missed between the two.
Check turn.subagent_id before breaking. If the agent delegates work, subagent turns complete on the same stream; breaking on the first agent.session.turn.completed would tear the devbox down mid-run. Only the turn with subagent_id is None is yours.
7

Read the result off the devbox

The agent’s real output is the file it wrote, not the streamed text. Read it back and verify it is non-empty — a completed turn does not guarantee the file exists.
8

Tear down both resources

Two independent resources, two cleanups, both in finally, and nested so a failed devbox shutdown still deletes the session.If the create request fails before returning an ID, the application cannot target that devbox for cleanup. The fixed lifetime remains the backstop; use the orphan check below after an interrupted run.

Agents API concepts on Runloop

Timeouts and lifetimes

The example layers four limits. Enclosing deadlines take precedence over longer SDK timeouts, so adjust the limits together when extending a run.
For idle-based shutdown, set launch_parameters.lifecycle.after_idle with on_idle="shutdown" and remove keep_alive_time_seconds. Mixing the two policies can be rejected or cause the idle policy to take precedence. This bounded batch example uses a fixed lifetime. See Start and Stop Devboxes.

Multi-turn sessions

The example is single-turn: it breaks out of the stream on the main turn’s agent.session.turn.completed and immediately tears down. For follow-up turns, keep both the session and the devbox alive between turns and call client.beta.agents.sessions.stream(session.id, ...) again — the executor is still running and the workspace still holds the previous turn’s files. Extend keep_alive_time_seconds, or replace it with lifecycle.after_idle, so the devbox does not expire between turns.

Troubleshooting

Verify nothing is left running

Session deletion and devbox shutdown are independent. After any interrupted run, check for orphans:
Devboxes created by this example are named agents-api-<session-suffix>, which makes them easy to spot. See the CLI reference and Devbox Lifecycle.

Complete script

Every snippet above is an excerpt of this file. Save it as openai-agents-api-runloop.py and run it with uv run openai-agents-api-runloop.py.
openai-agents-api-runloop.py

References