Skip to content

feat(simple-agent): run simple agent through environment server sessions - #3784

Merged
ananthsub merged 13 commits into
mainfrom
ananthsub/simple-agent-sessions
Oct 5, 2026
Merged

ananthsub merged 13 commits into
mainfrom
ananthsub/simple-agent-sessions

Conversation

@ananthsub

@ananthsub ananthsub commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Lets Simple Agent run inside an Environment Server episode through agent sessions, gives the example single-tool-call Resources Server typed sessions, and adds a config that runs the example through the single-agent-turn Environment Server end to end. /run is unchanged.

Simple Agent sessions:

  • Simple Agent implements only the base agent's session hooks, _seed_agent_session_state and _close_agent_session_state. The base agent's shared bookkeeping from #3554 handles the rest: idempotent seed, binding a session to its seed, the session cookie, retries, and close receipts. The cookie key is one constant, AGENT_SESSION_COOKIE_KEY, in nemo_gym.base_responses_api_agent.
  • During the session, Simple Agent calls tools through the direct HTTP tool access the Environment Server grants, which carries the Resources session cookie. It accepts at most one direct HTTP grant and rejects sandbox access and required MCP access, which it does not support. A session without a grant never falls back to the configured Resources Server, which has no session for the episode: a tool call there fails the activation.
  • When observability is on, the session returns its agent invocation record at close, where the Environment Server stores it as the episode's agent observations.
  • Sessions live in the worker's memory, so seeding one requires num_workers: 1. /run keeps no session and still supports several workers.
  • resources_server is optional in the Simple Agent config, because a session gets its tools from the Environment Server. /run and /aggregate_metrics still require it.

Example single-tool-call Resources Server:

  • Implements typed, idempotent seed and close by resources_session_id. Close overrides the close_resources_session hook; the example no longer registers its own /close_session route, which the base route takes precedence over.
  • configs/example_single_tool_call_single_agent_turn.yaml fronts Simple Agent with single_agent_turn_legacy and grants it direct HTTP access to the example's tools. The existing config, which the quickstart and training docs use, still runs through /run.

single_agent_turn_legacy accepts a task_source that names its Resources Server, its agent, or itself. Collation sets task_source to the instance that declares the dataset, usually the agent, so the adapter rejected collated rows before.

The close_resources_session docstring states what a close override must do: succeed for a session that is already closed or was never seeded, keep a later seed from recreating a closed session, and raise when releasing fails so the caller can retry. It also notes that the default seed does not tie the session cookie to resources_session_id.

Contract checks:

  • nemo_gym.testing.session_conformance.check_resources_session_contract is an integration test of a server's session lifecycle: it seeds, re-seeds and closes a session through the server's real routes and middleware, in process, and checks the close rules, so it also catches a shadowed route. The base default and the example run it; a migrated server calls it from its own tests. nemo_gym.testing holds helpers that tests import.
  • tests/unit_tests/test_session_contract_repo.py reads every agent and Resources Server's source, since servers run in their own environments. It fails when an agent overrides the public seed or close instead of the hooks, when an agent implements the seed hook without the close hook, when a Resources Server registers its own /close_session, or when a typed seed has no close_resources_session.

Issue

No issue is linked; this is part of the Environment Server work.

Validation

  • pytest responses_api_agents/simple_agent/tests -q: 26 passed. A seed after close now returns 409 from the base bookkeeping instead of a 500. A session tool call without a grant fails and reaches no Resources Server; that test fails without the fix.
  • pytest resources_servers/example_single_tool_call/tests -q: 5 passed, including the session contract check.
  • pytest environment_servers/single_agent_turn/tests environment_servers/single_agent_turn_legacy/tests -q: 30 and 12 passed, including task_source naming the agent or the Resources Server, and rejecting another instance.
  • pytest resources_servers/swebench_pro/tests -q: 96 passed. pytest responses_api_agents/hermes_agent/tests -q (its own venv): 219 passed.
  • pytest tests/unit_tests/test_session_contract_repo.py tests/unit_tests/test_base_resources_server.py tests/unit_tests/test_agent_sessions.py tests/unit_tests/test_agent_session_retries.py tests/unit_tests/test_base_responses_api_agent.py -q: all passed. The repo-wide checks and the contract check each fail when the example registers its own /close_session again; the repo-wide agent check fails when Simple Agent overrides close_agent_session.
  • pytest tests/unit_tests -q: all passed except tests that need packages this environment lacks (opensandbox, tenacity, mlflow, wandb) and a Slurm test that calls the system pip.
  • pre-commit run on the changed files.

Rollouts

gym dataset collate on the new config stamped task_source with the agent's name, and the five collated example_single_tool_call tasks ran with gpt-5.6-sol through single_agent_turn_legacy with no failures. Three called get_weather and scored 1.0; the other two prompts are ambiguous ("get the weather for 3 cities in the us", "how's the outside?"), and the model asked which city was meant, so they scored 0.0 as the verifier intends.

A stateful check, run locally with example_session_state_mgmt given a typed seed and close (that change is a separate PR) and a single_agent_turn config like the one here: its counter starts at the task's initial_count, so a reward of 1.0 needs the seed, the agent's tool calls, and verification to reach the same Resources session. With the direct HTTP grant, all five collated tasks scored 1.0 (for example 3 seeded + 1 + 2 = 6), which confirms the grant's cookie reaches the seeded session without forwarding cookies on the agent seed request. With no grant, the earlier code scored 0.0 on all five because tool calls landed in fresh sessions; with the fix, those episodes fail with "Model called tool 'increment_counter', but this agent session has no direct HTTP tool access".

Compatibility

/run, its request and result shapes, and existing Simple Agent configurations are unchanged. resources_server becomes optional in the Simple Agent config, and configurations that set it behave as before.

@nemo-automation-bot
nemo-automation-bot Bot requested review from a team September 29, 2026 20:51
@yaoyu-33 yaoyu-33 added area:agent Agent harnesses and Responses API agent behavior complexity:medium Single-domain change with interacting parts or a moderate review surface feature New capabilities, enhancements, or enablement work needs-review PR is ready for code review and waiting on a reviewer labels Sep 29, 2026
@ananthsub
ananthsub requested a review from ffrujeri September 30, 2026 00:30
@ananthsub
ananthsub force-pushed the ananthsub/simple-agent-sessions branch from beaf7f1 to 3b34283 Compare September 30, 2026 00:52
aroshanghias-nvd pushed a commit to aroshanghias-nvd/Gym that referenced this pull request Sep 30, 2026
)

## What does this PR do?

Adds the configuration that runs SWE-bench Pro with Hermes through an
Environment Server:

- `benchmarks/swebench/pro/hermes.yaml`, alongside the existing
`opencode.yaml`. It runs SWE-bench Pro rows through
`single_agent_turn_legacy`, with the SWE-bench Pro Resources Server
preparing the task sandbox and Hermes working inside it.
- `resources_servers/swebench_pro/configs/swebench_pro.yaml` lists
`hermes_agent` among its allowed agents, so the Hermes configuration
composes the same Resources Server config as the other SWE-bench Pro
agents.

Rows reach the Environment Server by their agent, as with
`opencode.yaml`: one Environment Server fronts the Hermes agent in this
configuration.

Simple Agent sessions and the example Resources Server's sessions moved
to their own PR, [NVIDIA-NeMo#3784](NVIDIA-NeMo#3784).

## Stack position

Part 3 of 3. Previous:
[NVIDIA-NeMo#3721](NVIDIA-NeMo#3721). This is the top
of the stack.

No issue is linked because this is one layer of the Environment Server
stack.

## Validation

- `gym env resolve --config benchmarks/swebench/pro/hermes.yaml ...`
resolves to the Hermes agent, the SWE-bench Pro Resources Server, and
the `single_agent_turn_legacy` Environment Server, with that Environment
Server as the only one fronting the Hermes agent.
- `pytest tests/unit_tests/test_task_data.py
tests/unit_tests/test_global_config.py
tests/unit_tests/test_benchmarks.py -q`: 572 passed.
- `pre-commit run` on the changed files.

## Rollouts

Real Hermes + SWE-bench Pro rollouts with `gpt-5.6-sol` ran through
`single_agent_turn_legacy` on this stack, with Hermes calling the Model
Server directly from the task sandbox. The task resolved with reward
`1.0` (300 of 300 tests) in two runs and passed 299 of 300 tests in two
others, with empty failure sidecars and no leftover sandboxes. Those
runs selected the Environment Server explicitly; this configuration
reaches the same Environment Server by agent routing.

## Compatibility and benchmark impact

Configuration only. Existing SWE-bench Pro configurations are unchanged
apart from the added allowed agent.

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
aroshanghias-nvd pushed a commit to aroshanghias-nvd/Gym that referenced this pull request Sep 30, 2026
…VIDIA-NeMo#3786)

## What does this PR do?

Lets any Resources Server take part in an Environment Server episode
without writing session code.

The single-agent-turn Environment Server seeds each episode with a typed
`ResourcesSeedSessionRequest`, expects the caller's
`resources_session_id` back, and closes the session through
`/close_session` during cleanup. On `main`, only Resources Servers that
implement both calls themselves can run an episode. For every other
server, the base `/seed_session` returns an empty body that fails
validation, so the episode fails before verification, and
`/close_session` returns 404.

`SimpleResourcesServer` now answers both calls by default:

- `/seed_session` accepts a `ResourcesSeedSessionRequest` and returns
its `resources_session_id`. An Agent's `/run` still seeds with its
legacy body and still gets the empty response, so existing agents are
unaffected.
- `/close_session` is registered in the base and calls
`close_resources_session`, which does nothing by default.

A Resources Server with per-rollout state overrides `seed_session()` and
`close_resources_session()`. The hook is not called `close_session`
because several Gymnasium-based servers (`gymnasium`,
`openair_congestion`, `tales`) already define a
`close_session(session_id)` helper for their own state. Registering that
name would have exposed those helpers as HTTP routes with the wrong
signature.

SWE-bench Pro, the only server on `main` with its own `/close_session`,
moves its handler onto the hook. The base route is registered first, so
a route the server registered itself would never be reached. Its
behavior is unchanged: the same handler still accepts both the typed
close and the empty cookie-session close that `hermes_sandboxed_agent`
sends.

The Resources Server API guide describes the defaults and when to
override them.

## Issue

No issue is linked. This fills a gap found while reviewing the
Environment Server stack
([NVIDIA-NeMo#3720](NVIDIA-NeMo#3720)).

## Validation

- `pytest tests/unit_tests/test_base_resources_server.py -q`: 14 passed.
A new HTTP-level test checks that a server with no session code answers
a typed seed with its `resources_session_id`, a legacy seed with an
empty body, and a typed close. It fails on `main`.
- `pytest resources_servers/swebench_pro/tests -q`: 84 passed, including
the HTTP test that sends a typed close, an empty cookie close, and a
malformed close to `/close_session`.
- `gym env test` for `gymnasium` (10), `grl_sokoban` (9), and `tales`
(10), which define their own `close_session`, and for
`finance_sec_search` (96), which calls `super().seed_session()`; all
pass. `example_mcp_weather` (6) and `example_session_state_mgmt` (1)
pass.
- `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

N/A. This adds default responses to two session calls that `main`'s
agents do not make, and moves SWE-bench Pro's close handler without
changing its behavior, which its HTTP tests cover. No agent on `main`
seeds typed sessions yet, so an end-to-end Environment Server episode
needs [NVIDIA-NeMo#3784](NVIDIA-NeMo#3784) or
[NVIDIA-NeMo#3720](NVIDIA-NeMo#3720).

## Compatibility

- Legacy `/seed_session` bodies get the same empty response as before,
and servers that override `seed_session()` are unchanged.
- A server that registers its own `/close_session` route after calling
`super().setup_webserver()` is shadowed by the base route and must
override `close_resources_session()` instead. On `main` that applies
only to SWE-bench Pro, which this PR migrates.
[NVIDIA-NeMo#3784](NVIDIA-NeMo#3784) also registers one
for the example single-tool-call server. Whichever of the two PRs merges
second should move that server onto the hook.

---------

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
@github-actions github-actions Bot added the sla:review-overdue Review response is over the one-business-day SLA label Sep 30, 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/37380663103

@ananthsub ananthsub added the r0.7.0 Auto-cherrypick to release branch. Apply before merge; cherrypick happens after merge. label Oct 5, 2026
Merged via the queue into main with commit e0e63e5 Oct 5, 2026
44 checks passed
@ananthsub
ananthsub deleted the ananthsub/simple-agent-sessions branch October 5, 2026 22:23
ananthsub added a commit that referenced this pull request Oct 5, 2026
…nvironment Server

Adds a config that fronts Simple Agent with single_agent_turn_legacy and grants
it direct HTTP access to the counter tools, and checks the server with the
shared session contract check. Both needed Simple Agent sessions and
nemo_gym.testing from #3784.

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
ananthsub added a commit that referenced this pull request Oct 6, 2026
## What does this PR do?

Removes "native" from the Environment Server code. It meant two
different things there, which made the code hard to read:

- **Sessions an Environment Server creates**, as opposed to the cookie
sessions an agent's `/run` starts. These now use the protocol's own
names: agent session, Resources session, or Environment Server session
when both are meant.
- **Typed episode requests**, as opposed to flat legacy rows. These are
now episode requests, and tasks with a `task_id.taskset` and
`task_input` are materialized tasksets and rows, matching the existing
`MaterializedTask` and `_materialized_taskset`.

Renames:

| Before | After |
|---|---|
| `SingleAgentTurnLegacyEnvironmentServer._native_request` |
`_episode_request_from_row` (it builds an episode request from a flat
row) |
| `rollout_collection._native_episode_request_body` |
`_episode_request_body` |
| SWE-bench Pro `_native_session_locks`, `_closed_native_sessions` |
`_session_locks`, `_closed_sessions` |

Comments and help text that said "native taskset", "native row",
"native-only run", or "native episode response" now say materialized
taskset or row, a run of materialized rows only, or episode response.
Test names and fixture values in the adapter, `single_agent_turn`,
SWE-bench Pro, and Hermes tests follow the same names.

Other uses of "native" are unrelated and unchanged: Gym's built-in
agents, native Gym rollouts in reverification, native PDFs, Anthropic-
and MCP-native harnesses, and `is_responses_native`.

No behavior changes.

## Issue

No issue is linked; this is naming cleanup for the Environment Server
work, following review of
[#3784](#3784).

## Validation

- `pytest environment_servers/single_agent_turn/tests
environment_servers/single_agent_turn_legacy/tests -q`: 30 and 12
passed.
- `pytest resources_servers/swebench_pro/tests -q`: 96 passed.
- `pytest tests/unit_tests/test_rollout_collection.py
tests/unit_tests/test_environment_server.py -q`: 433 passed.
- `pytest responses_api_agents/hermes_agent/tests -q` (its own venv):
219 passed.
- `pre-commit run` on the changed files.

## Rollouts

N/A: renames identifiers, comments, help text, and test names only; no
behavior changes.

## Compatibility

No API, configuration, or result changes. The renamed functions and
attributes are private.

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
ananthsub added a commit to ananthsub/Gym that referenced this pull request Oct 6, 2026
…-NeMo#4070)

Removes "native" from the Environment Server code. It meant two
different things there, which made the code hard to read:

- **Sessions an Environment Server creates**, as opposed to the cookie
sessions an agent's `/run` starts. These now use the protocol's own
names: agent session, Resources session, or Environment Server session
when both are meant.
- **Typed episode requests**, as opposed to flat legacy rows. These are
now episode requests, and tasks with a `task_id.taskset` and
`task_input` are materialized tasksets and rows, matching the existing
`MaterializedTask` and `_materialized_taskset`.

Renames:

| Before | After |
|---|---|
| `SingleAgentTurnLegacyEnvironmentServer._native_request` |
`_episode_request_from_row` (it builds an episode request from a flat
row) |
| `rollout_collection._native_episode_request_body` |
`_episode_request_body` |
| SWE-bench Pro `_native_session_locks`, `_closed_native_sessions` |
`_session_locks`, `_closed_sessions` |

Comments and help text that said "native taskset", "native row",
"native-only run", or "native episode response" now say materialized
taskset or row, a run of materialized rows only, or episode response.
Test names and fixture values in the adapter, `single_agent_turn`,
SWE-bench Pro, and Hermes tests follow the same names.

Other uses of "native" are unrelated and unchanged: Gym's built-in
agents, native Gym rollouts in reverification, native PDFs, Anthropic-
and MCP-native harnesses, and `is_responses_native`.

No behavior changes.

No issue is linked; this is naming cleanup for the Environment Server
work, following review of
[NVIDIA-NeMo#3784](NVIDIA-NeMo#3784).

- `pytest environment_servers/single_agent_turn/tests
environment_servers/single_agent_turn_legacy/tests -q`: 30 and 12
passed.
- `pytest resources_servers/swebench_pro/tests -q`: 96 passed.
- `pytest tests/unit_tests/test_rollout_collection.py
tests/unit_tests/test_environment_server.py -q`: 433 passed.
- `pytest responses_api_agents/hermes_agent/tests -q` (its own venv):
219 passed.
- `pre-commit run` on the changed files.

N/A: renames identifiers, comments, help text, and test names only; no
behavior changes.

No API, configuration, or result changes. The renamed functions and
attributes are private.

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
(cherry picked from commit 412efc7)
Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
ananthsub added a commit to ananthsub/Gym that referenced this pull request Oct 6, 2026
## Expected
Harden Hermes sandbox sessions. Data/CLI changes are separate in NVIDIA-NeMo#3818.
Implements
[MB-1694](https://linear.app/nvidia/issue/MB-1694/runbook-onboard-sandboxed-harnesses-to-native-environmentserver).

## Added
- Shared base-agent seed/close retry handling, immutable seed binding,
cookie checks, session locks, and close receipts.
- Confirmed sandbox process cleanup, launch fencing, internal deadlines,
and owned-sandbox teardown. Close confirms cleanup before cancelling
borrowed-sandbox execution.
- Wall-time stops checkpoint partial results; stale PID signaling is
guarded by the remote receipt. Failed setup retains cleanup-only state
through a shared API.
- SWE-Pro keeps its existing scoring and completion rules.
Patch-extraction exceptions remain visible in the task's `error` field
without masking the sample.
- Identical activation retries join or replay the original run. Provider
failures remain retryable and skip verification.
- One request validator and prompt/settings builder for host and sandbox
execution.

## Concerns
- Sandbox prompt composition is unchanged: config system prompt, request
instructions, then input system message. Host execution now combines
these prompts instead of letting config replace the dataset prompt, and
honors request temperature, including zero. `developer` messages now
return 422. These host-path changes can change benchmark scores.
- Unsupported non-default controls return 422, including `top_p=1.0`,
`store`, `service_tier`, and metadata overrides. `model` must match the
configured model. `max_output_tokens` is rejected because Hermes does
not enforce a total-response budget; config `max_tokens` remains per
model call. These 422s are permanent episode errors.
- One activation per session, one agent worker, Linux exec-capable
sandbox. Close retries must finish within the retention window. Final
usage aggregation remains TODO.
- Separate follow-up found during smoke: the iteration-limit summary
sends `function.call_id` / `function.response_item_id` in tool history,
which Gym rejects with 422. The run returns `incomplete`; its existing
patch remains gradable. This update does not fix that summary path.
- Simple Agent (NVIDIA-NeMo#3784) is not modified here. The SWE-Pro recipe still
uses prepared flat rows through `single_agent_turn_legacy` and the
EnvironmentServer session lifecycle.

## Validation
Latest review follow-up: `e378cd45c` (includes the merge-queue assertion
fix in `4df61bf18`).

- Removed the redundant session-lock lookup guard and unused
thinking-setting diagnostics. Close-requested stops now report
`cancelled`, not `wall_time`; deadline behavior and scoring are
unchanged.
- Five targeted regression cases failed on the previous code. Final
focused suite: **563 passed on Linux**; **548 passed, 15 platform skips
on macOS**, Python 3.13.14.
- `pre-commit run --all-files`, `git diff --check`, and **18 Fern link
tests** passed. `cd fern && npm run check`: **0 errors, 1 warning**.
Cleanup docs now identify close ordering, not `setsid()` alone, as the
guarantee.
- Real-model Hermes × SWE-Pro smoke with
`nvidia/nemotron-3-super-120b-a12b` and a 15-second runner limit: 3
model calls, `incomplete` / `stop_reason=wall_time`, reward **0**,
`mask_sample=false`, `evaluation_completed=true`, 18 verifier tests.
Agent close preceded verification; no owned containers/network remained.
Runtime file hashes matched the local revision. This is a lifecycle
smoke, not an accuracy baseline.

<details>
<summary>Latest focused test command</summary>

```bash
python -m pytest \
  environment_servers/single_agent_turn/tests environment_servers/single_agent_turn_legacy/tests \
  environment_servers/legacy_agent/tests tests/unit_tests/test_episode_types.py \
  tests/unit_tests/test_environment_server.py tests/unit_tests/test_rollout_outcomes.py \
  tests/unit_tests/test_failure_kinds.py responses_api_agents/hermes_agent/tests \
  responses_api_agents/hermes_sandboxed_agent/tests resources_servers/swebench_pro/tests \
  tests/unit_tests/test_agent_session_retries.py tests/unit_tests/test_agent_sessions.py \
  tests/unit_tests/test_base_responses_api_agent.py tests/unit_tests/test_process_supervisor.py \
  tests/unit_tests/test_local_provider.py \
  --import-mode=importlib -q -o addopts='' -o log_cli=false --tb=short -rs
```

</details>

Scoring revert: `f06ef8dba888fcffaf86eae265fb63f3411db5f6`.

- `python -m pytest resources_servers/swebench_pro/tests
environment_servers/single_agent_turn/tests
environment_servers/single_agent_turn_legacy/tests
--import-mode=importlib -q -o addopts='' -o log_cli=false --tb=short`:
**135 passed**. Four failure-policy regression cases failed before the
revert, then passed; extraction errors retain the original exception
message.
- `pre-commit run --all-files` and `git diff --check` passed.
- Real-model SWE-Pro HTTP smoke with
`nvidia/nemotron-3-super-120b-a12b`, a 15-second agent limit, and an
invalid Git base injected only in the smoke driver: reward **0**,
`mask_sample=false`, `evaluation_completed=true`, and the actual
extraction exception in `error`. The agent closed before verification;
all owned containers/network were cleaned up. This checks failure
reporting, not benchmark accuracy.
- No documentation change needed: this restores the existing verifier
contract rather than adding an API.

Earlier review-fix validation at
`95a2b6481fa0225fc5a363443d9a536e98cd304f`:

- macOS/Python 3.13.14: **494 passed, 13 platform skips**.
- Linux/Python 3.13.14: **422 passed** across Hermes, shared sessions,
process supervision, SWE-Pro, and Environment Server tests. Final
verifier tests: **49 passed**; final supervisor/runner recheck: **40
passed**.
- Real-process regressions cover provider-group cancellation, lost spawn
handles, detached children, timeout checkpoints (including an
uncooperative worker), stale-PID protection, partial setup, and cleanup
retries.
- Two real-model SWE-Pro HTTP smokes with
`nvidia/nemotron-3-super-120b-a12b`: both reached verification after
agent close, returned valid reward 0, and left no owned containers or
networks. One stopped on model-output truncation; the forced 15-second
wall-time run returned `incomplete` / `stop_reason=wall_time`. Both had
no patch; these are lifecycle checks, **not accuracy baselines**.
Partial-work preservation is separately covered by deterministic
subprocess tests.
- `pre-commit run --all-files` and diff checks passed; Fern: 0 errors, 1
warning. The test import conflict with main was removed without
rebasing.
- Full repository CI is not claimed. The inherited DCO mismatch in
`07ca331ca1e12d234f1a3a60bb12e1651e496786` remains; the new commit is
signed off.

<details>
<summary>Current focused command</summary>

```bash
python -m pytest responses_api_agents/hermes_agent/tests \
  responses_api_agents/hermes_sandboxed_agent/tests \
  tests/unit_tests/test_process_supervisor.py tests/unit_tests/test_local_provider.py \
  tests/unit_tests/test_sandbox_stop_retry.py tests/unit_tests/test_base_responses_api_agent.py \
  tests/unit_tests/test_agent_sessions.py tests/unit_tests/test_agent_session_retries.py \
  tests/unit_tests/test_environment_server.py resources_servers/swebench_pro/tests \
  environment_servers/single_agent_turn/tests \
  --import-mode=importlib -q -o addopts='' -o log_cli=false --tb=short
python -m pre_commit run --all-files
cd fern && npm run check
```
</details>

<details>
<summary>Previous validation at d5f13f5 (historical)</summary>

Head: `d5f13f54cf762c7e687dd2d50d2583efe2c9b717`.

- macOS/Python 3.13.14: **487 passed, 8 skipped** (Linux-only checks).
- Linux/Python 3.13.14: **292 passed**, including Hermes, sessions,
process cleanup, and EnvironmentServer tests. Uploaded source and test
hashes match this commit.
- New regressions failed against the old code: ignored controls and
host/sandbox prompt/settings drift. Also test matching model names, zero
temperature, unchanged input, validation once per request, and rejection
of future non-default schema controls.
- `pre-commit run --all-files`, diff checks, and Fern passed (0 errors,
1 warning). New commit is DCO-signed.
- Fresh real-model public SWE-Pro HTTP smoke using
`nvidia/nemotron-3-super-120b-a12b`: **reward 1.0**, **18/18 verifier
tests passed**, 11,273-byte patch. Verified prompt ordering and
temperature at the Model Server, agent close before verification,
Resources close, and no remaining owned containers/network. Inspected
the patch and verifier results. Agent reached the 20-turn smoke limit
and returned `incomplete`, with the separate summary-call 422 described
above.
- This is one functional smoke, not an accuracy/baseline comparison.
Reused isolated dependency environments; full repository CI is not
claimed. The inherited DCO author/sign-off mismatch in
`07ca331ca1e12d234f1a3a60bb12e1651e496786` is unchanged.

<details>
<summary>Focused commands</summary>

```bash
python -m pytest responses_api_agents/hermes_agent/tests \
  tests/unit_tests/test_agent_session_retries.py \
  tests/unit_tests/test_agent_sessions.py tests/unit_tests/test_base_responses_api_agent.py \
  tests/unit_tests/test_process_supervisor.py tests/unit_tests/test_local_provider.py \
  responses_api_agents/hermes_sandboxed_agent/tests \
  environment_servers/single_agent_turn/tests environment_servers/single_agent_turn_legacy/tests \
  resources_servers/swebench_pro/tests tests/unit_tests/test_resources_sessions.py \
  tests/unit_tests/test_environment_server.py \
  --import-mode=importlib -q -o addopts='' -o log_cli=false --tb=short -rs
# Linux subset:
python -m pytest tests/unit_tests/test_process_supervisor.py tests/unit_tests/test_local_provider.py \
  tests/unit_tests/test_agent_session_retries.py responses_api_agents/hermes_agent/tests \
  environment_servers/single_agent_turn/tests \
  --import-mode=importlib -q -o addopts='' -o log_cli=false --tb=short -rs
python -m pre_commit run --all-files
cd fern && npm run check
```
</details>

</details>

---------

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: Ananth Subramaniam <ansubramania@nvidia.com>
Co-authored-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Co-authored-by: Adil Asif <adasif@nvidia.com>
(cherry picked from commit b846dfa)
Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
ananthsub added a commit to ananthsub/Gym that referenced this pull request Oct 6, 2026
…ons (NVIDIA-NeMo#3784)

Lets Simple Agent run inside an Environment Server episode through agent
sessions, gives the example single-tool-call Resources Server typed
sessions, and adds a config that runs the example through the
single-agent-turn Environment Server end to end. `/run` is unchanged.

Simple Agent sessions:

- Simple Agent implements only the base agent's session hooks,
`_seed_agent_session_state` and `_close_agent_session_state`. The base
agent's shared bookkeeping from
[NVIDIA-NeMo#3554](NVIDIA-NeMo#3554) handles the rest:
idempotent seed, binding a session to its seed, the session cookie,
retries, and close receipts. The cookie key is one constant,
`AGENT_SESSION_COOKIE_KEY`, in `nemo_gym.base_responses_api_agent`.
- During the session, Simple Agent calls tools through the direct HTTP
tool access the Environment Server grants, which carries the Resources
session cookie. It accepts at most one direct HTTP grant and rejects
sandbox access and required MCP access, which it does not support. A
session without a grant never falls back to the configured Resources
Server, which has no session for the episode: a tool call there fails
the activation.
- When observability is on, the session returns its agent invocation
record at close, where the Environment Server stores it as the episode's
agent observations.
- Sessions live in the worker's memory, so seeding one requires
`num_workers: 1`. `/run` keeps no session and still supports several
workers.
- `resources_server` is optional in the Simple Agent config, because a
session gets its tools from the Environment Server. `/run` and
`/aggregate_metrics` still require it.

Example single-tool-call Resources Server:

- Implements typed, idempotent seed and close by `resources_session_id`.
Close overrides the `close_resources_session` hook; the example no
longer registers its own `/close_session` route, which the base route
takes precedence over.
- `configs/example_single_tool_call_single_agent_turn.yaml` fronts
Simple Agent with `single_agent_turn_legacy` and grants it direct HTTP
access to the example's tools. The existing config, which the quickstart
and training docs use, still runs through `/run`.

`single_agent_turn_legacy` accepts a `task_source` that names its
Resources Server, its agent, or itself. Collation sets `task_source` to
the instance that declares the dataset, usually the agent, so the
adapter rejected collated rows before.

The `close_resources_session` docstring states what a close override
must do: succeed for a session that is already closed or was never
seeded, keep a later seed from recreating a closed session, and raise
when releasing fails so the caller can retry. It also notes that the
default seed does not tie the session cookie to `resources_session_id`.

Contract checks:

-
`nemo_gym.testing.session_conformance.check_resources_session_contract`
is an integration test of a server's session lifecycle: it seeds,
re-seeds and closes a session through the server's real routes and
middleware, in process, and checks the close rules, so it also catches a
shadowed route. The base default and the example run it; a migrated
server calls it from its own tests. `nemo_gym.testing` holds helpers
that tests import.
- `tests/unit_tests/test_session_contract_repo.py` reads every agent and
Resources Server's source, since servers run in their own environments.
It fails when an agent overrides the public seed or close instead of the
hooks, when an agent implements the seed hook without the close hook,
when a Resources Server registers its own `/close_session`, or when a
typed seed has no `close_resources_session`.

No issue is linked; this is part of the Environment Server work.

- `pytest responses_api_agents/simple_agent/tests -q`: 26 passed. A seed
after close now returns 409 from the base bookkeeping instead of a 500.
A session tool call without a grant fails and reaches no Resources
Server; that test fails without the fix.
- `pytest resources_servers/example_single_tool_call/tests -q`: 5
passed, including the session contract check.
- `pytest environment_servers/single_agent_turn/tests
environment_servers/single_agent_turn_legacy/tests -q`: 30 and 12
passed, including `task_source` naming the agent or the Resources
Server, and rejecting another instance.
- `pytest resources_servers/swebench_pro/tests -q`: 96 passed. `pytest
responses_api_agents/hermes_agent/tests -q` (its own venv): 219 passed.
- `pytest tests/unit_tests/test_session_contract_repo.py
tests/unit_tests/test_base_resources_server.py
tests/unit_tests/test_agent_sessions.py
tests/unit_tests/test_agent_session_retries.py
tests/unit_tests/test_base_responses_api_agent.py -q`: all passed. The
repo-wide checks and the contract check each fail when the example
registers its own `/close_session` again; the repo-wide agent check
fails when Simple Agent overrides `close_agent_session`.
- `pytest tests/unit_tests -q`: all passed except tests that need
packages this environment lacks (`opensandbox`, `tenacity`, `mlflow`,
`wandb`) and a Slurm test that calls the system pip.
- `pre-commit run` on the changed files.

`gym dataset collate` on the new config stamped `task_source` with the
agent's name, and the five collated `example_single_tool_call` tasks ran
with `gpt-5.6-sol` through `single_agent_turn_legacy` with no failures.
Three called `get_weather` and scored `1.0`; the other two prompts are
ambiguous ("get the weather for 3 cities in the us", "how's the
outside?"), and the model asked which city was meant, so they scored
`0.0` as the verifier intends.

A stateful check, run locally with `example_session_state_mgmt` given a
typed seed and close (that change is a separate PR) and a
`single_agent_turn` config like the one here: its counter starts at the
task's `initial_count`, so a reward of `1.0` needs the seed, the agent's
tool calls, and verification to reach the same Resources session. With
the direct HTTP grant, all five collated tasks scored `1.0` (for example
3 seeded + 1 + 2 = 6), which confirms the grant's cookie reaches the
seeded session without forwarding cookies on the agent seed request.
With no grant, the earlier code scored `0.0` on all five because tool
calls landed in fresh sessions; with the fix, those episodes fail with
"Model called tool 'increment_counter', but this agent session has no
direct HTTP tool access".

`/run`, its request and result shapes, and existing Simple Agent
configurations are unchanged. `resources_server` becomes optional in the
Simple Agent config, and configurations that set it behave as before.

---------

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
(cherry picked from commit e0e63e5)
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>
kajalj22 pushed a commit to kajalj22/Gym that referenced this pull request Oct 6, 2026
…on (NVIDIA-NeMo#4114)

## What does this PR do?

Makes a SimpleAgent agent session return the observations of every
activation at close, not only the last one.

During a session, each `/v1/responses` call replaced
`state.observations` with that call's invocation. An environment server
that drives several activations through one session, such as the user
simulation stack (NVIDIA-NeMo#3800, NVIDIA-NeMo#4080), would receive only the final
activation's invocation at close, and `ng_agent_observations` would show
one model call where there were several. `single_agent_turn` makes one
call per session, so it was unaffected.

The fix:

- Records and gaps now accumulate across a session's activations. The
bundle is rebuilt each time, so its unique-ID and parent-graph checks
still run.
- Every activation used the invocation ID `"root"`, so accumulating them
would fail the bundle's unique-ID check. Later activations in a session
now use `activation-<n>`. The first keeps `"root"`, so single-activation
sessions report exactly what they did before.

No issue is linked; this is a follow-up to NVIDIA-NeMo#3784, found while reviewing
the SimpleAgent changes in the user simulation stack.

## Validation

- New test
`test_agent_session_returns_observations_from_every_activation`: two
activations in one session return two invocations, `root` and
`activation-2`, each linked to its own model response. It fails without
the fix.
- `pytest responses_api_agents/simple_agent/tests
environment_servers/single_agent_turn/tests
environment_servers/single_agent_turn_legacy/tests`: all pass.
- `pytest tests/unit_tests/test_rollout_collection.py
tests/unit_tests/test_base_responses_api_agent.py
tests/unit_tests/test_rollout_observability.py`: all pass.
- `pre-commit run` on the changed files: all pass.

## Rollouts

N/A. Single-activation sessions, which every current configuration uses,
produce the same observations as before. The change affects only
sessions with several activations, which the user simulation environment
server introduces; that stack carries its own rollout evidence.

## Compatibility

No configuration change. For sessions with more than one activation,
`ng_agent_observations` now contains one invocation per activation
instead of only the last.

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
adil-a added a commit that referenced this pull request Oct 9, 2026
…ugh the base hooks)

main's session contract (#3784) makes the base agent own seed and close bookkeeping; an agent implements only
_seed_agent_session_state and _close_agent_session_state. The oracle agent keeps its behavior (sandbox connect at
seed, disconnect at close, 404 for an unseeded /v1/responses) but no longer overrides the routes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Adil Asif <adasif@nvidia.com>
adil-a added a commit that referenced this pull request Oct 9, 2026
…through the base hooks)

main's session contract (#3784) makes the base agent own seed and close bookkeeping; an agent implements only
_seed_agent_session_state and _close_agent_session_state. The Terminus 2 agent keeps its behavior (borrow the
resources server's sandbox at seed, disconnect at close) but no longer overrides the routes. The multi-worker
refusal now comes from the base as a ValueError before any Terminus state exists.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Adil Asif <adasif@nvidia.com>
adil-a added a commit that referenced this pull request Oct 9, 2026
… the base hooks, unbound template)

main's session contract (#3784) makes the base agent own seed and close bookkeeping; the mini-SWE agent now
implements _seed_agent_session_state and _close_agent_session_state. Episode state still lives in _sessions so
the harness and legacy /run paths are unchanged; the base agent's session cookie names the seeded state, so the
owner half of the episode key is derived from the agent session id instead of the resources cookie.

main's add_legacy_agent_environment_servers check wants every dispatchable agent config fronted by an
environment server; the mini-SWE episode-session config is an unbound template that prepare binds, so it
declares resources_server.name as ??? like the Terminus 2 and oracle templates.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Adil Asif <adasif@nvidia.com>
adil-a added a commit that referenced this pull request Oct 9, 2026
…ugh the base hooks)

main's session contract (#3784) makes the base agent own seed and close bookkeeping; an agent implements only
_seed_agent_session_state and _close_agent_session_state. The oracle agent keeps its behavior (sandbox connect at
seed, disconnect at close, 404 for an unseeded /v1/responses) but no longer overrides the routes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Adil Asif <adasif@nvidia.com>
adil-a added a commit that referenced this pull request Oct 9, 2026
…through the base hooks)

main's session contract (#3784) makes the base agent own seed and close bookkeeping; an agent implements only
_seed_agent_session_state and _close_agent_session_state. The Terminus 2 agent keeps its behavior (borrow the
resources server's sandbox at seed, disconnect at close) but no longer overrides the routes. The multi-worker
refusal now comes from the base as a ValueError before any Terminus state exists.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Adil Asif <adasif@nvidia.com>

This branch was previously deployed

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

Labels

area:agent Agent harnesses and Responses API agent behavior complexity:medium Single-domain change with interacting parts or a moderate review surface feature New capabilities, enhancements, or enablement work needs-review PR is ready for code review and waiting on a reviewer r0.7.0 Auto-cherrypick to release branch. Apply before merge; cherrypick happens after merge. sla:review-overdue Review response is over the one-business-day SLA

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants