Evaluate a Harbor Task Suite
Harbor is a container-based harness for agentic tasks: it runs each
task in a Docker sandbox, lets an agent work in it, then runs a verifier that emits named
rewards
(see Harbor’s Core Concepts for its task, trial,
and job model). If you already have Harbor task datasets, the Harbor runner runs them and scores
the verifier rewards through agent-eval — the same
AgentEvaluator and the same result and bundle as the
quickstart. Only the runner changes.
Unlike the quickstart, this runner is not zero-dependency — it shells out to Harbor and Docker:
- Python
>=3.12,<3.14— the SDK’s own floor is>=3.12, matching Harbor’s. Harbor is still imported lazily, so importing the SDK and building a Harbor config work without the extra installed; execution and existing-result adaptation need it. The upper bound isnemo-platform’s own; a source checkout resolves up to 3.14. - A Docker-compatible container runtime — Harbor invokes the
dockerexecutable onPATH(docker infomust succeed) and usesdocker compose. - Harbor
>=0.20,<0.21— see Install Harbor.
Install Harbor
nemo-evaluator-sdk is not published as a standalone package, so how you add Harbor depends on how
you installed NeMo Platform.
Installed package
Source checkout
Harbor ships with the nemo-platform all extra — services and plugins include it as well:
To add Harbor to an environment you already have, install it against the same constraint the SDK declares:
Use pip install in place of uv pip install if you prefer; both work here.
uv sync has no pip equivalent — it resolves nemo-evaluator-sdk as a workspace member, which is
why the source-checkout path needs the repository. The runner raises an error naming both of these
install paths if harbor is missing.
The dataset
A Harbor dataset is a directory of task folders. For discovery, the runner needs two files per task — the rest of the task format (environment, verifier, and solution config) is Harbor’s own:
discover_harbor_tasks reads each folder into an AgentEvalTask: the [task] name becomes the task
id and its human-readable intent, and instruction.md becomes inputs["instruction"] — the
instruction the agent is prompted with. See
Harbor’s task documentation for the full task format.
Those two files are the discovery contract, not a complete task — Harbor also needs an environment and a verifier to run the task and emit rewards. To author a new task, scaffold it with Harbor’s own CLI rather than writing the files by hand:
This writes a populated task.toml alongside instruction.md, environment, verifier, and solution
stubs, then prints the files to fill in. Add --steps N for a multi-step task, or use -d to
initialize a dataset. See Harbor’s documentation for
the full task format and the rest of its CLI.
The repo ships a one-task example dataset you can clone and point at (it is not shipped in the installed wheel). Or point the dataset path at your own Harbor suite.
Run it
A Harbor run is a normal agent-eval run: AgentEvaluator().run(tasks=..., target=runner), exactly like
the quickstart (a callable) and the
deployed-agent guide (an HTTP
target). Here the target is a HarborAgentTaskRunner.
The three pieces map onto the model: discover_harbor_tasks turns the suite into tasks,
HarborAgentTaskRunner runs the Harbor job and returns one trial per Harbor trial, and AgentEvaluator
scores each trial’s named rewards with HarborRewardMetric.
dataset_path is input; jobs_dir is output. The dataset is your read-only task suite. jobs_dir
is a directory the runner writes into — Harbor’s per-trial results land under jobs_dir/<job_name>/,
and that directory doubles as a re-run cache (see below).
The oracle agent is Harbor’s reference agent — it produces a passing trial on a well-formed task —
so for the one-task example above the verifier reward is 1.0:
Swap agent_name (or agent_import_path) for your own agent to get a real score.
Evaluator plugin submissions (nemo evaluator agent-evaluate submit) support the Harbor runner only
through the host subprocess executor. Standalone SDK runs using run_harbor_eval() or
HarborAgentTaskRunner invoke Harbor directly and do not use a Jobs execution profile.
Choosing an agent
HarborRuntimeConfig decides what runs inside each sandbox (see
Harbor’s agent documentation):
-
agent_name— a built-in Harbor agent (for example"oracle", Harbor’s reference agent, handy as a smoke test that the harness and dataset are wired up). -
agent_import_path— your own Harbor agent, e.g."my_agent_module:MyAgent". Setagent_diras well when it’s a loose file rather than an installed package. Overridesagent_name. -
agent_model_name— the model slug handed to the agent. -
agent_kwargs— keyword arguments forwarded to the agent’s constructor, the equivalent of Harbor’s--ak key=valueflags. Values must be JSON (strings, numbers, booleans, lists, mappings). Do not put secrets here. Harbor copies its config into the job directory’sconfig.jsonandlock.jsonand into every trial’sconfig.json,lock.json,result.jsonand agent run spec, and needs the real value to construct the agent, so there is no later point at which the value can be taken back. Name credentials inagent_env_from_hostinstead; a${NAME}template is fine here, since it carries no value.A credential-shaped value fails validation before the run starts — a credential-looking key (
api_key,token,secret,authorization, …) holding plaintext, or any value carrying a recognised issued-token shape (nvapi-,sk-,hf_, a PEM block, …). Treat that as a backstop, not a guarantee: a secret under a key of your own naming, in a format the check does not know, will pass straight through. The rule is still that secrets do not belong inagent_kwargs. -
agent_env_from_host— host environment variables to forward to the agent. Each becomes a${NAME}template on Harbor’sAgentConfig.env: Harbor resolves it from the host environment when it creates the agent and persists only the template, so the value never reachesconfig.json. Provenance records the names, never the values.
The agent fields map one-to-one onto Harbor’s AgentConfig, which Harbor’s agent factory unpacks
into the agent’s constructor:
For example, NeMo Fabric’s
FabricAgent
is one of Harbor’s existing agents;
its constructor requires fabric_adapter_id and accepts fabric_package and fabric_telemetry. For a
complete, runnable version with a Nemotron model, see
Run a NeMo Fabric Agent inside Harbor.
The same fields appear on the platform job’s HarborRunnerTarget, the target of an
AgentEvalInputSpec submitted with nemo evaluator agent-evaluate submit. Instead of
agent_env_from_host, the platform target takes env_secrets, a mapping of environment variable name
to NeMo Platform secret reference. The service resolves each reference into the job’s environment at
compile time and the job hands Harbor a ${NAME} template, so the credential appears on neither the
spec, the run bundle, nor config.json:
How scoring works
- Harbor runs each task in its sandbox and writes a
<task>__<hash>/result.jsonper trial, including the reward mapping from the task’s verifier. reward_keyidentifies the primary reward by name. If not specified, it defaults toreward. Mapping order and alphabetical order never select the primary.- The primary output is required. On a scoreable trial, a finite numeric value is emitted unchanged;
a missing or unusable primary emits
0.0and a diagnostic instead of skipping the trial. - Other keys from that task’s Harbor-valid results become optional secondaries:
- Finite numbers are emitted.
- Missing or Boolean values are omitted with a diagnostic; usable siblings are kept.
- A
null, nonnumeric string, or object in the reward mapping fails Harbor’sTrialResultcheck, so the whole attempt is skipped and sibling rewards are not scored. Harbor writesNaNand infinity asnull, which hits this gate. - A secondary reward key discovered for one task does not apply to another task.
result.summaryaggregates each named output,result.trialsholds each trial’s status and evidence, andresult.persist()writes the standard run bundle.
What counts as a numeric reward
Harbor’s task format defines each reward.json value
as a float or integer (reward.txt is a single number, stored under the key reward). Harbor reads
and validates that file before writing result.json:
- Finite numbers are retained. Numeric strings such as
"1.25"are coerced to numbers. - A nonnumeric string,
null, or an object fails validation of the whole reward mapping. - A JSON Boolean is coerced to
1.0or0.0. NaNand infinity pass Harbor’s numeric validation, but serialize asnullinresult.json.
The SDK first validates the whole result.json with Harbor’s TrialResult. A null, nonnumeric
string, or object in the reward mapping invalidates the attempt; its sibling rewards are not scored
or cached. For a Harbor-valid result, the SDK parses each reward per key. It emits finite numbers,
including numeric strings normalized by Harbor, and omits Boolean or non-finite values with a
diagnostic while retaining usable siblings.
Emit finite JSON numbers from the verifier. Use 1 and 0 when a reward represents pass/fail; do
not rely on Harbor’s coercion of strings or Booleans.
Sparse secondary and errored reward examples
Missing secondary reward
For two attempts on task A:
Resulting aggregates and coverage
Omission means unmeasured, not failure:
- It does not create a null, NaN, or zero per-trial metric output.
- Coverage records the missing measurement.
- Derived aggregates can therefore be unestimable (
mean=None).
See Reading Results for denominator, failed-trial, semantic-view, and persistence behavior.
Errored attempt with a valid primary reward
For two attempts on task A:
Resulting statuses and aggregate
The error changes a2’s status and error rollup; it does not discard or replace its finite reward.
Attempts and concurrency
n_attempts— desired attempts per task. On resume, Harbor runs only the missing attempts.n_concurrent_trials— maximum trials Harbor runs concurrently.
Retries
max_retries— maximum extra attempts per trial during the current Harbor run; defaults to0.- Error policy — Harbor retries only allowed errors. Its
default non-retryable errors
include
AgentTimeoutError. - Repeated SDK calls —
max_retriesnever reopens cached errored trials. Cache behavior is described below.
Caching
Caching activates only when HarborRuntimeConfig.job_name is pinned. Without it, every call creates a
fresh timestamped job directory.
A cache hit requires both:
- A usable matching stamp for the requested inputs.
- At least
n_attemptsHarbor-valid results for every requested task.
On a repeated SDK call:
- Cache hit — skips Harbor and Docker, then re-scores existing results. Harbor-valid errored results
count as completed, become
PARTIAL, and are not rerun. - Incomplete matching cache — preserves valid results, including errored results, then runs only attempts with missing or invalid results.
- Stale or unusable cache — deletes the job directory and reruns every requested attempt. This includes changed inputs, a missing or malformed stamp, and an unresolved requested task directory.
force_rerun=True— deletes the entire pinned job directory, then reruns every requested attempt.- Selective cached-error rerun — unsupported. The SDK cannot rerun cached attempts by error type.
- Concurrent processes — must not share a pinned
job_name; neither the SDK nor Harbor locks the job directory.
Cache identity
The SDK stores the cache stamp in jobs_dir/<job_name>/.nemo-eval-harbor-cache.json:
version— stamp schema; must match exactly.options— SHA-256 of result-affectingHarborRuntimeConfigfields, includingagent_kwargs; must match exactly.agent— digest ofagent_dircontents, or"<none>"when unset; must match exactly.tasks— every requested task digest must match; extra cached tasks are ignored. Cached A, B, C can serve A, but cached A cannot serve A and B.- Scoring and scheduling settings —
reward_key, selected metrics,quiet, andn_concurrent_trialsdo not invalidate Harbor execution results. agent_env_from_host— participates by name: adding or removing a forwarded variable invalidates, but the value it resolves to at run time is never fingerprinted, so rotating a credential keeps the cache valid.- Installed agents — when
agent_diris unset, the stamp covers the agent selection or import path, not the installed package contents. Change the import path, useagent_dir, or force a rerun after changing installed agent code.
Force a complete rerun
- Keep the previous job — use a new
job_name, or omit it to create a fresh timestamped directory.
Shortcut: run_harbor_eval
When a run is exactly “one Harbor suite, scored by its reward,” run_harbor_eval collapses the three
steps above — discover, run, score — into a single call:
It uses the same AgentEvaluator and HarborRewardMetric under the hood. Prefer the explicit form
above when you want to mix Harbor tasks with other tasks or metrics, or share one evaluator across
targets.