> This page is for version 0.5.1.
> 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.core.failure_records

What a failure records: the events Gym servers observe, the report that forwards them, and the episode failure.

Failure kinds live in `nemo_gym.core.failure_kinds`; `nemo_gym.core.failure_transport` carries reports over HTTP.

## Module Contents

### Classes

| Name                                                                            | Description                                                                                          |
| ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| [`EpisodeFailure`](#nemo_gym-core-failure_records-EpisodeFailure)               | Describe a failure using the same fields on the wire and in saved records.                           |
| [`FailureBoundary`](#nemo_gym-core-failure_records-FailureBoundary)             | The places a failure is observed, as the failure-boundaries RFC names them.                          |
| [`FailureEvent`](#nemo_gym-core-failure_records-FailureEvent)                   | One observed failure: who observed it, what failed, and which failure caused it.                     |
| [`FailureReport`](#nemo_gym-core-failure_records-FailureReport)                 | The failures one reply forwards: its own failure (`failure_id`, absent on success) and every failure |
| [`ModelFailureDetails`](#nemo_gym-core-failure_records-ModelFailureDetails)     | Observed model-request facts, independent of retry or scoring policy.                                |
| [`SandboxFailureDetails`](#nemo_gym-core-failure_records-SandboxFailureDetails) | Observed sandbox facts; missing evidence stays absent, with no inferred cause or policy.             |
| [`SdkErrorDetails`](#nemo_gym-core-failure_records-SdkErrorDetails)             | Diagnostics an adapter selected from a third-party SDK error before it raised its own failure.       |

### Functions

| Name                                                                                      | Description                                                                                        |
| ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| [`validate_failure_event_map`](#nemo_gym-core-failure_records-validate_failure_event_map) | Check that `events` is keyed by event id and that every `cause_id` resolves in it without a cycle. |

### Data

[`FailureDetails`](#nemo_gym-core-failure_records-FailureDetails)

### API

```python
class nemo_gym.core.failure_records.EpisodeFailure()
```

**Bases:** `BaseModel`

Describe a failure using the same fields on the wire and in saved records.

An environment server returns this envelope for one failed episode attempt. Collection uses its
existing `terminal` flag and retry budget; `failure_kind` describes the failure without deciding
terminality. Kind and stage are optional because a collector observing a lost reply may not know them.

The attempt's `FailureReport` travels beside this envelope (`BaseEpisodeResponse.failure_report`), so
records that nest `EpisodeFailure` keep their saved format.

**`failure_kind`** `str | None = None`

---

**`failure_reason`** `str = Field(max_length=2000)`

---

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

---

**`stage`** `FailureStage | None = None`

---

**`terminal`** `bool`

---

```python
nemo_gym.core.failure_records.EpisodeFailure._serialize_failure(
    handler: pydantic.SerializerFunctionWrapHandler
)
```

```python
nemo_gym.core.failure_records.EpisodeFailure._validate_failure_kind(
    value: str | None
) -> str | None
```

classmethod

```python
class nemo_gym.core.failure_records.FailureBoundary
```

**Bases:** `enum.Enum`

The places a failure is observed, as the failure-boundaries RFC names them.

Each one reports what it observed; none of them chooses retries, terminality or scores.

**`AGENT`** `= 'agent'`

---

**`COLLECTION`** `= 'collection'`

---

**`ENVIRONMENT`** `= 'environment'`

---

**`HARNESS`** `= 'harness'`

---

**`MODEL`** `= 'model'`

---

**`RESOURCES_SERVER`** `= 'resources_server'`

---

**`SANDBOX`** `= 'sandbox'`

---

```python
class nemo_gym.core.failure_records.FailureEvent()
```

**Bases:** `BaseModel`

One observed failure: who observed it, what failed, and which failure caused it.

This is the validated wire projection of a `NgFailureEvent`, whose fields and origin identity live on
the exception itself. `cause_id` names another Gym failure in the same map. Foreign SDK facts live in
`foreign_error`; they are not boundary-less Gym events. Canonical projections validate kind and boundary
on both failed and recovered reports, independently of the legacy outcome envelope. `details` uses
the schema selected by `boundary`; common facts stay on this event, without a second copy in details.

Readers count each failure once by `event_id`. Outcome policy stays with `EpisodeFailure.terminal` and
the collector's existing rules.

**`attempt_id`** `str | None = Field(default=None, max_length=128)`

---

**`boundary`** `FailureBoundary`

---

**`cause_id`** `str | None = Field(default=None, max_length=128)`

---

**`details`** `FailureDetails | None = None`

---

**`event_id`** `str`

---

**`exception_type`** `str | None = Field(default=None, max_length=256)`

---

**`failure_kind`** `str`

---

**`failure_reason`** `str = Field(default='', max_length=2000)`

---

**`foreign_error`** `SdkErrorDetails | None = None`

---

**`http_status`** `int | None = Field(default=None, ge=100, le=599)`

---

**`model_config`** `= ConfigDict(extra='forbid', frozen=True)`

---

**`operation`** `str | None = Field(default=None, max_length=256)`

---

**`operation_id`** `str | None = Field(default=None, max_length=128)`

---

**`stage`** `FailureStage | None = None`

---

```python
nemo_gym.core.failure_records.FailureEvent._serialize_event(
    handler: pydantic.SerializerFunctionWrapHandler
)
```

```python
nemo_gym.core.failure_records.FailureEvent._validate_details(
    value: object,
    info: pydantic.ValidationInfo
) -> nemo_gym.core.failure_records.FailureDetails | None
```

classmethod

```python
nemo_gym.core.failure_records.FailureEvent._validate_failure_kind(
    value: str
) -> str
```

classmethod

```python
class nemo_gym.core.failure_records.FailureReport()
```

**Bases:** `BaseModel`

The failures one reply forwards: its own failure (`failure_id`, absent on success) and every failure
observed while serving the request, each with its explicit causes.

`omitted` says that a report on the way here was too large to send, so events are missing.
`nemo_gym.core.failure_transport` carries it in one HTTP header; Collection saves it beside the outcome.

**`events`** `dict[str, FailureEvent] = Field(default_factory=dict)`

---

**`failure_id`** `str | None = Field(default=None, max_length=128)`

---

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

---

**`omitted`** `bool = False`

---

```python
nemo_gym.core.failure_records.FailureReport._failure_id_is_an_event() -> typing_extensions.Self
```

```python
nemo_gym.core.failure_records.FailureReport._restore_event_ids(
    value
)
```

classmethod

```python
nemo_gym.core.failure_records.FailureReport._validate_events(
    value: dict[str, nemo_gym.core.failure_records.FailureEvent]
) -> dict[str, nemo_gym.core.failure_records.FailureEvent]
```

classmethod

```python
nemo_gym.core.failure_records.FailureReport.merged(
    other: nemo_gym.core.failure_records.FailureReport
) -> nemo_gym.core.failure_records.FailureReport
```

This report with `other`'s events added; `failure_id` stays this report's.

An event id names one immutable record, so the first copy of an id is kept.

```python
class nemo_gym.core.failure_records.ModelFailureDetails()
```

**Bases:** `BaseModel`

Observed model-request facts, independent of retry or scoring policy.

**`model_config`**

---

**`provider_request_id`** `str | None = Field(default=None, max_length=256)`

---

**`request_upload_complete`** `bool | None = None`

---

```python
class nemo_gym.core.failure_records.SandboxFailureDetails()
```

**Bases:** `BaseModel`

Observed sandbox facts; missing evidence stays absent, with no inferred cause or policy.

A last execution ID does not establish causation. Preserve provider reason codes as reported;
use only reported command exit codes, not synthesized fallback codes.

**`execution_id`** `str | None = Field(default=None, max_length=256)`

---

**`exit_code`** `int | None = None`

---

**`model_config`**

---

**`provider`** `str | None = Field(default=None, max_length=256)`

---

**`provider_status_reason`** `str | None = Field(default=None, max_length=2000)`

---

**`sandbox_id`** `str | None = Field(default=None, max_length=256)`

---

```python
class nemo_gym.core.failure_records.SdkErrorDetails()
```

**Bases:** `BaseModel`

Diagnostics an adapter selected from a third-party SDK error before it raised its own failure.

The adapter chooses the message (for example the SDK's `message` field), because `str()` of an SDK
error can contain request data. The SDK object, its request and its response body are never stored.

**`exception_type`** `str = Field(min_length=1, max_length=256)`

---

**`http_status`** `int | None = Field(default=None, ge=100, le=599)`

---

**`message`** `str = Field(max_length=2000)`

---

**`model_config`** `= ConfigDict(extra='forbid', frozen=True)`

---

```python
nemo_gym.core.failure_records.validate_failure_event_map(
    events: collections.abc.Mapping[str, nemo_gym.core.failure_records.FailureEvent]
) -> collections.abc.Mapping[str, nemo_gym.core.failure_records.FailureEvent]
```

Check that `events` is keyed by event id and that every `cause_id` resolves in it without a cycle.

```python
nemo_gym.core.failure_records.FailureDetails = SandboxFailureDetails | ModelFailureDetails
```