> 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

### 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 `???` and undefined `$&#123;...&#125;` 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 :func:`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
nemo_gym.discovery._parse_no_environment_tolerating_unset_values(
    initial_config_dict: omegaconf.DictConfig
) -> omegaconf.DictConfig
```

`parse_no_environment` for listing: fill unset `???` and undefined `$&#123;...&#125;` 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; errors other than those two 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 :func:`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 :func:`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 a loaded config mapping (each top-level instance -> its `resources_servers`/
`responses_api_agents`/`responses_api_models` group -> each server). Defensive against malformed
shapes, so it never raises. The shared primitive behind metadata reads and the inspect deep-parse.

```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 :func:`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')
```

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

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