Development record
Preflight throughput sets the pace of all work on this machine, and nothing keeps it in bounds. On 2026-10-16 the load gate admitted one make preflight at a time, each took about 40 minutes, and about 20 waiters were queued across seven sessions. Every reviewed branch had its own preflight, so a seven-step feature needed seven full runs, and records-only branches paid the same full cost as code. Sessions waited hours, and the queue's ordering after the reboot starved late waiters (fixed separately on fix/load-queue-start-order). No rule tells sessions to batch work, and no part of preflight scales its cost to what a change touches.
Preflight throughput sets the pace of all work on this machine, and nothing keeps it in bounds. On 2026-10-16 the load gate admitted one make preflight at a time, each took about 40 minutes, and about 20 waiters were queued across seven sessions. Every reviewed branch had its own preflight, so a seven-step feature needed seven full runs, and records-only branches paid the same full cost as code. Sessions waited hours, and the queue's ordering after the reboot starved late waiters (fixed separately on fix/load-queue-start-order). No rule tells sessions to batch work, and no part of preflight scales its cost to what a change touches.
Evidence 2026-10-16 (abcd-a2, measured on this machine: 16 cores, macOS)
What a preflight costs
- No model tokens.
make preflightis local work only: format, lint and record gates, build, vet, unit tests, race tests, and the two eval lanes, which are build-tagged Go tests of about five seconds each. Its cost is CPU, memory and wall-clock time. - Wall-clock, from today's logs. Each figure is gate wait plus run.
| Branch | Total | Waiting at the gate | Running |
|---|---|---|---|
| embargo store fix | 61 min | 37.5 min | about 23 min |
| messaging step 4 | 86 min | 62 min | about 24 min |
The run takes about 23 to 24 minutes; the wait is the larger part.
What the queue costs
- Race runs queued alone, without a preflight, waited at the gate:
- 17 min (an independent reviewer's run);
- 33 to 37 min (three sub-agents' checks);
- one never opened inside its 2 h timeout (messaging step 3, fix round 2).
- Depth. About 20 waiters were queued across seven sessions at 17:30. Six of this session's branch preflights were then folded into two integration branches, to cut its share to two runs.
- Load. The gate's limit is 32 (2 × 16 cores). The one-minute load ran 38 to 75 through the afternoon and reached 164 after the reboot. Most of that was the operating system re-indexing (Spotlight, fseventsd, the VPN network extension), not test work, and it held the gate shut regardless.
- Ordering. After the reboot, process ids wrapped. Pid-ordered admission then starved waiters started earlier, a separate defect fixed on
fix/load-queue-start-order.
Where tokens actually go around preflights
- Waiting is cheap. A session waiting on a gated preflight in the background reads one short notice and a few tail lines: hundreds of tokens.
- Fix rounds cost the most. A fix-round sub-agent that runs the full package suites itself used about 200,000 to 390,000 tokens per round. Most of that was reasoning and reading code; the test output was the smaller part.
- Reviews cost less. A review pair used about 60,000 to 240,000 tokens per reviewer.
Next measurement. A trial tonight runs two preflights at once, with the race stage kept to one at a time (feat/two-preflights). It will record each run's wall-clock, its peak one-minute load, and the gate waits, against these one-at-a-time figures. Resource-derived admission is iss-2610161756384596.
Evidence 2026-10-16 evening: waiters timing out at the gate
At about 20:00 the gate held 23 waiter processes across sessions, admitting one preflight at a time.
- One session's preflight reached the Makefile's two-hour gate limit twice without any check failing. The second time, 8 waiters were ahead of it.
- A retry rejoins at the back of the queue: a new pid under the pid order, and a new start under the start-time order.
- The work-around in use is an outer
abcd implement load --wait --timeout 8h -- make preflight, which keeps its place while the inner gate takes over. - One session folded four reviewed branches into one, to need one preflight instead of four.
- A review's own race run also timed out after its 30-minute default without running a test.
Evidence: 2026-10-24
Two records-only branches (four issue records, and a draft intent with its index) failed abcd land five times between 16:20 and 23:19 CEST without a check failing:
- once on a smoke lane killed at its 10-minute limit while seven preflights ran on the machine;
- four times on the load gate's 2-hour wait, with first six and later two waiters ahead, the oldest waiting more than five hours.
Each retry re-ran every stage before the gate (about 20 minutes) and rejoined the queue at the back. A records-only change still needed the full build, test and race stages to earn the receipt the pre-push hook asks for (iss-2610171105560765 holds the timeout half).
Further evidence 2026-10-27 (abcd-1, build itd-82 lane-1)
- The implement loop's landing commits its own records after the lane's preflight has passed: the spec step's
landed:mark and the receipt's issue resolution, as one records-only commit (808720029 onbuild/run-2610260500113538-lane-1). That commit is the lane's new head, so the landing waits for a second wholemake preflightbefore it pushes. The first, on 6594725b5, had passed at 09:24Z. The second started at 09:59Z, with the machine's shutdown window closing, for a diff that touches two record files and no code.
Frontmatter
| id | iss-2610161639025702 |
|---|---|
| type | issue |
| lifecycle | open |
| severity | minor |
.abcd/work/issues/open/iss-2610161639025702-preflight-throughput-sets-the-pace-of.md
open on GitHub ↗ · commit history ↗
References1
- itd-2610170634394993Headless agents in parallel coordinate through the shared remote, not conversationrelated
mentions8
- dcn-26101701411392862026-10-17 — Refines dcn-2610161814279204, the two-preflights trial (iss-2610161639025702), on how the two gates share the hold. Each gate now keeps its own hold record, the first gate's in load-wait.json, the name it had before the race gate existed, and the race gate's in load-wait-race.json, and a wait judges and takes only its own gate's record. A first gate's command hold, taken by --wait -- make preflight, lasts until its preflight starts its race run, up to sixty minutes. While it also shut the race gate, an earlier preflight's race waiter on the same account waited on it, and the holder's own race waiter, which the hold does not shut because the holder is its ancestor, queued behind that earlier race waiter, which started earlier and has the lower pid; neither moved until its timeout. Even without the cycle, the earlier preflight's race stage waited out the holder's whole test stages. Rejected: letting a race waiter beneath a holder jump the race queue, as the first gate's waiter beneath one does. It breaks the cycle but keeps the stall, and since the queue is read across accounts, it would let that race waiter open beside another account's race waiter queued ahead of it, two race stages at once. Cost of the chosen fix: the first gate is no longer shut for two minutes after a race gate opens, nor the race gate after a first gate opens; with two slots each running preflight still takes a slot, and the load limit still applies, so nothing the trial relies on is lost. An abcd released before the race gate reads only load-wait.json, as before.
- dcn-26101701445917422026-10-17 — Refines dcn-2610161814279204, the two-preflights trial (iss-2610161639025702), on a machine config.json that cannot be read for load.preflights. The load gate now reads past it: a file that others can write, that is not valid JSON, or whose load is not an object gives the default of one preflight at a time, and the waiter prints one line on stderr naming the file, the cause and how to fix it; a key under load that nothing reads is named on that line, and a usable load.preflights beside it still counts. Only a load.preflights that is there and unusable (out of range, or not a whole number), or an unusable ABCD_LOAD_PREFLIGHTS, refuses the wait. Reason: with one slot the gate never read that file before the setting existed, so refusing made a file that holds other machine settings stop every make preflight of a person who never set the count, a regression against main. This departs from the layered reader's rule that an unreadable file fails loudly; the note is what stays loud, and what a read past drops is only a count the file could not be trusted to carry, falling back to the setting's safe default. Rejected: keeping the refusal with a clearer message, which the branch had, since a person who never set the count still could not run a preflight without editing a file they did not know mattered.
- dcn-26101701494326652026-10-17 — Refines dcn-2610161814279204, the two-preflights trial (iss-2610161639025702), on a lone race run gated with --wait -- go test -race. With two slots a live race run does not shut the first gate by itself, and the command form's hold ends once its race run starts, so a second such run saw one unit of two busy and opened, two race runs at their full -p together. Chosen: a command-form waiter whose command is a race run (go, then test with -race, read as a race run's arguments are) is gated as a race run for both gates. Above one slot it stays shut while another race run is live, judges and takes the race gate's hold as well as the first gate's (its hold record is written to both), and every race gate and every other such waiter counts it as a race run from the moment it opens, its mark showing it running before its go test is in the table. With one slot nothing changes: the first gate is already shut by every race run and every running waiter, and the race gate opens at once. Rejected: letting a live race run shut every command-form first-gate waiter. It closes the same gap, but it also holds --wait -- make preflight shut through every other preflight's race stage, which is the overlap the trial exists to measure. A shell or make that runs a race run is not read into; only a command whose program is go is judged one.
- iss-2610161756384596abcd's load gate admits a fixed number of preflights, one, whatever the machine can carry. The product thinker asked on 2026-10-16 that abcd know how many concurrent preflights, race runs and sub-agents are feasible from the resources it actually has, instead of a number a person sets and re-sets by hand (the machine-wide cap moved six times in two weeks: six, eight, ten, twelve, ten, fourteen).
- iss-2610171105560765abcd's wait and timeout defaults are fixed numbers, while what they wait for grows with the project. The load gate gives up after 30 minutes by default and after 2 hours in preflight, and go test kills a package at 10 minutes unless told otherwise. On 2026-10-17 two preflights here lost hours to these limits without any check failing: one waited the full 2 hours at the gate and gave up, and another was killed when one package took about ten minutes on a loaded machine. As a managed project gets more packages, tests and concurrent sessions, every fixed default is outgrown in turn, and each time a person or session must notice it and raise it by hand.
- iss-2610241549396753The remedy of iss-2610171626398856 (the v0.15.1 release review) asks for iss-2610161639025702 and iss-2610161756384596 to be resolved, as if the two-preflight work had delivered them. Neither ask has shipped. Deriving the preflight count from the machine's resources is still named a later step in internal/core/implement/load_slots.go:37-39 and in the brief (04-surfaces/27-implement.md:373-374). iss-2610161639025702 asks for more than the trial, including merge batching and test stages that run only the packages a change can affect. Following that remedy as written would close two open asks.
- itd-82`abcd drain` fixes the issues that need no decision, and hands the rest back by kind
- itd-2610261025516272Private repos land checked work without renting CI