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

Shared component-discovery helpers: which roots to scan for a component, how to resolve name
collisions across them, and how to read a component's `(domain, description)`.

Lives below the per-component registries (`registry.py`, `benchmarks.py`, `agent_registry.py`) so
they can share it without depending on each other. Reads configs only; never starts servers.

## Module Contents

### Classes

| Name                                                               | Description |
| ------------------------------------------------------------------ | ----------- |
| [`_ListingConfigParser`](#nemo_gym-discovery-_ListingConfigParser) | -           |

### Functions

| Name                                                                                                                 | Description                                                                                                   |
| -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| [`_parse_no_environment_tolerating_unset_values`](#nemo_gym-discovery-_parse_no_environment_tolerating_unset_values) | `parse_no_environment` for listing: fill unset `???`, `$&#123;...&#125;` and environment values (runtime-only |
| [`_scan_servers_for_metadata`](#nemo_gym-discovery-_scan_servers_for_metadata)                                       | Best-effort `(domain, description)` from a config mapping: the first of each found across all                 |
| [`discover_components`](#nemo_gym-discovery-discover_components)                                                     | Run `dir_scanning_fn` on `subdir` of every `component_search_roots` root and merge the results.               |
| [`iter_server_configs`](#nemo_gym-discovery-iter_server_configs)                                                     | Yield `(group_key, server_name, server_config)` for every server across all instances in a config.            |
| [`merge_by_name`](#nemo_gym-discovery-merge_by_name)                                                                 | Merge per-root `name -&gt; entry` mappings; earlier roots win on a collision (user shadows built-in),         |
| [`read_config_metadata`](#nemo_gym-discovery-read_config_metadata)                                                   | Shared `(domain, description)` reader for an environment *or* benchmark config. Two passes, because           |

### Data

[`_SERVER_GROUP_KEYS`](#nemo_gym-discovery-_SERVER_GROUP_KEYS)

[`_T`](#nemo_gym-discovery-_T)

[`_UNSET_VALUE_PLACEHOLDER`](#nemo_gym-discovery-_UNSET_VALUE_PLACEHOLDER)

### API

```python
class nemo_gym.discovery._ListingConfigParser()
```

**Bases:** [GlobalConfigDictParser](/nemo/gym/nemo-gym/nemo_gym/global_config#nemo_gym-global_config-GlobalConfigDictParser)

```python
nemo_gym.discovery._ListingConfigParser.raise_on_missing_values(
    global_config_dict: omegaconf.DictConfig
) -> None
```

```python
nemo_gym.discovery._parse_no_environment_tolerating_unset_values(
    initial_config_dict: omegaconf.DictConfig
) -> omegaconf.DictConfig
```

`parse_no_environment` for listing: fill unset `???`, `$&#123;...&#125;` and environment values (runtime-only
things like API keys/endpoints) with a placeholder so the config still resolves enough to identify the
component. Never mutates the input; other config and resolver errors propagate.

```python
nemo_gym.discovery._scan_servers_for_metadata(
    container
) -> typing.Tuple[typing.Optional[str], typing.Optional[str]]
```

Best-effort `(domain, description)` from a config mapping: the first of each found across all
server groups. Never raises.

```python
nemo_gym.discovery.discover_components(
    subdir: str,
    dir_scanning_fn: typing.Callable[[Path], typing.Dict[str, nemo_gym.discovery._T]]
) -> typing.Dict[str, nemo_gym.discovery._T]
```

Run `dir_scanning_fn` on `subdir` of every `component_search_roots` root and merge the results.

The shared body of `discover_environments`/`discover_agents`/`discover_models`/
`discover_benchmarks`: each passes its `&lt;type&gt;/` subdir and a single-directory scan function, and
gets user-shadows-built-in merging (via `merge_by_name`) for free.

```python
nemo_gym.discovery.iter_server_configs(
    container
)
```

Yield `(group_key, server_name, server_config)` for every server across all instances in a config.

Walks each top-level instance and each known server group.
It is defensive against malformed shapes and never raises.

```python
nemo_gym.discovery.merge_by_name(
    per_root: typing.Iterable[typing.Dict[str, nemo_gym.discovery._T]]
) -> typing.Dict[str, nemo_gym.discovery._T]
```

Merge per-root `name -&gt; entry` mappings; earlier roots win on a collision (user shadows built-in),
matching `component_search_roots` precedence. Insertion order preserved.

```python
nemo_gym.discovery.read_config_metadata(
    config_path: pathlib.Path
) -> typing.Tuple[typing.Optional[str], typing.Optional[str]]
```

Shared `(domain, description)` reader for an environment *or* benchmark config. Two passes, because
the two declare metadata differently:

1. Raw (non-resolving) scan — environment configs declare it inline, and this is safe even though they
   reference servers defined elsewhere (resolving in isolation would raise).
2. Resolving fallback for whatever's still unset — benchmark configs inherit it via
   `config_paths`/`_inherit_from`. Tolerates unset runtime values; on failure keeps the raw result.

Never raises: an unreadable/unresolvable config yields `(None, None)`.

```python
nemo_gym.discovery._SERVER_GROUP_KEYS = ('resources_servers', 'responses_api_agents', 'responses_api_models', 'environme...
```

```python
nemo_gym.discovery._T = TypeVar('_T')
```

```python
nemo_gym.discovery._UNSET_VALUE_PLACEHOLDER = '__unset_for_listing__'
```