|
| 1 | +--- |
| 2 | +name: run-benchmark |
| 3 | +description: >- |
| 4 | + Run one harness-engineering-bench optimization end-to-end (compile → inference |
| 5 | + gateway → optimizer agent → sandboxed evals → finalize on held-out test), |
| 6 | + including the preflight to confirm before launching and the health checks to |
| 7 | + verify after. Use when launching, reproducing, or debugging a benchmark run. |
| 8 | +--- |
| 9 | + |
| 10 | +# Running a harness-engineering-bench optimization |
| 11 | + |
| 12 | +This is a runbook for launching one benchmark's optimization run and confirming |
| 13 | +it is healthy. It is written to be provider-agnostic: fill in your own inference |
| 14 | +endpoint, Modal account, and W&B account. Treat every `<placeholder>` as |
| 15 | +something you supply. **Never commit real keys, tokens, endpoints, or absolute |
| 16 | +personal paths** — they belong only in a local, git-ignored `secrets.env`. |
| 17 | + |
| 18 | +## What a run is |
| 19 | + |
| 20 | +Each benchmark compiles from `harness-engineering-bench/<benchmark>/baseline/build.yaml`, |
| 21 | +the single source of truth. `vero harbor run` compiles it into a Harbor task and |
| 22 | +stands up three things: an **inference gateway** (holds the real upstream key, |
| 23 | +enforces per-scope model allow-lists), an **evaluation sidecar** (owns the cases, |
| 24 | +scoring, and final candidate selection), and the **optimizer agent** (a coding |
| 25 | +agent that edits only `target/`, commits candidates, and scores them via the |
| 26 | +`evals` CLI). When the optimizer finishes, the trusted verifier scores the |
| 27 | +selected candidate on the held-out `test` partition and writes the final reward. |
| 28 | + |
| 29 | +The optimizer never sees the real upstream key — it gets a scoped producer token |
| 30 | +pointed at the gateway. Keep it that way. |
| 31 | + |
| 32 | +## Prerequisites (confirm these exist first) |
| 33 | + |
| 34 | +- The repo checkout containing both the `vero/` CLI package and |
| 35 | + `harness-engineering-bench/`. Run commands from the `vero/` subdirectory. |
| 36 | +- `uv` installed (the CLI is invoked as `uv run vero ...`). |
| 37 | +- **Docker** running — needed for `--environment docker` (the outer optimizer |
| 38 | + compose runs locally). |
| 39 | +- A **Modal** account + tokens — the inner evaluation sandboxes run there by |
| 40 | + default (`environment_name: ${inner_env:-modal}`). |
| 41 | +- A **Weights & Biases** account + API key (self-hosted or cloud) for telemetry. |
| 42 | +- An **OpenAI-compatible inference endpoint** + key that can serve both your |
| 43 | + optimizer model and each benchmark's target model. The gateway proxies to it. |
| 44 | +- A local `secrets.env` (copy from a benchmark's `secrets.env.example` where |
| 45 | + present). Required keys (names only — never values in any committed file): |
| 46 | + `OPENAI_API_KEY`, `OPENAI_BASE_URL`, `MODAL_TOKEN_ID`, `MODAL_TOKEN_SECRET`, |
| 47 | + `WANDB_API_KEY`, `WANDB_BASE_URL`. A few benchmarks that run an in-container |
| 48 | + judge/user-sim also need `OPENAI_API_BASE` (set it equal to `OPENAI_BASE_URL`). |
| 49 | + See each benchmark's `baseline/README.md` and `CONFIGURATION.md`. |
| 50 | + |
| 51 | +## Launch command (template) |
| 52 | + |
| 53 | +```bash |
| 54 | +cd <repo-root>/vero |
| 55 | +uv run vero harbor run \ |
| 56 | + --config ../harness-engineering-bench/<benchmark>/baseline/build.yaml \ |
| 57 | + --env-file secrets.env \ |
| 58 | + --environment docker \ # outer optimizer location (see tradeoff below) |
| 59 | + --agent <agent-name> \ # e.g. codex or claude-code (exact registry name) |
| 60 | + --model <optimizer-model> \ # must match the gateway producer allow-list |
| 61 | + --param wandb_run=<benchmark>__<optimizer-model> \ |
| 62 | + --yes \ |
| 63 | + -o ../runs/<benchmark>/<optimizer-label>/jobs |
| 64 | +``` |
| 65 | + |
| 66 | +Notes that bite if you get them wrong: |
| 67 | + |
| 68 | +- **`--agent` is the exact Harbor registry name**, not a friendly alias. If it is |
| 69 | + wrong you get `Agent name <x> is not valid. Valid agent names: {...}` at init |
| 70 | + (a fast, harmless crash — no containers start). The Claude Code agent is |
| 71 | + `claude-code` (not `claude`); the OpenAI coding agent is `codex`. If unsure, |
| 72 | + run with a deliberately bogus `--agent` once and read the valid-names list. |
| 73 | +- **`--model` must be spelled exactly as the producer allow-list** in the |
| 74 | + build.yaml (`inference_gateway.producer.allowed_models`, usually |
| 75 | + `${optimizer_model:-<default>}`). `--model` is threaded into that placeholder, |
| 76 | + so the agent's model and the allow-list stay in lockstep; a mismatch is a |
| 77 | + gateway 403 on the optimizer's first request. If a coding agent rewrites the |
| 78 | + model string on the wire (some strip a provider prefix), pass the bare form |
| 79 | + that both the upstream serves and the allow-list expects. |
| 80 | +- **`-o <dir>`** is forwarded to Harbor; use the systematic layout |
| 81 | + `runs/<benchmark>/<optimizer-label>/jobs`. |
| 82 | +- **`--environment`** controls where the *optimizer* runs: `docker` = local, |
| 83 | + fully observable, but the run dies if the machine sleeps; `modal` = survives a |
| 84 | + local sleep/disconnect, less local visibility. Inner evals are on Modal either |
| 85 | + way (override with `--param inner_env=docker` only for a local shakedown). |
| 86 | + |
| 87 | +## Preflight — confirm BEFORE launching |
| 88 | + |
| 89 | +Do a dry compile and inspect the artifacts. This catches almost every |
| 90 | +misconfiguration for free: |
| 91 | + |
| 92 | +```bash |
| 93 | +cd <repo-root>/vero |
| 94 | +VERO_SKIP_SECRET_CHECK=1 uv run vero harbor build \ |
| 95 | + --config ../harness-engineering-bench/<benchmark>/baseline/build.yaml \ |
| 96 | + --output /tmp/precompile |
| 97 | +``` |
| 98 | + |
| 99 | +Then check, in the compiled `/tmp/precompile`: |
| 100 | + |
| 101 | +1. **`environment/sidecar/serve.json` → `backends`**: each partition backend has |
| 102 | + the `n_attempts` / `aggregate_attempts` you intend. If you want the noisy |
| 103 | + held-out finalize averaged over N, only the **test** backend should show |
| 104 | + `n_attempts: N` / `aggregate_attempts: mean`; development and validation stay |
| 105 | + at the global (usually 1). This is set per target in build.yaml |
| 106 | + (`targets[].n_attempts` / `aggregate_attempts`). |
| 107 | +2. **`serve.json` → `wandb`**: `project`, `group`, `name` are what you expect. |
| 108 | +3. **Model allow-lists** (`serve.json` and `environment/gateway/config.json`): |
| 109 | + the **evaluation** allow-list is the benchmark's target model; the |
| 110 | + **producer** allow-list is your optimizer model (it shows the build default |
| 111 | + here — at launch `--model` overrides it). |
| 112 | +4. **`instruction.md`** (the optimizer's task prompt): read it end to end. The |
| 113 | + objective, the `evals run --backend ... --partition ...` command, and the |
| 114 | + exposed partitions should be right, and it must not advertise tools/skills/ |
| 115 | + subagents that aren't actually shipped in the workspace. |
| 116 | + |
| 117 | +Also confirm, out of band: |
| 118 | + |
| 119 | +- **The optimizer model actually serves on your upstream** — send one tiny |
| 120 | + request to your inference endpoint with that exact model string. A model the |
| 121 | + upstream doesn't recognize 403/404s the optimizer immediately. |
| 122 | +- **W&B auth works** and the project name is correct (a viewer query against your |
| 123 | + W&B host with the key). |
| 124 | +- **Docker is up** (`docker info`) and **Modal tokens are valid**. |
| 125 | +- **The target model and `baseline_reward`** pinned in build.yaml are the ones |
| 126 | + you mean to compare against (`CONFIGURATION.md` records the held-out baselines). |
| 127 | + |
| 128 | +## Launch discipline |
| 129 | + |
| 130 | +- **Launch detached** so the run outlives your shell/session. Redirect to a log |
| 131 | + and record the PID, e.g. `nohup bash launch.sh > run.log 2>&1 &` (or `setsid`; |
| 132 | + on macOS, which lacks `setsid`, a Python double-fork + `os.setsid()` daemonizer |
| 133 | + works). Keep `launch.sh`, `run.log`, `run.pid` alongside the `jobs/` output. |
| 134 | +- Prefer to **confirm the launch** (of a full-budget run) before firing — these |
| 135 | + spend real target-model and optimizer tokens plus Modal compute. |
| 136 | + |
| 137 | +## Post-launch health check — verify AFTER launching |
| 138 | + |
| 139 | +Watch for a fast crash first (invalid agent name, missing secret, model 403, |
| 140 | +Docker down), then confirm forward progress. A tail filtered for failure |
| 141 | +signatures catches the crashes: |
| 142 | + |
| 143 | +```bash |
| 144 | +tail -f run.log | grep -E 'Traceback|Exception|Error|denied|40[0-9]|429|Killed|OOM|not valid|Cannot connect to the Docker' |
| 145 | +``` |
| 146 | + |
| 147 | +After a few minutes (first build can be slow), confirm the good path: |
| 148 | + |
| 149 | +- Outer process still alive; no traceback in `run.log`. |
| 150 | +- The compose containers are up (gateway, sidecar, optimizer/agent). |
| 151 | +- The optimizer is issuing **gateway-authorized** requests — no 403s in the |
| 152 | + gateway request log (a 403 storm = producer model ≠ allow-list). |
| 153 | +- Inner eval sandboxes are dispatching (Modal auth is good) and `result.json` |
| 154 | + files begin appearing under the `jobs/` tree. |
| 155 | +- A W&B run shows up under the expected project, not immediately failed. |
| 156 | +- Provider **429s**: the seed agents retry transient rate limits, but sustained |
| 157 | + 429s mean you are hitting a shared upstream quota — throttle concurrency. |
| 158 | + |
| 159 | +If you see a clear failure signal, **stop the run and diagnose from disk** rather |
| 160 | +than letting it burn budget. Detached/daemon-owned sandboxes can keep running |
| 161 | +after the parent dies, so check for and clean up orphans when you kill a run. |
| 162 | + |
| 163 | +## What "done / green" looks like |
| 164 | + |
| 165 | +- **`finalize.json`** (admin volume) / **`harbor-finalization.json`** (session): |
| 166 | + `shipped: true`, a `rewards` map (keyed by the target's `reward_key`), and |
| 167 | + `baseline_rewards`. `shipped: false` means selection produced nothing. |
| 168 | +- Session artifacts exported: a portable `experiment.html` report, the session |
| 169 | + archive, and the candidates repo. |
| 170 | +- The W&B run is finished (not failed); its summary carries `shipped`. |
| 171 | +- Compare the candidate's held-out reward against the pinned `baseline_reward` |
| 172 | + for that benchmark — an improvement is `shipped: true` with a higher reward. |
| 173 | + |
| 174 | +## Cost awareness |
| 175 | + |
| 176 | +A full run spends: target-model tokens on development + validation (bounded by |
| 177 | +each partition's `total_cases`), the optimizer-model session, plus finalization |
| 178 | +(`n_attempts × test_size` case-evaluations; the pinned baseline is not re-scored |
| 179 | +when `score_baseline: false`). Read `budgets.json` in the session for actuals. |
| 180 | + |
| 181 | +## Per-benchmark gotchas |
| 182 | + |
| 183 | +Don't hardcode assumptions — read `CONFIGURATION.md` and the benchmark's |
| 184 | +`baseline/build.yaml` and `baseline/README.md`. Common differences: a benchmark |
| 185 | +may pin a **different target model**; some run an **in-container judge or |
| 186 | +user-simulator** on the real upstream (extra credentials + cost, and reduced |
| 187 | +isolation); some pull **large prebuilt images or corpora** (long first build); |
| 188 | +timeouts and partition sizes vary widely. When in doubt, dry-compile and read the |
| 189 | +rendered `instruction.md` and `serve.json`. |
0 commit comments