Repository navigation
feat: add single-agent-turn environment server - #3717
Conversation
Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
f84ad7c to
c47a96c
Compare
β¦e result SingleAgentTurnResult nested the Resources verify response under `verification`. Stored rollout records therefore differed from an agent's /run result, and the collector had to reach into the nested object to find the reward. Make SingleAgentTurnResult the verify response itself, with agent observations as `ng_agent_observations`. It allows extra fields so each Resources Server's benchmark-specific fields are preserved, which makes ResourcesVerifyResponse redundant; remove it. Rollout collection can then store any Environment Server's result as returned. Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
| single_agent_turn: | ||
| environment_servers: | ||
| single_agent_turn: |
There was a problem hiding this comment.
mentioning single_agent_turn feels a bit clunky because it's not clear whether it should be the top level item or under environment_servers. any way we can fix this confusion?
There was a problem hiding this comment.
The top-level key is the instance name, which routes, _inherit_from, and server references use. The key under environment_servers is the implementation directory. The two usually match, which is where the confusion comes from. This follows the repo's config convention, so I added a comment to the template explaining both keys in 895d0af instead of renaming.
| ) from error | ||
|
|
||
| try: | ||
| await resources_cleanup.close() |
There was a problem hiding this comment.
Could we leave this teardown to the registered final cleanup? It still runs under the episode deadline: if that expires here, an already verified reward becomes an Episode timed out failure, even when final cleanup succeeds.
There was a problem hiding this comment.
Good catch. I removed the explicit close after verify, so the Resources session now closes only in final cleanup, outside the episode deadline. A new test checks that a slow close keeps the verified result. Fixed in 895d0af.
| try: | ||
| verify_http_response = await self.server_client.post( | ||
| server_name=self.config.resources_server.name, | ||
| url_path="/verify", |
There was a problem hiding this comment.
Could we bind rollout_context(request.episode_id.capture_key) for downstream calls? This route has no rollout prefix, and ServerClient's fallback does not recognize native episode_id, so verifier/judge model calls lose rollout correlation when observability is enabled.
There was a problem hiding this comment.
Agreed. run_request now binds rollout_context(request.episode_id.capture_key) around the whole episode, including final cleanup, so the base class covers every Environment Server. A new test checks that each downstream call, including /verify and /close_session, carries the attempt's rollout ID. Fixed in 895d0af.
| def _is_retryable_dependency_error(error: Exception) -> bool: | ||
| if isinstance(error, ClientResponseError): | ||
| return error.status in {408, 425, 429} or error.status >= 500 | ||
| return isinstance(error, (ClientConnectionError, TimeoutError)) |
There was a problem hiding this comment.
Could we also classify interrupted response-body reads as retryable? A mid-body disconnect raises ClientPayloadError, not ClientConnectionError, so this currently produces terminal=True and prevents another rollout attempt.
There was a problem hiding this comment.
Yes, ClientPayloadError is now retryable, and the retry test covers it. Fixed in 895d0af.
## What does this PR do?
Adds compatibility Environment Servers before environment-specific
Resources and Agent migrations:
- `legacy_agent` relays existing Agent `/run` and `/aggregate_metrics`
contracts without interpreting them.
- `single_agent_turn_legacy` converts legacy flat rows to the
single-agent-turn protocol and returns its result in the legacy result
shape, adding only `agent_ref`.
- Config composition, Agent swapping, scaffolding, migration tooling,
and repository configs route existing Agents through compatibility
deployments.
Rollout collection routes each flat row to the Environment Server that
fronts its Agent. The Agent-to-Environment-Server map is built once, and
every Agent in a batch is resolved before any rollout is dispatched. An
Agent fronted by several Environment Servers raises
`AmbiguousEnvironmentServerError` before dispatch, instead of silently
sending every row to the first one in config order. Several Environment
Servers may still front one Agent when no row routes by that Agent.
## Compatibility flow
This is the compatibility flow for existing single-agent environments.
Other Environment Servers may coordinate different participants.
```mermaid
sequenceDiagram
participant C as RolloutCollector
participant E as LegacyEnvironmentServer
participant A as ExistingAgentServer
C->>E: POST /run with legacy row
E->>A: Relay existing /run
A-->>E: Existing result body
E-->>C: Unchanged result body
```
## Stack position
Part 2 of 6. Previous:
[NVIDIA-NeMo#3717](NVIDIA-NeMo#3717). Next:
[NVIDIA-NeMo#3718](NVIDIA-NeMo#3718).
No issue is linked because this is one layer of the Environment Server
stack.
## Validation
- `uv run pytest tests/unit_tests/test_global_config.py
tests/unit_tests/test_rollout_collection.py -q`
- `uv run pytest
environment_servers/single_agent_turn_legacy/tests/test_app.py -q`
- `uv run pytest environment_servers/legacy_agent/tests/test_app.py -q`
- The rollout collection tests check that an ambiguous Agent fails
before any request is sent, and that twin Environment Servers for an
Agent no row uses do not interfere.
## Rollouts
`example_single_tool_call` with Simple Agent was run through
`legacy_agent` against a real model. The relay returned the same status,
result fields, and `Set-Cookie` as calling the Agent's `/run` directly,
and passed a malformed request's 422 through unchanged. End-to-end
evidence for the whole stack is on
[NVIDIA-NeMo#3722](NVIDIA-NeMo#3722).
## Compatibility and migration impact
Every runnable Agent instance now needs an Environment Server in front
of it. Out-of-tree configurations, including NeMo RL configurations,
must add a `legacy_agent` deployment or run
`scripts/add_legacy_agent_environment_servers.py`. Legacy request and
result bodies do not change.
A run whose rows route by an Agent that several Environment Servers
front now fails before dispatch with an error naming the candidates.
---------
Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>
Co-authored-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: yaoyu-33 <yaoyu.094@gmail.com>
## What does this PR do? Adds typed session support to SWE-bench Pro while preserving its legacy request shapes, and makes the single-agent-turn Environment Server verify through each Resources Server's own verify body. SWE-bench Pro sessions: - The Resources Server owns task sandbox setup, verification, and cleanup. - The typed sessions build on the SWE-bench Pro lifecycle from NVIDIA-NeMo#3542. `/close_session` closes a typed session when given a `ResourcesCloseSessionRequest`, and closes the cookie session when given an empty body, as `hermes_sandboxed_agent` sends. - A repeated verify of a typed session fails once its task sandbox has been consumed, instead of grading an empty patch and returning a normal-looking reward. Verification contract: - `single_agent_turn` now sends `/verify` the flat body an Agent's `/run` sends: the task's `task_data` fields, `responses_create_params`, and the agent's `response`. The request carries the Resources session cookie from seeding, which identifies the episode. - `SingleAgentTurnVerificationInput` and `SingleAgentTurnResourcesVerifyRequest` are removed, so no Resources Server depends on an Environment Server's protocol. This changes `single_agent_turn` code merged in NVIDIA-NeMo#3717. - SWE-bench Pro accepts one verify model, `SWEBenchProVerifyRequest`, and finds its sandbox through the session. - `ResourcesVerifyRequest[T]` stays for Resources Servers paired with their own Environment Server, whose payload type they define. - Records from `single_agent_turn` now carry the same echoed task fields as records from an Agent's `/run`, and reverification sends the body live grading sends. ## Single-agent-turn interaction flow This diagram describes SWE-bench Pro under the built-in single-agent-turn Environment Server. Other Environment Servers may use different lifecycle and result contracts. ```mermaid sequenceDiagram participant E as EnvironmentServer participant R as SWEProResources participant S as TaskSandbox E->>R: Seed with task data and caller ID R->>S: Start and prepare sandbox R-->>E: SandboxAccess and session cookie E->>R: Verify with task fields and the Agent response, under the session cookie R->>S: Extract patch and run evaluator R-->>E: Verification result E->>R: Close session ``` ## Stack position Part 1 of 4, based on `main`. Next: [NVIDIA-NeMo#3720](NVIDIA-NeMo#3720). No issue is linked because this is one layer of the Environment Server stack. ## Validation - `uv run pytest resources_servers/swebench_pro/tests -q` β 84 passed - `uv run pytest environment_servers/single_agent_turn/tests/test_app.py -q` β 12 passed, including a check that `/verify` receives the flat body under the Resources session cookie - `uv run pytest tests/unit_tests -q` β passes except two tests that also fail on `main` in the local environment (missing `sandbox` extras and a pip notice) - `pre-commit run` on the changed files ## Rollouts A real Hermes + SWE-bench Pro rollout through `single_agent_turn_legacy` resolved its task (reward `1.0`, 300 of 300 tests) with the flat verify body under a typed session, with an empty failure sidecar and no leftover sandboxes. Its record carries the task fields, as a `/run` record does. ## Compatibility and benchmark impact Legacy SWE-bench Pro request bodies remain supported. Existing Resources Servers verify single-agent-turn episodes without changes. `SingleAgentTurnVerificationInput` and `SingleAgentTurnResourcesVerifyRequest` are removed from `nemo_gym.single_agent_turn_types`; they landed in NVIDIA-NeMo#3717 and had no users outside this stack. --------- Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
β¦r stack Rebase the TB4 full-swapping integration onto main 17add3c, including mini-SWE refactor #3680 and the merged EnvironmentServer replacement stack #3717, #3718, #3720, #3721, and #3722. Adapt native OpenCode session support from #3639 (dce7f83) and Hermes session hardening from #3554 (6dc9c53). Keep existing legacy entrypoints. Add optional resource-resolved task context, caller-assigned native TB4 sessions, a generic swappable profile, and lifecycle-audited CPU/GPU/Compose smokes. Preserve the mini-SWE CLI/Responses flow while supervising all agent descendants before verification. Honor task users and limits, select sandbox-compatible bootstrap binaries, and avoid an unnecessary root UID switch when preparing the already-executable TB4 verifier. Bind native TB4 close to the merged HTTP hook and confirm destruction, allowing retries after provider deletion failures. Route Hermes iteration-limit summaries through its regular request formatter, preserving reasoning, history, and request settings without leaking internal Responses IDs into chat requests. Signed-off-by: Frederic Lafrance <flafrance@nvidia.com>
NVIDIA-NeMo#3812) ## What `interactive_browser` now serves the resources-session protocol an Environment Server uses (NVIDIA-NeMo#3717): a typed `/seed_session` under the caller's `resources_session_id`, and a `/close_session` that actually releases the browser. This is the follow-up I mentioned when closing NVIDIA-NeMo#2612. ## Why Two gaps under Environment Server episodes today: 1. **Typed seed is swallowed.** `BrowserSeedSessionRequest` allows extra fields, so a `ResourcesSeedSessionRequest` body validates as a legacy seed and gets an empty `{}` back. `single_agent_turn` then fails validating the seed response. 2. **Close is a no-op.** `/close_session` falls through to the base default, so an episode that ends before `verify` (timeout, cancelled agent) keeps its browser. With a remote provider that is a paid session held until the provider reclaims it. ## How Same shape as `swebench_pro`: - `seed_session` takes `Union[ResourcesSeedSessionRequest, BrowserSeedSessionRequest]` with `union_mode="left_to_right"`, so the typed body wins. It opens the browser from `task_data` under the caller's id and binds the cookie to that id, so direct-HTTP tool calls and the MCP token both land on that browser. A retried seed for the same `(episode_id, task_id)` returns the existing session; a different episode on the same id is rejected. - `close_resources_session` releases the browser, is idempotent, checks `episode_id`, and remembers closed ids. A seed that arrives after its close is rejected, so it can't open a browser nobody will close. An empty body still closes the cookie session for `/run` callers. - A per-id lock serializes seed and close. A close that arrives while the browser is still opening waits for it, then releases it. - `num_workers` other than 1 is refused, since browsers live in process. Legacy `/run` seeding is unchanged and still returns `{}`. ## Tests `tests/test_resources_sessions.py` (15 new): typed seed over HTTP, tool calls reaching the seeded browser over direct HTTP and over MCP, retried seed, id reuse across episodes, close before verify, double close, mismatched episode, malformed close (422), legacy seed and empty close, close during a slow seed, seed after close, `num_workers` guard, verify after close. ``` cd resources_servers/interactive_browser && pytest tests -q # 49 passed pytest tests/unit_tests/test_mcp_auto_exposure.py tests/unit_tests/test_base_resources_server.py -q # 60 passed ``` ## Not in this PR Idle expiry for sessions whose Environment Server never closes them (process killed mid-episode). I'll ask on NVIDIA-NeMo#3037 whether that belongs in the base class or per server before opening anything. π€ Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: waple0820 <232305951+waple0820@users.noreply.github.com> Co-authored-by: waple0820 <232305951+waple0820@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Stage the portable NOOA runtime for borrowed task sandboxes. This layer now builds on the standard single-agent-turn session integration. This is part of the existing NOOA integration stack, based on NVIDIA-NeMo#3848; no separate issue is needed for this requested architecture revision. Shared/base environment servers, shared agent contracts, generic sandbox code, and other harnesses remain fixed. Patterns adopted from merged NVIDIA-NeMo#3717 (single-agent-turn lifecycle), NVIDIA-NeMo#3554/NVIDIA-NeMo#3961 (Hermes sessions and sandbox cleanup), NVIDIA-NeMo#3640 (Codex), NVIDIA-NeMo#3626 (Pi), and open NVIDIA-NeMo#4560 (borrowed DeepSWE/OpenCode sandbox): seed Resources, seed agent, activate, close agent and collect evidence/cookies, verify, close Resources. Compatibility: in sandboxed NOOA, agent-owned background services stop before verification; the Resources-owned sandbox and persisted artifacts remain available. Tasks requiring a NOOA-started service during grading are unsupported by this lifecycle. The standard contract has an episode-wide deadline and no separate NOOA agent deadline; Terminal-Bench recipes retain verifier timeouts but do not reproduce canonical per-agent deadlines. Current revision: `0efed19193e1bb3e906318a6c6486f64078188ee`. Validation evidence: Implementation pushed before writing or running new behavior tests or local checks, as requested. Existing tests received only mechanical naming/import updates before this push. Checks, exact-layer tests, live-model smoke rollouts, and current-head CI are pending for this revision. Earlier test and CI evidence applies only to earlier revisions and does not validate this change. No new benchmark scores are claimed. ## Tests and validation Current revision: `a55f1eddbf13448c30728dafd17ef0af8ac26dbc`. Rebased the existing ten-PR stack onto main `9ef5ed0f80a9f81c5d82af6588cd4cc956c4f5d1`; assembled tip is `196bd5e333abd6e8ee7a93f4c8805ae33f67a66a` (NVIDIA-NeMo#4357). Main compatibility adds only two test updates: NVIDIA-NeMo#3846 gives the fake HTTP response its headers mapping for checkpoint-aware Environment seeding; NVIDIA-NeMo#4088 checks serialized tool-output snapshots while retaining history, round-trip, and interpreter-object coverage. Both changes were independently reviewed. The rebase preserves upstream NOOA serialization and runtime configuration changes. Validation on the exact rebased revisions: - NVIDIA-NeMo#3843: **170 passed** (one expected skip-verification aggregate-metrics warning). - NVIDIA-NeMo#3846: **195 passed**. - NVIDIA-NeMo#3844: **218 passed, 8 Linux-only installer skips** on macOS. - Assembled NVIDIA-NeMo#4357 tip: **373 passed, 16 platform skips** (10 installer and 6 Linux supervisor tests). - `pre-commit run --all-files` passed. DCO sign-offs, sequential layer ancestry, and the focused final diff were checked. Command from each revision's `responses_api_agents/nooa_agent`: `python -m pytest tests --import-mode=importlib -q -o addopts= -o log_cli=false --tb=short -rs`. Exact early-layer tests use installed NOOA `051472343211914222e24ce36d8752f4e86bbe43`; the assembled tip uses coherent core/CLI/BenchAgent `19caab169b018476ac433d040f6ae3f06aeff101`. Commands and logs: `/tmp/nooa-merge-main-candidate-20261016/final-tests-{3843,3846,3844,4357}/`. Prior real-model evidence for the unchanged reviewer-fix behavior remains at `/tmp/nooa-review-fixes-20261016/live-final/`, run on pre-rebase tip `8185eb1f405ba476aab42b00a222f92dbec4364f`: real Qwen output-token and iteration exhaustion each verified once; an actual iteration limit followed by injected cleanup failure remained fatal, skipped verification, and closed both sessions. This main sync adds test compatibility rather than new agent behavior, so that smoke was not rerun and is not presented as a smoke of the rebased tree. It used the integration-test Resources fixture and was not a new SWE-bench/OpenSandbox canary. GitHub CI is requested for the new heads. Optional cross-harness conformance failures also reproduce on main and are separate from the required `Nemo_CICD_Test` gate. Existing branch names, bases, draft states, native groups, and description text outside this validation section are preserved. Required [branch CI](https://github.com/NVIDIA-NeMo/Gym/actions/runs/38964312169) passed on this revision: 8,793 core tests, 921 sandbox tests, 95.07% coverage, and 226 NOOA tests (including Linux installer tests for NVIDIA-NeMo#3844). Signed-off-by: Nikhil Deshmudre <nikhil.deshmudre@gmail.com>
What does this PR do?
Implements the built-in resources-backed single-agent-turn Environment Server protocol. The Environment Server assigns participant session IDs, registers cleanup before each seed, runs one Agent activation, verifies the response through Resources, and closes both participants.
The episode result is the Resources verify response itself, plus the Agent's session observations as
ng_agent_observations.SingleAgentTurnResultsubclassesBaseVerifyResponseand allows extra fields, so each Resources Server's benchmark-specific fields are preserved. A stored rollout record therefore has the same shape as an Agent's/runresult, and rollout collection can store it without knowing its fields. This makesResourcesVerifyResponseredundant, so it is removed.Tool-grant session requests are serialized as JSON before dispatch so Pydantic URLs are strings and the
kind/transportdiscriminators remain present. The Resources session closes only in final cleanup, which the episode deadline does not bound, so a slow or failed close is logged and cannot replace a verified result. Every downstream call in an episode, including cleanup, carries the attempt's rollout ID for observability. Connection failures, including a response body cut off mid-read, are retryable. Verification needs the Agent close response, which carries the Agent's observations and final Resources cookies, so a transient close failure makes the episode fail as retryable instead of retrying the close.Single-agent-turn interaction flow
This diagram describes the built-in single-agent-turn use case. Other Environment Servers may coordinate different participants and use different request, result, and lifecycle flows.
sequenceDiagram participant C as RolloutCollector participant E as EnvironmentServer participant R as ResourcesServer participant A as AgentServer participant M as ModelServer C->>E: POST /run E->>R: Seed resources session R-->>E: Tool access and optional SandboxAccess E->>A: Seed agent session E->>A: One /v1/responses activation A->>M: Model requests A-->>E: Agent response E->>A: Close agent session A-->>E: Agent observations E->>R: Verify response Note over E,R: Final cleanup, outside the episode deadline E->>R: Close resources session E-->>C: Verify response plus agent observationsStack position
Part 1 of 6. Next: #3724.
No issue is linked because this is one layer of the Environment Server stack.
Validation
uv run pytest environment_servers/single_agent_turn/tests/test_app.py -quv run pytest tests/unit_tests/test_resources_sessions.py tests/unit_tests/test_agent_sessions.py -qorjsonand validated withAgentSeedSessionRequest.model_validate_json.verificationand keeps benchmark-specific fields.Rollouts
See #3722 for real-model rollouts through this Environment Server.
Compatibility and benchmark impact
No rollout is redirected by default. Resources tool transports remain explicit opt-ins. Cleanup failures after a verified reward no longer turn that rollout into a terminal failure.
ResourcesVerifyResponse, added tonemo_gym.base_resources_serverin #3544, is removed. Nothing outside this stack used it. A caller that parses a verify response without knowing its concrete type can declareextra="allow"on its own model, asSingleAgentTurnResultdoes.