Skip to content

[GDPVal] Merge main into gdpval-devel - #2932

Open
Kh4L wants to merge 277 commits into
gdpval-develfrom
spanev/gdpval-devel-main-sync
Open

Kh4L wants to merge 277 commits into
gdpval-develfrom
spanev/gdpval-devel-main-sync

Conversation

@Kh4L

@Kh4L Kh4L commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Merges main (272 commits, head cd6acf792) into gdpval-devel. gdpval-devel stayed frozen at its hardware-validated head (2ce39b13e) until this branch passed the same real-checkpoint validation ladder; it has now passed 3/3 (offline refit + resume compatibility; 5-task real-checkpoint rollout smoke with 654 policy calls and 0×422; TRUE3 judging 80/80 trials with fail-closed resume).

Merge notes

  • 8 conflicted files, resolved as semantic unions. rollout_collection.py unifies devel's dispatch budget/drain/latency tracker with main's opt-in sidecar failure routing, loud drop banners, per-rollout latency stamping, and exporter uploads; two textual-merge bugs fixed (time import shadowing, lost result_strs init). Venv-path helper renamed to main's get_venv_path. local_vllm_model takes main's vllm==0.25.1 + flashinfer pin.

Regressions found by hardware validation and fixed here

  • Main's extra="forbid" on NeMoGymChatCompletionCreateParamsNonStreaming cascaded into the message TypedDicts and rejected payloads Gym's own producers emit and the previous ingress silently dropped:
    • Typed the provider extensions chat_template_kwargs, thinking, output_config (strictness retained; typos still 422).
    • Canonicalized Stirrup's provider-bound tool history to exactly the form the validated ingress forwarded ({role, content, tool_calls[{id,type,function}]} / {role, content, tool_call_id}), verified byte-identical against the pinned baseline over a 67-shape matrix; history objects untouched.
    • Restored the open input_audio.format token (the SDK literal is wav/mp3-only; Gym emits m4a/flac/ogg/aac/aiff).
  • run_e2e_multistage now forwards route_failures_to_sidecar (it silently reverted to raising).
  • Multistage fingerprint: dropped the input path (content is already hashed) and treats vLLM endpoint_* liveness knobs as connection fields, so relocating a run or adding endpoint knobs no longer stales journals.
  • Request-level chat_template_kwargs is accepted but forwarded only behind forward_request_chat_template_kwargs (default off) so agents attaching enable_thinking on every call do not silently change the generation regime relative to validated campaigns.

Compatibility notes

  • Journals written before this merge read STALE once (quarantined to .stale.*, never deleted) because result-relevant config keys were added on main; land syncs between campaigns.
  • route_failures_to_sidecar defaults to False on main (a failed /run raises); GDPVal configs that relied on sidecar routing must set it explicitly.
  • Result rows carry _ng_rollout_latency_ms.

Validation

  • Local: 4,010 unit tests pass (the same 31 fail on pristine main in this environment); 644 GDPVal + Stirrup tests; 550 core/proxy tests.
  • HSG: offline exact ELO refit 1342.0334; job 6849926 5/5 rollouts, 654 calls, 0×422; jobs 6793203/6795419 80/80 TRUE3 trials with byte-identical zero-POST resume.

kajalj22 and others added 30 commits August 6, 2026 11:59
## Summary

- Snapshots `fern/versions/latest/` → `fern/versions/v0.5.0/` (101
files) to freeze the docs at the 0.5.0 GA state
- Creates `fern/versions/v0.5.0.yml` with nav config pointing to the
frozen pages
- Registers `v0.5.0` as `stable` in `fern/docs.yml` (between Main and
0.4.0)
- Adds v0.5.0 release notes accordion to `release-notes.mdx` in both
`latest/` and the snapshot
- Drops pre-release tag: `PRE_RELEASE = "rc0"` → `""` in
`nemo_gym/package_info.py` (version is now `0.5.0`)

## Test plan

- [ ] Verify `docs.nvidia.com/nemo/gym` shows `0.5.0` in the version
picker as stable
- [ ] Verify the v0.5.0 docs snapshot renders correctly at `/v0.5.0/`
- [ ] Verify release notes page shows v0.5.0 accordion open by default,
v0.4.0 collapsed
- [ ] Verify `nemo_gym.__version__` returns `"0.5.0"` (no `rc0` suffix)
- [ ] Cherry-pick to `r0.5.0` after merging to main

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Kajal Jain <kajalj@nvidia.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
## Summary

Fixes the `test_internal_pages_are_linked_by_path_not_by_absolute_url`
test failure introduced by #2389.

The v0.5.0 release notes sandboxing section contained a
`docs.nvidia.com/nemo/gym/...` absolute link. The test requires all
internal docs links in `latest/` to use relative paths (to keep readers
within the version they're reading). Replaced with a relative path
`/nemo-gym/nemo_gym/sandbox/providers` in both `latest/` and the
`v0.5.0/` snapshot.

## Test plan

- [ ] `test_internal_pages_are_linked_by_path_not_by_absolute_url`
passes
- [ ] Cherry-pick to `r0.5.0`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Kajal Jain <kajalj@nvidia.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
## Summary
- Remove the NeMo Evaluator row from the Evaluation & Training table on
About → Ecosystem

## Test plan
- [x] `make docs-check`
- [x] Confirm Fern preview on the PR
- [x] Verify Ecosystem page no longer lists NeMo Evaluator under
Evaluation & Training

Signed-off-by: Seph Mard <smard@nvidia.com>
## Summary

- Adds v0.5.0 release highlights to the README News section with a link
to the full GitHub release notes
- Moves v0.4.0 and v0.3.0 into the Previous News collapsible as
condensed one-liners


🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Kajal Jain <kajalj@nvidia.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Looks like having more `num_workers` is beneficial in general.
Suggesting increasing the number of HTTP front ends.

Profiling run config from @sdevare-nv (nano opencode swe run) shows that
the model call latency is bottlenecked by the `num_workers` in the given
config.

I am adding two lines of logic change for this. 
I think session-based routing should be adjusted by using session ID
hashing when there are multiple workers in the run.

Summarized the profiling results in
[summary.html](https://sc.talos.nvidia.com/view/home/scratch.youngeunk_gpu_3/htmls/opencode_vs_openhands/interactive_opencode_openhands_trace_comparison.html)

---------

Signed-off-by: Youngeun Kwon <youngeunk@nvidia.com>
## Summary

- Bumps `PATCH` from `0` to `1` in `nemo_gym/package_info.py`
(cherry-pick of `0f9c375a1` from `r0.5.0`)

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Taken from #2175

---------

Signed-off-by: Brian Yu <bxyu@nvidia.com>
Signed-off-by: Brian Yu <bxyu@nvidia.com>
Signed-off-by: Brian Yu <bxyu@nvidia.com>
Bumps the pinned SHA for `_claude_review.yml` to FW-CI-templates
`v1.8.5` (`fe0ff3bbe10790c51273ee3c1189b3c0497bf860`).

**What changed in v1.8.5:**
- fix: tolerate trailing whitespace in `/claude review` trigger phrase

cc @KajalJ
## Summary

- Installs `enroot` via the NVIDIA CUDA apt repo (same pattern used in
the RL Dockerfile for `nsight-systems-cli`) so
`nemo_gym.sandbox.providers.enroot.EnrootProvider` works out of the box
— the provider calls `shutil.which("enroot")` at startup and raises
`RuntimeError` if the binary is missing
- Adds `bash` explicitly to the `apt-get install` block for clarity
(present in the Ubuntu 24.04 base but was undeclared)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Kajal Jain <kajalj@nvidia.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ATH (#2428)

`start_vllm_server` sets `runtime_env.py_executable` to
`ray_worker_py_executable`, which gives the vLLM actor this server's
venv interpreter. It does not put that venv's `bin` directory on the
actor's PATH, so console scripts installed next to the interpreter are
not resolvable in the actor or in the child processes it spawns.

vLLM declares `ninja` as a runtime dependency and shells out to it when
compiling kernels, so spinup fails with `Worker failed with error
'[Errno 2] No such file or directory: 'ninja''` even though ninja is
installed in the server venv.

This only shows up where ninja is not already on the system PATH. The
`vllm/vllm-openai` images have it at `/usr/local/bin`, so the gap was
invisible there.

NeMo-RL containers have ninja only inside a venv, and `local_vllm_model`
does not start there at all. I reproduced it on vllm 0.24.0 and 0.25.1,
so it is not tied to a version bump.

## Change

`_ray_actor_path` derives the PATH from `ray_worker_py_executable`, and
`start_vllm_server` passes it in `runtime_env.env_vars`. It is listed
ahead of the server's own `env_vars` so a config can still override
PATH.

Deriving it from `ray_worker_py_executable` rather than hardcoding a
directory keeps it correct wherever the venv lands, and makes
`py_executable` and PATH consistent instead of the actor getting the
interpreter from one venv and its binaries from another. It also covers
future vLLM shell-outs rather than ninja specifically.

## Testing

Three unit tests in
`responses_api_models/local_vllm_model/tests/test_app.py` cover the
helper (leading entry, inherited PATH preserved, no empty segment when
PATH is unset), and one covers the wiring by capturing the actor's
`runtime_env` — including that a server-supplied PATH still wins.

End to end in `nvcr.io/nvidian/nemo-rl:nightly`, serving
`Qwen/Qwen3-0.6B` via `ng_run`: without this change spinup fails on
ninja; with it the server reaches `All 1 / 1 servers ready!` and returns
completions

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
… rollouts (#2111)

## Summary

Adds a Gym-native pipeline for generating and evaluating long-horizon
synthetic conversational tool-use tasks.

The pipeline is split into five composable components:

- `conversational_tool_use_domain_generation`: generates domain
candidates.
- `conversational_tool_use_policy_tool_generation`: generates, refines,
validates, and judges domain policies and tool schemas.
- `conversational_tool_use_scenario_generation`: generates customer
scenarios for accepted policy/tool artifacts.
- `conversational_tool_use_agent`: runs the policy-model conversation
loop.
- `conversational_tool_use_simulation`: simulates users and tool
results, maintains session state, and verifies completed trajectories.

Each component uses standard Gym agents, resource servers, model-server
references, rollout JSONL, and explicit materialization steps. This PR
does not change `nemo_gym` core infrastructure.

## Design

### Generation pipeline

The generation stages are independent Gym agents connected through typed
rollout artifacts:

```text
domain rollouts
  -> policy/tool inputs and rollouts
  -> scenario inputs and rollouts
  -> conversational tool-use inputs
  -> policy agent + rollout simulation
```

Each rollout preserves the generated artifact, source lineage, model
requests and responses, parsing outcomes, and validation or judgment
results. Materializers produce stable IDs and the exact row contract
required by the next stage.

The pipeline supports `general` and `proactive` profiles. Prompt assets,
schemas, golden policy/tool references, checked-in examples, and
package-specific configuration live with the component that owns them.

### Conversational rollout environment

The policy agent supports multi-turn assistant messages, sequential and
parallel function calls, prefilled histories, transfers, and bounded
policy-model steps. A normal assistant message does not terminate the
rollout: the agent asks the simulator for the next user turn and
continues until a stop condition, transfer, incomplete response,
verification failure, or step limit.

The simulation resource server owns:

- per-rollout policy, tools, scenario, and conversation state;
- user and tool-result simulation;
- generated tool routing and JSON Schema validation;
- message-level and final-conversation verification;
- retryable provider-failure handling and failure labels;
- cleanup of completed or abandoned sessions.

Policy, user-simulator, tool-simulator, and judge roles are separate
model-server references and can use different models or providers.

### Transcript and data contracts

The returned Gym row has two aligned views:

- `responses_create_params` and `response` contain the policy-visible
Responses API transcript;
- `result.trajectory` contains simulator-native user, agent, tool,
terminal, and verification state.

The agent validates that the materialized system prompt and
model-visible tools match the top-level policy and simulator tool
definitions before starting a rollout.

Provider output order is preserved when a response contains assistant
content and function calls. Parallel tool results are appended only
after all selected response items. Each decomposed trajectory message
records its source `response_id` and `response_output_index`; the
complete raw response is stored once rather than duplicated on every
message.

Transient seeding, generation, and verification failures use Gym's
failure-sidecar contract and are excluded from scored rollout JSONL.
Semantic failures remain verified rollout results with synchronized
reward, invalid-reason, failure-label, and judge-diagnostic fields.

### Dataset preparation

The resource-server package includes builders for checked-in examples,
full `general` and `proactive` datasets, combined datasets,
parallel-tool-call variants, and deterministic balanced sampling.

Builders reject malformed or incomplete tools, invalid JSON Schemas,
malformed scenarios, duplicate tool names, and model-template leakage.
Full generated datasets, reports, and rollout outputs are intentionally
not checked in.

## Validation

### Automated validation

- `68 passed`: `resources_servers/conversational_tool_use_simulation`
test suite.
- `34 passed`: `responses_api_agents/conversational_tool_use/simulation`
test suite.
- Ruff and repository pre-commit hooks pass.

### Live rollout validation

- A hosted-inference rollout completed through Gym's
`inference_provider` using DeepSeek V4 Pro as the policy and Nemotron 3
Ultra as simulator and judge.
- The reward-1 trajectory exercised parallel function calls and
confirmed converted output ordering as `reasoning -> message ->
function_call(s)`.

### End-to-end NeMo RL validation

The materialized conversational tool-use dataset was used to train
`NVIDIA-Nemotron-3-Nano-30B-A3B-BF16` through Step 30 with multi-turn
GRPO. Steps 10, 20, and 30 were converted from DCP to Hugging Face
format and evaluated alongside the base checkpoint on TAU2 and TAU3 BM25
artificial analysis.

| Checkpoint | Airline | Retail | Telecom | TAU2 average | TAU3 BM25-AA
|
| --- | ---: | ---: | ---: | ---: | ---: |
| Base | 33.50% | 62.94% | 44.08% | 46.84% | 4.95% |
| Step 10 | 36.00% | 62.39% | 43.53% | 47.31% | 4.33% |
| Step 20 | 36.00% | 63.93% | 44.08% | 48.00% | 5.36% |
| Step 30 | 35.50% | 62.94% | 46.49% | 48.31% | 4.54% |

`TAU2 average` is the arithmetic mean across airline, retail, and
telecom. This run validates the complete downstream path from dataset
materialization through rollout collection, verification, training,
checkpoint conversion, and Gym evaluation. TAU2 average increased from
`46.84%` at base to `48.31%` at Step 30.

---------

Signed-off-by: Junkeun Yi <jkyi@nvidia.com>
Co-authored-by: Jonathan Li <jonli@nvidia.com>
…kends (#2418)

## Summary

Makes OpenSandbox `exec` resilient to two failure modes that both
surface as proxy `502`s.

1. **execd bind race.** Sandbox pods have no readiness probe, so the
server can mark a sandbox `Running` and proxy exec requests before its
exec daemon (`execd`) has bound its port. The proxy then returns `502
"could not connect to the backend"` for a short window after a pod first
reports `Running` (worse on cold images). The command never reached
`execd`, so retrying it is safe **even when `command_retries=0`** — the
default, since agent commands are usually mutating and must not
double-run.
2. **Dead backend.** A container that OOM-kills mid-run leaves a live
server record whose every request `502`s forever, because sandbox pods
never restart.

With `command_retries=0`, either mode currently fails the exec outright.

## Changes

- Add `_submit_command`, which retries **only** connect-class `502`s
under `operations.retries` (independent of `command_retries`) to cover
the bind window.
- Once that budget is exhausted, raise a typed
`SandboxBackendUnreachableError` so a dead backend **fails fast**
instead of retrying for hours.
- Retry logging now carries `operation` + `sandbox_id`, so an absorbed
create-probe retry is distinguishable from a failing agent exec.

No config change: `command_retries` stays `0`, and the `502`-retry lives
in a separate budget so mutating commands are never double-run.

## Testing

Two unit tests in `tests/unit_tests/test_opensandbox_provider.py`:
- transient `502`s are retried with `command_retries=0` without the
command double-running;
- persistent `502`s raise the typed backend-unreachable error.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Hemil Desai <hemild@nvidia.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Christian Munley <cmunley@nvidia.com>
## What changed

- Restore redirects from the former flat `/main/training-tutorials` path
to `/main/tutorials/training-tutorials`.
- Restore redirects from the former flat `/main/evaluation-tutorials`
path to `/main/tutorials/evaluation-tutorials`.
- Cover both each tutorial root and all descendant pages with explicit
root and `:path*` rules.

## Why

The Training Tutorials and Evaluation Tutorials sections gained a
`/tutorials` parent in the Fern navigation hierarchy. The compatibility
redirects for the previous versioned paths were later lost during Fern
multi-source configuration changes, causing previously shared URLs such
as `/nemo/gym/main/training-tutorials/nemo-rl-grpo/` to return 404.

The existing MDX links are intentionally unchanged: Fern renders them
through the working unversioned redirect chain.

## User impact

Previously shared `main` tutorial URLs will redirect to their current
nested locations instead of returning 404.

## Validation

- `make docs-check` — 0 errors (25 existing warnings)
- `git diff --check`

Signed-off-by: Lawrence Lane <25370251+lbliii@users.noreply.github.com>
Co-authored-by: Marta Stepniewska-Dziubinska <marta-sd@users.noreply.github.com>
…2 image check (#2440)

## What

Adds startup validation to `PinchBenchAgentConfig` and hardens the
agent's error handling and test coverage.

## Changes

**`app.py`**
- `web_search_provider` is now `Literal["brave", "tavily"]` — invalid
values rejected at startup
- Validates that the selected provider's API key is set
(`_validate_api_key_present`)
- Validates `sandbox_work_base` is an absolute path at startup (was only
checked inside `_run_in_apptainer_direct`)
- Validates `max_tokens <= context_window`
- Validates `sandbox_provider` name is known via `get_provider_class`
(catches typos before the first task)
- **FEP-1322**: validates `sandbox_spec.image` exists on disk when it is
a local path — fails fast at startup instead of silently scoring 0
across every task in a chained Slurm job
- Non-clean apptainer exit (rc ≠ 0 but archive present): logs a warning
and records `non_clean_exit_rc` in `raw_rollout` instead of silently
continuing
- `_parse_result`: logs a warning when multiple result JSON files are
found
- Imports `NG_*` constants from `nemo_gym.rollout_collection` instead of
redefining them
- Removes redundant comments, section dividers, and PR references
throughout

**`benchmarks/pinchbench/config.yaml`**
- Minor cleanup

**Tests** (`test_app.py`, `test_sandbox_isolation.py`,
`test_validation.py` — new file)
- 63 tests total, up from 26
- Parametrized routing sentinel tests and `_classify_task_failure`
- Removed ghost assertion for `PINCHBENCH_FORCE_LOCAL`, strengthened
`OPENCLAW_GATEWAY_TOKEN` check
- New cases: signal-kill detection (rc −15/137/143), `non_clean_exit_rc`
end-to-end, missing `task_id`, optional env var injection, `toolResult`
details fallback, transcript dedup, malformed JSON toleration, image
existence check

---------

Signed-off-by: plaszkiewicz <plaszkiewicz@nvidia.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
## Summary

- add a copy-paste model call capture workflow from environment startup
through inspecting `ng_model_call_capture`
- show representative attachment and CaptureStore JSONL output,
including gaps and the full metrics shape
- lead with the consumer workflow and move custom-agent correlation
helpers into an advanced section
- clarify that agent observations are currently emitted by the Claude
Code agent
- add model call capture to the model-server landing-page card grid
- keep the `latest` and `v0.5.0` documentation copies aligned

## Why

Addresses NVBug 6534797. The existing page described capture internals
but did not show readers how to enable capture in a real Gym run, where
to find the result, or the complete output shape.

## Validation

- `pytest -q tests/unit_tests/test_fern_docs_links.py`
- `ruff check tests/unit_tests/test_fern_docs_links.py`
- `git diff --check`

Release cherry-pick companion: #2416

---------

Signed-off-by: Rita Fernandes Neves <rfernandesne@nvidia.com>
Fixes #2436

## Problem

The Responses API can emit output-call items that were missing from
`NeMoGymResponseInputItem`. Because `NeMoGymResponseOutputItem` shares
that union, valid upstream responses containing those items failed local
validation and the model proxy returned HTTP 500 after the provider had
succeeded.

## Changes

- Add SDK-backed wrappers for file search, web search, computer use,
image generation, code interpreter, local shell, and custom tool calls.
- Preserve each upstream model's required `type` discriminator.
- Reject untagged output-call dictionaries before permissive union
coercion can misclassify them.
- Treat every non-message model output item as a transcript output
boundary.
- Keep input-only transcripts on the input side and preserve tool-only
model output during simulation canonicalization.
- Add regression coverage for schema validation, all output boundary
types, input-only transcripts, and tool-only responses.

Web search, file search, image generation, and code interpreter are
provider-executed. Computer actions, local-shell commands, and custom
calls are returned for the integrator to execute.

## Behavior change: the no-match case in
`split_responses_input_output_items`

Separately from the new item types, this changes what happens when no
output boundary is found at all. Calling it out explicitly since it is
not implied by the title.

The old loop read the leaked loop variable after `break`, so when
nothing matched it fell through with `i == len(items) - 1` and the final
item was always classified as output. The new code initializes `split_at
= len(items)`, so a transcript with no output boundary is now treated as
entirely input.

| Items | Before | After |
| --- | --- | --- |
| `[function_call_output]` | `([], [function_call_output])` |
`([function_call_output], [])` |
| `[user_message, function_call_output]` | `([user_message],
[function_call_output])` | `([user_message, function_call_output], [])`
|
| `[user_message, function_call]` | `([user_message], [function_call])`
| unchanged |

The old result was an off-by-one: a trailing user or tool item was
reported as model output even though the transcript contained no model
turn. Transcripts that do contain a boundary item are unaffected,
because the loop still breaks at the same index.

Worth noting that a single-item transcript of one of the newly
recognized types, for example `[web_search_call]`, returns the same pair
before and after, because the old fallback happened to land on the
correct split for that case. The two changes mask each other there,
which is why the existing tests did not surface the difference.

### Effect on the two callers

In both callers `output_items` becomes `response.output`, so the only
transcripts affected are those with no output boundary anywhere.

- `responses_api_agents/tau2/app.py`: the first split breaks at the
leading assistant message, per the comment there, so only the second
split over the remainder is in scope. If that remainder is entirely user
and tool items, its trailing item is no longer reported as model output.
An empty `output_items` slices safely, so `output_items[:1]` and
`output_items[1:]` still behave.
- `responses_api_agents/swe_agents/app.py`: only the non-replay branch
calls this, since the replay branch computes its own `split_at`. A
trajectory with no model turn now yields an empty `output` rather than
one mislabeled trailing item.

In both cases the new result is the accurate one, and neither path
raises on an empty list.

## Verification

- `1580 passed, 369 deselected, 5 subtests passed` in the complete
non-sandbox unit suite.
- `117 passed` across the response schemas, converter, model proxy, and
conversational simulation suites.
- All applicable pre-commit hooks passed.
- `git diff --check` passed.
- Live forced web-search requests returned valid `web_search_call`
output for `gpt-4.1-mini`, `gpt-5.4-mini`, `gpt-5.6-luna`, and
`gpt-5.6-terra`; the proxy failure was reproduced before the schema fix
and resolved afterward.

---------

Signed-off-by: Atharva-Kanherkar <142440039+Atharva-Kanherkar@users.noreply.github.com>
## Summary

Add OpenClaw rollout observations for both the standalone agent endpoint
and the PinchBench benchmark path.

OpenClaw is the observation producer in both paths. PinchBench exports
retained OpenClaw session artifacts and attaches the resulting OpenClaw
observation bundle; it does not define a separate observation format or
producer identity.

Observations are additive and are emitted only when observability and
rollout correlation are enabled. Existing agent responses, rewards, and
grading fields are unchanged.

## Capability coverage

`V` = supported, `O` = partial or path-dependent, `X` = unavailable. C1,
C2, and C4 are evaluated on the correlated Gym Model Server path.

| Agent | C1 | C2 | C3 | C4 | C5 | C6 | C7 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| OpenClaw | V | V | X | O | V | V | V |

C3 remains unsupported because OpenClaw does not emit standardized
per-turn records. C4 is partial because retained branched sessions are
reported but cannot always reconstruct a single model-visible branch.

## Changes

- Normalize retained OpenClaw session records into user, assistant,
reasoning, tool-call, and tool-result observations.
- Reconstruct subagent lineage from retained OpenClaw session stores.
- Correlate policy and judge model calls through rollout-prefixed Gym
Model Servers when configured.
- Preserve independent tool-call timing, status, duration, and output.
- Record context compaction and explicit observation gaps.
- Preserve direct model endpoint support when no Gym Model Server is
configured.
- Isolate observation capture failures from agent responses and
benchmark results.

## Evidence boundaries

- Model-call correlation requires transcript response IDs and a
configured Gym Model Server.
- OpenClaw does not identify the exact tool call that spawned a child
session.
- Branches within one session are reported but are not reconstructed as
separate invocations.
- Missing timestamps, transcripts, or correlation evidence are
represented as explicit gaps.

## Validation

- OpenClaw and PinchBench suites: 88 passed.
- Core trajectory, collector, correlation, base-agent, and model suites:
155 passed.
- Producer-to-collector projection verifies model calls, token details,
tool output, status, and independent timing in `ng_trajectory`.
- Ruff, formatting, compilation, shell, and diff checks passed.

## Follow-up pull requests

- [#2117 — Add Hermes rollout
observations](#2117)
- [#2118 — Add Pi rollout
observations](#2118)
- [#2119 — Add OpenCode rollout
observations](#2119)
- [#2120 — Add SWE and OpenHands rollout
observations](#2120)
- [#2122 — Add Harbor rollout
observations](#2122)

Foundation work is available in
[#2114](#2114) and
[#2153](#2153), both merged into
`main`.

---------

Signed-off-by: Michal Bien <mbien@nvidia.com>
## Summary
- Add an Equivalence Match verification pattern guide with code examples
- Cover exact, normalized, numeric, symbolic, fuzzy, structured, and
constraint-based matching
- Link relevant resources servers and update the Verification Patterns
index

## Testing
- `cd fern && npm run check`

Signed-off-by: Felipe Vieira Frujeri <ffrujeri@nvidia.com>
Co-authored-by: Seph Mard <seph.mard@gmail.com>
…sponse (#2461)

`NeMoGymResponseOutputItem` gained a `BeforeValidator` in #2438 that
rejects dicts without a `type` field. `test_run_workflow` mocks the
/verify echo with an untagged item, `{"role": "user", "content":
"obs"}`, so validating that payload into `AviaryAgentVerifyResponse` now
raises.

Without the guard, an untagged output-call dict validates as the wrong
union member: a web-search call with no `type` becomes
`NeMoGymResponseMcpListTools`.

The Aviary integration is unaffected. `AviaryAgent.run` posts
`verify_request.model_dump()`, and `AviaryResourcesServer.verify` echoes
that back, so each item carries its `type`.

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
## Summary
- Add new `responses_api_models/litellm_model` server for LiteLLM proxy
endpoints
- `LiteLLMModelServer` extends `openai_model`'s `SimpleModelServer`,
overriding only `responses()` to normalize LiteLLM proxy quirks
- Normalize `chat.completion` hybrid responses to standard Responses API
format (LiteLLM may downgrade `/v1/responses` internally)
- Fix `reasoning.effort="none"` (string) to `null` Pydantic validation
error for native Responses API responses
- `openai_model` remains clean -- no proxy-specific workarounds

## Test
- [x] Tested with GPT-5.4 via LiteLLM-backed endpoint -- native response
format, reasoning fix applied
- [x] Tested with Opus 4.6 via LiteLLM-backed endpoint --
chat.completion hybrid normalized
- [x] End-to-end rollout collection verified with both models
- [x] 11 unit tests for litellm_model (normalization + server
integration)
- [x] openai_model existing tests still pass after revert

---------

Signed-off-by: Jin Xu <jinx@nvidia.com>
Signed-off-by: Kajal Jain <kajalj@nvidia.com>
Signed-off-by: Jin Xu <jin.xu999@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Kajal Jain <kajalj@nvidia.com>
## Summary

On OpenSandbox deployments that enforce API-key auth on the server's
`/proxy/*` routes, every proxied execd request fails with `401
{"code":"MISSING_API_KEY"}` **even though `api_key` is configured** —
because the SDK wires the key only into the lifecycle-API clients. The
execd-facing clients (health ping, commands, filesystem) send only
`ConnectionConfig.headers`, which the provider never populated.

The visible symptom is nasty: `Sandbox.create` returns `202`, endpoint
lookups return `200`, but the SDK's internal health wait pings
`/proxy/44772/ping` keyless, gets `401` forever, and creation spins
until `ready_timeout` before failing with
`SandboxReadyTimeoutException`. Exec and file operations through the
proxy fail the same way.

## Change

In `_connection_config`, when `use_server_proxy` is enabled and an
`api_key` is configured, also pass the key as the `OPEN-SANDBOX-API-KEY`
header via `ConnectionConfig.headers` — the SDK merges those headers
into every execd client, so proxied pings/execs authenticate.

Scoped deliberately:
- **Proxy mode only.** In direct-endpoint mode the request terminates at
the sandbox itself, which runs untrusted code and must never see the
key.
- Harmless on servers that don't enforce proxy auth (verified: the
header is accepted either way).

## Testing

Extended `test_connection_config_and_image_policy`:
- proxy mode + api_key → `headers={"OPEN-SANDBOX-API-KEY": ...}`
present;
- direct mode + api_key → no `headers` injected.

Verified against a live enforcing deployment: keyless ping/exec → `401
MISSING_API_KEY`; with the header → `200`, including immediately after
create.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Hemil Desai <hemild@nvidia.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…hands.sh make-build retry (#2446)

The retry loop around `make build` in `setup_scripts/openhands.sh` has
three bugs:

1. **Wrong exit code captured.** `exit_code=$?` runs after the `if`
statement has completed, so it captures the if-statement's status
(always 0), not make's. Timeouts are reported as "failed with exit code
0" and exit 124 is never detected.
2. **Infinite loop on final failure.** On the final attempt, a failure
neither increments `attempt` nor exits, so the loop re-runs `make build`
forever instead of failing the setup. In a Docker build this hangs until
the CI job times out; worse, if a late retry "succeeds" against a
half-broken tree, the failure is deferred to runtime.
3. **Stale `.venv` between retries.** A first-attempt timeout leaves a
partially-built in-project `.venv` behind
(`POETRY_VIRTUALENVS_IN_PROJECT=true` is set just above); `make clean`
does not remove it. The script has already `cd`'d into `$openhands_dir`
at this point, so the removal only targets the OpenHands checkout.

This PR captures the exit code in an `else` branch (while it is still
make's), `exit`s with it on the final failure, and removes `.venv`
before retrying.

It also makes the first-attempt timeout env-overridable
(`MAKE_BUILD_TIMEOUT_SECONDS`, default unchanged at 2 minutes), so hosts
where a healthy `make build` legitimately takes longer can raise it
without editing the script — on such hosts the 2-minute guard otherwise
kills every healthy first attempt and forces the fallback path above.

Signed-off-by: Michal Futrega <mfutrega@nvidia.com>
#2449)

The request body's `max_output_tokens` is already mapped into
`inference_params` as `tokens_to_generate`, but `get_run_command` never
writes it into the generated OpenHands `config.toml` — only
`temperature` and `top_p` are forwarded. OpenHands therefore runs with
its own default output limit, and a caller-requested cap (e.g. from a
training harness that budgets generation lengths) is silently ignored.

Forward it as `llm.model.max_output_tokens` when present; behavior is
unchanged for requests that don't set it.

Signed-off-by: Michal Futrega <mfutrega@nvidia.com>
## Summary

Add Hermes production of `ng_agent_observations` for correlated rollouts
and update the trajectory capability matrix.

Hermes observations use instance-local hooks. Uncorrelated requests
retain the existing response path.

## Capability coverage

`V` = supported, `O` = partial or path-dependent, `X` = unavailable. C1,
C2, and C4 require correlated Gym Model Server capture.

| Agent | C1 | C2 | C3 | C4 | C5 | C6 | C7 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `hermes_agent` | V | V | X | V | V | V | V |

C3 remains unavailable because Hermes does not emit standardized
per-turn records.

## Captured evidence

- root and delegated-agent conversations, including system prompts and
plain reasoning
- parent and `delegate_task` spawn relationships when directly observed
- model-call references from response IDs
- model-visible tool outputs, execution status, timestamps, duration,
and independent concurrent-call intervals
- context-compaction outcomes and available pre/post token estimates
- invocation outcomes

Existing Hermes callbacks are chained. Observation failures do not mask
agent failures.

## Evidence boundaries

- Missing Hermes hooks and unattributed child spawns are reported as
explicit gaps.
- Opaque `reasoning_details` are not normalized.
- Compaction summaries and exact adjacent model calls are unavailable;
failed compactions do not report a post-compaction token count.
- Local terminal execution reports `no_sandbox_runtime`.
- External terminal backends report `sandbox_observation_unavailable`
because Gym does not receive their lifecycle or resource telemetry.

## Compatibility

- observation capture is enabled only for correlated rollouts
- the private self-call attachment is removed before verification
- verifier payloads, rewards, and token-bearing output retain existing
semantics
- `ng_agent_observations` is optional and additive

## Validation

- Hermes suite: 44 passed
- compatibility suite: 239 passed
- local E2E: pinned Hermes agent, concurrent tools, model-call
correlation, normalized conversation, and trajectory projection
- Ruff, formatting, compile, and diff checks passed

## Related pull requests

This is **4/7** in the rollout-observability series. Producer PRs 4/7
through 7/7 are independently based on `main`.

<!-- stack-links -->

- [1/7 #2114 — Define the rollout observation and correlation
contract](#2114)
- [2/7 #2153 — Add Claude Code rollout
observations](#2153)
- [3/7 #2115 — Add OpenClaw rollout
observations](#2115)
- [4/7 #2117 — Add Hermes rollout
observations](#2117)
- [5/7 #2118 — Add Pi rollout
observations](#2118)
- [6/7 #2119 — Add OpenCode rollout
observations](#2119)
- [7/7 #2120 — Add SWE and OpenHands rollout
observations](#2120)

---------

Signed-off-by: Michal Bien <mbien@nvidia.com>
Signed-off-by: Jakub Slowikowski <jslowikowski@nvidia.com>
Signed-off-by: Wojciech Prazuch <wprazuch@nvidia.com>
Signed-off-by: Rita Fernandes Neves <rfernandesne@nvidia.com>
Signed-off-by: ritaneves <rfernandesne@nvidia.com>
Co-authored-by: Jakub Slowikowski <jslowikowski@nvidia.com>
Co-authored-by: Wojciech Prazuch <wprazuch@nvidia.com>
Co-authored-by: Grzegorz Chlebus <gchlebus@nvidia.com>
## Summary

Add Pi production of `ng_agent_observations` for correlated rollouts and
update the trajectory capability matrix.

Pi's JSON event stream is captured while the process runs. Uncorrelated
requests retain the existing response path and provider configuration.

## Capability coverage

`V` = supported, `O` = partial or path-dependent, `X` = unavailable. C1,
C2, and C4 require correlated Gym Model Server capture.

| Agent | C1 | C2 | C3 | C4 | C5 | C6 | C7 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `pi_agent` | V | V | X | V | V | V | V |

C3 remains unavailable because Pi does not emit standardized per-turn
records.

## Captured evidence

- model-visible system prompt, user instruction, assistant output, tool
calls, and tool results
- model-call references from response IDs when `model_server` is
configured
- independent tool-call intervals and outcomes, including overlapping
calls
- context-compaction outcomes, summaries, token counts, retained
boundaries, and adjacent model-call references when available
- invocation status from Pi's terminal `agent_end` event
- partial evidence from incomplete or timed-out runs

## Evidence boundaries

- Pi does not expose subagent hierarchy.
- Tool timings use harness receipt timestamps rather than executor
timestamps.
- Incomplete model-call, tool, and compaction pairs are reported as
gaps.
- Unknown terminal outcomes remain `unknown` with an
`invocation_outcome_unavailable` gap.
- The integration runs on the host and reports `no_sandbox_runtime`.

## Compatibility

- `model_server` remains optional; direct-provider configuration is
unchanged
- configured Gym Model Servers use rollout-prefixed routing
- response, verification, reward, and token-accounting behavior is
unchanged
- observation parsing failures do not fail the rollout
- the private self-call attachment is removed before verification
- `ng_agent_observations` is optional and additive

## Validation

- Pi suite: 34 passed
- Shared observability, correlation, and collector suites: 82 passed
- Prefixed FastAPI response round-trip covered
- Ruff, formatting, and diff checks passed

## Related pull requests

This is **5/7** in the rollout-observability series. Producer PRs 4/7
through 7/7 are independently based on `main`.

<!-- stack-links -->

- [1/7 #2114 — Define the rollout observation and correlation
contract](#2114)
- [2/7 #2153 — Add Claude Code rollout
observations](#2153)
- [3/7 #2115 — Add OpenClaw rollout
observations](#2115)
- [4/7 #2117 — Add Hermes rollout
observations](#2117)
- [5/7 #2118 — Add Pi rollout
observations](#2118)
- [6/7 #2119 — Add OpenCode rollout
observations](#2119)
- [7/7 #2120 — Add SWE and OpenHands rollout
observations](#2120)

---------

Signed-off-by: Michal Bien <mbien@nvidia.com>
ananthsub and others added 15 commits August 31, 2026 11:21
## Summary

Gym depends on plain `uvicorn`, so `httptools` is not guaranteed to be
installed and Uvicorn can silently fall back to its pure-Python h11
parser.

This change adds `httptools` as a dependency and explicitly selects it
in the Uvicorn configuration. Explicit selection also turns a missing or
incompatible wheel into a startup error instead of silently changing the
server's parser.

## Performance methodology

For testing, added a synthetic benchmark that used a deterministic
`SimpleResponsesAPIModel` which returns cached, training-shaped
dictionaries. This isolates Gym's dispatch, middleware, serialization,
and HTTP serving costs from model generation.

This was tested with #2867 

The matrix covered:

- 32 output tokens (4.9 KB response) at 1,024, 4,096, and 8,192
concurrent requests
- 1,024 output tokens (22.6 KB response) at 1,024 and 4,096 concurrent
requests
- 65,536 output tokens (1.40 MB response) at 1,024 concurrent requests

The httptools-only variant improved median throughput by up to 4.2%. The
result was neutral at 8,192 concurrency, where server saturation and
scheduling variance dominated parser cost.

<img width="1205" height="780" alt="Screenshot 2026-08-28 at 5 11 33 PM"
src="https://github.com/user-attachments/assets/1d7382de-2ab4-44cf-b198-8d9f5f4aa716"
/>
<img width="1200" height="233" alt="Screenshot 2026-08-28 at 5 11 42 PM"
src="https://github.com/user-attachments/assets/8606eea7-6baa-46c9-8255-d1a62ee813da"
/>


## Test plan

- [x] `uv run pytest tests/unit_tests/test_server_utils.py -q`
- [x] `uv run pre-commit run --files nemo_gym/server_utils.py
pyproject.toml uv.lock`

---------

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
## What does this PR do?

Clarifies how to interpret the trajectory capability matrix for the
0.6.0 documentation:

- explains that the matrix reports observed producer coverage rather
than absolute schema capability
- adds an upfront Key caveats summary for path-dependent and conditional
behavior
- makes the relationship between model-call capture, trajectories, and
direct-provider calls explicit
- adds practical guidance for interpreting V, O, and X
- restructures producer-specific coverage notes into scannable bullets

## Validation

- make docs-check — 0 errors; the authentication-only redirects check is
deferred to CI
- python3 tests/unit_tests/test_fern_docs_links.py — 14 tests passed
- uvx pre-commit run --all-files — passed

## Checklist

- [x] I have read the contributing guidelines.
- [x] The change is focused; unrelated drive-by edits are tracked
separately.
- [x] Tests are N/A for this docs-only change; documentation validation
passes locally.
- [x] Pre-commit checks pass locally.
- [x] All commits have DCO sign-off.

Signed-off-by: Michal Bien <mbien@nvidia.com>
<!-- Thanks for contributing to NeMo Gym! Please fill out the sections
below. -->

## What does this PR do?

Adds `gym compare`, a new CLI command that diffs a baseline eval run
against a candidate run and writes a report.
Related epic: #1493 and issue
addressed by this PR: #2531

### Note
This to be fully completed needs statistical testing and [rollout
quality](https://rfc.frontier-evals.nvidia.com/r/gym-rollout-verification#ann=0da3dd8330c7),
this PR add a skeleton of the report and machine readible output file,
report deltas and ci.

Later output of statistical tests and  rollout quality work can be just
plugged in follow-up steps.

###  Output report example

[compare_report.md](https://github.com/user-attachments/files/31390538/compare_report.md)

## Checklist

- [x] I have read the [contributing
guidelines](https://docs.nvidia.com/nemo/gym/latest/contribute/development-setup).
- [x] The change is focused; unrelated "drive-by" edits are tracked as
separate issues/PRs.
- [x] Tests added or updated and pass locally, or N/A for docs-only /
non-code changes (so CI unit/server checks pass when applicable).
- [x] Pre-commit checks pass locally (`pre-commit run --all-files`) (so
CI lint/format/copyright pass).
- [x] All commits have DCO sign-off (`git commit -s`) (so the DCO check
passes).

---------

Signed-off-by: Martyna Patelka <mpatelka@nvidia.com>
…ine dispatch (#2892)

## Summary

The openai-2.44 pin rework (65129dd, #2456) stopped popping `strict`
from function tool definitions in
`responses_to_chat_completion_create_params`. This restores the
pre-#2456 strip.

`strict` is an OpenAI-hosted enforcement flag with no Chat Completions
semantics on self-hosted engines: vLLM 0.25.1's `FunctionDefinition`
keeps it, and chat templates that render unknown function keys (e.g.
Nemotron's `render_extra_keys`) inject `<strict>True</strict>` into
every `<function>` block. That perturbs the prompt of every tool-bearing
request off-distribution.

## Observed impact

In RL training (jobs 6707880/6712632 vs baseline 6682989): ~17% of
sequences per step ran away to the max-token cap, collected reward-0
truncation penalties, and were masked by sequence logprob error
(1e4–1e14). Restoring the strip returns the engine request to
byte-parity with the pre-#2456 tree.

## Question for reviewers

Was dropping the strip in #2456 intentional? If `strict` support on
self-hosted engines is planned, a config flag may be preferable — but
unconditional stripping matches pre-#2456 behavior and `strict` has no
Chat Completions semantics today.

## Changes

- `nemo_gym/responses_converter.py`: pop `strict` from function tool
dicts before building `NeMoGymChatCompletionToolParam`
- `tests/unit_tests/test_responses_converter.py`: assert `strict` does
not reach the engine params

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Pranav Thombre <pthombre@nvidia.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Fixes four inaccuracies in the Switchyard model-server docs page, found
by cross-checking every claim against the Gym and Switchyard 0.2.0
source: `--model` resolves a route's `id` field rather than its TOML
table name, the durable routing log is unavailable to the Gym-hosted
proxy (not standalone-binary-only in Switchyard), the stats snapshot
counts per-target `calls` not requests, and the switchyard-server build
line needed `--locked -p switchyard-server`. Filed against nvbug 6674632
for the 0.6.0 release. `fern check` passes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Lin Jia <linj@nvidia.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## What does this PR do?

Adds SWE-bench Pro as a standalone resources server using NeMo Gym's
`AsyncSandbox` API. SWE-bench Pro is kept
separate from the existing SWE-bench server because its public evaluator
is a standalone Modal/local-Docker script
with different task metadata, images, run scripts, parser scripts, and
grading flow; it does not expose the
`swebench` package's `run_instance` seam.

### Upstream evaluator parity

The verifier is ported from the pinned

[`swe_bench_pro_eval.py`](https://github.com/scaleapi/SWE-bench_Pro-os/blob/ca10a60a5fcae51e6948ffe1485d4153d421e6c5/swe_bench_pro_eval.py).
Relevant upstream functions retain their names and structure so they can
be compared directly:

- `strip_binary_hunks` is copied directly (apart from repository
formatting).
- `create_entryscript` preserves the upstream reset, patch, setup, test,
and parser sequence.
- `assemble_workspace_files` preserves the upstream workspace contract.
- `grade_output` extracts the pass/fail logic from upstream `main()`.

Substantive adaptations inside those functions are marked `NeMo Gym
change`. The intentional differences are:

- parse dataset list fields with `json.loads`/`ast.literal_eval` instead
of `eval`;
- read Dockerfiles, `run_script.sh`, and `parser.py` from prepared JSONL
rows instead of a runtime upstream checkout;
- retain patch-application status for the structured verifier response;
and
- replace Modal/Docker orchestration with a separate NeMo-specific
`run_verification` function.

### Sandbox and patch flow

`seed_session` creates the agent workspace from the task's immutable
image digest. At verification time the server
extracts committed, modified, and untracked changes relative to
`base_commit`, stops the agent sandbox, and uploads
the resulting patch plus evaluator assets into a fresh verification
sandbox. Golden-patch mode follows the same
verification path but uses the dataset patch, allowing evaluator and
image health to be tested independently of an
agent.

The server also:

- stops verification sandboxes in `finally` blocks and cleans abandoned
session sandboxes during application
  shutdown;
- persists the candidate patch, generated entry script, parser output,
test stdout/stderr, execution output, and
  patch status per request; and
- reports extraction, sandbox, patch-application, parser, and test
failures as structured zero-reward responses.

### Reproducible data and configuration

The preparation script pins both the Hugging Face dataset revision and
upstream evaluator commit, embeds each task's
evaluator assets, and resolves Docker Hub tags to immutable image
digests. This avoids runtime dependency on the
upstream repository layout and avoids case-sensitive tag rewriting by
registry mirrors.

The primary resources-server config remains in normal model-patch mode.
A separate example-only server uses
golden-patch mode with the simple agent so CI can validate all five
checked-in examples without depending on a
particular coding-agent harness. The OpenCode harness also falls back to
Python when an image lacks `curl` and strips
the newline from `pwd` before downloading its exported trajectory.

### Validation

- 17 SWE-bench Pro unit tests pass.
- Example data and all five checked-in rollouts pass `gym env test ...
+should_validate_data=true`.
- The five example golden-patch rollouts completed with rewards `[1.0,
1.0, 1.0, 0.0, 1.0]`; the unresolved NodeBB
example applied its patch and completed evaluation but did not report
every required test as passed.
- The OpenCode agent tests pass (3 tests).
- Ruff and scoped pre-commit checks pass.

## Checklist

- [x] I have read the [contributing
guidelines](https://docs.nvidia.com/nemo/gym/latest/contribute/development-setup).
- [x] The change is focused; unrelated "drive-by" edits are tracked as
separate issues/PRs.
- [x] Tests added or updated and pass locally, or N/A for docs-only /
non-code changes (so CI unit/server checks pass when applicable).
- [x] Pre-commit checks pass locally (`pre-commit run --all-files`) (so
CI lint/format/copyright pass).
- [x] All commits have DCO sign-off (`git commit -s`) (so the DCO check
passes).
- [x] Validation runs with opencode setup:

https://wandb.ai/nvidia/sdevare-gym-eval/runs/iugntgc0?nw=nwusersugamdevare
https://wandb.ai/nvidia/ffrujeri-gym-eval/runs/pz5753j6/logs

---------

Signed-off-by: Felipe Vieira Frujeri <ffrujeri@nvidia.com>
…2886)

## Summary
- pass the request context into workplace assistant verification so the
active session can be identified
- remove the per-session tool environment after verification, including
when scoring raises
- cover successful and failed verification cleanup

## Test plan
- [x] `uv run gym env test --resources-server workplace_assistant` (32
passed)
- [x] `pre-commit run --files
resources_servers/workplace_assistant/app.py
resources_servers/workplace_assistant/tests/test_app.py`

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
Signed-off-by: Sugam Devare <sdevare@nvidia.com>
Wraps [ViBench](https://github.com/ViBench/vibench-public) as a NeMo Gym
environment: a model builds a web app from a PRD, and grading stands the
app up for real, seeds it through its own UI, and drives it in a browser
against a human-written test plan. The score reflects what a user can
reach, not what the diff contains.

Adds `resources_servers/vibench` and
`responses_api_agents/vibench_agent`.

## Design

The agent owns the build sandbox and copies the finished app out as a
tarball; the resources server unpacks it and grades it in a fresh
compose project, one per test plan, via ViBench's `run-seed.py` →
`run-evaluate-post-seeding.py`.

The sandbox is never shared. Reaching into the agent's box needs
`serialize()`/`connect()`, which only the OpenSandbox provider
implements, so the shape `swebench` uses cannot run on Docker, Apptainer
or enroot. See [#2082](#2082)
for the design discussion; this PR has no dependency on it or on #2085.

`vibench_agent` subclasses `OpenCodeSandboxedAgent` and overrides only
sandbox acquisition and harvesting.

## Reward

One row is one `(app, artifact)` pair: built once, graded across that
artifact's test plans. Reward is the mean normalized score, continuous
rather than binary, with per-plan values in `reward_components`.
`build_failed`, `seeding_failure_rate` and `plans_graded_rate` are
reported separately, because a mean reward cannot distinguish a weak app
from one that never built or could not be seeded.

`REVERIFY_MODE` is `UNSUPPORTED` — grading depends on live app and
database state.

## Validation

Five example tasks graded end to end on a Docker host. Scores spread
from 0.00 to 1.00 with partial credit in between, and every zero carried
a real scorecard with per-step diagnoses (HTTP 500s, SQL errors) rather
than a harness failure.

The reward tracks capability: a weaker policy model scored 0.0 on a task
with 0/19 steps, its app unreachable, where a stronger model scored 1.0
on the same task with the same graders.

Scores also vary across repeats of the same model, since it does not
build the same app twice. Quantifying that — and separating it from any
variance in the LLM-driven verifier — is what reward profiling is for,
so `verified: false` stands.

`data/example_rollouts.jsonl` holds a five-task run.

## Reviewer notes

- **`network: host` is deliberately not used.** The in-sandbox harness
reaches the policy model at `get_server_url(...)` → `127.0.0.1:<port>`,
which inside a bridged container is the container itself; the harness
then makes zero LLM calls and exports an empty app with nothing logged.
`configs/docker.yaml` instead adds `host.docker.internal` via
`host-gateway` and the agent rewrites loopback model URLs to it. Only
`policy_model` binds `0.0.0.0`; the servers holding grader credentials
stay on loopback. Residual: the sandbox can reach host-published TCP
ports, which inference requires.
- **Grading timeouts interrupt rather than terminate.** ViBench tears
its compose project down in a `finally`, which CPython does not unwind
on SIGTERM. Escalation is SIGINT → SIGTERM → SIGKILL. Verified with a
forced timeout on a real host: SIGTERM leaked two containers and a
network, SIGINT cleaned up fully.
- **`artifact_dir` is a filesystem path shared by agent and resources
server.** Not a new constraint — grading already shells into a local
Docker daemon.
- **Requires `docker-compose` on `PATH`.** ViBench's scripts use the
legacy name; Docker 29.x ships only the plugin. The failure misleads, so
it is called out in the README.
- **Task fields are top-level, not under `verifier_metadata`**,
following `swebench` since `seed_session` and `verify` share one typed
request model. Happy to move them.
- **Grading runs on the resources server's Docker daemon**, not in a Gym
sandbox, because ViBench's grading stack is multi-container.
Single-image packaging is the prerequisite for multi-host.
- **`mvp` artifacts only.** ViBench ships reference implementations for
21 of 24 apps, so `feature-ri` is a follow-up; it needs a starting
codebase staged into the sandbox the way the PRD already is.
- **Row size**: rollout rows carry the harness's own stdout/stderr, so
`example_rollouts.jsonl` is ~500 KB for five tasks. Happy to trim.

## Security

The tarball comes from a box the model controlled, so it is untrusted:
unpacking refuses members and links resolving outside the app dir and
passes `filter="data"`, and artifact paths outside `artifact_dir` are
refused and never deleted. Grader credentials are scrubbed from captured
output before it is stored, since that output ships in the rollout
JSONL. Test plans and `test_assets/` are never staged into the build
box.

## Testing

`gym env test --resources-server vibench` passes. 75 unit tests (60
resources server, 15 agent), coverage 99%. `pytest tests/unit_tests/`
2621 pass. `pre-commit run --all-files` clean.

## Checklist

- [x] I have read the [contributing
guidelines](https://docs.nvidia.com/nemo/gym/latest/contribute/development-setup).
- [x] The change is focused; unrelated "drive-by" edits are tracked as
separate issues/PRs.
- [x] Tests added or updated and pass locally.
- [x] Pre-commit checks pass locally (`pre-commit run --all-files`).
- [x] All commits have DCO sign-off (`git commit -s`).

---------

Signed-off-by: raghavendran ramakrishnan <raramakrishn@nvidia.com>
Co-authored-by: raghavendran ramakrishnan <raramakrishn@nvidia.com>
<!-- Thanks for contributing to NeMo Gym! Please fill out the sections
below. -->

## What does this PR do?

1. Enable model-specific params for sbatch_external_vllm.sh:
benchmarks/nemotron_3.5_super/vllm_configs/*,
benchmarks/nemotron_3.5_super/sbatch_external_vllm.sh
2. Pass default overrides:
benchmarks/nemotron_3.5_super/sandbox_utils.yaml,
benchmarks/nemotron_3.5_super/sbatch_external_vllm.sh,
benchmarks/swebench/pro/opencode.yaml
3. Fix GlobalConfig struct: nemo_gym/global_config.py,
tests/unit_tests/test_global_config.py
4. Rollout collection always prints 500 error info:
nemo_gym/rollout_collection.py
5. Misc cleanup: responses_api_agents/opencode_sandboxed_agent/app.py

## Checklist

- [ ] I have read the [contributing
guidelines](https://docs.nvidia.com/nemo/gym/latest/contribute/development-setup).
- [ ] The change is focused; unrelated "drive-by" edits are tracked as
separate issues/PRs.
- [ ] Tests added or updated and pass locally, or N/A for docs-only /
non-code changes (so CI unit/server checks pass when applicable).
- [ ] Pre-commit checks pass locally (`pre-commit run --all-files`) (so
CI lint/format/copyright pass).
- [ ] All commits have DCO sign-off (`git commit -s`) (so the DCO check
passes).

---------

Signed-off-by: Brian Yu <bxyu@nvidia.com>
<!-- Thanks for contributing to NeMo Gym! Please fill out the sections
below. -->

## What does this PR do?

1. Terminus 2 initial implementation. Functionally works, but not fully
stable: responses_api_agents/terminus_2_sandboxed_agent,
benchmarks/terminal_bench_2_1/terminus_2.yaml
2. Append to Super 3.5 evals config:
benchmarks/nemotron_3.5_super/eval_container_config_with_staged.yaml,
benchmarks/nemotron_3.5_super/eval_container_config.yaml

## Checklist

- [ ] I have read the [contributing
guidelines](https://docs.nvidia.com/nemo/gym/latest/contribute/development-setup).
- [ ] The change is focused; unrelated "drive-by" edits are tracked as
separate issues/PRs.
- [ ] Tests added or updated and pass locally, or N/A for docs-only /
non-code changes (so CI unit/server checks pass when applicable).
- [ ] Pre-commit checks pass locally (`pre-commit run --all-files`) (so
CI lint/format/copyright pass).
- [ ] All commits have DCO sign-off (`git commit -s`) (so the DCO check
passes).

---------

Signed-off-by: Brian Yu <bxyu@nvidia.com>
`vibench_agent` shipped without a `README.md` in #2674. `ng_test_all`
filters testable components to directories containing one, then asserts
the filtered count matches the discovered count — so discovery found 174
components and tested 173, failing that assertion in all eight
server-suite shards even though every selected module test passed:

```
AssertionError: Mismatch on the number of total modules found (174) and the number of actual modules tested (173)!
Extra candidate paths:
- responses_api_agents/vibench_agent
```

`nemo_gym/cli/env.py:1114`, on `main` at a2c5222.

This adds the missing README. Docs-only — no code or config changes.

## Contents

- The build → harvest → verify flow, and that everything about driving
OpenCode is inherited from `opencode_sandboxed_agent` (only sandbox
acquisition and harvesting are overridden).
- Why the agent owns its sandbox instead of sharing one: reaching into
the agent's box needs `serialize()`/`connect()`, which only OpenSandbox
implements, so the `swebench` shape cannot run on Docker, Apptainer or
enroot. See #2082.
- Why `configs/docker.yaml` must be used instead of the stock provider
config — the stock 180s exec timeout kills long installs, and the
harness is told the model is at `http://127.0.0.1:<port>`, which inside
a bridged container is the container itself, so it makes zero LLM calls
and exports an empty app.
- An explicit warning not to "fix" that with `network: host`, and the
`0.0.0.0` exposure caveat for the policy-model bind.

Setup, task-row generation, reward definition and grading are not
duplicated here; they stay in the [resources server
README](https://github.com/NVIDIA-NeMo/Gym/blob/main/resources_servers/vibench/README.md).

## Note on the other CI failure

The `Test Wheel Use` failure in that run is independent of this change:
the wheel test enables prereleases/`unsafe-best-match`, unbounded
`mlflow>=3.15.1` resolved to 3.15.2, which selected `sqlalchemy
2.1.0rc1`, whose source metadata uv rejects (`duplicate normalized extra
name 'mssql-pymssql'`), aborting `arc_agi_resources_server` startup.
That needs a SQLAlchemy pin below 2.1 or prerelease containment in
transitive resolution — happy to open a separate issue or PR for it if
useful.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: raghavendran ramakrishnan <raramakrishn@nvidia.com>
Co-authored-by: raghavendran ramakrishnan <raramakrishn@nvidia.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Fixes #2699 and another bug found while testing

---------

Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Fixes #2221. Passing a config without fully defined mode emits a
warning. If user passes `--strict` - the error is raised for such cases.

---------

Signed-off-by: Marta Stepniewska-Dziubinska <martas@nvidia.com>
Signed-off-by: Serge Panev <spanev@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Kh4L added 4 commits September 1, 2026 13:23
…e strict chat schema

Signed-off-by: Serge Panev <spanev@nvidia.com>
… path-only fingerprint drift

Signed-off-by: Serge Panev <spanev@nvidia.com>
… schema

Signed-off-by: Serge Panev <spanev@nvidia.com>
…s for regime parity

Signed-off-by: Serge Panev <spanev@nvidia.com>
@Kh4L
Kh4L marked this pull request as ready for review September 3, 2026 17:12
@Kh4L
Kh4L requested a review from a team as a code owner September 3, 2026 17:12
@github-actions github-actions Bot added the sla:triage-overdue Review assignment is over the one-business-day SLA label Sep 4, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sla:triage-overdue Review assignment is over the one-business-day SLA

Projects

None yet

Development

Successfully merging this pull request may close these issues.