> This page is for version 0.3.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.server_runtime.base_environment_server

Shared lifecycle for environment servers.

## Module Contents

### Classes

| Name                                                                                                          | Description                                                     |
| ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| [`BaseEnvironmentServer`](#nemo_gym-server_runtime-base_environment_server-BaseEnvironmentServer)             | Expose a typed episode protocol with shared limits and cleanup. |
| [`BaseEnvironmentServerConfig`](#nemo_gym-server_runtime-base_environment_server-BaseEnvironmentServerConfig) | Configure protocol-neutral episode limits.                      |
| [`CleanupContext`](#nemo_gym-server_runtime-base_environment_server-CleanupContext)                           | Hold bounded process-local cleanup callbacks for one episode.   |
| [`CleanupHandle`](#nemo_gym-server_runtime-base_environment_server-CleanupHandle)                             | Close one registered participant at a protocol boundary.        |
| [`_CleanupEntry`](#nemo_gym-server_runtime-base_environment_server-_CleanupEntry)                             | -                                                               |

### Functions

| Name                                                                                                                | Description                                                                                       |
| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| [`dependency_failure`](#nemo_gym-server_runtime-base_environment_server-dependency_failure)                         | Turn a failed call to another server into the episode's failure.                                  |
| [`handled_episode_failure`](#nemo_gym-server_runtime-base_environment_server-handled_episode_failure)               | The environment's handled episode failure: one `NgFailureEvent` with the local policy kept apart. |
| [`is_handled_episode_failure`](#nemo_gym-server_runtime-base_environment_server-is_handled_episode_failure)         | Whether `error` is an environment failure built by `handled_episode_failure`.                     |
| [`is_retryable_dependency_error`](#nemo_gym-server_runtime-base_environment_server-is_retryable_dependency_error)   | Return whether a failed call to another server is worth another episode attempt.                  |
| [`is_retryable_dependency_status`](#nemo_gym-server_runtime-base_environment_server-is_retryable_dependency_status) | Return whether an HTTP error status from another server is worth another episode attempt.         |

### Data

[`CleanupCallback`](#nemo_gym-server_runtime-base_environment_server-CleanupCallback)

[`EpisodeRequestT`](#nemo_gym-server_runtime-base_environment_server-EpisodeRequestT)

[`EpisodeResponseT`](#nemo_gym-server_runtime-base_environment_server-EpisodeResponseT)

[`LOGGER`](#nemo_gym-server_runtime-base_environment_server-LOGGER)

[`_ENVIRONMENT_CHECKPOINT_MODES`](#nemo_gym-server_runtime-base_environment_server-_ENVIRONMENT_CHECKPOINT_MODES)

[`_RETRYABLE_CLIENT_ERROR_STATUSES`](#nemo_gym-server_runtime-base_environment_server-_RETRYABLE_CLIENT_ERROR_STATUSES)

### API

```python
class nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServer()
```

**Bases:** [SimpleServer](/nemo/gym/nemo-gym/nemo_gym/server_runtime/base_server#nemo_gym-server_runtime-base_server-SimpleServer), `Generic[EpisodeRequestT, EpisodeResponseT]`

Expose a typed episode protocol with shared limits and cleanup.

**`_admission`** `Semaphore | None = None`

---

**`_checkpoint`** `EnvironmentParticipant | None = None`

---

**`checkpoint_mode`** `CheckpointMode = 'restart_only'`

---

**`config`** `BaseEnvironmentServerConfig`

---

**`request_model`** `type[EpisodeRequestT]`

---

**`response_model`** `type[EpisodeResponseT]`

---

```python
nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServer._unhandled_failure_response(
    request: nemo_gym.server_runtime.base_environment_server.EpisodeRequestT,
    error: Exception
) -> nemo_gym.server_runtime.base_environment_server.EpisodeResponseT
```

```python
nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServer.aggregate_metrics(
    body: nemo_gym.config.config_types.AggregateMetricsRequest = Body()
) -> nemo_gym.config.config_types.AggregateMetrics
```

async

abstract

Aggregate per-rollout scores into task-level metrics.

```python
nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServer.checkpoint_boundary(
    request: nemo_gym.server_runtime.base_environment_server.EpisodeRequestT,
    state: dict[str, pydantic.JsonValue] | collections.abc.Callable[[], dict[str, pydantic.JsonValue]]
) -> None
```

async

Record that a protocol step completed; `state` names the next step and what it needs.

While a checkpoint is open the episode parks here until resume.
A replacement attempt
that continues a checkpoint receives the latest recorded `state` from `checkpoint_continuation`.
Pass a function when building `state` costs something: it is called only with checkpointing on.

```python
nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServer.checkpoint_continuation(
    request: nemo_gym.server_runtime.base_environment_server.EpisodeRequestT
) -> dict[str, pydantic.JsonValue] | None
```

Return the protocol state this attempt continues from a checkpoint, once, or `None`.

```python
nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServer.checkpoint_restart(
    request: nemo_gym.server_runtime.base_environment_server.EpisodeRequestT
) -> None
```

async

Mark this episode as a restart: a server it uses cannot capture its part, such as a restart-only agent.

Call it when a seed reply says so (`nemo_gym.server_runtime._checkpoint.steps.seed_restarts`).
The episode then never holds up a checkpoint and is never exported; after a crash,
the controller starts it over from its input.

```python
nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServer.checkpoint_step(
    request: nemo_gym.server_runtime.base_environment_server.EpisodeRequestT,
    step_type: nemo_gym.server_runtime._checkpoint.steps.StepType
) -> contextlib.AbstractAsyncContextManager[None]
```

Run one protocol step as a `wait` or `replay` step (see `nemo_gym.server_runtime._checkpoint.steps`).

Use `replay` only for a step that is safe to run again after a crash
and does not change checkpointed state; everything else waits.

```python
nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServer.episode_timeout_seconds(
    request: nemo_gym.server_runtime.base_environment_server.EpisodeRequestT
) -> float | None
```

async

The wall-clock bound of this episode: `default_episode_timeout_seconds` (`None` is no bound).

A protocol whose tasks carry their own budgets may derive a longer one per episode (the Harbor server
does for a multi-step task whose steps add up to more than the default); it must never raise here, as
nothing of the episode has started yet.

```python
nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServer.failure_response(
    request: nemo_gym.server_runtime.base_environment_server.EpisodeRequestT,
    failure: nemo_gym.core.failure_records.EpisodeFailure | nemo_gym.core.typed_exceptions.NgFailureEvent,
    report: nemo_gym.core.failure_records.FailureReport | None = None
) -> nemo_gym.server_runtime.base_environment_server.EpisodeResponseT
```

Adapt runtime event, local policy and evidence to the protocol's wire failure model.

The failure's `FailureReport` goes in the reply's `failure_report`, beside the failure.

```python
nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServer.model_post_init(
    context: typing.Any
) -> None
```

```python
nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServer.run(
    request: nemo_gym.server_runtime.base_environment_server.EpisodeRequestT,
    cleanup: nemo_gym.server_runtime.base_environment_server.CleanupContext
) -> nemo_gym.server_runtime.base_environment_server.EpisodeResponseT
```

async

abstract

Run one concrete environment protocol.

```python
nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServer.run_request(
    request: nemo_gym.server_runtime.base_environment_server.EpisodeRequestT
) -> nemo_gym.server_runtime.base_environment_server.EpisodeResponseT
```

async

```python
nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServer.setup_environment_checkpoint(
    app: fastapi.FastAPI
) -> None
```

Take part in partial-rollout checkpoints when they are enabled.

Every episode is tracked.
A protocol that records boundaries (`checkpoint_mode = "exported"`) continues after a restore;
the episodes of a protocol that records none are restarts,
which never hold up a checkpoint and start over from their input after a crash.
Call this from any `setup_webserver` that builds its own app.

```python
nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServer.setup_webserver() -> fastapi.FastAPI
```

```python
nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServer.validate_response_identity(
    request: nemo_gym.server_runtime.base_environment_server.EpisodeRequestT,
    response: nemo_gym.server_runtime.base_environment_server.EpisodeResponseT
) -> None
```

staticmethod

```python
class nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServerConfig()
```

**Bases:** [BaseRunServerInstanceConfig](/nemo/gym/nemo-gym/nemo_gym/config/config_types#nemo_gym-config-config_types-BaseRunServerInstanceConfig)

Configure protocol-neutral episode limits.

**`cleanup_timeout_seconds`** `PositiveFloat`

---

**`default_episode_timeout_seconds`** `PositiveFloat | None = None`

---

**`max_concurrent_episodes`** `PositiveInt | None = None`

---

**`model_config`** `= ConfigDict(extra='forbid')`

---

**`queue_timeout_seconds`** `PositiveFloat | None = None`

---

```python
nemo_gym.server_runtime.base_environment_server.BaseEnvironmentServerConfig.validate_queue_timeout() -> typing_extensions.Self
```

```python
class nemo_gym.server_runtime.base_environment_server.CleanupContext(
    episode_id: nemo_gym.core.episode_types.EpisodeId,
    cleanup_timeout_seconds: float,
    _cleanups: list[nemo_gym.server_runtime.base_environment_server._CleanupEntry] = list(),
    failure_report: nemo_gym.core.failure_records.FailureReport = FailureReport()
)
```

Dataclass

Hold bounded process-local cleanup callbacks for one episode.

Callbacks:

* are process-local Python objects, although they may issue remote close requests;
* run sequentially in LIFO order, each with its own cleanup timeout;
* must be idempotent because a timed-out remote request may have succeeded;
* are lost on process or host failure, so remote owners need expiry or reaping.

Callback failures or timeouts do not prevent later owners from tearing down
their resources. Total cleanup is bounded by the number of registered callbacks
times the per-callback timeout. Cleanup has no
durable retry after this context is discarded.

**`_cleanups`** `list[_CleanupEntry] = field(default_factory=list)`

---

**`cleanup_timeout_seconds`** `float`

---

**`episode_id`** `EpisodeId`

---

**`failure_report`** `FailureReport = field(default_factory=FailureReport)`

---

```python
nemo_gym.server_runtime.base_environment_server.CleanupContext._observe_failure(
    entry: nemo_gym.server_runtime.base_environment_server._CleanupEntry,
    error: Exception
) -> None
```

```python
nemo_gym.server_runtime.base_environment_server.CleanupContext.aclose() -> None
```

async

```python
nemo_gym.server_runtime.base_environment_server.CleanupContext.register_cleanup(
    name: str,
    callback: nemo_gym.server_runtime.base_environment_server.CleanupCallback
) -> nemo_gym.server_runtime.base_environment_server.CleanupHandle
```

```python
class nemo_gym.server_runtime.base_environment_server.CleanupHandle(
    entry: nemo_gym.server_runtime.base_environment_server._CleanupEntry,
    timeout_seconds: float
)
```

Dataclass

Close one registered participant at a protocol boundary.

**`entry`** `_CleanupEntry`

---

**`timeout_seconds`** `float`

---

```python
nemo_gym.server_runtime.base_environment_server.CleanupHandle.close() -> None
```

async

Run this idempotent callback once.

The episode timeout bounds calls made during the protocol. The context's
cleanup timeout bounds callbacks left for final unwinding.

```python
class nemo_gym.server_runtime.base_environment_server._CleanupEntry(
    name: str,
    callback: nemo_gym.server_runtime.base_environment_server.CleanupCallback,
    active: bool = True,
    lock: asyncio.Lock = asyncio.Lock()
)
```

Dataclass

**`active`** `bool = True`

---

**`callback`** `CleanupCallback`

---

**`lock`** `Lock = field(default_factory=(asyncio.Lock))`

---

**`name`** `str`

---

```python
nemo_gym.server_runtime.base_environment_server._CleanupEntry.close() -> None
```

async

```python
nemo_gym.server_runtime.base_environment_server.dependency_failure(
    error: BaseException,
    stage: nemo_gym.core.failure_kinds.FailureStage,
    terminal: bool | None = None,
    name_error_type: bool = False,
    fields: typing.Any = {}
) -> nemo_gym.core.typed_exceptions.NgFailureEvent
```

Turn a failed call to another server into the episode's failure.

`failure_reason` keeps the dependency's own explanation from its response body
(see `format_exception_with_response_body`), prefixed with the error's class name when `name_error_type` is set,
and bounded to the failure's 2000 characters. `terminal` defaults to
`not is_retryable_dependency_error(error)`; pass it only where the stage decides on its own.
`fields` retain partial evidence for the response adapter, such as a partial response.

```python
nemo_gym.server_runtime.base_environment_server.handled_episode_failure(
    terminal: bool,
    evidence: dict[str, object] | None = None,
    failure_kind: str | None = None,
    event_fields: typing.Any = {}
) -> nemo_gym.core.typed_exceptions.NgFailureEvent
```

The environment's handled episode failure: one `NgFailureEvent` with the local policy kept apart.

`terminal` and `evidence` are runtime outcome data for the response adapter, never event fields; the
adapter builds the protocol's `EpisodeFailure` after cleanup. `failure_kind` is the outcome's kind and
may stay unset; the event then records the registered `episode_failed`.

```python
nemo_gym.server_runtime.base_environment_server.is_handled_episode_failure(
    error: BaseException
) -> bool
```

Whether `error` is an environment failure built by `handled_episode_failure`.

```python
nemo_gym.server_runtime.base_environment_server.is_retryable_dependency_error(
    error: BaseException
) -> bool
```

Return whether a failed call to another server is worth another episode attempt.

An HTTP error is classified by its status (see `is_retryable_dependency_status`). A refused or dropped
connection and a timeout are transient. Any other error, such as a reply that fails validation, would fail the
same way again.

```python
nemo_gym.server_runtime.base_environment_server.is_retryable_dependency_status(
    status: int
) -> bool
```

Return whether an HTTP error status from another server is worth another episode attempt.

408, 425, 429 and every 5xx are retried; every other 4xx is terminal.

```python
nemo_gym.server_runtime.base_environment_server.CleanupCallback = Callable[[], Awaitable[None]]
```

```python
nemo_gym.server_runtime.base_environment_server.EpisodeRequestT = TypeVar('EpisodeRequestT', bound=(BaseEpisodeRequest[Any]))
```

```python
nemo_gym.server_runtime.base_environment_server.EpisodeResponseT = TypeVar('EpisodeResponseT', bound=(BaseEpisodeResponse[Any]))
```

```python
nemo_gym.server_runtime.base_environment_server.LOGGER = logging.getLogger(__name__)
```

```python
nemo_gym.server_runtime.base_environment_server._ENVIRONMENT_CHECKPOINT_MODES = ('restart_only', 'exported')
```

```python
nemo_gym.server_runtime.base_environment_server._RETRYABLE_CLIENT_ERROR_STATUSES = frozenset({408, 425, 429})
```