Cua Docs

Create a sandbox pool with Python

Create a reusable warm sandbox pool, claim a sandbox, and run commands with the Cua Sandbox SDK.

Use the Cua Sandbox SDK when you want to create and claim a reusable sandbox pool from Python. This guide applies a one-replica pool, connects to a named claim, takes a screenshot, and runs a shell command.

Prerequisites#

  • Python >=3.11,<3.14.
  • uv to run the self-contained script.
  • A run.cua.ai access token or OAuth user key with permission to manage sandbox pools.

The SDK connects to https://run.cua.ai by default. Authenticate with a Fleet access token:

export FLEETS_TOKEN="<your-access-token>"

Or authenticate with OAuth client credentials:

export CUA_CLIENT_ID="<your-client-id>"
export CUA_CLIENT_SECRET="<your-client-secret>"

Set CUA_FLEET_BASE_URL only when you need to use a different Fleet API endpoint.

Create and claim the pool#

Save the following script as create_pool.py:

create_pool.py
# /// script
# requires-python = ">=3.11,<3.14"
# dependencies = [
#   "cua-sandbox",
# ]
# ///
 
import asyncio
import os
from pathlib import Path
 
from cua_sandbox import Image, Pool
 
 
IMAGE = (
    "296062593712.dkr.ecr.us-west-2.amazonaws.com/desktop-workspace-duo"
    "@sha256:5b9cb82f482834f7541901b87be956e7544d0db13fabc0b372cbc5eca5a74180"
)
 
POOL_NAME = os.environ.get("CUA_POOL_NAME", "cua-live-main-source-manual")
CLAIM_NAME = os.environ.get("CUA_CLAIM_NAME", POOL_NAME)
 
 
async def main() -> None:
    # Create or reconcile the warm pool and its template.
    pool = await Pool.apply(
        Image.from_registry(IMAGE),
        name=POOL_NAME,
        replicas=1,
        cpu=4,
        memory_mb=4096,
        services={"server": 8000},
    )
 
    # Create or reconnect to the named claim.
    # Exiting this block releases the claim but leaves the pool warm.
    async with pool.claim(
        name=CLAIM_NAME,
        service="server",
        time_to_start=900,
    ) as sandbox:
        print(f"Pool:    {sandbox.pool_name}")
        print(f"Claim:   {sandbox.claim_name}")
        print(f"Sandbox: {sandbox.name}")
 
        screenshot = Path("sandbox.png")
        screenshot.write_bytes(await sandbox.screenshot())
        print(f"Screenshot: {screenshot.resolve()}")
 
        result = await sandbox.shell.run("uname -a")
        if not result.success:
            raise RuntimeError(result.stderr)
 
        print(result.stdout.strip())
 
 
asyncio.run(main())

Run it with uv. The script metadata installs cua-sandbox in an isolated environment automatically:

uv run create_pool.py

Pool.apply() creates the pool and its sandbox template when they do not exist. If the named pool already exists, it reconciles the pool to the image, replica, CPU, memory, and service configuration in the script.

pool.claim() creates or reconnects to the named claim and waits up to 900 seconds for the server service on port 8000. Exiting the async with block releases the claim, but the pool and its one warm replica remain available for the next claim.

The script writes sandbox.png in the current directory and raises an error if uname -a does not complete successfully.

Choose pool and claim names#

The defaults use the same value for the pool and claim. Override either name without editing the script:

export CUA_POOL_NAME="my-sandbox-pool"
export CUA_CLAIM_NAME="my-sandbox-claim"
uv run create_pool.py

Use stable names when later runs should reconcile the same pool and reuse the same claim identity. Names must be lowercase DNS labels.

Delete the pool#

The example intentionally leaves the pool warm. If you no longer need it, call await pool.delete() after the claim context exits. This deletes the pool and the template created by that Pool.apply() call.