Run
tdb run writes a Harbor job.yaml, validates credentials, and runs
harbor run -c job.yaml.
uv add "trialdesignbench[harbor]"
export ANTHROPIC_API_KEY=... # agent (anthropic/ models) and judge
uv run tdb run \
--tasks tmp/tasks \
--agent claude-code \
--model anthropic/claude-opus-5-5 \
--effort high \
--n-attempts 3 \
--n-concurrent 2 \
--canary
Which agents can run, the credentials each needs, the reasoning effort
levels each accepts, and the closed-book settings tdb run applies are
described in Agents. The rubric
judge in the verifier needs the key of the built judge model's provider
(ANTHROPIC_API_KEY for the default anthropic/ model, OPENAI_API_KEY
for openai/, XAI_API_KEY for xai/, OPENCODE_API_KEY for
opencode-go/); see Judge.
Options
| Option | Meaning |
|---|---|
--agent, --model |
Harbor agent and provider/model. Repeat the pair to run a matrix in one job. |
--effort |
Reasoning effort level (none, minimal, low, medium, high, xhigh, max, or default), once for all agents or once per --agent. Each agent accepts a subset, and each model a subset of that; see Agents. Unset, the harness default applies and tdb run warns. |
--agent-version |
Must equal the pinned version (default). |
--n-attempts |
Attempts per task (default 1). |
--n-concurrent |
Concurrent trials (default 2 for api, 1 for subscription). |
--auth api\|subscription |
Credential mode, see Agents. |
--skill |
Extra skills (local path or git ref), passed to Harbor. The image already ships /skills. |
--task-ids |
Subset of tasks. |
--job-name, --jobs-dir |
Job location (default jobs/<timestamp>). |
--canary |
Run the network canary as the first task. |
--yes |
Pass --yes to Harbor (skip its prompt before passing the judge key to the verifier). |
--dry-run |
Write job.yaml and tdb-run.json, print the command, and stop. |
tdb run fails before launching when an agent is not supported, the model's
provider does not match the agent, an effort level is not one the agent
accepts, a required credential is missing, or the local image was built for
other agent pins or another grader version.
Files
<jobs_dir>/<job_name>/ Harbor's job directory
job.yaml generated config
tdb-run.json RunManifest (provenance)
<trial dirs...> written by Harbor
<jobs_dir>/<job_name>.tasks/ task copies with both allowlists filled in
harbor-plugin/
tdb_harbor_agents.py the agent classes Harbor imports (see below)
Task copies live beside the job directory, not inside it: Harbor deletes any
job subdirectory without a result.json when a job is resumed.
job.yaml uses the field names of Harbor's JobConfig. It is written in JSON
syntax, which is valid YAML, so no YAML dependency is needed. Secret values
are never written to job.yaml or tdb-run.json; only variable names are
recorded.
How the instruction reaches the agent
Harbor's adapters for all four supported agents hand the rendered
instruction.md to the CLI inside a single docker compose exec call:
Claude Code as an environment variable, Codex CLI, Grok Build, and OpenCode
as a quoted argument of the bash -c command. Linux caps one argument or
environment string at 128 KiB (MAX_ARG_STRLEN), on the host and again
inside the container, so an instruction carrying a protocol or SAP of a few
hundred kilobytes kills the trial before the agent starts
(OSError: [Errno 7] Argument list too long: 'docker'). Harbor 0.23.0 and
0.24.0 share this limitation.
tdb run therefore launches every agent through Harbor's import_path
rather than its agent name. The classes live in
trialdesignbench.harbor_agents, a Harbor plugin: subclasses of Harbor's
adapters that upload the instruction into the container
(/installed-agent/tdb-instruction.md, via docker compose cp) and make the
CLI read it from there, byte for byte:
| Agent | Harbor's transport | With the plugin |
|---|---|---|
claude-code |
env var piped into claude --print |
the env var becomes a shell variable read from the file, piped the same way |
codex |
codex exec -- '<instruction>' |
codex exec -- - <FILE (prompt from stdin) |
grok-build |
grok --single '<instruction>' |
grok --prompt-file FILE |
opencode |
opencode run -- '<instruction>' |
opencode run -- <FILE (prompt from stdin) |
Everything else is inherited from Harbor's adapter: credentials, settings,
skills, sessions, cleanup, and trajectories, and name() still answers
claude-code, codex, grok-build, or opencode, which is what
result.json and tdb report show. job.yaml has import_path =
"tdb_harbor_agents:<Class>" and no name (Harbor would prefer a valid
name and skip the plugin); each trial's result.json records the same
config.agent.import_path.
tdb run copies the module beside the task copies
(<job>.tasks/harbor-plugin/tdb_harbor_agents.py), runs harbor with that
directory first on PYTHONPATH, prints the command with that prefix so it
can be replayed by hand, and records the copy's digest in tdb-run.json
(harbor_plugin) next to each agent's import_path. The copy makes the job
runnable whether or not Harbor shares a Python environment with tdb. A
launch command the plugin does not recognize (a future Harbor adapter)
fails the trial with InstructionTransportError instead of silently using
the old transport; a command that no longer carries the instruction passes
through unchanged.
Grok Build excerpts long requests on its own
Grok Build receives the whole instruction but, for requests over about
100 KB, sends the model an excerpt with a note that the full request is
saved in its session directory and that the omitted line ranges can be
read with its read_file tool. This is the CLI's own behavior for any
long prompt, not the transport's; the trajectory records the excerpted
message. Expect Grok Build models to read the rest of a long protocol
with tool calls, or not at all.
Network allowlists
tdb build leaves two deny-all allowlists in every task.toml; tdb run
fills them in the task copies:
[agent] allowed_hosts, applied by Harbor duringagent.run(): the model API hosts of the agents and auth mode.[environment] allowed_hosts, the baseline during agent setup: the same hosts plus the install hosts of agents that Harbor installs at setup (grok-build, opencode). For preinstalled agents both lists are equal.
The per-agent hosts are listed in Agents and the policy itself in Environment. Closed book explains how Harbor enforces the allowlist and why it alone does not make an agent closed book.
Matrices
Repeating --agent/--model puts all agents in one job. Harbor applies one
task definition to every agent, so when agents need different hosts every
trial gets the union (for example api.anthropic.com and api.openai.com,
plus any setup hosts). tdb run warns when this happens; run one job per
agent if you need strict per-agent allowlists.
A matrix may repeat the same agent and model at different --effort levels.
Harbor names each trial by task plus a random suffix, every trial's
result.json records its own agent kwargs, and tdb report keeps the
levels apart.
Regrade
uv run tdb regrade \
"jobs/<job>" \
--tasks tmp/tasks-v2 \
--job-name "<job>-regrade"
This wraps harbor job regrade. The recorded agent outputs and trajectories
are re-scored with the verifier from --tasks (for example after a rubric or
grader change). No agent is re-run and no agent credentials are needed; the
judge still needs its provider's key (ANTHROPIC_API_KEY by default).