fix: export the session on every terminal outcome, not only when the verifier runs - #77
Merged
Merged
Conversation
…ccess Two outer optimization trials died on 2026-07-31 and only one left anything behind. The difference was not vero code, it was which exception Harbor's agent phase raised: single_step.py:83 swallows only AgentTimeoutError and NonZeroAgentExitCodeError, so the run that hit a provider budget limit reached `_run_verifier` (tests/test.sh -> `vero harbor finalize` + `export-session`) and left an 8.8M verifier/session.tar.gz, while the run that hit a Modal grpclib StreamTerminatedError at 71 minutes propagated past the verifier entirely and left nothing. That second run had already produced a validation score of 0.1224 on 49 cases; its candidate is unrecoverable. Artifact collection, unlike the verifier phase, does run on the failure path: Trial._recover_outputs calls _collect_artifacts, and in the lost run it succeeded from the same sandbox moments after the stream died (manifest.json records status "ok"). So hang the snapshot off that instead. - `vero harbor archive-session`: token-free, HTTP-free, finalize-free snapshot of the sidecar session directory. 3.2s measured on a real 63M / ~2300-file session, versus the 28 minutes the verifier phase took. - The compiled task.toml now declares a [[verifier.collect]] hook running it inside the eval-sidecar, plus a matching [[artifacts]] entry, so the archive lands at <task>/artifacts/session-rescue.tar.gz on every terminal outcome. Both are best-effort on Harbor's side and cannot fail a trial. This is deliberately not a resume. The optimizer's working tree and agent context live in the Modal sandbox, which is torn down; what the archive preserves is every candidate commit (candidates/repository.git) and every evaluation and score (database.json), which is what recovery actually needs. Verified against harbor 0.20.0's own TaskConfig: the rendered task.toml validates, and the sidecar collection pass now runs (it previously short- circuited on an empty sidecar set). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The existing tests assert what we emit into task.toml. This asserts what Harbor makes of it, which is where the change can silently become a no-op: `Trial._collect_artifacts_phased` opens the sidecar pass with `if not sidecars: return`, so a hook that parses but lands under `main`, or that does not survive into `verifier.collect` at all, leaves the failure path exactly as it was while every emitted-config assertion still passes. Validated through Harbor's real `TaskConfig`, the same model the runtime builds a trial from, so a renamed or dropped field surfaces here rather than in a dead run. A/B'd rather than assumed: with the `[[verifier.collect]]` block stripped from the template the test fails on `assert []`, and it passes with the block restored. Verified separately by source trace that the hook is reached on the failure path: `Trial.run` catches broad `Exception` (trial.py:361), calls `_recover_outputs` (trial.py:362), which calls `_collect_artifacts` (single_step.py:60) -> `_collect_artifacts_phased` (single_step.py:69) -> `_run_collect_hooks` (trial.py:1023). That is the path a grpclib StreamTerminatedError takes, and it is why the rescue can hang off collection when it cannot hang off the verifier.
varunursekar
approved these changes
Aug 1, 2026
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.
The gap
An optimization's durable output (candidate commits, evaluation records, per-case scores) is written by the verifier phase. But the verifier phase does not run on every failure, so whether hours of work survives depends on which exception type harbor caught.
harbor/trial/single_step.pyruns_run_agent()->_collect_artifacts()->_run_verifier(), and_run_agentswallows only two types:Anything else propagates and
_run_verifier()never executes.Verified against two real runs on 2026-07-31
StreamTerminatedError(grpclib)ApiRateLimitErrorApiRateLimitErroris aNonZeroAgentExitCodeError, confirmed viaissubclass)result.jsonverifiernullverifier/contentssession.tar.gz8.8MRun 1 lost everything despite having already earned a validation score of 0.1224 on 49 cases. Run 2 kept a shipped candidate (
da6ab2d68f22) that was later recovered and rescored.The sandbox was reachable the whole time. Run 1's
trial.logshowsTrial failed: Connection lostimmediately followed byCollecting main service artifacts, and itsartifacts/manifest.jsonrecordsstatus: "ok". Harbor calls_collect_artifactsfrom_recover_outputson the failure path. The evidence was sitting on a volume harbor could still read; nothing exported it.Fix
Hang the export off artifact collection, which runs on every terminal outcome, instead of off the verifier phase, which does not.
vero harbor archive-session: token-free, HTTP-free, finalize-free snapshot straight off the admin volume. Measured 3.2s on the real 63M / ~2300-file Run 2 session, against the 28 minutes Run 2's verifier phase took.task.toml.j2emits a[[verifier.collect]]hook plus a matching[[artifacts]]entry, landing the archive at<task>/artifacts/session-rescue.tar.gz. Both are best-effort in harbor and cannot fail a trial.LAYOUT.session_rescue_archiveis a sibling ofsession_dir, so the verifier's own later export cannot recursively contain it.Deliberately not done
rescore_candidate.pywas not ported into vero. Its value is the scoring, which needs the benchmark'sbuild.yamland pinnedbaseline_rewardfromharness-engineering-bench. A vero command that extracts a worktree but cannot score it would not close the gap. The docs now name the script explicitly so recovery stops being tribal knowledge.A true "resume" is infeasible and was not attempted: the optimizer's working tree and harness context die with the sandbox (
environment.delete: true). What is worth keeping is the candidates and the scores, and those are recoverable.Verification beyond unit tests
task.tomlvalidates against harbor 0.20.0's realTaskConfig; the collect hook parses as('eval-sidecar', 'vero harbor archive-session', 600.0). Previouslysidecarswas empty and_collect_artifacts_phasedshort-circuited atif not sidecars: return.extract_harbor_session_archiveand yields candidateda6ab2d68f22...with parent43bc6a0f..., matchingfinalization.jsonexactly.Tests
Three new:
test_compiled_task_rescues_the_session_outside_the_verifier_phase,test_archive_session_command_snapshots_without_a_token_or_a_live_sidecar,test_archive_session_command_reports_a_missing_session, plus asession_rescue_archiveassertion in the pinned-layout test.Note the suite requires credentials in the environment; without them six tests fail on
compiler.py:566credential validation and look like regressions. Withset -a; . heb.secrets.env; set +athe full suite is 485 passed, 15 skipped, one unrelated collection error intest_uv_with_editable.py.🤖 Generated with Claude Code
Greptile Summary
Adds a best-effort rescue path for optimization sessions when Harbor does not reach the verifier phase.
archive-sessioncommand.Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
Sequence Diagram
sequenceDiagram participant H as Harbor trial participant S as Evaluation sidecar participant V as Admin volume participant A as Trial artifacts H->>S: Run agent/verifier lifecycle alt Verifier completes S->>V: Finalize and export session.tar.gz else Terminal outcome skips verifier H->>S: Run archive-session collect hook S->>V: Create session-rescue.tar.gz end H->>V: Collect configured archive V-->>A: Copy session-rescue.tar.gzReviews (2): Last reviewed commit: "test: assert Harbor itself files the res..." | Re-trigger Greptile