fix(harbor): make baseline scoring at finalize durable and retried#27
Merged
varunursekar merged 3 commits intoJul 17, 2026
Merged
Conversation
A live trial silently skipped baseline scoring: the nested baseline eval failed
transiently, the failure was swallowed by a warn-and-continue except, and the only
record (a log line) died with the sidecar container at teardown. The admin volume
that baseline.json was written to does not survive teardown either, so there was no
durable evidence of whether the baseline was scored, skipped, or crashed.
Two changes:
- Retry the baseline eval (default 2 attempts) so a single transient nested-run
failure does not drop the regression check.
- finalize() now returns {"rewards": ..., "baseline": ...}; the baseline outcome
(scores, a skip reason, or an error) is surfaced in the finalize response. The CLI
writes only rewards to reward.json (the outer harness consumes its keys, unchanged)
and echoes the full payload to stdout, which is captured into the trial's stdout on
the host, the one channel that survives teardown. The CLI tolerates the old
bare-rewards shape for a mixed-version sidecar.
Baseline scoring still never fails the trial.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s it auto_best excludes base_commit from the candidate pool, so when every candidate regressed it still selected the least-bad one and shipped a regression (observed live: an opus optimizer on a weak haiku inner model produced only below-baseline candidates and finalize shipped one 0.10 below the baseline, even though the free baseline reference was available). Visibility alone did not prevent the harm; nothing acted on it. Add a selection floor: after the admin re-score picks the best candidate, admin- score the untouched base_commit on the selection split and revert to it when the best candidate does not strictly beat it (a statistical tie reverts too: if the optimizer cannot show an improvement, shipping the seed is the safe outcome). On by default, gated on a base_commit being set; costs one extra admin eval. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment on lines
+195
to
+199
| logger.exception( | ||
| "baseline scoring failed after %d attempt(s); reward.json is unaffected", | ||
| self._baseline_score_attempts, | ||
| exc_info=last_error, | ||
| ) |
There was a problem hiding this comment.
logger.exception(..., exc_info=last_error) is called outside an except block with an exception instance as exc_info. In Python 3.11 (the minimum required by pyproject.toml), the logging.LogRecord constructor treats any non-tuple exc_info value by falling through to sys.exc_info(), which returns (None, None, None) when there is no active exception context — so the traceback of last_error is silently dropped from the final log message. Exception-instance support for exc_info was only added in Python 3.12. Passing an explicit 3-tuple makes this work on all supported versions.
Suggested change
| logger.exception( | |
| "baseline scoring failed after %d attempt(s); reward.json is unaffected", | |
| self._baseline_score_attempts, | |
| exc_info=last_error, | |
| ) | |
| logger.error( | |
| "baseline scoring failed after %d attempt(s); reward.json is unaffected", | |
| self._baseline_score_attempts, | |
| exc_info=(type(last_error), last_error, last_error.__traceback__), | |
| ) |
Prompt To Fix With AI
This is a comment left during a code review.
Path: vero/src/vero/harbor/verifier.py
Line: 195-199
Comment:
`logger.exception(..., exc_info=last_error)` is called outside an `except` block with an exception instance as `exc_info`. In Python 3.11 (the minimum required by `pyproject.toml`), the `logging.LogRecord` constructor treats any non-tuple `exc_info` value by falling through to `sys.exc_info()`, which returns `(None, None, None)` when there is no active exception context — so the traceback of `last_error` is silently dropped from the final log message. Exception-instance support for `exc_info` was only added in Python 3.12. Passing an explicit 3-tuple makes this work on all supported versions.
```suggestion
logger.error(
"baseline scoring failed after %d attempt(s); reward.json is unaffected",
self._baseline_score_attempts,
exc_info=(type(last_error), last_error, last_error.__traceback__),
)
```
How can I resolve this? If you propose a fix, please make it concise.fix(harbor): auto_best reverts to the baseline when no candidate beats it
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.
Problem (observed live, then root-caused)
A trial silently skipped
score_baselineat finalize: reward.json landed ~2 minutes after the winner's validation eval with no baseline.json and no baseline ledger row, while a byte-identical config produced one in another run. A three-agent investigation traced the mechanism: the nested baseline eval failed transiently within seconds,_maybe_score_baseline's warn-and-continueexceptswallowed it, and the only record (alogger.exceptionto sidecar stderr) was destroyed with the container at teardown. The admin volume holding baseline.json does not survive teardown either. Two subsequent identical runs scored the baseline fine, confirming the failure is intermittent and environmental, not config- or state-dependent.Fix
baseline_score_attemptstotal attempts (default 2, wired throughServeConfig), so a single transient nested-run failure no longer drops the regression check.finalize()now returns{"rewards": ..., "baseline": ...}wherebaselineis{"scores": ...},{"skipped": reason}or{"error": ..., "error_type": ..., "attempts": N}. The CLI writes onlyrewardsto reward.json (the outer harness contract is unchanged) and echoes the full payload to stdout, which harbor captures into the trial'stest-stdout.txton the host: the one channel that survives teardown. The CLI tolerates the old bare-rewards response shape for a mixed-version sidecar.Baseline scoring still never fails the trial.
Tests
attempts: 2/finalizemock updated to the real wrapper shapeStacked on #25 (
harbor-2-free-baseline-eval). An adversarial review panel (4 lenses + verification) ran pre-open; all confirmed findings addressed.🤖 Generated with Claude Code
Greptile Summary
This PR fixes a silent baseline-scoring loss at finalize by adding retry logic and surfacing the outcome through a durable channel (stdout rather than the ephemeral admin volume). It also adds an
auto_best_baseline_floorguard that prevents shipping a candidate that fails to beat the baseline on the selection split.Verifier.finalize()now returns{\"rewards\": ..., \"baseline\": ...}. The CLI writes onlyrewardstoreward.json(harness contract unchanged) and echoes the full response to stdout, which survives container teardown. A plain-rewards response from older sidecars is handled via a back-compat fallback._maybe_score_baseline()retries up tobaseline_score_attemptstimes (default 2) and returns a structured{\"scores\"}/{\"error\"}/{\"skipped\"}dict instead of swallowing failures;baseline.jsonon the admin volume is demoted to a best-effort debug copy.auto_best_baseline_floor(defaultTrue) admin-scoresbase_commiton the selection split during_best_from_dband reverts to it if no candidate strictly improves over the baseline, closing the live regression observed with a weak inner model.Confidence Score: 5/5
Safe to merge — the harness contract (reward.json shape) is unchanged, failures in baseline scoring still never affect the trial reward, and the back-compat path for older sidecars is tested.
The changes are well-scoped: the new wrapper return type is isolated to Verifier/CLI, retry logic is simple and bounded, and the floor feature is gated on a default-on flag that no-ops safely when base_commit is absent. Test coverage is thorough — retry recovery, persistent failure, floor revert, tie-break, and floor-off cases are all exercised.
No files require special attention; verifier.py carries the most logic but is well-covered by the new test suite.
Important Files Changed
Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant CLI as finalize_cmd (cli.py) participant App as /finalize (app) participant V as Verifier.finalize() participant SC as _select_commit() participant BS as _maybe_score_baseline() participant E as EvaluationEngine CLI->>App: POST /finalize (Bearer token) App->>V: await finalize() V->>SC: await _select_commit() alt auto_best mode SC->>E: evaluate_admin (rescore shortlist) SC->>E: evaluate_admin (base_commit, floor check) [new] alt "best_score <= base_score" SC-->>V: return base_commit (reverted) else SC-->>V: return best_commit end else submit mode SC-->>V: return submitted commit end V->>E: evaluate_admin (winner, target splits) V->>BS: await _maybe_score_baseline(rewards) loop up to baseline_score_attempts times [new] BS->>E: evaluate_admin (base_commit, target splits) alt success BS-->>V: "{"scores": {...}, "attempts": N}" else transient failure Note over BS: log warning, retry end end alt all attempts failed BS-->>V: "{"error": ..., "error_type": ..., "attempts": N}" end V-->>App: "{"rewards": {...}, "baseline": {...}} [new shape]" App-->>CLI: HTTP 200 full response CLI->>CLI: extract resp["rewards"] to reward.json CLI->>CLI: echo full resp to stdout (durable channel)%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant CLI as finalize_cmd (cli.py) participant App as /finalize (app) participant V as Verifier.finalize() participant SC as _select_commit() participant BS as _maybe_score_baseline() participant E as EvaluationEngine CLI->>App: POST /finalize (Bearer token) App->>V: await finalize() V->>SC: await _select_commit() alt auto_best mode SC->>E: evaluate_admin (rescore shortlist) SC->>E: evaluate_admin (base_commit, floor check) [new] alt "best_score <= base_score" SC-->>V: return base_commit (reverted) else SC-->>V: return best_commit end else submit mode SC-->>V: return submitted commit end V->>E: evaluate_admin (winner, target splits) V->>BS: await _maybe_score_baseline(rewards) loop up to baseline_score_attempts times [new] BS->>E: evaluate_admin (base_commit, target splits) alt success BS-->>V: "{"scores": {...}, "attempts": N}" else transient failure Note over BS: log warning, retry end end alt all attempts failed BS-->>V: "{"error": ..., "error_type": ..., "attempts": N}" end V-->>App: "{"rewards": {...}, "baseline": {...}} [new shape]" App-->>CLI: HTTP 200 full response CLI->>CLI: extract resp["rewards"] to reward.json CLI->>CLI: echo full resp to stdout (durable channel)Reviews (2): Last reviewed commit: "Merge pull request #28 from scaleapi/har..." | Re-trigger Greptile