Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion vero/src/vero/evaluation/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,22 +193,39 @@ async def evaluate_admin(
split: str,
commit: str,
sample_ids: list[int] | None = None,
model: str | None = None,
) -> Experiment:
"""Admin/verifier evaluation: explicit ``task``, no budget, no allowlist.

Unlike :meth:`evaluate` (which is bound to ``default_task`` and metered),
this scores an arbitrary ``(task, dataset_id, split)`` — including held-out
tasks/splits the agent never had access to. Used by the verifier to score
the selected commit on its configured targets.

``model`` overrides the executor model for this one eval (rides
``task_params`` so the eval strategy can honor it; the Mode-B
HarborRunner does, the Mode-A vero-task path ignores it). Used for
transfer targets: scoring the champion under a model it was NOT
optimized on.
"""
params = self.run_constraints
if model is not None:
params = params.model_copy(
update={
"task_params": {
**(params.task_params or {}),
"harbor_model_override": model,
}
}
)
return await self.evaluator.evaluate(
commit=commit,
dataset_id=dataset_id,
split=split,
task=task,
sample_ids=sample_ids,
db=self.db,
evaluation_parameters=self.run_constraints,
evaluation_parameters=params,
)

def status(self) -> dict[tuple[str, str], SplitBudget]:
Expand Down
1 change: 1 addition & 0 deletions vero/src/vero/harbor/build/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ def _serve_config(
"split": t.split,
"reward_key": t.reward_key,
"sample_ids": t.sample_ids,
"model": t.model,
}
for t in config.targets
],
Expand Down
6 changes: 6 additions & 0 deletions vero/src/vero/harbor/build/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ class TargetSpec(BaseModel):
split: str
reward_key: str = "reward"
sample_ids: list[int] | None = None
# Executor-model override for this target (transfer probe; Mode B only):
# score the selected commit AND the baseline under a model it was not
# optimized on, so model-specific couplings (measured live: hardcoded
# temperature=0 crashing 72/72 on an executor that rejects it) surface at
# finalize instead of one substrate away.
model: str | None = None


class _BuildConfigBase(BaseModel):
Expand Down
38 changes: 38 additions & 0 deletions vero/src/vero/harbor/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,32 @@ class HarborConfig:
# without running anything). None keeps the current behavior: the
# candidate env supplies harbor, and is trusted to.
harbor_requirement: str | None = None
# Bounded within-eval retry for infra-destroyed samples. A sample whose
# EVERY attempt died of a transient infrastructure cause (connection,
# timeout, rate limit, 5xx) was never measured at all: re-run it after a
# backoff instead of booking the outage as a permanent error. Measured
# live: a 65-second host DNS blip killed 44 of 72 attempts of one eval
# with ConnectionError, and nothing in the record distinguished the blip
# from a bad candidate.
#
# OFF BY DEFAULT, and it must stay off when the candidate is an
# adversarial optimizer. The qualifying predicate is built from exception
# types raised inside candidate code, and agents are stochastic: a
# candidate that raises an allowlisted exception whenever an attempt is
# going badly loses nothing on partially-good samples (its fakes
# zero-fill like honest failures) but converts every all-bad sample from
# a booked 0.0 into a fresh re-roll. That is one-sided selection over
# attempt sets, exactly what the zero-fill invariant exists to prevent.
# Enable only for trusted-candidate evaluations (frozen agents,
# operator-run matrices), where re-measuring an outage is pure signal
# recovery. Candidate crashes and exhausted key budgets never retry
# regardless (a crash is a result; a spent key cannot recover by
# waiting), and recovered samples carry an ``infra_retry`` audit marker.
infra_retry_rounds: int = 0
# Backoff before retry round N is N times this many seconds. Transient
# infra needs time, not immediacy: instant retries burned 6 of 8 run
# attempts inside one live DNS blip.
infra_retry_delay_s: float = 30.0
extra_args: list[str] = field(default_factory=list) # passthrough harbor run flags

def __post_init__(self) -> None:
Expand All @@ -50,6 +76,18 @@ def __post_init__(self) -> None:
f"aggregate_attempts must be 'best' or 'mean', got "
f"{self.aggregate_attempts!r}"
)
if self.infra_retry_rounds < 0:
raise ValueError(
f"infra_retry_rounds must be >= 0, got {self.infra_retry_rounds}"
)
# A zero (or negative) delay silently nullifies the backoff, and an
# instant retry re-enters the same outage: 6 of 8 run attempts once
# burned inside a single live DNS blip for exactly this reason.
if self.infra_retry_rounds > 0 and self.infra_retry_delay_s <= 0:
raise ValueError(
f"infra_retry_delay_s must be > 0 when infra retries are "
f"enabled, got {self.infra_retry_delay_s}"
)

@property
def is_registry(self) -> bool:
Expand Down
Loading