Repository navigation
feat(config): support environment servers that front several agents - #4053
Merged
ananthsub merged 7 commits intoOct 6, 2026
Merged
Conversation
Merged
7 tasks done
Contributor
|
πΏ Preview your docs: https://nvidia-preview-pr-4053-21d3adfdcb7d.docs.buildwithfern.com/nemo/gym |
ffrujeri
previously approved these changes
Oct 5, 2026
This was referenced Oct 6, 2026
ffrujeri
approved these changes
Oct 6, 2026
Contributor
|
π Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/37490986888 |
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>
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
main with #4053 and #4060 merged, plus the simplified #3800 (UserSim's resources server, preparation, and simple_agent changes; no shared data code). #3801, #4080, and #3802 are left out until they are restacked onto the simplified #3800, because they still carry the previous #3800. This branch only previews that base for review of the task-schema stack. It is replaced by main once the UserSim stack merges. Signed-off-by: Ananth Subramaniam <ansubramania@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>
yaoyu-33
added a commit
to yaoyu-33/Gym
that referenced
this pull request
Oct 6, 2026
β¦VIDIA-NeMo#3961) Hermes can be composed with a Resources Server through Environment Server. This PR decouples its default configuration and extracts a shared sandbox execution lifecycle for Pi (NVIDIA-NeMo#3626), Codex (NVIDIA-NeMo#3640), and OpenCode to adopt. Implements [GYMINT-5](https://linear.app/nvidia/issue/GYMINT-5), follows NVIDIA-NeMo#3554, and includes merged NVIDIA-NeMo#4053 from main. Only Hermes adopts the shared session in this PR; the other adapters retain their own output parsing when they migrate. ## Changes - Keep shared execution code in `nemo_gym/agent_utils/`, the agreed package name. This name preserves the agreed organization of the lifecycle and protocol helpers. Core connection, exec, transfer and provider lifecycle APIs remain in `nemo_gym/sandbox/`. - `SandboxSession` uploads its supervisor into the session directory, executes the harness, confirms cleanup and collects artifacts. `SandboxCommand` carries only argv and an explicitly installed Python interpreter. Preflight validates that interpreter before claiming launch; Python on the task image is not assumed. - Stage input and upload the supervisor in one cancellable task. Concurrent/retried closes share work; closing/closed derive from the close task. Execution retains the sandbox until explicit close. Borrowed cleanup failures retain state for retry; owned sandboxes can fall back to provider teardown. - Use one timeout-budget helper, one set of protocol filenames, and one cleanup receipt shape. Optional malformed diagnostics remain nonfatal; literal positive cleanup confirmation is still required. Session log access preserves diagnostics before files are released. - Hermes separates seed-time `install_runtime()` from `stage_activation()` and `collect_artifacts()`. Its process-identity schema stays in the adapter. The runner receives the stop-marker path from the session, preserving cancellation versus deadline reporting. - Keep `upload_text()` / `read_text()` in `sandbox/utils.py`, with Hemil's import/ordering cleanup. Remove unused byte helpers and their tests as requested. - Preserve native Hermes templates with explicit `resources_server: null` during legacy Environment Server migration. Bound Hermes, inherited Resources bindings and self-contained agents such as OSWorld retain their existing migration behavior. This fixes the merge-queue audit introduced by NVIDIA-NeMo#4084 without adding a conflicting legacy route. - Default an owned sandbox's missing `ttl_s` to install timeout + runner timeout + 600 seconds (23,100 seconds with default settings), so providers that enforce TTL can expire it after an agent-server crash. Preserve explicit values, including null, and borrowed connections; do not mutate shared configuration. ## Architecture From [Ananth's component diagram](NVIDIA-NeMo#3961 (comment)): ```mermaid flowchart LR subgraph AS["Agent server"] A["Harness adapter (Hermes)<br/>installs runtime, stages input, parses output"] S["Sandbox session<br/>uploads supervisor, runs, stops, collects, releases"] C["Supervisor client<br/>shell protocol over the session files"] end subgraph TS["Task sandbox"] D["Session directory<br/>claim, supervisor pid, stop request, receipt, log"] P["Process supervisor<br/>deadline, kills the process tree, writes receipt"] H["Harness process<br/>Hermes runner, Codex CLI"] end A -->|"argv and interpreter"| S S -->|"launch, stop, read receipt"| C C -->|"runs"| P C -->|"reads and writes"| D P -->|"starts and reaps"| H P -->|"writes receipt"| D ``` The session and supervisor client run on the agent server. Only `process_supervisor.py` from the shared package is copied into the sandbox; it uses only the standard library. A common runtime installer and provider default mounts from [the earlier diagram](NVIDIA-NeMo#3961 (comment)) remain follow-ups. The [owned-sandbox TTL request](NVIDIA-NeMo#3961 (comment)) is now implemented in this PR. ## Folder layout ```text nemo_gym/ βββ agent_utils/ β βββ __init__.py # Component responsibilities and execution locations β βββ sandbox_session.py # Server: upload supervisor; execute, stop, collect, close β βββ supervisor_client.py # Server: protocol files, timeout budget, launch/stop/receipt β βββ process_supervisor.py # Sandbox: deadlines, kill/reap descendants, write receipt βββ sandbox/ βββ api.py # Core connection, exec, file transfer and provider lifecycle βββ providers/ # Backend implementations βββ utils.py # Text transfer, CPU caps and image rewriting responses_api_agents/hermes_agent/ βββ app.py # Config, request/session hooks and Gym response conversion βββ sandbox.py # Install Hermes, stage input, parse artifacts/runtime metadata βββ sandbox_runner.py # Sandbox: Hermes SDK entry point and partial-result capture ``` The control files are `process_supervisor.py`, `launch.claim`, `supervisor.pid`, `stop.request`, `cleanup.json`, and `output.log`. `supervisor_client.py` documents each writer and timing. `session_dir` holds those files; `workdir` is the task repository. ## Compatibility - Downstream branches must use `nemo_gym.agent_utils.{sandbox_session,supervisor_client,process_supervisor}`. Both in-branch Hermes adapters and tests use the new package. Session adopters use `session_dir`, `stage_activation`, and `stop_harness`; they no longer upload or choose a supervisor path. - Default `resources_server` is null; unbound compatibility `/run` returns 422. Explicitly bound recipes remain supported; legacy agent swapping must restore that binding. The default model uses `${policy_model_name}`. - Owned TTL defaults only when the `ttl_s` key is absent. Explicit null retains provider defaults; long-lived or delayed sessions can set their own lifetime. Expiry still depends on provider support; this does not add TTL support to providers that ignore it. - No seed protocol or Hermes version change. Token accounting remains separate in NVIDIA-NeMo#3981. Invalid runtime metadata leaves valid responses intact and records an observation gap. - Transport errors remain execution errors after partial capture; capture failures are independent of cleanup confirmation. Model-writable output still does not qualify these trajectories for RL training. ## Validation **Latest reviewed head: `d5e6ad20b733faeeb3a35cabc4288c13609d4b11`.** [Full CI 37531286874](https://github.com/NVIDIA-NeMo/Gym/actions/runs/37531286874) and [build 37531287625](https://github.com/NVIDIA-NeMo/Gym/actions/runs/37531287625) **passed** on this head: core **6,594 passed / 2 skipped**, sandbox **913 passed / 5 skipped**, **95.25%** coverage, and all eight server shards. All applicable head checks are complete. The PR currently has no merge-queue entry; a maintainer needs to re-add it for fresh combined-tree CI. The prior failed queue run does not validate this fix. - Reproduced the [merge-queue failure](https://github.com/NVIDIA-NeMo/Gym/actions/runs/37526772843/job/112486565515) at combined SHA `0f1001ef33be7f4ab99d0e236a2ce15053f0f64e`. With this patch applied to that exact tree, migration and configuration tests passed: **219 passed**, including the unchanged repository-wide audit. - Final PR checkout: migration, configuration and session-contract tests **220 passed**. Six new cases cover explicit null versus bound Hermes, OSWorld, inherited bindings and exactly one native Environment Server after composition. Two independent reviews found no remaining issues; the six focused cases also passed independently. - Full `pre-commit run --all-files`, `git diff --check` and the default Hermes migration `--check` passed. Model rollout: **N/A for this follow-up**, which changes offline configuration migration only. - Prior head `a309dfb9a5cb8c65b6bd7366260c189c3dcd3284`: both Hermes suites plus shared session/client tests **375 passed, 9 skipped**; mocked Docker TTL contract tests **2 passed**. No live provider expiry/model run was performed for the TTL change; earlier model evidence remains scoped to its recorded revisions. - That prior head passed [CICD 37525046614](https://github.com/NVIDIA-NeMo/Gym/actions/runs/37525046614): core **6,588 passed, 2 skipped**, sandbox **913 passed, 5 skipped**, combined coverage **95.25%**, and all eight server shards. [Wheel/build 37525047397](https://github.com/NVIDIA-NeMo/Gym/actions/runs/37525047397) and lightweight checks also passed. These results do not validate this new migration commit or the failed merge-group tree. - All 20 implementation review requests remain addressed, including owned TTL; reviewer threads remain for reviewers to resolve. Shared Python provisioning remains future installer work. ```sh python -m pytest tests/unit_tests/test_add_legacy_agent_environment_servers.py \ tests/unit_tests/test_global_config.py tests/unit_tests/test_session_contract_repo.py \ --import-mode=importlib -q -o addopts='' -o log_cli=false --tb=short # Exact failed merge-group source plus the two-file patch: python -m pytest tests/unit_tests/test_add_legacy_agent_environment_servers.py \ tests/unit_tests/test_global_config.py \ --import-mode=importlib -q -o addopts='' -o log_cli=false --tb=short python scripts/add_legacy_agent_environment_servers.py --check \ responses_api_agents/hermes_agent/configs/hermes_agent.yaml pre-commit run --all-files git diff --check ``` --------- Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>
khazic
pushed a commit
to khazic/Gym-lao
that referenced
this pull request
Oct 7, 2026
## What changed and why Adds the paired NeMo User Sim runtime on the materialized `resolved_row` contract, tracked by NVIDIA-NeMo#985. - Adds `resources_servers/nemo_user_sim` for immutable-row validation, bounded verification, scorer classification, and conformant session cleanup. - Adds `environment_servers/nemo_user_sim`, which owns `ProbeEpisodeRuntime`, participant dispatch, tool simulation, embeddings, evidence capture, and cleanup. - Pins both servers to the same public UserSim revision: `a5f676bf6dc5a73914c8a0860f97c10dd2c214ee`. - Routes `nemo_user_sim:example` and `nemo_user_sim:validation` through `nemo_user_sim_environment`. - Uses the multi-agent environment bindings and materialized-row metrics already merged through NVIDIA-NeMo#4053 and NVIDIA-NeMo#4060. - Adds five synthetic example rows, matching metrics, and compact live rollout evidence so this PR passes changed-server data validation at its own head. - Adds Resources and Environment Server READMEs so both servers participate in the scheduled full suite. ## Review fixes - Assistant activation failures retain the prepared `trajectory_id` and are verified by the real Resources Server as unmasked reward zero. - UserSim contract rejection after an assistant activation is scored as a policy failure; Agent 404 and 409 responses remain retryable infrastructure failures. - Stable source `task_id` is accepted, and per-role overrides use `role_request_params` rather than the reserved `responses_create_params` row key. - Guarded health rows are rejected clearly at seed time for this pinned UserSim revision, while evaluator `reasoning_effort` is forwarded. - Resources session seed and close behavior passes Gym's session-conformance test, including idempotent seed/close and closed-session tombstones. - UserSim no longer forces global taskset routing mode. - Verifier evidence remains single-copy: invocations and the UserSim result are stored only at the top-level episode result. ## Validation - `uv run gym env test --resources-server nemo_user_sim` β 22 passed. - `resources_servers/nemo_user_sim/.venv/bin/pytest -q environment_servers/nemo_user_sim/tests` β 26 passed. - `uv run gym env resolve --config resources_servers/nemo_user_sim/configs/nemo_user_sim.yaml ...` β passed. - Scoped pre-commit, secret baseline validation, and `git diff --check` β passed. ## Rollout evidence Five live examples were generated with `meta/llama-3.2-11b-vision-instruct` as the user/policy model and `openai/gpt-oss-20b` as the judge/support model. All 5 completed with `mask_sample=false`; rewards were 0.8, 0.8667, 0.7333, 0.8, and 0.0. The committed rollout artifact is approximately 316 KiB and does not duplicate verifier evidence. ## Compatibility and benchmark impact This is a new pre-release runtime. Integration paths, component names, and tasksets use `nemo_user_sim`; upstream package imports and UserSim-owned data fields retain `usersim`. Infrastructure failures remain retryable and masked, while assistant-caused invalid responses are measured as unmasked reward zero. --------- Signed-off-by: Felipe Vieira Frujeri <ffrujeri@nvidia.com>
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 #4059 with fixes to
scripts/add_legacy_agent_environment_servers.py, theServerRefNotFoundErrorrename hint, and the troubleshooting docs, so the environment server and agent server changes land together. The same commits are in ther0.7.0cherry-pick of #3929 (#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 byenvironment_server_routes. The goal is one path, where every dataset declares a taskset and routes to an environment server: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: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 nextGray: 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 #3800. It references them through fields such as
user_agentandassistant_agent. Gym only recognized theagent_serverfield. For such a server:legacy_agentrelay in front of each._environment_servers_by_agentonly readagent_server.The first commit from #4059 (authored by Felipe Vieira Frujeri) adds
environment_server_agent_refstonemo_gym/global_config.py. Any top-level field whose value hastype: responses_api_agentsreferences an agent, and so doesagent_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.pystill read onlyagent_server:_agent_name_for_rownames 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'sagent_server, or to the only agent it references. A server that fronts several agents without anagent_serverhas no single agent to attribute, as before._validate_environment_serversrejects a row whoseagent_refnames an agent the server does not front. It skipped the check for any server withoutagent_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 asenvironment_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 inresources_servers/usersim/configs/usersim.yamlfrom #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 alegacy_agentrelay. That failed in three ways:InheritPathNotFoundError.single_agent_turn_legacy: the inherited server gains a secondlegacy_agentblock without an entrypoint, raisingAlmostServerError.environment_server_routesmay still use. For example, usersim routes its tasksets tousersim_environment.The script now handles a rename this way:
The renamed-agent hint
When an environment server references an agent that no longer exists,
ServerRefNotFoundErrorsuggests 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.agent_server.name.agent_server.fern/versions/latest/pages/troubleshooting/configuration.mdxdescribes 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
#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'sagent_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_routesnames and never uses an agent. So resolution now checks the route instead of choosing an agent:environment_server_agent_refs, must exist.taskset:cannot setagent:. Only benchmark datasets acceptagent:, 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 anagent_server.environment_server_agent_namesandenvironment_server_attributed_agentmove toglobal_config.pyso resolution and rollout attribution share that rule. Benchmarks record the environment server, andgym eval listshows 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.mdxdescribes the route check.This supersedes #4059. No issue is linked. It follows up #3929, from review on #4050, and provides the runtime support #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 anagent_serverand auser_agentreference.tests/unit_tests/test_rollout_collection.py: attribution toagent_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 withGlobalConfigDictParser. They cover a rename whose source server is generated, custom-named, orsingle_agent_turn_legacy, a two-agent server the script must leave alone, renaming both agents of that server (parsed witherror_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 andenvironment_server_agent_refsfind the same references.tests/unit_tests/test_benchmarks.py: a multi-agent taskset resolves without a pin, to none or to the server'sagent_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 onmain. They need thesandboxand telemetry extras, or Slurm.resources_servers/usersim/configs/usersim.yamlunchanged. After renamingusersim_assistant, the usersim config parses witherror_on_agent_without_environment_server: true,usersim_environmentfronts both agents, and both usersim routes still name an existing server.python scripts/add_legacy_agent_environment_servers.py --checkon this repository reports the same files and blocks before and after this change.pre-commit runon 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 onmainuntil #3800, which carries its own rollout evidence. The migration script runs offline.Compatibility and benchmark impact
taskset:can no longer setagent:. feat(data): collate declared tasksetsΒ #3818 accepted a pin that matched the routed agent; no configuration in this repository declares a taskset.gym eval list --jsonadds anenvironment_serverfield, andagent_nameis null for a taskset routed to a server with several agents.