Development record
A preflight waits for its turn as one whole run
Press Release
When several sessions on one machine push work at once, each preflight waits for its turn as one whole run (a records-only change takes a shorter preflight that waits at no gate). A queued run does nothing until the gate admits it: no lint, no build, no test. So the runs waiting never add to the load the running one is judged by. The number allowed at once is read again at every look, so a change to it applies to the runs already queued at their next look, and the wait's report names the value it judged by and where that value came from (always in its JSON form, and on the line saying the gate opened when more than one run is allowed at once). Two runs that reach the gate together never wait on each other until they time out.
Why This Matters
On a shared machine, how preflights queue decides how fast everything lands. In the autonomous run of 24 to 26 October 2026 on a 10-core laptop, only a preflight's test stages waited at the gate: its lint gates, its two evaluation lanes, its build and its vet ran at once, for every queued run, so thirty queued runs kept the machine overloaded while they waited, and the admitted run's tests timed out. Sessions that wrapped a whole preflight in the gate's command form ran one at a time and passed at the first attempt, so the work-around showed the fix. A person who changed the number allowed at once saw nothing change for the runs already queued, because each run read the setting once, when it began to wait.
The first of three pieces split from one draft at the product thinker's ruling of 26 October 2026: this piece makes the whole run wait; itd-2610262140595217 records how each run behaved; itd-2610262141001890 adjusts the number allowed at once from that record. The fast lane for short targeted test runs stays its own record, iss-2610260845422800, because it needs its own decision about what a run may claim about itself.
It resolves iss-2610241537197997 (concurrent preflights fail; the remedy is this piece) and iss-2610241214065079 (stages that never wait), and the parts of iss-2610241414124940 (every heavy stage behind one admission; the setting read again while waiting) and iss-2610241911052191 (the admitted run overloaded by others' unmetered stages) that admission decides. What those two records also ask moves elsewhere: releasing a slot whose holder is idle to iss-2610262155232329, and admitting on the headroom a run will need to itd-2610262141001890.
Mechanism
We expect fewer test timeouts and faster landings, because runs that wait no longer load the machine the running one needs; the sessions that already waited as a whole run passed at the first attempt. The claim is wrong if timeouts stay as frequent once queued runs sit idle.
Scope Conditions
- macOS and Linux machines.
- Sessions running an abcd that carries this change; a session on an older abcd keeps today's queue and its unmetered stages.
- One machine shared by several sessions, whichever account each runs in.
- The number allowed at once is a whole number from 1 to 4, and 1 by default (product thinker, 26 October 2026; the bound was raised from 2 to 4 on 28 October 2026).
- Not on CI runners, which never wait.
Acceptance Criteria
- Given one run allowed at once and three full preflights started within a minute, when they run, then only the admitted one does any work (lint, build, test or any other check), and the other two wait doing nothing.
- Given a run waiting, when the number allowed at once changes, then the waiting run judges by the new number at its next look, and its report names the value and where it came from (always in its JSON form, and on the opening line when more than one run is allowed at once).
- Given two runs from different accounts reaching the gate at the same moment, when both wait, then one is admitted within a minute and neither waits on the other until it times out.
- Given a repository whose own settings name a number of runs for the gate, when any run waits, then that number is ignored.
Decisions
- Split from one draft into three pieces; this is the first (product thinker, 26 October 2026).
- The number allowed at once is adjusted automatically, never by hand. A person may name a starting default, and abcd uses even that only once the machine's capacity has been measured (product thinker, 26 October 2026). The adjustment is itd-2610262141001890's promise; this piece only makes a change to the number reach the runs already waiting. The ruling reverses the standing rule that the person writes the number, as the LOAD rules and dcn-2610161814279204 state it, and is carried to that piece's planning.
Open Questions
None recorded yet.
Audit Notes
Fidelity review OWED (receipt rcp-a712216d677f).
Grounds
- pursued: it clears two of the serious problems that block the next release, and it stops queued runs overloading the machine, which held up the whole autonomous run; it is shown wrong if the release is still blocked by them, or runs still time out while others wait
Frontmatter
| id | itd-2610262127138979 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | minor |
.abcd/development/intents/shipped/itd-2610262127138979-abcd-works-out-for-itself-how-many.md
open on GitHub ↗ · commit history ↗
References2
- itd-2609201925079472An autonomous run paces itself by default, and one flag sets the pacebuilds on
- itd-2609231434459890abcd's own tests warn before running on an overloaded machinebuilds on
Referenced by3
- itd-2610262140595217abcd records how each preflight on a machine behavedblocks
- itd-2610262140595217abcd records how each preflight on a machine behavedbuilt on by
- spc-2610262202438999A preflight waits for its turn as one whole runimplemented by
mentions17
- dcn-26101618142792042026-10-16 — The product thinker approved a measured trial of two preflights in their test stages at once, with the race stage kept to one at a time; the default stays one, and a person may raise it only for their own machine (load.preflights in ~/.abcd.noindex/config.json, or ABCD_LOAD_PREFLIGHTS), never from a repository file. So the setting takes 1 or 2 and nothing more: 3 and 4 are refused, saying the limit rises once a measured trial supports it. At the default of one the preflight runs exactly as before, its race gate opening at once.
- dcn-26102719082377322026-10-27 — Release themes, product thinker, 27 October 2026: the next release cut must make abcd faster to build with, by managing the machine's resources intelligently (the self-adjusting preflight admission, itd-2610262127138979 and its two later pieces, with the load-gate issues they gather); the release after that must make it possible to work across accounts, computers and maintainers. This supersedes the earlier plan to cut the next release after 48 more hours of work (dcn-2610241420262850): the cut waits on its theme, not on a clock.
- dcn-26102907410943042026-10-29 — Declined part of iss-2610241414124940's remedy: once a run holds a slot, the gate does not switch to judging capacity by the slot count alone and keeps its load limit. Grounds, the timeout evidence of the two records: on 2026-10-24, with several preflights' test stages admitted together at a load near 20 on 10 cores, six packages hit their 20-minute per-binary timeouts in one run, so admitting more than one at a time made all of them fail (iss-2610241414124940); and the gate opened a whole run at load 17.8 on 10 cores, under its limit, after which three packages (claim, implement/loop, surface/cli) hit the 20-minute timeout because the load stayed near the threshold for the whole run (iss-2610241911052191). Admitting by slots past the load would admit more runs into exactly that load. The starvation that part answered came from queued runs' ungated stages feeding the load they waited on, which whole-run admission removes (itd-2610262127138979): a queued run now does no work. The idle-slot part of the same remedy is carried by iss-2610262155232329. The same record's 2026-10-25 evidence points the other way from slot-only admission too: a run admitted alone at load 8.5 on 10 cores still timed out three packages while the machine's on-access scanner used one to two cores, a cost the load average does not show, so the load is if anything an optimistic signal there; admission on measured headroom is the draft itd-2610262141001890.
- dcn-26103005284944732026-10-30 — Merging main's disk floor into whole-run admission (itd-2610262127138979): the load gate reads the disk where main reads it (before the wait, which main's one-slot race shortcut followed, and at the open look after the jitter) and also before a one-slot race shortcut a later look reaches, not at every look, and judges each read against the floor sized by the slot count read at that same look. A race shortcut reached at a later look, after the count fell to one, refuses with the at-open wording, since the waiter has waited by then.
- dcn-26103006183713212026-10-30 — itd-2610262127138979: when a look reads more than one slot, a first-gate command hold written by a run admitted at one slot is judged as a settle hold (in force only until its open plus the settle time), so raising load.preflights reaches a run already queued behind a held command, as iss-2610241414124940's resolution says; the running preflights are still counted against the slots from the process table. A disk refusal at the open look whose floor rose because the slot count rose says so, naming the new count, rather than saying the disk fell.
- dcn-26103007004343602026-10-30 — itd-2610262127138979: four review notes on step 2's last fix are carried into step 3 rather than fixed on steps 1-2, since fixing them there would void a running preflight on the cut's critical path and none is a defect: the Makefile comment naming the load gate's go run as the first Go step holds on the full rule only; the settle-hold rewrite above one slot also covers a race command's own first-gate hold, which is slower and never less safe; the tests do not pin the settle window or the disk floor's boundaries; and the floor-rose refusal's parenthesis should name the floor. Step 3 fixes all four.
- dcn-26103009544339522026-10-30 — itd-2610262127138979 step 3: the first gate's settle rewrite above one slot keeps judging a race command's own first-gate hold as the settle hold, and the code says so, rather than narrowing it to holds written at one slot. A hold does not record the count it was written at, so narrowing would add a field to the hold file every abcd on the machine reads; the race run is counted from the process table either way, so the gate never admits more than the slots. Agent decision in an autonomous run, on the review note carried in dcn-2610300700434360.
- dcn-26103010371126562026-10-30 — itd-2610262127138979 step 3, review round 1: the test comments that call a running preflight one 'in its test stages' (machineload/slots_test.go, implement/load_slots_test.go, load_race_command_test.go, load_whole_run_test.go, surface/cli/implement_load_slots_test.go) stay as written. Each describes a fixture whose preflight is running its stages, which is literally true; the reviewer graded them a nit that ships unfixed, and the code comments and taught text that said the gate admits test stages are reworded. The taught sites (LOAD rule, commands/implement.md, the implement brief chapter, machineload/race.go) now say once that a records-only change takes a shorter rule that waits at no gate. Agent decision in an autonomous run.
- dcn-26103013163404112026-10-30 — itd-2610262127138979, delta docs-fidelity findings 7 and 8 before the cut: docs/how-to/install.md now says make preflight waits its turn at the load gate before its load check, but AGENTS.md's preflight comment is left as it is, because AGENTS.md has under thirty bytes of room under its smallest reader's cap and the gate's wait is taught in the implement page, the LOAD rule and the Makefile. The intent's slug and title stay as they are: a rename is not a wording fix, and the intent's own Decisions section hands sizing the count automatically to itd-2610262141001890. Agent decision in an autonomous run.
- iss-2610241214065079The load gate meters only a preflight's test stages; every stage before them runs ungated. On 2026-10-24 seven sessions on one 10-core machine each ran make preflight, and all seven ran their lint gates, the smoke and cold-reading eval lanes (five to six minutes each) and build and vet at once, holding the load average near 25. Tests timed out under that load (internal/surface/cli passed go test's ten-minute default), and every preflight then queued behind one test slot, so one nitpick fix waited over an hour. Raising load.preflights to 2 widens only the test slot; nothing bounds the stages before it.
- iss-2610241414124940The preflight load gate can starve every waiter on a busy machine and holds its only slot for a hung command. It opens only while the one-minute load is below twice the online cores (20 on 10 cores), but the preflight stages it does not gate (lint, record gates, evals, build, vet) run before it, so seven sessions' preflights on 2026-10-24 kept the load near 25 and the gate never opened: one drain lane's preflight ended 'load gate still shut after 2h0m0s: load 20.9 is not below 20 (2 x 10 online cores); 3 make preflights are running'. Later the same day, at 14:04 UTC, the load was 8 on 10 cores and about nine preflights were still queued, their logs unchanged for 45 minutes, because the one open slot (~/.abcd.noindex/load-wait.json, form command, opened 13:19 UTC, held to 14:19) belonged to a package test run that had used about 11% CPU for 45 minutes; load.preflights was 2, yet only one slot was open, possibly because waiters started before the setting was written read one slot when they began. Nothing reclaims a slot whose holder is idle, and nothing lets the gate open when the load it waits on comes from the ungated stages of its own queued runs. Adjacent records on an unmerged branch cover the ungated stages running unbounded and the unmeasured wait, which this issue does not restate.
- iss-2610241537197997Concurrent preflights on one machine all fail: on 2026-10-24 a records-only branch's preflight reached its test stage while six other preflights and several direct test runs shared a 10-core machine at a load near 20, and six packages (cloudscripts, ahoy, autonomy, claim, embargo, implement/loop) hit their 20-minute per-binary timeouts, so the run failed on nothing its own change touched. The load gate admits each preflight's test stage once the load dips under its limit, and the stages before it run ungated, so every admitted run then competes with the rest and none can finish inside its timeouts. Wrapping the whole run in the gate's command form, which one session found by hand, serialises them.
- iss-2610241911052191The load gate opened a whole make preflight at load 17.8 on ten cores, under the 20 threshold, yet three packages (internal/core/claim, internal/core/implement/loop, internal/surface/cli) then hit the twenty-minute test timeout, because the other sessions' ungated stages kept the load near the threshold for the whole run
- iss-2610260845422800The load gate queues a targeted go test (one package, a few minutes) behind whole preflights in one FIFO line, so a short job starves: on 2026-10-26 an implementer's package test for the review-set lane timed out at the gate twice at the 2h limit, behind 19 waiters and 17 make preflight processes (load averages 31.5, 20.7, 40.5 at 09:44). Neither the fixed admission count (iss-2610161756384596) nor the ungated stages (iss-2610241214065079) covers the ordering: even a measured admission count keeps a short job behind every long one already queued.
- iss-2610262155232329A load-gate slot held by a run that has stopped using the machine is never released: a holder that hangs or idles keeps its slot until its own timeout, and every other waiter waits behind it (part of the remedy of iss-2610241414124940, split out of itd-2610262127138979 at its planning on 2026-10-26, because whole-run admission does not decide it). Admitting a run on the headroom it will need rather than the load at one instant (part of iss-2610241911052191) moves with it to the adaptive count, itd-2610262141001890.
- iss-2610290750089812When load.preflights is lowered to one while two whole runs are admitted, the race gate's at-one shortcut opens for a run that is waiting behind the other's live race run, so two race stages overlap. At two slots, runs A and B are both admitted. A is in its race stage, and B waits at its race gate because A's race run is live. The person lowers the count to 1, and B's race gate opens at its next look. The shortcut assumes a first gate that admitted this run alone, which no longer holds for runs admitted under the higher count. Lowering the count is meant to reduce load, and here it does the opposite. The spec of itd-2610262127138979 asks for this shortcut, and TestWaitRaceGateOpensOnceTheCountFallsToOne asserts it.
- itd-2610262141001890abcd adjusts how many preflights run at once from what it recorded