Repository navigation
Conversation
… ceiling scripts/confirmatory_run.py runs the frozen pre-registration's run procedure on the treatment harness (6b00a6f's tool_pilot_argon.py, unchanged) and writes the run records the confirmatory report checks: - two lanes per model, households alternating in draw order, each household's arms in its drawn order, then reruns in passes; - one ToolPool per model with no features of its own, each call framed with its arm's features; - admission against each model's cap and reserve, waiting while a pass's reruns are undecided; - the hard ceiling over every model's spend, through the harness's Spend; - the failure classes and actions, with operator pauses, harness and inconclusive stops, and replaced attempts saved before their reruns; - the 30-second host log and the guard's and power log's lines. Tests follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Unit tests for the streams, arm strings and features, the classes and actions, the host evidence and the ceiling. Fake runs (a fake provider, pool and host log, on a fake board, with the windows scaled down) plant reruns, host trouble, an operator pause, a harness stop, the inconclusive stop, a refusal, the hard ceiling, a raising attempt, pool restarts and an interrupt. run_problems recomputes every rule of the run procedure from the files the runner wrote, and a property test checks it over random failures and spend. Two tests run the harness's own conversation under the runner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A model whose lanes never decide, because the ceiling was crossed before its first admission, wrote no admission log, and the confirmatory report reads a missing log as a lost one. The runner now creates each model's run and admission logs when it writes the model's manifest. Found by the differential test in the study's scripts repo, which runs this runner and checks its records with the report. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
force-pushed
the
v2-confirmatory-runner
branch
from
October 25, 2026 08:11
7d8eaec to
aeff9a9
Compare
…cle fixes The wall clock steps back by up to about 0.1 ms between threads on the run's host (40 backward steps in 190 million readings over 12 threads, the largest 91 us). The run records order attempts, stops, admission decisions and the ceiling's crossing by their times, and the confirmatory report compares them exactly: an attempt's end and the next start in its lane can be microseconds apart, so a step back could make a valid run fail the report. `clock()` gives time.time(), never earlier than a time it gave before, and each of those times is taken under the lock that orders it. A test reads the wall clock up to 0.2 ms early everywhere; without `clock()` it fails every time (attempts overlap, a household runs before its admission). The test oracle had three faults, two of them exposed by the full suite: - A household a stop cut short was taken to finish at its last attempt. The runner counts it under way until the stop, as the report now does. A new test makes the case deterministic (it failed the old oracle with the flake's message, spend 0.02 vs 0.03). - The scaled host-log gap was 90 ms, and under a load of 12 the sampler stalled past it, so an untouched run got host reruns. The gap is 0.5 s in the tests, and the stall tests stall 0.8 s. - The oracle allows the harness's raw request times a second outside their attempt, as the report does. 121 tests pass, 8 runs of the full suite in a row at a load of 12 to 146. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
force-pushed
the
v2-confirmatory-runner
branch
from
October 25, 2026 08:17
aeff9a9 to
808ccc8
Compare
… chains and anchors Answers runner-r1 (R1-R8) and the runner side of the pre-registration and report reviews: - A failed read of the guard's or the power log is tried five times; one that still fails is a runner error, which stops every model's lanes after settling and writing what had ended. A failed memory-pressure reading is no sample. - An attempt still running at an hour is abandoned: a harness-error record (AttemptDeadline) with its spend so far, a stop of cause deadline, and its later spend in late-cost events. An attempt that raises keeps the spend it counted. - Admission takes its time and checks the ceiling under the ceiling's lock. - A resume is accepted only if written after its pause began. - The signal handler only notes the signal; the main loop stops the lanes. - A sampler whose samples fail for five minutes, or whose thread ends, stops every model. - --workers must equal the design's. - Every stop names its cause. The error classes add the litellm subclasses the harness groups by isinstance, explain traces count as calls, and a Timeout on the escalated request after a truncated turn is class model. - Run, admission and host logs are hash chains; anchors.jsonl records every log's head every 15 minutes and at the end, and the design's anchor command (confirmatory_anchor.py) commits and pushes it. - In-flight records are fsynced before their rename. 189 tests pass (tests/test_confirmatory_run.py, tests/test_confirmatory_anchor.py). 19 mutants of the new rules each fail a named test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The docstring and EXCERPT_LEAD's comment said "the booking"; the code keeps the guard's and power log's lines from a day before the host log's start. No behaviour changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test sent its interrupt on a 0.15 s timer, and at load 49 the run had not started the household's first attempt by then, so no attempt was cut short and the test failed. The interrupt now arrives from inside that first attempt, which finishes only once the main loop has stopped the lanes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Answers the first part of review runner-r2: - The ledger can close. Ceiling.charge counts spend with the lock held and, once close() has run, counts nothing more and keeps the amount apart as unrecorded_usd. AttemptSpend now changes its totals under the ceiling's lock, so the attempt's totals and the ceiling's always agree. - The sampler's failure timer starts on every caught sampling exception (finding 3), so a reader that keeps raising stops the run after the silence limit instead of never. - Guard-log lines carry no offset, so guard_times reads each as both readings of a repeated or skipped local hour (New York repeats 01:00-02:00 on 2026-11-01). A pause opens at its earliest reading and closes at its latest; excerpts keep a line if either reading falls in the window. - ANCHOR_RETRY and ANCHOR_PUSH_TIMEOUT are defined for the anchor changes that follow; LATE_JOIN becomes one overall deadline. The suite has not run on this commit: the next commit runs it and finishes the round (stop_all, end_attempt, runner errors, startup, finish(), the resume race and the anchor push). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Answers review runner-r2 and finishes f210b6e: - Set and notify every model's stop state before event I/O. Retain ended attempts before pause/stop writes; recover their classes and final records best-effort before runner-error stops, and block new starts during recovery. - Finish against one abandoned-request join deadline. Close pools and host before the ledger, then record late costs, crossings and interrupts before the final anchor. Snapshot unresolved requests under the ledger lock and record their unknown future spend explicitly as a runner error. - Confirm persistent sampler exceptions stop the run and a good sample clears the failure timer. Validate a resume against the current pause under the condition that clears it. - Poll pool startup with supervision and process pending stops before every lane launch. Record startup/restart spans for the report's idle calculation; drain supervisors on runner errors before closing their host and ledger. - Kill the whole anchor command process group at ANCHOR_PUSH_TIMEOUT. Retry failed pushes using ANCHOR_RETRY and sync each outcome, anchor timestamp and push interval. Surface push-log I/O failures and retain final local coverage. - Exercise disk-error recovery, late cleanup spend, unresolved requests, persistent sampler errors, the resume race, startup signals and slow pools, failed pushes and real child/grandchild timeout termination. The report must subtract validated setup spans from lane idle time and compute remote exposure from successful pushed snapshots. Local cadence alone cannot support the draft's claimed fifteen-minute maximum; neither report nor draft is changed here. Co-Authored-By: GPT-6.1 Sol (Subfleet) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Answers review runner-r3 findings 2, 4, 5 and 6, and orders push times: - Push a synced, atomically installed snapshot of the anchors file. Give each push its own path, including after a thread is abandoned, and keep the remote record named anchors.jsonl. - Try the final push up to three times per set of heads, a minute apart, writing a new anchor for each try without changing the covered logs. - Bound pipe draining after process-group termination and every push join. Record a stuck thread once as an error and reject its late outcome. - Ignore signals before the last cleanup check, then log and anchor a first signal that arrived in the gap. Keep first and second signal handling active throughout retries. - Take anchor cadence and push intervals from the runner's ordered clock. Exercise overlapping pushes, retry exhaustion, escaped descendants, abandoned threads, cleanup signals and a backward wall-clock step. The report's record fields and outcome names are unchanged. Push snapshots are copies, not records; the draft should distinguish final retry exhaustion from the ongoing run's retry cadence, and retries after newly logged signals. Co-Authored-By: GPT-6.1 Sol (Subfleet) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Answers review runner-r4 finding 1 and tests the remaining clock and retry spacing requirements: - Hold an exclusive, non-blocking lock in the anchor clone's .git directory through the push. Refuse busy helpers and snapshots that do not extend the clone's anchors byte for byte; allow equality and never force-push. Leave a stale index.lock to fail normally. - Keep worker retirement and the next push, but say the retired command may still be running. A helper holding the lock makes later pushes fail as busy; final anchors left local are a deviation. - Exercise older helpers after newer pushes, shared-clone retirement, lock contention, interior edits, equality and stale git locks against a temporary clone and local bare remote. - Test anchor_due across a backward wall-clock step and measure each final retry from the preceding try's end, with a controlled clock. - Clarify that final retries wait a minute after a failed try ends. First and second signal handling stays active only until cleanup ignores signals before its last check; signals during re-anchoring after that check are ignored. State that write/fsync under _pushing can delay supervision and retirement beyond the thread join bound. The report's record fields and outcome names are unchanged. Retired-worker output now identifies the surviving-command risk; helper refusals are failed pushes. The clone's persistent lock file is auxiliary, outside run records. Co-Authored-By: GPT-6.1 Sol (Subfleet) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Answers review runner-r5 finding 1 and fills the retry and conversation coverage gaps: - Pass the clone's flock descriptor to every Git command. A surviving Git writer, hook or remote helper retains the lock after helper-only death; later helpers still refuse busy clones and non-extending snapshots. Disable fsmonitor startup, whose daemon closes inherited descriptors. Keep ordinary, non-force pushes and leave Git's own index.lock alone. - Kill the real helper after Git has entered pre-commit, pre-push and local pre-receive hooks. Check busy refusals, inherited hook descriptors and unchanged clone file, index, HEAD and remote, then newer pushes and every remote commit's byte-prefix extension. Test that configured fsmonitor is never started. - Pin the registered 900-second anchor interval and 60-second retry wait independently of production constants, citing draft line 218. - Compare fake OpenAI Responses and Gemini JSON conversations against the unchanged treatment harness, including complete requests and calculator inputs. Exercise both through the runner's lanes. Use the existing Gemini 3.1 JSON profile; both confirmatory Geminis retain their tool contracts. - Describe surviving Git lock holders and advisory/custom-command limits. The report's record fields and outcome names are unchanged. Busy refusal output now identifies a helper or Git process as the holder. The existing clone lock remains auxiliary, outside run records. Co-Authored-By: GPT-6.1 Sol (Subfleet) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Answers review runner-r6's two P3 items: - Narrow the fsmonitor claim to disabling normal index-triggered startup. A configured hook can explicitly start the descriptor-closing daemon; configured background clone writers must retain the locked descriptor without unlocking it. The flock is local, while an external remote's safety rests on the byte-prefix check and ordinary non-force pushes. - Describe credential-cache and other descriptor holders keeping later helpers busy for their lifetimes, including the default 900-second cache. Every try while the lock is retained, including all three final tries, fails as busy and is reported as a failed push. - Block real Git clean filters during add and status, kill only the helper, and check later busy refusals leave file, raw index, staged contents, HEAD and remote history unchanged. Release the writer and verify newer snapshots push with prefix-preserving history. - Double-fork a hook descendant into a new session. Let its hook and helper finish, check the detached holder still prevents later pushes, and verify pushes succeed after it exits. Check each new regression against a temporary helper with inheritance removed only from its target command. Production behavior, report fields and outcomes are unchanged. This adds no dependencies and leaves the report implementation and draft untouched. Co-Authored-By: GPT-6.1 Sol (Subfleet) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run each selected model in its own thread, one drawn household at a time, using the pinned harness's none profile and a tool callback that raises. Reuse the calculator runner's classification and action rules without host evidence. Rerun eligible provider and unanswered attempts immediately. Pause account failures until re-invocation, stop a model on a harness class, and stop every model on a budget stop. Limit reruns to two while retaining the action helper's harness and ceiling stop precedence. Move each replaced final's exact bytes into consecutively numbered attempt files before rerunning. Atomically write finals with complete ordered number, path, hash, status, class and reason links. Recover an interrupted archive from the files alone. Refuse native prior_attempts, missing attempts lists, numbering gaps, altered history links and ineligible archives. Carry every model's final and archived spend across invocations, including optional model folders. Share the five-dollar cap across all model threads, check it before each attempt, and retain an exception's spend in the harness fallback record. Show each attempt, model state and cumulative spend; dry runs show standing and owed households. Return success only when every selected model has a standing final for every drawn household. Cover synthetic providers, retry exhaustion, operator resume, harness and budget stops, interrupted archives, corrupted histories, exception spend, optional models and agreement with the separate report's registered cap. The existing calculator runner remains unchanged. Co-Authored-By: GPT-6.1 Sol (Subfleet) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two host-log tests slept a fixed time and expected the 0.02 s sampler to have run in it. On a loaded host it did not: test_the_host_log_samples_and_closes got three samples where it expects four (09:26Z run at 047c868), and test_pressure_near_an_attempt_is_host_trouble_once_settled found no sample during its 0.05 s of pressure (09:39Z run). Neither failure was a fault in the runner. A helper, wait_for_samples, now polls the log's complete lines for up to 5 seconds until they hold what each test needs: four samples, or one sample under pressure. The assertions are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Install and restore run-scoped SIGINT and SIGTERM handlers. The first signal lets active attempts save, prevents further archives and attempts, and returns a nonzero interrupted state. A second signal warns that the active attempt and its spend are lost before immediate exit. Hold an exclusive nonblocking stage-root lock through initialization, history scanning and execution, including dry runs. Require the baseline exclusions file to exist and match its declared sha256 before startup. Provide a board-free stage validator that requires every cheap-model drawn final to stand and rejects invalid histories in every model folder. Exercise archive household identity, rerun eligibility, third-attempt harness stops, both blocked dry-run classes, and the real harness turn check after another model crosses the shared cap. Regression evidence records seven expected failures before these fixes and eleven passing focused cases afterwards; the expanded no-tools file passes all 33 tests. Co-Authored-By: GPT-6.1 Sol (Subfleet) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Calculator preflight calls the no-tools history validator and refuses an unfinished stage. Every drawn household must have a standing final for every required cheap model, and every saved history must pass the writer rules. Synthetic completed no-tools histories keep existing preflight tests meaningful. Require the calculator and no-tools runs roots to be disjoint after resolving their paths. Equal roots and either direction of containment are refused. Changes to the calculator runner are confined to preflight. All seven new preflight regressions failed before the change. The complete calculator test file passes 230 tests after the change, including those regressions. Co-Authored-By: GPT-6.1 Sol (Subfleet) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Archive moves and attempt admissions share a short lock with the first signal handler, so every admission after that handler returns is refused. Providers run outside the admission lock, and admitted attempts finish and save. Exceptional shutdown interrupts and joins every started worker before restoring signal handlers or releasing the invocation lock. A second signal writes a nonblocking stderr message and exits immediately without taking shared locks; every active unsaved attempt and its spend are lost, while saved finals and archives survive. Co-Authored-By: GPT-6.1 Sol (Subfleet) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared history reader classifies every saved final, including optional models and households outside the draw, before initialization and calculator preflight accept its history. Preflight then requires every drawn cheap-model final to stand. Startup validates the hash-bound baseline as an object containing an exclusions list, with unique household and output cells identified by nonempty strings, before any provider call. The empty baseline fixture uses the report schema. Co-Authored-By: GPT-6.1 Sol (Subfleet) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exceptional cleanup sets interruption directly before joining every started worker. Keeping the main thread outside the admission lock lets a first signal safely acquire that lock if it interrupts cleanup. The invocation lock remains held until every worker finishes and saves. Co-Authored-By: GPT-6.1 Sol (Subfleet) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Record signals and wake a pipe watcher with a handler that takes no lock and raises nothing. The watcher sets interruption under admission and announces admitted work after releasing the lock. Archive moves and attempts commit under the same lock before interruption; admitted attempts finish and save outside it. Track worker began and done events independently of Thread identity and join bookkeeping. Escaping exceptions retain handlers and the invocation lock through completion, allow ten seconds for uncertain startup, tolerate repeated wait exceptions and preserve the original exception. Late targets return before file access. Apply completion tracking and safe late entry to the watcher as well. A caught model exception marks that model error and leaves global interruption unset. A second signal exits with 128 plus its number and preserves saved finals, archives and their recorded spend. Every unsaved attempt then under way is lost with its spend. A signal before handler restoration requires graceful exit 1 even when all workers completed. Run signal and cleanup regressions in bounded subprocesses, synchronize through events and use a thirty-second forced-exit deadline. Co-Authored-By: GPT-6.1 Sol (Subfleet) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pipe Until the handlers are restored, a signal still writes to the watcher pipe; closing it first could let that write land in a file that reuses the descriptor. Note that an exception another handler raises before the exceptional wait begins cannot be shielded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The admission probe's control thread sent the second SIGINT and then at once raised "forced exit returned". The handler runs on the main thread, so on a loaded host the control thread could reach its own os._exit(99) first; one of three suite runs at 6597adb failed that way, with the forced message printed after the control thread's traceback. The thread now sleeps 20 s, inside the probe's 30 s execution deadline, before it reports a forced exit that never came. Test only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exercise real signals after descriptor reuse and watcher shutdown. Require both handlers to restore before either descriptor closes. Pin late first SIGINT to exit 1 with completed finals, an unset interrupt and no announcement. Exercise watcher start failures before and after native startup and target entry beyond startup grace. Require the original start error to propagate, both handlers and descriptors to be released, and delayed targets to avoid closed descriptors and finish. Co-Authored-By: GPT-6.1 Sol (Subfleet) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Describe the handler recording a signal and waking the watcher, with allocation failure as its exception limit. Describe the watcher setting interruption while running and a first signal recorded after shutdown forcing exit 1 without setting interruption or announcing it. State that guarded waits retry exceptions raised within them. Explain that another handler raising in an unguarded instruction before a guarded call, in cleanup bookkeeping or in finally can replace the original exception and skip waits. Keep executable behavior unchanged. Co-Authored-By: GPT-6.1 Sol (Subfleet) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Catch recursion failures while reading saved finals, archived attempts, baseline exclusions and the CLI design so deeply nested JSON becomes a refusal naming the file. Include the baseline path in its validation errors, and require the CLI design to be a JSON object before stage initialization. Valid stage decisions stay the same. Saved records and baseline exclusions must refuse decoder recursion failures with a path-bearing StateError. The CLI must refuse unreadable, undecodable and non-object designs with their path and exit 1 before initializing a stage. Invalid UTF-8 must remain a decoding refusal. Synthetic regressions exercise every reader in a subprocess with a timeout and require the path before provider calls; final and archive cases also check preflight. Co-Authored-By: GPT-6.1 Sol (Subfleet) <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch was successfully deployed
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 #240 (
v2-microcosm-batches). The calculator stage is reviewed (rounds 6 and 7 agreed at 9b30a77 and 4002f8e). Commit 047c868 adds the no-tools stage's script. Rounds 8 to 10 asked for changes, made in 08d7b18 and 5a9735b, then in c60086b, 248e777 and 4ce7f8e, and then in 2f795ea, 6597adb and 9acc920, which round 11 reviewed. Round 11 asked for four P3 changes (watcher-startup and descriptor-order tests, three wordings), made in a1114fe (tests only) and 89dd946 (docstrings and comments only), which round 12 reviews. The PR stays a draft until the pre-registration it implements is frozen.scripts/confirmatory_run.pyruns the v2 calculator-interface study's confirmatory calculator stage as its draft pre-registration specifies, and writes the run records the confirmatory report checks before it scores anything. It runs the treatment harness unchanged:tool_pilot_argon.pyandpolicybenchhere are 6b00a6f's, and the runner refuses to start if they differ.Nothing here runs a model. The confirmatory run waits on the frozen pre-registration and Max's go.
What it does
order/<model>), then its reruns in passes, and only then decides its next household. The six models run at once.Spend. When spend crosses it, no attempt starts, conversations under way endbudget_stop, and every model's log gets aceilingevent. Admission checks the ceiling and takes its time under the ceiling's lock, so no household is decided after the crossing.AttemptDeadline, with the spend so far, and the model's lanes stop with causedeadline. The conversation endsbudget_stopat its next turn; its spend after the deadline still counts toward the ceiling and goes to the run log aslate-costevents. An attempt whose conversation raises keeps the spend it counted.isinstance, explain traces count as calls, and aTimeouton the escalated request after a truncated turn is class model. An operator failure pauses the model's lanes untilconfirmatory_run.py resume, accepted only if written after the pause began. A harness failure stops them, and so does one inconclusive error that ends runs in more than five households. Replaced attempts are saved, and in-flight records fsynced, before their reruns.harness,inconclusive,deadline,pool,runner-error,interruptorsampler(ceiling events have causeceiling). An exception in the runner itself stops every model's lanes after settling and writing what had ended. The signal handler only notes the signal, and the main loop stops the lanes.prev, the sha256 of the line before. Every 15 minutes and at the end,anchors.jsonlgets every log's line count and head and every manifest's sha256, and the design'sanchor_commandruns on its own snapshot of the file (so no push sends a line half written) in the background.scripts/confirmatory_anchor.pyis that command: it commits the snapshot to a clone and pushes it without forcing, so a remote outside the run holds each anchor's time. It holds an exclusive lock in the clone and passes the locked descriptor to every Git command it runs, so a helper, hook or remote helper that outlives a push makes later pushes fail as busy, never writing beside them. It refuses a snapshot that does not extend the clone's file byte for byte. A push still running after 5 minutes is killed with its process group; the final heads are pushed up to three times, a minute apart. Each push's anchor, times, return code and outcome go toanchor-pushes.jsonl.late-unresolvedwith the spend it had counted. After its last check the runner ignores signals, checks once more, and anchors again if a first interrupt arrived in between.--workersmust equal the design's engine workers per model.clock()returnstime.time(), never earlier than a time it returned before, and each such time is taken under the lock that orders it. The harness's request times stay raw. The report allows them 0.1 s of slack, widened by the largest backward step the host log shows.The no-tools stage
scripts/confirmatory_no_tools.py(047c868) runs the stage before the calculator stage: the three cheap models answer every drawn household without tools, and their answers only flag references for audit. It calls the harness'srun_householddirectly with each model'snoneprofile and a tool function that raises, so the harness's own resume, which folds earlier attempts into their status and totals, is never used.classifyandaction, unchanged, with no host evidence. A provider or none attempt reruns at once; an operator attempt pauses its model until the script is run again; a harness attempt stops its model; a ceiling attempt stops every model. At most two reruns.households/attempts/<household>-<n>.json, and the new final lists every replaced attempt (number, file, sha256, status, class, reason). The script resumes from these files alone, refusesprior_attempts, numbering gaps, links that disagree with the files and archives no rule reruns, and resumes an archive left without a final as the next attempt.baseline_exclusions({path, sha256}): the board's exclusions as they stood before the stage's first call. Before any provider call, the script refuses to start unless that file hashes as declared and has the form the report reads: an object whoseexclusionslist holds one object per cell, with nonemptyscenario_idandvariablestrings and no cell twice ({"exclusions": []}when empty).flockon.confirmatory-no-tools.lockin the stage's runs folder, held for the whole invocation, dry runs included. A second invocation refuses.execute, a thread that fails to start included, sets the interrupt under the admission lock and waits for every started thread before the handlers are restored and the invocation lock is released. Each thread's start and finish are tracked with the script's own events, a wait cut short by another exception is resumed, and a thread whosestart()raised gets 10 seconds to begin; one that begins later returns before touching a file. The watcher thread is tracked the same way. The handlers are restored before the watcher's pipe is closed. The original exception propagates. An exception caught in a model's thread marks that modelerrorand does not set the interrupt; the other models go on, and the exit is nonzero.--dry-runlists the standing households and the next attempt owed, with no provider call.The calculator runner changes only in its
preflight(5a9735b). It callsstage_problems, a file-only check that loads no board and calls no provider. It applies the script's own history checks, which classify every saved final (since 248e777, an optional model's and one outside the draw too), and refuses to start while any no-tools history fails them or any cheap model lacks a standing final for a drawn household. It also refuses no-tools and calculator runs roots that are the same folder or where one contains the other.Invariants
The tests' oracle (
run_problemsintests/test_confirmatory_run.py) checks these on every fake run, and the property test checks them on random mixes of planted failures:clock()never returns a time earlier than one it has returned, including under a wall clock that steps back (test_the_clock_never_goes_backandtest_a_wall_clock_that_steps_back_keeps_the_run_procedure). The run log itself is not in time order: an attempt's line is written when its household ends, and aceilingevent carries the crossing's time. Every check that orders events reads their times, never their line order.late-costevent.Tests
tests/test_confirmatory_no_tools.py(60 tests, fake providers through the realrun_household): draw order and one thread per model; a rerun links the unchanged archived bytes; a third provider failure stands with two archives; operator pause and resume; harness and budget stops that persist; a crash after the archive move; spend carried across invocations and optional models; refusals of foreign, gapped and corrupted histories; an attempt that raises keeps its spend; the cap agrees with the report's. Commit 08d7b18 adds a realos.kill(os.getpid(), signal.SIGINT)from inside a fake provider call (the attempt saves, no later household starts, the exit is 1, the handlers are restored), a secondStagerefused while the first holds the lock, both baseline refusals, archive identity and rerun eligibility, a harness stop at attempt 3, and a conversation through the realrun_householdthat ends at its next turn after another model's spend crosses the cap. c60086b to 4ce7f8e add real signals between the interrupt check and an archive or an attempt (both refused); a second thread that fails to start while the first holds a fake provider (an independentflockprobe shows the invocation lock held until the first finishes); a second signal while a worker holds the output lock (exit 128 plus SIGTERM within the deadline); optional and off-draw finals that cannot be classified, refused at startup and by the preflight; and baseline files that are a hashed bare list, invalid JSON or missing a field. Commit 2f795ea replaces the handler's lock with the watcher and adds tests that run each signal path in a bounded subprocess: a real SIGINT inside the cleanup's own Event condition (the worker saves, the handlers stay installed and the invocation lock held until it does); exceptions injected once and twice into the cleanup's waits; astart()that raises before or after the native thread exists, within and past the start grace; a signal while a model's thread holds its admission (with the lock removed from the worker or from the watcher, all four fail); a second signal while the watcher waits for admission or while a worker holds the output lock; and a signal after every model finished (exit 1). The tests that slept 50 ms now wait for the interrupt event, and the forced-exit deadline is 30 s. Commit 9acc920 fixes a race in one new probe: its control thread could exit the process before the main thread's handler did (at 6597adb it failed that way in two of three runs: once in run 2, twice in run 3). The two forced-exit probes then pass five more runs in a row. a1114fe adds six cases in bounded subprocesses: the handlers must be restored before either of the watcher's pipe descriptors closes, and a real SIGINT after a descriptor's number is reused must write nothing to its new owner (with the teardown order reversed, both fail); a watcher whosestart()raises before or after the native thread exists, or whose target begins after the start grace (the original error propagates, the handlers and descriptors are released, and a late target returns without reading a closed descriptor); and a first SIGINT after every model finished (exit 1, every final saved, no interrupt set and no announcement). All six pass on the script at 9acc920, which needed no change. Commit 89dd946 qualifies the docstrings and comments on the handler and the cleanup's waits; its executable code is identical to 9acc920's. With the existing files, 315 pass at 89dd946, one worker, in each of three runs in a row (309 at 9acc920, 297 at 4ce7f8e), and the two forced-exit probes pass five more runs. The fix build also started a run of the wholetests/directory; it reached about 57% before the build's 6-hour limit stopped it, with three or four failures that its-qoutput does not name. This PR changes onlyscripts/confirmatory_*.py,tests/test_confirmatory_*.pyandtests/fake_anchor.py, and the whole directory has not been run to completion at this head. Commit 060b287 makes two host-log tests (test_the_host_log_samples_and_closesandtest_pressure_near_an_attempt_is_host_trouble_once_settled) wait for the samples they need instead of sleeping a fixed time; on a loaded host the 0.02 s sampler had missed both windows.tests/test_confirmatory_run.pyandtests/test_confirmatory_anchor.py: 248 pass at 4002f8e, one worker. The anchor tests run the helper against a real temporary clone and a local bare remote. They kill the helper inside Git's pre-commit, pre-push and pre-receive hooks, inside a clean filter duringgit addand duringgit status, and with a detached descendant holding the lock. Each checks that later helpers fail as busy and leave the clone's file, index, HEAD and the remote unchanged, and the clean-filter and descendant tests each fail when inheritance is removed from only the command they target. Fake OpenAI Responses and Gemini JSON conversations are compared with the unchanged treatment harness's, complete requests and calculator inputs included.v2-microcosm-batchesget no CI, sinceci.ymlruns on pull requests to main.clock()it fails every time: attempts overlap, and a household runs before its admission.test_confirmatory_differential.py). It runs this runner'sRunon a copy of the development board. The harness'srun_householdis real, and so are this runner's host log and lanes. Only three things are fakes: the model (replying in each API's shape: chat tool, chat JSON, Responses), the engine pool and the host's readers. The confirmatory report'sanalysethen checks every record and log. At the report's 7cf22e2 against 4002f8e, 27 tests pass, and a 60-example run of its property test passed. At the report's ef297fa against 047c868, 34 pass, among them the no-tools script's own output checked by the report, and a 60-example run of the property test passed. At the report's 0b8e275 against 5a9735b, 39 pass, among them a harness stop and a ceiling stop on the last household, each of which the report refuses, and a 60-example run of the property test passed. At the report's 35005db against 89dd946, 64 pass, among them 15 baseline files that this runner and the report must both accept or both refuse, and a 60-example run of the property test passed. (At eda54e1 against f5099db, 22 passed, among them the same jittered clock.)Plan
🤖 Generated with Claude Code