> 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.data.tasks.harbor.compose_overlay

The job-level Compose overlay: extra Compose files, MCP entries and a prompt template from the run config.

Harbor's job config names `extra_docker_compose` files, appended to every task's `docker compose -f ...`
command after the task's own file, `agents[].mcp_servers` entries, merged by name with the task's
`[[environment.mcp_servers]]` (`trial.py` `_init_agent` at v0.22.0: the task's first, the job's after,
so a job entry replaces a task entry of the same name), and a `prompt_template_path` the agent renders
around the instruction (`harbor/utils/templating.py`). Gym reads the same three things from one block of
the environment server's configuration, `compose_overlay` (`HarborComposeOverlay`), and changes no
task file.

The overlay files are merged onto the task's Compose document the way Compose merges `-f` files
(`merge_compose`): mappings key by key, sequences appended (`ports`, `volumes`, `expose` and the
other keyed sequences unique on their key, the later entry winning), `environment`, `labels` and
`extra_hosts` as mappings whatever their form, `depends_on` and `networks` as mappings, `command`,
`entrypoint` and `healthcheck.test` replaced whole, every scalar by the last file, a key written with no
value keeping the earlier file's value. The `!reset` tag removes the attribute the earlier files set
(`ports: !reset []`, `networks.x.name: !reset null`, `environment: {DISABLE_AUTH: !reset}`) and
`!override` replaces a value instead of merging into it, as Compose defines both. Each file is
interpolated with the same variables as the task's file (Harbor's one `docker compose` command sees one
environment). Its relative paths resolve against the project directory, the task's `environment` folder, never
against the overlay file's own folder: Compose resolves the relative paths of every `-f` file against the project
directory (compose-go's `ResolveRelativePaths` with the one `WorkingDir`; "Paths in override files are not
resolved relative to the override file's own location"), and Harbor runs `docker compose --project-directory <task>/environment -f <task file> -f <extra> ...`, so an extra file's `./x.env` or `./conf` is the task's
`environment/x.env` or `environment/conf` under Harbor and here alike (the memory team's
`${GRAPHITI_CONFIG_PATH:-./config.yaml}` and `${MAMS_HARBOR_MCP_BUILD_CONTEXT:-./mcp-server}` defaults land in the
task folder under both; set the variable to an absolute path, as the Harbor job must). An overlay file's
`env_file` entries are resolved to absolute paths before the merge (`absolute_env_files`) and the env files of
the merged document are read once after it, the merged `environment` winning over them, as Compose reads them (so
a later file's `env_file: !reset []` drops the earlier files' lists, the task's included). An overlay file is the
operator's, so its `env_file` may name any readable file (an absolute path anywhere, a relative one wherever it
lands), where a task's must stay under the task; its bind sources are left for the volume planner, which resolves a
relative source against the same folder for every file of the group.
A task that declares only an image gets a one-service group, `main`, for the overlay to extend
(`synthesized_group`).

## Module Contents

### Classes

| Name                                                                                       | Description                                                                                                |
| ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
| [`ComposeOverlayError`](#nemo_gym-data-tasks-harbor-compose_overlay-ComposeOverlayError)   | An overlay file, its merge or the prompt template cannot be applied as written.                            |
| [`HarborComposeOverlay`](#nemo_gym-data-tasks-harbor-compose_overlay-HarborComposeOverlay) | The `compose_overlay` block of the Harbor environment server's configuration.                              |
| [`OverrideMapping`](#nemo_gym-data-tasks-harbor-compose_overlay-OverrideMapping)           | A mapping tagged `!override`: it replaces the earlier files' value instead of merging into it.             |
| [`OverrideScalar`](#nemo_gym-data-tasks-harbor-compose_overlay-OverrideScalar)             | A scalar tagged `!override`. A scalar replaces the earlier value whether tagged or not, so the tag changes |
| [`OverrideSequence`](#nemo_gym-data-tasks-harbor-compose_overlay-OverrideSequence)         | A sequence tagged `!override`: it replaces the earlier files' value instead of appending to it.            |
| [`_OverlayLoader`](#nemo_gym-data-tasks-harbor-compose_overlay-_OverlayLoader)             | `yaml.SafeLoader` that reads Compose's `!reset` and `!override` tags.                                      |
| [`_Reset`](#nemo_gym-data-tasks-harbor-compose_overlay-_Reset)                             | A `!reset` value: the attribute the earlier files set is removed; the value written after the tag is       |

### Functions

| Name                                                                                                             | Description                                                                                                     |
| ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| [`_construct_override`](#nemo_gym-data-tasks-harbor-compose_overlay-_construct_override)                         | -                                                                                                               |
| [`_construct_reset`](#nemo_gym-data-tasks-harbor-compose_overlay-_construct_reset)                               | -                                                                                                               |
| [`_dependencies`](#nemo_gym-data-tasks-harbor-compose_overlay-_dependencies)                                     | -                                                                                                               |
| [`_jinja`](#nemo_gym-data-tasks-harbor-compose_overlay-_jinja)                                                   | The `jinja2` module, imported when a prompt template is read: the Harbor environment server's own               |
| [`_mapping_key`](#nemo_gym-data-tasks-harbor-compose_overlay-_mapping_key)                                       | -                                                                                                               |
| [`_match`](#nemo_gym-data-tasks-harbor-compose_overlay-_match)                                                   | -                                                                                                               |
| [`_merge`](#nemo_gym-data-tasks-harbor-compose_overlay-_merge)                                                   | -                                                                                                               |
| [`_networks`](#nemo_gym-data-tasks-harbor-compose_overlay-_networks)                                             | -                                                                                                               |
| [`_pairs`](#nemo_gym-data-tasks-harbor-compose_overlay-_pairs)                                                   | A keyed field (`environment`, `labels`, `extra_hosts`, ...) as a mapping, whichever form it was                 |
| [`_passthrough_entry`](#nemo_gym-data-tasks-harbor-compose_overlay-_passthrough_entry)                           | A list-form `environment` entry, a bare `NAME` that `variables` sets resolved to `NAME=value`.                  |
| [`_plain`](#nemo_gym-data-tasks-harbor-compose_overlay-_plain)                                                   | `value` without markers: `!override` wrappers become plain mappings, sequences and strings, and a               |
| [`_port_key`](#nemo_gym-data-tasks-harbor-compose_overlay-_port_key)                                             | -                                                                                                               |
| [`_shape_error`](#nemo_gym-data-tasks-harbor-compose_overlay-_shape_error)                                       | A field of the wrong shape, named by its path and the value's type, never the value: a mistyped                 |
| [`_text_key`](#nemo_gym-data-tasks-harbor-compose_overlay-_text_key)                                             | -                                                                                                               |
| [`_unique`](#nemo_gym-data-tasks-harbor-compose_overlay-_unique)                                                 | `items` with one entry per key, the last one written, in the order its key first appeared.                      |
| [`_volume_key`](#nemo_gym-data-tasks-harbor-compose_overlay-_volume_key)                                         | -                                                                                                               |
| [`_yaml_problem`](#nemo_gym-data-tasks-harbor-compose_overlay-_yaml_problem)                                     | What went wrong and where, without PyYAML's snippet of the offending line: an overlay file commonly carries the |
| [`absolute_env_files`](#nemo_gym-data-tasks-harbor-compose_overlay-absolute_env_files)                           | Each overlay service's `env_file` entries replaced by the absolute paths they resolve to against                |
| [`compile_prompt_template`](#nemo_gym-data-tasks-harbor-compose_overlay-compile_prompt_template)                 | The template at `path` compiled once it passes `validate_prompt_template`: a `jinja2.Template` from             |
| [`interpolate_overlay`](#nemo_gym-data-tasks-harbor-compose_overlay-interpolate_overlay)                         | `document` with `substitute` applied to every string, the `!reset` and `!override` markers kept.                |
| [`load_overlay_file`](#nemo_gym-data-tasks-harbor-compose_overlay-load_overlay_file)                             | Read one overlay Compose file (`load_overlay_text`); a file that cannot be read raises                          |
| [`load_overlay_text`](#nemo_gym-data-tasks-harbor-compose_overlay-load_overlay_text)                             | Parse one overlay Compose document with its `!reset` and `!override` tags, as a mapping.                        |
| [`merge_compose`](#nemo_gym-data-tasks-harbor-compose_overlay-merge_compose)                                     | `overlay` merged onto `base` as Compose merges a later `-f` file onto the earlier ones.                         |
| [`merged_mcp_servers`](#nemo_gym-data-tasks-harbor-compose_overlay-merged_mcp_servers)                           | The task's MCP entries with the overlay's merged in by name, in Harbor's order and with Harbor's rule.          |
| [`overlay_env_file_resolver`](#nemo_gym-data-tasks-harbor-compose_overlay-overlay_env_file_resolver)             | The resolver of an overlay file's `env_file` entries: a relative path against `project_dir`, the Compose        |
| [`overlay_files`](#nemo_gym-data-tasks-harbor-compose_overlay-overlay_files)                                     | The overlay's Compose files, `[]` without an overlay.                                                           |
| [`render_compiled_template`](#nemo_gym-data-tasks-harbor-compose_overlay-render_compiled_template)               | `instruction` rendered into `template` (`compile_prompt_template`), `path` named in the errors:                 |
| [`render_prompt_template`](#nemo_gym-data-tasks-harbor-compose_overlay-render_prompt_template)                   | `instruction` rendered into the template at `path`, as Harbor renders it (`render_prompt_template` in           |
| [`resolve_passthrough_environment`](#nemo_gym-data-tasks-harbor-compose_overlay-resolve_passthrough_environment) | `document` with each overlay service's pass-through `environment` entries resolved against `variables`:         |
| [`synthesized_group`](#nemo_gym-data-tasks-harbor-compose_overlay-synthesized_group)                             | The Compose document of a task that declares no Compose file: Harbor's own `main` service, nothing else.        |
| [`validate_prompt_template`](#nemo_gym-data-tasks-harbor-compose_overlay-validate_prompt_template)               | The template's text, once it parses and names `instruction`; otherwise `ComposeOverlayError`.                   |
| [`wrap_first_instruction`](#nemo_gym-data-tasks-harbor-compose_overlay-wrap_first_instruction)                   | A copy of `responses_create_params` with `render` applied to the instruction: the first user message's          |

### Data

[`OVERRIDE_TAG`](#nemo_gym-data-tasks-harbor-compose_overlay-OVERRIDE_TAG)

[`PROMPT_TEMPLATE_VARIABLE`](#nemo_gym-data-tasks-harbor-compose_overlay-PROMPT_TEMPLATE_VARIABLE)

[`RESET`](#nemo_gym-data-tasks-harbor-compose_overlay-RESET)

[`RESET_TAG`](#nemo_gym-data-tasks-harbor-compose_overlay-RESET_TAG)

[`_DEPENDS_ON`](#nemo_gym-data-tasks-harbor-compose_overlay-_DEPENDS_ON)

[`_ENV_KEY`](#nemo_gym-data-tasks-harbor-compose_overlay-_ENV_KEY)

[`_KEYED_MAPPINGS`](#nemo_gym-data-tasks-harbor-compose_overlay-_KEYED_MAPPINGS)

[`_REPLACED`](#nemo_gym-data-tasks-harbor-compose_overlay-_REPLACED)

[`_SERVICE_NETWORKS`](#nemo_gym-data-tasks-harbor-compose_overlay-_SERVICE_NETWORKS)

[`_UNIQUE_SEQUENCES`](#nemo_gym-data-tasks-harbor-compose_overlay-_UNIQUE_SEQUENCES)

### API

```python
class nemo_gym.data.tasks.harbor.compose_overlay.ComposeOverlayError()
```

**Bases:** `ValueError`

An overlay file, its merge or the prompt template cannot be applied as written.

```python
class nemo_gym.data.tasks.harbor.compose_overlay.HarborComposeOverlay()
```

**Bases:** `BaseModel`

The `compose_overlay` block of the Harbor environment server's configuration.

`files` are Compose files merged, in order, onto every task's Compose document after the task's own
file (Harbor's `extra_docker_compose`); `mcp_servers` are MCP entries merged by name with the task's
`[[environment.mcp_servers]]`, the overlay's winning on a name (Harbor's `agents[].mcp_servers`);
`prompt_template` is a Jinja template with an `{{ instruction }}` variable, rendered around the
instruction of the episode's first turn (Harbor's `prompt_template_path`). Every path must exist when
the block is read: Harbor refuses a missing extra Compose file the same way (`FileNotFoundError` in
`_normalize_extra_docker_compose_paths`), and a template that does not name `instruction` or does
not parse is refused before any episode, as Harbor's `render_prompt_template` refuses it. The template
is read and compiled once, when the block is read (`compile_prompt_template`); every episode renders
the compiled template (`render_instruction`), so the file is not re-read per episode and an edit to it
under a running server is not seen until the server is started again.

**`_compiled_template`** `Any = PrivateAttr(default=None)`

---

**`configured`** `bool`

---

**`files`** `list[Path] = Field(default_factory=list)`

---

**`mcp_servers`** `list[HarborMCPServer] = Field(default_factory=list)`

---

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

---

**`prompt_template`** `Path | None = None`

---

```python
nemo_gym.data.tasks.harbor.compose_overlay.HarborComposeOverlay.compile_template() -> 'HarborComposeOverlay'
```

Read, check and compile the template once, when the block is read: a template that does not parse or
does not name `instruction` is refused here, before any episode.

```python
nemo_gym.data.tasks.harbor.compose_overlay.HarborComposeOverlay.existing_files(
    files: list[pathlib.Path]
) -> list[pathlib.Path]
```

classmethod

```python
nemo_gym.data.tasks.harbor.compose_overlay.HarborComposeOverlay.readable_template(
    path: pathlib.Path | None
) -> pathlib.Path | None
```

classmethod

```python
nemo_gym.data.tasks.harbor.compose_overlay.HarborComposeOverlay.render_instruction(
    instruction: str
) -> str
```

`instruction` rendered into the compiled `prompt_template` (`render_compiled_template`); the
instruction as given when the block names no template.

```python
class nemo_gym.data.tasks.harbor.compose_overlay.OverrideMapping()
```

**Bases:** `dict`

A mapping tagged `!override`: it replaces the earlier files' value instead of merging into it.

```python
class nemo_gym.data.tasks.harbor.compose_overlay.OverrideScalar()
```

**Bases:** `str`

A scalar tagged `!override`. A scalar replaces the earlier value whether tagged or not, so the tag changes
nothing where the value stays a scalar; it is kept for the fields whose scalar form is a one-entry sequence
(`env_file: !override /path/x.env`, `tmpfs`, `dns`), which the normalization before the merge turns into
an `OverrideSequence`, so the one entry replaces the earlier files' lists instead of appending to them,
as the tagged list form does. `_plain` leaves a plain `str` in the merged document.

```python
class nemo_gym.data.tasks.harbor.compose_overlay.OverrideSequence()
```

**Bases:** `list`

A sequence tagged `!override`: it replaces the earlier files' value instead of appending to it.

```python
class nemo_gym.data.tasks.harbor.compose_overlay._OverlayLoader()
```

**Bases:** `SafeLoader`

`yaml.SafeLoader` that reads Compose's `!reset` and `!override` tags.

```python
class nemo_gym.data.tasks.harbor.compose_overlay._Reset()
```

A `!reset` value: the attribute the earlier files set is removed; the value written after the tag is
not read (Compose ignores it too).

```python
nemo_gym.data.tasks.harbor.compose_overlay._Reset.__repr__() -> str
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._construct_override(
    loader: yaml.SafeLoader,
    node: yaml.Node
) -> typing.Any
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._construct_reset(
    loader: yaml.SafeLoader,
    node: yaml.Node
) -> nemo_gym.data.tasks.harbor.compose_overlay._Reset
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._dependencies(
    value: typing.Any,
    path: tuple[str, ...]
) -> dict[str, typing.Any]
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._jinja() -> typing.Any
```

The `jinja2` module, imported when a prompt template is read: the Harbor environment server's own
requirements install it (Harbor renders its templates with Jinja2 too); the loader and the harnesses that import
this module for the merge and the MCP rule do not need it.

```python
nemo_gym.data.tasks.harbor.compose_overlay._mapping_key(
    field: str
) -> collections.abc.Callable[[Any], typing.Any]
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._match(
    path: tuple[str, ...],
    pattern: tuple[str, ...]
) -> bool
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._merge(
    base: typing.Any,
    over: typing.Any,
    path: tuple[str, ...]
) -> typing.Any
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._networks(
    value: typing.Any,
    path: tuple[str, ...]
) -> dict[str, typing.Any]
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._pairs(
    value: typing.Any,
    path: tuple[str, ...]
) -> dict[str, typing.Any]
```

A keyed field (`environment`, `labels`, `extra_hosts`, ...) as a mapping, whichever form it was
written in: a list of `KEY=value` (`KEY` alone is `None`; `host:ip` for `extra_hosts`).

```python
nemo_gym.data.tasks.harbor.compose_overlay._passthrough_entry(
    item: typing.Any,
    variables: collections.abc.Mapping[str, str]
) -> typing.Any
```

A list-form `environment` entry, a bare `NAME` that `variables` sets resolved to `NAME=value`.

```python
nemo_gym.data.tasks.harbor.compose_overlay._plain(
    value: typing.Any
) -> typing.Any
```

`value` without markers: `!override` wrappers become plain mappings, sequences and strings, and a
`!reset` under a key no earlier file set leaves the key absent, as Compose leaves it.

```python
nemo_gym.data.tasks.harbor.compose_overlay._port_key(
    entry: typing.Any
) -> typing.Any
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._shape_error(
    path: tuple[str, ...],
    value: typing.Any
) -> nemo_gym.data.tasks.harbor.compose_overlay.ComposeOverlayError
```

A field of the wrong shape, named by its path and the value's type, never the value: a mistyped
`environment: API_KEY=...` would otherwise print the key in prepare's `skipped.json` and the seed's reason.

```python
nemo_gym.data.tasks.harbor.compose_overlay._text_key(
    entry: typing.Any
) -> typing.Any
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._unique(
    items: list[typing.Any],
    key: collections.abc.Callable[[Any], typing.Any]
) -> list[typing.Any]
```

`items` with one entry per key, the last one written, in the order its key first appeared.

```python
nemo_gym.data.tasks.harbor.compose_overlay._volume_key(
    entry: typing.Any
) -> typing.Any
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._yaml_problem(
    exc: yaml.YAMLError
) -> str
```

What went wrong and where, without PyYAML's snippet of the offending line: an overlay file commonly carries the
deployment's keys, and the message reaches prepare's `skipped.json` and the seed's failure reason. A
`MarkedYAMLError` gives its context and problem (PyYAML's own phrases, no source text) with the line and column;
any other error only its kind.

```python
nemo_gym.data.tasks.harbor.compose_overlay.absolute_env_files(
    document: typing.Any,
    project_dir: pathlib.Path
) -> typing.Any
```

Each overlay service's `env_file` entries replaced by the absolute paths they resolve to against
`project_dir` (`overlay_env_file_resolver`,
`resolve_env_file_entries`), the files neither read nor checked to
exist yet: Compose resolves each file's paths before the merge and reads the env files of the merged document once
after it, the merged `environment` winning, so a file a later overlay file drops (`env_file: !reset []`) is
never required to exist and a missing one that survives the merge is reported at that reading. The markers are
kept for the merge: `env_file: !reset []` and a service tagged `!reset` pass through, an `!override` list
keeps its tag and an `!override` scalar becomes an `!override` list of its one path (so it replaces the earlier
files' lists, as Compose applies the tag), and `environment` is not touched (its own `!reset` and `!override`
markers, and its normalization, are the merge's and the final reading's). Bind sources are not rewritten: the
volume planner resolves a relative source of any file against the same project directory, which is where Compose
puts it.

```python
nemo_gym.data.tasks.harbor.compose_overlay.compile_prompt_template(
    path: pathlib.Path
) -> typing.Any
```

The template at `path` compiled once it passes `validate_prompt_template`: a `jinja2.Template` from
an environment with `StrictUndefined`, as Harbor's `render_prompt_template` builds one per render. The block
compiles its template when it is read and renders the compiled one per episode, so the file is read and parsed
once, not twice per episode on the event loop.

```python
nemo_gym.data.tasks.harbor.compose_overlay.interpolate_overlay(
    document: typing.Any,
    substitute: collections.abc.Callable[[str], str]
) -> typing.Any
```

`document` with `substitute` applied to every string, the `!reset` and `!override` markers kept.

Compose interpolates each `-f` file against the command's environment before merging, so the overlay
files take the same `substitute` as the task's file (the task's resolver, with the deployment's values
when the server has them).

```python
nemo_gym.data.tasks.harbor.compose_overlay.load_overlay_file(
    path: pathlib.Path
) -> dict[str, typing.Any]
```

Read one overlay Compose file (`load_overlay_text`); a file that cannot be read raises
`ComposeOverlayError` naming it.

```python
nemo_gym.data.tasks.harbor.compose_overlay.load_overlay_text(
    text: str,
    source: str = '<overlay>'
) -> dict[str, typing.Any]
```

Parse one overlay Compose document with its `!reset` and `!override` tags, as a mapping.

Top-level extension fields (`x-*`, the anchors a file keeps its shared blocks under) are dropped, as
Compose ignores them; their aliases were expanded at parse time. Anything that is not a mapping, or does
not parse, raises `ComposeOverlayError` naming `source`.

```python
nemo_gym.data.tasks.harbor.compose_overlay.merge_compose(
    base: collections.abc.Mapping[str, typing.Any],
    overlay: collections.abc.Mapping[str, typing.Any]
) -> dict[str, typing.Any]
```

`overlay` merged onto `base` as Compose merges a later `-f` file onto the earlier ones.

Mappings merge key by key; sequences are appended, the keyed ones (`ports` by host, published port,
target and protocol; `volumes` by target; `expose`, `cap_add`, `env_file`, ... by value) unique
on their key with the later entry winning; `environment`, `labels`, `extra_hosts`, `sysctls` and
`build.args` merge as mappings in either written form; `depends_on` and a service's `networks` merge
as mappings (a list becomes `{name: {condition: service_started}}` and `{name: null}`); `command`,
`entrypoint` and `healthcheck.test` are replaced whole; any other scalar, or a value of another shape,
is the later file's. A key written with no value in a later file (a bare `memory_net:` under
`networks` re-declaring one an earlier file configured) keeps the earlier value, as compose-go keeps it for a nil override, except
on the replaced paths. A `!reset` removes the key the earlier files set; an `!override` mapping or
sequence replaces instead of merging. Neither input is modified; the result carries no markers.

```python
nemo_gym.data.tasks.harbor.compose_overlay.merged_mcp_servers(
    task_servers: collections.abc.Iterable[nemo_gym.data.tasks.harbor.models.HarborMCPServer],
    overlay: nemo_gym.data.tasks.harbor.compose_overlay.HarborComposeOverlay | None
) -> list[nemo_gym.data.tasks.harbor.models.HarborMCPServer]
```

The task's MCP entries with the overlay's merged in by name, in Harbor's order and with Harbor's rule.

`trial.py` `_init_agent` (v0.22.0) builds `{server.name: server}` over the task's
`environment.mcp_servers` followed by the job's `agent.mcp_servers` and hands the agent the values:
a job entry replaces a task entry of the same name, in the task entry's position; a new name goes last.

```python
nemo_gym.data.tasks.harbor.compose_overlay.overlay_env_file_resolver(
    project_dir: pathlib.Path
) -> nemo_gym.data.tasks.harbor.compose_variables.EnvFileResolver
```

The resolver of an overlay file's `env_file` entries: a relative path against `project_dir`, the Compose
project directory (the task's `environment` folder, Harbor's `--project-directory`; Compose resolves every
`-f` file's relative paths against it, not against the file that wrote them), an absolute path as written, any
readable file accepted. The overlay files are the operator's own, named in the run config, so the containment rule
of a task's entries (third-party content that prepare digested,
`under_task_folder`) does not apply, as it does not under Harbor,
where Compose reads an extra file's env files from wherever they are.

```python
nemo_gym.data.tasks.harbor.compose_overlay.overlay_files(
    overlay: nemo_gym.data.tasks.harbor.compose_overlay.HarborComposeOverlay | None
) -> list[pathlib.Path]
```

The overlay's Compose files, `[]` without an overlay.

```python
nemo_gym.data.tasks.harbor.compose_overlay.render_compiled_template(
    template: typing.Any,
    path: pathlib.Path,
    instruction: str
) -> str
```

`instruction` rendered into `template` (`compile_prompt_template`), `path` named in the errors:
`instruction` is the one variable, so a variable the template names and nothing defines raises
`ComposeOverlayError` (`StrictUndefined`), as under Harbor.

```python
nemo_gym.data.tasks.harbor.compose_overlay.render_prompt_template(
    path: pathlib.Path,
    instruction: str
) -> str
```

`instruction` rendered into the template at `path`, as Harbor renders it (`render_prompt_template` in
`harbor/utils/templating.py` at v0.22.0): Jinja2 with `StrictUndefined`, `instruction` the one
variable. Reads and compiles the file on every call; the server renders a block's compiled template instead
(`HarborComposeOverlay.render_instruction`).

```python
nemo_gym.data.tasks.harbor.compose_overlay.resolve_passthrough_environment(
    document: typing.Any,
    variables: collections.abc.Mapping[str, str]
) -> typing.Any
```

`document` with each overlay service's pass-through `environment` entries resolved against `variables`:
a bare `NAME` in the list form becomes `NAME=value` and a `NAME:` with no value in the mapping form becomes
`NAME: value` for every name `variables` sets; a name it does not set stays bare, and the markers are kept (a
`!reset` entry, an `!override` list or mapping, a service tagged either).

Compose resolves the pass-through spelling against the command's environment, the one every `-f` file is
interpolated with, so under Harbor it follows the order of `${NAME}` in every file. Gym reads the merged
document's pass-through entries once, after the merge, against the task's variables
(`inline_env_files`), which is the task's own file's order; an overlay
file's entries follow the overlay's order (the deployment's values above the task's sources) only when they are
resolved before the merge, here, with the variables `interpolate_overlay` took for the same file. A name
nobody sets stays bare and is read after the merge as the task's entries are: unset in the container.

```python
nemo_gym.data.tasks.harbor.compose_overlay.synthesized_group(
    task: nemo_gym.data.tasks.harbor.task.HarborTask | None = None
) -> dict[str, typing.Any]
```

The Compose document of a task that declares no Compose file: Harbor's own `main` service, nothing else.

Harbor starts every task through Compose (its base file plus the build or prebuilt file define `main`),
so an extra Compose file extends a plain image task the same way it extends a Compose task. The sandbox
adapter fills `main`'s image and command from the task, as it does for a task file that leaves them out.

For a Dockerfile task whose image configuration the loader resolved, preserve its ENTRYPOINT (including an explicit
clear) and final WORKDIR instead of inheriting the base image's settings. The server's setup hook runs
any Dockerfile RUN lines and installs the image's startup user before the command and healthcheck. The
adapter defers the working directory until that setup finishes, so it cannot create a root-owned directory
ahead of a non-root Dockerfile WORKDIR step. No `user` is written here: numeric users need resolution
inside the running image, while an explicit user in a job overlay still wins over the installed default.

`[agent].user` and `[environment].workdir` apply to the agent's later commands, independently of startup.
A prebuilt `docker_image` task without a Dockerfile is left to image resolution; without a task, main is empty.

```python
nemo_gym.data.tasks.harbor.compose_overlay.validate_prompt_template(
    path: pathlib.Path
) -> str
```

The template's text, once it parses and names `instruction`; otherwise `ComposeOverlayError`.

Harbor's `render_prompt_template` refuses a template that does not use the `instruction` variable
(the task's instruction would be lost) and renders with `StrictUndefined`, so any other variable fails.

```python
nemo_gym.data.tasks.harbor.compose_overlay.wrap_first_instruction(
    responses_create_params: collections.abc.Mapping[str, typing.Any],
    render: collections.abc.Callable[[str], str]
) -> dict[str, typing.Any]
```

A copy of `responses_create_params` with `render` applied to the instruction: the first user message's
text (a string content, or the text of its first `input_text` part). The row's instruction is that
message, as the loader materializes it; a request without one raises `ComposeOverlayError`.

```python
nemo_gym.data.tasks.harbor.compose_overlay.OVERRIDE_TAG = '!override'
```

```python
nemo_gym.data.tasks.harbor.compose_overlay.PROMPT_TEMPLATE_VARIABLE = 'instruction'
```

```python
nemo_gym.data.tasks.harbor.compose_overlay.RESET = _Reset()
```

```python
nemo_gym.data.tasks.harbor.compose_overlay.RESET_TAG = '!reset'
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._DEPENDS_ON = ('services', '*', 'depends_on')
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._ENV_KEY = re.compile('^[^=]+')
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._KEYED_MAPPINGS = (('services', '*', 'environment'), ('services', '*', 'labels'), ('services', '*'...
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._REPLACED = (('services', '*', 'command'), ('services', '*', 'entrypoint'), ('services', '*'...
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._SERVICE_NETWORKS = ('services', '*', 'networks')
```

```python
nemo_gym.data.tasks.harbor.compose_overlay._UNIQUE_SEQUENCES: dict[tuple[str, ...], Callable[[Any], Any]] = {}
```