Guide #2 Getting Started 18 min read March 2026

OpenAI Agents SDK +
Sandbox Platform

The OpenAI Agents SDK has 10.3 million monthly downloads and 19K GitHub stars. It gives you tool-calling agents with handoffs and guardrails. What it doesn't give you is somewhere for those agents to run code.

This guide wires sandbox tools into the SDK. Your agent gets execute_command, launch_browser, and save_file as callable tools. It decides when to use them. Every command runs in an isolated cloud VM, not on your laptop.

The gap: agents that think but can't act

The OpenAI Agents SDK is excellent at orchestration. You define agents with instructions and tools, wire them together with handoffs, add guardrails for safety, and run them with Runner.run(). The model decides which tools to call and in what order.

But the SDK ships with no built-in compute tools. There's no execute_code. No run_shell_command. No open_browser. Your agent can reason about code, but it can't run it. It can plan a web research task, but it can't navigate a URL.

OpenAI's cloud Codex product solves this with bundled containers, but that locks you into GPT models and Codex's pricing. The Agents SDK is model-agnostic in theory — it works with OpenAI, Azure, and any OpenAI-compatible API. The compute layer should be model-agnostic too.

OpenAI Agents SDK → tool calls → Sandbox Platform Code / Browser / Files The SDK orchestrates. The sandbox executes.

Prerequisites

1

Python 3.10+ with the SDK installed

pip install openai-agents httpx

2

API keys

Set OPENAI_API_KEY for the model and SANDBOX_API_KEY for the sandbox platform.

Building sandbox tools

The Agents SDK uses the @function_tool decorator to register Python functions as tools the model can call. Each function gets a docstring (which becomes the tool description) and typed parameters (which become the JSON schema).

We'll build four tools: create a sandbox, execute a command, launch a browser, and save a file.

The sandbox client

python
# sandbox_tools.py
import os
import httpx

SANDBOX_URL = os.environ.get("SANDBOX_URL", "https://sandbox.dev.ab0t.com")
SANDBOX_API_KEY = os.environ["SANDBOX_API_KEY"]

client = httpx.Client(
    base_url=SANDBOX_URL,
    headers={"Authorization": f"Bearer {SANDBOX_API_KEY}"},
    timeout=120.0,
)

# Track the active sandbox
_active_sandbox_id: str | None = None

Tool: create_sandbox

python
from agents import function_tool

@function_tool
def create_sandbox(name: str, instance_type: str = "ab0t.medium") -> str:
    """Create an isolated Linux sandbox for code execution.
    Use ab0t.micro for light tasks, ab0t.medium for builds and tests,
    ab0t.gpu for GPU workloads. Returns the sandbox ID."""
    global _active_sandbox_id
    resp = client.post("/api/sandboxes", json={
        "name": name,
        "instance_type": instance_type,
        "auto_stop_minutes": 60,
    })
    resp.raise_for_status()
    data = resp.json()
    _active_sandbox_id = data["sandbox_id"]
    return (
        f"Sandbox created: {data['sandbox_id']}\n"
        f"Status: {data['status']}\n"
        f"Cost: ${data.get('hourly_cost', '0.04')}/hr\n"
        f"Ready for commands."
    )

Tool: execute_command

python
@function_tool
def execute_command(command: str) -> str:
    """Execute a shell command in the active sandbox.
    Use for: installing packages, running tests, git operations,
    building projects, running scripts. Returns stdout, stderr, and exit code."""
    if not _active_sandbox_id:
        return "No active sandbox. Call create_sandbox first."
    resp = client.post(
        f"/api/sandboxes/{_active_sandbox_id}/execute",
        json={"command": command},
    )
    resp.raise_for_status()
    data = resp.json()
    parts = []
    if data.get("stdout"):
        parts.append(data["stdout"])
    if data.get("stderr"):
        parts.append(f"STDERR:\n{data['stderr']}")
    parts.append(f"Exit code: {data.get('exit_code', 0)}")
    if data.get("execution_time_ms"):
        parts.append(f"Time: {data['execution_time_ms']}ms")
    return "\n".join(parts)

Tool: launch_browser

python
@function_tool
def launch_browser(url: str = "", browser_type: str = "chrome") -> str:
    """Launch a cloud browser for web automation.
    Returns a CDP URL for connecting with Playwright or Puppeteer,
    plus an access URL for viewing the browser in your own browser.
    Use for: web research, form filling, scraping, testing."""
    resp = client.post("/api/browsers", json={
        "browser_type": browser_type,
        "homepage_url": url,
    })
    resp.raise_for_status()
    data = resp.json()
    return (
        f"Browser launched: {data['container_id']}\n"
        f"CDP URL: {data.get('cdp_url', 'pending...')}\n"
        f"View URL: {data.get('access_url', 'pending...')}\n"
        f"Connect: browser = await pw.chromium.connect_over_cdp(cdp_url)"
    )

Tool: save_file

python
@function_tool
def save_file(filename: str, content: str) -> str:
    """Save a file to the sandbox's /workspace directory.
    Use for: writing scripts, config files, test fixtures."""
    if not _active_sandbox_id:
        return "No active sandbox."
    resp = client.post(
        f"/api/sandboxes/{_active_sandbox_id}/files",
        json={"filename": filename, "content": content},
    )
    resp.raise_for_status()
    return f"Saved: /workspace/{filename}"

@function_tool
def stop_sandbox() -> str:
    """Stop the active sandbox. Preserves data. Stops billing."""
    global _active_sandbox_id
    if _active_sandbox_id:
        client.post(f"/api/sandboxes/{_active_sandbox_id}/stop")
        sid = _active_sandbox_id
        _active_sandbox_id = None
        return f"Sandbox {sid} stopped. Billing paused."
    return "No active sandbox."

Creating the agent

Now wire the tools into an Agent. The agent gets instructions that tell it about the sandbox and when to use each tool.

python
# agent.py
from agents import Agent, Runner
from sandbox_tools import (
    create_sandbox, execute_command, launch_browser, save_file, stop_sandbox
)

coding_agent = Agent(
    name="Sandbox Developer",
    model="gpt-4.1",
    instructions="""You are a software developer with access to a cloud sandbox.

You can create isolated Linux environments, execute shell commands,
launch browsers for web tasks, and save files.

WORKFLOW:
1. Always create_sandbox first before executing commands.
2. Use execute_command for: git, pip, npm, pytest, build commands.
3. Use launch_browser for: web research, scraping, testing web UIs.
4. Use save_file to write code, scripts, or config files.
5. Always stop_sandbox when the task is complete to stop billing.

The sandbox is a fresh Ubuntu VM. Install any packages you need.
You have root access via sudo.""",
    tools=[create_sandbox, execute_command, launch_browser, save_file, stop_sandbox],
)

Full example: clone, test, and fix

Here's a complete script that gives the agent a task, lets it work autonomously in a sandbox, and prints the results:

python
# run_agent.py
import asyncio
from agents import Agent, Runner
from sandbox_tools import (
    create_sandbox, execute_command, launch_browser, save_file, stop_sandbox
)

agent = Agent(
    name="Code Reviewer",
    model="gpt-4.1",
    instructions="""You review code repositories. For each task:
1. Create a sandbox
2. Clone the repo
3. Install dependencies
4. Run the test suite
5. Analyze failures and suggest fixes
6. Stop the sandbox when done

Be thorough. Read test output carefully. If tests fail,
look at the relevant source code to understand why.""",
    tools=[create_sandbox, execute_command, save_file, stop_sandbox],
)

async def main():
    result = await Runner.run(
        agent,
        input="Review https://github.com/fastapi/fastapi — clone it, "
              "run the tests, and report which ones fail and why.",
    )
    print("\n=== Agent Output ===")
    print(result.final_output)

asyncio.run(main())

When you run this, the agent will:

  1. Call create_sandbox("fastapi-review", "ab0t.medium")
  2. Call execute_command("git clone https://github.com/fastapi/fastapi && cd fastapi && pip install -e '.[all,dev]'")
  3. Call execute_command("cd fastapi && python -m pytest tests/ -x --tb=short -q")
  4. Read the output, reason about failures, possibly look at source files
  5. Call stop_sandbox()
  6. Return a structured analysis
What GPT-4.1 sees

The model sees the tool descriptions and decides what to call. It doesn't know about the sandbox API directly — it just sees functions like "execute a shell command in the active sandbox" and decides when to use them based on the task.

Streaming output

For long-running tasks, use Runner.run_streamed() to see tool calls and results in real time:

python
async def main_streamed():
    result = Runner.run_streamed(
        agent,
        input="Clone the repo, run tests, fix any failures, run tests again.",
    )
    async for event in result.stream_events():
        if event.type == "tool_call":
            print(f"  Tool: {event.tool_call.name}({event.tool_call.arguments})")
        elif event.type == "tool_output":
            output = event.output[:200]  # Truncate for display
            print(f"  Result: {output}...")
        elif event.type == "text_delta":
            print(event.text, end="", flush=True)

    print(f"\n\nFinal: {result.final_output}")

Multiple agents, multiple sandboxes

The SDK supports handoffs between agents. Give each specialized agent its own sandbox:

python
# Each agent manages its own sandbox via separate tool instances
from agents import Agent, Runner

# Research agent uses browsers
researcher = Agent(
    name="Researcher",
    model="gpt-4.1",
    instructions="You research topics using web browsers. "
                  "Launch a browser, navigate to relevant pages, "
                  "and extract key findings.",
    tools=[create_sandbox, launch_browser, execute_command, stop_sandbox],
)

# Coding agent uses terminals
developer = Agent(
    name="Developer",
    model="gpt-4.1",
    instructions="You write and test code in a sandbox. "
                  "Create a sandbox, write code, run tests, iterate.",
    tools=[create_sandbox, execute_command, save_file, stop_sandbox],
)

# Orchestrator hands off between them
orchestrator = Agent(
    name="Project Lead",
    model="gpt-4.1",
    instructions="You manage a research project. First, hand off to "
                  "Researcher to gather information. Then hand off to "
                  "Developer to implement the findings as code.",
    handoffs=[researcher, developer],
)

async def main():
    result = await Runner.run(
        orchestrator,
        input="Research the top 5 Python testing frameworks in 2026, "
              "then write a comparison script that benchmarks each one "
              "against a sample project.",
    )
    print(result.final_output)

Codex CLI on a sandbox

OpenAI's Codex CLI is a terminal agent similar to Claude Code. Same pattern applies — SSH into a sandbox and run it there:

bash
# Create a sandbox
SANDBOX=$(curl -s -X POST "$SANDBOX_URL/api/sandboxes" \
  -H "Authorization: Bearer $SANDBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"codex-dev","instance_type":"ab0t.medium","auto_stop_minutes":120}')

# SSH in
ssh ubuntu@$(echo $SANDBOX | jq -r '.instance_ip')

# Inside the sandbox:
npm install -g @openai/codex
export OPENAI_API_KEY="sk-YOUR_KEY"

# Run Codex in Full Auto mode
cd /home/ubuntu/workspace
git clone https://github.com/your-org/your-project.git
cd your-project
codex --approval-mode full-auto "Run the tests and fix any failures"
Codex CLI vs Codex Cloud

Codex Cloud provides its own containers but locks you into GPT models and their pricing. Codex CLI on a sandbox gives you the same experience with transparent compute costs ($0.08/hr for ab0t.medium) and the freedom to switch models later.

What it costs

ScenarioSandboxDurationCompute CostModel Cost (est.)
Clone + test a repoab0t.medium~15 min$0.01~$0.05
Multi-agent research + code2x ab0t.medium~30 min$0.04~$0.15
Browser scraping (10 sites)10x Chrome~10 min$0.07~$0.10
Codex CLI full sessionab0t.medium~2 hrs$0.08~$0.50
Compare to Codex Cloud

Codex Cloud bundles compute into its pricing. With a sandbox, compute and model costs are separate and transparent. A typical task that costs $0.50 in model tokens uses $0.01–$0.04 in sandbox compute.

Tips

Always call stop_sandbox

Add it to the agent instructions and as the last tool in the list. If the agent forgets, the auto_stop_minutes setting catches it, but explicit stops are cheaper.

Set timeouts on long commands

The HTTP client timeout is 120 seconds by default. For long builds, increase it: timeout=300.0. Or break long commands into steps so the agent gets intermediate feedback.

Use guardrails for dangerous commands

The SDK's guardrails feature can intercept tool calls before execution. Add a guardrail that blocks rm -rf /, shutdown, and other destructive commands.

Log tool calls for debugging

Use Runner.run_streamed() during development to see every tool call and result. This makes it easy to spot when the agent is going off-track.

Troubleshooting

Agent doesn't call create_sandbox first

Add explicit instructions: "ALWAYS call create_sandbox before any other sandbox tool." Put it in bold in the instructions. GPT-4.1 follows this reliably.

"No active sandbox" errors

The _active_sandbox_id is module-level state. If you restart the script, it forgets the ID. Either create a new sandbox or persist the ID to a file.

Tool calls timeout

The sandbox API returns results synchronously. For commands that take >2 minutes, increase the httpx timeout or use execute_command("nohup long_command &") and poll for results.

Agent creates too many sandboxes

The agent may create a new sandbox for each subtask. Add to instructions: "Reuse the existing sandbox unless you need a different instance type."

What's next

Give your OpenAI agent a computer

Terminals, browsers, and desktops. Model-agnostic compute for any agent.

Get Started Free