Skip to navigation

nemo_gym.sandbox.providers.opensandbox.provider

View as MarkdownOpen in Claude

OpenSandbox provider implementation.

Module Contents

Classes

NameDescription
OpenSandboxConnectionConfigOpenSandbox server connection settings.
OpenSandboxCreateConfigOpenSandbox create/reconnect retry settings.
OpenSandboxCreateOutcomePolicyCreate/reconnect defaults; pending fail-fast remains an explicit execution constraint.
OpenSandboxDiagnosticsConfigOptional control-plane checks after a command loses backend status.
OpenSandboxImagesConfigOperator image policy for every sandbox this provider creates.
OpenSandboxNetworkingConfigOperator assertion that direct sandbox IPs are mutually reachable.
OpenSandboxOperationConfigRetry and timeout settings for SDK operations after create.
OpenSandboxOutcomePolicyRuntime request policy; execution separately enforces replay safety and budgets.
OpenSandboxProbeConfigPost-create probe settings.
OpenSandboxProviderProvider backed by the OpenSandbox SDK/server API.
OpenSandboxProviderOptionsRecognized per-sandbox create options read from SandboxSpec.provider_options.
OpenSandboxRuntimeRequirementsConfigOperator-supplied capability probes and create-time runtime metadata.
OpenSandboxSetupConfigCommands run as root in every new sandbox before it is handed out.
OpenSandboxSharedStorageConfigShared host mount and optional deployment-specific placement metadata.
_MissingSandboxTerminateFilterHide the SDK warning emitted when terminate reaches its desired state.
_SandboxDeathNotice-

Functions

NameDescription
_exception_status_code-
_execution_outcomeMap a streamed SDK execution to (return_code, error_type, stderr_note).
_inspect_oom_evidenceRead only the current user container state from OpenSandbox inspect text.
_is_deleted_output_errorReturn whether a log read failed because execd’s output file for the command is gone.
_is_missing_sandbox_delete_errorMatch kill errors meaning the sandbox is already gone (terminate’s goal state).
_is_request_timeoutWhether an SDK create failure is the transport’s request timeout on the create request itself.
_is_retryable_create_errorSelect create recovery from observed facts and explicit boundary policy.
_is_terminal_delete_error-
_lifecycle_oom_evidenceRead the known controller’s persisted current-container report from the control plane.
_log_create_retry-
_log_operation_retry-
_metadata_map-
_metadata_value-
_normalize_spec-
_request_was_not_sentWhether the transport failed before the request left the client, so sending it again cannot run it twice.
_require_opensandbox_lifecycle_sdkThe SDK’s management-API pieces that work without a sandbox handle: list and delete.
_require_opensandbox_sdk-
_require_tenacity-
_resource_map-
_resource_quantity-
_retry_delayBound this request’s Retry-After without searching other failure events’ causes.
_root_launch_refusalRecognize execd’s pre-launch refusal, never user stdout/stderr or exception prose.
_sdk_error_attributes-
_string_map-
_to_image_spec-
_to_network_policy-
_to_platform_spec-
_to_sandbox_status-
_to_volumes-
split_domain_schemeSplit a configured OpenSandbox domain into (domain, scheme).
validate_image_pull_policyValidate a Kubernetes-compatible container image pull policy.

Data

CREATE_ATTEMPT_KEY

CREATE_BUDGET_SLACK_S

DEFAULT_IMAGE_PULL_POLICY

DELETED_OUTPUT_NOTE

IMAGE_PULL_POLICY_ANNOTATION_EXTENSION_KEY

IMAGE_PULL_POLICY_EXTENSION_KEY

LOGGER

OpenSandboxAttributionConfig

PENDING_SANDBOX_STATES

SDK_DEFAULT_REQUEST_TIMEOUT_S

SDK_DEFAULT_RESOURCE_LIMITS

STATUS_CODE_RE

VALID_IMAGE_PULL_POLICIES

_BUDGET_WARNINGS

_LIFECYCLE_SINGLE_POD_SUFFIX

_ROOT_LAUNCH_REFUSAL

_USER_CONTAINER

API

class nemo_gym.sandbox.providers.opensandbox.OpenSandboxConnectionConfig(
domain: str | None = None,
api_key: str | None = None,
protocol: str | None = None,
request_timeout_s: int | None = None,
use_server_proxy: bool = False,
disable_connection_pooling: bool = False,
keepalive_expiry_s: float | None = 3.0,
max_keepalive_connections: int = 20,
max_connections: int | None = 100,
connect_retries: int = 2,
transport_backend: str = 'aiohttp',
tls_verify: bool = False
)
Dataclass

OpenSandbox server connection settings.

With the legacy httpx backend, keepalive_expiry_s must stay below the server’s own keep-alive idle timeout (uvicorn defaults to 5s), or sockets are reused after the server has closed them; null falls back to the SDK’s default transport only when certificate verification is enabled and pooling is not disabled. transport_backend=aiohttp uses Gym’s global client and connector limits; provider-local pooling settings apply only to transport_backend=httpx. With the explicit legacy combination transport_backend=httpx, keepalive_expiry_s=None and tls_verify=True (pooling enabled), the SDK owns its default transport. SDK 0.1.15 then discards command-status 410 bodies; lifecycle evidence remains available independently of that response. tls_verify applies to every connection the provider opens (SDK transport and PTY sockets) and is off by default; set it for endpoints whose certificate the client can verify. domain may carry its scheme (https://sandbox.example). The scheme is moved into protocol and takes precedence over a configured protocol, so every URL the provider builds itself (the PTY WebSocket target) agrees with the SDK’s base URL; the SDK receives the host plus any path prefix (sandbox.example:8080/prefix). The SDK would accept a scheme in domain on its own, but the provider reads protocol directly, hence the normalization here. Only scheme://host[:port][/path-prefix] is accepted: a query string or fragment is a configuration error.

api_key
str | None = None
connect_retries
int = 2
disable_connection_pooling
bool = False
domain
str | None = None
keepalive_expiry_s
float | None = 3.0
max_connections
int | None = 100
max_keepalive_connections
int = 20
protocol
str | None = None
request_timeout_s
int | None = None
tls_verify
bool = False
transport_backend
str = 'aiohttp'
use_server_proxy
bool = False
nemo_gym.sandbox.providers.opensandbox.OpenSandboxConnectionConfig.__post_init__() -> None
class nemo_gym.sandbox.providers.opensandbox.OpenSandboxCreateConfig(
request_timeout_s: int | None = None,
timeout_s: float | None = None,
retries: int = 2,
retry_delay_s: float = 5.0,
retry_max_delay_s: float = 60.0,
fail_fast_on_pending: bool = False,
image_pull_policy: str | None = DEFAULT_IMAGE_PULL_POLICY,
skip_health_check: bool = False,
connect_attempt_timeout_s: float = 30.0,
connect_poll_s: float = 2.0,
renew_interval_s: float | None = None,
)
Dataclass

OpenSandbox create/reconnect retry settings.

connect_attempt_timeout_s
float = 30.0
connect_poll_s
float = 2.0
fail_fast_on_pending
bool = False
image_pull_policy
str | None = DEFAULT_IMAGE_PULL_POLICY
outcome_policy
OpenSandboxCreateOutcomePolicy
renew_interval_s
float | None = None
request_timeout_s
int | None = None
retries
int = 2
retry_delay_s
float = 5.0
retry_max_delay_s
float = 60.0
skip_health_check
bool = False
timeout_s
float | None = None
nemo_gym.sandbox.providers.opensandbox.OpenSandboxCreateConfig.__post_init__() -> None
class nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxCreateOutcomePolicy()

Bases: OpenSandboxOutcomePolicy

Create/reconnect defaults; pending fail-fast remains an explicit execution constraint.

by_failure_kind
dict[str, OutcomeAction]
class nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxDiagnosticsConfig(
inspect_oom: bool = False,
timeout_s: float = 5.0,
request_timeout_s: float = 2.0,
poll_interval_s: float = 0.5
)
Dataclass

Optional control-plane checks after a command loses backend status.

Background status recovery shares timeout_s across diagnostic I/O only; status requests and polling delays do not spend that diagnostic budget. Every lookup also stays within the original status-recovery deadline.

inspect_oom
bool = False
poll_interval_s
float = 0.5
request_timeout_s
float = 2.0
timeout_s
float = 5.0
nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxDiagnosticsConfig.__post_init__() -> None
class nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxImagesConfig(
rewrites: list[dict[str, str]] = list(),
auth: dict[str, dict[str, str]] = dict()
)
Dataclass

Operator image policy for every sandbox this provider creates.

rewrites are ordered {from, to} prefix rules (a mirror, or a derived image that adds what a deployment needs); auth maps an image prefix to {username, password}, longest prefix wins. Tasks and servers keep naming the published image; the deployment decides what is pulled.

auth
dict[str, dict[str, str]] = field(default_factory=dict)
rewrites
list[dict[str, str]] = field(default_factory=list)
nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxImagesConfig.resolve(
image: str | None
) -> tuple[str | None, dict[str, str] | None]
class nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxNetworkingConfig(
enabled: bool = False,
loopback_forwarding: bool = False,
python_executable: str = 'python3',
setup_command: str | None = None
)
Dataclass

Operator assertion that direct sandbox IPs are mutually reachable.

enabled
bool = False
loopback_forwarding
bool = False
python_executable
str = 'python3'
setup_command
str | None = None
class nemo_gym.sandbox.providers.opensandbox.OpenSandboxOperationConfig(
retries: int = 3,
retry_delay_s: float = 1.0,
retry_max_delay_s: float = 15.0,
command_retries: int = 0,
outcome_policy: nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxOutcomePolicy | collections.abc.Mapping[str, typing.Any] = OpenSandboxOutcomePolicy(),
command_timeout_headroom_s: float = 60.0,
status_recovery_timeout_s: float = 300.0,
exec_hard_cap_slack_s: float = 30.0,
close_timeout_s: float | None = 30.0,
pause_resume_timeout_s: float = 600.0,
background_exec: bool = False,
background_poll_initial_s: float = 0.25,
background_poll_interval_s: float = 2.0,
status_poll_timeout_s: float | None = 10.0
)
Dataclass

Retry and timeout settings for SDK operations after create.

background_exec
bool = False
background_poll_initial_s
float = 0.25
background_poll_interval_s
float = 2.0
close_timeout_s
float | None = 30.0
command_retries
int = 0
command_timeout_headroom_s
float = 60.0
exec_hard_cap_slack_s
float = 30.0
outcome_policy
OpenSandboxOutcomePolicy | Mapping[str, Any] = field(default_factory=OpenSandboxOutcomePolicy)
pause_resume_timeout_s
float = 600.0
retries
int = 3
retry_delay_s
float = 1.0
retry_max_delay_s
float = 15.0
status_poll_timeout_s
float | None = 10.0
status_recovery_timeout_s
float = 300.0
nemo_gym.sandbox.providers.opensandbox.OpenSandboxOperationConfig.__post_init__() -> None
class nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxOutcomePolicy()

Bases: OutcomePolicyConfig

Runtime request policy; execution separately enforces replay safety and budgets.

by_failure_kind
dict[str, OutcomeAction]
by_status_code
dict[int, OutcomeAction]
classification
FailureClassificationConfig = Field(default_factory=FailureClassificationConfig)
default_action
OutcomeAction = OutcomeAction.FAIL
supported_actions
frozenset[OutcomeAction]
class nemo_gym.sandbox.providers.opensandbox.OpenSandboxProbeConfig(
command: str | None = 'printf nemo-gym-sandbox-re...,
expected_stdout: str | None = 'nemo-gym-sandbox-ready',
timeout_s: int = 30,
deadline_s: float | None = None,
stable_count: int = 1,
stable_delay_s: float = 0.0,
user: str | int | None = None
)
Dataclass

Post-create probe settings.

command
str | None = 'printf nemo-gym-sandbox-ready'
deadline_s
float | None = None
expected_stdout
str | None = 'nemo-gym-sandbox-ready'
stable_count
int = 1
stable_delay_s
float = 0.0
timeout_s
int = 30
user
str | int | None = None
nemo_gym.sandbox.providers.opensandbox.OpenSandboxProbeConfig.__post_init__() -> None
class nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider(
connection: nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxConnectionConfig | collections.abc.Mapping[str, typing.Any] | None = None,
create: nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxCreateConfig | collections.abc.Mapping[str, typing.Any] | None = None,
probe: nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxProbeConfig | collections.abc.Mapping[str, typing.Any] | None = None,
operations: nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxOperationConfig | collections.abc.Mapping[str, typing.Any] | None = None,
diagnostics: nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxDiagnosticsConfig | collections.abc.Mapping[str, typing.Any] | None = None,
attribution: nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxAttributionConfig | collections.abc.Mapping[str, typing.Any] | None = None,
networking: nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxNetworkingConfig | collections.abc.Mapping[str, typing.Any] | None = None,
shared_storage: nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxSharedStorageConfig | collections.abc.Mapping[str, typing.Any] | None = None,
runtime_requirements: nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxRuntimeRequirementsConfig | collections.abc.Mapping[str, typing.Any] | None = None,
images: nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxImagesConfig | collections.abc.Mapping[str, typing.Any] | None = None,
setup: nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxSetupConfig | collections.abc.Mapping[str, typing.Any] | None = None
)

Provider backed by the OpenSandbox SDK/server API.

_attribution
_connection
_create
= _coerce_config(create, OpenSandboxCreateConfig)
_diagnostics
_images
= _coerce_config(images, OpenSandboxImagesConfig)
_networking
_operations
_probe
= _coerce_config(probe, OpenSandboxProbeConfig)
_pty_sessions
set[Any] = set()
_renewals
dict[str, Task[None]] = {}
_runtime_requirements
_setup
= _coerce_config(setup, OpenSandboxSetupConfig)
_shared_storage
_transport
Any | None = None
name
= 'opensandbox'
nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._await_sdk_call(
awaitable: typing.Any,
operation: str,
sandbox_id: str | None,
timeout_s: float | None
) -> typing.Any
async
nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._await_sdk_operation(
operation_factory: typing.Callable[[], typing.Awaitable[typing.Any]],
operation: str,
sandbox_id: str | None,
timeout_s: float | None,
retries: int | None = None,
is_retryable: typing.Callable[[BaseException], bool] | None = None,
replay_safe: bool = False
) -> typing.Any
async
nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._build_transport() -> typing.Any

Use Gym’s global HTTP pool, or the explicitly selected legacy backend.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._cleanup_failed_create_handle(
) -> None
async
nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._command_retry_count() -> int
nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._connect_after_create(
async

Reconnect after SDK create so follow-up calls use a fresh SDK handle.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._connection_config(
request_timeout_s: int | float | None = None
) -> typing.Any
nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._control_plane_inspect_oom_evidence(
deadline: float
) -> str | None
async

Opt-in OpenSandbox 1.1.0 text fallback; no supported structured equivalent is assumed.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._create_budget(
) -> tuple[float | None, int | float | None]

The per-attempt budget and the SDK request timeout for one create of spec.

The create request blocks until the pod is allocated, so a configured request timeout (create.request_timeout_s, else connection.request_timeout_s) is capped at the spec’s ready timeout: an unschedulable pod then fails within one ready timeout instead of the full request timeout. When none is configured the SDK’s own default stays, as the connection config the create is given lives on the handle for the sandbox’s life.

The SDK’s readiness wait starts only once the request returns, so the attempt budget must hold the request bound plus ready_timeout_s. A configured create.timeout_s is respected as is (a warning names it when it is below that sum); otherwise the budget is that sum plus a slack.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._create_once(
async

Create a sandbox through opensandbox.Sandbox.create.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._create_with_retries(
async
nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._delete_sandboxes_by_metadata(
scope: dict[str, str],
attempt_key: str
) -> list[dict[str, typing.Any]]
async
nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._exec(
command: str,
cwd: str | None = None,
env: dict[str, str] | None = None,
timeout_s: int | float | None = None,
user: str | int | None = None,
retries: int | None = None
async

Run a command inside an OpenSandbox sandbox.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._exec_background(
command: str,
opts_kwargs: dict[str, typing.Any],
sdk_timeout_s: float | None,
total_timeout_s: int | float | None,
retries: int
async

Run a command as a background execution polled via short requests.

The logs endpoint returns one combined stream, so unlike the foreground path stdout carries both streams and stderr is set only when the sandbox itself reports an error.

Gym enforces total_timeout_s itself: once its own deadline passes while the command is still running, it interrupts the command and returns the timeout result, as the session path does. execd reports its own timeout kill only as a signal death, so its timer is moved past Gym’s deadline and serves as a backstop.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._exec_setup_command(
command: str
async

One setup.commands entry as root, or as the image’s own user when the sandbox cannot switch identity.

An image with a non-root USER on a cell that drops CAP_SETUID refuses the switch before the command runs (nemo_gym.sandbox.transfer.exec_as_root degrades the same way), so a command written to no-op where it does not apply gets to no-op, and one that needs root there fails on its own result, as before.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._get_transport() -> typing.Any

Return the provider-owned shared transport, building it on first use.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._interrupt_at_deadline(
execution_id: str,
timeout_s: float,
request_timeout_s: float
) -> bool
async

Interrupt a background command still running at Gym’s deadline; whether the interrupt was accepted.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._kill_with_retry(
kill: typing.Callable[[], typing.Awaitable[None]],
sandbox_id: str
) -> None
async

Apply the same idempotent deletion and permission guards to handles and orphan cleanup.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._pty_failure(
cause: BaseException
async
nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._pty_http_client() -> typing.Any

Return the aiohttp client for one PTY session (same tls_verify as the SDK transport).

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._pty_session_missing(
base_url: str,
headers: dict[str, str],
session_id: str,
request_timeout_s: float | None
) -> bool
async

True only when execd itself reports the PTY session does not exist.

A proxy 404 (route not registered yet) lacks execd’s error code, and a failed check is treated as unknown so the attach proceeds as before.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._pty_target(
) -> tuple[str, dict[str, str], float | None]
async

Resolve the sandbox’s execd base URL, headers and request timeout.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._raise_timed_out_create(
cause: BaseException,
metadata: dict[str, str],
attempt_key: str,
how: str
) -> typing.NoReturn
async

Reap what a timed-out create left behind and raise the error that describes it.

Report provider_create_pending only for an observed Pending sandbox; otherwise report provider_create_timeout. The create policy chooses retry, with fail_fast_on_pending preventing another attempt for a Pending sandbox when configured.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._reap_orphaned_create(
metadata: dict[str, str],
attempt_key: str
) -> list[dict[str, typing.Any]]
async

Find and delete the sandbox a timed-out create request left behind; return what was found.

The request never returned, so the client holds no sandbox id: the sandbox is found through the run’s attribution metadata and this attempt’s unique key. Each match is deleted and described (id, state, reason, message) so the caller can tell a pod that never left PENDING from one that was merely slow. Failures are logged, never raised: the create’s own timeout error is the one the caller must see.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._resolve_extensions(
extensions: collections.abc.Mapping[str, str]
) -> dict[str, str]

Add the configured default image pull policy to SDK create extensions.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._retire_closed_pty_sessions() -> None
async

Release sessions that ended on their own; their aiohttp client is only freed by close(). Called from create/attach so the tracking set cannot grow without bound.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._run_setup_commands(
) -> None
async
nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._sandbox_death_evidence(
deadline: float,
poll: bool = True
async

One evidence path for exec and PTY, bounded by the caller’s existing deadline.

Lifecycle reasons do not identify the affected container, so even OOMKilled establishes only unavailability. A recognized control-plane container report or optional inspect can confirm OOM for the current user container.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._start_renewal(
ttl_s: int | float
) -> None
nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._stop_renewal(
sandbox_id: str
) -> BaseException | None
async
nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._submit_command(
operation_factory: typing.Callable[[], typing.Awaitable[typing.Any]],
operation: str,
sandbox_id: str,
timeout_s: float | None,
retries: int
) -> typing.Any
async

Submit once unless the request never left the client.

Neither a 502 nor a timeout proves non-delivery, and command retry settings cannot override that: the provider has no idempotency key or authoritative non-delivery receipt. A connection that never opened is that receipt, so such a failure, and only such a failure, is sent again within operations.retries.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._verify_created_handle(
) -> None
async
nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider._write_file(
target_path: str,
data: str | bytes
) -> None
async

Write one file into an OpenSandbox sandbox.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.aclose() -> None
async

Close provider-owned resources.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.attach_pty(
session_id: str,
takeover: bool = True,
since: int | None = None
async

Re-attach to an existing execd PTY session by id.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.close(
) -> None
async

Terminate the sandbox and close local SDK resources.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.configure_runtime(
cap_add: tuple[str, ...],
shm_size: int | None
) -> None
async

Probe capabilities and verify the shared memory allocated at creation.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.connect(
descriptor: collections.abc.Mapping[str, typing.Any]
async

Rebuild a live handle from an OpenSandbox sandbox id via the SDK.

Running sandboxes are health-checked unless the caller opts out. A paused sandbox has no exec daemon to check; resume rebuilds its endpoints and performs the health check instead.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.create(
async

Create one sandbox through the configured OpenSandbox path.

Job attribution keys (team / user / workload / run) are merged into the spec’s metadata (explicit spec keys win) so every sandbox is attributable via its labels.

async

Open an interactive execd PTY session inside a sandbox.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.download_file(
source_path: str,
target_path: pathlib.Path
) -> None
async

Download one file from an OpenSandbox sandbox, streamed to disk in chunks.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.endpoint(
port: int
async

Resolve one client-reachable direct or server-proxied service URL.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.exec(
command: str,
cwd: str | None = None,
env: dict[str, str] | None = None,
timeout_s: int | float | None = None,
user: str | int | None = None
async

Run a command inside an OpenSandbox sandbox.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.exec_with_background_services(
command: str,
cwd: str | None = None,
env: dict[str, str] | None = None,
timeout_s: int | float | None = None,
user: str | int | None = None
async

Preserve background services that redirect their stdout and stderr.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.forward_ports(
target_address: str,
ports: tuple[int, ...],
ready_file: str
) -> None
async

Run loopback listeners in a foreground command owned by the collection.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.network_address(
) -> str
async

Resolve a direct container IP, independently of client proxy mode.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.pause(
) -> None
async

Pause a sandbox and wait until it reports paused.

Local PTY clients are detached first, while execd can still answer the close handshake; server sessions are never deleted, so they remain attachable if the pause request fails. After resume, the Kubernetes backend has replaced the runtime (open a new PTY); the Docker backend thawed it (re-attach by id).

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.pull_policy_options(
policy: str
) -> dict[str, typing.Any]

Compose’s pull_policy as the per-sandbox image pull policy extension (_resolve_extensions lets a per-sandbox value win over the deployment default).

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.resource_allocation(
async

Read effective requests and limits from the deployment’s inspection endpoint.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.resource_sample(
) -> dict[str, str | int | float | bool]
async

Read execd’s existing snapshot; preserve its timestamp and measurement names.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.resume(
) -> None
async

Resume a paused sandbox and rebuild its SDK clients and endpoints.

One pause_resume_timeout_s deadline covers the request, endpoint rebuild and readiness check. On timeout the server-side state is unknown: reconnect and check status() before retrying. See pause() for what happens to PTY sessions.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.serialize_handle(
scope: str | None = None
) -> dict[str, typing.Any]
async

Return a descriptor for reattaching to this sandbox by id.

OpenSandbox sandboxes are reachable by id from any process that has the connection config, so the id alone is enough to reconnect and no sandbox server is needed to share one. scope is ignored: OpenSandbox has no lease concept of its own.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.set_hosts(
hosts: collections.abc.Mapping[str, str]
) -> None
async

Append validated peer aliases to the sandbox hosts file.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.shared_volume_metadata() -> dict[str, str]

Return placement metadata required by the configured shared storage.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.shared_volume_options(
source: str | None,
target: str,
read_only: bool = False
) -> dict[str, typing.Any]

Mount a project directory, or the shared root for bootstrap when source is None.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.status(
async

Return the current OpenSandbox lifecycle status.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.upload_file(
source_path: pathlib.Path,
target_path: str
) -> None
async

Upload one local file into an OpenSandbox sandbox.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.uploads_independent_of_execs() -> bool

A foreground command holds one of the shared HTTP pool’s connections for as long as it runs, so max_connections running commands leave an upload none; a background command is polled and holds none.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.validate_networking() -> None

Require explicit deployment support for inter-sandbox networking.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.validate_port_forwarding() -> None

Require explicit deployment support for loopback TCP listeners.

nemo_gym.sandbox.providers.opensandbox.OpenSandboxProvider.validate_runtime_requirements(
cap_add: tuple[str, ...],
shm_size: int | None
) -> dict[str, str]

Reject requirements without an operator-configured implementation.

class nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxProviderOptions(
image_auth: collections.abc.Mapping[str, typing.Any] | None = None,
network_policy: collections.abc.Mapping[str, typing.Any] | None = None,
platform: collections.abc.Mapping[str, typing.Any] | None = None,
snapshot_id: str | None = None,
volumes: tuple[collections.abc.Mapping[str, typing.Any], ...] = (),
skip_health_check: bool | None = None,
extensions: collections.abc.Mapping[str, str] = dict(),
resource_requests: collections.abc.Mapping[str, typing.Any] | typing.Literal['limits'] | None = None
)
Dataclass

Recognized per-sandbox create options read from SandboxSpec.provider_options.

image_auth, network_policy, platform, and volumes entries are passed through to the OpenSandbox SDK, so their inner fields are validated by the SDK rather than here.

extensions
Mapping[str, str] = field(default_factory=dict)
image_auth
Mapping[str, Any] | None = None
network_policy
Mapping[str, Any] | None = None
platform
Mapping[str, Any] | None = None
resource_requests
Mapping[str, Any] | Literal['limits'] | None = None
skip_health_check
bool | None = None
snapshot_id
str | None = None
volumes
tuple[Mapping[str, Any], ...] = ()
classmethod
class nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxRuntimeRequirementsConfig(
capability_probes: dict[str, str] = dict(),
capability_metadata: dict[str, dict[str, str]] = dict(),
shm_size_metadata_key: str | None = None
)
Dataclass

Operator-supplied capability probes and create-time runtime metadata.

capability_metadata
dict[str, dict[str, str]] = field(default_factory=dict)
capability_probes
dict[str, str] = field(default_factory=dict)
shm_size_metadata_key
str | None = None
class nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxSetupConfig(
commands: list[str] = list(),
timeout_s: float = 600.0,
env: dict[str, str] = dict()
)
Dataclass

Commands run as root in every new sandbox before it is handed out.

For repairs the deployment owns rather than the task: an end-of-life distro pointed at its archive, a tool the platform needs in every image. A failing command fails the create.

commands
list[str] = field(default_factory=list)
env
dict[str, str] = field(default_factory=dict)
timeout_s
float = 600.0
class nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxSharedStorageConfig(
host_path: str | None = None,
metadata: dict[str, str] = dict()
)
Dataclass

Shared host mount and optional deployment-specific placement metadata.

host_path
str | None = None
metadata
dict[str, str] = field(default_factory=dict)
class nemo_gym.sandbox.providers.opensandbox.provider._MissingSandboxTerminateFilter()

Bases: Filter

Hide the SDK warning emitted when terminate reaches its desired state.

nemo_gym.sandbox.providers.opensandbox.provider._MissingSandboxTerminateFilter.filter(
record: logging.LogRecord
) -> bool
class nemo_gym.sandbox.providers.opensandbox.provider._SandboxDeathNotice(
message: str,
failure_kind: str,
provider_status_reason: str | None = None
)
Dataclass
failure_kind
str
message
str
provider_status_reason
str | None = None
nemo_gym.sandbox.providers.opensandbox.provider._exception_status_code(
exception: BaseException
) -> int | None
nemo_gym.sandbox.providers.opensandbox.provider._execution_outcome(
execution: typing.Any,
operation: str,
sandbox_id: str,
execution_id: str | None = None
) -> tuple[int, str | None, str | None]

Map a streamed SDK execution to (return_code, error_type, stderr_note).

The SDK fills in exit_code=0 itself once execd sends its completion event. A stream that ends without a terminal event has an unknown outcome, even if it contains partial logs or an error without an exit status.

nemo_gym.sandbox.providers.opensandbox.provider._inspect_oom_evidence(
inspect: str
) -> str | None

Read only the current user container state from OpenSandbox inspect text.

nemo_gym.sandbox.providers.opensandbox.provider._is_deleted_output_error(
exception: BaseException
) -> bool

Return whether a log read failed because execd’s output file for the command is gone.

execd keeps background output under os.TempDir() (/tmp unless TMPDIR is set), so any command that empties /tmp deletes it, and the read fails with “error open combined output file for command <id>: … no such file or directory”. Rereading cannot bring it back.

nemo_gym.sandbox.providers.opensandbox.provider._is_missing_sandbox_delete_error(
exception: BaseException
) -> bool

Match kill errors meaning the sandbox is already gone (terminate’s goal state).

Only the terminate path may treat this as success; other operations must keep failing loudly on not-found.

nemo_gym.sandbox.providers.opensandbox.provider._is_request_timeout(
exception: BaseException
) -> bool

Whether an SDK create failure is the transport’s request timeout on the create request itself.

The request outlived the SDK’s request timeout, so the sandbox it asked for may exist without the client ever learning its id, exactly as when the provider’s own attempt budget fires. The SDK wraps transport errors (SandboxInternalException with the httpx timeout as its cause; newer SDKs raise a SandboxTimeoutException), so the cause chain is inspected. A connect or pool timeout sent no request and leaves nothing behind; a readiness timeout is raised with the id known and the sandbox already killed.

nemo_gym.sandbox.providers.opensandbox.provider._is_retryable_create_error(
exception: BaseException,
fail_fast_on_pending: bool = False
) -> bool

Select create recovery from observed facts and explicit boundary policy.

nemo_gym.sandbox.providers.opensandbox.provider._is_terminal_delete_error(
error: BaseException
) -> bool
nemo_gym.sandbox.providers.opensandbox.provider._lifecycle_oom_evidence(
info: typing.Any,
sandbox_id: str

Read the known controller’s persisted current-container report from the control plane.

Command-status 410 bodies can come from the sandbox’s exec daemon, even through the server proxy. Only GET /sandboxes/{id} supplies this lifecycle evidence. Unknown layouts, mismatched identities and ambiguous container reports fail closed.

nemo_gym.sandbox.providers.opensandbox.provider._log_create_retry(
retry_state: typing.Any
) -> None
nemo_gym.sandbox.providers.opensandbox.provider._log_operation_retry(
retry_state: typing.Any,
operation: str = '?',
sandbox_id: str | None = '?'
) -> None
nemo_gym.sandbox.providers.opensandbox.provider._metadata_map(
values: dict[str, typing.Any]
) -> dict[str, str]
nemo_gym.sandbox.providers.opensandbox.provider._metadata_value(
value: typing.Any
) -> str
nemo_gym.sandbox.providers.opensandbox.provider._normalize_spec(
nemo_gym.sandbox.providers.opensandbox.provider._request_was_not_sent(
exception: BaseException
) -> bool

Whether the transport failed before the request left the client, so sending it again cannot run it twice.

The innermost transport error in the cause chain decides, as in the SWE-bench Pro patch capture. The SDK wraps its client’s error, and Gym’s aiohttp transport re-raises every aiohttp connection error as httpx.ConnectError, a reset after the request was written included; the aiohttp error underneath says whether the connection ever opened. httpx raises ConnectError and ConnectTimeout only while it opens a connection, and PoolTimeout while it waits for one.

nemo_gym.sandbox.providers.opensandbox.provider._require_opensandbox_lifecycle_sdk() -> tuple[typing.Any, typing.Any]

The SDK’s management-API pieces that work without a sandbox handle: list and delete.

nemo_gym.sandbox.providers.opensandbox.provider._require_opensandbox_sdk() -> tuple[typing.Any, typing.Any, typing.Any, typing.Any, typing.Any]
nemo_gym.sandbox.providers.opensandbox.provider._require_tenacity() -> tuple[typing.Any, typing.Any, typing.Any, typing.Any]
nemo_gym.sandbox.providers.opensandbox.provider._resource_map(
) -> dict[str, str]
nemo_gym.sandbox.providers.opensandbox.provider._resource_quantity(
value: float | int
) -> str
nemo_gym.sandbox.providers.opensandbox.provider._retry_delay(
error: BaseException | None,
fallback: float,
max_delay_s: float
) -> float

Bound this request’s Retry-After without searching other failure events’ causes.

nemo_gym.sandbox.providers.opensandbox.provider._root_launch_refusal(
execution: typing.Any,
uid: int | None

Recognize execd’s pre-launch refusal, never user stdout/stderr or exception prose.

nemo_gym.sandbox.providers.opensandbox.provider._sdk_error_attributes(
exception: BaseException,
operation: str,
sandbox_id: str,
attempt_number: int | None = None,
max_attempts: int | None = None,
sleep_s: float | None = None
) -> dict[str, typing.Any]
nemo_gym.sandbox.providers.opensandbox.provider._string_map(
values: collections.abc.Mapping[str, typing.Any]
) -> dict[str, str]
nemo_gym.sandbox.providers.opensandbox.provider._to_image_spec(
image: str,
image_auth: collections.abc.Mapping[str, typing.Any] | None
) -> typing.Any
nemo_gym.sandbox.providers.opensandbox.provider._to_network_policy(
network_policy: collections.abc.Mapping[str, typing.Any]
) -> typing.Any
nemo_gym.sandbox.providers.opensandbox.provider._to_platform_spec(
platform: dict[str, typing.Any]
) -> typing.Any
nemo_gym.sandbox.providers.opensandbox.provider._to_sandbox_status(
state: typing.Any
nemo_gym.sandbox.providers.opensandbox.provider._to_volumes(
volumes: list[collections.abc.Mapping[str, typing.Any]]
) -> list[typing.Any]
nemo_gym.sandbox.providers.opensandbox.provider.split_domain_scheme(
domain: str
) -> tuple[str, str | None]

Split a configured OpenSandbox domain into (domain, scheme).

"https://sandbox.example:8080/prefix/" -> ("sandbox.example:8080/prefix", "https"); a bare host is returned unchanged with scheme None. Surrounding whitespace is stripped either way. Only scheme://host[:port][/path-prefix] is accepted: other schemes, a missing host, or a query string / fragment raise ValueError.

nemo_gym.sandbox.providers.opensandbox.provider.validate_image_pull_policy(
image_pull_policy: str
) -> str

Validate a Kubernetes-compatible container image pull policy.

nemo_gym.sandbox.providers.opensandbox.provider.CREATE_ATTEMPT_KEY = 'create-attempt'
nemo_gym.sandbox.providers.opensandbox.provider.CREATE_BUDGET_SLACK_S = 30.0
nemo_gym.sandbox.providers.opensandbox.provider.DEFAULT_IMAGE_PULL_POLICY = 'IfNotPresent'
nemo_gym.sandbox.providers.opensandbox.provider.DELETED_OUTPUT_NOTE = "OpenSandbox could not return this command's output: the output file was deleted...
nemo_gym.sandbox.providers.opensandbox.provider.IMAGE_PULL_POLICY_ANNOTATION_EXTENSION_KEY = 'opensandbox.extensions.image-pull-policy'
nemo_gym.sandbox.providers.opensandbox.provider.IMAGE_PULL_POLICY_EXTENSION_KEY = 'imagePullPolicy'
nemo_gym.sandbox.providers.opensandbox.provider.LOGGER = logging.getLogger(__name__)
nemo_gym.sandbox.providers.opensandbox.provider.OpenSandboxAttributionConfig = AttributionConfig
nemo_gym.sandbox.providers.opensandbox.provider.PENDING_SANDBOX_STATES = {'PENDING'}
nemo_gym.sandbox.providers.opensandbox.provider.SDK_DEFAULT_REQUEST_TIMEOUT_S = 30.0
nemo_gym.sandbox.providers.opensandbox.provider.SDK_DEFAULT_RESOURCE_LIMITS = SandboxResources.from_mapping({'cpu': 1, 'memory_mib': 2048})
nemo_gym.sandbox.providers.opensandbox.provider.STATUS_CODE_RE = re.compile('(?:status code|http)\\D+(\\d{3})', re.IGNORECASE)
nemo_gym.sandbox.providers.opensandbox.provider.VALID_IMAGE_PULL_POLICIES = {'Always', 'IfNotPresent', 'Never'}
nemo_gym.sandbox.providers.opensandbox.provider._BUDGET_WARNINGS: set[tuple[float, float, float]] = set()
nemo_gym.sandbox.providers.opensandbox.provider._LIFECYCLE_SINGLE_POD_SUFFIX = '-0'
nemo_gym.sandbox.providers.opensandbox.provider._ROOT_LAUNCH_REFUSAL = re.compile('fork/exec [^\\r\\n]+: operation not permitted(?: \\(switching to uid...
nemo_gym.sandbox.providers.opensandbox.provider._USER_CONTAINER = 'sandbox'