Skip to content

feat(config): deprecate agents without an environment server and fix the migration script - #3929

Merged
yaoyu-33 merged 4 commits into
mainfrom
ananthsub/auto-legacy-environment-servers
Oct 5, 2026
Merged

yaoyu-33 merged 4 commits into
mainfrom
ananthsub/auto-legacy-environment-servers

Conversation

@ananthsub

@ananthsub ananthsub commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Since #3724, config validation rejects any agent that no environment server names, with AgentWithoutEnvironmentServerError. A config written before environment servers therefore stops loading when Gym is upgraded. This also affects configs in this repository: resources_servers/wordle/configs/wordle.yaml and environments/wordle/config.yaml fail to parse on main.

This PR turns that error into a deprecation, so these configs keep running while users migrate. It also fixes the migration script so it can update configs outside this repository.

What happens to an agent without an environment server

During config parsing, Gym finds each agent that no environment server names. For each one, it adds a legacy_agent environment server in front of the agent. A legacy_agent server relays /run to its agent unchanged, so rollouts and results are the same as before. Gym then logs one deprecation warning:

DEPRECATED: agents without an environment server: `workplace_assistant_simple_agent`.
Rollout collection reaches an agent only through an environment server.
This run generated a legacy_agent environment server for each agent above.
Each one relays /run to its agent unchanged, so results are not affected.
A future release will reject these configs with AgentWithoutEnvironmentServerError.

To migrate, declare the environment servers in your config. Either:

1. Run the migration script from a NeMo Gym checkout on your config files or directories:

     python scripts/add_legacy_agent_environment_servers.py path/to/config.yaml [more paths] [--check]

   --check reports what would change without writing.

2. Or add these blocks yourself, at the same level as the agents:

     workplace_assistant_environment_server:
       environment_servers:
         legacy_agent:
           entrypoint: app.py
           agent_server:
             type: responses_api_agents
             name: workplace_assistant_simple_agent

To make this an error now, set error_on_agent_without_environment_server: true in your config.
You can also pass +error_on_agent_without_environment_server=true on the command line.
  • The generated server is named the way the migration script names it: the agent type is stripped from the agent name. For example, workplace_assistant_simple_agent gets workplace_assistant_environment_server. The block in the warning is therefore the block the script writes. If that name is already taken, the server is named <agent>_environment_server instead.

  • Gym only adds a server for an agent that has none. An agent with an existing environment server is left alone, so the new server can never make agent routing ambiguous.

  • Setting error_on_agent_without_environment_server: true restores the error. It follows the existing error_on_almost_servers switch. The error message now also points at the migration script.

  • Renaming an agent with _inherit_from moves it to the new name. An environment server that named the old agent is then left pointing at an agent that no longer exists, and parsing fails with ServerRefNotFoundError. That error now says the environment server must be renamed with the agent, and points at the script.

    Follow-up: feat(config): support environment servers that front several agents #4053 changes this hint to say the environment server must reference the agent's new name, and shows it for any agent field of an environment server.

The test this PR replaces explained why the error existed: without it, a config written before environment servers "would otherwise run, silently dispatching straight to the agent." The generated server keeps that guarantee. Rollout collection still reaches the agent only through an environment server, and the warning names every server Gym added.

Fixes to scripts/add_legacy_agent_environment_servers.py

test_langchain_deepagents_configs_have_environment_servers now parses with error_on_agent_without_environment_server: true. The test checks that in-repo configs declare their environment server. A generated server would have the same name, so without strict mode the test would pass even when the declaration is missing.

The script's --check mode reports six configs on main that declare no environment server: two wordle configs, two swemer_oml configs, and two swe_internal_v1 configs. With this PR they load with the deprecation warning. Migrating them is left to their owners.

No issue is linked. This is a compatibility follow-up to #3724.

Validation

  • pytest tests/unit_tests/test_global_config.py, covering the generated server, its name, the warning text, strict mode, an agent that already has a server, a name collision, a full parse() of a config without environment servers, and the rename hint on ServerRefNotFoundError.
  • pytest tests/unit_tests/test_add_legacy_agent_environment_servers.py (new), covering configs outside the repository, --check not writing, an agent that already has a server, a rename through _inherit_from, an unreadable config, and that the script and Gym name servers the same way. The behavior tests fail against the script on main.
  • Six configs outside this repository that failed validation were migrated with the script, covering both added agents and _inherit_from renames. Each migrated config was then parsed with GlobalConfigDictParser and error_on_agent_without_environment_server: true. An unmigrated copy, parsed the same way, raised AgentWithoutEnvironmentServerError.
  • pytest tests/unit_tests/test_rollout_collection.py tests/unit_tests/test_environment_scaffold.py.
  • pytest tests/unit_tests. The tests that fail in this environment fail identically on main. They need the sandbox extra, or mlflow and wandb, which (fix) Move mlflow/wandb out of core deps and into extras #3547 moved into extras.
  • pre-commit run --all-files.

Rollouts

example_single_tool_call was run with its environment server block removed from the config, against Qwen/Qwen3-0.6B served by a local vLLM with the hermes tool parser:

  • gym env start logged the deprecation warning and started the generated environment server alongside the agent, the resources server, and the model server.
  • gym eval run --no-serve completed every requested rollout without failures. Each record has the generated server in _ng_environment_server and legacy_agent in _ng_result_type.
  • When the model called get_weather, the verifier scored the rollout 1.0. When it answered in prose without calling the tool, the verifier scored it 0.0.

Dispatch through the generated server has not changed since that run. Only the server's name and the warning text have.

Compatibility and benchmark impact

  • Configs that failed with AgentWithoutEnvironmentServerError now load and log a deprecation warning. Results do not change, because legacy_agent relays /run to the agent unchanged.
  • Users who want the error back can set error_on_agent_without_environment_server: true. The warning tells users that a future release will make this the default.
  • No benchmark impact.

Proposed labels: feature, area:config.

Checklist

  • I have read the contributing guidelines.
  • The change is focused; unrelated "drive-by" edits are tracked as separate issues/PRs.
  • Tests added or updated and pass locally, or N/A for docs-only / non-code changes (so CI unit/server checks pass when applicable).
  • Documentation added or updated, or N/A with justification.
  • Pre-commit checks pass locally (pre-commit run --all-files) (so CI lint/format checks pass).
  • New source files include the required Apache-2.0 SPDX header.
  • All commits have DCO sign-off (git commit -s) (so the DCO check passes).

…y relay

Rollout collection reaches agents only through an environment server, and
config validation has rejected any agent that no environment server names
since #3724. That breaks every configuration written before environment
servers, including out-of-tree training configs, on upgrade.

Instead of failing, generate the `legacy_agent` relay that
scripts/add_legacy_agent_environment_servers.py would declare, named
`<agent>_environment_server`, and log one deprecation warning listing every
generated relay. Only agents with no environment server get one, so a
generated relay never makes routing ambiguous or rewrites a reference.
Setting `error_on_agent_without_environment_server: true` restores the
error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
@ananthsub ananthsub added feature New capabilities, enhancements, or enablement work area:config Hydra configuration, schemas, composition, and compatibility needs-review PR is ready for code review and waiting on a reviewer labels Oct 2, 2026
@nemo-automation-bot
nemo-automation-bot Bot requested review from a team October 2, 2026 12:51
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

@ananthsub
ananthsub requested review from marta-sd and zyzhou5 October 2, 2026 12:53
@yaoyu-33 yaoyu-33 added the complexity:low Localized change in one scope with a small, straightforward review surface label Oct 2, 2026
yaoyu-33
yaoyu-33 previously approved these changes Oct 2, 2026

@yaoyu-33 yaoyu-33 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The compatibility relay preserves environment-server routing, and the warning encourages users to declare it explicitly in their config.

@yaoyu-33 yaoyu-33 added ready-to-merge PR is approved, current, and waiting for final CI or merge and removed needs-review PR is ready for code review and waiting on a reviewer labels Oct 2, 2026
Resolve the import conflict in tests/unit_tests/test_global_config.py by keeping
both upstream's hide_secrets_in_overrides import (#3679) and this branch's
rollout_collection import.

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
…the migration script

Make the generated legacy_agent relay an explicit deprecation: the warning
says a future release will reject these configs, prints the exact block to
add for each agent, and explains how to run the migration script or make
the error take effect now. Generated relays are named the way the
migration script declares them, so the printed block matches what the
script writes.

Fix scripts/add_legacy_agent_environment_servers.py so users can migrate
their own configs:

- Accept config files and directories outside the repository; it could
  only rewrite the repository's own configs.
- Skip an agent that any environment server already names. It used to add
  a second server for such agents (for example the one
  benchmarks/swebench/pro/hermes.yaml fronts with single_agent_turn_legacy),
  which makes agent routing ambiguous.
- Handle an agent renamed only through `_inherit_from` by inheriting the
  source agent's server; such renames were skipped.
- Report configs that cannot be parsed (exit 2) instead of skipping them.

A dangling environment server agent reference, left when an agent is
renamed with `_inherit_from`, now points at the script.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
@nemo-automation-bot
nemo-automation-bot Bot requested a review from a team October 5, 2026 01:39
@ananthsub ananthsub changed the title feat(config): front agents without an environment server with a legacy relay feat(config): deprecate agents without an environment server and fix the migration script Oct 5, 2026
… text

Comments, docstrings, the deprecation warning, the strict-mode error, and the troubleshooting page now
keep each sentence on its own line instead of reflowing clauses across lines. Long sentences are split.
The warning quoted in the docs matches the new text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
@yaoyu-33
yaoyu-33 added this pull request to the merge queue Oct 5, 2026
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/37255919835

Merged via the queue into main with commit 1f2446c Oct 5, 2026
44 checks passed
@yaoyu-33
yaoyu-33 deleted the ananthsub/auto-legacy-environment-servers branch October 5, 2026 02:46
ananthsub added a commit to ananthsub/Gym that referenced this pull request Oct 5, 2026
…the migration script (NVIDIA-NeMo#3929)

Since NVIDIA-NeMo#3724, config validation rejects any agent that no environment
server names, with `AgentWithoutEnvironmentServerError`. A config
written before environment servers therefore stops loading when Gym is
upgraded. This also affects configs in this repository:
`resources_servers/wordle/configs/wordle.yaml` and
`environments/wordle/config.yaml` fail to parse on `main`.

This PR turns that error into a deprecation, so these configs keep
running while users migrate. It also fixes the migration script so it
can update configs outside this repository.

During config parsing, Gym finds each agent that no environment server
names. For each one, it adds a `legacy_agent` environment server in
front of the agent. A `legacy_agent` server relays `/run` to its agent
unchanged, so rollouts and results are the same as before. Gym then logs
one deprecation warning:

```
DEPRECATED: agents without an environment server: `workplace_assistant_simple_agent`.
Rollout collection reaches an agent only through an environment server.
This run generated a legacy_agent environment server for each agent above.
Each one relays /run to its agent unchanged, so results are not affected.
A future release will reject these configs with AgentWithoutEnvironmentServerError.

To migrate, declare the environment servers in your config. Either:

1. Run the migration script from a NeMo Gym checkout on your config files or directories:

     python scripts/add_legacy_agent_environment_servers.py path/to/config.yaml [more paths] [--check]

   --check reports what would change without writing.

2. Or add these blocks yourself, at the same level as the agents:

     workplace_assistant_environment_server:
       environment_servers:
         legacy_agent:
           entrypoint: app.py
           agent_server:
             type: responses_api_agents
             name: workplace_assistant_simple_agent

To make this an error now, set error_on_agent_without_environment_server: true in your config.
You can also pass +error_on_agent_without_environment_server=true on the command line.
```

- The generated server is named the way the migration script names it:
the agent type is stripped from the agent name. For example,
`workplace_assistant_simple_agent` gets
`workplace_assistant_environment_server`. The block in the warning is
therefore the block the script writes. If that name is already taken,
the server is named `<agent>_environment_server` instead.
- Gym only adds a server for an agent that has none. An agent with an
existing environment server is left alone, so the new server can never
make agent routing ambiguous.
- Setting `error_on_agent_without_environment_server: true` restores the
error. It follows the existing `error_on_almost_servers` switch. The
error message now also points at the migration script.
- Renaming an agent with `_inherit_from` moves it to the new name. An
environment server that named the old agent is then left pointing at an
agent that no longer exists, and parsing fails with
`ServerRefNotFoundError`. That error now says the environment server
must be renamed with the agent, and points at the script.

The test this PR replaces explained why the error existed: without it, a
config written before environment servers "would otherwise run, silently
dispatching straight to the agent." The generated server keeps that
guarantee. Rollout collection still reaches the agent only through an
environment server, and the warning names every server Gym added.

- The script now accepts config files and directories as arguments.
Before, it could only update this repository's configs, so users had no
way to migrate their own. Run without arguments, it still updates this
repository's configs.
- The script now skips an agent that any environment server already
names. Before, it only checked whether a server with the generated name
existed. In `benchmarks/swebench/pro/hermes.yaml`, a
`single_agent_turn_legacy` server already fronts the agent under a
different name, so the script added a second server. Two servers in
front of one agent make agent routing ambiguous, and rollout collection
then fails.
- The script now handles an agent that only renames another one, such as
`renamed_agent: {_inherit_from: some_agent}`. Before, such configs were
skipped because the new instance has no agent body of its own. The
script now writes a server for the new name that inherits the original
agent's server. Because `_inherit_from` moves the original server, the
renamed agent ends up with exactly one server.
- A config that cannot be read or parsed is now reported, and the script
exits with 2. Before, such files were skipped without any message.

`test_langchain_deepagents_configs_have_environment_servers` now parses
with `error_on_agent_without_environment_server: true`. The test checks
that in-repo configs declare their environment server. A generated
server would have the same name, so without strict mode the test would
pass even when the declaration is missing.

The script's `--check` mode reports six configs on `main` that declare
no environment server: two `wordle` configs, two `swemer_oml` configs,
and two `swe_internal_v1` configs. With this PR they load with the
deprecation warning. Migrating them is left to their owners.

No issue is linked. This is a compatibility follow-up to NVIDIA-NeMo#3724.

- `pytest tests/unit_tests/test_global_config.py`, covering the
generated server, its name, the warning text, strict mode, an agent that
already has a server, a name collision, a full `parse()` of a config
without environment servers, and the rename hint on
`ServerRefNotFoundError`.
- `pytest tests/unit_tests/test_add_legacy_agent_environment_servers.py`
(new), covering configs outside the repository, `--check` not writing,
an agent that already has a server, a rename through `_inherit_from`, an
unreadable config, and that the script and Gym name servers the same
way. The behavior tests fail against the script on `main`.
- Six configs outside this repository that failed validation were
migrated with the script, covering both added agents and `_inherit_from`
renames. Each migrated config was then parsed with
`GlobalConfigDictParser` and `error_on_agent_without_environment_server:
true`. An unmigrated copy, parsed the same way, raised
`AgentWithoutEnvironmentServerError`.
- `pytest tests/unit_tests/test_rollout_collection.py
tests/unit_tests/test_environment_scaffold.py`.
- `pytest tests/unit_tests`. The tests that fail in this environment
fail identically on `main`. They need the `sandbox` extra, or `mlflow`
and `wandb`, which NVIDIA-NeMo#3547 moved into extras.
- `pre-commit run --all-files`.

`example_single_tool_call` was run with its environment server block
removed from the config, against `Qwen/Qwen3-0.6B` served by a local
vLLM with the hermes tool parser:

- `gym env start` logged the deprecation warning and started the
generated environment server alongside the agent, the resources server,
and the model server.
- `gym eval run --no-serve` completed every requested rollout without
failures. Each record has the generated server in
`_ng_environment_server` and `legacy_agent` in `_ng_result_type`.
- When the model called `get_weather`, the verifier scored the rollout
1.0. When it answered in prose without calling the tool, the verifier
scored it 0.0.

Dispatch through the generated server has not changed since that run.
Only the server's name and the warning text have.

- Configs that failed with `AgentWithoutEnvironmentServerError` now load
and log a deprecation warning. Results do not change, because
`legacy_agent` relays `/run` to the agent unchanged.
- Users who want the error back can set
`error_on_agent_without_environment_server: true`. The warning tells
users that a future release will make this the default.
- No benchmark impact.

Proposed labels: `feature`, `area:config`.

- [x] I have read the [contributing
guidelines](https://github.com/NVIDIA-NeMo/Gym/blob/main/CONTRIBUTING.md).
- [x] The change is focused; unrelated "drive-by" edits are tracked as
separate issues/PRs.
- [x] Tests added or updated and pass locally, or N/A for docs-only /
non-code changes (so CI unit/server checks pass when applicable).
- [x] Documentation added or updated, or N/A with justification.
- [x] Pre-commit checks pass locally (`pre-commit run --all-files`) (so
CI lint/format checks pass).
- [x] New source files include the required Apache-2.0 SPDX header.
- [x] All commits have DCO sign-off (`git commit -s`) (so the DCO check
passes).


---------

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
(cherry picked from commit 1f2446c)
Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
ananthsub added a commit to ananthsub/Gym that referenced this pull request Oct 6, 2026
…the migration script (NVIDIA-NeMo#3929)

Since NVIDIA-NeMo#3724, config validation rejects any agent that no environment
server names, with `AgentWithoutEnvironmentServerError`. A config
written before environment servers therefore stops loading when Gym is
upgraded. This also affects configs in this repository:
`resources_servers/wordle/configs/wordle.yaml` and
`environments/wordle/config.yaml` fail to parse on `main`.

This PR turns that error into a deprecation, so these configs keep
running while users migrate. It also fixes the migration script so it
can update configs outside this repository.

During config parsing, Gym finds each agent that no environment server
names. For each one, it adds a `legacy_agent` environment server in
front of the agent. A `legacy_agent` server relays `/run` to its agent
unchanged, so rollouts and results are the same as before. Gym then logs
one deprecation warning:

```
DEPRECATED: agents without an environment server: `workplace_assistant_simple_agent`.
Rollout collection reaches an agent only through an environment server.
This run generated a legacy_agent environment server for each agent above.
Each one relays /run to its agent unchanged, so results are not affected.
A future release will reject these configs with AgentWithoutEnvironmentServerError.

To migrate, declare the environment servers in your config. Either:

1. Run the migration script from a NeMo Gym checkout on your config files or directories:

     python scripts/add_legacy_agent_environment_servers.py path/to/config.yaml [more paths] [--check]

   --check reports what would change without writing.

2. Or add these blocks yourself, at the same level as the agents:

     workplace_assistant_environment_server:
       environment_servers:
         legacy_agent:
           entrypoint: app.py
           agent_server:
             type: responses_api_agents
             name: workplace_assistant_simple_agent

To make this an error now, set error_on_agent_without_environment_server: true in your config.
You can also pass +error_on_agent_without_environment_server=true on the command line.
```

- The generated server is named the way the migration script names it:
the agent type is stripped from the agent name. For example,
`workplace_assistant_simple_agent` gets
`workplace_assistant_environment_server`. The block in the warning is
therefore the block the script writes. If that name is already taken,
the server is named `<agent>_environment_server` instead.
- Gym only adds a server for an agent that has none. An agent with an
existing environment server is left alone, so the new server can never
make agent routing ambiguous.
- Setting `error_on_agent_without_environment_server: true` restores the
error. It follows the existing `error_on_almost_servers` switch. The
error message now also points at the migration script.
- Renaming an agent with `_inherit_from` moves it to the new name. An
environment server that named the old agent is then left pointing at an
agent that no longer exists, and parsing fails with
`ServerRefNotFoundError`. That error now says the environment server
must be renamed with the agent, and points at the script.

The test this PR replaces explained why the error existed: without it, a
config written before environment servers "would otherwise run, silently
dispatching straight to the agent." The generated server keeps that
guarantee. Rollout collection still reaches the agent only through an
environment server, and the warning names every server Gym added.

- The script now accepts config files and directories as arguments.
Before, it could only update this repository's configs, so users had no
way to migrate their own. Run without arguments, it still updates this
repository's configs.
- The script now skips an agent that any environment server already
names. Before, it only checked whether a server with the generated name
existed. In `benchmarks/swebench/pro/hermes.yaml`, a
`single_agent_turn_legacy` server already fronts the agent under a
different name, so the script added a second server. Two servers in
front of one agent make agent routing ambiguous, and rollout collection
then fails.
- The script now handles an agent that only renames another one, such as
`renamed_agent: {_inherit_from: some_agent}`. Before, such configs were
skipped because the new instance has no agent body of its own. The
script now writes a server for the new name that inherits the original
agent's server. Because `_inherit_from` moves the original server, the
renamed agent ends up with exactly one server.
- A config that cannot be read or parsed is now reported, and the script
exits with 2. Before, such files were skipped without any message.

`test_langchain_deepagents_configs_have_environment_servers` now parses
with `error_on_agent_without_environment_server: true`. The test checks
that in-repo configs declare their environment server. A generated
server would have the same name, so without strict mode the test would
pass even when the declaration is missing.

The script's `--check` mode reports six configs on `main` that declare
no environment server: two `wordle` configs, two `swemer_oml` configs,
and two `swe_internal_v1` configs. With this PR they load with the
deprecation warning. Migrating them is left to their owners.

No issue is linked. This is a compatibility follow-up to NVIDIA-NeMo#3724.

- `pytest tests/unit_tests/test_global_config.py`, covering the
generated server, its name, the warning text, strict mode, an agent that
already has a server, a name collision, a full `parse()` of a config
without environment servers, and the rename hint on
`ServerRefNotFoundError`.
- `pytest tests/unit_tests/test_add_legacy_agent_environment_servers.py`
(new), covering configs outside the repository, `--check` not writing,
an agent that already has a server, a rename through `_inherit_from`, an
unreadable config, and that the script and Gym name servers the same
way. The behavior tests fail against the script on `main`.
- Six configs outside this repository that failed validation were
migrated with the script, covering both added agents and `_inherit_from`
renames. Each migrated config was then parsed with
`GlobalConfigDictParser` and `error_on_agent_without_environment_server:
true`. An unmigrated copy, parsed the same way, raised
`AgentWithoutEnvironmentServerError`.
- `pytest tests/unit_tests/test_rollout_collection.py
tests/unit_tests/test_environment_scaffold.py`.
- `pytest tests/unit_tests`. The tests that fail in this environment
fail identically on `main`. They need the `sandbox` extra, or `mlflow`
and `wandb`, which NVIDIA-NeMo#3547 moved into extras.
- `pre-commit run --all-files`.

`example_single_tool_call` was run with its environment server block
removed from the config, against `Qwen/Qwen3-0.6B` served by a local
vLLM with the hermes tool parser:

- `gym env start` logged the deprecation warning and started the
generated environment server alongside the agent, the resources server,
and the model server.
- `gym eval run --no-serve` completed every requested rollout without
failures. Each record has the generated server in
`_ng_environment_server` and `legacy_agent` in `_ng_result_type`.
- When the model called `get_weather`, the verifier scored the rollout
1.0. When it answered in prose without calling the tool, the verifier
scored it 0.0.

Dispatch through the generated server has not changed since that run.
Only the server's name and the warning text have.

- Configs that failed with `AgentWithoutEnvironmentServerError` now load
and log a deprecation warning. Results do not change, because
`legacy_agent` relays `/run` to the agent unchanged.
- Users who want the error back can set
`error_on_agent_without_environment_server: true`. The warning tells
users that a future release will make this the default.
- No benchmark impact.

Proposed labels: `feature`, `area:config`.

- [x] I have read the [contributing
guidelines](https://github.com/NVIDIA-NeMo/Gym/blob/main/CONTRIBUTING.md).
- [x] The change is focused; unrelated "drive-by" edits are tracked as
separate issues/PRs.
- [x] Tests added or updated and pass locally, or N/A for docs-only /
non-code changes (so CI unit/server checks pass when applicable).
- [x] Documentation added or updated, or N/A with justification.
- [x] Pre-commit checks pass locally (`pre-commit run --all-files`) (so
CI lint/format checks pass).
- [x] New source files include the required Apache-2.0 SPDX header.
- [x] All commits have DCO sign-off (`git commit -s`) (so the DCO check
passes).

---------

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
(cherry picked from commit 1f2446c)
Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
ananthsub added a commit to ananthsub/Gym that referenced this pull request Oct 6, 2026
…VIDIA-NeMo#4053)

## What does this PR do?

Makes Gym and its migration tooling agree on which agents an environment
server fronts, including servers that front several agents. It combines
the runtime change from NVIDIA-NeMo#4059 with fixes to
`scripts/add_legacy_agent_environment_servers.py`, the
`ServerRefNotFoundError` rename hint, and the troubleshooting docs, so
the environment server and agent server changes land together. The same
commits are in the `r0.7.0` cherry-pick of NVIDIA-NeMo#3929 (NVIDIA-NeMo#4050).

## Where this fits

Datasets reach an environment server in one of two ways today: run
request rows route by their agent, and datasets that declare `taskset:`
are materialized during collation and route by
`environment_server_routes`. The goal is one path, where every dataset
declares a taskset and routes to an environment server:

```mermaid
flowchart LR
    P["Preparation script<br/>task rows with a stable task_id"] --> C["Collation<br/>validates, computes metrics,<br/>materializes"]
    Y["Dataset YAML<br/>file + taskset"] --> C
    S["task_data.py schemas<br/>environment server + resources server"] -.-> C
    C --> R["Rollout collection<br/>routes by taskset"]
    T["environment_server_routes"] --> R
    R --> E["Environment server<br/>single-agent, multi-agent,<br/>or legacy_agent relay"]
```

This PR is one step on the way there. Each step is additive, and
datasets without `taskset:` keep working until the last one:

```mermaid
flowchart TB
    S3818["PR 3818 (merged)<br/>collation materializes<br/>declared tasksets"]
    S4053["PR 4053<br/>tasksets route to servers<br/>with several agents"]
    S4096["PR 4096<br/>stable, unique<br/>task IDs"]
    S4097["PR 4097<br/>metrics for<br/>every row"]
    S4098["PR 4098<br/>schema per<br/>environment server"]
    S4099["PR 4099<br/>legacy_agent relays<br/>serve tasksets"]
    SNEXT["Later<br/>configs declare taskset:,<br/>then agent routing retires"]
    S3818 --> S4053 --> S4096 --> S4097 --> S4098 --> S4099 --> SNEXT
    classDef done fill:#F1EFE8,stroke:#5F5E5A,color:#444441
    classDef this fill:#EEEDFE,stroke:#534AB7,color:#26215C,stroke-width:2px
    classDef next fill:#E1F5EE,stroke:#0F6E56,color:#085041
    class S3818 done
    class S4053 this
    class S4096,S4097,S4098,S4099,SNEXT next
```

Gray: merged. Purple: this PR. Green: other steps.

### Environment servers that front several agents

An environment server can front several agents, such as the user and
assistant agents of the user-simulation server in NVIDIA-NeMo#3800. It references
them through fields such as `user_agent` and `assistant_agent`. Gym only
recognized the `agent_server` field. For such a server:

- The deprecation check from NVIDIA-NeMo#3929 treated its agents as unfronted and
generated a `legacy_agent` relay in front of each.
- Agent-routed rows could not find the server, because
`_environment_servers_by_agent` only read `agent_server`.

The first commit from NVIDIA-NeMo#4059 (authored by Felipe Vieira Frujeri) adds
`environment_server_agent_refs` to `nemo_gym/global_config.py`. Any
top-level field whose value has `type: responses_api_agents` references
an agent, and so does `agent_server`, whose type may be left out. The
deprecation check, agent routing, and `_retarget_environment_servers`
(which follows an agent swapped during composition) use it.

Two places in `nemo_gym/rollout_collection.py` still read only
`agent_server`:

- `_agent_name_for_row` names the agent a row's results are attributed
to. It decides whether token IDs are captured and labels aggregate
metrics. Results are now attributed to the server's `agent_server`, or
to the only agent it references. A server that fronts several agents
without an `agent_server` has no single agent to attribute, as before.
- `_validate_environment_servers` rejects a row whose `agent_ref` names
an agent the server does not front. It skipped the check for any server
without `agent_server`. A row may now name any agent its server
references.

### The migration script

The script assumed an environment server references one agent, through
`agent_server`. It now finds agent references with the same rule as
`environment_server_agent_refs`. The script depends only on PyYAML, so
it keeps its own copy of the rule, and a test checks that both agree.
Each referenced agent counts as fronted, so the script no longer adds
relays in front of agents a multi-agent server already fronts, such as
those in `resources_servers/usersim/configs/usersim.yaml` from NVIDIA-NeMo#3800.

For an agent renamed with `_inherit_from`, the renamed agent's server
used to inherit from the name the script would generate for the source,
always as a `legacy_agent` relay. That failed in three ways:

- The source's server has a custom name: the inherited name does not
exist, raising `InheritPathNotFoundError`.
- The source's server is `single_agent_turn_legacy`: the inherited
server gains a second `legacy_agent` block without an entrypoint,
raising `AlmostServerError`.
- Inheriting retires the source server's name, which
`environment_server_routes` may still use. For example, usersim routes
its tasksets to `usersim_environment`.

The script now handles a rename this way:

- Where a declared server references the source, the script points that
reference at the new name, on the existing server. The server keeps its
name, so routes and the server's other agents are unaffected. Renaming
several agents of one server writes one block for that server.
- Where the source has only the server the script generates, the renamed
agent's server inherits it, as before.
- A rename in the same file as a server that references its source
cannot be migrated by appending, because the server's key would repeat.
The script reports the field to update by hand and exits with 2.

### The renamed-agent hint

When an environment server references an agent that no longer exists,
`ServerRefNotFoundError` suggests the agent may have been renamed with
`_inherit_from`. The hint said the environment server "must be renamed
with it", which no longer matches the script.

- The hint now says the environment server must reference the agent's
new name, and names the field to update instead of always
`agent_server.name`.
- It is shown for any agent reference on an environment server, not only
`agent_server`.
- `fern/versions/latest/pages/troubleshooting/configuration.mdx`
describes the same rename behavior, and the script's exit code 2 for a
rename that needs a manual update.

### Declared tasksets routed to a multi-agent server

NVIDIA-NeMo#3818 added `resolve_dataset_agent`'s taskset path, used by benchmark
discovery, `gym eval prepare`, manifest validation, and collation. It
read only the routed environment server's `agent_server`, so a taskset
routed to a server that fronts several agents failed with "must bind a
valid agent_server".

A taskset routes to an environment server, not to an agent: rollout
collection sends materialized rows to the server that
`environment_server_routes` names and never uses an agent. So resolution
now checks the route instead of choosing an agent:

- Every agent the server references, through
`environment_server_agent_refs`, must exist.
- A dataset declared on an agent must be one of those agents.
- A dataset declared on a resources server must be the one the server
binds.
- A dataset with `taskset:` cannot set `agent:`. Only benchmark datasets
accept `agent:`, so requiring a pin would have left example and train
datasets for a server with several agents unable to collate, and would
have made an environment server that owns its dataset name one of its
own agents.

It returns the declaring agent, or the server's attributed agent (its
`agent_server`, or its only agent), or none when the server fronts
several agents without an `agent_server`.
`environment_server_agent_names` and
`environment_server_attributed_agent` move to `global_config.py` so
resolution and rollout attribution share that rule. Benchmarks record
the environment server, and `gym eval list` shows it when there is no
single agent. Environment manifest validation reports that a manifest,
which describes one agent, cannot describe such a taskset.
`resources-server-apis.mdx` describes the route check.

This supersedes NVIDIA-NeMo#4059. No issue is linked. It follows up NVIDIA-NeMo#3929, from
review on NVIDIA-NeMo#4050, and provides the runtime support NVIDIA-NeMo#3800 needs.

## Validation

- `tests/unit_tests/test_global_config.py`: a multi-agent server fronts
all of its agents and needs no relays, removing one agent's reference
makes that agent unfronted, and a composition swap retargets only the
swapped agent's reference. The rename hint test runs for an
`agent_server` and a `user_agent` reference.
- `tests/unit_tests/test_rollout_collection.py`: attribution to
`agent_server`, to a server's only agent, and to no agent for a server
with several, and row validation accepting any agent of the server and
rejecting others.
- `tests/unit_tests/test_add_legacy_agent_environment_servers.py`:
migrate-then-parse tests, which run the script and parse the result with
`GlobalConfigDictParser`. They cover a rename whose source server is
generated, custom-named, or `single_agent_turn_legacy`, a two-agent
server the script must leave alone, renaming both agents of that server
(parsed with `error_on_agent_without_environment_server: true`), and a
rename in the same file as the server it would update. A test checks
that the script and `environment_server_agent_refs` find the same
references.
- `tests/unit_tests/test_benchmarks.py`: a multi-agent taskset resolves
without a pin, to none or to the server's `agent_server`; any pin on a
taskset dataset is rejected; a reference to a non-agent is rejected; a
declaring participant is accepted; benchmark, example, and train
datasets collate and route end to end; the benchmark listing names the
environment server; and manifest validation reports a multi-agent
taskset. These tests fail without the change.
- `pytest tests/unit_tests/test_global_config.py
tests/unit_tests/test_add_legacy_agent_environment_servers.py
tests/unit_tests/test_rollout_collection.py
tests/unit_tests/test_environment_scaffold.py
tests/unit_tests/test_benchmarks.py
tests/unit_tests/test_task_materialization.py`: all pass.
- `pytest tests/unit_tests`: the tests that fail in this environment
fail identically on `main`. They need the `sandbox` and telemetry
extras, or Slurm.
- With NVIDIA-NeMo#3800 merged locally: the script leaves
`resources_servers/usersim/configs/usersim.yaml` unchanged. After
renaming `usersim_assistant`, the usersim config parses with
`error_on_agent_without_environment_server: true`, `usersim_environment`
fronts both agents, and both usersim routes still name an existing
server.
- `python scripts/add_legacy_agent_environment_servers.py --check` on
this repository reports the same files and blocks before and after this
change.
- `pre-commit run` on the changed files: all pass.

## Rollouts

N/A. Single-agent servers resolve, route, and attribute exactly as
before, and no configuration in this repository changes. The new
behavior applies only to servers that reference agents through fields
other than `agent_server`. None exist on `main` until NVIDIA-NeMo#3800, which
carries its own rollout evidence. The migration script runs offline.

## Compatibility and benchmark impact

- Environment servers that front several agents no longer get generated
relays, and their agents route to them.
- The error for a row that names the wrong agent for its environment
server now lists the server's agents.
- A dataset that declares `taskset:` can no longer set `agent:`. NVIDIA-NeMo#3818
accepted a pin that matched the routed agent; no configuration in this
repository declares a taskset.
- `gym eval list --json` adds an `environment_server` field, and
`agent_name` is null for a taskset routed to a server with several
agents.
- The migration script updates a declared server in place when an agent
is renamed, so server names and routes no longer change.
- No benchmark impact.

---------

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
Signed-off-by: Felipe Vieira Frujeri <ffrujeri@nvidia.com>
Co-authored-by: Felipe Vieira Frujeri <ffrujeri@nvidia.com>
(cherry picked from commit 89c065b)
Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
ehosseiniasl pushed a commit to ehosseiniasl/Gym that referenced this pull request Oct 6, 2026
…VIDIA-NeMo#4053)

## What does this PR do?

Makes Gym and its migration tooling agree on which agents an environment
server fronts, including servers that front several agents. It combines
the runtime change from NVIDIA-NeMo#4059 with fixes to
`scripts/add_legacy_agent_environment_servers.py`, the
`ServerRefNotFoundError` rename hint, and the troubleshooting docs, so
the environment server and agent server changes land together. The same
commits are in the `r0.7.0` cherry-pick of NVIDIA-NeMo#3929 (NVIDIA-NeMo#4050).

## Where this fits

Datasets reach an environment server in one of two ways today: run
request rows route by their agent, and datasets that declare `taskset:`
are materialized during collation and route by
`environment_server_routes`. The goal is one path, where every dataset
declares a taskset and routes to an environment server:

```mermaid
flowchart LR
    P["Preparation script<br/>task rows with a stable task_id"] --> C["Collation<br/>validates, computes metrics,<br/>materializes"]
    Y["Dataset YAML<br/>file + taskset"] --> C
    S["task_data.py schemas<br/>environment server + resources server"] -.-> C
    C --> R["Rollout collection<br/>routes by taskset"]
    T["environment_server_routes"] --> R
    R --> E["Environment server<br/>single-agent, multi-agent,<br/>or legacy_agent relay"]
```

This PR is one step on the way there. Each step is additive, and
datasets without `taskset:` keep working until the last one:

```mermaid
flowchart TB
    S3818["PR 3818 (merged)<br/>collation materializes<br/>declared tasksets"]
    S4053["PR 4053<br/>tasksets route to servers<br/>with several agents"]
    S4096["PR 4096<br/>stable, unique<br/>task IDs"]
    S4097["PR 4097<br/>metrics for<br/>every row"]
    S4098["PR 4098<br/>schema per<br/>environment server"]
    S4099["PR 4099<br/>legacy_agent relays<br/>serve tasksets"]
    SNEXT["Later<br/>configs declare taskset:,<br/>then agent routing retires"]
    S3818 --> S4053 --> S4096 --> S4097 --> S4098 --> S4099 --> SNEXT
    classDef done fill:#F1EFE8,stroke:#5F5E5A,color:#444441
    classDef this fill:#EEEDFE,stroke:#534AB7,color:#26215C,stroke-width:2px
    classDef next fill:#E1F5EE,stroke:#0F6E56,color:#085041
    class S3818 done
    class S4053 this
    class S4096,S4097,S4098,S4099,SNEXT next
```

Gray: merged. Purple: this PR. Green: other steps.

### Environment servers that front several agents

An environment server can front several agents, such as the user and
assistant agents of the user-simulation server in NVIDIA-NeMo#3800. It references
them through fields such as `user_agent` and `assistant_agent`. Gym only
recognized the `agent_server` field. For such a server:

- The deprecation check from NVIDIA-NeMo#3929 treated its agents as unfronted and
generated a `legacy_agent` relay in front of each.
- Agent-routed rows could not find the server, because
`_environment_servers_by_agent` only read `agent_server`.

The first commit from NVIDIA-NeMo#4059 (authored by Felipe Vieira Frujeri) adds
`environment_server_agent_refs` to `nemo_gym/global_config.py`. Any
top-level field whose value has `type: responses_api_agents` references
an agent, and so does `agent_server`, whose type may be left out. The
deprecation check, agent routing, and `_retarget_environment_servers`
(which follows an agent swapped during composition) use it.

Two places in `nemo_gym/rollout_collection.py` still read only
`agent_server`:

- `_agent_name_for_row` names the agent a row's results are attributed
to. It decides whether token IDs are captured and labels aggregate
metrics. Results are now attributed to the server's `agent_server`, or
to the only agent it references. A server that fronts several agents
without an `agent_server` has no single agent to attribute, as before.
- `_validate_environment_servers` rejects a row whose `agent_ref` names
an agent the server does not front. It skipped the check for any server
without `agent_server`. A row may now name any agent its server
references.

### The migration script

The script assumed an environment server references one agent, through
`agent_server`. It now finds agent references with the same rule as
`environment_server_agent_refs`. The script depends only on PyYAML, so
it keeps its own copy of the rule, and a test checks that both agree.
Each referenced agent counts as fronted, so the script no longer adds
relays in front of agents a multi-agent server already fronts, such as
those in `resources_servers/usersim/configs/usersim.yaml` from NVIDIA-NeMo#3800.

For an agent renamed with `_inherit_from`, the renamed agent's server
used to inherit from the name the script would generate for the source,
always as a `legacy_agent` relay. That failed in three ways:

- The source's server has a custom name: the inherited name does not
exist, raising `InheritPathNotFoundError`.
- The source's server is `single_agent_turn_legacy`: the inherited
server gains a second `legacy_agent` block without an entrypoint,
raising `AlmostServerError`.
- Inheriting retires the source server's name, which
`environment_server_routes` may still use. For example, usersim routes
its tasksets to `usersim_environment`.

The script now handles a rename this way:

- Where a declared server references the source, the script points that
reference at the new name, on the existing server. The server keeps its
name, so routes and the server's other agents are unaffected. Renaming
several agents of one server writes one block for that server.
- Where the source has only the server the script generates, the renamed
agent's server inherits it, as before.
- A rename in the same file as a server that references its source
cannot be migrated by appending, because the server's key would repeat.
The script reports the field to update by hand and exits with 2.

### The renamed-agent hint

When an environment server references an agent that no longer exists,
`ServerRefNotFoundError` suggests the agent may have been renamed with
`_inherit_from`. The hint said the environment server "must be renamed
with it", which no longer matches the script.

- The hint now says the environment server must reference the agent's
new name, and names the field to update instead of always
`agent_server.name`.
- It is shown for any agent reference on an environment server, not only
`agent_server`.
- `fern/versions/latest/pages/troubleshooting/configuration.mdx`
describes the same rename behavior, and the script's exit code 2 for a
rename that needs a manual update.

### Declared tasksets routed to a multi-agent server

NVIDIA-NeMo#3818 added `resolve_dataset_agent`'s taskset path, used by benchmark
discovery, `gym eval prepare`, manifest validation, and collation. It
read only the routed environment server's `agent_server`, so a taskset
routed to a server that fronts several agents failed with "must bind a
valid agent_server".

A taskset routes to an environment server, not to an agent: rollout
collection sends materialized rows to the server that
`environment_server_routes` names and never uses an agent. So resolution
now checks the route instead of choosing an agent:

- Every agent the server references, through
`environment_server_agent_refs`, must exist.
- A dataset declared on an agent must be one of those agents.
- A dataset declared on a resources server must be the one the server
binds.
- A dataset with `taskset:` cannot set `agent:`. Only benchmark datasets
accept `agent:`, so requiring a pin would have left example and train
datasets for a server with several agents unable to collate, and would
have made an environment server that owns its dataset name one of its
own agents.

It returns the declaring agent, or the server's attributed agent (its
`agent_server`, or its only agent), or none when the server fronts
several agents without an `agent_server`.
`environment_server_agent_names` and
`environment_server_attributed_agent` move to `global_config.py` so
resolution and rollout attribution share that rule. Benchmarks record
the environment server, and `gym eval list` shows it when there is no
single agent. Environment manifest validation reports that a manifest,
which describes one agent, cannot describe such a taskset.
`resources-server-apis.mdx` describes the route check.

This supersedes NVIDIA-NeMo#4059. No issue is linked. It follows up NVIDIA-NeMo#3929, from
review on NVIDIA-NeMo#4050, and provides the runtime support NVIDIA-NeMo#3800 needs.

## Validation

- `tests/unit_tests/test_global_config.py`: a multi-agent server fronts
all of its agents and needs no relays, removing one agent's reference
makes that agent unfronted, and a composition swap retargets only the
swapped agent's reference. The rename hint test runs for an
`agent_server` and a `user_agent` reference.
- `tests/unit_tests/test_rollout_collection.py`: attribution to
`agent_server`, to a server's only agent, and to no agent for a server
with several, and row validation accepting any agent of the server and
rejecting others.
- `tests/unit_tests/test_add_legacy_agent_environment_servers.py`:
migrate-then-parse tests, which run the script and parse the result with
`GlobalConfigDictParser`. They cover a rename whose source server is
generated, custom-named, or `single_agent_turn_legacy`, a two-agent
server the script must leave alone, renaming both agents of that server
(parsed with `error_on_agent_without_environment_server: true`), and a
rename in the same file as the server it would update. A test checks
that the script and `environment_server_agent_refs` find the same
references.
- `tests/unit_tests/test_benchmarks.py`: a multi-agent taskset resolves
without a pin, to none or to the server's `agent_server`; any pin on a
taskset dataset is rejected; a reference to a non-agent is rejected; a
declaring participant is accepted; benchmark, example, and train
datasets collate and route end to end; the benchmark listing names the
environment server; and manifest validation reports a multi-agent
taskset. These tests fail without the change.
- `pytest tests/unit_tests/test_global_config.py
tests/unit_tests/test_add_legacy_agent_environment_servers.py
tests/unit_tests/test_rollout_collection.py
tests/unit_tests/test_environment_scaffold.py
tests/unit_tests/test_benchmarks.py
tests/unit_tests/test_task_materialization.py`: all pass.
- `pytest tests/unit_tests`: the tests that fail in this environment
fail identically on `main`. They need the `sandbox` and telemetry
extras, or Slurm.
- With NVIDIA-NeMo#3800 merged locally: the script leaves
`resources_servers/usersim/configs/usersim.yaml` unchanged. After
renaming `usersim_assistant`, the usersim config parses with
`error_on_agent_without_environment_server: true`, `usersim_environment`
fronts both agents, and both usersim routes still name an existing
server.
- `python scripts/add_legacy_agent_environment_servers.py --check` on
this repository reports the same files and blocks before and after this
change.
- `pre-commit run` on the changed files: all pass.

## Rollouts

N/A. Single-agent servers resolve, route, and attribute exactly as
before, and no configuration in this repository changes. The new
behavior applies only to servers that reference agents through fields
other than `agent_server`. None exist on `main` until NVIDIA-NeMo#3800, which
carries its own rollout evidence. The migration script runs offline.

## Compatibility and benchmark impact

- Environment servers that front several agents no longer get generated
relays, and their agents route to them.
- The error for a row that names the wrong agent for its environment
server now lists the server's agents.
- A dataset that declares `taskset:` can no longer set `agent:`. NVIDIA-NeMo#3818
accepted a pin that matched the routed agent; no configuration in this
repository declares a taskset.
- `gym eval list --json` adds an `environment_server` field, and
`agent_name` is null for a taskset routed to a server with several
agents.
- The migration script updates a declared server in place when an agent
is renamed, so server names and routes no longer change.
- No benchmark impact.

---------

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
Signed-off-by: Felipe Vieira Frujeri <ffrujeri@nvidia.com>
Co-authored-by: Felipe Vieira Frujeri <ffrujeri@nvidia.com>
ananthsub added a commit to ananthsub/Gym that referenced this pull request Oct 6, 2026
…igration

Six configs added after NVIDIA-NeMo#3929 (wordle, swemer_oml, swe_internal_v1) declare
agents without an environment server, so every run generates legacy_agent
relays with a deprecation warning, and runs with
error_on_agent_without_environment_server fail. Add the relays with
scripts/add_legacy_agent_environment_servers.py.

Nothing checked repository configs, so add a test that runs the script's
--check over them.

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
ananthsub added a commit that referenced this pull request Oct 6, 2026
…vers (#3818, #4070, #3929, #4053) into `r0.7.0` (#4107)

## What

This PR only cherry-picks four already-merged `main` PRs into `r0.7.0`.
It adds no new code. The commits are applied in their `main` merge order
with `git cherry-pick -x`, so each commit message records its source
commit.

| Original PR | Commit on `main` | Change |
|---|---|---|
| #3818 | `325fb49d6` |
feat(data): collate declared tasksets |
| #4070 | `412efc730` | refactor:
name episode requests and sessions without "native" |
| #3929 | `1f2446c17` |
feat(config): deprecate agents without an environment server and fix the
migration script |
| #4053 | `89c065b0e` |
feat(config): support environment servers that front several agents |

## Why

#4070 and #4053 carry the `r0.7.0` label, but the automatic cherry-pick
workflow could not apply them: both hit merge conflicts on `r0.7.0`. The
workflow still reports success, so the failure was silent. #3818 was
intended for `r0.7.0` but never had the label.

#3929 is included because #4053 builds on it. Every #4053 conflict came
from #3929 changes to `nemo_gym/global_config.py`,
`scripts/add_legacy_agent_environment_servers.py`, and their tests. With
#3929 applied first, #4053 applies cleanly.

## Conflict resolution

Picking #3818 first removed one #4070 conflict. The remaining conflicts
were in tests that other PRs added on `main` after the branch cut:

- **#4070**: renames identifiers, comments, and test names; no behavior
change. Where the conflict was a test that exists only on `main`
(`single_agent_turn`, SWE-bench Pro, and Hermes
`test_agent_sessions.py`), the `r0.7.0` version is kept and the
`main`-only test is not brought in. In `single_agent_turn_legacy`, the
existing test is renamed to
`test_flat_rows_and_episode_requests_project_the_same_result` to match
`main`, and the `task_source` tests from a separate PR are left out.
- **#3929**: one import line in
`tests/unit_tests/test_global_config.py`. `main` also imports
`hide_secrets_in_overrides`, added by
#3679 and unused in this file, so
the `r0.7.0` import is kept.
- **#3818** and **#4053**: applied cleanly.

## Validation

- `pytest tests/unit_tests/test_global_config.py
tests/unit_tests/test_add_legacy_agent_environment_servers.py
tests/unit_tests/test_rollout_collection.py
tests/unit_tests/test_environment_server.py
tests/unit_tests/test_task_materialization.py
tests/unit_tests/test_benchmarks.py`: pass.
- `pytest environment_servers/single_agent_turn/tests` and `pytest
environment_servers/single_agent_turn_legacy/tests` (run separately):
pass.
- `pytest tests/unit_tests -n 8`: same failures as unmodified `r0.7.0`
and no new ones. The existing failures are local-environment tests in
`test_opensandbox_compose.py`, `test_sandbox_stop_retry.py`, and
`test_slurm_script.py`.
- `pre-commit run --files` on all changed files: pass.
- Checked for old names #4070 renames (`_native_request`,
`_native_episode_request_body`, `_native_session_locks`,
`_closed_native_sessions`): none remain. The few remaining "native row"
comments are also on `main`.
- SWE-bench Pro and Hermes server tests were not run locally (they need
per-server venvs); release-branch CI covers them.

## Rollout evidence

N/A. Each change was reviewed and validated on `main`. This PR only
carries them to the release branch: #4070 is a rename with no behavior
change, and the config changes are covered by the unit tests above.

---------

Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>
Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
Signed-off-by: Felipe Vieira Frujeri <ffrujeri@nvidia.com>
Co-authored-by: Yu Yao <54727607+yaoyu-33@users.noreply.github.com>
Co-authored-by: Felipe Vieira Frujeri <ffrujeri@nvidia.com>
ananthsub added a commit that referenced this pull request Oct 6, 2026
…ions (#3554, #3784) into `r0.7.0` (#4113)

## What

This PR only cherry-picks two already-merged `main` PRs into `r0.7.0`.
It adds no new code. Each commit is applied with `git cherry-pick -x`,
so its message records the source commit.

| Original PR | Commit on `main` | Change |
|---|---|---|
| #3554 | `b846dfaa5` |
fix(hermes): harden sandbox sessions |
| #3784 | `e0e63e526` |
feat(simple-agent): run simple agent through environment server sessions
|

This branch is based on the current `r0.7.0`, which already includes
#4107 (#3818, #4070, #3929,
#4053) and #4078.

## Why

#3784 has the `r0.7.0` label, but the automatic cherry-pick workflow
could not apply it: it hit a merge conflict in
`nemo_gym/base_responses_api_agent.py` and reported success anyway, so
nothing was opened.

#3554 is included because #3784 builds on it. #3554 added the agent
session handling (seed and close retries, session locks, close receipts)
in `nemo_gym/base_responses_api_agent.py` that #3784 extends. With #3554
applied first, the conflict in that file goes away. #3554 has no
`r0.7.0` label; it is included on purpose.

### Behavior changes from #3554

#3554's own description lists these, and they now apply on `r0.7.0` too:

- Hermes host execution now combines the config system prompt, request
instructions, and input system message instead of letting the config
prompt replace the dataset prompt. It honors the request temperature,
including zero, and rejects `developer` messages with 422. These changes
can change benchmark scores for Hermes on the host path.
- Hermes rejects unsupported non-default controls with a permanent 422,
including `top_p=1.0`, `store`, `service_tier`, metadata overrides,
`max_output_tokens`, and a `model` that does not match the configured
model.

## Conflict resolution

- **#3554**: applied cleanly.
- **#3784**: one conflict in
`environment_servers/single_agent_turn_legacy/tests/test_app.py`. #3784
adds two `task_source` tests next to a test that #4070 (already on
`r0.7.0` through #4107) renamed. The resolution keeps both, and the new
tests call the adapter method by its post-#4070 name,
`_episode_request_from_row`. The resulting file is identical to the file
on `main`.

## Validation

- `pytest tests/unit_tests -n 8`: same failures as unmodified `r0.7.0`
and no new ones. The existing failures are local-environment tests in
`test_opensandbox_compose.py`, `test_sandbox_stop_retry.py`, and
`test_slurm_script.py`.
- #3554's focused suite, each path run separately with
`--import-mode=importlib`:
`environment_servers/single_agent_turn/tests`,
`environment_servers/single_agent_turn_legacy/tests`,
`environment_servers/legacy_agent/tests`,
`responses_api_agents/hermes_sandboxed_agent/tests`,
`resources_servers/swebench_pro/tests`, and
`tests/unit_tests/test_{episode_types,environment_server,rollout_outcomes,failure_kinds,agent_session_retries,agent_sessions,base_responses_api_agent,process_supervisor,local_provider}.py`:
all pass.
- `responses_api_agents/hermes_agent/tests` in its own venv from
`requirements.txt`: all pass.
- `responses_api_agents/simple_agent/tests` and
`resources_servers/example_single_tool_call/tests`: all pass.
- `pre-commit run --files` on all files changed by the two commits:
pass.

## Rollout evidence

N/A for the cherry-pick itself. Both changes are applied unchanged from
`main`, except for the one test-file conflict described above. #3554
includes a real-model Hermes with SWE-bench Pro lifecycle smoke rollout.

---------

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Signed-off-by: Adil Asif <adasif@nvidia.com>
Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>
Co-authored-by: Yu Yao <54727607+yaoyu-33@users.noreply.github.com>
Co-authored-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Co-authored-by: Adil Asif <adasif@nvidia.com>
katherineh123 pushed a commit to katherineh123/Gym that referenced this pull request Oct 6, 2026
…igration (NVIDIA-NeMo#4084)

## What does this PR do?

Declares `legacy_agent` environment servers for the six configs added
after NVIDIA-NeMo#3929 that have agents with no environment server:

- `environments/wordle/config.yaml`
- `resources_servers/wordle/configs/wordle.yaml`
- `resources_servers/swemer_oml/configs/swemer_oml_example.yaml`
- `resources_servers/swemer_oml/configs/swemer_oml_opencode.yaml`
-
`resources_servers/swe_internal_v1/configs/swe_internal_v1_example.yaml`
-
`resources_servers/swe_internal_v1/configs/swe_internal_v1_opencode.yaml`

Without these, every run of these configs generates the relays at
startup and logs the deprecation warning, and a run with
`error_on_agent_without_environment_server: true` fails with
`AgentWithoutEnvironmentServerError`. The blocks were written by
`scripts/add_legacy_agent_environment_servers.py`, so they match what
the runtime generates today.

Nothing checked the repository's configs, which is how these slipped in.
This PR adds a unit test that runs the script's `--check` over the
repository and fails if any agent still lacks an environment server.

## Issue

No issue; follow-up to NVIDIA-NeMo#3929.

## Validation

- The new test,
`test_repository_configs_declare_every_environment_server`, fails on
`main`'s configs and passes with this change.
- `gym env resolve --config <file>
+error_on_agent_without_environment_server=true` on each of the six
configs: before this change, all six are rejected for having no
environment server; after it, all six pass that check. Resolution then
stops at the unset `model_server` values, which need a local `env.yaml`.
- `pytest tests/unit_tests/test_add_legacy_agent_environment_servers.py
tests/unit_tests/test_global_config.py`: all pass.
- `pre-commit run` on the changed files: all pass.

## Rollouts

N/A. The added blocks are the relays the runtime already generates for
these agents at startup, so a run's servers and routing don't change;
only the deprecation warning goes away.

## Compatibility

No behavior change for existing runs. Runs with
`error_on_agent_without_environment_server: true` now accept these
configs.

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>

This branch was previously deployed

1 inactive deployment
public — 2a117746 Deployed Oct 5, 2026 by copy-pr-bot[bot] via release / finalize / notify #4249
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:config Hydra configuration, schemas, composition, and compatibility complexity:low Localized change in one scope with a small, straightforward review surface feature New capabilities, enhancements, or enablement work ready-to-merge PR is approved, current, and waiting for final CI or merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants