feat(evaluator-sdk): native Harbor runtime with one-call run_harbor_eval#620
Open
arpitsardhana wants to merge 3 commits into
Open
feat(evaluator-sdk): native Harbor runtime with one-call run_harbor_eval#620arpitsardhana wants to merge 3 commits into
arpitsardhana wants to merge 3 commits into
Conversation
5 tasks
SandyChapman
reviewed
Jul 9, 2026
Comment on lines
+169
to
+171
| * **Injected / offline** — pass ``job_dir`` (and optionally a ``run_job`` | ||
| callback); ``run_job`` is awaited before the job dir is read, and | ||
| ``run_job=None`` simply adapts an already-completed job dir. |
Contributor
There was a problem hiding this comment.
I wonder if we shouldn't make these separate modes, but instead have job_dir act like a cache. If the task has a result in job_dir, we convert to a trial. If it's missing, we run the harbor task? It might fit better for a runner vs what we have with offline/online evals which expect an omitted target (runner).
SandyChapman
reviewed
Jul 9, 2026
Comment on lines
+502
to
+507
| AgentEvalTask( | ||
| id=task_name, | ||
| intent=intent, | ||
| inputs={"instruction": intent}, | ||
| metrics=[HarborRewardMetric()], | ||
| ) |
Contributor
There was a problem hiding this comment.
Think we could store the path on the task itself so that we don't need to pass dataset_path as an argument to the runner? Might need to make a subtype of AgentEvalTask for that to work though.
a856149 to
31666c9
Compare
Make the SDK run Harbor tasks natively from a declarative config so callers no longer re-implement the Harbor plumbing (JobConfig build, Job.create/run, task discovery). - Add HarborRuntimeConfig (plain/pydantic fields only, mapped onto Harbor's JobConfig lazily) and a native mode on HarborAgentTaskRunner that builds and runs the job itself. The existing injected/offline job_dir mode is preserved. - Add HarborTasksetLoader / discover_harbor_tasks (folded in from the example) and a one-call run_harbor_eval(config, dataset_path) that loads tasks, runs, and scores — so caller code is two lines apart from imports. - harbor is imported lazily inside run_tasks (never at module load), matching the FabricAgentRuntime pattern. It is intentionally not added to the locked deps: harbor needs Python >=3.12 while the workspace supports >=3.11, so it is installed separately. - Shrink the example to the two-line native/optimizer path, refresh the README, point the e2e test at run_harbor_eval, and add no-Docker unit coverage for discovery, the taskset loader, and runner argument validation. Signed-off-by: Arpit Singh (SW-CLOUD) <arpsingh@nvidia.com>
Make an arbitrary user harbor_wrapper.py runnable through the SDK's native Harbor runtime. When agent_import_path + agent_dir are set, the runtime injects the agent package into sys.modules under a uniquely-named synthetic root for the duration of the run and tears it down afterwards, via the new scoped_harbor_agent_import context manager. The mutation is guarded by a process-wide lock and each run gets its own package name, so concurrent runs don't corrupt each other's import state and callers never touch global imports. - HarborRuntimeConfig gains agent_dir + a validator requiring it when agent_import_path is set. - _build_native_job resolves the job dir up front and builds/runs the JobConfig inside run_job so the scoped import path is used for wrapped agents. - Adds a no-Docker unit test for scoping/cleanup + config validation, and documents the wrapper path in the example README. All changes are contained in packages/nemo_evaluator_sdk/; harbor stays a lazily imported optional extra. Docker network prune / parallelism reconcile from the plan's 2.2 are intentionally omitted: the runtime runs a single Harbor job for all tasks (no double fan-out) and a global network prune is destructive on shared hosts. Signed-off-by: Arpit Singh (SW-CLOUD) <arpsingh@nvidia.com>
Relax the wrapper-agent contract so the SDK isn't opinionated about how a custom agent is packaged. agent_import_path no longer requires agent_dir: - agent_dir set -> loose wrapper file; the SDK scopes its directory into sys.modules for the run (unchanged behavior). - agent_dir unset -> already-importable module (installed package); the import path is passed straight to Harbor's own importer (plain importlib.import_module). The validator now only rejects a dangling agent_dir without an import_path. Test + README updated to cover both packaging shapes. Signed-off-by: Arpit Singh (SW-CLOUD) <arpsingh@nvidia.com>
2776570 to
ef45c2a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes the NeMo Evaluator SDK run Harbor tasks natively from a declarative config, so callers no longer re-implement the Harbor plumbing (JobConfig build,
Job.create/job.run, task discovery, and custom-agent import scoping). TargetsNemoOptimizeIntegration(stacks on top of the experimental Harbor runtime in #544).This PR delivers Phase 1 and Phase 2 of the deep-integration plan as two atomic commits. Scope is SDK-only — NeMo Optimizer is untouched.
import_pathwrapped-agent support (a userharbor_wrapper.py), with thesys.modulesscoping owned by the SDK.Minimal client-side plumbing is the acceptance criterion for both.
Caller side is two lines (apart from imports)
Phase 1 — config-driven native runtime
HarborRuntimeConfig— declarative config (agent, attempts, concurrency, timeouts, artifacts) with plain/pydantic fields only, mapped onto Harbor'sJobConfiglazily.HarborAgentTaskRunner— new native mode builds and runs the job itself; the existing injected/offlinejob_dirmode is preserved (back-compatible).HarborTasksetLoader/discover_harbor_tasks— "dataset dir → tasks" in one call (implementsAgentEvalTasksetLoader).run_harbor_eval(config, dataset_path)— one-call entry point: load tasks, run, score withHarborRewardMetric.Phase 2 — custom
import_pathagents in the SDKscoped_harbor_agent_import(agent_dir, import_path)— context manager that injects the user's agent package intosys.modulesunder a uniquely-named synthetic root for the duration of the run and tears it down after. Harbor resolves custom agents with a plainimportlib.import_module(nosys.pathhandling of its own), so this fills the gap for directory-shape wrappers while leaving Harbor's mechanism intact.sys.modulesmutation is guarded by a process-wide lock, and each run gets its own package name, so concurrent runs don't corrupt each other's import state. Callers never touch global import state.HarborRuntimeConfig.agent_diradded;_build_native_jobresolves the job dir up front and builds/runs theJobConfiginsiderun_jobso wrapped agents get the scoped import path.Design notes
harboris imported lazily insiderun_tasks(never at module load), matchingFabricAgentRuntime. Importing the SDK never requires Harbor.nemo_fabricisn't locked). Install separately:uv pip install "harbor>=0.16.1".result.json→ trial adapter (build_trials_from_job_dir) is unchanged in both phases.docker network prune+n_concurrent_trials↔parallelismreconcile) is intentionally omitted: the runtime runs a single Harbor job for all tasks (no double fan-out), and a global network prune is destructive on shared hosts.Test plan
uv run --frozen pytest packages/nemo_evaluator_sdk/tests/agent_eval/test_harbor_runtime.py— 4 passed (adapter, discovery/loader/config, runner validation, and Phase-2 import scoping/cleanup + config validation; no Docker).uv run ruff check/uv run ruff format --check/uv run --frozen ty checkon changed files.test_harbor_runtime_e2e.py) collects and skips cleanly without Harbor; runsreward == 1.0locally with Harbor + Docker.