OpenAI Agents SDK sandbox

The OpenAI Agents SDK runs a SandboxAgent's shell, file and patch tools inside a sandbox. A sandbox client supplies it. mandala_computer.openai_agents is that client for Mandala: each sandbox session is one Linux computer, made when the run needs it and deleted when the run ends.

The agent stays the same as with the SDK's local or Docker clients. Only the run config changes. The model loop runs in your process, against OpenAI. The commands the model asks for run on the computer.

This is not computer use. The agent gets a shell, not a screen. To drive the desktop with screenshots, see the OpenAI computer use guide.

Before you start

pip install "mandala-computer[openai-agents]"

The extra installs openai-agents 0.24. That is the release line the client is tested against, so the extra pins >=0.24,<0.25.

A complete program

import asyncio
import sys

from agents import Runner
from agents.run import RunConfig
from agents.sandbox import SandboxAgent, SandboxRunConfig
from agents.sandbox.capabilities import Shell
from mandala_computer.openai_agents import (
    MandalaSandboxClient,
    MandalaSandboxClientOptions,
)

agent = SandboxAgent(
    name="builder",
    instructions="Work in the workspace. Run what you write, and say what it printed.",
    capabilities=[Shell()],
)


async def main(task):
    # The Mandala key comes from MANDALA_API_KEY or a saved profile.
    # The model key is the Agents SDK's own: OPENAI_API_KEY.
    async with MandalaSandboxClient() as client:
        result = await Runner.run(
            agent,
            task,
            max_turns=30,
            run_config=RunConfig(
                sandbox=SandboxRunConfig(
                    client=client,
                    options=MandalaSandboxClientOptions(size="small"),
                ),
            ),
        )
    return result.final_output


if __name__ == "__main__":
    task = " ".join(sys.argv[1:]) or "Write a Python script that prints the first ten primes, and run it."
    print(asyncio.run(main(task)))

Run it with a task:

python builder.py "Write a script that counts the words in /etc/os-release, and run it."

What happens:

  1. The runner asks the client for a session. The client creates a computer, named agents-<session id>, and waits for it to answer.
  2. The model's shell calls run on the computer as root, in /workspace.
  3. When the run ends, the runner saves the workspace to its snapshot store, and the client deletes the computer.

max_turns bounds the run: each model call is a turn. The agent uses the Agents SDK's default model. Set model= on the SandboxAgent to choose another.

MandalaSandboxClient() makes its own AsyncClient and closes it at the end of the async with. Pass one you already have, MandalaSandboxClient(client), and it stays yours to close. Closing the sandbox client also shuts down any session that was never closed, as its on_shutdown says.

Options

MandalaSandboxClientOptions decides what computer a session gets. Every field is optional, and options can be left out of SandboxRunConfig.

OptionDefaultWhat it does
sizethe template'sA catalogue shape from client.sizes.list(). Not with template, cpu, ram_mb or disk_gb.
template, cpu, ram_mb, disk_gbthe template'sThe same as for computers.create().
nameagents-<session>The computer's name. The default names it after the session, so a computer left behind can be traced.
workspace_idnoneThe same as for computers.create().
ends_in_hours24A safety end time. The platform stops the computer then, keeping its disk, even if your process is gone. None for none.
on_shutdown"delete"What happens to the computer when the session ends: "delete", "stop" or "suspend".
exposed_portsnonePorts the agent's tools may serve through public URLs.
public_url_accessthe plan's"public" or "private" for those URLs.
launch_timeout_s300How long creating or restarting a computer may take to answer.

Unknown fields are refused. So is a size with explicit numbers, a port outside 1 to 65535, port 22, and an end time that is not between 0 and 8784 hours.

The end time is sent with the create. Even a computer your process leaves behind stops on its own.

Shutdown and resuming

When the runner owns the session, it saves the workspace snapshot, shuts the session down and calls the client's delete(). Shutting down applies on_shutdown once.

Files written by background processes after the last save are lost with the computer on a delete. Use "stop" when you mean to come back.

To keep one computer across runs, make the session yourself, save its state, and resume it later. The state holds the computer's id and how to make another. It holds no key, so another process with its own key can resume it.

The state does not hold the files. They live on the computer's disk, which "stop" keeps, and in the snapshot store, if you give the session one. Closing the session saves the workspace to that store. Without a store, deleting the computer loses the workspace for good, and a resume makes an empty one. So use on_shutdown="stop" when you mean to resume, and a snapshot store as well if you want the files back even after the computer is gone.

import json
from pathlib import Path

from agents.sandbox import LocalSnapshotSpec, Manifest

options = MandalaSandboxClientOptions(size="small", on_shutdown="stop")
snapshots = LocalSnapshotSpec(base_path=Path("snapshots"))  # a directory on your machine

async with MandalaSandboxClient() as client:
    session = await client.create(manifest=Manifest(), options=options, snapshot=snapshots)
    async with session:
        await Runner.run(agent, "Write notes.txt", run_config=RunConfig(sandbox=SandboxRunConfig(session=session)))
    # Closing saved the workspace to snapshots/ and stopped the computer.
    saved = json.dumps(client.serialize_session_state(session.state))

    # Later, or in another process:
    session = await client.resume(client.deserialize_session_state(json.loads(saved)))
    async with session:  # starts the same computer; notes.txt is still on its disk
        await Runner.run(agent, "Read notes.txt", run_config=RunConfig(sandbox=SandboxRunConfig(session=session)))

If the computer was deleted in between, the resume makes a new one from the same options and restores notes.txt from snapshots/.

A stopped computer keeps its disk on your plan until you delete it.

The disk wins. While the computer exists, its disk is the truth about the workspace. A resume onto the same computer never restores a snapshot over it, so files the agent edited stay edited. A snapshot is used only to rebuild a computer that is gone: a resume after "delete" makes a new computer from the same options and restores the workspace from the snapshot. With no snapshot store, that new computer starts with an empty workspace.

A resume reattaches a running computer as it is, starts a stopped or suspended one, and replaces one that is gone. If the platform cannot be reached, or the key cannot see the computer, the resume fails rather than making another.

A computer also suspends itself after the host's idle window, 30 minutes by default. The next command or file transfer resumes it in about a second.

Exposed ports

A port the agent serves can be reached through a public URL. Name it in exposed_ports, then resolve it from the session. The first resolve makes the URL, or reuses one the computer already has for that port. The endpoint is HTTPS on 443.

from agents.sandbox import Manifest

options = MandalaSandboxClientOptions(exposed_ports=(8000,))

async with MandalaSandboxClient() as client:
    session = await client.create(manifest=Manifest(), options=options)
    async with session:
        await session.exec("nohup python3 -m http.server 8000 >/dev/null 2>&1 &")
        endpoint = await session.resolve_exposed_port(8000)
        print(endpoint.url_for("http"))  # https://<label>.<base_domain>/
    await client.delete(session)

What it supports and refuses

Errors are the SDK's own. A failed create or resume is a WorkspaceStartError; a command that could not be run or read is an ExecTransportError; a timeout is an ExecTimeoutError; a missing file is a WorkspaceReadNotFoundError. Each keeps the platform's status and code in context.

A failure to stop or delete at shutdown is logged on the mandala_computer.openai_agents logger with the computer's id, not raised over the run's own result. The end time is the backstop for that.

See also: Troubleshooting.