feat: [no-ticket] harbor - infra resilience (dead-attempt classification, opt-in outage retry, key-budget alarm)#39
Open
shehabyasser-scale wants to merge 2 commits into
Conversation
…utage retry, key-budget alarm Three infra failure modes measured live in the E5 matrix runs, fixed at the measurement layer: - Dead attempts are classified infra vs candidate (conservative exception-type allowlist + the litellm key-budget message signature). Labels flow into dead_exception_types, error strings, and a new n_dead_infra metric. Classification never moves a score: every dead attempt still zero-fills, or faking infra would excuse failures. Exception type names are candidate-authored, so brackets are neutralized before labeling (a class named 'XError[infra]' cannot walk in pre-suffixed). - An OPT-IN, bounded, backoff-spaced within-eval retry re-measures samples whose every attempt died of a transient infra cause (the 65-second DNS blip that killed 44/72 attempts of one eval). Off by default: against an adversarial optimizer the qualifying predicate is a re-roll lever, since a stochastic candidate that raises allowlisted exceptions on failing attempts converts all-bad rounds into fresh draws. Retry rounds run in fresh sibling jobs dirs (nesting would pool dead attempts into later resumed means) and recovered samples carry an infra_retry audit marker naming the discarded attempts. - An ERROR-level alarm names key-budget exhaustion (a spent key fails every later call identically; two matrix cells of budget-exceeded zeros were nearly booked as a portability finding), with hedged wording since the signature reads candidate-process exceptions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d audit history - HarborConfig rejects infra_retry_delay_s <= 0 when retries are enabled (a zero delay silently nullified the backoff) and negative infra_retry_rounds. - The infra_retry audit marker now lists EVERY discarded round in order (discarded_rounds), not just the one immediately before recovery; recovered_round names when the sample finally measured. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Stacked on #38. Fixes the measurement layer for the three infrastructure failure modes that hit the live transfer-matrix runs. The guiding rule throughout: infra classification may inform diagnostics, alarms, and (opt-in) retries, but it must never move a booked score, because every input to the classifier is ultimately raised inside candidate code.
The three live incidents
ConnectionError, and with no pause between attempts the whole burst fit inside the blip. Nothing in the record distinguished the blip from a bad candidate; the cell had to be diagnosed by hand from attempt timestamps.BadRequestErrorwith "Budget has been exceeded" in the message, so two cells of budget-exceeded zeros were nearly booked as a portability finding.What this adds
ConnectionError, timeouts,RateLimitError, 5xx, ...) marks transient infra asType[infra]; the key-budget message signature marksBadRequestError[infra:llm-key-budget](persistent infra: waiting cannot refill a key). Anything unlisted counts against the candidate. Newn_dead_inframetric alongsiden_dead; the no-rewards error path now also persists its structureddead_exception_types. Zero-fill is unchanged in every path.infra_retry_rounds, default 0) for samples whose every attempt died of a transient infra cause: bounded rounds, linear backoff, fresh sibling jobs dir per round, and recovered samples carry aninfra_retryaudit marker naming the discarded dead attempts.Why the retry is off by default
An adversarial-review pass on this diff (3 lenses, findings adversarially verified) established that with retries on, the qualifying predicate is a re-roll lever: agents are stochastic, and a candidate that raises an allowlisted builtin exception whenever an attempt is going badly loses nothing on partially-good samples (its fakes zero-fill exactly like honest failures) but converts every all-bad sample from a booked 0.0 into a fresh draw (worked example: per-attempt pass rate 0.3, one retry round lifts the expected booked score to 0.51). "A retry re-measures the same code" is only a defense for deterministic candidates. So the mechanism ships default-off, documented as trusted-candidate-only (frozen agents, operator-run matrices), which is exactly the context of the incidents it fixes.
The same review drove: sibling round dirs (nested dirs let a later resume rglob pool a discarded round's dead attempts into a fresh mean; reproduced booking 0.333 where 1.0 was correct), globally fresh dir ordinals (no stale-trial leakage across eval calls of one result dir), bracket neutralization in labels (a candidate exception class literally named
XError[infra]must not walk in pre-suffixed), the audit marker (a discarded round must stay visible in the durable record), and two mutation-killing tests (all->anyin the retry gate and linear -> constant backoff both now fail the suite; both previously survived it).Config
Both knobs (
infra_retry_rounds,infra_retry_delay_s) are plainHarborConfigfields and ride the existingharbor:passthrough in serve configs; no schema plumbing needed.Upstream note: the attempt-burst problem (no inter-attempt spacing inside one
harbor run) belongs to harbor itself; this layer classifies and, where trusted, re-measures.Tests
tests/test_harbor_runner.py: 11-testTestInfraResilienceclass covering label classification and zero-fill invariance, forged-suffix neutralization, key-budget labeling + alarm, default-off, single- and multi-round retries (backoff sequence, per-round task narrowing, sibling dir names), mixed-cause and partially-scored non-retry, persistent-outage error preservation, mean non-dilution, and the audit marker. Full harbor-family + engine suite: 203 passed, 1 skipped.🤖 Generated with Claude Code
Greptile Summary
This PR adds infrastructure-failure resilience to the harbor evaluation runner: dead-attempt classification (transient vs. persistent infra vs. candidate-caused), an opt-in bounded retry for samples whose every attempt was killed by transient infra, and an ERROR-level alarm for the LLM key-budget exhaustion signature. All three address live incidents described in the PR.
_dead_attempt_label,_INFRA_EXCEPTION_TYPES): bracket-neutralizes exception type names to prevent suffix forgery, addsn_dead_inframetric alongside existingn_dead, and persists structureddead_exception_typeson the no-rewards error path so the retry gate can operate on structured data rather than string parsing._retry_transient_infra,infra_retry_rounds=0): runs only on samples whose every attempt carries a transient-infra label, in a fresh sibling jobs dir per round with linear backoff, accumulating a full-historydiscard_historyacross rounds and stamping aninfra_retryaudit marker on recovery._alarm_key_budget): fires ERROR-level on theBadRequestError[infra:llm-key-budget]suffix with hedged wording; never triggers a retry since waiting cannot refill a key.Confidence Score: 5/5
Safe to merge; the retry path is off by default, zero-fill invariant is unchanged across all code paths, and prior P1 validation findings are addressed.
The classification and alarm paths are additive and do not touch score computation. The retry path is disabled by default and guarded by a conservative allowlist. Both previously flagged issues are addressed in this revision. The one remaining finding only affects the audit marker field, not scores or retry eligibility, and requires an edge-case total runner crash to trigger.
The _retry_transient_infra method in runner.py is the most complex new path and the only one with a non-trivial edge case around discard_history accumulation when a round produces no output.
Important Files Changed
Comments Outside Diff (1)
vero/src/vero/harbor/config.py, line 71-78 (link)infra_retry_delay_smakes every retry round fire withdelay = 0, silently disabling the linear backoff. The PR's own incident data shows that immediate retries burned 6 of 8 attempts inside a single DNS blip. Mirroring the existing validation foraggregate_attemptsprevents a misconfigured value from nullifying the protection.Prompt To Fix With AI
Reviews (2): Last reviewed commit: "fix(harbor): review follow-ups: validate..." | Re-trigger Greptile