Skip to content

refactor(evaluator)!: unify the backend contract on evaluate/evaluate_dataset - #1173

Closed
SandyChapman wants to merge 4 commits into
mainfrom
evaluator-agent-eval-submit/schapman
Closed

refactor(evaluator)!: unify the backend contract on evaluate/evaluate_dataset#1173
SandyChapman wants to merge 4 commits into
mainfrom
evaluator-agent-eval-submit/schapman

Conversation

@SandyChapman

@SandyChapman SandyChapman commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

The evaluator SDK carried two dataset entrypoints that differed only in metric arity and return type — evaluate took one metric and returned an EvaluationResult, evaluate_benchmark took a list and returned a BenchmarkEvaluationResult. One metric is the degenerate case of several, so this collapses them into evaluate_dataset(metrics=[...]) and frees the evaluate name for the taskset entrypoint. It also introduces a job-handle contract (wait_until_done + get_result) that both evaluation paths return, so the plugin resource is consistent and satisfies the backend protocol structurally.

Related Issue

None.

Changes

SDK backend contract (packages/nemo_evaluator_sdk)

  • EvaluationBackend / SyncEvaluationBackend now declare evaluate(taskset=...) and evaluate_dataset(metrics=[...]). evaluate_benchmark is gone.
  • Evaluator.run / run_sync lose their single-metric overloads (6 dropped, 3 remain, discriminating on config/target rather than metric arity).
  • BenchmarkEvaluationResult gains the __str__ EvaluationResult already had, so print(result) stays readable through the collapsed path instead of falling back to a pydantic dump.
  • Removed is_metric / is_metric_sequence, whose only purpose was the single-vs-sequence dispatch.

Job-handle contract (new execution/jobs.py)

  • EvaluationJob[ResultT] / SyncEvaluationJob[ResultT] declare wait_until_done and get_result. A backend that runs work elsewhere returns a handle so the caller chooses when to wait; in-process execution returns an already-finished CompletedJob.
  • Evaluator.submit / submit_sync wait on the caller's behalf, so the public SDK API still returns an AgentEvalResult.
  • Timeout defaults are shared with the dataset job resources, so the agent-eval path picks up a pending-timeout it previously lacked.

Plugin agent evaluation (plugins/nemo-evaluator)

  • New agent_eval_job_resources.py handles satisfy the SDK contract structurally, so the SDK imports nothing from the plugin. Both evaluate and evaluate_dataset return handles.
  • The handle carries its taskset: rebuilding the result needs the caller's live tasks, because persisted task metrics serialize as descriptors that cannot be validated back into Metric objects. Holding them on the handle means no caller has to know that.
  • Polling and bundle reassembly moved out of the executor onto the handle, dropping duplicated URL/wait/download logic from both executor flavours.
  • Bundle files are matched on base name only, so a malformed archive has no path to traverse.
  • Rejects two silent failures: a task with no instruction (the wire schema permits null, so a job would otherwise reach the agent with nothing to do) and run params that do not match the target kind (previously dropped, so a caller's parallelism vanished without a word).

Retiring plugin-local execution

  • client.evaluator.run() removed; client.evaluator.submit() renamed to evaluate_dataset() and now takes a metric list.
  • That orphaned the executor's local paths, so run_local, evaluate, evaluate_benchmark and evaluate_remote are removed from both executors, together with two dead spec resolvers.
  • bundle_metrics_for_spec handles sequences only.

Docs, examples, skills, e2e

  • Call sites updated to metrics=[...] and evaluate_dataset across README, skill references, examples, the walkthrough notebook, and the e2e suite.
  • plugin_examples.py loses its execution_mode switch, which selected between a local path that no longer exists and the platform path.
  • Vendored SDK refreshed with make vendor per sdk/python/AGENTS.md.

Type of Change

  • Code change with documentation updates
  • Code change (feature, bug fix, or refactor)
  • Documentation only
  • Contributor tooling or automation
  • CI, build, or test infrastructure

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Documentation updated for user-visible behavior
  • Documentation not applicable — justification:

New tests/test_agent_eval_executor.py covers the previously untested executor and handle: spec construction, metric packaging and the cloudpickle opt-in, the two new rejections, bundle reading including the traversal case, result assembly, and create/wait/get_result on both flavours. Evaluator.submit had no coverage at all before this change; TestEvaluatorSubmit now covers it, including that a sync backend's handle is driven off the event loop. Tests for the removed local-execution paths were deleted rather than left pinning dead code.

Verification

  • Pull request title follows the repository's Conventional Commit format
  • Every commit includes an appropriate Signed-off-by: trailer
  • uv run pre-commit run -a passes, or any blocked checks are identified below
  • Targeted tests pass, or tests are marked not applicable above
  • No secrets, API keys, or credentials are included

Targeted validation, after rebasing onto main:

Command Result
uv run --frozen pytest packages/nemo_evaluator_sdk/tests -q 1452 passed
uv run --frozen pytest plugins/nemo-evaluator/tests -q --ignore=.../integration 792 passed
uv run ruff check / ruff format --check passed
bash tools/lint/run-ty-check.sh passed
make vendor no delta; vendored copy matches source
uv run pre-commit run -a all hooks pass except Run uv lock with platform uv — see below

Run uv lock with platform uv fails locally because the hook requires uv 0.9.14 exactly and this machine has 0.9.30; it exits on the version check before reading any file. Check for uv.lock drift passes and this PR changes no pyproject.toml, so there is nothing to relock. Flagging it rather than marking the gate passed.

Integration tests were not run — they need a live platform.

Limitations

  • EvaluatorJobResource.get_result() still returns an EvaluationResult rather than a BenchmarkEvaluationResult, so evaluate_dataset satisfies the handle contract but not its result type. The full BenchmarkEvaluationResult is already persisted as the evaluation-results artifact, so closing this is a download-and-validate against an existing route.
  • The SDK ships only an async CompletedJob; a third-party sync backend would need the equivalent sync shape, which currently exists only as a test double.

Summary by CodeRabbit

  • New Features

    • Added agent evaluation with tasksets, targets or precomputed trials, run configuration, and synchronous or asynchronous job handling.
    • Added job polling, timeout support, status tracking, and result retrieval for remote evaluations.
    • Added multi-metric dataset evaluation through evaluate_dataset for synchronous and asynchronous clients.
    • Evaluation results now include concise previews and complete result downloads.
  • Breaking Changes

    • Replaced legacy submission and single-metric interfaces with evaluate_dataset and metrics lists.
    • Local execution is no longer available through platform plugin APIs.

@github-actions github-actions Bot added breaking breaking change (!-marked title) refactor labels Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 32111/40824 78.7% 63.4%
Integration Tests 18632/38750 48.1% 20.8%

@SandyChapman
SandyChapman force-pushed the evaluator-agent-eval-submit/schapman branch from d2ccb45 to 6e36303 Compare August 9, 2026 22:53
@SandyChapman
SandyChapman marked this pull request as ready for review August 9, 2026 23:14
@SandyChapman
SandyChapman requested review from a team as code owners August 9, 2026 23:14
@coderabbitai

coderabbitai Bot commented Aug 9, 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

The evaluator SDK now uses metric sequences and evaluate_dataset for dataset jobs. Agent tasksets use synchronous and asynchronous job APIs with polling, bundle retrieval, and result assembly. Examples, documentation, and tests use the new interfaces.

Changes

Evaluator API migration

Layer / File(s) Summary
SDK contracts and job execution
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/..., packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/values/multi_metric_results.py
Added taskset evaluation contracts, job protocols, local jobs, and collection-based run and run_sync behavior.
Plugin dataset and agent APIs
plugins/nemo-evaluator/src/nemo_evaluator/sdk/..., plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py
Replaced singular-metric submission with evaluate_dataset(metrics=...). Added taskset evaluation through synchronous and asynchronous job resources.
Result retrieval and validation
plugins/nemo-evaluator/src/nemo_evaluator/sdk/job_resources.py, plugins/nemo-evaluator/src/nemo_evaluator/sdk/_agent_eval_bundle.py
Unified benchmark-result downloads and added agent-result bundle reconstruction.
Tests and examples
packages/nemo_evaluator_sdk/tests/..., plugins/nemo-evaluator/tests/..., e2e/test_evaluator_plugin.py
Updated coverage for metric sequences, evaluation jobs, backend adaptation, agent jobs, result retrieval, and migrated submission calls.
Documentation and workflow guidance
packages/nemo_evaluator_sdk/examples/*, skills/nemo-evaluator-plugin/*, plugins/nemo-evaluator/README.md, packages/nemo_platform_ext/src/nemo_platform_ext/skills/nemo-evaluator/references/sdk-execution.md
Updated examples and guidance to use list-based metrics and evaluate_dataset.

Possibly related PRs

Suggested reviewers: arpitsardhana, ngoncharenko

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.01% 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 clearly summarizes the breaking refactor that unifies evaluator backend contracts around evaluate and evaluate_dataset.
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 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch evaluator-agent-eval-submit/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: 7

🧹 Nitpick comments (1)
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/evaluator.py (1)

200-242: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Expose the polling and timeout knobs on submit.

submit calls wait_until_done() with defaults only, so every run is capped at DEFAULT_JOB_TIMEOUT_SECONDS (3600s). A caller with a longer agent run has no way to raise the ceiling without dropping to backend.evaluate. Forward the three parameters.

♻️ Forward timeout arguments
     async def submit(
         self,
         *,
         taskset: Sequence[AgentEvalTask],
         target: AgentEvalTarget | None = None,
         trials: Sequence[AgentEvalTrial] | None = None,
         config: AgentEvalRunConfig | None = None,
+        poll_interval_seconds: float = DEFAULT_POLL_INTERVAL_SECONDS,
+        job_timeout_seconds: float = DEFAULT_JOB_TIMEOUT_SECONDS,
+        pending_timeout_seconds: float = DEFAULT_PENDING_TIMEOUT_SECONDS,
     ) -> AgentEvalResult:
@@
         job = await self._backend.evaluate(taskset=taskset, target=target, trials=trials, config=config)
-        await job.wait_until_done()
+        await job.wait_until_done(
+            poll_interval_seconds=poll_interval_seconds,
+            job_timeout_seconds=job_timeout_seconds,
+            pending_timeout_seconds=pending_timeout_seconds,
+        )
         return await job.get_result()
🤖 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/src/nemo_evaluator_sdk/execution/evaluator.py`
around lines 200 - 242, Expose the polling and timeout parameters accepted by
job.wait_until_done through Evaluator.submit, including them in its signature
and documentation. Forward all three values to job.wait_until_done instead of
relying on defaults, and propagate the same parameters through submit_sync so
both APIs support longer-running evaluations.
🤖 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/execution/backends/local/backend.py`:
- Around line 58-71: Update LocalBackend.evaluate to prepare each task’s metrics
with the backend resolver flow before invoking AgentEvaluator.run, using
prepare_metric_for_execution consistently with evaluate_dataset. Ensure
registered ModelRef and SecretRef values are resolved while preserving the
existing task, trial, target, and config execution behavior.

In `@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/jobs.py`:
- Around line 30-31: Update EvaluationJob to use Python 3.11-compatible generic
Protocol syntax with a separately declared TypeVar, or consistently raise the
package’s minimum Python version and Ruff target to 3.12 in pyproject.toml; keep
the existing generic behavior intact.

In `@plugins/nemo-evaluator/README.md`:
- Around line 102-103: Update plugins/nemo-evaluator/README.md lines 102-103 so
the evaluate_dataset documentation states that it returns EvaluatorJobResource.
In skills/nemo-evaluator-plugin/references/execution.md lines 50-54, replace
both stale submit API references with evaluate_dataset references, preserving
the surrounding usage guidance.

In `@plugins/nemo-evaluator/src/nemo_evaluator/sdk/_agent_eval_executor.py`:
- Around line 133-143: Update _target_spec so target-only configuration is
rejected when target is None instead of returning silently; validate
config.params and config.prompt_template and raise the established configuration
error for either field. Preserve the existing behavior for compatible
ModelTarget or AgentTarget values, and add a regression test covering
precomputed trials with RunConfigOnlineModel.

In `@plugins/nemo-evaluator/src/nemo_evaluator/sdk/agent_eval_job_resources.py`:
- Around line 97-128: Carry the platform timeout through _JobAddress and pass it
as the timeout argument to every synchronous and asynchronous GET request,
including get_job_status, wait_until_done, get_result, and the related methods
around the async implementation. Ensure each status and bundle request is
bounded so the existing timeout checks can execute.

In `@plugins/nemo-evaluator/src/nemo_evaluator/sdk/resources.py`:
- Around line 155-184: The plugin resource method evaluate_dataset must not
satisfy the EvaluationBackend contract while accepting incompatible config and
returning EvaluatorJobResource. Prevent _validate_backend_client from accepting
plugin resources, or add an explicit adapter that translates params to config,
waits for the submitted job, and returns BenchmarkEvaluationResult; ensure
unsupported keyword arguments cannot reach _executor.submit.

In `@skills/nemo-evaluator-plugin/SKILL.md`:
- Around line 54-57: Update the recommendation sentence in the plugin evaluation
guidance to default to evaluate_dataset only for dataset-driven evaluations.
Preserve the existing task-driven routing to nemo evaluator agent-evaluate
submit and do not imply that evaluate_dataset applies to every plugin
evaluation.

---

Nitpick comments:
In `@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/evaluator.py`:
- Around line 200-242: Expose the polling and timeout parameters accepted by
job.wait_until_done through Evaluator.submit, including them in its signature
and documentation. Forward all three values to job.wait_until_done instead of
relying on defaults, and propagate the same parameters through submit_sync so
both APIs support longer-running evaluations.
🪄 Autofix

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: f5e85650-8fe0-4543-9efe-a9d92022b123

📥 Commits

Reviewing files that changed from the base of the PR and between 0c4dc81 and 6e36303.

⛔ Files ignored due to path filters (7)
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/execution/backends/base.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/execution/backends/local/backend.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/execution/evaluator.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/execution/jobs.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/execution/utils.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/values/multi_metric_results.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/skills/nemo-evaluator/references/sdk-execution.md is excluded by !sdk/**
📒 Files selected for processing (37)
  • e2e/test_evaluator_plugin.py
  • packages/nemo_evaluator_sdk/examples/examples.py
  • packages/nemo_evaluator_sdk/examples/high_level_evaluate_walkthrough.ipynb
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/README.md
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/backends/base.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/backends/local/backend.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/evaluator.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/jobs.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/utils.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/values/multi_metric_results.py
  • packages/nemo_evaluator_sdk/tests/execution/backends/local/test_backend.py
  • packages/nemo_evaluator_sdk/tests/execution/test_evaluator.py
  • packages/nemo_evaluator_sdk/tests/execution/test_metric_execution.py
  • packages/nemo_evaluator_sdk/tests/execution/test_resolvers.py
  • packages/nemo_evaluator_sdk/tests/metrics/test_bleu.py
  • packages/nemo_evaluator_sdk/tests/metrics/test_f1.py
  • packages/nemo_evaluator_sdk/tests/metrics/test_number_check.py
  • packages/nemo_evaluator_sdk/tests/metrics/test_rouge.py
  • packages/nemo_evaluator_sdk/tests/metrics/test_string_check.py
  • packages/nemo_evaluator_sdk/tests/metrics/test_tool_calling.py
  • packages/nemo_evaluator_sdk/tests/test_api.py
  • packages/nemo_platform_ext/src/nemo_platform_ext/skills/nemo-evaluator/references/sdk-execution.md
  • plugins/nemo-evaluator/README.md
  • plugins/nemo-evaluator/examples/plugin_examples.py
  • plugins/nemo-evaluator/src/nemo_evaluator/jobs/evaluate.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/_agent_eval_bundle.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/_agent_eval_executor.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/_executor.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/agent_eval_job_resources.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/resources.py
  • plugins/nemo-evaluator/tests/test_agent_eval_executor.py
  • plugins/nemo-evaluator/tests/test_evaluate_job.py
  • plugins/nemo-evaluator/tests/test_sdk.py
  • plugins/nemo-evaluator/tests/test_skill_examples.py
  • skills/nemo-evaluator-plugin/SKILL.md
  • skills/nemo-evaluator-plugin/assets/examples/plugin_sdk_examples.py
  • skills/nemo-evaluator-plugin/references/execution.md

Comment thread packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/jobs.py Outdated
Comment thread plugins/nemo-evaluator/README.md
Comment thread plugins/nemo-evaluator/src/nemo_evaluator/sdk/resources.py
Comment thread skills/nemo-evaluator-plugin/SKILL.md Outdated
@SandyChapman
SandyChapman force-pushed the evaluator-agent-eval-submit/schapman branch from 6e36303 to 740c115 Compare August 9, 2026 23:41

@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/execution/jobs.py`:
- Around line 136-147: Serialize the completion check and _run() execution in
the job’s wait method with a single asyncio.Lock, so concurrent callers cannot
both start the once-only operation. Keep existing timeout handling, error
caching, and result propagation intact, and add a regression test that awaits
the same job concurrently and verifies _run() executes only once.
🪄 Autofix

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: ac743aef-2c77-414f-ba68-64db7ce95b2b

📥 Commits

Reviewing files that changed from the base of the PR and between 6e36303 and 740c115.

⛔ Files ignored due to path filters (4)
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/evaluator.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/execution/backends/base.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/execution/backends/local/backend.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/execution/jobs.py is excluded by !sdk/**
📒 Files selected for processing (5)
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/evaluator.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/backends/base.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/backends/local/backend.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/jobs.py
  • packages/nemo_evaluator_sdk/tests/execution/test_evaluator.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/backends/base.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/backends/local/backend.py

Comment thread packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/jobs.py Outdated
@SandyChapman
SandyChapman force-pushed the evaluator-agent-eval-submit/schapman branch from 740c115 to 8ef831e Compare August 9, 2026 23:58

@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 (2)
packages/nemo_evaluator_sdk/tests/execution/test_evaluator.py (2)

86-101: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

_CompletedSyncJob.waits is never asserted.

No test reads waits. Either assert the forwarded wait parameters in test_submit_bridges_a_sync_backend_job_off_the_loop, or drop the field.

🤖 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/execution/test_evaluator.py` around lines
86 - 101, Update test_submit_bridges_a_sync_backend_job_off_the_loop to assert
the _CompletedSyncJob.waits entry contains the forwarded poll_interval_seconds,
job_timeout_seconds, and pending_timeout_seconds values; alternatively, remove
the unused waits field and its recording logic if those parameters are not part
of the test’s assertions.

636-643: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cancelled task is never awaited; the test also reaches into LocalJob._task.

job._task.cancel() returns before the task processes the cancellation, so asyncio can emit "Task was destroyed but it is pending!" and pollute output. Await the cancellation and suppress CancelledError. Consider exposing a cancel() on LocalJob if backends need it.

🧹 Proposed fix
     def test_get_result_before_the_run_finishes_says_so(self):
         async def _drive():
-            job = LocalJob(asyncio.create_task(asyncio.sleep(30)))
+            task = asyncio.create_task(asyncio.sleep(30))
+            job = LocalJob(task)
             with pytest.raises(RuntimeError, match="has not finished yet"):
                 await job.get_result()
-            job._task.cancel()
+            task.cancel()
+            with contextlib.suppress(asyncio.CancelledError):
+                await task
 
         asyncio.run(_drive())

Add import contextlib at the top of the file.

🤖 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/execution/test_evaluator.py` around lines
636 - 643, Update test_get_result_before_the_run_finishes_says_so to await the
cancelled task and suppress asyncio.CancelledError, using contextlib as
suggested, instead of directly cancelling LocalJob._task; if cancellation
belongs in the public API, use or add LocalJob.cancel() and await its
completion.
🤖 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/tests/execution/test_evaluator.py`:
- Around line 645-663: Make
test_job_timeout_gives_up_waiting_without_cancelling_the_run deterministic by
increasing _run’s sleep duration substantially relative to the 0.01-second
timeout, such as 1.0 second, and remove the finished == [1] assertion and any
now-unnecessary tracking. Preserve verification that the later wait collects
_TASKSET_RESULT without cancelling the run.

---

Nitpick comments:
In `@packages/nemo_evaluator_sdk/tests/execution/test_evaluator.py`:
- Around line 86-101: Update test_submit_bridges_a_sync_backend_job_off_the_loop
to assert the _CompletedSyncJob.waits entry contains the forwarded
poll_interval_seconds, job_timeout_seconds, and pending_timeout_seconds values;
alternatively, remove the unused waits field and its recording logic if those
parameters are not part of the test’s assertions.
- Around line 636-643: Update test_get_result_before_the_run_finishes_says_so to
await the cancelled task and suppress asyncio.CancelledError, using contextlib
as suggested, instead of directly cancelling LocalJob._task; if cancellation
belongs in the public API, use or add LocalJob.cancel() and await its
completion.
🪄 Autofix

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: f0cbeec1-b957-4c11-8df0-1fc908a59cf8

📥 Commits

Reviewing files that changed from the base of the PR and between 740c115 and 8ef831e.

⛔ Files ignored due to path filters (2)
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/execution/backends/local/backend.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/execution/jobs.py is excluded by !sdk/**
📒 Files selected for processing (3)
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/backends/local/backend.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/jobs.py
  • packages/nemo_evaluator_sdk/tests/execution/test_evaluator.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/backends/local/backend.py

Comment thread packages/nemo_evaluator_sdk/tests/execution/test_evaluator.py
@SandyChapman
SandyChapman force-pushed the evaluator-agent-eval-submit/schapman branch from 8ef831e to b6037de Compare August 10, 2026 01:06

@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/execution/backends/local/test_backend.py (1)

33-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for LocalBackend.evaluate. This file only exercises evaluate_dataset. The new taskset path adds validate_run_inputs and per-task metric resolution through prepare_metric_for_execution in _run_taskset, and neither is covered here. Add tests that assert invalid input raises before the job is created, and that a task metric carrying a ModelRef/SecretRef reaches AgentEvaluator resolved.

🤖 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/execution/backends/local/test_backend.py`
around lines 33 - 34, Add focused tests for LocalBackend.evaluate covering
_run_taskset: verify invalid inputs raise via validate_run_inputs before any job
is created, and verify task metrics containing ModelRef or SecretRef are
resolved through prepare_metric_for_execution before being passed to
AgentEvaluator. Reuse the existing test fixtures and mocks, and assert both the
failure ordering and resolved metric delegation.
🤖 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 `@plugins/nemo-evaluator/src/nemo_evaluator/sdk/job_resources.py`:
- Around line 42-44: Update the comment immediately above
_RES_FULL_RESULT_DOWNLOAD to remove the obsolete references to aggregate and
row-score artifacts, leaving only an accurate description of the full result
download route.

---

Nitpick comments:
In `@packages/nemo_evaluator_sdk/tests/execution/backends/local/test_backend.py`:
- Around line 33-34: Add focused tests for LocalBackend.evaluate covering
_run_taskset: verify invalid inputs raise via validate_run_inputs before any job
is created, and verify task metrics containing ModelRef or SecretRef are
resolved through prepare_metric_for_execution before being passed to
AgentEvaluator. Reuse the existing test fixtures and mocks, and assert both the
failure ordering and resolved metric delegation.
🪄 Autofix

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: 1676d732-1f31-4e5f-bbbd-03ffe299c132

📥 Commits

Reviewing files that changed from the base of the PR and between 8ef831e and b6037de.

⛔ Files ignored due to path filters (5)
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/__init__.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/execution/backends/base.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/execution/backends/local/backend.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/execution/evaluator.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/execution/jobs.py is excluded by !sdk/**
📒 Files selected for processing (12)
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/__init__.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/backends/base.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/backends/local/backend.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/evaluator.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/jobs.py
  • packages/nemo_evaluator_sdk/tests/execution/backends/local/test_backend.py
  • packages/nemo_evaluator_sdk/tests/execution/test_evaluator.py
  • packages/nemo_evaluator_sdk/tests/execution/test_metric_execution.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/_agent_eval_executor.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/agent_eval_job_resources.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/job_resources.py
  • plugins/nemo-evaluator/tests/test_sdk_job_resources.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/agent_eval_job_resources.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/execution/jobs.py
  • plugins/nemo-evaluator/src/nemo_evaluator/sdk/_agent_eval_executor.py
  • packages/nemo_evaluator_sdk/tests/execution/test_metric_execution.py

Comment thread plugins/nemo-evaluator/src/nemo_evaluator/sdk/job_resources.py Outdated
@SandyChapman
SandyChapman force-pushed the evaluator-agent-eval-submit/schapman branch 4 times, most recently from 3098e8e to 6929a38 Compare August 10, 2026 12:09
@github-actions

Copy link
Copy Markdown
Contributor

@SandyChapman
SandyChapman force-pushed the evaluator-agent-eval-submit/schapman branch 7 times, most recently from 4e86660 to 5c95cb3 Compare August 10, 2026 18:52
SandyChapman added a commit that referenced this pull request Aug 11, 2026
…change

#1173 retires `run_sync`/`submit` for `run_dataset_sync`/`evaluate_dataset`,
which needed the same six skill files updated and so put that PR behind the
NVSkills gate too. Rather than open a second skills PR that waits on the same
broken gate, its skill updates join this one.

`plugin_sdk_examples.py` is the one file both PRs touch, and they touch
different functions — #1071 moves `store_resources` to the discriminated `spec`,
#1173 moves `evaluate_standalone` and `submit_and_collect` to the dataset API —
so the two are merged here rather than one overwriting the other.
`test_skill_examples.py` likewise carries both sets of assertions.

`test_skill_standalone_example_scores_pass_and_failure` fails on this branch and
is expected to: it calls `run_dataset_sync`, which exists on #1173's branch, not
on this PR's #1071 base. It passes once #1173 lands. It is left failing rather
than skipped, so that it is re-verified for real instead of quietly staying off.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
SandyChapman and others added 4 commits August 12, 2026 12:32
…_dataset

The SDK backend protocol carried two dataset entrypoints that differed only in
metric arity and return type: `evaluate` took one metric and returned an
`EvaluationResult`, `evaluate_benchmark` took a list and returned a
`BenchmarkEvaluationResult`. One metric is the degenerate case of several, so
collapse them into a single `evaluate_dataset(metrics=[...])` and free the
`evaluate` name for the taskset entrypoint.

`Evaluator.run`/`run_sync` lose their single-metric overloads; callers pass a
list. `BenchmarkEvaluationResult` gains the `__str__` that `EvaluationResult`
had, so `print(result)` stays readable through the collapsed path.

Introduce a job-handle contract. `EvaluationJob`/`SyncEvaluationJob` declare
`wait_until_done` and `get_result`, so a backend that runs work elsewhere hands
back a handle and the caller chooses when to wait; in-process execution returns
an already-finished `CompletedJob`. `Evaluator.submit` waits on the caller's
behalf, so the convenience API still returns a result either way.

The plugin's job resources satisfy that contract structurally, so both
`evaluate` and `evaluate_dataset` now return handles and the resource is
consistent. The agent-eval handle carries the taskset it submitted: rebuilding
the result needs the caller's live tasks, because persisted task metrics
serialize as descriptors that cannot be validated back into `Metric` objects,
and holding them on the handle means no caller has to know that. Polling and
bundle reassembly move out of the executor onto the handle, which drops the
duplicated URL, wait, and download logic from both executor flavours and
replaces two hard-coded timeout constants with the three the dataset handles
already take.

Retire plugin-local execution. `client.evaluator.run()` is gone and `submit` is
now `evaluate_dataset`, taking a metric list. That orphaned the executor's local
paths, so remove `run_local`, `evaluate`, `evaluate_benchmark` and
`evaluate_remote` from both executors along with the two metric type-guards
whose only purpose was the single-vs-sequence dispatch.

Reject two silent failures the agent-eval path allowed: a task with no
instruction (the wire schema permits null, so a job would reach the agent with
nothing to do) and run params that do not match the target kind (previously
dropped, so a caller's parallelism vanished without a word).

BREAKING CHANGE: `EvaluationBackend.evaluate` now takes a taskset and returns an
`EvaluationJob`; dataset evaluation moves to `evaluate_dataset` with a metric
list. `Evaluator.run`/`run_sync` no longer accept a bare metric.
`client.evaluator.run()` is removed, `client.evaluator.submit()` is now
`client.evaluator.evaluate_dataset()`, and `client.evaluator.evaluate()` returns
a job handle rather than a completed result.

Signed-off-by: Sandy Chapman <schapman@nvidia.com>
Retiring `run_sync`/`submit` in favour of `run_dataset_sync`/`evaluate_dataset`
meant updating the evaluator skill to match, which put six `skills/` files in
the diff and so put this PR behind the NVSkills gate. That gate cannot currently
pass: tier 3 is invoked with `--env-mode local`, whose bubblewrap sandbox fails
its smoke test on the nvcarps runners, so nothing is evaluated and it blocks on
empty coverage. It is an infrastructure problem, already reported, and nothing
in this repo can resolve it.

With no `skills/` file touched, the gate no longer applies and the backend
contract change can land on its own merits. The skill updates move to #1237,
which can sit behind the gate for as long as it takes.

Unlike the equivalent split on #1071, this one has a cost worth naming. The
skill's `evaluate_standalone` example is *executed* by
`test_skill_standalone_example_scores_pass_and_failure`, and the reverted
example calls the retired `Evaluator.run_sync`, so the test now fails for a real
reason: the shipped example is genuinely broken against this refactor. It is
skipped rather than deleted, with the reason and the restoring PR named in the
marker, so the gap is visible and expires. The other 29 tests in that file still
run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
The poll loop billed both ceilings from one wall-clock reading, so a job that
ran longer than `pending_timeout_seconds` and then reported a pending status
tripped the pending ceiling and was reported as never having started. The
dataset handles in `job_resources` already keep the two totals apart; this
brings the agent-eval handles in line with a `_Clock` that bills each tick to
whichever total the status belongs to.

`_PENDING` also carried `queued` and `scheduled`, neither of which exists in
`PlatformJobStatus`. It now names the enum's pre-start members, so every other
non-terminal status is charged against the job ceiling.

Nothing covered either ceiling, which is why this survived. Four tests drive a
controlled clock through the cases that distinguish them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
`test_prefers_sync_sdk_for_fileset_ref_when_both_sdks_injected` arrived from
main in #1211, after this branch renamed the evaluate job's dataset call to
`run_dataset_sync`. It mocked `run_sync`, so the job reached an auto-created
Mock instead, which failed on the way into the result artifact as
`TypeError: Object of type Mock is not JSON serializable`.

Its four siblings in the same class already mock `run_dataset_sync` with the
same helper; this brings the newcomer in line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
@SandyChapman
SandyChapman force-pushed the evaluator-agent-eval-submit/schapman branch from 415b724 to 8f51e4f Compare August 12, 2026 16:03
SandyChapman added a commit that referenced this pull request Aug 20, 2026
The skill drifted from the code because the NVSkills CI gate blocked any PR
touching top-level `skills/`, so several evaluator changes landed with their
docs updated and the skill left behind. That gate is gone as of #1302, so this
catches the skill up.

Stored tasks (#1071, #566). `TaskInput` now carries a runner-discriminated
`spec`, and `EvaluatorTaskDefinition` has the grader-only `reference` field.
The skill still showed the flat pre-#1071 shape and told readers that held-out
ground truth required an inline `AgentEvalTaskInput` -- which would cost them
tasksets and revision pinning for a limitation that no longer exists. Three
places said it; all three are corrected.

Local execution (#1262). The skill lumped `client.evaluator.run()` together
with the `nemo evaluator ... run` CLI verb as "being retired", but only the CLI
verb still exists -- the method was removed a week ago. SKILL.md now warns about
the CLI verb alone: naming a method that cannot be called, four lines from the
seven live `.run()` calls the skill teaches (`AgentEvaluator().run`,
`Evaluator().run_sync`), invited the wrong generalization. The removal is
recorded in `troubleshooting.md` instead, which is symptom-indexed and so only
reached by someone who already called it from memory.

`GymRunnerTarget` was also missing from SKILL.md's platform-target list,
alongside the same omission in the agent-evaluation reference.

Taskset submission (#1367). `submit` grew a second shape -- `tasks` + `target`
against a live runner -- which was previously CLI-only and went out with no
skill or docs coverage. Added to the interface table and the agent-evaluation
reference, along with `GymRunnerTarget` in the target table, the four row-only
options the taskset path refuses, and the Gym-only translation limit.

The returned `AgentEvaluatorJobResource` deliberately has no `get_result()` or
`download_artifacts()`, while every other job example in the skill ends in
`get_result()`. That trap gets its own troubleshooting row.

`evals.json` graded the agent on producing `nemo evaluator evaluate run
--spec`, the very path SKILL.md says not to build on. Both verbs take
identical spec flags, so the eval was rewarding the discouraged one for no
benefit.

Deliberately NOT included: the skill updates written for #1173. That PR closed
unmerged, so `Evaluator.run_dataset_sync` and
`client.evaluator.evaluate_dataset` do not exist. `evaluate_dataset` on main is
the *backend* contract method, which makes the rename look landed when it is
not. The public surface is still `run_sync` and `submit(metric=..., config=...)`.

Every claim was verified by executing it against main rather than reading the
source, which caught two errors in my own first draft: an example missing the
required `resources_server`, and a claim that `env_vars` can hold a callable.
It cannot -- it is `dict[str, str]`, so pydantic refuses one at construction
and it never reaches the serializability guard. Only `hydra_params` is
`dict[str, Any]`. (The `_gym_target` docstring names both and is likewise
overstated, but that is merged code and out of scope here.)

Four tests added, each mutation-verified. The largest gap they close is that
`store_resources` -- the skill's canonical stored-task example -- was only ever
asserted as text, so no schema change to `TaskInput` could fail it. It now runs
against the real resource signatures and re-validates through the wire form
`create` actually posts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
SandyChapman added a commit that referenced this pull request Aug 20, 2026
The skill drifted from the code because the NVSkills CI gate blocked any PR
touching top-level `skills/`, so several evaluator changes landed with their
docs updated and the skill left behind. That gate is gone as of #1302, so this
catches the skill up.

Stored tasks (#1071, #566). `TaskInput` now carries a runner-discriminated
`spec`, and `EvaluatorTaskDefinition` has the grader-only `reference` field.
The skill still showed the flat pre-#1071 shape and told readers that held-out
ground truth required an inline `AgentEvalTaskInput` -- which would cost them
tasksets and revision pinning for a limitation that no longer exists. Three
places said it; all three are corrected.

Local execution (#1262). The skill lumped `client.evaluator.run()` together
with the `nemo evaluator ... run` CLI verb as "being retired", but only the CLI
verb still exists -- the method was removed a week ago. SKILL.md now warns about
the CLI verb alone: naming a method that cannot be called, four lines from the
seven live `.run()` calls the skill teaches (`AgentEvaluator().run`,
`Evaluator().run_sync`), invited the wrong generalization. The removal is
recorded in `troubleshooting.md` instead, which is symptom-indexed and so only
reached by someone who already called it from memory.

`GymRunnerTarget` was also missing from SKILL.md's platform-target list,
alongside the same omission in the agent-evaluation reference.

Taskset submission (#1367). `submit` grew a second shape -- `tasks` + `target`
against a live runner -- which was previously CLI-only and went out with no
skill or docs coverage. Added to the interface table and the agent-evaluation
reference, along with `GymRunnerTarget` in the target table, the four row-only
options the taskset path refuses, and the Gym-only translation limit.

The returned `AgentEvaluatorJobResource` deliberately has no `get_result()` or
`download_artifacts()`, while every other job example in the skill ends in
`get_result()`. That trap gets its own troubleshooting row.

`evals.json` graded the agent on producing `nemo evaluator evaluate run
--spec`, the very path SKILL.md says not to build on. Both verbs take
identical spec flags, so the eval was rewarding the discouraged one for no
benefit.

Deliberately NOT included: the skill updates written for #1173. That PR closed
unmerged, so `Evaluator.run_dataset_sync` and
`client.evaluator.evaluate_dataset` do not exist. `evaluate_dataset` on main is
the *backend* contract method, which makes the rename look landed when it is
not. The public surface is still `run_sync` and `submit(metric=..., config=...)`.

Every claim was verified by executing it against main rather than reading the
source, which caught two errors in my own first draft: an example missing the
required `resources_server`, and a claim that `env_vars` can hold a callable.
It cannot -- it is `dict[str, str]`, so pydantic refuses one at construction
and it never reaches the serializability guard. Only `hydra_params` is
`dict[str, Any]`. (The `_gym_target` docstring names both and is likewise
overstated, but that is merged code and out of scope here.)

Four tests added, each mutation-verified. The largest gap they close is that
`store_resources` -- the skill's canonical stored-task example -- was only ever
asserted as text, so no schema change to `TaskInput` could fail it. It now runs
against the real resource signatures and re-validates through the wire form
`create` actually posts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
SandyChapman added a commit that referenced this pull request Aug 20, 2026
The skill drifted from the code because the NVSkills CI gate blocked any PR
touching top-level `skills/`, so several evaluator changes landed with their
docs updated and the skill left behind. That gate is gone as of #1302, so this
catches the skill up.

Stored tasks (#1071, #566). `TaskInput` now carries a runner-discriminated
`spec`, and `EvaluatorTaskDefinition` has the grader-only `reference` field.
The skill still showed the flat pre-#1071 shape and told readers that held-out
ground truth required an inline `AgentEvalTaskInput` -- which would cost them
tasksets and revision pinning for a limitation that no longer exists. Three
places said it; all three are corrected.

Local execution (#1262). The skill lumped `client.evaluator.run()` together
with the `nemo evaluator ... run` CLI verb as "being retired", but only the CLI
verb still exists -- the method was removed a week ago. SKILL.md now warns about
the CLI verb alone: naming a method that cannot be called, four lines from the
seven live `.run()` calls the skill teaches (`AgentEvaluator().run`,
`Evaluator().run_sync`), invited the wrong generalization. The removal is
recorded in `troubleshooting.md` instead, which is symptom-indexed and so only
reached by someone who already called it from memory.

`GymRunnerTarget` was also missing from SKILL.md's platform-target list,
alongside the same omission in the agent-evaluation reference.

Taskset submission (#1367). `submit` grew a second shape -- `tasks` + `target`
against a live runner -- which was previously CLI-only and went out with no
skill or docs coverage. Added to the interface table and the agent-evaluation
reference, along with `GymRunnerTarget` in the target table, the four row-only
options the taskset path refuses, and the Gym-only translation limit.

The returned `AgentEvaluatorJobResource` deliberately has no `get_result()` or
`download_artifacts()`, while every other job example in the skill ends in
`get_result()`. That trap gets its own troubleshooting row.

`evals.json` graded the agent on producing `nemo evaluator evaluate run
--spec`, the very path SKILL.md says not to build on. Both verbs take
identical spec flags, so the eval was rewarding the discouraged one for no
benefit.

Deliberately NOT included: the skill updates written for #1173. That PR closed
unmerged, so `Evaluator.run_dataset_sync` and
`client.evaluator.evaluate_dataset` do not exist. `evaluate_dataset` on main is
the *backend* contract method, which makes the rename look landed when it is
not. The public surface is still `run_sync` and `submit(metric=..., config=...)`.

Every claim was verified by executing it against main rather than reading the
source, which caught two errors in my own first draft: an example missing the
required `resources_server`, and a claim that `env_vars` can hold a callable.
It cannot -- it is `dict[str, str]`, so pydantic refuses one at construction
and it never reaches the serializability guard. Only `hydra_params` is
`dict[str, Any]`. (The `_gym_target` docstring names both and is likewise
overstated, but that is merged code and out of scope here.)

Four tests added, each mutation-verified. The largest gap they close is that
`store_resources` -- the skill's canonical stored-task example -- was only ever
asserted as text, so no schema change to `TaskInput` could fail it. It now runs
against the real resource signatures and re-validates through the wire form
`create` actually posts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
SandyChapman added a commit that referenced this pull request Aug 20, 2026
The skill drifted from the code because the NVSkills CI gate blocked any PR
touching top-level `skills/`, so several evaluator changes landed with their
docs updated and the skill left behind. That gate is gone as of #1302, so this
catches the skill up.

Stored tasks (#1071, #566). `TaskInput` now carries a runner-discriminated
`spec`, and `EvaluatorTaskDefinition` has the grader-only `reference` field.
The skill still showed the flat pre-#1071 shape and told readers that held-out
ground truth required an inline `AgentEvalTaskInput` -- which would cost them
tasksets and revision pinning for a limitation that no longer exists. Three
places said it; all three are corrected.

Local execution (#1262). The skill lumped `client.evaluator.run()` together
with the `nemo evaluator ... run` CLI verb as "being retired", but only the CLI
verb still exists -- the method was removed a week ago. SKILL.md now warns about
the CLI verb alone: naming a method that cannot be called, four lines from the
seven live `.run()` calls the skill teaches (`AgentEvaluator().run`,
`Evaluator().run_sync`), invited the wrong generalization. The removal is
recorded in `troubleshooting.md` instead, which is symptom-indexed and so only
reached by someone who already called it from memory.

`GymRunnerTarget` was also missing from SKILL.md's platform-target list,
alongside the same omission in the agent-evaluation reference.

Taskset submission (#1367). `submit` grew a second shape -- `tasks` + `target`
against a live runner -- which was previously CLI-only and went out with no
skill or docs coverage. Added to the interface table and the agent-evaluation
reference, along with `GymRunnerTarget` in the target table, the four row-only
options the taskset path refuses, and the Gym-only translation limit.

The returned `AgentEvaluatorJobResource` deliberately has no `get_result()` or
`download_artifacts()`, while every other job example in the skill ends in
`get_result()`. That trap gets its own troubleshooting row.

`evals.json` graded the agent on producing `nemo evaluator evaluate run
--spec`, the very path SKILL.md says not to build on. Both verbs take
identical spec flags, so the eval was rewarding the discouraged one for no
benefit.

Deliberately NOT included: the skill updates written for #1173. That PR closed
unmerged, so `Evaluator.run_dataset_sync` and
`client.evaluator.evaluate_dataset` do not exist. `evaluate_dataset` on main is
the *backend* contract method, which makes the rename look landed when it is
not. The public surface is still `run_sync` and `submit(metric=..., config=...)`.

Every claim was verified by executing it against main rather than reading the
source, which caught two errors in my own first draft: an example missing the
required `resources_server`, and a claim that `env_vars` can hold a callable.
It cannot -- it is `dict[str, str]`, so pydantic refuses one at construction
and it never reaches the serializability guard. Only `hydra_params` is
`dict[str, Any]`. (The `_gym_target` docstring names both and is likewise
overstated, but that is merged code and out of scope here.)

Four tests added, each mutation-verified. The largest gap they close is that
`store_resources` -- the skill's canonical stored-task example -- was only ever
asserted as text, so no schema change to `TaskInput` could fail it. It now runs
against the real resource signatures and re-validates through the wire form
`create` actually posts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
SandyChapman added a commit that referenced this pull request Aug 21, 2026
The skill drifted from the code because the NVSkills CI gate blocked any PR
touching top-level `skills/`, so several evaluator changes landed with their
docs updated and the skill left behind. That gate is gone as of #1302, so this
catches the skill up.

Stored tasks (#1071, #566). `TaskInput` now carries a runner-discriminated
`spec`, and `EvaluatorTaskDefinition` has the grader-only `reference` field.
The skill still showed the flat pre-#1071 shape and told readers that held-out
ground truth required an inline `AgentEvalTaskInput` -- which would cost them
tasksets and revision pinning for a limitation that no longer exists. Three
places said it; all three are corrected.

Local execution (#1262). The skill lumped `client.evaluator.run()` together
with the `nemo evaluator ... run` CLI verb as "being retired", but only the CLI
verb still exists -- the method was removed a week ago. SKILL.md now warns about
the CLI verb alone: naming a method that cannot be called, four lines from the
seven live `.run()` calls the skill teaches (`AgentEvaluator().run`,
`Evaluator().run_sync`), invited the wrong generalization. The removal is
recorded in `troubleshooting.md` instead, which is symptom-indexed and so only
reached by someone who already called it from memory.

`GymRunnerTarget` was also missing from SKILL.md's platform-target list,
alongside the same omission in the agent-evaluation reference.

Taskset submission (#1367). `submit` grew a second shape -- `tasks` + `target`
against a live runner -- which was previously CLI-only and went out with no
skill or docs coverage. Added to the interface table and the agent-evaluation
reference, along with `GymRunnerTarget` in the target table, the four row-only
options the taskset path refuses, and the Gym-only translation limit.

The returned `AgentEvaluatorJobResource` deliberately has no `get_result()` or
`download_artifacts()`, while every other job example in the skill ends in
`get_result()`. That trap gets its own troubleshooting row.

`evals.json` graded the agent on producing `nemo evaluator evaluate run
--spec`, the very path SKILL.md says not to build on. Both verbs take
identical spec flags, so the eval was rewarding the discouraged one for no
benefit.

Deliberately NOT included: the skill updates written for #1173. That PR closed
unmerged, so `Evaluator.run_dataset_sync` and
`client.evaluator.evaluate_dataset` do not exist. `evaluate_dataset` on main is
the *backend* contract method, which makes the rename look landed when it is
not. The public surface is still `run_sync` and `submit(metric=..., config=...)`.

Every claim was verified by executing it against main rather than reading the
source, which caught two errors in my own first draft: an example missing the
required `resources_server`, and a claim that `env_vars` can hold a callable.
It cannot -- it is `dict[str, str]`, so pydantic refuses one at construction
and it never reaches the serializability guard. Only `hydra_params` is
`dict[str, Any]`. (The `_gym_target` docstring names both and is likewise
overstated, but that is merged code and out of scope here.)

Four tests added, each mutation-verified. The largest gap they close is that
`store_resources` -- the skill's canonical stored-task example -- was only ever
asserted as text, so no schema change to `TaskInput` could fail it. It now runs
against the real resource signatures and re-validates through the wire form
`create` actually posts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
SandyChapman added a commit that referenced this pull request Aug 21, 2026
The skill drifted from the code because the NVSkills CI gate blocked any PR
touching top-level `skills/`, so several evaluator changes landed with their
docs updated and the skill left behind. That gate is gone as of #1302, so this
catches the skill up.

Stored tasks (#1071, #566). `TaskInput` now carries a runner-discriminated
`spec`, and `EvaluatorTaskDefinition` has the grader-only `reference` field.
The skill still showed the flat pre-#1071 shape and told readers that held-out
ground truth required an inline `AgentEvalTaskInput` -- which would cost them
tasksets and revision pinning for a limitation that no longer exists. Three
places said it; all three are corrected.

Local execution (#1262). The skill lumped `client.evaluator.run()` together
with the `nemo evaluator ... run` CLI verb as "being retired", but only the CLI
verb still exists -- the method was removed a week ago. SKILL.md now warns about
the CLI verb alone: naming a method that cannot be called, four lines from the
seven live `.run()` calls the skill teaches (`AgentEvaluator().run`,
`Evaluator().run_sync`), invited the wrong generalization. The removal is
recorded in `troubleshooting.md` instead, which is symptom-indexed and so only
reached by someone who already called it from memory.

`GymRunnerTarget` was also missing from SKILL.md's platform-target list,
alongside the same omission in the agent-evaluation reference.

Taskset submission (#1367). `submit` grew a second shape -- `tasks` + `target`
against a live runner -- which was previously CLI-only and went out with no
skill or docs coverage. Added to the interface table and the agent-evaluation
reference, along with `GymRunnerTarget` in the target table, the four row-only
options the taskset path refuses, and the Gym-only translation limit.

The returned `AgentEvaluatorJobResource` deliberately has no `get_result()` or
`download_artifacts()`, while every other job example in the skill ends in
`get_result()`. That trap gets its own troubleshooting row.

`evals.json` graded the agent on producing `nemo evaluator evaluate run
--spec`, the very path SKILL.md says not to build on. Both verbs take
identical spec flags, so the eval was rewarding the discouraged one for no
benefit.

Deliberately NOT included: the skill updates written for #1173. That PR closed
unmerged, so `Evaluator.run_dataset_sync` and
`client.evaluator.evaluate_dataset` do not exist. `evaluate_dataset` on main is
the *backend* contract method, which makes the rename look landed when it is
not. The public surface is still `run_sync` and `submit(metric=..., config=...)`.

Every claim was verified by executing it against main rather than reading the
source, which caught two errors in my own first draft: an example missing the
required `resources_server`, and a claim that `env_vars` can hold a callable.
It cannot -- it is `dict[str, str]`, so pydantic refuses one at construction
and it never reaches the serializability guard. Only `hydra_params` is
`dict[str, Any]`. (The `_gym_target` docstring names both and is likewise
overstated, but that is merged code and out of scope here.)

Four tests added, each mutation-verified. The largest gap they close is that
`store_resources` -- the skill's canonical stored-task example -- was only ever
asserted as text, so no schema change to `TaskInput` could fail it. It now runs
against the real resource signatures and re-validates through the wire form
`create` actually posts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
dnandakumar-nv pushed a commit to dnandakumar-nv/nemo-platform that referenced this pull request Aug 21, 2026
The skill drifted from the code because the NVSkills CI gate blocked any PR
touching top-level `skills/`, so several evaluator changes landed with their
docs updated and the skill left behind. That gate is gone as of NVIDIA-NeMo#1302, so this
catches the skill up.

Stored tasks (NVIDIA-NeMo#1071, NVIDIA-NeMo#566). `TaskInput` now carries a runner-discriminated
`spec`, and `EvaluatorTaskDefinition` has the grader-only `reference` field.
The skill still showed the flat pre-NVIDIA-NeMo#1071 shape and told readers that held-out
ground truth required an inline `AgentEvalTaskInput` -- which would cost them
tasksets and revision pinning for a limitation that no longer exists. Three
places said it; all three are corrected.

Local execution (NVIDIA-NeMo#1262). The skill lumped `client.evaluator.run()` together
with the `nemo evaluator ... run` CLI verb as "being retired", but only the CLI
verb still exists -- the method was removed a week ago. SKILL.md now warns about
the CLI verb alone: naming a method that cannot be called, four lines from the
seven live `.run()` calls the skill teaches (`AgentEvaluator().run`,
`Evaluator().run_sync`), invited the wrong generalization. The removal is
recorded in `troubleshooting.md` instead, which is symptom-indexed and so only
reached by someone who already called it from memory.

`GymRunnerTarget` was also missing from SKILL.md's platform-target list,
alongside the same omission in the agent-evaluation reference.

Taskset submission (NVIDIA-NeMo#1367). `submit` grew a second shape -- `tasks` + `target`
against a live runner -- which was previously CLI-only and went out with no
skill or docs coverage. Added to the interface table and the agent-evaluation
reference, along with `GymRunnerTarget` in the target table, the four row-only
options the taskset path refuses, and the Gym-only translation limit.

The returned `AgentEvaluatorJobResource` deliberately has no `get_result()` or
`download_artifacts()`, while every other job example in the skill ends in
`get_result()`. That trap gets its own troubleshooting row.

`evals.json` graded the agent on producing `nemo evaluator evaluate run
--spec`, the very path SKILL.md says not to build on. Both verbs take
identical spec flags, so the eval was rewarding the discouraged one for no
benefit.

Deliberately NOT included: the skill updates written for NVIDIA-NeMo#1173. That PR closed
unmerged, so `Evaluator.run_dataset_sync` and
`client.evaluator.evaluate_dataset` do not exist. `evaluate_dataset` on main is
the *backend* contract method, which makes the rename look landed when it is
not. The public surface is still `run_sync` and `submit(metric=..., config=...)`.

Every claim was verified by executing it against main rather than reading the
source, which caught two errors in my own first draft: an example missing the
required `resources_server`, and a claim that `env_vars` can hold a callable.
It cannot -- it is `dict[str, str]`, so pydantic refuses one at construction
and it never reaches the serializability guard. Only `hydra_params` is
`dict[str, Any]`. (The `_gym_target` docstring names both and is likewise
overstated, but that is merged code and out of scope here.)

Four tests added, each mutation-verified. The largest gap they close is that
`store_resources` -- the skill's canonical stored-task example -- was only ever
asserted as text, so no schema change to `TaskInput` could fail it. It now runs
against the real resource signatures and re-validates through the wire form
`create` actually posts.

Signed-off-by: Sandy Chapman <schapman@nvidia.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking breaking change (!-marked title) refactor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant