> This page is for version 0.7.0.
> For other versions, use one of these documentation indexes:
> - Main (default): https://docs.nvidia.com/nemo/gym/main/llms.txt
> - 0.7.0: https://docs.nvidia.com/nemo/gym/v0.7.0/llms.txt
> - 0.6.0: https://docs.nvidia.com/nemo/gym/v0.6.0/llms.txt
> - 0.5.1: https://docs.nvidia.com/nemo/gym/v0.5.1/llms.txt
> - 0.5.0: https://docs.nvidia.com/nemo/gym/v0.5.0/llms.txt
> - 0.4.0: https://docs.nvidia.com/nemo/gym/v0.4.0/llms.txt
> - 0.3.0: https://docs.nvidia.com/nemo/gym/v0.3.0/llms.txt
> - 0.2.1: https://docs.nvidia.com/nemo/gym/v0.2.1/llms.txt

> 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.agents.utils.sandbox_session

Supervised harness activations over one retained sandbox connection.

## Module Contents

### Classes

| Name                                                                                      | Description                                                                                               |
| ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| [`ExpiryUnsupportedError`](#nemo_gym-agents-utils-sandbox_session-ExpiryUnsupportedError) | `expire` cannot stop this running harness: its sandbox's uploads can wait for capacity the harness holds. |
| [`SandboxCommand`](#nemo_gym-agents-utils-sandbox_session-SandboxCommand)                 | Harness argv, run under the bash process supervisor.                                                      |
| [`SandboxSession`](#nemo_gym-agents-utils-sandbox_session-SandboxSession)                 | Execute, stop, capture, then release an owned or borrowed sandbox.                                        |

### Functions

| Name                                                                            | Description                                                                   |
| ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| [`_reuse_or_start`](#nemo_gym-agents-utils-sandbox_session-_reuse_or_start)     | -                                                                             |
| [`shell_assignments`](#nemo_gym-agents-utils-sandbox_session-shell_assignments) | Bash source assigning each value, for sandbox runners that cannot parse JSON. |

### Data

[`HARNESS_COMMAND_NOT_STARTED`](#nemo_gym-agents-utils-sandbox_session-HARNESS_COMMAND_NOT_STARTED)

[`HARNESS_RUNNER_FAILED`](#nemo_gym-agents-utils-sandbox_session-HARNESS_RUNNER_FAILED)

[`HARNESS_RUNNER_FILE`](#nemo_gym-agents-utils-sandbox_session-HARNESS_RUNNER_FILE)

[`HARNESS_RUNNER_SCRIPT`](#nemo_gym-agents-utils-sandbox_session-HARNESS_RUNNER_SCRIPT)

[`LOG`](#nemo_gym-agents-utils-sandbox_session-LOG)

[`_SHELL_NAME`](#nemo_gym-agents-utils-sandbox_session-_SHELL_NAME)

### API

```python
class nemo_gym.agents.utils.sandbox_session.ExpiryUnsupportedError()
```

Exception

**Bases:** `Exception`

`expire` cannot stop this running harness: its sandbox's uploads can wait for capacity the harness holds.

```python
class nemo_gym.agents.utils.sandbox_session.SandboxCommand(
    argv: list[str]
)
```

Dataclass

Harness argv, run under the bash process supervisor.

**`argv`** `list[str]`

---

```python
class nemo_gym.agents.utils.sandbox_session.SandboxSession(
    sandbox: nemo_gym.sandbox.api.AsyncSandbox,
    session_dir: str,
    workdir: str | None,
    harness: str,
    owns_sandbox: bool = False
)
```

Dataclass

Execute, stop, capture, then release an owned or borrowed sandbox.

Runtime installation belongs in the adapter's seed hook. `stage_activation`
stages input and returns a command; `collect` copies harness artifacts
to the agent server after cleanup is confirmed. It may run a bounded snapshot
command, but must not restart the harness. Parsing responses stays in the adapter.

Each activation stages input and a fresh supervisor, runs the harness process, then
stops and collects artifacts. Adapters may explicitly rearm it with `begin_activation`
after confirmed cleanup, retaining native state outside the per-activation directory.
The default still permits only one launch. Execution retains the sandbox until close
releases it. Missing termination evidence remains retryable. A final negative
receipt skips capture, releases a borrowed connection and leaves teardown to
Resources; an owned sandbox falls back to provider stop. Closed means release succeeded.

The supervisor stops the harness process group; a forced stop also reaches the
harness's descendants in other groups and processes that inherited its
`NEMO_GYM_SUPERVISOR` marker. Processes that had left the harness when the stop
began, processes in other groups after a natural exit, and a process that left
between two scans without the marker remain until the sandbox owner destroys the
sandbox.
Borrowed sandboxes stay available for Resources verification; owned sandboxes are
stopped on close. Unconfirmed harness termination prevents capture and verification.

Each stop, collection and release phase is bounded by `close_timeout` (or
the timeout passed to `close`). Failed cleanup/release retains the handle
for retry. Failed capture is recorded separately and does not prevent release.
HTTP request binding and activation retries belong to the agent session layer.

**`_capture_attempted`** `bool = field(default=False, init=False)`

---

**`_close_task`** `Task[None] | None = field(default=None, init=False)`

---

**`_collect`** `Callable[[], Awaitable[Artifacts]] | None = field(default=None, init=False)`

---

**`_exec_task`** `Task[SandboxExecResult] | None = field(default=None, init=False)`

---

**`_expire_requested`** `bool = field(default=False, init=False)`

---

**`_expire_write`** `Task[None] | None = field(default=None, init=False)`

---

**`_finalize_task`** `Task[None] | None = field(default=None, init=False)`

---

**`_session_root`** `str | None = field(default=None, init=False)`

---

**`_stage_task`** `Future[SandboxCommand] | None = field(default=None, init=False)`

---

**`_unconfirmed_expire_reason`** `str | None = field(default=None, init=False)`

---

**`_unconfirmed_expiry`** `bool = field(default=False, init=False)`

---

**`artifacts`** `Artifacts | None = field(default=None, init=False)`

---

**`capture_error`** `Exception | None = field(default=None, init=False)`

---

**`cleanup`** `CleanupReceipt | None = field(default=None, init=False)`

---

**`cleanup_confirmed`** `bool`

Whether no launch occurred, termination was witnessed, or owned sandbox stopped.

---

**`closed`** `bool`

Whether sandbox release completed successfully.

---

**`closing`** `bool`

Whether close has been requested, including a failed attempt.

---

**`execution_oom`** `NgFailureEvent | None = field(default=None, init=False)`

---

**`expire_reason`** `str | None = field(default=None, init=False)`

---

**`expired`** `bool`

Whether `expire` ended this activation; its receipt then records the timeout.

---

**`expired_before_launch`** `bool`

Whether `expire` kept the harness from starting, so it left no output to collect.

---

**`harness`** `str`

---

**`launch_started`** `bool = field(default=False, init=False)`

---

**`owns_sandbox`** `bool = False`

---

**`sandbox`** `AsyncSandbox`

---

**`sandbox_stopped`** `bool = field(default=False, init=False)`

---

**`session_dir`** `str`

---

**`stop_request_path`** `str`

Marker path an adapter may pass to its harness for cancellation diagnostics.

---

**`termination_error`** `NgFailureEvent | None = field(default=None, init=False)`

---

**`workdir`** `str | None`

---

```python
nemo_gym.agents.utils.sandbox_session.SandboxSession._cancel_and_wait(
    task: asyncio.Future[nemo_gym.agents.utils.sandbox_session.SandboxSession._cancel_and_wait[T]],
    timeout: float
) -> None
```

async

```python
nemo_gym.agents.utils.sandbox_session.SandboxSession._close(
    timeout: float
) -> None
```

async

```python
nemo_gym.agents.utils.sandbox_session.SandboxSession._confirm_unacknowledged_expiry(
    timeout: float
) -> None
```

async

Keep a stop whose marker write failed only if the marker is in place and the receipt timed out.

A write still in flight is awaited first, so a failure that arrives during finalization is checked too.
The whole check, including any wait for provider capacity, is bounded by `timeout`; without a
confirmation the stop stays unattributed and finalization goes on. That includes a write still in flight
when the bound runs out: the harness may have ended at its own deadline, before any marker arrived.

```python
nemo_gym.agents.utils.sandbox_session.SandboxSession._finalize(
    timeout: float
) -> None
```

async

```python
nemo_gym.agents.utils.sandbox_session.SandboxSession._finish(
    timeout: float
) -> None
```

async

Join process finalization without releasing the sandbox connection.

```python
nemo_gym.agents.utils.sandbox_session.SandboxSession._release_exec(
    timeout: float
) -> None
```

async

```python
nemo_gym.agents.utils.sandbox_session.SandboxSession._request_expiry(
    timeout: float
) -> None
```

async

Write the marker the supervisor watches, within `timeout` including any wait for the provider.

A file write, not an exec: a provider that bounds its concurrent execs (Local) counts the harness's
own launch against that bound for as long as it runs, and a stop must not wait for the harness to end.

```python
nemo_gym.agents.utils.sandbox_session.SandboxSession._stage(
    stage_activation: collections.abc.Callable[[], collections.abc.Awaitable[nemo_gym.agents.utils.sandbox_session.SandboxCommand]]
) -> nemo_gym.agents.utils.sandbox_session.SandboxCommand
```

async

```python
nemo_gym.agents.utils.sandbox_session.SandboxSession._write_expiry(
    timeout: float,
    reason: str | None
) -> None
```

async

```python
nemo_gym.agents.utils.sandbox_session.SandboxSession.begin_activation(
    session_dir: str,
    timeout: float
) -> None
```

async

Rearm a stopped harness using fresh supervisor files in the same sandbox.

The adapter retains its native conversation elsewhere in the sandbox. A
previous launch must be reaped before any activation fields can be reset.
Close fences all later activations, including when release needs retry.

```python
nemo_gym.agents.utils.sandbox_session.SandboxSession.close(
    timeout: float
) -> None
```

async

Join finalization before release; concurrent/retried closes never duplicate capture.

```python
nemo_gym.agents.utils.sandbox_session.SandboxSession.execute(
    stage_activation: collections.abc.Callable[[], collections.abc.Awaitable[nemo_gym.agents.utils.sandbox_session.SandboxCommand]],
    collect: collections.abc.Callable[[], collections.abc.Awaitable[nemo_gym.agents.utils.sandbox_session.SandboxSession[Artifacts]]],
    timeout: float,
    close_timeout: float,
    deadline: float | None = None
) -> nemo_gym.agents.utils.sandbox_session.SandboxSession[Artifacts]
```

async

Run once, preserving captured artifacts even when execution raises.

Cancellation stops the remote harness before releasing provider exec.
Callers that merely stop waiting (such as disconnected HTTP requests)
must shield their shared activation task.

```python
nemo_gym.agents.utils.sandbox_session.SandboxSession.expire(
    timeout: float,
    reason: str | None = None
) -> bool
```

async

End the harness as at its time limit, so its receipt records `timed_out`.

Unlike `stop_harness` (a cancellation), the activation is then graded like a timeout.
A request made before launch ends the harness as it would launch. Returns False when the
activation already finished, the session is closing, or expiry was already requested.

Stopping a running harness takes an upload, which on a provider without
`SupportsSandboxIndependentUploads` can wait behind the very commands it must stop, so there it
raises `ExpiryUnsupportedError`. A request before launch works on every provider.

```python
nemo_gym.agents.utils.sandbox_session.SandboxSession.read_output_log() -> str
```

async

Read combined supervisor/harness diagnostics without masking the original failure.

```python
nemo_gym.agents.utils.sandbox_session.SandboxSession.stop_harness(
    timeout: float
) -> None
```

async

Confirm harness termination before capture and transport release.

```python
nemo_gym.agents.utils.sandbox_session._reuse_or_start(
    task: asyncio.Task[nemo_gym.agents.utils.sandbox_session._reuse_or_start[T]] | None,
    factory: collections.abc.Callable[[], collections.abc.Coroutine[object, object, nemo_gym.agents.utils.sandbox_session._reuse_or_start[T]]]
) -> asyncio.Task[nemo_gym.agents.utils.sandbox_session._reuse_or_start[T]]
```

```python
nemo_gym.agents.utils.sandbox_session.shell_assignments(
    values: collections.abc.Mapping[str, str | collections.abc.Sequence[str] | collections.abc.Mapping[str, str]]
) -> str
```

Bash source assigning each value, for sandbox runners that cannot parse JSON.

A string becomes a scalar and a sequence an array. A mapping becomes an array of
`KEY=VALUE` words, ready for `export` or `env`. Every word is shell-quoted.
Words must be strings without NUL, which bash cannot hold.

```python
nemo_gym.agents.utils.sandbox_session.HARNESS_COMMAND_NOT_STARTED = (126, 127)
```

```python
nemo_gym.agents.utils.sandbox_session.HARNESS_RUNNER_FAILED = 125
```

```python
nemo_gym.agents.utils.sandbox_session.HARNESS_RUNNER_FILE = 'harness_runner.sh'
```

```python
nemo_gym.agents.utils.sandbox_session.HARNESS_RUNNER_SCRIPT = Path(__file__).with_name(HARNESS_RUNNER_FILE)
```

```python
nemo_gym.agents.utils.sandbox_session.LOG = logging.getLogger(__name__)
```

```python
nemo_gym.agents.utils.sandbox_session._SHELL_NAME = re.compile('[A-Za-z_][A-Za-z0-9_]*')
```