This file provides guidance to AI coding assistants when working with code in this repository.
Python client library for CoreWeave Sandbox - a remote code execution platform. The SDK provides a sync/async hybrid API for creating, managing, and executing code in containerized sandbox environments.
When adding, removing, or renaming public exports in src/cwsandbox/__init__.py, the API reference generator in the coreweave/docs repo needs its MANIFEST_GROUPS updated (in scripts/cwsandbox-api-ref/generate.py). Docstrings use Google style with Examples: and Attributes: sections for structured parsing by Griffe.
See DEVELOPMENT.md for setup, workflow, and all development tasks.
Sandbox (_sandbox.py): Main entry point with sync/async hybrid API. All methods return immediately; operations execute in background via _LoopManager.
Construction patterns:
# Factory method (recommended)
sb = Sandbox.run("echo", "hello") # Returns immediately
result = sb.exec(["echo", "more"]).result() # Block for result
sb.stop().result() # Block for completion
# Context manager (recommended for most use cases)
with Sandbox.run() as sb: # Default command keeps sandbox alive
result = sb.exec(["echo", "hello"]).result()
# Automatically stopped on exit
# Streaming output before getting result
with Sandbox.run() as sb:
process = sb.exec(["echo", "hello"])
for line in process.stdout: # Stream lines as they arrive
print(line, end="")
result = process.result() # Get final ProcessResult
# Async context manager
async with Sandbox.run() as sb:
result = await sb.exec(["echo", "hello"])Key methods:
run(*args, **kwargs): Create and start sandbox, return immediately. Accepts advanced configuration kwargs (see below).start(): Send start request, returnOperationRef[None]. Call.result()to block until backend accepts.wait(): Block until RUNNING status, returns self for chainingwait_until_complete(timeout=None, raise_on_termination=True): Wait until terminal state (COMPLETED, FAILED, TERMINATED), returnOperationRef[Sandbox]. Polls through TERMINATING automatically. Call.result()to block orawaitin async contexts. Setraise_on_termination=Falseto handle externally-terminated sandboxes without raisingSandboxTerminatedError.exec(command, cwd=None, check=False, timeout_seconds=None, stdin=False): Execute command, returnProcess. Call.result()to block forProcessResult. Iterateprocess.stdoutbefore.result()for real-time streaming. Setcheck=Trueto raiseSandboxExecutionErroron non-zero returncode. Setcwdto an absolute path to run the command in a specific working directory (implemented via shell wrapping, requires /bin/sh in container). Setstdin=Trueto enable stdin streaming viaprocess.stdin.shell(command=None, *, width=None, height=None): Start an interactive TTY session, returnTerminalSession. Always allocates a TTY and enables stdin. Output is raw bytes (merged stdout/stderr) with no buffering — safe for long-running interactive sessions. Defaults to["/bin/bash"].stream_logs(*, follow=False, tail_lines=None, since_time=None, timestamps=False): Stream logs from the sandbox's main process (PID 1), returnStreamReader[str]. Only captures stdout/stderr from the command passed toSandbox.run()— output fromexec()commands is not included. Setfollow=Truefor continuous streaming (liketail -f). Uses bounded queues for backpressure in follow mode.read_file(path): ReturnOperationRef[bytes]write_file(path, content): ReturnOperationRef[None]stop(snapshot_on_stop=False, graceful_shutdown_seconds=10.0, missing_ok=False, wait_for_ready=True, idempotency_key=None): Stop sandbox and returnOperationRef[None]. The sandbox transitions through TERMINATING (grace period) before reaching a terminal state (COMPLETED or FAILED). The returned OperationRef resolves when the backend confirms a terminal state, not just when the stop RPC succeeds. Multiple callers share the same stop task. RaisesSandboxErroron failure. Setsnapshot_on_stop=Trueto capture a file-system snapshot (FSS) of the configured mount before shutdown — the resulting ID is then available via thefile_system_snapshot_idproperty. Becausestop()coalesces concurrent callers onto one shared stop task, asnapshot_on_stop=Truerequest that would join (or observe) a stop not capturing a snapshot — sandbox already stopping/stopped, or a plainstop()already in flight — raisesSnapshotOnStopConflictErrorrather than completing with no archive; plain stops always coalesce.wait_for_ready/idempotency_keyapply only whensnapshot_on_stop=True(the client uses a larger timeout when snapshotting, since the stop blocks on the archive). Setmissing_ok=Trueto suppressSandboxNotFoundError.snapshot(wait_for_ready=True, idempotency_key=None): Capture a file-system snapshot (FSS) of the configured mount without stopping, returnOperationRef[str](the new snapshot's ID). CallSandbox.get_snapshot(id)for the full record. Requires the sandbox to have been started with afile_system_snapshotmount and the org to be enabled for FSS. Auto-starts the sandbox first if needed. Withwait_for_ready=True(default) blocks until the snapshot is READY/FAILED. To fork a sandbox,snapshot()thenSandbox.run(file_system_snapshot=FileSystemSnapshotOptions(..., file_system_snapshot_id=<id>)).get_status(): Fetch fresh status from API (sync). Returns cached status for terminal sandboxes (COMPLETED, FAILED, TERMINATED) since terminal states are immutable. TERMINATING is non-terminal and always fetches fresh status.
Properties:
status: Cached status from last API call (useget_status()for fresh)status_updated_at: When status was last fetchedsandbox_id,runner_id,profile_id,runner_group_id,returncode,started_atresource_requests,resource_limits- Confirmed resources from start response (None for discovered sandboxes)file_system_snapshot_id- Snapshot ID produced bystop(snapshot_on_stop=True)once the stop resolves (None otherwise)
Advanced configuration kwargs (for run(), Session.sandbox(), and @session.function()):
resources- Resource configuration viaResourceOptions, nested dict, or legacy flat dict (CPU, memory, GPU)mounted_files- Files to mount into the sandbox at startup (read-only at runtime; usewrite_file()for writable files)s3_mount- S3 bucket mount configurationports- Port mappings for the sandboxnetwork- Network configuration viaNetworkOptionsor dict (ingress/egress modes, exposed ports)file_system_snapshot- File-system snapshot (FSS) mount configuration viaFileSystemSnapshotOptionsor dict (mount_path, optionalsize, optionalfile_system_snapshot_idto restore/fork)secrets- Secrets to inject from secret stores as environment variables, viaSecretor dictmax_timeout_seconds- Maximum timeout for sandbox operationsenvironment_variables- Environment variables to inject (merges with defaults)annotations- Kubernetes pod annotations (merges with defaults, explicit keys win)
Class methods:
Sandbox.session(defaults): Create aSessionfor managing multiple sandboxes (sync)Sandbox.list(tags=None, status=None, profile_ids=None, profile_names=None, runner_ids=None, include_stopped=False, ...): Query existing sandboxes, returnOperationRef[list[Sandbox]]. Use.result()to block orawaitin async contexts. By default, terminal sandboxes (completed, failed, terminated) are excluded. Setinclude_stopped=Trueto include them.profile_namesandprofile_idsresolve independently; either or both may be supplied.Sandbox.from_id(sandbox_id): Attach to existing sandbox by ID, returnOperationRef[Sandbox]. Works for both active and stopped sandboxes.Sandbox.delete(sandbox_id, missing_ok=False): Delete sandbox by ID, returnOperationRef[None]. RaisesSandboxErroron failure. Setmissing_ok=Trueto suppressSandboxNotFoundErrorfor already-deleted sandboxes.Sandbox.get_snapshot(file_system_snapshot_id): Fetch aFileSystemSnapshotrecord by ID, returnOperationRef[FileSystemSnapshot]. Snapshots are org-scoped. RaisesSnapshotNotFoundErrorif absent.Sandbox.list_snapshots(source_sandbox_id=None, status=None, ...): List FSS records for the org, returnOperationRef[list[FileSystemSnapshot]](auto-paginated).source_sandbox_idandstatusare applied client-side.Sandbox.delete_snapshot(file_system_snapshot_id, missing_ok=False): Delete an FSS by ID, returnOperationRef[None]. Does not affect sandboxes already restored from it. Setmissing_ok=Trueto suppressSnapshotNotFoundError.Sandbox.get_snapshot_bucket_config()/Sandbox.set_snapshot_bucket_config(*, bucket_name, region=""): Get/set the org's FSS object-storage bucket (admin), returnOperationRef[FileSystemSnapshotBucketConfig]. Passbucket_name=""to revert to the CoreWeave-managed bucket.
Session (_session.py): Manages multiple sandboxes with shared defaults. Supports both sync and async context managers for the hybrid API.
Key methods:
session.sandbox(command, args, **kwargs)- create an unstarted sandbox with session defaults. Auto-starts on first operation (exec, read_file, write_file, wait). Accepts advanced configuration kwargs.session.function()- decorator for remote function executionsession.adopt(sandbox)- register an existing Sandbox (fromSandbox.list()orSandbox.from_id()) for cleanup when session closessession.close()- returnOperationRef[None]for cleanupsession.list(tags=None, status=None, profile_ids=None, profile_names=None, runner_ids=None, include_stopped=False, adopt=False)- find sandboxes matching session tags, returnOperationRef[list[Sandbox]]. Use.result()to block orawaitin async contexts. Setinclude_stopped=Trueto include terminal sandboxes.profile_idsandprofile_nameseach fall back to the matching session default independently.session.from_id(sandbox_id, adopt=True)- attach to existing sandbox by ID, returnOperationRef[Sandbox]
Properties:
sandbox_count: Number of sandboxes currently tracked by this session
Usage pattern:
with Session(defaults) as session:
sb = session.sandbox() # Default command keeps sandbox alive
result = sb.exec(["echo", "hello"]).result()
# Automatically cleans up all sandboxes on exitSandboxDefaults (_defaults.py): Immutable configuration dataclass. Tags propagate to backend for filtering.
Fields (all optional with sensible defaults):
container_image,command,args- Container configurationbase_url- API endpoint (default:https://api.cwsandbox.com)request_timeout_seconds- Client-side HTTP timeout (default: 300.0)max_lifetime_seconds- Server-side sandbox lifetime limit (default: None, backend controls)temp_dir- Sandbox temp directory (default:/tmp)tags- Tuple of tags for filteringprofile_ids,profile_names,runner_ids- Infrastructure filtering (optional tuples).profile_namesis the preferred form; both fields resolve independently through the None/empty/defaults precedenceresources- Resource configuration (ResourceOptions | dict[str, Any] | None)network- Network configuration viaNetworkOptionsfile_system_snapshot- FSS mount configuration viaFileSystemSnapshotOptions(shareable mount_path/size; explicitrun()value replaces it wholesale)secrets- Secrets to inject from secret stores (tuple ofSecret)environment_variables- Environment variables to injectannotations- Kubernetes pod annotations (dict[str, str], default: empty)
Utility methods:
merge_tags(additional)- Combine default tags with additional tags listmerge_annotations(additional)- Combine default annotations with additional dict (explicit keys win)merge_environment_variables(additional)- Combine default env vars with additional dict (explicit keys win)with_overrides(**kwargs)- Create new defaults with some values overridden
Key constants (from _defaults.py):
DEFAULT_CONTAINER_IMAGE = "python:3.11"DEFAULT_COMMAND = "/bin/sh",DEFAULT_ARGS = ("-c", 'trap "exit 0" TERM INT; sleep infinity & wait')- shell-trapped keep-alive so PID 1 responds to SIGTERM on stopDEFAULT_BASE_URL = "https://api.cwsandbox.com"DEFAULT_REQUEST_TIMEOUT_SECONDS = 300.0- Client-side HTTP timeoutDEFAULT_MAX_LIFETIME_SECONDS = None- Server controls sandbox lifetimeDEFAULT_GRACEFUL_SHUTDOWN_SECONDS = 10.0DEFAULT_TEMP_DIR = "/tmp"- Polling:
DEFAULT_POLL_INTERVAL_SECONDS = 0.2,DEFAULT_POLL_BACKOFF_FACTOR = 1.5,DEFAULT_MAX_POLL_INTERVAL_SECONDS = 2.0 DEFAULT_CLIENT_TIMEOUT_BUFFER_SECONDS = 5.0- Buffer added to exec timeout
OperationRef[T] (_types.py): Generic wrapper for async operations with lazy result retrieval. Bridges concurrent.futures.Future to asyncio for the sync/async hybrid API.
Key methods:
result(timeout=None)- Block until complete and return result__await__- Awaitable in async contexts
Usage pattern:
ref = sandbox.read_file("/path") # Returns immediately
data = ref.result() # Block when result needed
# Or in async context:
data = await refSandboxStatus (_sandbox.py): StrEnum for sandbox lifecycle states. Lifecycle: CREATING -> RUNNING -> TERMINATING -> COMPLETED | FAILED. Values: PENDING, CREATING, RUNNING, PAUSED, TERMINATING, COMPLETED, FAILED, TERMINATED (deprecated), UNSPECIFIED. TERMINATING is non-terminal: the sandbox is draining through its grace period. TERMINATED is deprecated in favor of the TERMINATING -> COMPLETED/FAILED flow but still emitted by older backends. Terminal statuses (used for caching and polling): COMPLETED, FAILED, TERMINATED. Methods from_proto() and to_proto() for protobuf conversion.
Exec Types (_types.py): Types for command execution, returned by Sandbox.exec():
Process: Handle for running process withstdout/stderrStreamReaders and optionalstdinStreamWriter. Properties:returncode(exit code or None),command(list executed),stdin(StreamWriter whenstdin=True, or None). Methods:poll(),wait(timeout),result(timeout),cancel(). Awaitable in async contexts.StreamReader: Dual sync/async iterable wrapping asyncio.Queue. Supports bothfor line in readerandasync for line in reader. Parameterized:StreamReader[str]for text (exec output, logs),StreamReader[bytes]for raw bytes (TTY output). Callclose()to stop the underlying producer and end iteration.StreamWriter: Writable stream for stdin. Methods:write(data: bytes),writeline(text: str),close(). All returnOperationRef[None]. Property:closed(bool). Uses bounded queue (16 items, ~1MB with 64KB chunks) for backpressure.ProcessResult: Dataclass withstdout,stderr,returncode,command, plus raw byte variants (stdout_bytes,stderr_bytes).
Terminal Types (_types.py): Types for interactive TTY sessions, returned by Sandbox.shell():
TerminalSession: Handle for an interactive TTY session. ExtendsOperationRef[TerminalResult]. Properties:output(StreamReader[bytes] — merged stdout/stderr as raw bytes),stdin(StreamWriter — always present),command(list executed). Methods:resize(width, height)(fire-and-forget),wait(timeout)(blocks until session ends, returns exit code),result(timeout)(returns TerminalResult). Awaitable in async contexts.TerminalResult: Frozen dataclass withreturncodeandcommand. UnlikeProcessResult, does not contain captured stdout/stderr because TTY sessions do not buffer output.
NetworkOptions (_types.py): Frozen dataclass for typed network configuration. Controls sandbox ingress and egress modes. The network parameter accepts either a NetworkOptions instance or a plain dict (which is automatically converted).
Fields:
ingress_mode: str | None- Inbound traffic mode. Available modes depend on the profile configurations of runners you have access to.exposed_ports: tuple[int, ...] | None- Ports to expose (required withingress_mode). Lists are normalized to tuples for immutability.egress_mode: str | None- Outbound traffic mode. Available modes depend on the profile configurations of runners you have access to.
Usage:
from cwsandbox import NetworkOptions
# Using NetworkOptions (recommended for type safety)
sandbox = Sandbox.run(
network=NetworkOptions(
ingress_mode="public",
exposed_ports=(8080,),
egress_mode="internet",
),
)
# Using dict (convenient for quick scripts)
sandbox = Sandbox.run(
network={"ingress_mode": "public", "exposed_ports": [8080]},
)Secret (_types.py): Frozen dataclass for injecting secrets from secret stores into sandbox environment variables. The secrets parameter accepts Secret instances or plain dicts (which are automatically converted via Secret(**d)).
Fields:
store: str- Name of the secret store (e.g."wandb").name: str- Name of the secret in the store.field: str- Specific field within a structured secret (optional, defaults to"").env_var: str | None- Environment variable name the secret is injected as (defaults toname).
Duplicate env_var targets across secrets raise ValueError at merge time.
Usage:
from cwsandbox import Secret
# Minimal: env_var defaults to name
sandbox = Sandbox.run(
secrets=[Secret(store="wandb", name="HF_TOKEN")],
)
# Extracting a field from a structured secret
sandbox = Sandbox.run(
secrets=[
Secret(store="wandb", name="db-credentials", field="password", env_var="DB_PASS"),
],
)
# Using dicts (convenient for config files)
sandbox = Sandbox.run(
secrets=[{"store": "wandb", "name": "HF_TOKEN"}],
)ResourceOptions (_types.py): Frozen dataclass for typed resource configuration. Supports separate requests and limits for Burstable QoS pods. GPU is a separate top-level field because GPU overcommit is not supported by the backend. The resources parameter accepts a ResourceOptions instance, a nested dict, or a legacy flat dict (which is automatically coerced).
Fields:
requests: dict[str, str] | None- CPU/memory resource requests (e.g.{"cpu": "1", "memory": "256Mi"})limits: dict[str, str] | None- CPU/memory resource limits (e.g.{"cpu": "8", "memory": "2Gi"})gpu: dict[str, Any] | None- GPU configuration (e.g.{"count": 1, "type": "A100"})
Usage:
from cwsandbox import ResourceOptions
# Using ResourceOptions (recommended for overcommit)
sandbox = Sandbox.run(
resources=ResourceOptions(
requests={"cpu": "1", "memory": "256Mi"},
limits={"cpu": "8", "memory": "2Gi"},
),
)
# Using nested dict
sandbox = Sandbox.run(
resources={"requests": {"cpu": "1"}, "limits": {"cpu": "8"}},
)
# Legacy flat dict (Guaranteed QoS - requests == limits)
sandbox = Sandbox.run(
resources={"cpu": "8", "memory": "2Gi"},
)File System Snapshots (FSS) (_types.py): A configured working directory can be snapshotted (on request or on stop) and restored into new sandboxes, letting you fork a sandbox's filesystem. FSS is gated per-organization on the backend; orgs that are not enabled get SnapshotNotSupportedError.
FileSystemSnapshotOptions: Frozen dataclass for the FSS mount. Fields:mount_path(absolute dir, required — this is the configured directory captured/restored),size(K8s quantity like"10Gi", optional),file_system_snapshot_id(optional — set to restore that snapshot intomount_pathat start). Accepts a plain dict. Passed asfile_system_snapshot=torun()/Session.sandbox()/@session.function()and settable onSandboxDefaults.FileSystemSnapshot: Frozen record returned byget_snapshot()andlist_snapshots()(snapshot()returns only the ID). Fields:file_system_snapshot_id,status(FileSystemSnapshotStatus),status_reason,size_bytes,source_sandbox_id,trigger(FileSystemSnapshotTrigger),idempotency_key,object_bucket,created_at/updated_at/completed_at.FileSystemSnapshotStatus: StrEnum —UNSPECIFIED,CREATING,READY,FAILED,DELETING.FileSystemSnapshotTrigger: StrEnum —UNSPECIFIED,STOP(fromstop(snapshot_on_stop=True)),MANUAL(fromsnapshot()).FileSystemSnapshotBucketConfig/FileSystemSnapshotBucketMode: Org bucket config (mode,bucket_name,region,effective_bucket_name); mode isUNSPECIFIED/CW_MANAGED/BRING_YOUR_OWN.
Usage:
from cwsandbox import Sandbox, FileSystemSnapshotOptions
# Start with a snapshot-capable mount and snapshot it (returns the ID)
with Sandbox.run(
file_system_snapshot=FileSystemSnapshotOptions(mount_path="/workspace", size="10Gi"),
) as sb:
sb.exec(["sh", "-c", "echo seed > /workspace/data.txt"]).result()
snapshot_id = sb.snapshot().result() # str (snapshot is READY)
# Fork = restore the snapshot into a fresh sandbox
with Sandbox.run(
file_system_snapshot=FileSystemSnapshotOptions(mount_path="/workspace", file_system_snapshot_id=snapshot_id),
) as restored:
...
# Snapshot on stop, then read the ID
sb.stop(snapshot_on_stop=True).result()
file_system_snapshot_id = sb.file_system_snapshot_id_auth.py implements a pluggable auth mode system with a single active mode:
CWSANDBOX_API_KEYenv var - Bearer token auth (built-in default)- No auth (built-in fallback)
Provider integrations (e.g. wandb.sandbox) can replace the active mode for the current process via set_auth_mode().
RemoteFunction[P, R]: Wrapper class returned by @session.function() decorator. Provides sync/async hybrid API for remote function execution.
Usage pattern:
with Session(defaults) as session:
@session.function()
def compute(x: int, y: int) -> int:
return x + y
# Call .remote() to execute in sandbox
ref = compute.remote(2, 3) # Returns OperationRef immediately
result = ref.result() # Block for result: 5
# Parallel execution across inputs
refs = compute.map([(1, 2), (3, 4), (5, 6)])
results = [r.result() for r in refs] # [3, 7, 11]
# Local testing without sandbox
result = compute.local(2, 3) # Runs in current processKey methods:
__call__(*args, **kwargs)- Execute in sandbox via.remote(), enabling naturalfunc(args)syntaxremote(*args, **kwargs)- Execute in sandbox, returnOperationRef[R]immediatelymap(items)- Execute for each item tuple in parallel, return list ofOperationRef[R]local(*args, **kwargs)- Execute locally without sandbox (for testing)
Configuration options (passed to decorator):
container_image- Override image for this function- Plus advanced configuration kwargs (see Sandbox section above)
Internals:
- Extracts function source via AST, removes the
@session.functiondecorator - Captures closure variables from
__closure__andco_freevars - Walks bytecode (
LOAD_GLOBAL,STORE_GLOBAL,DELETE_GLOBAL) to find referenced globals - Serializes payload as JSON, creates ephemeral sandbox, executes, reads JSON result
Arguments, closures, referenced globals, and return values must be
JSON-serializable (str, int, float, dict, list, bool, None). Non-JSON
values surface as a SandboxExecutionError from inside the sandbox.
_LoopManager: Singleton managing a background daemon thread with asyncio event loop. Enables sync code to execute async operations without user-managed event loops.
Key methods:
_LoopManager.get()- Get singleton instance (thread-safe, double-checked locking)run_sync(coro)- Execute coroutine and block until completerun_async(coro)- Execute coroutine and return Future immediatelyregister_session(session)- Track session in WeakSet for cleanupcleanup_all()- Stop all sandboxes in registered sessions
The daemon thread approach:
- Works in Jupyter notebooks without nest_asyncio
- Independent of user-managed event loops
- Allows cleanup via atexit and signal handlers
Auto-installed handlers for graceful sandbox shutdown on process exit. Installed automatically on module import.
_cleanup(): Calls_LoopManager.cleanup_all()with re-entrancy guard_signal_handler(): Handles SIGINT/SIGTERM, chains to original handlers_install_handlers(): Registers atexit handler and signal handlers_reset_for_testing(): Resets module state for test isolation
On first signal, performs cleanup then chains to original handler. On second signal during cleanup, forces immediate exit.
cwsandbox.results(): Block for one or more OperationRefs and return results.
# Single ref
data = cwsandbox.results(sandbox.read_file("/path"))
# Multiple refs
all_data = cwsandbox.results([sb.read_file(f) for f in files])cwsandbox.wait(): Wait for Sandbox, OperationRef, or Process objects to complete. Returns (done, pending) tuple.
# Wait for all sandboxes to be running
sandboxes = [Sandbox.run(...) for _ in range(5)]
done, pending = cwsandbox.wait(sandboxes)
# Wait for first N to complete
done, pending = cwsandbox.wait(refs, num_returns=2)
# Wait with timeout
done, pending = cwsandbox.wait(procs, timeout=30.0)Waitable: Type alias for objects that can be waited on: Sandbox | OperationRef[Any] | Process | TerminalSession.
Module-level sync functions (_discovery.py) for querying available runners and profiles. These are simple read-only queries that return results directly (no OperationRef/await needed).
Functions:
list_runners(*, runner_group_id=None, profile_name=None, gpu_type=None, architecture=None, include_resources=False, min_available_cpu_millicores=None, min_available_memory_bytes=None, min_available_gpu_count=None, service_exposure_mode=None, egress_mode=None)->list[Runner]: List available runners with optional filtering. Setinclude_resources=Truefor live resource availability (automatically enabled bymin_available_*filters). Theservice_exposure_modeandegress_modefilters require an additional profile fetch and check across all profiles on each runner. Auto-paginates.get_runner(runner_id)->Runner: Get a single runner by ID. Always returns full details including resources. RaisesRunnerNotFoundErrorif not found.list_profiles(*, gpu_type=None, architecture=None, runner_id=None, service_exposure_mode=None, egress_mode=None)->list[Profile]: List available profiles with optional filtering. Theservice_exposure_modeandegress_modefilters are applied client-side. Auto-paginates.get_profile(profile_name, *, runner_id=None)->Profile: Get a single profile by name, optionally scoped to a runner. RaisesProfileNotFoundErrorif not found.
Types:
Runner: Frozen dataclass with runner capabilities (CPU, memory, GPU), health status,profile_names, and optionalRunnerResources. Has human-readable__repr__.RunnerResources: Live resource availability (available_cpu_millicores,available_memory_bytes,available_gpu_count,running_sandboxes).Profile: Frozen dataclass withprofile_name,runner_id,supported_gpu_types,supported_architectures, andservice_exposure_modes/egress_modes.ServiceExposureMode,EgressMode: Wrapper dataclasses (withnamefield) for forward compatibility.
Utilities:
format_bytes(value): Format bytes as human-readable string (e.g.,17179869184->'16.0 GiB').format_cpu(millicores): Format CPU millicores (e.g.,4000->'4.0 vCPU').
Usage:
import cwsandbox
# List all runners with resources
runners = cwsandbox.list_runners(include_resources=True)
for r in runners:
print(r) # Human-readable repr
# Get a specific runner
runner = cwsandbox.get_runner("runner-123")
print(f"CPU: {cwsandbox.format_cpu(runner.max_cpu_millicores)}")
# List profiles, filter by GPU type
profiles = cwsandbox.list_profiles(gpu_type="A100")Note: profile_names from discovery map directly to SandboxDefaults(profile_names=[...]) (preferred) or SandboxDefaults(profile_ids=[...]) for backward compatibility. The backend unions both fields server-side, so either works; profile_names is clearer. The API reference generator in coreweave/docs repo needs MANIFEST_GROUPS updated in scripts/cwsandbox-api-ref/generate.py to include the new discovery types and functions.
Uses gRPC via grpcio with vendored proto stubs in src/cwsandbox/_proto/. The stubs (gateway_pb2, gateway_pb2_grpc, streaming_pb2, streaming_pb2_grpc) are updated via scripts/update-protos.sh.
Channel management (_network.py): Provides parse_grpc_target() for URL-to-target conversion and create_channel() for secure/insecure async channel creation. Auth headers are passed directly to streaming calls via metadata (interceptors don't work with request iterators).
Streaming exec: Uses native gRPC bidirectional streaming with request iterator pattern for proper half-close semantics via iterator completion.
- Backend: github.com/coreweave/sandbox - Server-side implementation (Go). Use
/repo-exploreto investigate backend behavior, API contracts, or debug client-server issues.
tests/unit/- Mock-based tests, no network calls. Default pytest path.tests/integration/- Real sandbox operations, requires auth. Run explicitly.
Unit test conftest clears all auth env vars before each test (autouse=True fixture).
Integration tests create real sandboxes and take significant time:
- Individual test: 5-15 seconds (sandbox startup + operation)
- Full suite: ~3 minutes total
- Sandbox startup: 30-60 seconds (mostly backend scheduling)
When running integration tests:
mise run test:e2e # Full suite (~2.5 minutes)
mise run test:e2e:parallel # Parallel execution (faster)
# Individual test with timeout
timeout 120 uv run pytest tests/integration/cwsandbox/test_sandbox.py::test_sandbox_lifecycle -vImportant: If integration tests hang beyond expected times, check:
- API patterns match current sync/async hybrid design (use
.result(), notawait) - Sandbox reaches RUNNING status before file operations
Tests should use the sync/async hybrid API:
# Correct pattern
def test_sandbox_example(sandbox_defaults: SandboxDefaults) -> None:
with Sandbox.run(defaults=sandbox_defaults) as sandbox:
result = sandbox.exec(["echo", "hello"]).result()
assert result.returncode == 0CWSandboxError
├── CWSandboxAuthenticationError
├── SandboxError
│ ├── SandboxNotRunningError
│ │ └── SandboxUnavailableError # transient service unavailability (gRPC UNAVAILABLE / AIP-193 UNAVAILABLE_REASONS)
│ │ └── SnapshotBackendThrottledError # transient FSS throttle/inflight cap (CWSANDBOX_FSS_BACKEND_THROTTLED / _INFLIGHT_LIMIT); retryable
│ │ # raw SandboxNotRunningError is also emitted for CANCELLED and local-stop paths
│ ├── SandboxTimeoutError
│ │ ├── SandboxRequestTimeoutError # gRPC request deadline (DEADLINE_EXCEEDED)
│ │ ├── SandboxCommandTimeoutError # user command exceeded its timeout (AIP-193 CWSANDBOX_COMMAND_TIMEOUT)
│ │ └── SnapshotWaitTimeoutError # wait_for_ready budget exceeded (CWSANDBOX_FSS_WAIT_TIMEOUT)
│ ├── SandboxResourceExhaustedError # backend resource pressure (gRPC RESOURCE_EXHAUSTED)
│ ├── SandboxTerminalStateUnavailableError # post-stop NOT_FOUND past retry budget (backend did not report terminal state)
│ ├── SandboxTerminatedError
│ ├── SandboxFailedError
│ ├── SandboxNotFoundError # .sandbox_id attribute
│ ├── SandboxExecutionError # .exec_result, .exception_type, .exception_message attributes
│ ├── SandboxFileError # .filepath attribute
│ └── SandboxSnapshotError # FSS failures; .file_system_snapshot_id attribute
│ ├── SnapshotNotFoundError # CWSANDBOX_FSS_NOT_FOUND
│ ├── SnapshotNotReadyError # CWSANDBOX_FSS_NOT_READY
│ ├── SnapshotNotSupportedError # CWSANDBOX_FSS_NOT_SUPPORTED (org not enabled for FSS)
│ ├── SnapshotSizeExceededError # CWSANDBOX_FSS_SIZE_EXCEEDED
│ ├── SnapshotQuotaExceededError # CWSANDBOX_FSS_QUOTA_EXCEEDED
│ └── SnapshotBucketMismatchError # CWSANDBOX_FSS_BUCKET_MISMATCH (reversible)
├── DiscoveryError
│ ├── RunnerNotFoundError # .runner_id attribute
│ └── ProfileNotFoundError # .profile_name, .runner_id attributes
└── FunctionError
└── AsyncFunctionError
Poll retry classification: The sandbox-status poll loop splits exception
classes into retryable and fatal, dispatched purely by isinstance against a
registry tuple. See _classify_poll_error and _RETRYABLE_POLL_EXCEPTIONS
in src/cwsandbox/_sandbox.py for the current membership. Retryable classes
are subclasses of the existing umbrella exceptions, so callers catching the
parent classes continue to work unchanged.
FSS RPC retries: The file-system snapshot RPCs (snapshot()/create,
get_snapshot, list_snapshots, delete_snapshot, bucket config) retry
transient errors with a bounded wall-clock budget
(DEFAULT_FSS_RETRY_BUDGET_SECONDS, default 30s; set to 0 to disable). They
reuse the poll loop's _classify_poll_error, so only the same retryable classes
are retried — transient unavailability, request-deadline, resource-exhaustion,
and FSS backend-throttling. Everything else (NOT_FOUND, FAILED_PRECONDITION,
quota/size, NOT_SUPPORTED) is fatal on the first attempt. Backoff is decorrelated
jitter honoring AIP-193 RetryInfo hints, via the shared _retry_transient_rpc
helper. snapshot() auto-generates an idempotency key when the caller omits one,
so a retried create dedups instead of producing a duplicate snapshot.
The examples/ directory contains runnable scripts demonstrating common patterns:
quick_start.py,basic_execution.py,streaming_exec.py,stdin_streaming.py- Sandbox creation and executionresource_configuration.py- ResourceOptions, flat dict, nested dict, GPU, and response propertiesfunction_decorator.py- Remote function execution with@session.function()multiple_sandboxes.py- Session-based parallel executioninteractive_streaming_sandbox.py- Log streaming withstream_logs()and CLI interaction (exec,sh,logs)reconnect_to_sandbox.py,async_patterns.py- Discovery and reconnectiondelete_sandboxes.py- Deletion patterns withSandbox.delete()file_system_snapshots.py- Snapshot/restore/fork withfile_system_snapshot,snapshot(), and snapshot managementerror_handling.py- Exception hierarchy and error recovery patternssession_adopt_orphans.py,cleanup_by_tag.py,cleanup_old_sandboxes.py- Orphan management and cleanupparallel_batch_job.py- Parallel batch processing with progress tracking
See examples/README.md and examples/AGENTS.md for full documentation. For detailed guides, see docs.coreweave.com.
Thread Safety: The sync API is designed for single-threaded use. Calling .result() from multiple threads simultaneously is not supported without external synchronization. Users wanting multi-threaded access should use one sandbox per thread or add their own locking. This is intentional to keep the implementation simple.
Lazy-Start Model: Sandbox.run() returns immediately once the backend accepts the request - it does NOT wait for RUNNING status. Blocking happens explicitly via .result() or .wait().
Single Internal Implementation: There is one async implementation internally. The sync/async flexibility comes from how users consume results (.result() vs await), not from duplicate code paths.
ResourceOptions and Overcommit: GPU is a separate field from requests/limits because the backend does not support GPU overcommit. Flat dict backward compatibility is maintained for CPU/memory (legacy form sets requests == limits for Guaranteed QoS). The flat dict form for GPU ({"gpu_count": N}) is a breaking change - use ResourceOptions(gpu={"count": N}) instead. The resource_requests and resource_limits properties are populated only from start-response data, so they are None for sandboxes discovered via Sandbox.from_id() or Sandbox.list().
All new files MUST include an SPDX license header. See CONTRIBUTING.md for full policy.
License by directory:
- Everything:
Apache-2.0 examples/:BSD-3-Clause
Python files (.py):
# SPDX-FileCopyrightText: 2025 CoreWeave, Inc.
# SPDX-License-Identifier: Apache-2.0
# SPDX-PackageName: cwsandbox-clientMarkdown files (.md):
<!--
SPDX-FileCopyrightText: 2025 CoreWeave, Inc.
SPDX-License-Identifier: Apache-2.0
SPDX-PackageName: cwsandbox-client
-->Use BSD-3-Clause instead for files under examples/. Validate with reuse lint.
When creating temporary analysis or planning documents, use these filename suffixes to ensure they are gitignored:
| Suffix | Use Case |
|---|---|
-OLD.md |
Superseded or archived versions of documents |
-draft.md |
Work in progress, not ready for review |
-tmp.md |
Temporary files for single-session analysis |
-notes.md |
Personal analysis notes |
Example: docs/api-redesign-draft.md or docs/spec-sync-api-OLD.md
Files with these suffixes are excluded from git via .gitignore. For permanent documentation, use clear names without temporary markers.