Skip to content

feat(evaluator): add NeMo Gym runner for agent-eval - #986

Merged
SandyChapman merged 1 commit into
mainfrom
aalgo-432-walking-skeleton/schapman
Jul 31, 2026
Merged

feat(evaluator): add NeMo Gym runner for agent-eval#986
SandyChapman merged 1 commit into
mainfrom
aalgo-432-walking-skeleton/schapman

Conversation

@SandyChapman

@SandyChapman SandyChapman commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

What

Adds GymAgentTaskRunner, a NeMo Gym-backed AgentTaskRunner that runs an existing NeMo Gym environment through the gym CLI and adapts its rollout bundle into AgentEvalTrial objects, so AgentEvaluator can score and report Gym runs through the same seam as Harbor/Fabric. Gym owns execution and scoring; this runner only surfaces its per-attempt reward — it does not re-derive rewards.

Includes a worked mcqa example (packages/nemo_evaluator_sdk/examples/gym/) and unit tests against captured fixtures. Verified end-to-end against a real Gym checkout (5 tasks → 10 trials).

Mapping (Gym → Evaluator)

  • One Gym dataset → one run; each distinct row → one AgentEvalTask (id = content hash of the row); each attempt (--num-repeats) → one AgentEvalTrial.
  • Rollouts are attributed back to tasks by Gym's positional _ng_task_index (Gym mutates responses_create_params and drops injected fields, so a content round-trip is impossible), with a guard that fails loud if our row-dedup partition diverges from Gym's.
  • GymRewardMetric surfaces Gym's per-attempt reward; a missing/malformed reward is left unscored (None → nan, excluded from the mean) rather than counted as a spurious 0.0.
  • The *_failures.jsonl sidecar is read and surfaced as FAILED trials (counted + diagnosed), not silently dropped.

Execution & robustness

  • Two-step Gym flow — gym env start then gym eval run --no-serve --input — which reads a dataset directly without triggering Gym's split-driven data-prep. Shells out to the gym executable in the caller-provided checkout; never imports nemo_gym, never handles secrets.
  • Teardown signals the whole process group with SIGINT (mimics Ctrl-C so Gym's own shutdown() stops Ray + uvicorn cleanly), escalating to SIGKILL after a grace window.
  • Readiness keys off Gym's own All N / N servers ready! line (no server-count guessing); bounded collection via collection_timeout_s; refuses to reuse a populated output_dir (Gym appends to its failures sidecar); sets RAY_ENABLE_UV_RUN_RUNTIME_ENV=0 so Gym's per-server venvs don't trip Ray's uv run hook.

Scope / follow-ups

  • Aggregations (native pass@k cross-check + bubbling Gym's rollouts_aggregate_metrics.json as run diagnostics) are intentionally out of scope here — tracked separately.
  • Service-side execution (docker/k8s, Ray provisioning) is the evaluator plugin's job, not this SDK path.

Draft for early review.

Summary by CodeRabbit

  • New Features

    • Added Gym-backed agent evaluation with dataset-driven task discovery, configurable execution, repeat runs, and reward scoring.
    • Added a runnable command-line example that reports task and trial counts, aggregate results, and output locations.
    • Improved handling of trial attribution, failures, incomplete results, and malformed evaluation records.
  • Documentation

    • Added setup guidance, commands, expected outputs, and operational considerations for Gym evaluations.
  • Tests

    • Added fixtures and comprehensive coverage for task mapping, rollouts, scoring, failures, and incomplete data.

@github-actions github-actions Bot added the feat label Jul 30, 2026
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 29341/37347 78.6% 63.2%
Integration Tests 17287/36065 47.9% 20.5%

@SandyChapman
SandyChapman marked this pull request as ready for review July 30, 2026 12:30
@SandyChapman
SandyChapman requested review from a team as code owners July 30, 2026 12:30
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Gym-backed NeMo Evaluator runtime with task discovery, subprocess orchestration, rollout-to-trial conversion, reward metrics, tests, and a runnable CLI example with documentation.

Changes

Gym evaluation integration

Layer / File(s) Summary
Task discovery and reward contracts
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/gym_runtime.py, packages/nemo_evaluator_sdk/tests/agent_eval/test_gym_runtime.py, packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_example.jsonl
Defines runtime configuration, instruction rendering, stable task IDs, indexed dataset materialization, Gym task discovery, and reward metric behavior with fixture-backed tests.
Gym subprocess execution
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/gym_runtime.py, packages/nemo_evaluator_sdk/tests/agent_eval/test_gym_runtime.py
Runs gym env start and gym eval run --no-serve, waits for readiness, streams logs, validates output coverage, enforces timeouts, and terminates process groups.
Rollout attribution and trial conversion
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/gym_runtime.py, packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_rollouts.jsonl, packages/nemo_evaluator_sdk/tests/agent_eval/test_gym_runtime.py
Maps _ng_task_index values to tasks and creates completed, partial, or failed trials from rollout and failure JSONL records, including unique IDs for indexless attempts.
Runnable evaluation example
packages/nemo_evaluator_sdk/examples/gym/*
Adds CLI wiring for Gym evaluation and documents prerequisites, invocation, output mapping, logging, and operational constraints.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant run_gym_eval.py
  participant GymAgentTaskRunner
  participant NeMoGym
  participant AgentEvaluator

  User->>run_gym_eval.py: provide Gym checkout and runtime flags
  run_gym_eval.py->>GymAgentTaskRunner: discover tasks and run evaluation
  GymAgentTaskRunner->>NeMoGym: start environment and collect rollouts
  NeMoGym-->>GymAgentTaskRunner: return rollout and failure records
  GymAgentTaskRunner-->>AgentEvaluator: return evaluator trials
  AgentEvaluator-->>User: report aggregate scores and output bundle
Loading

Possibly related PRs

  • NVIDIA-NeMo/nemo-platform#544: Adds a parallel Harbor agent-evaluation runtime with similar task-runner, metric, and trial-adaptation patterns.

Suggested reviewers: arpitsardhana, ngoncharenko

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.31% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a NeMo Gym runner for agent evaluation. It is concise, specific, and clearly conveys the primary contribution.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch aalgo-432-walking-skeleton/schapman

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/nemo_evaluator_sdk/examples/gym/README.md (1)

1-67: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Reorder sections and add a "Next Steps" section per doc guidelines.

Move "## Prerequisites" ahead of "## How it runs Gym" (prerequisites must lead the page), and add a closing "Next Steps" section with cross-links to related docs (e.g., AgentEvaluator, Harbor/Fabric runtime examples). Consider moving the "How it runs Gym" architecture explanation behind a cross-link rather than inline, to keep this page a single how-to.

As per coding guidelines, "Always list prerequisites at the top of documentation pages before other content" and "Include 'Next Steps' section at the end with cross-links to related documentation content."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nemo_evaluator_sdk/examples/gym/README.md` around lines 1 - 67,
Reorder the README so the existing “## Prerequisites” section appears before “##
How it runs Gym,” keeping the introductory overview first. Add a closing “##
Next Steps” section with links to related AgentEvaluator and Harbor/Fabric
runtime documentation, and move the detailed Gym architecture explanation behind
a cross-link if needed to keep this page focused as a how-to.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/gym_runtime.py`:
- Around line 421-430: Update _dataset_path_from_tasks to inspect every task’s
non-empty gym_dataset_path, retaining the first path and validating that all
subsequent stamped paths match it. Raise ValueError when tasks contain
conflicting dataset paths, while preserving the existing missing-path error when
none are stamped.
- Around line 527-546: Update the success-path rollout handling in the loop that
builds AgentEvalTrial objects to validate NG_ROLLOUT_INDEX with the same type
check and synthesized suffix used by the failure branch. Ensure missing or null
indices receive unique fallback values rather than producing task IDs ending in
0 or None, while preserving valid rollout indices unchanged.

---

Outside diff comments:
In `@packages/nemo_evaluator_sdk/examples/gym/README.md`:
- Around line 1-67: Reorder the README so the existing “## Prerequisites”
section appears before “## How it runs Gym,” keeping the introductory overview
first. Add a closing “## Next Steps” section with links to related
AgentEvaluator and Harbor/Fabric runtime documentation, and move the detailed
Gym architecture explanation behind a cross-link if needed to keep this page
focused as a how-to.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e667e985-2206-4d60-b2eb-2c94966c2358

📥 Commits

Reviewing files that changed from the base of the PR and between 434c0db and 11b09dc.

⛔ Files ignored due to path filters (1)
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/runtimes/gym_runtime.py is excluded by !sdk/**
📒 Files selected for processing (6)
  • packages/nemo_evaluator_sdk/examples/gym/README.md
  • packages/nemo_evaluator_sdk/examples/gym/run_gym_eval.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/gym_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_example.jsonl
  • packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_rollouts.jsonl
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_gym_runtime.py

Comment thread packages/nemo_evaluator_sdk/examples/gym/README.md
Comment thread packages/nemo_evaluator_sdk/examples/gym/run_gym_eval.py Outdated
@SandyChapman
SandyChapman force-pushed the aalgo-432-walking-skeleton/schapman branch from 4156b6b to 18a7332 Compare July 31, 2026 13:08

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/nemo_evaluator_sdk/tests/agent_eval/test_gym_runtime.py (1)

391-455: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for _wait_for_servers.

The subprocess helpers are tested, but the readiness loop is not. It has three failure branches: early process exit, the uv required-version gate, and the startup timeout. All three are pure functions of a log file plus a fake process object, so they are cheap to test with a stub exposing returncode.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nemo_evaluator_sdk/tests/agent_eval/test_gym_runtime.py` around
lines 391 - 455, Add focused async tests for _wait_for_servers using a temporary
log file and a fake process exposing returncode, covering early process exit,
failure of the required uv-version check, and startup timeout. Assert each
branch raises or reports the expected outcome, and keep the tests isolated from
real subprocesses or server startup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/nemo_evaluator_sdk/examples/gym/README.md`:
- Line 45: Update the fenced code block in the README example to specify the
text language, using the existing output snippet unchanged and adding the
appropriate fence language identifier so it satisfies markdownlint MD040.

In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/gym_runtime.py`:
- Around line 807-812: Update the Gym trial collection flow around the
missing-task handling in the function returning trials: instead of only warning
for task IDs in missing, append one FAILED trial record for each unrepresented
task, including the task ID and diagnostic context such as rollouts_path or the
relevant log files. Preserve the existing warning and empty-collection behavior
as appropriate, and ensure _score_trials receives these failure trials so
partial runs complete without raising for absent tasks.

---

Nitpick comments:
In `@packages/nemo_evaluator_sdk/tests/agent_eval/test_gym_runtime.py`:
- Around line 391-455: Add focused async tests for _wait_for_servers using a
temporary log file and a fake process exposing returncode, covering early
process exit, failure of the required uv-version check, and startup timeout.
Assert each branch raises or reports the expected outcome, and keep the tests
isolated from real subprocesses or server startup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d29e61d2-d7c7-43ce-9b18-899d22e66b74

📥 Commits

Reviewing files that changed from the base of the PR and between 4156b6b and 18a7332.

⛔ Files ignored due to path filters (1)
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/runtimes/gym_runtime.py is excluded by !sdk/**
📒 Files selected for processing (6)
  • packages/nemo_evaluator_sdk/examples/gym/README.md
  • packages/nemo_evaluator_sdk/examples/gym/run_gym_eval.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/gym_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_example.jsonl
  • packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_rollouts.jsonl
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_gym_runtime.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_rollouts.jsonl
  • packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_example.jsonl
  • packages/nemo_evaluator_sdk/examples/gym/run_gym_eval.py

Comment thread packages/nemo_evaluator_sdk/examples/gym/README.md Outdated
@SandyChapman
SandyChapman force-pushed the aalgo-432-walking-skeleton/schapman branch from 18a7332 to 6c1bbde Compare July 31, 2026 15:12

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/gym_runtime.py`:
- Around line 803-809: The _resolve_task_id call at line 805 raises ValueError
when an index is absent from the index_to_task_id map, which aborts the loop
despite tolerant=True protecting against truncated lines. Wrap the
_resolve_task_id call in a try-except block that catches ValueError, increments
unattributed_failures, and continues the loop instead of propagating the
exception. This preserves the "count, don't drop" behavior stated in the
comment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9c000293-8244-45f2-80f5-a5a8f2d5d4ec

📥 Commits

Reviewing files that changed from the base of the PR and between 18a7332 and 6c1bbde.

⛔ Files ignored due to path filters (1)
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/runtimes/gym_runtime.py is excluded by !sdk/**
📒 Files selected for processing (6)
  • packages/nemo_evaluator_sdk/examples/gym/README.md
  • packages/nemo_evaluator_sdk/examples/gym/run_gym_eval.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/gym_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_example.jsonl
  • packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_rollouts.jsonl
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_gym_runtime.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_rollouts.jsonl
  • packages/nemo_evaluator_sdk/examples/gym/run_gym_eval.py
  • packages/nemo_evaluator_sdk/examples/gym/README.md
  • packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_example.jsonl

@SandyChapman
SandyChapman force-pushed the aalgo-432-walking-skeleton/schapman branch from 6c1bbde to 5483ada Compare July 31, 2026 15:20

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/nemo_evaluator_sdk/tests/agent_eval/test_gym_runtime.py (1)

271-281: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for a pre-existing rollouts.jsonl.

The tests cover only the sidecar. Add a case where rollouts.jsonl itself already exists, so the primary-artifact branch of _ensure_fresh_output is locked in too.

♻️ Suggested additional test
+def test_ensure_fresh_output_rejects_existing_rollouts(tmp_path: Path) -> None:
+    rollouts = tmp_path / "rollouts.jsonl"
+    rollouts.write_text("{}\n", encoding="utf-8")
+    with pytest.raises(FileExistsError):
+        _ensure_fresh_output(rollouts)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nemo_evaluator_sdk/tests/agent_eval/test_gym_runtime.py` around
lines 271 - 281, Add a test alongside
test_ensure_fresh_output_rejects_reused_dir that creates a pre-existing
rollouts.jsonl in tmp_path, calls _ensure_fresh_output with that path, and
asserts FileExistsError is raised, covering the primary artifact branch while
preserving the existing clean-directory case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/gym_runtime.py`:
- Around line 790-793: Update the success-record loop in the rollout evaluation
flow to count records where _resolve_task_id returns None instead of silently
continuing. Add an unattributed_successes counter and emit a warning alongside
the existing failures warning, including the count, rollouts_path, and
NG_TASK_INDEX, while preserving the current behavior of skipping those records.

---

Nitpick comments:
In `@packages/nemo_evaluator_sdk/tests/agent_eval/test_gym_runtime.py`:
- Around line 271-281: Add a test alongside
test_ensure_fresh_output_rejects_reused_dir that creates a pre-existing
rollouts.jsonl in tmp_path, calls _ensure_fresh_output with that path, and
asserts FileExistsError is raised, covering the primary artifact branch while
preserving the existing clean-directory case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 007eed31-828b-45aa-9589-568ead82c78c

📥 Commits

Reviewing files that changed from the base of the PR and between 6c1bbde and 5483ada.

⛔ Files ignored due to path filters (1)
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/runtimes/gym_runtime.py is excluded by !sdk/**
📒 Files selected for processing (6)
  • packages/nemo_evaluator_sdk/examples/gym/README.md
  • packages/nemo_evaluator_sdk/examples/gym/run_gym_eval.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/gym_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_example.jsonl
  • packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_rollouts.jsonl
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_gym_runtime.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_rollouts.jsonl
  • packages/nemo_evaluator_sdk/tests/agent_eval/fixtures/gym_mcqa_example.jsonl
  • packages/nemo_evaluator_sdk/examples/gym/README.md
  • packages/nemo_evaluator_sdk/examples/gym/run_gym_eval.py

Adds `GymAgentTaskRunner`, a NeMo Gym-backed `AgentTaskRunner` that runs an
existing NeMo Gym environment through the `gym` CLI and adapts its rollout
bundle into `AgentEvalTrial` objects, so `AgentEvaluator` can score and report
Gym runs through the same seam as Harbor/Fabric. Gym owns execution *and*
scoring; this runner only surfaces its per-attempt reward.

Mapping: one Gym dataset -> one run; each distinct row -> one `AgentEvalTask`
(id = content hash of the row); each attempt (`num_repeats`) -> one
`AgentEvalTrial`.

Attribution is by `_ng_task_index`, which the runner *assigns* rather than
infers. Gym only auto-assigns an index when a row lacks one, and its fallback
dedup keys off the raw jsonl line text -- a rule we cannot reproduce from
parsed rows, so inferring it risked silently swapping results between tasks.
Instead the runner materializes a normalized dataset (one row per requested
task, `_ng_task_index` stamped explicitly) and feeds that to Gym, giving a
total, order-independent index -> task map. This also confines a run to the
requested subset instead of rolling out the whole dataset.

Other behaviour:

- `GymRewardMetric` surfaces Gym's per-attempt reward; a missing or malformed
  reward is left unscored (None -> nan, excluded from the mean) rather than
  counted as a spurious 0.0.
- The `*_failures.jsonl` sidecar is read and surfaced as FAILED trials
  (counted + diagnosed), not silently dropped.
- One distinct row is one task: duplicate rows collapse and warn, since
  repeated attempts are `num_repeats`, not row duplication.
- Trial fan-out rejects an index map that disagrees with the task list, so a
  future Gym change cannot quietly attribute results to the wrong tasks.
- An incomplete collection fails the run rather than scoring a subset, and the
  error names the rollout bundle, the failures sidecar and the Gym logs instead
  of leaving the operator with the evaluator's generic task-id list.
- Teardown signals the whole process group with SIGINT (mimicking Ctrl-C so
  Gym's own shutdown() stops Ray + uvicorn cleanly), escalating to SIGKILL
  after a grace window.
- Subprocess output streams to log files under the run's work dir and is
  mirrored to the module logger at DEBUG, so callers control terminal
  visibility through ordinary logging config. Log drains are bounded: a
  grandchild that outlives teardown (e.g. Ray's detached gcs_server) can hold
  the inherited pipe open, which must not wedge the run.
- Readiness keys off Gym's own "All N / N servers ready!" line; refuses to
  reuse a populated output_dir (Gym appends to its failures sidecar); sets
  RAY_ENABLE_UV_RUN_RUNTIME_ENV=0 so Gym's per-server venvs don't trip Ray's
  `uv run` hook.

Includes a worked `mcqa` example and unit tests against captured fixtures.
Verified end-to-end against a real Gym checkout (5 tasks -> 10 trials).

Aggregations (native pass@k cross-check, bubbling Gym's
rollouts_aggregate_metrics.json) and service-side execution are out of scope
here and tracked separately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
@SandyChapman
SandyChapman force-pushed the aalgo-432-walking-skeleton/schapman branch from 5483ada to e38fad2 Compare July 31, 2026 15:26
@SandyChapman
SandyChapman added this pull request to the merge queue Jul 31, 2026
Merged via the queue into main with commit 2db2ea0 Jul 31, 2026
95 of 96 checks passed
@SandyChapman
SandyChapman deleted the aalgo-432-walking-skeleton/schapman branch July 31, 2026 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants