nemo_gym.sandbox.providers.openshell.provider

View as Markdown

OpenShell sandbox provider: sandboxes managed by an OpenShell gateway (github.com/NVIDIA/OpenShell).

The provider talks to the gateway’s gRPC control plane through the synchronous openshell SDK; blocking SDK calls run on a thread pool bounded by exec.concurrency. The client and thread pool are cached at module scope keyed on the connection config, so concurrent sandboxes created from identical provider configs share one gRPC channel and one pool instead of allocating one per sandbox. The SDK has no file-transfer API, so uploads stream bytes through exec stdin (chunked to stay under the gateway’s gRPC message size limit) and downloads round-trip through base64 on the sandbox’s stdout.

Module Contents

Classes

NameDescription
OpenShellConnectionConfigGateway connection settings. Defaults target a local plaintext gateway (deploy/docker compose).
OpenShellCreateConfig-
OpenShellCreateErrorRaised when the OpenShell gateway cannot create a sandbox.
OpenShellCreateVerificationErrorRaised when a new sandbox fails its readiness probe.
OpenShellExecConfig-
OpenShellOperationsConfig-
OpenShellProbeConfig-
OpenShellProviderSandbox provider backed by an OpenShell gateway’s gRPC control plane.
OpenShellProviderOptionsValidated per-sandbox options carried in SandboxSpec.provider_options.
_OpenShellSandbox-
_SharedClientStateOne gRPC client + worker pool shared by every provider with the same connection config.

Functions

NameDescription
_acquire_shared_client-
_build_client-
_coerce_config-
_grpc_status_codeThe grpc.StatusCode of an RPC error, else None.
_is_already_exists-
_is_grpc_error-
_is_grpc_timeout-
_is_not_found-
_is_retryable_create_errorWhether a CreateSandbox RPC failure is likely transient (safe to retry with the same name).
_is_runtime_failure-
_is_sdk_error-
_normalize_image-
_phase-
_phase_to_status_mapSandboxPhase -> SandboxStatus, built from the SDK’s generated proto constants.
_release_shared_client-
_require_openshell-

Data

DEFAULT_UPLOAD_CHUNK_BYTES

LOGGER

READY_PROBE_COMMAND

READY_PROBE_EXPECTED

SANDBOX_LABEL

SANDBOX_NAME_PREFIX

SANDBOX_RUNTIME_RETURN_CODE

_SHARED_CLIENTS

_SHARED_CLIENTS_LOCK

API

class nemo_gym.sandbox.providers.openshell.provider.OpenShellConnectionConfig(
endpoint: str = 'localhost:8080',
workspace: str = 'default',
bearer_token: str | None = None,
tls_ca_path: str | None = None,
tls_cert_path: str | None = None,
tls_key_path: str | None = None,
request_timeout_s: float = 30.0
)
Dataclass

Gateway connection settings. Defaults target a local plaintext gateway (deploy/docker compose).

bearer_token
str | None = None
endpoint
str = 'localhost:8080'
request_timeout_s
float = 30.0
tls_ca_path
str | None = None
tls_cert_path
str | None = None
tls_key_path
str | None = None
workspace
str = 'default'
nemo_gym.sandbox.providers.openshell.provider.OpenShellConnectionConfig.__post_init__() -> None
class nemo_gym.sandbox.providers.openshell.provider.OpenShellCreateConfig(
ready_timeout_s: float = 300,
poll_interval_s: float = 1.0,
retries: int = 2,
retry_delay_s: float = 1.0,
retry_max_delay_s: float = 30.0
)
Dataclass
poll_interval_s
float = 1.0
ready_timeout_s
float = 300
retries
int = 2
retry_delay_s
float = 1.0
retry_max_delay_s
float = 30.0
nemo_gym.sandbox.providers.openshell.provider.OpenShellCreateConfig.__post_init__() -> None
class nemo_gym.sandbox.providers.openshell.provider.OpenShellCreateError()

Bases: SandboxCreateError

Raised when the OpenShell gateway cannot create a sandbox.

class nemo_gym.sandbox.providers.openshell.provider.OpenShellCreateVerificationError()

Bases: SandboxCreateVerificationError

Raised when a new sandbox fails its readiness probe.

class nemo_gym.sandbox.providers.openshell.provider.OpenShellExecConfig(
default_timeout_s: float | None = 180,
concurrency: int = 32,
exec_shell: str = '/bin/sh',
upload_chunk_bytes: int = DEFAULT_UPLOAD_CHUNK_BYTES
)
Dataclass
concurrency
int = 32
default_timeout_s
float | None = 180
exec_shell
str = '/bin/sh'
upload_chunk_bytes
int = DEFAULT_UPLOAD_CHUNK_BYTES
nemo_gym.sandbox.providers.openshell.provider.OpenShellExecConfig.__post_init__() -> None
class nemo_gym.sandbox.providers.openshell.provider.OpenShellOperationsConfig(
close_wait_deleted: bool = True,
close_timeout_s: float = 60,
poll_interval_s: float = 1.0
)
Dataclass
close_timeout_s
float = 60
close_wait_deleted
bool = True
poll_interval_s
float = 1.0
nemo_gym.sandbox.providers.openshell.provider.OpenShellOperationsConfig.__post_init__() -> None
class nemo_gym.sandbox.providers.openshell.provider.OpenShellProbeConfig(
command: str | None = READY_PROBE_COMMAND,
expected_stdout: str | None = READY_PROBE_EXPECTED,
timeout_s: int = 30,
deadline_s: float | None = 60,
stable_count: int = 1,
stable_delay_s: float = 1.0
)
Dataclass
command
str | None = READY_PROBE_COMMAND
deadline_s
float | None = 60
expected_stdout
str | None = READY_PROBE_EXPECTED
stable_count
int = 1
stable_delay_s
float = 1.0
timeout_s
int = 30
nemo_gym.sandbox.providers.openshell.provider.OpenShellProbeConfig.__post_init__() -> None
class nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider(
connection: nemo_gym.sandbox.providers.openshell.provider.OpenShellConnectionConfig | collections.abc.Mapping[str, typing.Any] | None = None,
create: nemo_gym.sandbox.providers.openshell.provider.OpenShellCreateConfig | collections.abc.Mapping[str, typing.Any] | None = None,
exec: nemo_gym.sandbox.providers.openshell.provider.OpenShellExecConfig | collections.abc.Mapping[str, typing.Any] | None = None,
probe: nemo_gym.sandbox.providers.openshell.provider.OpenShellProbeConfig | collections.abc.Mapping[str, typing.Any] | None = None,
operations: nemo_gym.sandbox.providers.openshell.provider.OpenShellOperationsConfig | collections.abc.Mapping[str, typing.Any] | None = None
)

Sandbox provider backed by an OpenShell gateway’s gRPC control plane.

_connection
_create_config
= _coerce_config(create, OpenShellCreateConfig)
_exec_config
= _coerce_config(exec, OpenShellExecConfig)
_operations
_probe
= _coerce_config(probe, OpenShellProbeConfig)
_shared
name
= 'openshell'
nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider._build_policy(
policy: str | collections.abc.Mapping[str, typing.Any]
) -> typing.Any
nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider._build_sandbox_spec(
spec: nemo_gym.sandbox.providers.base.SandboxSpec,
image: str | None,
options: nemo_gym.sandbox.providers.openshell.provider.OpenShellProviderOptions
) -> typing.Any
nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider._call(
func: typing.Any,
args: typing.Any = (),
kwargs: typing.Any = {}
) -> typing.Any
async
nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider._cleanup_failed_create_handle(
handle: nemo_gym.sandbox.providers.base.SandboxHandle
) -> None
async
nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider._create_sandbox_with_retries(
pb_spec: typing.Any,
name: str,
labels: dict[str, str]
) -> typing.Any
async

Issue CreateSandbox, retrying transient gRPC failures with the same name.

Retrying with the same name is safe: if an earlier attempt actually committed, the retry fails ALREADY_EXISTS and the sandbox is recovered via GetSandbox.

nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider._verify_created_handle(
handle: nemo_gym.sandbox.providers.base.SandboxHandle
) -> None
async

Poll the readiness probe until it passes stable_count times or the deadline elapses.

nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider._wait_deleted(
inst: nemo_gym.sandbox.providers.openshell.provider._OpenShellSandbox
) -> None
async

Poll GetSandbox until NOT_FOUND (transient RPC failures keep polling until the deadline).

nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider._wait_ready(
handle: nemo_gym.sandbox.providers.base.SandboxHandle,
timeout_s: int | float
) -> None
async

Poll GetSandbox until READY, raising on the ERROR/DELETING phases or the deadline.

nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider.aclose() -> None
async

Release the shared client/pool (closed for real when the last provider releases it).

nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider.close(
handle: nemo_gym.sandbox.providers.base.SandboxHandle
) -> None
async

Delete the sandbox (already-gone counts as success), then wait until it is fully gone.

nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider.create(
spec: nemo_gym.sandbox.providers.base.SandboxSpec
) -> nemo_gym.sandbox.providers.base.SandboxHandle
async

Create a sandbox through the gateway, wait for the READY phase, then probe exec readiness.

spec.image is optional (the gateway’s configured default image is used when unset). spec.ttl_s is not enforced (OpenShell sandboxes live until deleted) and only logs a warning. spec.entrypoint is unsupported: the OpenShell supervisor owns the sandbox entrypoint. spec.provider_options accepts providers (OpenShell credential-provider names), policy (a SandboxPolicy mapping or YAML path), and template_resources / driver_config (free-form driver passthrough Structs). A half-created sandbox is deleted on any failure.

nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider.download_file(
handle: nemo_gym.sandbox.providers.base.SandboxHandle,
source_path: str,
target_path: pathlib.Path
) -> None
async

Download one sandbox file via a base64 round-trip (binary-safe over the text exec stream).

The whole file is buffered in memory (inflated 4/3 by base64), so this is intended for small-to-medium artifacts rather than large archives.

nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider.exec(
handle: nemo_gym.sandbox.providers.base.SandboxHandle,
command: str,
cwd: str | None = None,
env: dict[str, str] | None = None,
timeout_s: int | float | None = None,
user: str | int | None = None,
stdin: bytes | None = None
) -> nemo_gym.sandbox.providers.base.SandboxExecResult
async

Run <shell> -c <command> through the gateway’s streaming exec; never raises for command failure.

The timeout is enforced by the gateway (timeout_seconds); the SDK extends its gRPC deadline past it. user is ignored with a warning: the OpenShell exec API has no user field, so commands run as the sandbox’s default user.

nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider.status(
handle: nemo_gym.sandbox.providers.base.SandboxHandle
) -> nemo_gym.sandbox.providers.base.SandboxStatus
async

Sandbox phase via GetSandbox (missing -> STOPPED; RPC failure -> UNKNOWN).

nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider.upload_file(
handle: nemo_gym.sandbox.providers.base.SandboxHandle,
source_path: pathlib.Path,
target_path: str
) -> None
async

Upload one local file by streaming its bytes through exec stdin (creates the parent dir).

Bytes are sent in exec.upload_chunk_bytes chunks because each chunk travels as a single gRPC message that must stay under the gateway’s max decode size.

class nemo_gym.sandbox.providers.openshell.provider.OpenShellProviderOptions(
providers: list[str] = list(),
policy: typing.Any | None = None,
template_resources: dict[str, typing.Any] = dict(),
driver_config: dict[str, typing.Any] = dict()
)
Dataclass

Validated per-sandbox options carried in SandboxSpec.provider_options.

driver_config
dict[str, Any] = field(default_factory=dict)
policy
Any | None = None
providers
list[str] = field(default_factory=list)
template_resources
dict[str, Any] = field(default_factory=dict)
classmethod
class nemo_gym.sandbox.providers.openshell.provider._OpenShellSandbox(
name: str,
sandbox_id: str,
workspace: str,
image: str | None,
env: dict[str, str] = dict(),
workdir: str | None = None
)
Dataclass
env
dict[str, str] = field(default_factory=dict)
image
str | None
name
str
sandbox_id
str
workdir
str | None = None
workspace
str
class nemo_gym.sandbox.providers.openshell.provider._SharedClientState(
key: tuple[typing.Any, ...],
client: typing.Any,
executor: concurrent.futures.ThreadPoolExecutor,
refcount: int = 0
)
Dataclass

One gRPC client + worker pool shared by every provider with the same connection config.

executor
ThreadPoolExecutor
key
tuple[Any, ...]
refcount
int = 0
nemo_gym.sandbox.providers.openshell.provider._acquire_shared_client(
connection: nemo_gym.sandbox.providers.openshell.provider.OpenShellConnectionConfig,
concurrency: int
) -> nemo_gym.sandbox.providers.openshell.provider._SharedClientState
nemo_gym.sandbox.providers.openshell.provider._build_client(
connection: nemo_gym.sandbox.providers.openshell.provider.OpenShellConnectionConfig
) -> typing.Any
nemo_gym.sandbox.providers.openshell.provider._coerce_config(
value: typing.Any,
config_cls: type[typing.Any]
) -> typing.Any
nemo_gym.sandbox.providers.openshell.provider._grpc_status_code(
exc: BaseException
) -> typing.Any | None

The grpc.StatusCode of an RPC error, else None.

nemo_gym.sandbox.providers.openshell.provider._is_already_exists(
exc: BaseException
) -> bool
nemo_gym.sandbox.providers.openshell.provider._is_grpc_error(
exc: BaseException
) -> bool
nemo_gym.sandbox.providers.openshell.provider._is_grpc_timeout(
exc: BaseException
) -> bool
nemo_gym.sandbox.providers.openshell.provider._is_not_found(
exc: BaseException
) -> bool
nemo_gym.sandbox.providers.openshell.provider._is_retryable_create_error(
exc: BaseException
) -> bool

Whether a CreateSandbox RPC failure is likely transient (safe to retry with the same name).

nemo_gym.sandbox.providers.openshell.provider._is_runtime_failure(
exc: BaseException
) -> bool
nemo_gym.sandbox.providers.openshell.provider._is_sdk_error(
exc: BaseException
) -> bool
nemo_gym.sandbox.providers.openshell.provider._normalize_image(
image: str
) -> str
nemo_gym.sandbox.providers.openshell.provider._phase(
name: str
) -> int
nemo_gym.sandbox.providers.openshell.provider._phase_to_status_map() -> dict[int, nemo_gym.sandbox.providers.base.SandboxStatus]

SandboxPhase -> SandboxStatus, built from the SDK’s generated proto constants.

nemo_gym.sandbox.providers.openshell.provider._release_shared_client(
state: nemo_gym.sandbox.providers.openshell.provider._SharedClientState
) -> None
nemo_gym.sandbox.providers.openshell.provider._require_openshell() -> None
nemo_gym.sandbox.providers.openshell.provider.DEFAULT_UPLOAD_CHUNK_BYTES = 512 * 1024
nemo_gym.sandbox.providers.openshell.provider.LOGGER = logging.getLogger(__name__)
nemo_gym.sandbox.providers.openshell.provider.READY_PROBE_COMMAND = 'printf openshell-sandbox-ready'
nemo_gym.sandbox.providers.openshell.provider.READY_PROBE_EXPECTED = 'openshell-sandbox-ready'
nemo_gym.sandbox.providers.openshell.provider.SANDBOX_LABEL = 'nemo-gym.sandbox'
nemo_gym.sandbox.providers.openshell.provider.SANDBOX_NAME_PREFIX = 'nemo-gym-'
nemo_gym.sandbox.providers.openshell.provider.SANDBOX_RUNTIME_RETURN_CODE = 125
nemo_gym.sandbox.providers.openshell.provider._SHARED_CLIENTS: dict[tuple[Any, ...], _SharedClientState] = {}
nemo_gym.sandbox.providers.openshell.provider._SHARED_CLIENTS_LOCK = threading.Lock()