---
title: "Run OpenAI Agents SDK agents in a Promigence sandbox"
description: "Give an OpenAI Agents SDK SandboxAgent a Promigence sandbox, so its shell and file tools run there instead of on your machine."
url: "https://www.promigence.ai/docs/guides/openai-agents-sdk"
site: "Promigence"
---

# Run OpenAI Agents SDK agents in a Promigence sandbox

Give an OpenAI Agents SDK SandboxAgent a Promigence sandbox, so its shell and file tools run there instead of on your machine.

The OpenAI Agents SDK has a sandbox interface for agents that need a shell and a filesystem. The
`promigence-openai-agents` package implements it with `PromigenceSandboxClient`, so a `SandboxAgent`
runs its commands and file edits in a Promigence sandbox. Your agent code stays the same. Only the
client you pass in changes.

## What you need

- A Promigence API key in `PROMIGENCE_API_KEY`.
- Python 3.10 or newer.
- `openai-agents` 0.22.3. The sandbox base classes are not a stable API upstream, so the package
  pins `>=0.22.3,<0.23`.

## Install

```bash
pip install promigence-openai-agents
export PROMIGENCE_API_KEY=prom_api_...
export PROMIGENCE_SPEND_CAP=5
```

## A minimal example

Hand the client to the SDK through `SandboxRunConfig`. The SDK creates the session, starts it, gives
the model its `exec_command` tool and ends the session when the run is over.

```python
import asyncio
from agents import RunConfig, Runner
from agents.sandbox import SandboxAgent, SandboxRunConfig
from promigence_openai_agents import PromigenceSandboxClient, PromigenceSandboxClientOptions

agent = SandboxAgent(name="coder", instructions="Use the shell.")

async def main() -> None:
    result = await Runner.run(
        agent,
        "Write a note to note.txt and read it back.",
        run_config=RunConfig(sandbox=SandboxRunConfig(
            client=PromigenceSandboxClient(),
            options=PromigenceSandboxClientOptions(image="python:3.12-slim", spend_cap=1),
        )),
    )
    print(result.final_output)

asyncio.run(main())
```

You can also drive a session yourself:

```python
client = PromigenceSandboxClient()
session = await client.create(options=PromigenceSandboxClientOptions(image="python:3.12-slim", spend_cap=1))
async with session:
    result = await session.exec("python", "-c", "print(1 + 1)", shell=False)
    print(result.stdout.decode())
await client.delete(session)
```

Pass exactly one of `image` or `snapshot`. The first time you name an image, Promigence pins the tag
to a digest, builds a snapshot from it and checks it; later sandboxes reuse that snapshot. That build
is billed per second while it runs, whether it verifies or not, up to $0.17 at the default small size
([quickstart](https://www.promigence.ai/docs/quickstart)), and the sandboxes that reuse the snapshot pay no further build
charge. A `snapshot` (an id, an alias, or `alias@commit7`) is used as given. Other useful options:
`tier` (`small`, `medium` or `large`), `setup` and `verify` to prepare and check the snapshot,
`allow_internet_access=False` for no network and `registry_secret` for a private image. Preview URLs
are not enabled on Promigence yet, so resolving a port in `exposed_ports` raises
`ExposedPortUnavailableError`; to reach a port, forward it over SSH with
`promigence sandbox ssh <sandbox_id> -- -N -L <port>:localhost:<port>`.

## One sandbox per session

Each SDK session is one sandbox, a fresh copy of the snapshot. Two runs never share a filesystem.

To carry a session's work across runs, the SDK persists the workspace. With
`workspace_persistence="tar"` (the default) you get a plain tar of the workspace directory, which any
other sandbox backend can read. With `workspace_persistence="pause"` the sandbox itself is parked: it
bills no seconds while parked, and `client.resume(state)` brings the whole machine back, installed
packages included. A parked reference only works on Promigence, so pick `tar` when state has to move.

## Setting a spend cap

`spend_cap` in `PromigenceSandboxClientOptions` is the most one sandbox may spend, in US dollars. It
falls back to `PROMIGENCE_SPEND_CAP`, then to your organization's default. If none of the three is set,
creating the session fails with a configuration error before anything starts. `sandbox_timeout_sec`
(3600 by default) bounds the sandbox's lifetime.

## Cleaning up

`await client.delete(session)` ends the sandbox, or parks it when you set `pause_on_exit=True`. A
session parked through `workspace_persistence="pause"` stays parked even after `delete`, because the
saved reference is the only way back to it. End those with `promigence sandbox kill <id>` once you
no longer need them.

Failures are kept apart. A command that exits non-zero comes back as a result, a timeout raises
`ExecTimeoutError`, and a failure on the Promigence side raises `ExecTransportError` with
`context["failure_class"] == "env"`, marked retryable so the SDK's own retry logic can try again.

## What is not supported yet

- Interactive terminals. `supports_pty()` is `False`, so `pty_exec_start` is unavailable.
- Streaming output. A command's output arrives when it finishes.
- Manifest entries that need a cloud mount. Plain files, directories and git artifacts work.
- A JavaScript version. The JS SDK's sandbox contract differs and needs its own package.
- The image needs `/bin/sh`, and `tar` for the default persistence mode.

## Next

Read [the agent briefing](https://www.promigence.ai/agents) for what a sandbox gives an agent, or start with
[the quickstart](https://www.promigence.ai/docs/quickstart).

