Runtimes and configuration
Local runtimes (Docker, QEMU, Lume, Hyper-V, Tart, Android emulator), support checks, configuration, sign-in and errors.
Local sandboxes run on a runtime the SDK picks for the image. Pass one explicitly to Sandbox.create(..., runtime=...) to override it.
Support checks#
RuntimeSupport#
class RuntimeSupport
RuntimeSupport(
supported: bool,
hw_accel: bool,
runtime_installed: bool,
auto_installable: bool,
runtime_name: str,
host_os: str,
host_arch: str,
guest_os: str,
reason: str,
)Result of a local runtime compatibility check for a given Image.
| Attribute | Type | Description |
|---|---|---|
supported | bool | |
hw_accel | bool | |
runtime_installed | bool | |
auto_installable | bool | |
runtime_name | str | |
host_os | str | |
host_arch | str | |
guest_os | str | |
reason | str |
check_local_support#
def check_local_support(image: 'Image') -> RuntimeSupportReturn a RuntimeSupport describing whether image can run locally.
| Parameter | Type | Default | Description |
|---|---|---|---|
image | 'Image' | required | The Image to check. Use Image.linux(), Image.macos(), etc. |
Returns: RuntimeSupport with .supported, .hw_accel, .reason, and more.
skip_if_unsupported#
def skip_if_unsupported(image: 'Image', *, require_hw_accel: bool = False) -> NoneCall at the top of a pytest test/fixture to skip when the host can't run image.
Example:
async def test_macos_brew_install():
skip_if_unsupported(Image.macos())
async with Sandbox.ephemeral(Image.macos().brew_install("wget"), local=True) as sb:
assert (await sb.shell.run("wget --version")).success| Parameter | Type | Default | Description |
|---|---|---|---|
image | 'Image' | required | The Image the test intends to boot with local=True. |
require_hw_accel | bool | False | If True, also skip when hardware acceleration is unavailable (e.g. skip Android tests that would run in software emulation). |
Runtimes#
Runtime#
class Runtime(ABC)Base class for local runtimes that create VMs/containers from an Image.
Runtime.start#
async def start(image: Image, name: str, **opts) -> RuntimeInfoStart a VM/container and return connection info.
Runtime.stop#
async def stop(name: str) -> NoneStop and clean up a VM/container.
Runtime.is_ready#
async def is_ready(info: RuntimeInfo, timeout: float = 120) -> boolWait until the sandbox is ready.
Daemon-agnostic: "ready" is the runtime's own notion (instance running, plus any probe the caller declared), never a specific guest agent.
Runtime.suspend#
async def suspend(name: str) -> NoneSuspend (pause/save state of) a running VM.
Runtime.resume#
async def resume(image: 'Image', name: str, **opts) -> RuntimeInfoResume a suspended VM and return its RuntimeInfo.
Runtime.list#
async def list() -> list[dict]List known VMs managed by this runtime.
Returns a list of dicts with at minimum: name, status.
Runtime.ensure_base#
async def ensure_base(image: 'Image', base_name: str) -> CheckpointInfoPull/build image into a stopped base sandbox if not already present.
Idempotent. First call does the full pull; subsequent calls return immediately. The base sandbox is never started: it only serves as a fork source for ephemeral VMs.
Runtime.fork#
async def fork(base_name: str, new_name: str, **opts) -> NoneCreate a new sandbox from a base/checkpoint using CoW where possible.
Runtime.checkpoint#
async def checkpoint(name: str, checkpoint_name: str, **opts) -> CheckpointInfoCapture the current state of a running sandbox as a named checkpoint.
Runtime.list_checkpoints#
async def list_checkpoints() -> list[CheckpointInfo]Runtime.delete_checkpoint#
async def delete_checkpoint(checkpoint_name: str) -> NoneRuntimeInfo#
class RuntimeInfo
RuntimeInfo(
host: str,
api_port: int,
vnc_port: Optional[int] = None,
api_key: Optional[str] = None,
container_id: Optional[str] = None,
name: Optional[str] = None,
qmp_port: Optional[int] = None,
environment: Optional[str] = None,
agent_type: Optional[str] = None,
guest_server_port: int = 3211,
exposed_ports: Optional[dict] = None,
ssh_port: Optional[int] = None,
ssh_username: Optional[str] = None,
ssh_password: Optional[str] = None,
ssh_key_filename: Optional[str] = None,
vnc_host: Optional[str] = None,
vnc_password: Optional[str] = None,
grpc_port: Optional[int] = None,
native: Any = None,
env_ready_timeout: Optional[float] = None,
env_token: Optional[str] = None,
)Connection info returned after a runtime spins up.
| Attribute | Type | Description |
|---|---|---|
host | str | |
api_port | int | |
vnc_port | Optional[int] | |
api_key | Optional[str] | |
container_id | Optional[str] | |
name | Optional[str] | |
qmp_port | Optional[int] | |
environment | Optional[str] | |
agent_type | Optional[str] | |
guest_server_port | int | |
exposed_ports | Optional[dict] | |
ssh_port | Optional[int] | |
ssh_username | Optional[str] | |
ssh_password | Optional[str] | |
ssh_key_filename | Optional[str] | |
vnc_host | Optional[str] | |
vnc_password | Optional[str] | |
grpc_port | Optional[int] | |
native | Any | |
env_ready_timeout | Optional[float] | |
env_token | Optional[str] |
DockerRuntime#
class DockerRuntime(NativeRuntime)
DockerRuntime(
*,
api_port: int = DEFAULT_API_PORT,
vnc_port: int = DEFAULT_VNC_PORT,
ephemeral: bool = True,
volumes: Optional[list[str]] = None,
environment: Optional[dict[str, str]] = None,
devices: Optional[list[str]] = None,
platform: Optional[str] = None,
privileged: bool = False,
stop_timeout: int = 120,
cpus: Optional[int] = None,
memory_mb: Optional[int] = None,
server_port: Optional[int] = None,
)Runs OCI container images through the SDK's container backend.
api_port/vnc_port are accepted for compatibility; the SDK picks
free host ports and Sandbox.exposed_ports reports them.
volumes, devices, platform and privileged are not
supported by the sandboxed (gVisor) backend and are rejected.
| Attribute | Type | Description |
|---|---|---|
runtime_type | str | |
env_ready_timeout | float | |
delivers_guest_env | bool | |
api_port | int | |
vnc_port | int | |
stop_timeout | int |
DockerRuntime.start#
async def start(image: Image, name: str, **opts) -> RuntimeInfoQEMURuntime#
def QEMURuntime(mode: str = 'docker', **kwargs) -> RuntimeFactory that returns the appropriate QEMU runtime.
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | str | 'docker' | "docker" (default), "bare-metal", or "wsl2" |
QEMUDockerRuntime#
class QEMUDockerRuntime(NativeQEMURuntime)
QEMUDockerRuntime(
*,
api_port: int = DEFAULT_API_PORT,
vnc_port: int = QEMU_VNC_PORT,
ephemeral: bool = True,
storage_dir: Optional[str | Path] = None,
memory_mb: int = 8192,
cpu_count: int = 4,
)QEMURuntime(mode="docker"): kept for compatibility.
The trycua/cua-qemu-* wrapper images drove their guest through
computer-server, which is gone; Linux and Windows guests now boot on the
SDK's QEMU backend (the SDK provisions QEMU itself). Android guests use
cua_sandbox.runtime.AndroidEmulatorRuntime or bare-metal QEMU.
| Attribute | Type | Description |
|---|---|---|
api_port | int | |
vnc_port | int |
QEMUDockerRuntime.start#
async def start(image: Image, name: str, **opts) -> RuntimeInfoQEMUBaremetalRuntime#
class QEMUBaremetalRuntime(Runtime)
QEMUBaremetalRuntime(
*,
api_port: int = DEFAULT_API_PORT,
vnc_display: int = 0,
memory_mb: int = 4096,
cpu_count: int = 2,
arch: str = 'x86_64',
qmp_port: int = 4444,
use_qmp_transport: bool = False,
extra_args: Optional[list[str]] = None,
use_sdk: Optional[bool] = None,
)Bare-metal QEMU: launches qemu-system-* directly on the host.
Requires: - qemu-system-x86_64 (or qemu-system-aarch64) on PATH
- A disk image (qcow2/vhdx/raw) with computer-server pre-installed, provided via Image.from_file('/path/to/disk.qcow2')
use_sdk=False keeps the legacy in-process launcher, which boots
the given disk in place (the image builder needs that: the SDK always
puts an instance overlay on top of the disk).
| Attribute | Type | Description |
|---|---|---|
api_port | int | |
vnc_display | int | |
memory_mb | int | |
cpu_count | int | |
arch | str | |
qmp_port | int | |
use_qmp_transport | bool | |
extra_args | Optional[list[str]] | |
use_sdk | Optional[bool] |
QEMUBaremetalRuntime.start#
async def start(image: Image, name: str, **opts) -> RuntimeInfoQEMUBaremetalRuntime.stop#
async def stop(name: str) -> NoneQEMUBaremetalRuntime.is_ready#
async def is_ready(info: RuntimeInfo, timeout: float = 120) -> boolQEMUBaremetalRuntime.suspend#
async def suspend(name: str) -> NoneSave VM state via QMP savevm then quit QEMU.
QEMUBaremetalRuntime.resume#
async def resume(image: 'Image', name: str, **opts) -> RuntimeInfoRelaunch QEMU with -loadvm to restore saved state.
QEMUBaremetalRuntime.list#
async def list() -> list[dict]List known bare-metal QEMU sandboxes from state files, checking if alive.
QEMUWSL2Runtime#
class QEMUWSL2Runtime(Runtime)
QEMUWSL2Runtime(
*,
api_port: int = DEFAULT_API_PORT,
vnc_display: int = 0,
memory_mb: int = 4096,
cpu_count: int = 4,
arch: str = 'x86_64',
extra_args: Optional[list[str]] = None,
)QEMU via WSL2 with KVM hardware acceleration.
Runs qemu-system-x86_64 inside WSL2 where /dev/kvm is available, while keeping disk images on the Windows filesystem. Windows paths are automatically converted to /mnt/c/... paths for WSL access.
This is the fastest way to run QEMU on Windows: native KVM speeds vs TCG software emulation (bare-metal Windows) or Hyper-V (needs admin).
| Attribute | Type | Description |
|---|---|---|
api_port | int | |
vnc_display | int | |
memory_mb | int | |
cpu_count | int | |
arch | str | |
extra_args | Optional[list[str]] |
QEMUWSL2Runtime.available#
@staticmethod
def available() -> boolCheck if WSL2 + QEMU + KVM are available.
QEMUWSL2Runtime.start#
async def start(image: Image, name: str, **opts) -> RuntimeInfoQEMUWSL2Runtime.stop#
async def stop(name: str) -> NoneQEMUWSL2Runtime.is_ready#
async def is_ready(info: RuntimeInfo, timeout: float = 120) -> boolLumeRuntime#
class LumeRuntime(NativeRuntime)
LumeRuntime(
*,
lume_host: str = 'localhost',
lume_port: int = LUME_PROVIDER_PORT,
api_port: int = LUME_API_PORT,
ephemeral: bool = True,
cpus: Optional[int] = None,
memory_mb: Optional[int] = None,
)Runs macOS VMs via Lume, through the cua SDK.
| Attribute | Type | Description |
|---|---|---|
runtime_type | str | |
env_ready_timeout | float | |
lume_host | str | |
lume_port | int | |
api_port | int |
LumeRuntime.start#
async def start(image: Image, name: str, **opts) -> RuntimeInfoLumeRuntime.ensure_base#
async def ensure_base(image: Image, base_name: str)Pull an OCI image into a stopped golden base VM, if it doesn't exist yet.
Idempotent: if base_name already exists and is stopped, returns immediately. Raises if the base VM is running, because cloning a live VM can produce dirty or inconsistent clones. The base VM is never started; it only serves as a fork source.
LumeRuntime.fork#
async def fork(base_name: str, new_name: str, **opts) -> NoneClone a stopped VM base_name → new_name via APFS clonefile (instant).
The source VM must be stopped before calling this: cloning a running VM risks dirty state or clone failures.
LumeRuntime.checkpoint#
async def checkpoint(name: str, checkpoint_name: str, **opts)Stop name, clone it into a checkpoint, then restart name.
Stops, clones and restarts, so clones are always taken from a clean stopped state.
LumeRuntime.list_checkpoints#
async def list_checkpoints()Return VMs that are checkpoints (cua-base-* or cua-ckpt-* prefix).
LumeRuntime.delete_checkpoint#
async def delete_checkpoint(checkpoint_name: str) -> NoneDelete a checkpoint VM. Only operates on cua-base-* / cua-ckpt-* names.
LumeRuntime.list#
async def list() -> list[dict]List all Lume VMs.
HyperVRuntime#
class HyperVRuntime(Runtime)
HyperVRuntime(
*,
api_port: int = DEFAULT_API_PORT,
switch_name: str = 'Default Switch',
memory_mb: int = 4096,
cpu_count: int = 4,
disk_size_gb: int = 64,
cache_dir: Optional[Path] = None,
windows_iso: Optional[str] = None,
product_key: Optional[str] = None,
)Runs Windows VMs via Hyper-V with layered VHDX caching.
On first use, builds a base Windows image from ISO using unattended install (same Autounattend.xml as the QEMU builder). The base image includes CUA computer-server pre-installed. This is cached and reused.
Each sandbox session gets its own ephemeral differencing disk on top.
| Attribute | Type | Description |
|---|---|---|
api_port | int | |
switch_name | str | |
memory_mb | int | |
cpu_count | int | |
disk_size_gb | int | |
cache_dir | Optional[Path] | |
windows_iso | Optional[str] | |
product_key | Optional[str] |
HyperVRuntime.start#
async def start(image: Image, name: str, **opts) -> RuntimeInfoHyperVRuntime.stop#
async def stop(name: str) -> NoneStop the VM, remove it, and delete the ephemeral session disk.
HyperVRuntime.is_ready#
async def is_ready(info: RuntimeInfo, timeout: float = 120) -> boolDaemon-agnostic readiness: the VM is running and has an address.
start() already waited for the guest IP; the guest agent port is
a real TCP endpoint on the Hyper-V switch, so wait until it accepts a
connection (any daemon listening there), bounded by timeout.
TartRuntime#
class TartRuntime(Runtime)
TartRuntime(
*,
ephemeral: bool = True,
cpu_count: Optional[int] = None,
memory_mb: Optional[int] = None,
disk_gb: Optional[int] = None,
display: str = '1024x768',
ssh_username: str = TART_DEFAULT_USERNAME,
ssh_password: str = TART_DEFAULT_PASSWORD,
)Apple Virtualization.framework runtime via Tart CLI.
Uses VNC (host-side, random port) for screenshots and SSH for shell commands.
| Attribute | Type | Description |
|---|---|---|
ephemeral | bool | |
cpu_count | Optional[int] | |
memory_mb | Optional[int] | |
disk_gb | Optional[int] | |
display | str | |
ssh_username | str | |
ssh_password | str | |
vnc_host | Optional[str] | |
vnc_port | Optional[int] | |
vnc_password | Optional[str] |
TartRuntime.start#
async def start(image: Image, name: str, **opts) -> RuntimeInfoTartRuntime.is_ready#
async def is_ready(info: RuntimeInfo, timeout: float = 120) -> boolWait until SSH is reachable.
TartRuntime.stop#
async def stop(name: str) -> NoneAndroidEmulatorRuntime#
class AndroidEmulatorRuntime(Runtime)
AndroidEmulatorRuntime(
*,
api_level: int = 34,
img_type: str = 'google_apis',
device_id: str = 'pixel',
memory_mb: int = 4096,
cpu_count: int = 4,
adb_port: Optional[int] = None,
no_boot_anim: bool = True,
gpu_mode: str = 'swiftshader_indirect',
headless: bool = True,
grpc_port: Optional[int] = None,
)Android emulator runtime using the official Android SDK.
Creates an AVD and launches the Android emulator, following the docker-android pattern. Uses ADB for boot detection, APK install, and shell commands.
| Attribute | Type | Description |
|---|---|---|
api_level | int | |
img_type | str | |
device_id | str | |
memory_mb | int | |
cpu_count | int | |
adb_port | Optional[int] | |
no_boot_anim | bool | |
gpu_mode | str | |
headless | bool | |
grpc_port | Optional[int] |
AndroidEmulatorRuntime.start#
async def start(image: Image, name: str, **opts) -> RuntimeInfoAndroidEmulatorRuntime.is_ready#
async def is_ready(info: RuntimeInfo, timeout: float = 300) -> boolWait for sys.boot_completed == 1 via ADB, like docker-android.
AndroidEmulatorRuntime.list#
async def list() -> list[dict]List known Android emulator sandboxes from state files, checking if alive.
AndroidEmulatorRuntime.stop#
async def stop(name: str) -> NoneAndroidEmulatorRuntime.take_screenshot#
async def take_screenshot() -> bytesTake a screenshot via ADB screencap.
Configuration and errors#
Process-wide settings, sign-in helpers, transports and the typed errors of cua-sandbox.
Configuration and sign-in#
configure#
def configure(*, api_key: Optional[str] = None, base_url: Optional[str] = None) -> NoneSet global configuration for the legacy login() and whoami()
helpers.
login#
def login(*, base_url: Optional[str] = None) -> NoneOpen the CUA login page in a browser and store credentials.
This initiates a Clerk-based browser authentication flow. The user completes login in their browser, and the resulting API key is stored in ~/.cua/credentials.
whoami#
def whoami(*, api_key: Optional[str] = None) -> Dict[str, Any]Return info about the authenticated user.
Returns: Dict with user info (id, email, etc.) from the CUA API.
Errors#
SpacesdNotAvailable#
class SpacesdNotAvailable(RuntimeError)The sandbox has no reachable cua-spacesd.
Sandboxes are daemon-agnostic: creating, listing and deleting them never
needs a guest agent. The computer interfaces (screen, mouse,
shell, files ...) need cua-spacesd, or one of the agentless
transports (QMP, VNC, SSH, ADB) the runtime exposes.
InvalidArgument#
class InvalidArgument(ValueError)Contradictory or invalid options (the native SDK's
CuaError.InvalidArgument), for example on="local" together with
local=False.
InvalidPlacement#
class InvalidPlacement(InvalidArgument)A location, kind and runtime that do not go together (the native
SDK's CuaError.InvalidPlacement), for example kind="container"
with runtime="qemu". The message lists the valid values.
Unsupported#
class Unsupported(NotImplementedError)The operation is not supported for this sandbox, image or provider
(the native SDK's CuaError.Unsupported). A NotImplementedError,
so older except NotImplementedError handlers still catch it.
Transports#
EnvTransport#
class EnvTransport(Transport)Drives a sandbox through cua-spacesd.
| Parameter | Type | Default | Description |
|---|---|---|---|
url | Optional[str] | None | spacesd URL (http://host:3211 or a relay URL). Ignored when env_factory is given. |
token | Optional[str] | None | spacesd token, if the driver requires one. |
env_factory | Optional[EnvFactory] | None | coroutine returning a cua.SpacesdClient (used by local runtimes, which know how to reach the driver). |
environment | Optional[str] | None | OS hint (linux, windows, mac) used before the driver has been reached. |
fallback | Optional[Transport] | None | an agentless transport (QMP/VNC/SSH/ADB) to use when the sandbox has no spacesd. |
ready_timeout | float | 0.0 | how long to keep retrying the spacesd probe on first use (a freshly booted VM starts its driver late). 0 probes once. |
native_sandbox | Any | None | the cua.Sandbox handle, for port forwarding and named services. |
| Attribute | Type | Description |
|---|---|---|
url | Optional[str] |
EnvTransport.connect#
async def connect() -> NoneEnvTransport.disconnect#
async def disconnect() -> NoneEnvTransport.spacesd#
async def spacesd() -> AnyThe raw cua.SpacesdClient (typed client escape hatch).
EnvTransport.send#
async def send(action: str, **params: Any) -> AnyEnvTransport.screenshot#
async def screenshot(format: str = 'png', quality: int = 95) -> bytesEnvTransport.get_screen_size#
async def get_screen_size() -> Dict[str, int]EnvTransport.get_environment#
async def get_environment() -> strEnvTransport.get_display_url#
async def get_display_url(*, share: bool = False) -> strA browser URL for the sandbox's display.
With cua-spacesd (an env or viewer service), a link to its
HTML5 viewer carrying a scoped, expiring viewer ticket; the same
link serves share=True. For images without cua-spacesd, the page
of the first declared legacy web display service (novnc,
display, web; a raw RFB port 59xx is skipped, and
share=True returns its expiring public link), then the agentless
fallback's VNC address.
EnvTransport.request_service#
async def request_service(
name: str,
*,
method: str,
path: str,
json_body: Any = None,
headers: Any = None,
) -> AnyEnvTransport.native_handle#
async def native_handle() -> AnyThe cua.Sandbox handle behind this transport (services,
forwards, public URLs).
EnvTransport.native_service#
async def native_service(name: str) -> AnyEnvTransport.forward_tunnel#
async def forward_tunnel(sandbox_port: int | str) -> 'TunnelInfo'EnvTransport.close_tunnel#
async def close_tunnel(info: 'TunnelInfo') -> NoneEnvTransport.pty_create#
async def pty_create(
command: Optional[str] = None,
cols: int = 120,
rows: int = 40,
cwd: Optional[str] = None,
envs: Optional[Dict[str, str]] = None,
) -> Dict[str, Any]EnvTransport.pty_send#
async def pty_send(pid: int, data: str) -> NoneEnvTransport.pty_kill#
async def pty_kill(pid: int) -> boolEnvTransport.pty_info#
async def pty_info(pid: int) -> Optional[Dict[str, Any]]