Skip to content

Commit 92b392e

Browse files
SandyChapmanclaude
andcommitted
docs(evaluator): document the Gym runner and fix the runner protocol
The Gym runner shipped with no documentation. "Gym" appeared nowhere in `docs/`, even though `GymAgentTaskRunner` sits alongside the Callable and Harbor runners in the SDK and `GymRunnerTarget` alongside Codex, Fabric, and Harbor in the job spec. Targets and Runners was also wrong about the one thing readers copy from it. `AgentTaskRunner` is a two-member protocol -- `run_tasks` *and* `runner_info` -- but the page called it "the one-method protocol", showed only `run_tasks`, and its `EchoRunner` example omitted `runner_info`. That example does not work: being `@runtime_checkable`, the protocol rejects the class, and the run dies with `NotImplementedError: unsupported agent-eval target type: EchoRunner`, naming neither the protocol nor the missing method. Fixed, and the failure mode is now stated so the error is searchable. Adds "Evaluate a NeMo Gym Environment" beside the Harbor page, which had the same shape already: an example README in `examples/gym/` and no doc. It covers install and the `PATH` constraint, credentials, task discovery, the config reference, results, output directories, the two-step Gym invocation, and submission as a platform job. Adds "Evaluate with a NeMo Fabric Harness" alongside it. Fabric was the other shipped runner with no documentation at all -- absent from the whole `docs/` tree despite covering four harnesses through one config. The page documents the adapter map, the agent-config shape, the ATIF trajectory that distinguishes this runner, and the sandboxed `FabricContainerRuntime`. Codex and the OpenAI-Agents Docker sandbox are deliberately left undocumented: the Codex runner is being removed in #1419, and `DockerSandboxAgentRuntime` is stranded by the same PR (it existed only as the `sk-...`-key branch of ProfBench's Codex path) and is expected to follow it. The Fabric page is verified the same way: its config and run blocks execute verbatim against a real Fabric -> Codex -> Relay evaluation, producing a completed trial with an `atif` trajectory. That caught two defects in the draft -- `work_dir` needed a `Path`, and the example task declared `metrics=[]`, which the evaluator rejects outright. `FabricContainerRuntime` is documented from source rather than run: its own tests use a fake sandbox provider, so there was nothing live to exercise. Everything here was executed rather than read. A throwaway venv with `nemo-gym` installed ran three live evaluations against mcqa, which caught four errors in my own draft: `agent_config` was `configs/simple_agent.yaml` where the real value is `responses_api_agents/simple_agent/configs/simple_agent.yaml`; `result.summary.scores` does not iterate scores (`.scores.scores` does); `work_dir` needs a `Path`; and the page never showed how to set an output directory at all. The score names, the 0-100 vs 0-1 scale note, and the `gym_run/` artifact list are copied from real output. The taskset-submission block is executed verbatim in review too -- it stores five tasks from the bundled mcqa dataset and the job side rebuilds all five rows from them. The submission snippet was executed verbatim against #1315's branch, and two more traps came out of it: a task cannot be named after `task.id` (a 64-char hash starting with a digit, against a 63-char cap requiring a leading letter), and `GymRewardMetric` is not a built-in type so the inline packager rejects it. Two fixes outside the docs tree, both found while sourcing from the example: `examples/gym/README.md` said to install Gym "in the same environment as the SDK". The source says the opposite, and it is right -- Gym imports Ray at module load and nemo-platform excludes Ray by constraint. Its "Next steps" also linked `runtimes/gym_runtime.py`, which became the `runtimes/gym/` package; split into live links to `config.py` and `runtime.py`. The reuse guard's `FileExistsError` told the caller to "give each run a fresh output_dir". There is no such parameter -- it is `AgentEvalRunConfig.work_dir` -- so the message sent readers looking for an argument that does not exist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Sandy Chapman <schapman@nvidia.com>
1 parent 42b4009 commit 92b392e

7 files changed

Lines changed: 612 additions & 13 deletions

File tree

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
---
2+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
title: "Evaluate with a NeMo Fabric Harness"
6+
description: "Run a coding agent through NeMo Fabric and score it with agent-eval — one config selects the harness (Codex, Claude, Hermes), and Fabric returns an ATIF trajectory a metric can grade on."
7+
---
8+
9+
[NeMo Fabric](https://github.com/nvidia/nemo-fabric) runs an agent **harness** rather than a single
10+
agent. Which harness runs is selected entirely by `config["harness"]["adapter_id"]`, so one runtime
11+
covers several agent frontends without changing your evaluation. Fabric returns an **ATIF
12+
trajectory** alongside the final answer, so a metric can score *how* the agent worked, not just what
13+
it answered.
14+
15+
The result and bundle are the same as the
16+
[quickstart](/documentation/evaluate-models/agent-eval/quickstart) — only the runner changes.
17+
18+
## Harnesses
19+
20+
| `adapter_id` | Harness | Transport |
21+
|---|---|---|
22+
| `nvidia.fabric.codex` | Codex CLI | `cli` (subprocess) |
23+
| `nvidia.fabric.claude` | Claude | `cli` |
24+
| `nvidia.fabric.hermes` | Hermes SDK | `library` (in-process) |
25+
| `nvidia.fabric.langchain.deepagents` | LangChain deepagents | `library` |
26+
27+
<Warning>
28+
29+
This runner is **not** zero-dependency. It needs:
30+
31+
- **The harness adapters**, from the `fabric` extra:
32+
33+
```bash
34+
uv sync --frozen --package nemo-evaluator-sdk --extra fabric --inexact
35+
```
36+
37+
- **The `nemo-relay` gateway binary**, which captures the trajectory for out-of-process harnesses.
38+
The pip package ships bindings only, so the daemon comes from a GitHub release asset:
39+
40+
```bash
41+
script/dev-install-fabric.sh
42+
```
43+
44+
- **The harness's own CLI**, for `transport: cli` harnesses — `codex` on `PATH` and authenticated.
45+
46+
The `fabric` extra installs the Codex, Claude, and Hermes adapters. The deepagents adapter is
47+
deliberately excluded from it, because that adapter does not support the Relay observability
48+
configuration Fabric streaming generates; install its harness separately if you need it.
49+
50+
</Warning>
51+
52+
## The agent config
53+
54+
One mapping describes the whole agent — harness, runtime, environment, and model. This is a working
55+
Codex configuration:
56+
57+
```python
58+
config = {
59+
"schema_version": "fabric.agent/v1alpha1",
60+
"metadata": {"name": "eval-fabric"},
61+
"harness": {
62+
"adapter_id": "nvidia.fabric.codex",
63+
"resolution": "preinstalled",
64+
"settings": {"sandbox": "workspace-write"},
65+
},
66+
"runtime": {
67+
"mode": "oneshot",
68+
"transport": "cli",
69+
"input_schema": "text",
70+
"output_schema": "message",
71+
"timeout_seconds": 180,
72+
},
73+
"environment": {"provider": "local", "workspace": "ws"},
74+
"models": {"default": {"provider": "openai", "model": "<model>"}},
75+
"telemetry": {"enabled": False},
76+
}
77+
```
78+
79+
Across harnesses the shape differs mainly in `adapter_id`, `runtime.transport`, and any
80+
harness-specific `harness.settings`. Codex runs as a subprocess (`transport: cli`) while the Hermes
81+
SDK harness runs in-library (`transport: library`). For complete Codex-CLI and Hermes-SDK
82+
configurations, see `examples/fabric_harness_runtimes.py` in the SDK.
83+
84+
<Warning>
85+
86+
**The Codex adapter requires an explicit model provider.** It does not fall back to the Codex CLI's
87+
own configured default, and starting without `models.default` fails the adapter lifecycle with
88+
`codex_invalid_configuration`.
89+
90+
</Warning>
91+
92+
Fold the complete configuration into this mapping. Fabric profile overlays are not used here.
93+
94+
## Run it
95+
96+
```python
97+
from pathlib import Path
98+
99+
from nemo_evaluator_sdk.agent_eval.evaluator import AgentEvaluator
100+
from nemo_evaluator_sdk.agent_eval.runtimes.fabric.runtime import FabricAgentRuntime
101+
from nemo_evaluator_sdk import StringCheckMetric
102+
from nemo_evaluator_sdk.agent_eval.tasks import AgentEvalRunConfig, AgentEvalTask
103+
104+
tasks = [
105+
AgentEvalTask(
106+
id="reply-ok",
107+
intent="Reply with a fixed token.",
108+
inputs={"instruction": "Reply with the word OK."},
109+
metrics=[
110+
StringCheckMetric(
111+
operation="contains",
112+
left_template="{{sample.output_text}}",
113+
right_template="OK",
114+
)
115+
],
116+
)
117+
]
118+
119+
runtime = FabricAgentRuntime(
120+
config=config,
121+
work_root="fabric",
122+
capture_trajectory=True,
123+
)
124+
125+
result = AgentEvaluator().run_sync(
126+
tasks=tasks,
127+
target=runtime,
128+
config=AgentEvalRunConfig(work_dir=Path("out"), parallelism=1),
129+
)
130+
```
131+
132+
### Configuration
133+
134+
| Field | Required | Notes |
135+
|---|---|---|
136+
| `config` | yes | the agent config mapping above |
137+
| `model` | no | overrides `models.default.model` without editing the config |
138+
| `base_dir` | no | base directory for relative paths in the config |
139+
| `work_root` | no | where Fabric's per-run working directories are created |
140+
| `timeout_s` | no | overall run timeout, default `600` |
141+
| `capture_trajectory` | no | capture the ATIF trajectory as evidence, default `True` |
142+
| `trajectory_extra` | no | extra fields merged into the captured trajectory |
143+
| `runtime_name` | no | the name recorded on `runner_info`, default `fabric` |
144+
| `skills` | no | skills injected into the agent's workspace |
145+
| `task_hook` | no | a `FabricTaskRunHook` invoked around each task run |
146+
147+
## The trajectory
148+
149+
With `capture_trajectory=True`, each trial carries the agent's ATIF trajectory as **`trace`**
150+
evidence:
151+
152+
```python
153+
trial = result.trials[0]
154+
assert trial.evidence is not None
155+
trace = trial.evidence.descriptors["trace"]
156+
# trace.format == "atif"; trace.ref is a path to the trajectory JSON, which has a "steps" key
157+
```
158+
159+
That is what separates Fabric from the other runners: a metric can read the trajectory and score the
160+
agent's process — tool calls, reasoning, and observations — instead of only its final answer. See
161+
[Writing Metrics](/documentation/evaluate-models/agent-eval/writing-metrics) for reading evidence,
162+
and [Score by Component](/documentation/evaluate-models/agent-eval/score-by-component) for turning
163+
several signals into one reported score.
164+
165+
## Running in a sandbox
166+
167+
`FabricContainerRuntime` takes the same config and runs it inside a sandbox rather than on the host:
168+
169+
```python
170+
from nemo_evaluator_sdk.agent_eval.runtimes.fabric.container_runtime import FabricContainerRuntime
171+
from nemo_evaluator_sdk.agent_eval.runtimes.sandbox.providers.docker import DockerSandboxProvider
172+
173+
runtime = FabricContainerRuntime(config=config, provider=DockerSandboxProvider())
174+
```
175+
176+
| Field | Required | Notes |
177+
|---|---|---|
178+
| `config` | yes | the same agent config, or a `FabricConfig` |
179+
| `provider` | yes | a `SandboxProvider``DockerSandboxProvider` or `DockerComposeSandboxProvider` |
180+
| `secrets` | no | secret references made available inside the sandbox |
181+
| `image` | no | override the sandbox image |
182+
| `skills` | no | skills injected into the agent's workspace |
183+
184+
Use it when the agent should not run on the host — untrusted tasks, or a workspace that must be
185+
discarded per task. The host runtime is otherwise the simpler choice.
186+
187+
## Next steps
188+
189+
<Cards>
190+
<Card title="Agent Evaluation (concepts)" href="/documentation/evaluate-models/agent-eval" />
191+
<Card title="Targets and Runners" href="/documentation/evaluate-models/agent-eval/targets-and-runners" />
192+
<Card title="Writing Metrics" href="/documentation/evaluate-models/agent-eval/writing-metrics" />
193+
</Cards>

0 commit comments

Comments
 (0)