Never give a run a second chance at its own outcome - #529
Merged
Conversation
Once a model has answered, that attempt is the observation. Re-running it and keeping the better result is selection on the outcome, and in code it does not look like cheating -- it looks like a retry. So the distinction is structural rather than remembered: a durable marker is written the moment a first model turn is observed, and every path afterwards is checked against it in both directions. An adapter that reports a pre-turn failure after the marker exists is an integrity error, and so is a post-turn result that arrives without one. An adapter that throws is treated as interrupted rather than retryable, because a process can produce a turn before its host reports one, and the safe reading of "I do not know" is that it did. The tree is the observation too. An evaluator retry runs against the frozen tree bound by object id, never against the workspace, so an evaluator flake cannot become a second chance at a differently derived candidate. Resume computes the missing set from the randomization manifest and what is durably on disk, and refuses to write over an id that already has a row. A resume that re-runs a completed id silently replaces a recorded observation, which is the same defect as the rerun, arriving through the door marked recovery. Storage is written for the kill that lands at the worst moment. A row, an attempt and a final tree each land completely or not at all; a partial that was fsynced but never renamed is cleaned before resume rather than read, and a tree archive without its metadata is refused by the verifier instead of counting as a final tree. A half-written artifact that verifies is worse than a missing one, because only one of them is visible. The progress surface carries lifecycle and counts, and is shaped so it cannot carry anything else -- whoever watches a run must not learn which arm is winning while the run can still be stopped. A shape that cannot express the value beats a rule that says not to print it. Limit: the state machine cannot distinguish an agent that produced no model turn from one whose host failed to report the turn it produced, so it treats both as interrupted and neither as retryable -- a real pre-turn infrastructure failure is therefore sometimes charged as a lost run Limit: atomicity rests on rename and fsync semantics of the underlying filesystem; a filesystem that reorders them can still present a partial the recovery pass reads as whole Ruled-out: retrying an agent after a post-turn infrastructure failure | keeping whichever attempt survived selects on the outcome, and the surviving attempt is not a random draw Ruled-out: re-deriving the candidate tree for an evaluator retry | it converts an evaluator flake into a second candidate, which is a different experiment Warn: the analyzer reads only row files the freeze names, so the freeze must publish the full set of canonical row paths and this orchestrator must write rows at exactly those paths; the two are wired together by the freeze and nothing checks the pairing yet Blast: module Undo: easy Certainty: firm Verified: ninety-one cases pass across the orchestrator, verifier, isolation and ledger suites, covering opaque randomization, refusal to rerun an agent after its first turn, an evaluator retry on the same frozen tree, resume launching only missing ids, a cleaned fsynced-but-unrenamed partial finishing without a second agent or evaluation, a kill between tree archive and metadata refused as incomplete, and a progress surface with exactly five lifecycle fields; package and bench typechecks clean; two builds leave dist unchanged; both bench verifiers pass Unverified: no live study ran, so every agent and evaluator here is a recorded adapter rather than a container Provenance: authored Record-Id: r-cdeb07or
CommitLore — record lintTrailers: clean — 1 commit in Active constraints for the paths this PR touchesLimits (9)
Ruled out (18)
Warnings (9)
Trailer violations fail this check. Active constraints are informational — they are what the repository already decided, not a verdict on this PR. |
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.
CDEB-07. Depends on #523, #524, #521 (all merged). Pairs with #528 (CDEB-08).
The retry that is not a retry
Once a model has answered, that attempt is the observation. Re-running it and keeping the better result is selection on the outcome — and in code it does not look like cheating, it looks like a retry. That is why this cannot be a rule someone remembers.
A durable marker is written the moment a first model turn is observed, and every path afterwards is checked against it in both directions:
That last one is deliberate. A process can produce a turn before its host reports one, so the safe reading of "I do not know" is that it did. This costs us real pre-turn infrastructure failures, charged as lost runs. That trade is stated in the
Limit:.The tree is the observation too
An evaluator retry runs against the frozen tree bound by object id — never the workspace. Otherwise an evaluator flake becomes a second chance at a differently derived candidate, which is a different experiment wearing the same logical run id.
Resume is the same defect wearing a recovery badge
Resume computes the missing set from the randomization manifest and what is durably on disk, and refuses to write over an id that already has a row.
Written for the kill at the worst moment
A row, an attempt and a final tree each land completely or not at all. A partial that was fsynced but never renamed is cleaned before resume rather than read. A tree archive without its metadata is now refused by the verifier instead of counting as a final tree.
A half-written artifact that verifies is worse than a missing one, because only one of the two is visible.
Progress cannot leak an outcome
Whoever watches a run must not learn which arm is winning while the run can still be stopped. The surface carries five lifecycle and count fields, is frozen, and is shaped so it cannot carry anything else. A shape that cannot express the value beats a rule saying not to print it.
One wiring gap, stated not hidden
Warn:#528's analyzer reads only row files the freeze names. So the freeze must publish the full set of canonical row paths, and this orchestrator must write rows at exactly those paths. The two are wired together through the freeze and nothing checks the pairing yet — that closes once both land.91 cases pass across the orchestrator, verifier, isolation and ledger suites; both typechecks clean; two builds leave
distunchanged; both verifiers pass. No live study ran, so every agent and evaluator here is a recorded adapter rather than a container.