> 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.opensandbox.pty

execd PTY sessions over the OpenSandbox server proxy.

Speaks execd's documented PTY wire protocol directly: the
released OpenSandbox SDKs expose no PTY API. Sessions live at
`&#123;base&#125;/pty[/&#123;session_id&#125;[/ws]]` where `base` is the sandbox's execd
endpoint as resolved by the SDK (through the server proxy when
`use_server_proxy` is set).

execd sends the exit frame and closes the socket concurrently with its output
pumps, so a clean EOF means the socket drained, not that every byte the process
wrote was delivered. Callers that need the tail should have the command emit a
sentinel and read until it appears.

## Module Contents

### Classes

| Name                                                                                         | Description                                                   |
| -------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| [`OpenSandboxPtySession`](#nemo_gym-sandbox-providers-opensandbox-pty-OpenSandboxPtySession) | One live PTY WebSocket. Created via :func:`open_pty_session`. |

### Functions

| Name                                                                                   | Description                                                        |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| [`_connect_ws`](#nemo_gym-sandbox-providers-opensandbox-pty-_connect_ws)               | -                                                                  |
| [`_effective_command`](#nemo_gym-sandbox-providers-opensandbox-pty-_effective_command) | Rewrite env/user into the command: execd's PTY create accepts only |
| [`_start_session`](#nemo_gym-sandbox-providers-opensandbox-pty-_start_session)         | -                                                                  |
| [`attach_pty_session`](#nemo_gym-sandbox-providers-opensandbox-pty-attach_pty_session) | Attach to an existing execd PTY session. Owns `client` as above.   |
| [`open_pty_session`](#nemo_gym-sandbox-providers-opensandbox-pty-open_pty_session)     | Create an execd PTY session and attach its WebSocket.              |

### Data

[`CHAN_REPLAY`](#nemo_gym-sandbox-providers-opensandbox-pty-CHAN_REPLAY)

[`CHAN_STDERR`](#nemo_gym-sandbox-providers-opensandbox-pty-CHAN_STDERR)

[`CHAN_STDIN`](#nemo_gym-sandbox-providers-opensandbox-pty-CHAN_STDIN)

[`CHAN_STDOUT`](#nemo_gym-sandbox-providers-opensandbox-pty-CHAN_STDOUT)

[`LOGGER`](#nemo_gym-sandbox-providers-opensandbox-pty-LOGGER)

[`REPLAY_HEADER_BYTES`](#nemo_gym-sandbox-providers-opensandbox-pty-REPLAY_HEADER_BYTES)

[`SUPPORTED_SIGNALS`](#nemo_gym-sandbox-providers-opensandbox-pty-SUPPORTED_SIGNALS)

[`WS_CLOSE_POLICY_VIOLATION`](#nemo_gym-sandbox-providers-opensandbox-pty-WS_CLOSE_POLICY_VIOLATION)

[`WS_CLOSE_TAKEN_OVER`](#nemo_gym-sandbox-providers-opensandbox-pty-WS_CLOSE_TAKEN_OVER)

[`_DEFAULT_SHELL_SNIPPET`](#nemo_gym-sandbox-providers-opensandbox-pty-_DEFAULT_SHELL_SNIPPET)

[`_PTY_RETRY_DELAYS`](#nemo_gym-sandbox-providers-opensandbox-pty-_PTY_RETRY_DELAYS)

### API

```python
class nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession(
    client: aiohttp.ClientSession,
    ws: aiohttp.ClientWebSocketResponse,
    session_id: str,
    session_url: str,
    headers: dict[str, str],
    request_timeout_s: float | None,
    owned: bool = True
)
```

One live PTY WebSocket. Created via :func:`open_pty_session`.

**`_connected`** `Future[None] = asyncio.get_running_loop().create_future()`

---

**`_error`** `SandboxPtyError | None = None`

---

**`_exit`** `Future[int] = asyncio.get_running_loop().create_future()`

---

**`_output`** `Queue[bytes | None] = asyncio.Queue()`

---

**`_pump_task`** `= asyncio.create_task(self._pump())`

---

**`_received`** `= 0`

---

**`_stderr`** `Queue[bytes | None] = asyncio.Queue()`

---

**`closed`** `bool`

True once the session can no longer run commands: after `close()`,
or once the connection pump has ended (process exit, takeover eviction,
or connection loss). Resources are released by `close()`.

---

**`mode`** `str | None = None`

---

**`replay_offset`** `int | None = None`

---

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession.__aenter__() -> nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession
```

async

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession.__aexit__(
    exc_type: typing.Any,
    exc_val: typing.Any,
    exc_tb: typing.Any
) -> None
```

async

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession.__aiter__() -> collections.abc.AsyncIterator[bytes]
```

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession._close_error() -> nemo_gym.sandbox.providers.base.SandboxPtyError
```

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession._pump() -> None
```

async

Sole reader of the session's sockets; fans frames out to queue/future.

A socket that dies while the process still runs (proxy shed, connection
loss) is re-dialed with `since=&lt;bytes received&gt;`, so pending reads and
execs continue across the gap. A deliberate takeover, a protocol error,
an exit, or `close()` ends the session instead.

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession._pump_socket() -> None
```

async

Drain one WebSocket until it closes, fanning frames out.

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession._read_stream(
    queue: asyncio.Queue[bytes | None],
    timeout_s: float | None
) -> bytes
```

async

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession._reattach_socket() -> bool
```

async

Re-dial the session's WebSocket, resuming from the last received byte.

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession._send(
    frame: bytes | str
) -> None
```

async

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession._wait_connected(
    timeout_s: float | None
) -> None
```

async

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession.close() -> None
```

async

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession.read(
    timeout_s: float | None = None
) -> bytes
```

async

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession.read_stderr(
    timeout_s: float | None = None
) -> bytes
```

async

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession.resize(
    rows: int,
    cols: int
) -> None
```

async

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession.send_signal(
    signal: str
) -> None
```

async

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession.wait_exit(
    timeout_s: float | None = None
) -> int
```

async

```python
nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession.write(
    data: bytes
) -> None
```

async

```python
nemo_gym.sandbox.providers.opensandbox.pty._connect_ws(
    client: aiohttp.ClientSession,
    base_url: str,
    headers: dict[str, str],
    session_id: str,
    query: dict[str, str],
    request_timeout_s: float | None
) -> aiohttp.ClientWebSocketResponse
```

async

```python
nemo_gym.sandbox.providers.opensandbox.pty._effective_command(
    spec: nemo_gym.sandbox.providers.base.SandboxPtySpec
) -> str | None
```

Rewrite env/user into the command: execd's PTY create accepts only
`cwd` and `command`.

```python
nemo_gym.sandbox.providers.opensandbox.pty._start_session(
    client: aiohttp.ClientSession,
    ws: aiohttp.ClientWebSocketResponse,
    base_url: str,
    session_id: str,
    headers: dict[str, str],
    request_timeout_s: float | None,
    owned: bool = True
) -> nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession
```

async

```python
nemo_gym.sandbox.providers.opensandbox.pty.attach_pty_session(
    client: aiohttp.ClientSession,
    base_url: str,
    headers: dict[str, str],
    session_id: str,
    takeover: bool = True,
    since: int | None = None,
    request_timeout_s: float | None
) -> nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession
```

async

Attach to an existing execd PTY session. Owns `client` as above.

```python
nemo_gym.sandbox.providers.opensandbox.pty.open_pty_session(
    client: aiohttp.ClientSession,
    base_url: str,
    headers: dict[str, str],
    spec: nemo_gym.sandbox.providers.base.SandboxPtySpec,
    request_timeout_s: float | None
) -> nemo_gym.sandbox.providers.opensandbox.pty.OpenSandboxPtySession
```

async

Create an execd PTY session and attach its WebSocket.

Owns `client`: it is closed on failure and by `session.close()`.

```python
nemo_gym.sandbox.providers.opensandbox.pty.CHAN_REPLAY = 3
```

```python
nemo_gym.sandbox.providers.opensandbox.pty.CHAN_STDERR = 2
```

```python
nemo_gym.sandbox.providers.opensandbox.pty.CHAN_STDIN = 0
```

```python
nemo_gym.sandbox.providers.opensandbox.pty.CHAN_STDOUT = 1
```

```python
nemo_gym.sandbox.providers.opensandbox.pty.LOGGER = logging.getLogger(__name__)
```

```python
nemo_gym.sandbox.providers.opensandbox.pty.REPLAY_HEADER_BYTES = 9
```

```python
nemo_gym.sandbox.providers.opensandbox.pty.SUPPORTED_SIGNALS = frozenset({'SIGINT', 'SIGTERM', 'SIGKILL', 'SIGQUIT', 'SIGHUP'})
```

```python
nemo_gym.sandbox.providers.opensandbox.pty.WS_CLOSE_POLICY_VIOLATION = 1008
```

```python
nemo_gym.sandbox.providers.opensandbox.pty.WS_CLOSE_TAKEN_OVER = 4001
```

```python
nemo_gym.sandbox.providers.opensandbox.pty._DEFAULT_SHELL_SNIPPET = 'exec "$(command -v bash || echo sh)"'
```

```python
nemo_gym.sandbox.providers.opensandbox.pty._PTY_RETRY_DELAYS = (0.25, 0.5, 1.0, 2.0, 4.0, 8.0)
```