> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/nemo/gym/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/nemo/gym/_mcp/server.

# nemo_gym.sandbox.providers.openshell.provider

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

| Name                                                                                                                  | Description                                                                                     |
| --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| [`OpenShellConnectionConfig`](#nemo_gym-sandbox-providers-openshell-provider-OpenShellConnectionConfig)               | Gateway connection settings. Defaults target a local plaintext gateway (deploy/docker compose). |
| [`OpenShellCreateConfig`](#nemo_gym-sandbox-providers-openshell-provider-OpenShellCreateConfig)                       | -                                                                                               |
| [`OpenShellCreateError`](#nemo_gym-sandbox-providers-openshell-provider-OpenShellCreateError)                         | Raised when the OpenShell gateway cannot create a sandbox.                                      |
| [`OpenShellCreateVerificationError`](#nemo_gym-sandbox-providers-openshell-provider-OpenShellCreateVerificationError) | Raised when a new sandbox fails its readiness probe.                                            |
| [`OpenShellExecConfig`](#nemo_gym-sandbox-providers-openshell-provider-OpenShellExecConfig)                           | -                                                                                               |
| [`OpenShellOperationsConfig`](#nemo_gym-sandbox-providers-openshell-provider-OpenShellOperationsConfig)               | -                                                                                               |
| [`OpenShellProbeConfig`](#nemo_gym-sandbox-providers-openshell-provider-OpenShellProbeConfig)                         | -                                                                                               |
| [`OpenShellProvider`](#nemo_gym-sandbox-providers-openshell-provider-OpenShellProvider)                               | Sandbox provider backed by an OpenShell gateway's gRPC control plane.                           |
| [`OpenShellProviderOptions`](#nemo_gym-sandbox-providers-openshell-provider-OpenShellProviderOptions)                 | Validated per-sandbox options carried in `SandboxSpec.provider_options`.                        |
| [`_OpenShellSandbox`](#nemo_gym-sandbox-providers-openshell-provider-_OpenShellSandbox)                               | -                                                                                               |
| [`_SharedClientState`](#nemo_gym-sandbox-providers-openshell-provider-_SharedClientState)                             | One gRPC client + worker pool shared by every provider with the same connection config.         |

### Functions

| Name                                                                                                      | Description                                                                                 |
| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| [`_acquire_shared_client`](#nemo_gym-sandbox-providers-openshell-provider-_acquire_shared_client)         | -                                                                                           |
| [`_build_client`](#nemo_gym-sandbox-providers-openshell-provider-_build_client)                           | -                                                                                           |
| [`_coerce_config`](#nemo_gym-sandbox-providers-openshell-provider-_coerce_config)                         | -                                                                                           |
| [`_grpc_status_code`](#nemo_gym-sandbox-providers-openshell-provider-_grpc_status_code)                   | The `grpc.StatusCode` of an RPC error, else None.                                           |
| [`_is_already_exists`](#nemo_gym-sandbox-providers-openshell-provider-_is_already_exists)                 | -                                                                                           |
| [`_is_grpc_error`](#nemo_gym-sandbox-providers-openshell-provider-_is_grpc_error)                         | -                                                                                           |
| [`_is_grpc_timeout`](#nemo_gym-sandbox-providers-openshell-provider-_is_grpc_timeout)                     | -                                                                                           |
| [`_is_not_found`](#nemo_gym-sandbox-providers-openshell-provider-_is_not_found)                           | -                                                                                           |
| [`_is_retryable_create_error`](#nemo_gym-sandbox-providers-openshell-provider-_is_retryable_create_error) | Whether a CreateSandbox RPC failure is likely transient (safe to retry with the same name). |
| [`_is_runtime_failure`](#nemo_gym-sandbox-providers-openshell-provider-_is_runtime_failure)               | -                                                                                           |
| [`_is_sdk_error`](#nemo_gym-sandbox-providers-openshell-provider-_is_sdk_error)                           | -                                                                                           |
| [`_normalize_image`](#nemo_gym-sandbox-providers-openshell-provider-_normalize_image)                     | -                                                                                           |
| [`_phase`](#nemo_gym-sandbox-providers-openshell-provider-_phase)                                         | -                                                                                           |
| [`_phase_to_status_map`](#nemo_gym-sandbox-providers-openshell-provider-_phase_to_status_map)             | SandboxPhase -> SandboxStatus, built from the SDK's generated proto constants.              |
| [`_release_shared_client`](#nemo_gym-sandbox-providers-openshell-provider-_release_shared_client)         | -                                                                                           |
| [`_require_openshell`](#nemo_gym-sandbox-providers-openshell-provider-_require_openshell)                 | -                                                                                           |

### Data

[`DEFAULT_UPLOAD_CHUNK_BYTES`](#nemo_gym-sandbox-providers-openshell-provider-DEFAULT_UPLOAD_CHUNK_BYTES)

[`LOGGER`](#nemo_gym-sandbox-providers-openshell-provider-LOGGER)

[`READY_PROBE_COMMAND`](#nemo_gym-sandbox-providers-openshell-provider-READY_PROBE_COMMAND)

[`READY_PROBE_EXPECTED`](#nemo_gym-sandbox-providers-openshell-provider-READY_PROBE_EXPECTED)

[`SANDBOX_LABEL`](#nemo_gym-sandbox-providers-openshell-provider-SANDBOX_LABEL)

[`SANDBOX_NAME_PREFIX`](#nemo_gym-sandbox-providers-openshell-provider-SANDBOX_NAME_PREFIX)

[`SANDBOX_RUNTIME_RETURN_CODE`](#nemo_gym-sandbox-providers-openshell-provider-SANDBOX_RUNTIME_RETURN_CODE)

[`_SHARED_CLIENTS`](#nemo_gym-sandbox-providers-openshell-provider-_SHARED_CLIENTS)

[`_SHARED_CLIENTS_LOCK`](#nemo_gym-sandbox-providers-openshell-provider-_SHARED_CLIENTS_LOCK)

### API

```python
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'`

---

```python
nemo_gym.sandbox.providers.openshell.provider.OpenShellConnectionConfig.__post_init__() -> None
```

```python
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`

---

```python
nemo_gym.sandbox.providers.openshell.provider.OpenShellCreateConfig.__post_init__() -> None
```

```python
class nemo_gym.sandbox.providers.openshell.provider.OpenShellCreateError()
```

**Bases:** [SandboxCreateError](/nemo-gym/nemo_gym/sandbox/providers/base#nemo_gym-sandbox-providers-base-SandboxCreateError)

Raised when the OpenShell gateway cannot create a sandbox.

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

**Bases:** [SandboxCreateVerificationError](/nemo-gym/nemo_gym/sandbox/providers/base#nemo_gym-sandbox-providers-base-SandboxCreateVerificationError)

Raised when a new sandbox fails its readiness probe.

```python
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`

---

```python
nemo_gym.sandbox.providers.openshell.provider.OpenShellExecConfig.__post_init__() -> None
```

```python
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`

---

```python
nemo_gym.sandbox.providers.openshell.provider.OpenShellOperationsConfig.__post_init__() -> None
```

```python
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`

---

```python
nemo_gym.sandbox.providers.openshell.provider.OpenShellProbeConfig.__post_init__() -> None
```

```python
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'`

---

```python
nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider._build_policy(
    policy: str | collections.abc.Mapping[str, typing.Any]
) -> typing.Any
```

```python
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
```

```python
nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider._call(
    func: typing.Any,
    args: typing.Any = (),
    kwargs: typing.Any = {}
) -> typing.Any
```

async

```python
nemo_gym.sandbox.providers.openshell.provider.OpenShellProvider._cleanup_failed_create_handle(
    handle: nemo_gym.sandbox.providers.base.SandboxHandle
) -> None
```

async

```python
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.

```python
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.

```python
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).

```python
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.

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

async

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

```python
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.

```python
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.

```python
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.

```python
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 `&lt;shell&gt; -c &lt;command&gt;` 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.

```python
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).

```python
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.

```python
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)`

---

```python
nemo_gym.sandbox.providers.openshell.provider.OpenShellProviderOptions.from_mapping(
    options: collections.abc.Mapping[str, typing.Any]
) -> nemo_gym.sandbox.providers.openshell.provider.OpenShellProviderOptions
```

classmethod

```python
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`

---

```python
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`

---

```python
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
```

```python
nemo_gym.sandbox.providers.openshell.provider._build_client(
    connection: nemo_gym.sandbox.providers.openshell.provider.OpenShellConnectionConfig
) -> typing.Any
```

```python
nemo_gym.sandbox.providers.openshell.provider._coerce_config(
    value: typing.Any,
    config_cls: type[typing.Any]
) -> typing.Any
```

```python
nemo_gym.sandbox.providers.openshell.provider._grpc_status_code(
    exc: BaseException
) -> typing.Any | None
```

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

```python
nemo_gym.sandbox.providers.openshell.provider._is_already_exists(
    exc: BaseException
) -> bool
```

```python
nemo_gym.sandbox.providers.openshell.provider._is_grpc_error(
    exc: BaseException
) -> bool
```

```python
nemo_gym.sandbox.providers.openshell.provider._is_grpc_timeout(
    exc: BaseException
) -> bool
```

```python
nemo_gym.sandbox.providers.openshell.provider._is_not_found(
    exc: BaseException
) -> bool
```

```python
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).

```python
nemo_gym.sandbox.providers.openshell.provider._is_runtime_failure(
    exc: BaseException
) -> bool
```

```python
nemo_gym.sandbox.providers.openshell.provider._is_sdk_error(
    exc: BaseException
) -> bool
```

```python
nemo_gym.sandbox.providers.openshell.provider._normalize_image(
    image: str
) -> str
```

```python
nemo_gym.sandbox.providers.openshell.provider._phase(
    name: str
) -> int
```

```python
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.

```python
nemo_gym.sandbox.providers.openshell.provider._release_shared_client(
    state: nemo_gym.sandbox.providers.openshell.provider._SharedClientState
) -> None
```

```python
nemo_gym.sandbox.providers.openshell.provider._require_openshell() -> None
```

```python
nemo_gym.sandbox.providers.openshell.provider.DEFAULT_UPLOAD_CHUNK_BYTES = 512 * 1024
```

```python
nemo_gym.sandbox.providers.openshell.provider.LOGGER = logging.getLogger(__name__)
```

```python
nemo_gym.sandbox.providers.openshell.provider.READY_PROBE_COMMAND = 'printf openshell-sandbox-ready'
```

```python
nemo_gym.sandbox.providers.openshell.provider.READY_PROBE_EXPECTED = 'openshell-sandbox-ready'
```

```python
nemo_gym.sandbox.providers.openshell.provider.SANDBOX_LABEL = 'nemo-gym.sandbox'
```

```python
nemo_gym.sandbox.providers.openshell.provider.SANDBOX_NAME_PREFIX = 'nemo-gym-'
```

```python
nemo_gym.sandbox.providers.openshell.provider.SANDBOX_RUNTIME_RETURN_CODE = 125
```

```python
nemo_gym.sandbox.providers.openshell.provider._SHARED_CLIENTS: dict[tuple[Any, ...], _SharedClientState] = {}
```

```python
nemo_gym.sandbox.providers.openshell.provider._SHARED_CLIENTS_LOCK = threading.Lock()
```