Skip to content

Examples

Runnable examples live in examples/. These are product patterns, not toy demos.

Use them as starting points for CI fixers, review bots, hosted agent APIs, tenant-isolated services, and multi-agent coding workflows. Every example source is embedded below.

First runs

File Shows
examples/plain_docker_shell.py Shell backend in plain Docker
examples/docker_sbx_codex.py Codex backend in Docker SBX
examples/docker_sbx_opencode.py OpenCode backend in Docker SBX
examples/cli_runner.py reusable Runner loaded by sbxa CLI
examples/cli_runner_codex_default.py sbxa runner using Docker SBX Codex defaults and host-managed auth
examples/plain_docker_shell.py
from pathlib import Path

from sbx_agents import Agent, Runner
from sbx_agents.backends import Shell
from sbx_agents.sandboxes import DockerSandbox

agent = Agent(name="python-version", backend=Shell())

result = Runner.run_sync(
    agent,
    sandbox=DockerSandbox(workspace=Path("."), image="python:3.12-slim"),
    prompt="python --version",
)

print(result.output)
examples/docker_sbx_codex.py
from pathlib import Path

from sbx_agents import Agent, Runner
from sbx_agents.backends import Codex
from sbx_agents.sandboxes import DockerSbxSandbox

agent = Agent(
    name="fix-tests",
    backend=Codex(),
    instructions="Fix failing tests with the smallest safe diff.",
)

result = Runner.run_sync(
    agent,
    sandbox=DockerSbxSandbox(workspace=Path("."), branch="agent/fix-tests"),
    prompt="Fix the failing tests.",
)

print(result.output)
examples/docker_sbx_opencode.py
from pathlib import Path

from sbx_agents import Agent, Runner
from sbx_agents.backends import OpenCode
from sbx_agents.sandboxes import DockerSbxSandbox

agent = Agent(
    name="reviewer",
    backend=OpenCode(),
    instructions="Review the repo and produce a risk-ranked plan.",
)

result = Runner.run_sync(
    agent,
    sandbox=DockerSbxSandbox(workspace=Path("."), branch="agent/review"),
    prompt="Review this repository.",
)

print(result.output)
examples/cli_runner.py
from pathlib import Path

from sbx_agents import Agent, Runner
from sbx_agents.backends import Codex
from sbx_agents.sandboxes import DockerSbxSandbox

# This file is meant to be loaded by the `sbxa` CLI. Importing it should not run the
# agent. `sbxa run examples/cli_runner.py` loads this object and calls `run_sync()`.
#
# Docker SBX examples require login first:
#   sbx login
#   sbxa doctor --require docker-sbx
runner = Runner(
    Agent(
        name="fix-tests",
        backend=Codex(
            model="LiquidAI/LFM2.5-1.2B-Instruct-MLX-8bit",
            model_provider="local-mlx",
            extra_config={
                "model_providers": {
                    "local-mlx": {
                        "name": "Local MLX",
                        # Inside Docker SBX, localhost is the sandbox. Use host.docker.internal
                        # for a model server running on your host at localhost:8080.
                        "base_url": "http://host.docker.internal:8080/v1",
                        # Codex requires an env_key even for local unauthenticated providers.
                        # PATH is always present and is only used to satisfy that check.
                        "env_key": "PATH",
                        "wire_api": "responses",
                    }
                }
            },
        ),
        instructions="Fix failing tests with the smallest safe diff. Run tests before finishing.",
    ),
    sandbox=DockerSbxSandbox(
        workspace=Path("."),
        name="cli-fix-tests",
        branch="agent/cli-fix-tests",
        # Docker SBX reaches the host via host.docker.internal, but policy logs may
        # report the proxied request as localhost:8080. Allow both.
        network_allow=["host.docker.internal:8080", "localhost:8080"],
    ),
    default_prompt="Fix the failing pytest suite.",
)
examples/cli_runner_codex_default.py
from pathlib import Path

from sbx_agents import Agent, RunConfig, Runner
from sbx_agents.backends import Codex
from sbx_agents.sandboxes import DockerSbxSandbox

# CLI runner for Docker SBX + Codex defaults.
#
# This does not set a model. Codex chooses its default model. It does set the
# provider to OpenAI so stale project .codex/config.toml from other examples cannot
# accidentally point this run at a local provider.
# Codex declares OpenAI OAuth auth; Docker SBX implements it with its global
# OpenAI OAuth secret.
#
# One-time setup:
#   sbx login
#   sbxa doctor --require docker-sbx
#
# Run:
#   sbxa run examples.cli_runner_codex_default:runner
runner = Runner(
    Agent(
        name="codex-default-fixer",
        backend=Codex(model_provider="openai", auth="openai_oauth"),
        instructions="Fix failing tests with the smallest safe diff. Run tests before finishing.",
    ),
    sandbox=DockerSbxSandbox(
        workspace=Path("."),
        name="codex-default-fixer",
        branch="agent/codex-default-fixer",
    ),
    default_prompt="Fix the failing pytest suite.",
    run_config=RunConfig(json_events=True),
)

Composition

File Shows
examples/multi_step_codex.py diagnose -> fix -> verify with backend-native resume
examples/multi_step_app_managed.py app-managed context between turns
examples/multi_agent_same_sandbox.py reviewer, fixer, and verifier share one sandbox
examples/one_agent_multiple_sandboxes_copy_files.py same agent across sandboxes with explicit file transfer
examples/multi_step_codex.py
from pathlib import Path

from sbx_agents import Agent, RunConfig, Runner
from sbx_agents.backends import Codex
from sbx_agents.sandboxes import DockerSbxSandbox

agent = Agent(
    name="fix-tests",
    backend=Codex(model="gpt-5-codex"),
    instructions=(
        "You are a careful coding agent. Use the smallest safe diff. "
        "Run relevant tests before finishing."
    ),
)

sandbox = DockerSbxSandbox(
    workspace=Path("."),
    name="fix-tests",
    branch="agent/fix-tests",
)

# Step 1: inspect only. JSON events are opt-in; Codex may emit a thread_id.
diagnosis = Runner.run_sync(
    agent,
    sandbox=sandbox,
    prompt="Inspect the repo and identify why tests are failing. Do not edit files yet.",
    run_config=RunConfig(json_events=True),
)

session_id = diagnosis.thread_id or diagnosis.session_id

print("Diagnosis:")
print(diagnosis.output)

# Step 2: resume the backend conversation if possible.
fix = Runner.run_sync(
    agent,
    sandbox=sandbox,
    prompt="Now implement the smallest safe fix. Do not do unrelated cleanup.",
    run_config=RunConfig(
        json_events=True,
        resume_session_id=session_id,
    ),
)

session_id = fix.thread_id or fix.session_id or session_id

print("Fix output:")
print(fix.output)
print("Diff:")
print(fix.diff)

# Step 3: verify in the same conversation.
verify = Runner.run_sync(
    agent,
    sandbox=sandbox,
    prompt="Run the relevant test suite and report the result. Fix only if needed.",
    run_config=RunConfig(
        json_events=True,
        resume_session_id=session_id,
    ),
)

print("Verification:")
print(verify.output)
print("Final diff:")
print(verify.diff)
examples/multi_step_app_managed.py
from pathlib import Path

from sbx_agents import Agent, Runner
from sbx_agents.backends import Shell
from sbx_agents.sandboxes import DockerSandbox

agent = Agent(
    name="shell-workflow",
    backend=Shell(),
    instructions="Run shell commands requested by the orchestration layer.",
)

sandbox = DockerSandbox(
    workspace=Path("."),
    image="python:3.12-slim",
)

# Step 1: collect context.
inspect = Runner.run_sync(
    agent,
    sandbox=sandbox,
    prompt="python --version",
)

print("Inspect:")
print(inspect.output)

# Step 2: app carries prior output into the next prompt.
test = Runner.run_sync(
    agent,
    sandbox=sandbox,
    prompt=f"""
Previous command output:

{inspect.output}

Now check whether pytest is installed:
python -m pytest --version
""".strip(),
)

print("Test check:")
print(test.output)

# Step 3: app decides what to do from prior result.
summary = Runner.run_sync(
    agent,
    sandbox=sandbox,
    prompt=f"""
Summarize this workflow result in one line.

Step 1 output:
{inspect.output}

Step 2 output:
{test.output}
""".strip(),
)

print("Summary:")
print(summary.output)
examples/multi_agent_same_sandbox.py
from pathlib import Path

from sbx_agents import Agent, PermissionPolicy, Runner
from sbx_agents.backends import Codex, OpenCode, Shell
from sbx_agents.sandboxes import DockerSbxSandbox

# One sandbox config, many agents. Each run uses the same workspace and branch.
# This lets agents hand off through repo state and `git diff`.
sandbox = DockerSbxSandbox(
    workspace=Path("."),
    name="multi-agent-fix",
    branch="agent/multi-agent-fix",
)

reviewer = Agent(
    name="reviewer",
    backend=OpenCode(model="anthropic/claude-sonnet-4-5"),
    instructions="Review the repo. Identify likely test failures and risks. Do not edit files.",
    permission=PermissionPolicy(default="ask", tools={"edit": "deny", "bash": "allow"}),
)

fixer = Agent(
    name="fixer",
    backend=Codex(model="gpt-5-codex"),
    instructions="Implement the smallest safe fix. Do not do unrelated cleanup.",
)

verifier = Agent(
    name="verifier",
    backend=Shell(),
    instructions="Run verification commands and report results.",
)

# Step 1: OpenCode performs a read-only review.
review = Runner.run_sync(
    reviewer,
    sandbox=sandbox,
    prompt="Review this repository and identify the smallest useful fix plan.",
)

print("Review:")
print(review.output)

# Step 2: Codex applies the fix in the same sandbox workspace/branch.
fix = Runner.run_sync(
    fixer,
    sandbox=sandbox,
    prompt=f"""
Use this review as context:

{review.output}

Implement the smallest safe fix now.
""".strip(),
)

print("Fix:")
print(fix.output)
print("Diff after fix:")
print(fix.diff)

# Step 3: Shell verifies the result in the same sandbox workspace/branch.
verify = Runner.run_sync(
    verifier,
    sandbox=sandbox,
    prompt="pytest -q",
)

print("Verification:")
print(verify.output)
print("Final diff:")
print(verify.diff)
examples/one_agent_multiple_sandboxes_copy_files.py
from pathlib import Path
from tempfile import TemporaryDirectory

from sbx_agents import Agent, Runner
from sbx_agents.backends import Shell
from sbx_agents.sandboxes import DockerSandbox

agent = Agent(
    name="portable-worker",
    backend=Shell(),
    instructions="Run requested shell commands inside whichever sandbox is provided.",
)

with TemporaryDirectory() as tmp:
    root = Path(tmp)
    producer_workspace = root / "producer"
    consumer_workspace = root / "consumer"
    producer_workspace.mkdir()
    consumer_workspace.mkdir()

    producer = DockerSandbox(
        workspace=producer_workspace,
        image="python:3.12-slim",
        name="producer-sandbox",
    )
    consumer = DockerSandbox(
        workspace=consumer_workspace,
        image="python:3.12-slim",
        name="consumer-sandbox",
    )

    # Put an input file only into the producer sandbox.
    producer.put_file(
        "inputs/spec.txt",
        "Build a JSON artifact with status, source, and spec length.",
    )

    # Same agent works in sandbox A.
    producer_result = Runner.run_sync(
        agent,
        sandbox=producer,
        prompt="""
python - <<'PY'
from pathlib import Path
spec = Path('inputs/spec.txt').read_text()
Path('artifacts').mkdir(exist_ok=True)
Path('artifacts/report.json').write_text(
    '{"status":"ok","source":"producer","spec_chars":%d}\n' % len(spec)
)
PY
""".strip(),
    )

    print("Producer output:")
    print(producer_result.output)

    # Copy one selected file from sandbox A to sandbox B through app code.
    # This keeps transfer explicit and tenant/workspace boundaries clear.
    report = producer.get_file("artifacts/report.json")
    consumer.put_file("incoming/report.json", report)

    # Same agent now works in sandbox B against copied file.
    consumer_result = Runner.run_sync(
        agent,
        sandbox=consumer,
        prompt="""
python - <<'PY'
import json
from pathlib import Path
report = json.loads(Path('incoming/report.json').read_text())
Path('verified.txt').write_text(f"verified {report['status']} from {report['source']}\n")
print(Path('verified.txt').read_text(), end='')
PY
""".strip(),
    )

    print("Consumer output:")
    print(consumer_result.output)

    print("Consumer files:")
    print(consumer.list_files("**/*"))

    verified = consumer.get_file("verified.txt")
    print("Verified file:")
    print(verified)

Agent config

File Shows
examples/docker_sbx_codex_with_mcp_and_skill.py Codex with skill and stdio MCP
examples/docker_sbx_opencode_with_http_mcp_and_skill.py OpenCode with skill and HTTP MCP
examples/complex_codex_agent_config.py Codex reasoning, MCP, skills, permissions, JSON events, output schema
examples/complex_opencode_agent_config.py OpenCode permissions, LSP, formatter, MCP, skills, session flags
examples/docker_sbx_codex_with_mcp_and_skill.py
from pathlib import Path

from sbx_agents import Agent, Runner
from sbx_agents.backends import Codex
from sbx_agents.mcp import StdioMCP
from sbx_agents.sandboxes import DockerSbxSandbox
from sbx_agents.skills import Skill

agent = Agent(
    name="fix-tests",
    backend=Codex(
        model="gpt-5-codex",
        approval_policy="on-request",
        sandbox_mode="workspace-write",
    ),
    instructions="Fix failing tests with the smallest safe diff. Run tests before finishing.",
    skills=[Skill.from_dir(Path(__file__).parent / "skills" / "fix-tests")],
    mcp_servers=[
        StdioMCP(
            name="context7",
            command="npx",
            args=["-y", "@upstash/context7-mcp"],
        )
    ],
)

result = Runner.run_sync(
    agent,
    sandbox=DockerSbxSandbox(
        workspace=Path("."),
        name="fix-tests",
        branch="agent/fix-tests",
    ),
    prompt="Fix the failing pytest suite.",
)

print(result.output)
print(result.diff)
examples/docker_sbx_opencode_with_http_mcp_and_skill.py
from pathlib import Path

from sbx_agents import Agent, PermissionPolicy, Runner
from sbx_agents.backends import OpenCode
from sbx_agents.mcp import HttpMCP
from sbx_agents.sandboxes import DockerSbxSandbox
from sbx_agents.skills import Skill

agent = Agent(
    name="reviewer",
    backend=OpenCode(
        model="anthropic/claude-sonnet-4-5",
    ),
    permission=PermissionPolicy(tools={"edit": "deny", "bash": "allow"}),
    instructions="Review the repo and produce a risk-ranked plan.",
    skills=[Skill.from_dir(Path(__file__).parent / "skills" / "review-risk")],
    mcp_servers=[
        HttpMCP(
            name="docs",
            url="https://mcp.example.com/mcp",
            bearer_token_env_var="DOCS_MCP_TOKEN",
        )
    ],
)

result = Runner.run_sync(
    agent,
    sandbox=DockerSbxSandbox(
        workspace=Path("."),
        name="reviewer",
        branch="agent/review",
    ),
    prompt="Review this repository for release risk.",
)

print(result.output)
examples/complex_codex_agent_config.py
from pathlib import Path

from sbx_agents import Agent, PermissionPolicy, RunConfig, Runner
from sbx_agents.backends import Codex
from sbx_agents.mcp import HttpMCP, StdioMCP
from sbx_agents.sandboxes import DockerSbxSandbox
from sbx_agents.skills import Skill

agent = Agent(
    name="release-risk-fixer",
    backend=Codex(
        model="gpt-5-codex",
        model_provider="openai",
        model_reasoning_effort="high",
        model_reasoning_summary="auto",
        model_verbosity="medium",
        approval_policy="on-request",
        sandbox_mode="workspace-write",
        web_search="enabled",
        features={"web_search_request": True},
        shell_environment_policy={
            "inherit": "core",
            "include_only": ["PATH", "HOME", "OPENAI_API_KEY", "GITHUB_TOKEN"],
        },
        extra_config={
            "model_context_window": 200000,
        },
    ),
    instructions=(
        "Act as a release engineer. Identify high-risk failures, fix only the smallest "
        "safe issue, and return structured release risk output."
    ),
    permission=PermissionPolicy(default="ask", tools={"bash": "allow", "edit": "ask"}),
    skills=[
        Skill.from_dir(Path(__file__).parent / "skills" / "fix-tests"),
        Skill.from_dir(Path(__file__).parent / "skills" / "review-risk"),
    ],
    mcp_servers=[
        StdioMCP(
            name="context7",
            command="npx",
            args=["-y", "@upstash/context7-mcp"],
            env={"NODE_OPTIONS": "--max-old-space-size=2048"},
            startup_timeout_sec=20,
            tool_timeout_sec=60,
            enabled_tools=["resolve-library-id", "get-library-docs"],
        ),
        HttpMCP(
            name="internal-docs",
            url="https://mcp.example.com/mcp",
            bearer_token_env_var="DOCS_MCP_TOKEN",
            required=True,
        ),
    ],
)

result = Runner.run_sync(
    agent,
    sandbox=DockerSbxSandbox(
        workspace=Path("."),
        name="release-risk-fixer",
        branch="agent/release-risk-fixer",
        cpus=4,
        memory="8g",
    ),
    prompt="Review release risk, fix the smallest critical issue, and summarize validation.",
    run_config=RunConfig(
        json_events=True,
        output_schema={
            "type": "object",
            "properties": {
                "summary": {"type": "string"},
                "risk": {"type": "string", "enum": ["low", "medium", "high"]},
                "tests_run": {"type": "array", "items": {"type": "string"}},
                "follow_ups": {"type": "array", "items": {"type": "string"}},
            },
            "required": ["summary", "risk", "tests_run"],
        },
        extra_args=["--skip-git-repo-check"],
    ),
)

print(result.output)
print(result.diff)
examples/complex_opencode_agent_config.py
from pathlib import Path

from sbx_agents import Agent, PermissionPolicy, RunConfig, Runner
from sbx_agents.backends import OpenCode
from sbx_agents.mcp import HttpMCP, StdioMCP
from sbx_agents.sandboxes import DockerSbxSandbox
from sbx_agents.skills import Skill

agent = Agent(
    name="read-only-reviewer",
    backend=OpenCode(
        model="anthropic/claude-sonnet-4-5",
        small_model="anthropic/claude-haiku-4-5",
        agent="build",
        permission={
            "edit": "deny",
            "bash": {
                "pytest*": "allow",
                "git diff*": "allow",
                "*": "ask",
            },
        },
        lsp=True,
        formatter=True,
        instructions=["README.md", "CONTRIBUTING.md"],
        disabled_providers=["example-disabled-provider"],
        snapshot=True,
        share="manual",
        compaction={"enabled": True, "threshold": 0.8},
    ),
    instructions="Review the repository for release risk. Do not modify files.",
    permission=PermissionPolicy(default="ask", tools={"edit": "deny", "bash": "allow"}),
    skills=[Skill.from_dir(Path(__file__).parent / "skills" / "review-risk")],
    mcp_servers=[
        StdioMCP(
            name="context7",
            command="npx",
            args=["-y", "@upstash/context7-mcp"],
            startup_timeout_sec=20,
        ),
        HttpMCP(
            name="docs",
            url="https://mcp.example.com/mcp",
            headers={"X-Workspace": "release"},
        ),
    ],
)

result = Runner.run_sync(
    agent,
    sandbox=DockerSbxSandbox(
        workspace=Path("."),
        name="read-only-reviewer",
        branch="agent/read-only-reviewer",
    ),
    prompt="Review this repository and return a risk-ranked report.",
    run_config=RunConfig(
        json_events=True,
        extra_args=["--title", "Release risk review"],
    ),
)

print(result.output)

Sandbox config

File Shows
examples/complex_sandbox_setup.py Docker resource/env args and Docker SBX branch/resources
examples/sandbox_file_management.py put_file, get_file, list_files, delete_file
examples/plain_docker_shell_non_strict_skills.py skipping unsupported shell features with strict=False
examples/complex_sandbox_setup.py
from pathlib import Path

from sbx_agents import Agent, Runner
from sbx_agents.backends import Shell
from sbx_agents.sandboxes import DockerSandbox, DockerSbxSandbox

workspace = Path(".")

agent = Agent(
    name="sandbox-smoke-test",
    backend=Shell(),
    instructions="Run diagnostics inside the sandbox and report the environment.",
)

# Plain Docker: useful in CI or Linux environments where Docker SBX is unavailable.
# `docker_args` is the escape hatch for Docker-native settings the SDK has not modeled yet.
plain_docker = DockerSandbox(
    workspace=workspace,
    image="python:3.12-slim",
    name="sbx-agents-smoke-test",
    workdir="/workspace",
    env={
        "PYTHONUNBUFFERED": "1",
        "PIP_DISABLE_PIP_VERSION_CHECK": "1",
    },
    docker_args=[
        "--cpus",
        "2",
        "--memory",
        "4g",
        "--network",
        "bridge",
        "--tmpfs",
        "/tmp:rw,noexec,nosuid,size=512m",
        "-v",
        f"{(Path.home() / '.cache' / 'pip').resolve()}:/root/.cache/pip",
    ],
)

plain_result = Runner.run_sync(
    agent,
    sandbox=plain_docker,
    prompt="python --version && pwd && ls",
)

print("Plain Docker output:")
print(plain_result.output)

# Docker SBX: better local isolation for real coding agents, with branch/resource controls.
docker_sbx = DockerSbxSandbox(
    workspace=workspace,
    name="sbx-agents-isolated-smoke-test",
    branch="agent/sandbox-smoke-test",
    cpus=4,
    memory="8g",
    remove_on_exit=True,
)

sbx_result = Runner.run_sync(
    agent,
    sandbox=docker_sbx,
    prompt="pwd && git status --short && python --version || true",
)

print("Docker SBX output:")
print(sbx_result.output)
print("Docker SBX diff:")
print(sbx_result.diff)
examples/sandbox_file_management.py
from pathlib import Path

from sbx_agents import Agent, Runner
from sbx_agents.backends import Shell
from sbx_agents.sandboxes import DockerSandbox

sandbox = DockerSandbox(
    workspace=Path("."),
    image="python:3.12-slim",
)

agent = Agent(
    name="file-worker",
    backend=Shell(),
    instructions="Read and write only requested files.",
)

# Put a specific input file into the sandbox workspace before the run.
sandbox.put_file(
    "tmp/sbx-agent-spec.md",
    """
# Task Spec

Create a short JSON report at tmp/sbx-agent-report.json.
""".strip(),
)

result = Runner.run_sync(
    agent,
    sandbox=sandbox,
    prompt="""
python - <<'PY'
from pathlib import Path
spec = Path('tmp/sbx-agent-spec.md').read_text()
Path('tmp/sbx-agent-report.json').write_text(
    '{"status":"ok","spec_chars":%d}\n' % len(spec)
)
PY
""".strip(),
)

print(result.output)

# Get a specific output file back from the sandbox workspace after the run.
report = sandbox.get_file("tmp/sbx-agent-report.json")
print(report)

# List and clean specific files.
print(sandbox.list_files("tmp/sbx-agent-*"))
sandbox.delete_file("tmp/sbx-agent-spec.md")
sandbox.delete_file("tmp/sbx-agent-report.json")
examples/plain_docker_shell_non_strict_skills.py
from pathlib import Path

from sbx_agents import Agent, Runner
from sbx_agents.backends import Shell
from sbx_agents.sandboxes import DockerSandbox
from sbx_agents.skills import Skill

agent = Agent(
    name="shell-test",
    backend=Shell(),
    skills=[Skill.from_dir(Path(__file__).parent / "skills" / "fix-tests")],
    strict=False,
)

result = Runner.run_sync(
    agent,
    sandbox=DockerSandbox(workspace=Path("."), image="python:3.12-slim"),
    prompt="python --version",
)

print(result.output)

Skills

File Shows
examples/skills/fix-tests/SKILL.md portable skill with OpenCode-compatible frontmatter
examples/skills/review-risk/SKILL.md review skill with risk-focused instructions
examples/skills/fix-tests/SKILL.md
---
name: fix-tests
description: Fix failing tests with smallest safe diff
compatibility: sbx-agents
---
# Fix Tests

When fixing tests:

- Reproduce the failure first.
- Prefer the smallest safe diff.
- Run the targeted test before widening scope.
- Explain any test you could not run.
examples/skills/review-risk/SKILL.md
---
name: review-risk
description: Review code changes by release risk
compatibility: sbx-agents
---
# Risk Review

Review code changes by ranking findings from highest to lowest risk.

Focus on correctness, security, data loss, migrations, concurrency, and missing tests.

Services and CI

File Shows
examples/fastapi_agent_service.py minimal FastAPI API for runs and file endpoints
examples/fastapi_multi_tenant_isolation.py tenant-scoped workspaces, jobs, files, and sandbox names
examples/github_actions_fix_tests.py GitHub Actions script entrypoint
examples/github-actions/fix-tests.yml workflow template that runs the SDK and opens a PR
examples/precommit_runner.py import-safe Runner for Git pre-commit hooks
examples/git-hooks/pre-commit Git pre-commit hook template that calls sbxa
examples/fastapi_agent_service.py
from pathlib import Path
from uuid import uuid4

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

from sbx_agents import Agent, PermissionPolicy, RunConfig, Runner, RunResult
from sbx_agents.backends import Codex, OpenCode, Shell
from sbx_agents.sandboxes import DockerSandbox, DockerSbxSandbox

app = FastAPI(title="sbx-agents example")

jobs: dict[str, RunResult] = {}


class RunRequest(BaseModel):
    prompt: str
    workspace: Path = Path(".")
    agent_backend: str = "shell"
    sandbox_backend: str = "docker"
    branch: str | None = None
    json_events: bool = False
    readonly: bool = False
    docker_image: str = "python:3.12-slim"
    extra_args: list[str] = Field(default_factory=list)


class RunResponse(BaseModel):
    job_id: str
    output: str
    returncode: int
    diff: str | None
    command: list[str]


class FileWriteRequest(BaseModel):
    workspace: Path = Path(".")
    path: Path
    content: str


class FileReadRequest(BaseModel):
    workspace: Path = Path(".")
    path: Path


class FileReadResponse(BaseModel):
    path: Path
    content: str


class FileListResponse(BaseModel):
    files: list[Path]


@app.post("/runs", response_model=RunResponse)
def run_agent(request: RunRequest) -> RunResponse:
    agent = _build_agent(request)
    sandbox = _build_sandbox(request)
    result = Runner.run_sync(
        agent,
        sandbox=sandbox,
        prompt=request.prompt,
        run_config=RunConfig(
            json_events=request.json_events,
            extra_args=request.extra_args,
        ),
    )

    job_id = str(uuid4())
    jobs[job_id] = result
    return RunResponse(
        job_id=job_id,
        output=result.output,
        returncode=result.returncode,
        diff=result.diff,
        command=result.command,
    )


@app.get("/runs/{job_id}", response_model=RunResult)
def get_run(job_id: str) -> RunResult:
    result = jobs.get(job_id)
    if result is None:
        raise HTTPException(status_code=404, detail="Run not found")
    return result


@app.post("/files")
def put_file(request: FileWriteRequest) -> dict[str, str]:
    sandbox = DockerSandbox(workspace=request.workspace)
    path = sandbox.put_file(request.path, request.content)
    return {"path": str(path)}


@app.post("/files/read", response_model=FileReadResponse)
def read_file(request: FileReadRequest) -> FileReadResponse:
    sandbox = DockerSandbox(workspace=request.workspace)
    try:
        content = sandbox.get_file(request.path)
    except FileNotFoundError as exc:
        raise HTTPException(status_code=404, detail="File not found") from exc
    return FileReadResponse(path=request.path, content=content)


@app.get("/files", response_model=FileListResponse)
def list_files(workspace: Path = Path("."), pattern: str = "**/*") -> FileListResponse:
    sandbox = DockerSandbox(workspace=workspace)
    return FileListResponse(files=sandbox.list_files(pattern))


@app.delete("/files")
def delete_file(workspace: Path, path: Path) -> dict[str, str]:
    sandbox = DockerSandbox(workspace=workspace)
    try:
        sandbox.delete_file(path)
    except FileNotFoundError as exc:
        raise HTTPException(status_code=404, detail="File not found") from exc
    return {"deleted": str(path)}


def _build_agent(request: RunRequest) -> Agent:
    permission = None
    if request.readonly:
        permission = PermissionPolicy(default="ask", tools={"edit": "deny", "bash": "allow"})

    if request.agent_backend == "codex":
        backend = Codex(model="gpt-5-codex")
    elif request.agent_backend == "opencode":
        backend = OpenCode(model="anthropic/claude-sonnet-4-5")
    elif request.agent_backend == "shell":
        backend = Shell()
    else:
        raise HTTPException(status_code=400, detail="Unsupported agent_backend")

    return Agent(
        name=f"api-{request.agent_backend}",
        backend=backend,
        instructions="Run inside sandbox. Keep changes focused and report what happened.",
        permission=permission,
    )


def _build_sandbox(request: RunRequest) -> DockerSandbox | DockerSbxSandbox:
    if request.sandbox_backend == "docker":
        return DockerSandbox(
            workspace=request.workspace,
            image=request.docker_image,
        )
    if request.sandbox_backend == "docker_sbx":
        return DockerSbxSandbox(
            workspace=request.workspace,
            branch=request.branch,
        )
    raise HTTPException(status_code=400, detail="Unsupported sandbox_backend")


# Run:
#   uv add --dev fastapi uvicorn
#   uv run uvicorn examples.fastapi_agent_service:app --reload
#
# Example request:
#   curl -X POST http://127.0.0.1:8000/runs \
#     -H 'content-type: application/json' \
#     -d '{"prompt":"python --version","agent_backend":"shell","sandbox_backend":"docker"}'
#
# Write a file:
#   curl -X POST http://127.0.0.1:8000/files \
#     -H 'content-type: application/json' \
#     -d '{"path":"tmp/spec.md","content":"# Spec"}'
#
# Read a file:
#   curl -X POST http://127.0.0.1:8000/files/read \
#     -H 'content-type: application/json' \
#     -d '{"path":"tmp/spec.md"}'
examples/fastapi_multi_tenant_isolation.py
from __future__ import annotations

import re
import shutil
import tempfile
from pathlib import Path
from typing import Annotated
from uuid import uuid4

from fastapi import Depends, FastAPI, Header, HTTPException
from pydantic import BaseModel, Field

from sbx_agents import Agent, RunConfig, Runner, RunResult
from sbx_agents.backends import Codex, OpenCode, Shell
from sbx_agents.sandboxes import DockerSandbox, DockerSbxSandbox

app = FastAPI(title="sbx-agents multi-tenant isolation example")

# Production services should use durable storage and authenticated tenant identity.
# This example keeps per-tenant workspaces under one temp root for clarity.
TENANT_ROOT = Path(tempfile.gettempdir()) / "sbx-agents-tenants"

jobs: dict[tuple[str, str], TenantJob] = {}


class TenantContext(BaseModel):
    tenant_id: str
    user_id: str
    root: Path


class TenantJob(BaseModel):
    tenant_id: str
    user_id: str
    run_id: str
    workspace: Path
    result: RunResult


class TenantRunRequest(BaseModel):
    prompt: str
    agent_backend: str = "shell"
    sandbox_backend: str = "docker"
    docker_image: str = "python:3.12-slim"
    json_events: bool = False
    files: dict[str, str] = Field(default_factory=dict)


class TenantRunResponse(BaseModel):
    tenant_id: str
    run_id: str
    output: str
    returncode: int
    diff: str | None


class TenantFileRequest(BaseModel):
    path: Path
    content: str


class TenantFileResponse(BaseModel):
    path: Path
    content: str


@app.post("/runs", response_model=TenantRunResponse)
def run_agent(
    request: TenantRunRequest,
    tenant: Annotated[TenantContext, Depends(require_tenant)],
) -> TenantRunResponse:
    run_id = uuid4().hex
    workspace = tenant.root / run_id
    workspace.mkdir(parents=True, exist_ok=False)

    sandbox = _build_sandbox(request, tenant, run_id, workspace)
    for path, content in request.files.items():
        sandbox.put_file(path, content)

    result = Runner.run_sync(
        _build_agent(request, tenant),
        sandbox=sandbox,
        prompt=request.prompt,
        run_config=RunConfig(json_events=request.json_events),
    )

    jobs[(tenant.tenant_id, run_id)] = TenantJob(
        tenant_id=tenant.tenant_id,
        user_id=tenant.user_id,
        run_id=run_id,
        workspace=workspace,
        result=result,
    )
    return TenantRunResponse(
        tenant_id=tenant.tenant_id,
        run_id=run_id,
        output=result.output,
        returncode=result.returncode,
        diff=result.diff,
    )


@app.get("/runs/{run_id}", response_model=RunResult)
def get_run(
    run_id: str,
    tenant: Annotated[TenantContext, Depends(require_tenant)],
) -> RunResult:
    return _tenant_job(tenant, run_id).result


@app.post("/runs/{run_id}/files", response_model=dict[str, str])
def put_file(
    run_id: str,
    request: TenantFileRequest,
    tenant: Annotated[TenantContext, Depends(require_tenant)],
) -> dict[str, str]:
    job = _tenant_job(tenant, run_id)
    sandbox = DockerSandbox(workspace=job.workspace)
    sandbox.put_file(request.path, request.content)
    return {"path": str(request.path)}


@app.post("/runs/{run_id}/files/read", response_model=TenantFileResponse)
def read_file(
    run_id: str,
    request: TenantFileRequest,
    tenant: Annotated[TenantContext, Depends(require_tenant)],
) -> TenantFileResponse:
    job = _tenant_job(tenant, run_id)
    sandbox = DockerSandbox(workspace=job.workspace)
    try:
        content = sandbox.get_file(request.path)
    except FileNotFoundError as exc:
        raise HTTPException(status_code=404, detail="File not found") from exc
    return TenantFileResponse(path=request.path, content=content)


@app.delete("/runs/{run_id}", response_model=dict[str, str])
def delete_run(
    run_id: str,
    tenant: Annotated[TenantContext, Depends(require_tenant)],
) -> dict[str, str]:
    job = _tenant_job(tenant, run_id)
    jobs.pop((tenant.tenant_id, run_id), None)
    shutil.rmtree(job.workspace, ignore_errors=True)
    return {"deleted": run_id}


def require_tenant(
    x_tenant_id: Annotated[str, Header(alias="X-Tenant-ID")],
    x_user_id: Annotated[str, Header(alias="X-User-ID")],
) -> TenantContext:
    tenant_id = _safe_id(x_tenant_id, "tenant")
    user_id = _safe_id(x_user_id, "user")
    root = TENANT_ROOT / tenant_id
    root.mkdir(parents=True, exist_ok=True)
    return TenantContext(tenant_id=tenant_id, user_id=user_id, root=root)


def _build_agent(request: TenantRunRequest, tenant: TenantContext) -> Agent:
    if request.agent_backend == "shell":
        backend = Shell()
    elif request.agent_backend == "codex":
        backend = Codex(model="gpt-5-codex")
    elif request.agent_backend == "opencode":
        backend = OpenCode(model="anthropic/claude-sonnet-4-5")
    else:
        raise HTTPException(status_code=400, detail="Unsupported agent_backend")

    return Agent(
        name=f"tenant-{tenant.tenant_id}-{request.agent_backend}",
        backend=backend,
        instructions=(
            "You are running in an isolated tenant workspace. "
            "Never read or write outside the workspace."
        ),
    )


def _build_sandbox(
    request: TenantRunRequest,
    tenant: TenantContext,
    run_id: str,
    workspace: Path,
) -> DockerSandbox | DockerSbxSandbox:
    name = f"sbx-{tenant.tenant_id}-{run_id[:12]}"
    if request.sandbox_backend == "docker":
        return DockerSandbox(
            workspace=workspace,
            image=request.docker_image,
            name=name,
            docker_args=["--network", "none"],
        )
    if request.sandbox_backend == "docker_sbx":
        return DockerSbxSandbox(
            workspace=workspace,
            name=name,
            branch=f"agent/{tenant.tenant_id}/{run_id}",
        )
    raise HTTPException(status_code=400, detail="Unsupported sandbox_backend")


def _tenant_job(tenant: TenantContext, run_id: str) -> TenantJob:
    job = jobs.get((tenant.tenant_id, run_id))
    if job is None:
        raise HTTPException(status_code=404, detail="Run not found for tenant")
    return job


def _safe_id(value: str, label: str) -> str:
    if not re.fullmatch(r"[a-zA-Z0-9_-]{1,64}", value):
        raise HTTPException(status_code=400, detail=f"Invalid {label} id")
    return value


# Run:
#   uv add --dev fastapi uvicorn
#   uv run uvicorn examples.fastapi_multi_tenant_isolation:app --reload
#
# Tenant A:
#   curl -X POST http://127.0.0.1:8000/runs \
#     -H 'content-type: application/json' \
#     -H 'X-Tenant-ID: tenant-a' \
#     -H 'X-User-ID: alice' \
#     -d '{"prompt":"cat spec.md","files":{"spec.md":"tenant A spec"}}'
#
# Tenant B cannot read Tenant A run, even if it guesses run_id:
#   curl http://127.0.0.1:8000/runs/<tenant-a-run-id> \
#     -H 'X-Tenant-ID: tenant-b' \
#     -H 'X-User-ID: bob'
examples/github_actions_fix_tests.py
import os
from pathlib import Path

from sbx_agents import Agent, PermissionPolicy, RunConfig, Runner
from sbx_agents.backends import Codex, OpenCode, Shell
from sbx_agents.sandboxes import DockerSandbox, DockerSbxSandbox


def main() -> None:
    workspace = Path(os.environ.get("GITHUB_WORKSPACE", "."))
    prompt = os.environ.get("SBX_AGENT_PROMPT", "Run the test suite and fix failures.")

    agent = Agent(
        name="github-actions-agent",
        backend=_backend(),
        instructions=(
            "You are running in GitHub Actions. Keep diffs small. "
            "Run relevant tests before finishing."
        ),
        permission=PermissionPolicy(default="ask", tools={"bash": "allow"}),
    )

    result = Runner.run_sync(
        agent,
        sandbox=_sandbox(workspace),
        prompt=prompt,
        run_config=RunConfig(json_events=_bool_env("SBX_JSON_EVENTS")),
    )

    print(result.output)
    if result.diff:
        print("\n--- diff ---")
        print(result.diff)

    summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
    if summary_path:
        Path(summary_path).write_text(_summary(result.output, result.diff), encoding="utf-8")

    if result.returncode != 0:
        raise SystemExit(result.returncode)


def _backend() -> Shell | Codex | OpenCode:
    backend = os.environ.get("SBX_AGENT_BACKEND", "shell")
    if backend == "shell":
        return Shell()
    if backend == "codex":
        return Codex(model=os.environ.get("SBX_CODEX_MODEL"))
    if backend == "opencode":
        return OpenCode(model=os.environ.get("SBX_OPENCODE_MODEL"))
    raise ValueError(f"Unsupported SBX_AGENT_BACKEND: {backend}")


def _sandbox(workspace: Path) -> DockerSandbox | DockerSbxSandbox:
    sandbox = os.environ.get("SBX_SANDBOX_BACKEND", "docker")
    if sandbox == "docker":
        return DockerSandbox(
            workspace=workspace,
            image=os.environ.get("SBX_DOCKER_IMAGE", "python:3.12-slim"),
        )
    if sandbox == "docker_sbx":
        return DockerSbxSandbox(
            workspace=workspace,
            name="github-actions-agent",
            branch=os.environ.get("SBX_BRANCH"),
        )
    raise ValueError(f"Unsupported SBX_SANDBOX_BACKEND: {sandbox}")


def _summary(output: str, diff: str | None) -> str:
    body = ["# sbx-agents run", "", "## Output", "", "```", output, "```"]
    if diff:
        body.extend(["", "## Diff", "", "```diff", diff, "```"])
    return "\n".join(body) + "\n"


def _bool_env(name: str) -> bool:
    return os.environ.get(name, "").lower() in {"1", "true", "yes", "on"}


if __name__ == "__main__":
    main()
examples/github-actions/fix-tests.yml
name: sbx-agents fix tests

on:
  workflow_dispatch:
    inputs:
      prompt:
        description: Agent prompt
        required: true
        default: Run pytest and fix failures with the smallest safe diff.
      agent_backend:
        description: shell, codex, or opencode
        required: true
        default: shell

permissions:
  contents: write
  pull-requests: write

jobs:
  agent:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: astral-sh/setup-uv@v5

      - name: Install project
        run: uv sync

      - name: Run sandboxed agent
        env:
          SBX_AGENT_PROMPT: ${{ inputs.prompt }}
          SBX_AGENT_BACKEND: ${{ inputs.agent_backend }}
          SBX_SANDBOX_BACKEND: docker
          SBX_DOCKER_IMAGE: python:3.12-slim
          SBX_JSON_EVENTS: "true"
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: uv run python examples/github_actions_fix_tests.py

      - name: Show diff
        run: git diff --stat && git diff

      - name: Create pull request
        if: ${{ github.event_name == 'workflow_dispatch' }}
        uses: peter-evans/create-pull-request@v6
        with:
          branch: agent/fix-tests-${{ github.run_id }}
          title: Fix tests with sbx-agents
          body: Generated by `sbx-agents` GitHub Actions example.
          commit-message: Fix tests with sbx-agents
examples/precommit_runner.py
from pathlib import Path

from sbx_agents import Agent, Runner
from sbx_agents.backends import Shell
from sbx_agents.sandboxes import DockerSandbox

# Import-safe runner for a Git pre-commit hook.
# The hook calls this through `sbxa run examples/precommit_runner.py`.
# Keep pre-commit runners deterministic and non-mutating; hooks should fail fast.
runner = Runner(
    Agent(
        name="precommit-checks",
        backend=Shell(),
        instructions="Run deterministic pre-commit checks. Do not modify files.",
    ),
    sandbox=DockerSandbox(
        workspace=Path("."),
        image="python:3.12-slim",
        name="sbx-agents-precommit",
    ),
    default_prompt="python -m compileall .",
)
examples/git-hooks/pre-commit
#!/bin/sh
set -eu

# Example Git pre-commit hook for sbx-agents.
# Install:
#   cp examples/git-hooks/pre-commit .git/hooks/pre-commit
#   chmod +x .git/hooks/pre-commit
#
# This hook runs a Python-defined Runner through `sbxa`. It only runs when staged
# Python files exist, and it blocks the commit if the sandboxed check fails.

if ! command -v sbxa >/dev/null 2>&1; then
  echo "sbxa not found. Install with: uv tool install -e ."
  exit 1
fi

if ! git diff --cached --name-only -- '*.py' | grep -q .; then
  exit 0
fi

echo "Running sbx-agents pre-commit checks..."
sbxa run examples/precommit_runner.py

How to run examples

Most examples are scripts:

$ uv add sbx-agents
$ uv run python examples/plain_docker_shell.py

The CLI runner example is import-safe and runs through sbxa:

$ sbx login
$ sbxa doctor --require docker-sbx
$ sbxa run examples/cli_runner.py
$ sbxa run examples/cli_runner.py "Only fix lint failures."

Use Codex defaults and Docker SBX OAuth auth:

$ sbxa run examples.cli_runner_codex_default:runner

The runner declares auth on the Codex backend. Docker SBX adds required OpenAI network policy automatically:

backend=Codex(model_provider="openai", auth="openai_oauth")

FastAPI examples require FastAPI and Uvicorn:

$ uv add sbx-agents fastapi uvicorn
$ uv run uvicorn examples.fastapi_agent_service:app --reload

Docker SBX examples require sbx and agent credentials.

Install the pre-commit hook example:

$ cp examples/git-hooks/pre-commit .git/hooks/pre-commit
$ chmod +x .git/hooks/pre-commit

Next steps