Skip to navigation

nemo_gym.sandbox.api

View as Markdown

Provider-neutral public sandbox API.

Module Contents

Classes

NameDescription
AsyncSandboxAsync sandbox object backed by a runtime provider.
AsyncSandboxSidecarA sidecar container of a started sandbox; get one with AsyncSandbox.sidecar(name).
SandboxSynchronous wrapper around AsyncSandbox.
SandboxPtyPTY namespace of a sandbox: await sandbox.pty.create(...) for a live
_AsyncLoopRunnerRun async sandbox operations for sync callers.

Functions

NameDescription
_missing_keywordsThe names in keywords that method does not take, none when it takes them all or **kwargs.
_pty_timeout_result-
_run_in_pty_sessionRun command in a live session, delimited by unique markers.
_sandbox_idThe provider-neutral id of a handle, or None for a handle-less test double.

Data

SANDBOX_PTY_RUNTIME_RETURN_CODE

SYNC_LOOP_CLOSE_TIMEOUT_S

SYNC_OPERATION_TIMEOUT_S

T

API

class nemo_gym.sandbox.api.AsyncSandbox(
provider: collections.abc.Mapping[str, typing.Any] | nemo_gym.sandbox.providers.SandboxProvider,
spec: nemo_gym.sandbox.providers.SandboxSpec | None = None,
owns_provider: bool = True
)

Async sandbox object backed by a runtime provider.

With owns_provider=False, the caller closes the shared provider after all of its sandboxes have stopped.

_cleanup_errors
list[str] = []
_closed_at
float | None = None
_created_at
float | None = None
_handle
SandboxHandle | None = None
_provider
_resource_attributes
dict[str, Any] = {}
_resource_sampler
ResourceSampler | None = None
cleanup_errors
tuple[str, ...]

Every failed provider close or client release, in order, as close:<type> or aclose:<type>.

default_user
str | int | None

The user an exec runs as when it names none: SandboxSpec.user, else the provider’s own default.

observed_client_interval_s
float | None

Client-observed seconds from the provider’s create return to its close return in this instance.

Not the remote sandbox lifetime or its CPU time; None unless this instance observed both returns.

provider_name
str

The name of this sandbox’s runtime provider.

pty
= SandboxPty(self)
sandbox_id
str | None

The provider’s id for this sandbox, or None before it has started or connected.

teardown_confirmed
bool | None

True once the provider’s close returned, False after only failed closes, None before any stop.

uploads_independent_of_execs
bool

Whether an upload can run while commands hold the provider’s capacity (SupportsSandboxIndependentUploads).

async
nemo_gym.sandbox.api.AsyncSandbox.__aexit__(
exc_type: typing.Any,
exc_val: typing.Any,
exc_tb: typing.Any
) -> None
async
nemo_gym.sandbox.api.AsyncSandbox._exec_uninstrumented(
command: str,
cwd: str | None = None,
env: dict[str, str] | None = None,
timeout_s: int | float | None = 180,
user: str | int | None = None,
preserve_background_services: bool = False
async
nemo_gym.sandbox.api.AsyncSandbox._require_handle() -> nemo_gym.sandbox.providers.SandboxHandle
nemo_gym.sandbox.api.AsyncSandbox._telemetry_provider_name() -> str

Provider name for span attributes (docker, daytona, opensandbox, …).

descriptor: collections.abc.Mapping[str, typing.Any] | typing.Any,
owns_provider: bool = True
asyncclassmethod

Rebuild a sandbox in this process from a descriptor produced by serialize, using provider (which must support connect).

nemo_gym.sandbox.api.AsyncSandbox.disconnect() -> None
async

Release this client without stopping a borrowed sandbox.

Use this only for a sandbox rebuilt with connect. The component that created the sandbox remains responsible for stopping it.

nemo_gym.sandbox.api.AsyncSandbox.download(
remote_path: str,
local_path: pathlib.Path | str
) -> None
async
nemo_gym.sandbox.api.AsyncSandbox.endpoint(
port: int
async

Resolve a declared sandbox service port without exposing provider state.

nemo_gym.sandbox.api.AsyncSandbox.exec(
command: str,
cwd: str | None = None,
env: dict[str, str] | None = None,
timeout_s: int | float | None = 180,
user: str | int | None = None,
preserve_background_services: bool = False,
span_attributes: collections.abc.Mapping[str, typing.Any] | None = None
async

Run a command, optionally preserving services needed by later commands.

preserve_background_services selects a provider’s service-preserving execution when available; other providers use ordinary exec. Services must redirect stdout and stderr. Per-command env and user overrides apply to the service-preserving execution too.

nemo_gym.sandbox.api.AsyncSandbox.pause() -> None
async

Pause this sandbox while preserving its state.

Open PTY sessions are detached; whether processes survive and sessions can be re-attached after resume() depends on the provider backend.

nemo_gym.sandbox.api.AsyncSandbox.resume() -> None
async

Resume this sandbox and wait until it is ready.

On timeout the server-side state is unknown: reconnect and check status() before retrying.

nemo_gym.sandbox.api.AsyncSandbox.serialize(
scope: str | None = None
) -> dict[str, typing.Any]
async

Return a JSON descriptor another process can rebuild this box from.

Requires a provider that supports the connect capability (the remote provider, or an external-control-plane provider such as OpenSandbox). For the remote provider, scope mints a co-lease (scope="operate").

nemo_gym.sandbox.api.AsyncSandbox.set_default_user(
user: str | int | None
) -> None

Change the default exec user of a running sandbox.

A Dockerfile overlay creates the task’s user inside the sandbox after it started, so the sandbox is created without one and switches to it once the user exists. serialize() carries the new value, so a harness that reattaches gets the same default.

nemo_gym.sandbox.api.AsyncSandbox.set_default_workdir(
workdir: str | None
) -> None

Change the default exec directory after sandbox setup has created it.

This does not create or change ownership of the directory. serialize() carries the new default, so a harness that reattaches executes in the same directory.

nemo_gym.sandbox.api.AsyncSandbox.sidecar(
name: str

Return a sidecar container declared in SandboxSpec.sidecars, to run commands in and read files from.

spec: nemo_gym.sandbox.providers.SandboxSpec | None = None,
span_attributes: collections.abc.Mapping[str, typing.Any] | None = None
async
spec: nemo_gym.sandbox.providers.SandboxSpec | None,
setup: collections.abc.Callable[[AsyncSandbox], collections.abc.Awaitable[None]]
async

Start the sandbox, then run setup against it.

If setup raises, the sandbox is stopped before the exception propagates.

nemo_gym.sandbox.api.AsyncSandbox.status() -> nemo_gym.sandbox.providers.SandboxStatus
async
nemo_gym.sandbox.api.AsyncSandbox.stop() -> None
async
nemo_gym.sandbox.api.AsyncSandbox.upload(
local_path: pathlib.Path | str,
remote_path: str
) -> None
async
class nemo_gym.sandbox.api.AsyncSandboxSidecar(
provider: nemo_gym.sandbox.providers.SupportsSandboxSidecars,
name: str
)

A sidecar container of a started sandbox; get one with AsyncSandbox.sidecar(name).

nemo_gym.sandbox.api.AsyncSandboxSidecar.download(
remote_path: str,
local_path: pathlib.Path | str
) -> None
async

Download one file from the sidecar’s filesystem.

nemo_gym.sandbox.api.AsyncSandboxSidecar.exec(
argv: str = (),
timeout_s: int | float,
on_stdout: collections.abc.Callable[[str], None] | None = None
async

Run argv without a shell (sidecar images can be distroless) and wait for it to exit.

on_stdout receives stdout text as it arrives, so a caller can follow a long-running service.

class nemo_gym.sandbox.api.Sandbox(
provider: collections.abc.Mapping[str, typing.Any] | nemo_gym.sandbox.providers.SandboxProvider,
spec: nemo_gym.sandbox.providers.SandboxSpec | None = None
)

Synchronous wrapper around AsyncSandbox.

pty, serialize and connect are async-only; use AsyncSandbox for those.

_async_sandbox
_runner
= _AsyncLoopRunner()
nemo_gym.sandbox.api.Sandbox.__del__() -> None
nemo_gym.sandbox.api.Sandbox.__exit__(
exc_type: typing.Any,
exc_val: typing.Any,
exc_tb: typing.Any
) -> None
nemo_gym.sandbox.api.Sandbox.download(
remote_path: str,
local_path: pathlib.Path | str
) -> None
nemo_gym.sandbox.api.Sandbox.endpoint(
port: int
nemo_gym.sandbox.api.Sandbox.exec(
command: str,
cwd: str | None = None,
env: dict[str, str] | None = None,
timeout_s: int | float | None = 180,
user: str | int | None = None,
preserve_background_services: bool = False
nemo_gym.sandbox.api.Sandbox.pause() -> None
nemo_gym.sandbox.api.Sandbox.resume() -> None
spec: nemo_gym.sandbox.providers.SandboxSpec | None = None
nemo_gym.sandbox.api.Sandbox.status() -> nemo_gym.sandbox.providers.SandboxStatus
nemo_gym.sandbox.api.Sandbox.stop() -> None
nemo_gym.sandbox.api.Sandbox.upload(
local_path: pathlib.Path | str,
remote_path: str
) -> None
class nemo_gym.sandbox.api.SandboxPty(
)

PTY namespace of a sandbox: await sandbox.pty.create(...) for a live session, await sandbox.pty.exec(...) for one-shot run-and-collect.

_default_session
SandboxPtySession | None = None
_session_exec_lock
= asyncio.Lock()
nemo_gym.sandbox.api.SandboxPty._exec_detached(
command: str,
cwd: str | None,
env: dict[str, str] | None,
user: str | int | None,
rows: int,
cols: int,
pty: bool,
timeout_s: int | float | None,
poll_interval_s: float
async

exec(detach=True): hand the command to the session’s detached runner, which holds the socket only for brief completion polls.

nemo_gym.sandbox.api.SandboxPty.attach(
session_id: str,
takeover: bool = True,
since: int | None = None
async

Re-attach to a session opened earlier, here or in another process.

Sessions outlive the client that opened them, so session.session_id is all another process needs. takeover evicts the current holder, whose session then fails with SandboxPtyError; without it, attaching to a held session fails. since replays retained output from that byte offset first (0 replays all of it).

nemo_gym.sandbox.api.SandboxPty.create(
command: str | None = None,
cwd: str | None = None,
env: dict[str, str] | None = None,
rows: int = 24,
cols: int = 80,
user: str | int | None = None,
pty: bool = True
async

Open an interactive terminal; the returned session carries read/read_stderr/write/resize/send_signal/ wait_exit/close and is an async context manager.

pty=False selects pipe mode: no TTY, stdout/stderr split across read()/read_stderr(). rows/cols are applied right after the terminal connects, because the backend has no spawn-time size, so a command that reads the size in its first moments can still see the 80x24 default; programs that honor SIGWINCH pick up the real size. Close the session before the sandbox stops: a session that outlives its sandbox fails subsequent reads with SandboxPtyError. Async-only; the sync Sandbox facade does not mirror it.

nemo_gym.sandbox.api.SandboxPty.exec(
command: str,
cwd: str | None = None,
env: dict[str, str] | None = None,
timeout_s: int | float | None = 180,
user: str | int | None = None,
rows: int = 24,
cols: int = 80,
pty: bool = True,
detach: bool = False,
poll_interval_s: float = 15.0
async

Run one command in a terminal session and collect its output.

Without session, the sandbox’s default-shell session — the oldest live one opened by create() with no command — is reused, provided the call sets none of the session-shaping arguments (cwd/env/user, non-default rows/cols, or pty=False), since those are fixed at create(). Custom-command and attached sessions run arbitrary programs, so they are only used when passed explicitly. When no default-shell session exists (or shaping arguments are given) a private session is opened for the command, drained and closed. Session-mode execs are serialized per sandbox: concurrent calls into one shared stream would corrupt it. With session the command runs in that live session, which stays open and keeps its shell state. In a live session the output also contains the shell’s echo of the command, stderr is best-effort (pipe mode only), and a command that ends the shell (exit) raises SandboxPtyError.

With detach=True the command runs without holding a connection while it works: it starts in a session, the socket is dropped, and the session is briefly re-attached every poll_interval_s to drain output and check for completion, so a long command occupies a connection for milliseconds per poll instead of its whole runtime (completion latency is bounded by poll_interval_s). Nothing is written to the sandbox filesystem; output rides the server’s retained window (~1 MiB) between polls, comes back as one merged stream (stderr is None), and exceeding the window raises rather than returning truncated output — run bulk-output commands attached or via the exec API instead. A detached exec never reuses the default-shell session implicitly: without session it opens a private one. With session, the session is detached while the command works, must not be used concurrently, and is attached and reusable again when this returns.

PTY mode returns all output on stdout and None stderr; pipe mode splits the two. A command that outlives timeout_s returns error_type="timeout" like sandbox.exec() rather than raising; in an explicitly passed session that command keeps running and leaves unread output behind, so discard the session rather than reusing it (an implicitly reused session is retired automatically).

class nemo_gym.sandbox.api._AsyncLoopRunner(
wait_timeout_s: float = SYNC_OPERATION_TIMEOUT_S,
close_timeout_s: float = SYNC_LOOP_CLOSE_TIMEOUT_S
)

Run async sandbox operations for sync callers.

_loop
= asyncio.new_event_loop()
_ready
= threading.Event()
_thread
nemo_gym.sandbox.api._AsyncLoopRunner._ensure_can_block(
operation: str
) -> None
nemo_gym.sandbox.api._AsyncLoopRunner._run_loop() -> None
nemo_gym.sandbox.api._AsyncLoopRunner._wait_for_result(
operation: str,
future: concurrent.futures.Future[nemo_gym.sandbox.api.T]
nemo_gym.sandbox.api._AsyncLoopRunner.call(
operation: str,
func: collections.abc.Callable[[], nemo_gym.sandbox.api.T]
nemo_gym.sandbox.api._AsyncLoopRunner.close() -> None
nemo_gym.sandbox.api._AsyncLoopRunner.run(
operation: str,
awaitable_factory: collections.abc.Callable[[], collections.abc.Awaitable[nemo_gym.sandbox.api.T]]
nemo_gym.sandbox.api._missing_keywords(
method: collections.abc.Callable[..., typing.Any],
keywords: collections.abc.Mapping[str, typing.Any]
) -> list[str]

The names in keywords that method does not take, none when it takes them all or **kwargs.

nemo_gym.sandbox.api._pty_timeout_result(
command: str,
timeout_s: float | int | None,
reusable: bool
nemo_gym.sandbox.api._run_in_pty_session(
command: str
async

Run command in a live session, delimited by unique markers.

nemo_gym.sandbox.api._sandbox_id(
handle: typing.Any
) -> str | None

The provider-neutral id of a handle, or None for a handle-less test double.

nemo_gym.sandbox.api.SANDBOX_PTY_RUNTIME_RETURN_CODE = 125
nemo_gym.sandbox.api.SYNC_LOOP_CLOSE_TIMEOUT_S = 5.0
nemo_gym.sandbox.api.SYNC_OPERATION_TIMEOUT_S = 3600.0
nemo_gym.sandbox.api.T = TypeVar('T')