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 sessionOPENAI_EXECUTOR_API_KEY— a separate, restricted key that is the only OpenAI credential permitted inside the devbox
- Python 3.11 or newer and uv
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 toopenai-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 The assert is not decoration: it narrows the environment union so
session.environment carries the two values the executor needs
later: remote_url and id.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 Naming the devbox after the session ID is what makes an orphan traceable back to the session that
created it. Keep the convention.
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.3
Install the executor
The devbox side of the connection is The
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.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. Check
stream() subscribes before
it submits the input, so no events are missed between the two.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’sagent.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: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 asopenai-agents-api-runloop.py and run it
with uv run openai-agents-api-runloop.py.
openai-agents-api-runloop.py
