nemo_gym.sandbox.api

View as Markdown

Provider-neutral public sandbox API.

Module Contents

Classes

NameDescription
AsyncSandboxAsync sandbox object backed by a runtime provider.
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
_pty_timeout_result-
_run_in_pty_sessionRun command in a live session, delimited by a unique marker.

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
)

Async sandbox object backed by a runtime provider.

_handle
SandboxHandle | None = None
_provider
pty
= SandboxPty(self)
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
) -> nemo_gym.sandbox.providers.SandboxExecResult
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, …).

nemo_gym.sandbox.api.AsyncSandbox.connect(
descriptor: collections.abc.Mapping[str, typing.Any] | typing.Any,
provider: nemo_gym.sandbox.providers.SandboxProvider
) -> nemo_gym.sandbox.api.AsyncSandbox
asyncclassmethod

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

nemo_gym.sandbox.api.AsyncSandbox.download(
remote_path: str,
local_path: pathlib.Path | str
) -> None
async
nemo_gym.sandbox.api.AsyncSandbox.endpoint(
port: int
) -> nemo_gym.sandbox.providers.SandboxEndpoint
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
) -> nemo_gym.sandbox.providers.SandboxExecResult
async
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.start(
spec: nemo_gym.sandbox.providers.SandboxSpec | None = None
) -> nemo_gym.sandbox.api.AsyncSandbox
async
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.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.providers.SandboxEndpoint
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
) -> nemo_gym.sandbox.providers.SandboxExecResult
nemo_gym.sandbox.api.Sandbox.start(
spec: nemo_gym.sandbox.providers.SandboxSpec | None = None
) -> nemo_gym.sandbox.api.Sandbox
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(
sandbox: nemo_gym.sandbox.api.AsyncSandbox
)

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,
session: nemo_gym.sandbox.providers.SandboxPtySession | None,
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
) -> nemo_gym.sandbox.providers.SandboxExecResult
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
) -> nemo_gym.sandbox.providers.SandboxPtySession
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
) -> nemo_gym.sandbox.providers.SandboxPtySession
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,
session: nemo_gym.sandbox.providers.SandboxPtySession | None = None,
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
) -> nemo_gym.sandbox.providers.SandboxExecResult
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.T
nemo_gym.sandbox.api._AsyncLoopRunner.call(
operation: str,
func: collections.abc.Callable[[], nemo_gym.sandbox.api.T]
) -> 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.T
nemo_gym.sandbox.api._pty_timeout_result(
command: str,
timeout_s: float | int | None,
reusable: bool
) -> nemo_gym.sandbox.providers.SandboxExecResult
nemo_gym.sandbox.api._run_in_pty_session(
session: nemo_gym.sandbox.providers.SandboxPtySession,
command: str
) -> nemo_gym.sandbox.providers.SandboxExecResult
async

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

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')