feat(bench): a Frontier-Bench lane that keeps the adapter untouched - #1031
Merged
Conversation
added 5 commits
July 31, 2026 15:04
Frontier-Bench is Harbor's successor to Terminal-Bench: 74 tasks over seven domains, where the best published agents clear about a third. `stella_harbor:StellaAgent` runs it unmodified, so nothing under bench/harbor_adapter/ changes — its Python tree is digest-frozen for the audited claim path, and a new benchmark has to earn its place without perturbing that. Everything specific to this one lives in the new bench/evidence/frontier/. The lane pins its own Harbor, 0.20.0, rather than the Terminal-Bench claim's audited 0.6.1. All 74 tasks declare `environment_mode = "separate"` under [verifier]; 0.6.1 has no such field and pydantic's default extra="ignore" drops it silently, so 0.6.1 would run the set with the verifier sharing the agent's container, produce rewards, and warn about nothing. Plausible numbers answering the wrong question are worse than an error. The Terminal-Bench venv stays on 0.6.1. Three more differences, each because the Terminal-Bench assumption is false here rather than merely untidy: * No task has a `docker_image` — all 74 build from an environment/Dockerfile, 12 with compose. So warm_images.sh pulls the FROM bases plan.py collects, for the prepull's original reason: with --max-retries 0 a registry hiccup is a permanent reward-0 row. * Terminal-Bench's one-line resource rule puts 58 of 74 in the serial phase. plan.py tiers by footprint against what `docker info` reports and gives each tier the concurrency the host supports. * Four tasks want a GPU. On a GPU-less host they do not error — the container starts, the work is impossible, the verifier returns 0.0, and the row is arithmetically identical to a genuine miss, quietly costing ~5.4 points. They are excluded by name with a stated reason unless FB_ALLOW_GPU=1 and Docker exposes an nvidia runtime. Dataset pinned to sha256:97fd2ba3…, the leaderboard's own DATASET_REF. Resolving by name today yields sha256:63f363a1… instead, which exports a byte-identical tree (verified by diff) — two version records over the same content — but the leaderboard compares the ref string, so a run on the equally-valid other digest is rejected while being the same run. SUBMISSION.md records what a leaderboard entry needs and the two things that block one: four tasks need real GPUs and subsetting is not allowed, and a 10 GB Docker VM admits only 48 of 74. Both mean a submission run happens on Modal, not a Mac. The intake PR upstream has not merged yet. Verified: 74/74 task specs parse under both Harbors; the adapter's suite is 132/132 under 0.6.1 and 131/132 under 0.20.0, the one difference being a test asserting the audited 0.6.1 constant.
plan.py's headroom argument started life conflating two unrelated quantities — a 20 GB *storage* headroom was being subtracted from a 10 GB Docker VM's *memory*, which left a negative budget and excluded all 74 tasks with a polite explanation. Splitting them fixed the arithmetic but left env.sh exporting the old FB_STORAGE_HEADROOM_MB, which nothing reads any more, while plan.py's FB_MEMORY_HEADROOM_MB fell back to its default however the operator set it. Export the name plan.py actually reads, and say why the default is conservative: memory_mb is a cap Docker accepts even on a smaller VM, so an 8 GB task on a 10 GB daemon usually starts and then gets OOM-killed partway in — a reward-0 row indistinguishable from a real failure. At the 2048 MB default this host runs 48 of 74 tasks; at 512 it runs 66. Both are defensible, neither is silent.
plan.py decides which tasks are allowed to run, and every way it can be
wrong produces a plausible plan rather than an error — a GPU task
scheduled on a GPU-less host, a task admitted onto a machine that cannot
hold it, a build stage mistaken for an image to pull. Two of the 21 cases
are regressions already hit while writing it: the headroom that
subtracted a storage allowance from a memory budget and excluded all 74
tasks, and the Docker GPU probe, where the obvious
`{{if index .Runtimes "nvidia"}}` reports a GPU on every host alive
because Go returns a truthy zero-value struct for a missing key.
Also pins the boundary the first draft of the test got wrong: a task
whose footprint exactly equals the remaining budget is admitted, not
excluded. The comparison is strictly greater-than and should stay that
way — 8192 MB is the second most common footprint in the set, so an
off-by-one there would drop real tasks for no reason.
plan.py imports nothing outside the standard library, which is what lets
CI run these with `uv run --with pytest --no-project` — no project sync,
no Harbor install. The piece that decides what runs should not need the
benchmark harness to be testable. Extends bench.yml's scope check to
bench/evidence/frontier/ so the suite actually gates.
`--agent-import-path` is 0.6.1 spelling. By 0.20.0 it is folded into `--agent`, which takes either a built-in name or an import path. Both primary.sh and sentinel.sh passed the old one, so every run in this lane would have died on `No such option` at the first trial — after the venv build, the dataset download, the image warm, and the preflight had all passed. The Terminal-Bench lane keeps the old spelling and is still right to: it is pinned to 0.6.1, where that is the flag. Add fb_assert_cli_flags to the preflight so the next such rename costs one message instead of a run. Running two Harbor majors in one repo makes the CLI a moving contract, and it has now moved once. The check scrubs ANSI before matching, which is not incidental: Harbor renders help through Rich, which styles each option individually and leaves escape sequences inside the flag names. A literal search for `--env` over the raw 42 KB finds nothing, so the unscrubbed first draft reported all eleven flags missing and failed closed on a working CLI.
The flag rename is the kind of thing that reads as a mistake in the diff unless the reason is written down: this lane and the Terminal-Bench lane pass *different* agent flags on purpose, because they are pinned to different Harbor majors and each is correct for its own. Also notes what did not change — the trial result schema the sentinel's gates parse is identical in both, so those gates read the same fields either way.
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
Reviewer's GuideAdds a new Frontier-Bench development-baseline lane that runs Stella through the existing Harbor adapter without modifying it, by introducing a separate Harbor pin, dataset pin, resource-aware planner, warmup tooling, and CI tests under bench/evidence/frontier/ while keeping the Terminal-Bench lane and adapter behavior unchanged. Sequence diagram for Frontier-Bench primary run planning and executionsequenceDiagram
actor user
participant env_sh as env_sh
participant plan_py as plan.py
participant primary_sh as primary.sh
participant harbor_cli as harbor run
user->>primary_sh: run primary.sh <tier> <job-name>
primary_sh->>env_sh: source env.sh
env_sh->>env_sh: fb_preflight()
env_sh-->>primary_sh: preflight OK (Harbor 0.20.0, venv, dataset pin)
primary_sh->>primary_sh: read FB_PLAN
alt plan missing
primary_sh-->>user: FATAL: no plan at $FB_PLAN
else plan exists
note over primary_sh,plan_py: FB_PLAN created earlier by fetch_dataset.sh
primary_sh->>plan_py: compute tasks, concurrency for tier
plan_py-->>primary_sh: N tasks, tier concurrency
primary_sh->>harbor_cli: harbor run --env docker --dataset $FB_DATASET
primary_sh->>harbor_cli: --include-task-name FB_TASK_PREFIX/<slug>...
primary_sh->>harbor_cli: --agent stella_harbor:StellaAgent
primary_sh->>harbor_cli: --n-attempts $FB_ATTEMPTS --n-concurrent $CONC
harbor_cli-->>primary_sh: job results
primary_sh-->>user: harbor_exit, finished timestamp
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
added 2 commits
July 31, 2026 15:36
#1027 added stella_harbor/live_feed.py without adding it to _FIXED_ADAPTER_SOURCE_PATHS. The runtime digest rglobs *.py under the package, so it hashed the new module; the public-tree check walks the hand-written tuple, so it did not. The two disagreed and every path through the secure launcher failed closed — 30 tests, all reporting "public adapter tree hash differs from runtime identity". That is the freeze mechanism working exactly as designed. It was already red on origin/main before this branch existed; this PR is simply the first since #1027 to touch a path that makes the bench job run, and the job is not a required check, so nothing blocked the merge that broke it. Also adds the assertion that should have caught it. Thirty tests failing with a message that names neither the file nor the fix is a poor way to learn a module is unregistered, so one test now compares the tuple against the package on disk and names the missing path. Verified it fails with "live_feed.py" when the entry is removed again. harbor_adapter 311 passed / 1 skipped, terminal_bench_analysis 240 passed, frontier planner 21 passed.
Both increases are irreducible in the sense the ratchet cares about. _FIXED_ADAPTER_SOURCE_PATHS is the freeze list, so a registered module is one line in secure_launcher.py and cannot live anywhere else; the test that guards it asserts on that constant, so it belongs in that constant's test file. Splitting either to dodge the ceiling would move code away from the thing it describes. secure_launcher.py 4269 -> 4270, test_secure_launcher.py 3204 -> 3230.
| else echo "[$i/$total] FAILED $img"; echo "$img" >> "$FAILED"; fi | ||
| done < "$LIST" | ||
|
|
||
| nfailed=$(grep -c . "$FAILED" || echo 0) |
Contributor
There was a problem hiding this comment.
macanderson
marked this pull request as ready for review
July 31, 2026 23:48
Contributor
There was a problem hiding this comment.
Sorry @macanderson, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
macanderson
added a commit
that referenced
this pull request
Aug 1, 2026
Follow-on to #1031. That PR landed the Frontier-Bench lane but it could only ever produce a *development baseline* — on this laptop's 10 GB / 6-CPU Docker VM the plan is **48 of 74**. A submittable run must cover all 74 tasks, four of which want a GPU, without overriding any task's declared resources. No amount of scheduling fixes that; it needs a machine that can hand a container 32 GB and an H100 on demand. `FB_ENV` now selects the backend. **On `modal` the plan is 74/74 with zero exclusions.** ```bash FB_ENV=modal FB_CONCURRENCY=32 bench/evidence/frontier/fetch_dataset.sh ``` ## Three things change with the backend Each because a docker assumption is *false* there, not merely cautious: | | why | |---|---| | **Nothing excluded for capacity** | Modal builds a sandbox per trial from that trial's own declared resources, so "does this task fit" is the wrong question. Carrying the laptop's exclusions over would produce a 48-task result that looks exactly like a complete one. | | **GPU tasks default to on** | Harbor passes each task's `gpus`/`gpu_types` straight through. Defaulting them off would silently drop the four tasks a submission is *required* to cover — the same plausible-wrong-number failure this lane exists to prevent, inverted. `--no-allow-gpu` still wins when said explicitly, which is why the flag is tri-state rather than `store_true`. | | **`warm_images.sh` skips** | The builds happen in the cloud; a local pull warms a cache no trial reads. | **What does not change:** the glibc-2.17 portability assert runs on both. Modal is a different scheduler, not a different libc — a binary that cannot exec would fail there the same way it did on 2026-07-31, as an ordinary reward-0 row. ## Upload, opt-in `--upload` is now wired behind `FB_UPLOAD=1`, private by default. The leaderboard requires publicly readable trials — CI re-derives every trial from the uploaded job and a maintainer's LLM judge reads every trajectory — so `FB_UPLOAD_VISIBILITY=public` publishes the agent's full reasoning and every command it ran, permanently. That is a deliberate act, and it must never be inherited from a shell that had it set for an earlier run. ## Posture reporting `primary.sh` now names **every** reason a run would be refused as a submission, not the first: ``` posture=development-baseline — not submittable: tier=small(needs-all) attempts=1(needs-5) excluded=26(needs-0) upload=off visibility=private(needs-public) posture=submission-shaped ``` An operator who fixes one gap and reruns only to be told about the next has paid for a full run to learn something one line could have said. ## Also `setup_venv.sh` installs the `modal` extra and passes `--clear`, so it is idempotent — it previously failed the moment a venv existed, which is exactly when you want to run it (after changing the Harbor pin or adding an extra). Installing the extra unconditionally means switching backends is one variable rather than a rebuild, and lets the preflight tell "modal is not authenticated" apart from "the SDK was never installed" — different problems, different fixes. ## Verified - Full Modal chain end to end: dataset fetch → plan **74/74, 0 excluded** → `warm_images.sh` correctly skips. - Docker path unchanged: **48/74**, same exclusions and reasons as before. - `--no-allow-gpu` on Modal → 70/74, so the operator override genuinely wins. - Modal auth confirmed live (`~/.modal.toml`, workspace reachable), and the preflight distinguishes missing-SDK from unauthenticated. - **27 planner tests** (6 new for the backend), shell syntax + shellcheck clean. - Nothing under `bench/harbor_adapter/` is touched — the digest-frozen tree stays untouched, same as #1031. ## Not done No paid trial has run. The sentinel now works against either backend but is still unexecuted; that is the next step and it costs money. A real submission is ~370 multi-hour trials with no spend cap — a four-figure decision that deserves the same preregistration the Terminal-Bench claim got. The upstream intake ([PR #1405](harbor-framework/frontier-bench#1405)) is also still unmerged. ## Summary by Sourcery Add a selectable backend for Frontier-Bench runs (docker vs Modal), enabling full 74-task planning on Modal while keeping docker behavior unchanged, and wire upload posture and reporting around this. New Features: - Introduce a Modal backend that provisions capacity per task, allowing plans that include all 74 Frontier-Bench tasks without host-based exclusions. - Expose backend selection, concurrency, and GPU allowance via CLI flags and FB_ENV/FB_CONCURRENCY/FB_ALLOW_GPU environment variables, with sensible defaults per backend. - Add opt-in upload controls (FB_UPLOAD and FB_UPLOAD_VISIBILITY) so runs can be explicitly published to Harbor Hub as public or kept private. - Record backend and upload posture in run output, and report overall submission posture including all reasons a run is or is not submittable. Enhancements: - Make the planning logic support explicit concurrency overrides across backends and propagate the chosen backend into the plan metadata. - Refine preflight checks to validate the selected backend, including distinguishing missing Modal SDK from unauthenticated Modal credentials. - Update scripts (primary.sh, sentinel.sh, warm_images.sh) to respect the chosen backend, skipping local image warming and using the correct Harbor env as needed. - Improve documentation in README and SUBMISSION.md to describe backend choice, Modal workflow, hardware constraints, and submission posture. - Make setup_venv.sh idempotent by clearing the venv and always installing the harbor[modal] extra, simplifying backend switching. Tests: - Add Modal backend planner tests to ensure Docker capacity is ignored, GPU tasks are runnable, explicit concurrency overrides are honored, and backend is recorded in plans. Co-authored-by: Stella Test <test@stella.local>
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.
Harbor released Frontier-Bench — the successor to Terminal-Bench, 74 tasks over seven domains, where the best published agents clear about a third. This adds a lane to run Stella on it.
stella_harbor:StellaAgentruns it unmodified. Nothing underbench/harbor_adapter/changes. That was the design constraint, not a lucky outcome: the adapter's Python tree is digest-frozen for the audited claim path, so a new benchmark has to earn its place without perturbing it. Everything Frontier-Bench-specific lives in the newbench/evidence/frontier/.The one that would have been expensive to find later
All 74 tasks declare
environment_mode = "separate"under[verifier]. Harbor 0.6.1 — the Terminal-Bench claim's audited pin — has no such field, and pydantic's defaultextra="ignore"drops it silently. 0.6.1 will happily run the whole set with the verifier sharing the agent's container instead of the separate one the task asked for, produce rewards, and warn about nothing. Plausible numbers answering the wrong question are worse than an error.So this lane pins its own Harbor (0.20.0) in its own venv.
bench/harbor_adapter/.venvstays on 0.6.1, so Terminal-Bench reruns keep measuring what they measured.What else genuinely differs
docker_imageper taskEach of those becomes a silent wrong number if handled the Terminal-Bench way:
warm_images.shpullsFROMbases, not task images — same reason as the existing prepull, that with--max-retries 0a registry hiccup is a permanent reward-0 row.plan.pytiers by footprint against whatdocker inforeports. Terminal-Bench's one-line rule would put 58 of 74 in the serial phase.STELLA_BUDGET=(empty) means uncapped.Dataset pin
Pinned to
sha256:97fd2ba3…, the leaderboard's ownDATASET_REF. Resolving the dataset by name today yieldssha256:63f363a1…instead, which exports a byte-identical 74-task tree — verified by diff, no file differs; they're two version records over the same content. But the leaderboard compares the ref string, so a run pinned to the equally-valid other digest is rejected on submission while being, in substance, the same run.On getting to the leaderboard
SUBMISSION.mdhas the detail. Short version: the run can be produced now, the PR cannot be filed yet (the intake tooling is PR #1405, unmerged — the two "submissions" against upstream were closed by their own author as fork-test mistakes).Two things block a submittable run regardless, and both point the same way — it doesn't happen on a Mac:
plan.pyadmits 48 of 74; 22 more are over the memory budget.Also worth knowing before anyone commits to it: the metric is
reward > 0over all trials, errored trials count as 0 rather than being dropped, and 74 tasks × 5 required trials is a four-figure decision that should be preregistered like the Terminal-Bench claim was.primary.shhardcodes--env docker; a Modal path is the next piece of work, not something already done here.Also fixes a pre-existing break on main
The bench suite was already red before this branch existed. #1027 added
stella_harbor/live_feed.pywithout adding it to_FIXED_ADAPTER_SOURCE_PATHS. The runtime digest rglobs*.pyunder the package so it hashed the new module; the public-tree check walks the hand-written tuple so it didn't. The two disagreed and every path through the secure launcher failed closed — 30 tests, all reportingpublic adapter tree hash differs from runtime identity.That's the freeze mechanism working as designed. What let it land is that the bench job only runs when a PR touches its filtered paths, and it is not a required check — the same failure mode
bench.yml's own header documents from #659. This PR is simply the first since #1027 to touch a path that makes the job run.Fixed by registering the module, plus the assertion that should have caught it: one test now compares the tuple against the package on disk and names the missing path, instead of thirty tests describing its consequences. Verified it fails with
live_feed.pywhen the entry is removed again.Worth considering separately: making this check required, since it has now silently drifted red twice.
Verification
_harbor_version_value == "0.6.1", the audited constant, correctly failing under a different Harbor. CI runs it in the 0.6.1 venv and stays green.plan.pysuite: 21 tests, wired intobench.yml. Two cases are regressions hit while writing it — a headroom that subtracted a storage allowance from a memory budget and excluded all 74 tasks, and the Docker GPU probe, where the obvious{{if index .Runtimes "nvidia"}}reports a GPU on every host alive because Go returns a truthy zero-value struct for a missing key.--agent-import-pathdoesn't exist in 0.20.0 (folded into--agent). Every run in this lane would have died onNo such optionat the first trial, after the venv build, dataset download, image warm and preflight had all passed.fb_assert_cli_flagsnow asserts the whole flag list in preflight.Not done
No paid trial has been run. The sentinel is written and syntax-checked but unexecuted — running it costs money and is the natural first step after this merges.