Skip to main content
A comprehensive guide to integrating Runloop cloud sandboxes with the OpenAI Agents SDK.

Introduction

Runloop is a cloud sandbox provider that offers secure, isolated execution environments for AI agents. Unlike local Docker containers, Runloop provides fully managed cloud sandboxes with powerful features including:
  • Cloud-native sandboxes: No local Docker installation required
  • Pre-configured blueprints: Choose from optimized environment templates
  • Suspend and resume: Pause sandboxes to save state and costs, then resume exactly where you left off
  • Root access: Full system control when needed for package installation and configuration
  • Docker-in-Docker support: Run containers inside your sandbox for complex workloads

When to Use Runloop

Choose Runloop over other sandbox providers when you need:
  • Cloud-hosted execution without managing infrastructure
  • The ability to suspend long-running tasks and resume later
  • Docker-in-Docker capabilities for containerized workloads
  • Pre-configured environments for common tech stacks
  • Root access for system-level operations
For more details, visit the official Runloop documentation.

Prerequisites

Before getting started, ensure you have:
  • Python 3.10 or higher installed
  • uv package manager - Install here
  • OpenAI API access - For running the AI models
  • Runloop platform access - For cloud sandbox provisioning

Getting Your API Keys

You’ll need API keys from both OpenAI and Runloop to use this integration.

OpenAI API Key

  1. Navigate to https://platform.openai.com/api-keys
  2. Sign in to your OpenAI account (or create one if you don’t have one)
  3. Click “Create new secret key”
  4. Give your key a descriptive name like "Agents SDK Development"
  5. Copy the key immediately (it won’t be shown again)
  6. Set it as an environment variable:
export OPENAI_API_KEY=sk-...

Runloop API Key

  1. Navigate to https://platform.runloop.ai/
  2. Sign up for a Runloop account
    • No credit card required
    • $50 in free credits to get started
  3. Once logged in, navigate to your API settings or API keys section
  4. Click “Create API Key” or similar option
  5. Copy your API key
  6. Set it as an environment variable:
export RUNLOOP_API_KEY=...
You can add these to your ~/.bashrc, ~/.zshrc, or equivalent shell configuration file to make them persistent.

Installation

The Runloop sandbox backend is available as an optional extra in the OpenAI Agents SDK.

Install the SDK with Runloop Support

uv sync --extra runloop

Verify Installation

You can verify that Runloop support is installed correctly:
uv run python -c "from agents.extensions.sandbox import RunloopSandboxClient; print('✓ Runloop support installed!')"
If you see the success message, you’re ready to proceed!

Basic Example: Your First Runloop Sandbox

This example demonstrates the core concepts of running a sandboxed agent with Runloop:
"""
basic_runloop_example.py - Simple Runloop sandbox demonstration
"""
import asyncio
import os

from agents import Runner
from agents.run import RunConfig
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
from agents.extensions.sandbox import RunloopSandboxClient, RunloopSandboxClientOptions


async def main():
    # Verify API keys are set
    if not os.environ.get("OPENAI_API_KEY"):
        raise SystemExit("OPENAI_API_KEY must be set")
    if not os.environ.get("RUNLOOP_API_KEY"):
        raise SystemExit("RUNLOOP_API_KEY must be set")

    # Create a simple workspace manifest
    manifest = Manifest(
        root="/home/user",  # Default workspace root for Runloop
        entries={
            "README.md": {
                "type": "text",
                "text": (
                    "# Demo Project\n\n"
                    "This workspace demonstrates Runloop sandbox integration.\n"
                ),
            },
            "data.txt": {
                "type": "text",
                "text": "Sample data: 42, 100, 256\n",
            },
        },
    )

    # Configure the sandbox agent
    agent = SandboxAgent(
        name="Runloop Assistant",
        model="gpt-4o",
        instructions=(
            "You are a helpful assistant with access to a cloud sandbox. "
            "Inspect the workspace files and answer questions about them."
        ),
        default_manifest=manifest,
    )

    # Create Runloop sandbox client
    client = RunloopSandboxClient()

    # Configure the run with Runloop backend
    run_config = RunConfig(
        sandbox=SandboxRunConfig(
            client=client,
            options=RunloopSandboxClientOptions(
                # Optional: specify a blueprint for pre-configured environments
                # blueprint_name="runloop/universal-ubuntu-24.04-x86_64-dnd",
                pause_on_exit=False,  # Set to True to suspend instead of delete
            ),
        ),
        workflow_name="Basic Runloop Example",
    )

    try:
        # Run the agent
        result = await Runner.run(
            agent,
            "What files are in the workspace? Summarize their contents.",
            run_config=run_config,
        )

        print("Agent response:")
        print(result.final_output)

    finally:
        # Clean up the client
        await client.close()


if __name__ == "__main__":
    asyncio.run(main())

How to Run

Save the code above as basic_runloop_example.py and run:
uv run python basic_runloop_example.py

What’s Happening

  1. Manifest Creation: We define a simple workspace with two text files
  2. SandboxAgent: Configured with instructions and the default workspace
  3. RunloopSandboxClient: Manages the connection to Runloop’s API
  4. RunConfig: Ties together the sandbox client, options, and workflow
  5. Execution: The agent spins up a cloud sandbox, materializes the workspace, executes the task, and returns results
The sandbox is automatically created, the agent executes the task, and then the sandbox is cleaned up (unless pause_on_exit=True).

Advanced Example: Docker-in-Docker with PostgreSQL Suspend/Resume

This advanced example showcases Runloop’s unique suspend/resume capability. We’ll:
  1. Create a sandbox with Docker-in-Docker support
  2. Install and configure PostgreSQL
  3. Insert data into a database
  4. Suspend the sandbox (preserving all state)
  5. Serialize the session for later use
  6. Resume the sandbox from the suspended state
  7. Verify that installed packages, files, and database data persist
"""
dockerindocker_postgres_example.py - Advanced Runloop suspend/resume demonstration

This example demonstrates:
- Using custom blueprints (Docker-in-Docker)
- Root user access for package installation
- Database operations in a sandbox
- Suspending and resuming sandbox state
- State persistence across suspend/resume cycles
"""
import asyncio
import os
import json

from agents import Runner
from agents.run import RunConfig
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
from agents.extensions.sandbox import (
    RunloopSandboxClient,
    RunloopSandboxClientOptions,
    RunloopUserParameters,
)


async def main():
    # Verify API keys
    if not os.environ.get("OPENAI_API_KEY"):
        raise SystemExit("OPENAI_API_KEY must be set")
    if not os.environ.get("RUNLOOP_API_KEY"):
        raise SystemExit("RUNLOOP_API_KEY must be set")

    print("=== Phase 1: Creating sandbox and installing PostgreSQL ===\n")

    # Create a manifest with a workspace marker
    manifest = Manifest(
        root="/root",  # Root user uses /root as home
        entries={
            "workspace_marker.txt": {
                "type": "text",
                "text": "This file was created in the initial workspace setup.\n",
            },
        },
    )

    # Configure agent for database setup
    setup_agent = SandboxAgent(
        name="Database Setup Agent",
        model="gpt-4o",
        instructions=(
            "You are a database administrator with root access. "
            "Install PostgreSQL, create a database, create a table, and insert sample data. "
            "Verify the installation and data insertion by querying the database."
        ),
        default_manifest=manifest,
    )

    # Create Runloop client
    client = RunloopSandboxClient()

    # Configure sandbox with Docker-in-Docker blueprint and root access
    run_config = RunConfig(
        sandbox=SandboxRunConfig(
            client=client,
            options=RunloopSandboxClientOptions(
                # Use Docker-in-Docker capable blueprint
                blueprint_name="runloop/universal-ubuntu-24.04-x86_64-dnd",
                # Enable suspend on exit instead of deletion
                pause_on_exit=True,
                # Launch as root user for package installation
                user_parameters=RunloopUserParameters(username="root", uid=0),
                # Optional: give the sandbox a friendly name
                name="postgres-demo-sandbox",
            ),
        ),
        workflow_name="PostgreSQL Suspend/Resume Demo",
    )

    try:
        # Phase 1: Setup database
        print("Installing PostgreSQL and setting up database...")
        setup_result = await Runner.run(
            setup_agent,
            (
                "Install PostgreSQL using apt. Then as the postgres user, "
                "create a database called 'demo_db', create a table called 'users' "
                "with columns 'id' (serial) and 'name' (text), and insert three rows: "
                "Alice, Bob, and Charlie. Verify by running a SELECT query."
            ),
            run_config=run_config,
        )

        print("\nSetup Result:")
        print(setup_result.final_output)
        print("\n" + "="*60 + "\n")

        # Phase 2: Suspend and serialize
        print("=== Phase 2: Suspending sandbox ===\n")

        # The sandbox is automatically suspended because pause_on_exit=True
        # Get the session state for serialization
        session_state = await client.get_session_state()

        if session_state:
            # Serialize the session state
            state_json = session_state.model_dump_json()
            print(f"Session state serialized ({len(state_json)} bytes)")

            # Save to file (in real use, save to database or storage)
            with open("/tmp/runloop_session_state.json", "w") as f:
                f.write(state_json)
            print("State saved to /tmp/runloop_session_state.json")

        print("\nSandbox is now suspended (not deleted)")
        print("Installed packages, files, and database contents are preserved")
        print("\n" + "="*60 + "\n")

        # Close the client (sandbox remains suspended in the cloud)
        await client.close()

        # Phase 3: Resume from saved state
        print("=== Phase 3: Resuming from suspended state ===\n")

        # In a real application, this could happen hours or days later
        # Load the saved session state
        with open("/tmp/runloop_session_state.json", "r") as f:
            loaded_state_json = f.read()

        print("Loaded session state from file")

        # Deserialize the state
        from agents.extensions.sandbox import RunloopSandboxSessionState
        loaded_state = RunloopSandboxSessionState.model_validate_json(loaded_state_json)

        # Create a new client and resume the session
        resume_client = RunloopSandboxClient()

        # Configure agent for verification
        verify_agent = SandboxAgent(
            name="Database Verification Agent",
            model="gpt-4o",
            instructions=(
                "You are verifying database persistence after resume. "
                "Check if PostgreSQL is still installed, the workspace marker file exists, "
                "and the database table contains the original data."
            ),
            default_manifest=manifest,
        )

        # Resume with the loaded state
        resume_config = RunConfig(
            sandbox=SandboxRunConfig(
                client=resume_client,
                # Pass the loaded state to resume the sandbox
                state=loaded_state,
            ),
            workflow_name="PostgreSQL Resume Verification",
        )

        # Phase 4: Verify persistence
        print("Verifying that state persisted across suspend/resume...")
        verify_result = await Runner.run(
            verify_agent,
            (
                "Verify the following: 1) PostgreSQL is installed (check version), "
                "2) The workspace_marker.txt file exists in /root, "
                "3) Query the demo_db database and list all users from the users table. "
                "Report all findings."
            ),
            run_config=resume_config,
        )

        print("\nVerification Result:")
        print(verify_result.final_output)
        print("\n" + "="*60 + "\n")

        print("✓ Success! The sandbox resumed with all state intact:")
        print("  • Installed packages (PostgreSQL) persisted")
        print("  • Workspace files persisted")
        print("  • Database contents persisted")

    finally:
        # Clean up
        await resume_client.close()


if __name__ == "__main__":
    asyncio.run(main())

How to Run

Save the code as dockerindocker_postgres_example.py and run:
uv run python dockerindocker_postgres_example.py

Understanding the Flow

Phase 1: Initial Setup

  • Creates a sandbox using the Docker-in-Docker blueprint
  • Launches with root user access (uid=0)
  • Agent installs PostgreSQL via apt
  • Creates database, table, and inserts data
  • Creates a workspace marker file

Phase 2: Suspend

  • With pause_on_exit=True, the sandbox is suspended (not deleted)
  • Session state is serialized to JSON
  • All installed packages, files, and database state are preserved in the cloud
  • The client closes, but the sandbox remains suspended

Phase 3: Resume

  • Session state is loaded from the saved JSON
  • A new RunloopSandboxClient is created
  • The sandbox is resumed using the state parameter
  • The suspended sandbox comes back online with all state intact

Phase 4: Verification

  • Agent verifies PostgreSQL is still installed
  • Workspace marker file still exists
  • Database table still contains the original rows

Key Takeaways

  1. State Persistence: Everything in the sandbox persists during suspend/resume:
    • Installed system packages
    • Filesystem contents
    • Running databases and their data
    • Environment variables and configurations
  2. Cost Optimization: Suspend sandboxes during idle periods to save costs, then resume exactly where you left off
  3. Serialization: Session state can be stored in databases, files, or any persistent storage for long-term management
  4. Root Access: The RunloopUserParameters(username="root", uid=0) pattern enables system-level operations
  5. Blueprints: Different blueprints provide different capabilities (Docker-in-Docker, GPU access, etc.)

Configuration Options

RunloopSandboxClientOptions

Configure your Runloop sandbox with these options:
from agents.extensions.sandbox import RunloopSandboxClientOptions

options = RunloopSandboxClientOptions(
    blueprint_name="runloop/universal-ubuntu-24.04-x86_64-dnd",
    pause_on_exit=True,
    user_parameters=RunloopUserParameters(username="root", uid=0),
    name="my-sandbox",
    timeouts=RunloopTimeouts(create_s=600),
    env_vars={"DATABASE_URL": "postgresql://localhost/mydb"},
)

Parameters

  • blueprint_name (str | None): Name of a pre-configured environment template
    • Example: "runloop/universal-ubuntu-24.04-x86_64-dnd"
    • Find available blueprints in the Runloop dashboard
  • blueprint_id (str | None): Direct blueprint ID reference (alternative to blueprint_name)
  • pause_on_exit (bool): When True, suspends the sandbox instead of deleting it
    • Default: False
    • Use for long-running tasks or to preserve state
  • user_parameters (RunloopUserParameters | None): Custom user configuration
    • Controls the sandbox user and workspace root
  • name (str | None): Human-readable name for the sandbox
    • Appears in the Runloop dashboard
    • Helpful for managing multiple sandboxes
  • timeouts (RunloopTimeouts | None): Custom timeout configuration
  • env_vars (dict[str, str] | None): Environment variables to set in the sandbox
  • exposed_ports (tuple[int, …]): Ports to expose for external access
    • Example: (8080, 5432)

RunloopUserParameters

Controls the user account in the sandbox:
from agents.extensions.sandbox import RunloopUserParameters

# Standard user (default behavior)
user_params = RunloopUserParameters(username="user", uid=1000)

# Root user for package installation
root_params = RunloopUserParameters(username="root", uid=0)

Parameters

  • username (str): Username for the sandbox user
    • Standard: "user" (workspace root: /home/user)
    • Root: "root" (workspace root: /root)
  • uid (int): User ID
    • Standard user: typically 1000 or higher
    • Root: 0
    • Must be >= 0
When using root (uid=0), the default workspace root changes from /home/user to /root.

RunloopTimeouts

Fine-tune timeout settings for different operations:
from agents.extensions.sandbox import RunloopTimeouts

timeouts = RunloopTimeouts(
    create_s=600,           # 10 minutes for sandbox creation
    exec_timeout_unbounded_s=7200,  # 2 hours for long-running commands
    suspend_s=180,          # 3 minutes for suspend
    resume_s=600,           # 10 minutes for resume
)

Parameters

All timeouts are in seconds and must be >= 1:
  • exec_timeout_unbounded_s: Maximum execution time for unbounded commands
    • Default: 86400 (24 hours)
  • create_s: Timeout for sandbox creation
    • Default: 300 (5 minutes)
  • file_upload_s: Timeout for file uploads
    • Default: 1800 (30 minutes)
  • file_download_s: Timeout for file downloads
    • Default: 1800 (30 minutes)
  • snapshot_s: Timeout for snapshot creation
    • Default: 300 (5 minutes)
  • suspend_s: Timeout for suspending a sandbox
    • Default: 120 (2 minutes)
  • resume_s: Timeout for resuming a suspended sandbox
    • Default: 300 (5 minutes)
  • keepalive_s: Keepalive interval
    • Default: 10 (10 seconds)
  • cleanup_s: Cleanup timeout
    • Default: 30 (30 seconds)
  • fast_op_s: Timeout for fast operations
    • Default: 30 (30 seconds)

Blueprints

Blueprints are pre-configured environment templates that provide specific capabilities.

What are Blueprints?

Blueprints are VM images with pre-installed software, configurations, and capabilities:
  • Base OS with common utilities
  • Runtime environments (Node.js, Python, etc.)
  • Special capabilities (Docker-in-Docker, GPU access)
  • Optimized configurations for specific workloads

Common Blueprints

  • runloop/universal-ubuntu-24.04-x86_64-dnd: Ubuntu 24.04 with Docker-in-Docker support
    • Use when you need to run containers inside the sandbox
    • Supports docker, docker-compose, and container orchestration

When to Use Custom Blueprints

Use custom blueprints when you need:
  • Docker-in-Docker: Running containerized workloads
  • Specific OS versions: Ubuntu, Debian, Alpine, etc.
  • Pre-installed tools: Avoid installation time on every run
  • GPU access: For ML/AI workloads
  • Specialized environments: Data science, web development, etc.

Finding Available Blueprints

Visit the Runloop platform dashboard to browse available blueprints, or check the Runloop documentation for the latest list.

Key Features

Suspend and Resume

One of Runloop’s most powerful features is the ability to suspend a sandbox and resume it later with all state intact.

How It Works

  1. Suspend: Instead of deleting the sandbox, Runloop creates a snapshot
  2. State Preservation: Everything is saved:
    • Installed packages and binaries
    • Filesystem contents
    • Running processes (when possible)
    • Database contents
    • Environment variables
  3. Serialize: Session state can be saved to your storage
  4. Resume: Later, resume the sandbox from the saved state

When to Use Suspend/Resume

  • Long-running tasks: Pause overnight, resume the next day
  • Cost optimization: Only pay for active compute time
  • Checkpointing: Save progress at key milestones
  • Development workflows: Preserve complex setups between work sessions

Code Pattern

from agents.extensions.sandbox import (
    RunloopSandboxClient,
    RunloopSandboxClientOptions,
    RunloopSandboxSessionState,
)

# Initial run with suspend enabled
client = RunloopSandboxClient()
options = RunloopSandboxClientOptions(pause_on_exit=True)

# ... run agent tasks ...

# Get and serialize state
session_state = await client.get_session_state()
state_json = session_state.model_dump_json()

# Save state_json to database/file/storage
save_to_storage(state_json)

await client.close()  # Sandbox is suspended, not deleted

# --- Later, in a new session ---

# Load and deserialize state
state_json = load_from_storage()
loaded_state = RunloopSandboxSessionState.model_validate_json(state_json)

# Resume with the loaded state
resume_client = RunloopSandboxClient()
resume_config = RunConfig(
    sandbox=SandboxRunConfig(
        client=resume_client,
        state=loaded_state,  # Resume from here
    )
)

# Sandbox resumes with all state intact

Limitations and Considerations

  • Suspend time: Suspending can take 1-3 minutes depending on sandbox size
  • Resume time: Resuming can take 2-5 minutes
  • State size: Large filesystems take longer to suspend/resume
  • Running processes: Some processes may not resume cleanly (restart them if needed)
  • Costs: Suspended sandboxes may incur minimal storage costs (check Runloop pricing)

Root User Access

By default, sandboxes run as a non-root user. Root access enables system-level operations.

Why You Might Need Root

  • Package installation: apt install, yum install, etc.
  • System configuration: Editing /etc files, network setup
  • Service management: systemctl, starting daemons
  • Docker operations: Running Docker commands (Docker-in-Docker)

How to Enable Root Access

from agents.extensions.sandbox import RunloopUserParameters

user_parameters = RunloopUserParameters(username="root", uid=0)

options = RunloopSandboxClientOptions(
    user_parameters=user_parameters,
    # ...
)

Important Changes with Root Access

When running as root (uid=0):
  • Workspace root changes: /home/user/root
  • Update your manifest: Manifest(root="/root", ...)
  • HOME environment variable: Set to /root

Security Considerations

  • Use responsibly: Root access is powerful—use only when necessary
  • Sandboxes are isolated: Each sandbox is a separate VM, but still be cautious
  • Avoid in production: For production agents, prefer principle of least privilege
  • Audit commands: Review what commands the agent executes as root

Workspace Management

The workspace is the directory where your files and code live in the sandbox.

Default Workspace Roots

  • Standard user: /home/user
  • Root user: /root
The workspace root is automatically set based on the user, but you can override it in your manifest.

Creating Manifests

Use the Manifest class to define workspace contents:
from agents.sandbox import Manifest

manifest = Manifest(
    root="/home/user",  # or "/root" for root user
    entries={
        "config.json": {
            "type": "text",
            "text": '{"setting": "value"}\n',
        },
        "scripts/setup.sh": {
            "type": "text",
            "text": "#!/bin/bash\necho 'Setting up...'\n",
        },
        "data/": {
            "type": "directory",
        },
    },
)

Reading Files from the Sandbox

During execution, use the shell tool to read files:
# Agent instruction:
"Read the contents of /home/user/config.json and summarize it."
Or use the sandbox session API:
content = await session.read_file("/home/user/output.txt")

Writing Files to the Sandbox

Agents can write files using shell commands:
# Agent instruction:
"Create a file at /home/user/results.txt with the analysis results."
Or programmatically:
await session.write_file(
    "/home/user/output.txt",
    "Analysis complete\n",
)

Archive Operations

Download the entire workspace as a tarball:
archive_bytes = await session.read_workspace_archive()

# Save locally
with open("workspace.tar", "wb") as f:
    f.write(archive_bytes)
Upload a workspace archive:
with open("workspace.tar", "rb") as f:
    archive_bytes = f.read()

await session.write_workspace_archive(archive_bytes)

Troubleshooting

Common Issues and Solutions

”Runloop sandbox examples require the optional repo extra”

Problem: You see this error when trying to import Runloop classes. Solution: Install the Runloop extra:
uv sync --extra runloop
Verify with:
uv run python -c "from agents.extensions.sandbox import RunloopSandboxClient; print('OK')"

Authentication Errors

Problem: APIStatusError: 401 Unauthorized or similar authentication failures. Cause: Missing or incorrect RUNLOOP_API_KEY. Solution:
  1. Verify the environment variable is set:
    echo $RUNLOOP_API_KEY
    
  2. Check for trailing spaces or quotes:
    export RUNLOOP_API_KEY=your_key_here  # No quotes
    
  3. Regenerate the key in the Runloop dashboard if needed
  4. Ensure the key is active and not expired

Timeout Errors

Problem: PollingTimeout or operation timeout errors. Cause: Operation took longer than the configured timeout. Solutions:
  1. Increase the relevant timeout:
    from agents.extensions.sandbox import RunloopTimeouts
    
    timeouts = RunloopTimeouts(
        create_s=600,  # 10 minutes instead of 5
        exec_timeout_unbounded_s=3600,  # 1 hour instead of 24
    )
    
    options = RunloopSandboxClientOptions(timeouts=timeouts)
    
  2. Check the Runloop platform status
  3. Verify your network connection is stable
  4. Break large operations into smaller chunks

Blueprint Not Found

Problem: NotFoundError or “blueprint not found” when creating a sandbox. Cause: Invalid blueprint_name or blueprint_id. Solutions:
  1. Verify the blueprint name spelling:
    # Correct
    blueprint_name="runloop/universal-ubuntu-24.04-x86_64-dnd"
    
    # Incorrect (missing hyphen)
    blueprint_name="runloop/universal-ubuntu-24.04-x86_64dnd"
    
  2. Check available blueprints in your Runloop dashboard
  3. Remove the blueprint_name parameter to use the default blueprint
  4. Contact Runloop support if a specific blueprint should be available

Session Resume Failures

Problem: Cannot resume a suspended sandbox. Possible causes and solutions:
  1. pause_on_exit was not set:
    • Ensure pause_on_exit=True in the original RunloopSandboxClientOptions
    • Without this, the sandbox is deleted instead of suspended
  2. Serialized state is corrupted:
    • Verify the JSON is valid: json.loads(state_json)
    • Re-serialize from the original state if possible
  3. Sandbox was manually deleted:
    • Check the Runloop dashboard to see if the sandbox still exists
    • If deleted, you’ll need to create a new sandbox

Rate Limiting

Problem: APIStatusError: 429 Too Many Requests Cause: Exceeded Runloop API rate limits. Solutions:
  1. Add exponential backoff retry logic
  2. Space out sandbox creation requests
  3. Reuse existing sandboxes when possible
  4. Contact Runloop to discuss rate limit increases

Out of Credits

Problem: Sandbox creation fails with billing or quota errors. Cause: Runloop account is out of credits or missing billing information. Solutions:
  1. Check your credit balance in the Runloop dashboard
  2. Add more credits to your account
  3. Clean up unused suspended sandboxes to free resources

Additional Resources

Official Documentation

Example Code

Getting Help