> 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.task_data

Per-server task-data schemas.

A resources server ships a `task_data.py` module next to its `app.py` exporting a single
symbol `TaskData`: either a Pydantic `BaseModel` subclass or a type (e.g. an
`Annotated[Union[...], Field(discriminator=...)]` alias) accepted by `pydantic.TypeAdapter`.
A self-contained agent (one that declares datasets but no `resources_server` reference) ships
the same module under `responses_api_agents/&lt;implementation&gt;/` and owns its rows' schema; an
agent WITH a reference uses that resources server's schema instead.
It describes the task-owned fields of that server's dataset rows, written FLAT in the planned
end-state shape: the fields as they will appear inside the unified `task_data` row key after
the row-format migration. Framework-owned keys (see `RESERVED_ROW_KEYS`) are never part of
`TaskData`, and neither is a `verifier_metadata` wrapper: rows that still carry one have its
contents spliced up by `normalize_task_fields` before validation, so one flat schema validates
both today's rows and post-migration `task_data` contents. Fields that today's wire reads
EXCLUSIVELY from inside `verifier_metadata` should be annotated
`Field(..., json_schema_extra=&#123;"legacy_location": "verifier_metadata"&#125;)` — that reverse map is
what the row-format migration and the dispatch compatibility shim consume, and validation flags
rows that carry such a field only top-level (the server would not see it). Servers whose wire
accepts both placements (e.g. via a before-validator that nests top-level fields itself) must
not carry the marker.

This module is a dependency-light leaf: it may import only the standard library and Pydantic, and
per-server `task_data.py` modules may import only the standard library, Pydantic, this module,
and other servers' `task_data` modules. That keeps schemas loadable by data tooling (collate,
`gym env schema`, dataset import) without installing any server's requirements.

Conventions for `TaskData` models:

* `model_config = ConfigDict(extra="allow")` by default. `extra="forbid"` is opt-in for
  servers that are already fail-closed. Pydantic's default `extra="ignore"` is banned: silently
  dropping row fields is the existing bug class this system exists to catch.
* Required-ness mirrors the server's wire contract (its verify/run request models), not what
  `verify()` happens to read. A field the wire requires stays required even if unread.
* Fields may carry `json_schema_extra=&#123;"consumed_by": [...]&#125;` with values from
  `&#123;"verify", "metrics", "prompt", "provenance"&#125;`. These tags are purely informational: they
  document what reads a field for humans inspecting `gym env schema` output, and no tooling
  consumes them. Fields that are JSON-encoded strings on the wire stay typed `str`.

## Module Contents

### Classes

| Name                                                                       | Description                                                                         |
| -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| [`TaskDataSchemaError`](#nemo_gym-task_data-TaskDataSchemaError)           | A server's `task_data.py` exists but does not satisfy the protocol.                 |
| [`TaskDataValidationReport`](#nemo_gym-task_data-TaskDataValidationReport) | Accumulated validation outcome for one dataset file against one server's schema.    |
| [`TaskDataValidator`](#nemo_gym-task_data-TaskDataValidator)               | Validates dataset rows against a server's `TaskData` schema, accumulating a report. |

### Functions

| Name                                                                   | Description                                                                                    |
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| [`find_server_dir`](#nemo_gym-task_data-find_server_dir)               | Locate `&lt;base_folder&gt;/&lt;server_name&gt;` via the shared component search roots.        |
| [`legacy_metadata_fields`](#nemo_gym-task_data-legacy_metadata_fields) | Schema fields annotated `legacy_location: verifier_metadata` (today's wire reads them there).  |
| [`load_task_data_schema`](#nemo_gym-task_data-load_task_data_schema)   | Load `&lt;server_dir&gt;/task_data.py` and return a `TypeAdapter` for its `TaskData`.          |
| [`normalize_task_fields`](#nemo_gym-task_data-normalize_task_fields)   | The task-owned subset of a dataset row, normalized to the flat end-state shape.                |
| [`validate_jsonl_rows`](#nemo_gym-task_data-validate_jsonl_rows)       | Validate an iterable of JSONL lines against one schema; entry point for whole-file validation. |

### Data

[`LEGACY_METADATA_KEY`](#nemo_gym-task_data-LEGACY_METADATA_KEY)

[`RESERVED_ROW_KEYS`](#nemo_gym-task_data-RESERVED_ROW_KEYS)

[`TASK_DATA_EXPORT_NAME`](#nemo_gym-task_data-TASK_DATA_EXPORT_NAME)

[`TASK_DATA_MODULE_NAME`](#nemo_gym-task_data-TASK_DATA_MODULE_NAME)

[`TASK_DATA_ROW_KEY`](#nemo_gym-task_data-TASK_DATA_ROW_KEY)

### API

```python
class nemo_gym.task_data.TaskDataSchemaError()
```

Exception

**Bases:** `Exception`

A server's `task_data.py` exists but does not satisfy the protocol.

```python
class nemo_gym.task_data.TaskDataValidationReport(
    server_name: str,
    dataset_fpath: str,
    rows: int = 0,
    error_rows: int = 0,
    errors: typing.List[str] = list(),
    unknown_keys: typing.Dict[str, int] = dict(),
    conflicting_keys: typing.Dict[str, int] = dict(),
    misplaced_keys: typing.Dict[str, int] = dict()
)
```

Dataclass

Accumulated validation outcome for one dataset file against one server's schema.

**`MAX_RECORDED_ERRORS`** `= 5`

---

**`clean`** `bool`

---

**`conflicting_keys`** `Dict[str, int] = field(default_factory=dict)`

---

**`dataset_fpath`** `str`

---

**`error_rows`** `int = 0`

---

**`errors`** `List[str] = field(default_factory=list)`

---

**`misplaced_keys`** `Dict[str, int] = field(default_factory=dict)`

---

**`rows`** `int = 0`

---

**`server_name`** `str`

---

**`unknown_keys`** `Dict[str, int] = field(default_factory=dict)`

---

```python
nemo_gym.task_data.TaskDataValidationReport.summary() -> str
```

```python
class nemo_gym.task_data.TaskDataValidator(
    server_name: str,
    adapter: pydantic.TypeAdapter,
    dataset_fpath: str
)
```

Validates dataset rows against a server's `TaskData` schema, accumulating a report.

**`_legacy_fields`** `= legacy_metadata_fields(adapter)`

---

**`report`**

---

```python
nemo_gym.task_data.TaskDataValidator.validate_row(
    row_index: int,
    row: typing.Dict[str, typing.Any]
) -> None
```

```python
nemo_gym.task_data.find_server_dir(
    server_name: str,
    base_folder: str = 'resources_servers'
) -> typing.Optional[pathlib.Path]
```

Locate `&lt;base_folder&gt;/&lt;server_name&gt;` via the shared component search roots.

Resolves against `_resolve_under_cwd_or_install` (extra plugin roots first, then cwd, then
the Gym install root), without the CLI's venv-marker requirement (a schema can exist for a
server whose venv was never set up). Self-contained agents (which verify in-process) keep
their schemas under `responses_api_agents/&lt;name&gt;/`.

```python
nemo_gym.task_data.legacy_metadata_fields(
    adapter: pydantic.TypeAdapter
) -> frozenset
```

Schema fields annotated `legacy_location: verifier_metadata` (today's wire reads them there).

```python
nemo_gym.task_data.load_task_data_schema(
    server_dir: pathlib.Path
) -> typing.Optional[pydantic.TypeAdapter]
```

Load `&lt;server_dir&gt;/task_data.py` and return a `TypeAdapter` for its `TaskData`.

Returns `None` when the module does not exist (the server has not adopted schemas yet).
Raises `TaskDataSchemaError` when the module exists but cannot be imported or does not
export a usable `TaskData`.

```python
nemo_gym.task_data.normalize_task_fields(
    row: typing.Dict[str, typing.Any]
) -> tuple[typing.Dict[str, typing.Any], typing.List[str]]
```

The task-owned subset of a dataset row, normalized to the flat end-state shape.

Drops framework keys, then splices the contents of a legacy `verifier_metadata` dict and of
a migrated `task_data` dict up to the top level (schemas are written flat, so fields
validate the same whether a row is flat, legacy-nested, or migrated).
A key present in two places with the same value is a harmless duplicate; with different
values it is ambiguous data and gets reported. Returns `(fields, conflicts)`.

```python
nemo_gym.task_data.validate_jsonl_rows(
    server_name: str,
    adapter: pydantic.TypeAdapter,
    dataset_fpath: str,
    lines: typing.Iterable[str]
) -> nemo_gym.task_data.TaskDataValidationReport
```

Validate an iterable of JSONL lines against one schema; entry point for whole-file validation.

```python
nemo_gym.task_data.LEGACY_METADATA_KEY = 'verifier_metadata'
```

```python
nemo_gym.task_data.RESERVED_ROW_KEYS = frozenset({'responses_create_params', 'agent_ref', 'task_source', '_ng_task_inde...
```

```python
nemo_gym.task_data.TASK_DATA_EXPORT_NAME = 'TaskData'
```

```python
nemo_gym.task_data.TASK_DATA_MODULE_NAME = 'task_data'
```

```python
nemo_gym.task_data.TASK_DATA_ROW_KEY = 'task_data'
```