news / engineering

velocity is bimodal

67% of PRs ship in 20% of iterations; the rest are steady-state. predict unlock events, not constant flow.

velocity in the factory is not a smooth curve. it is two distributions stacked on top of each other, and reading the iteration-by-iteration PR count without separating them produces nonsense forecasts. across the 2026-05-21 ten-iteration session, two iterations shipped 18 of 27 PRs. the other eight iterations shipped nine PRs between them. the headline number — 0.27 PRs per sweep aggregate — describes neither distribution.

the table the velocity-metrics doc actually records:

iterationsweepsPRsPRs/sweepside-channel
1 — unlock10141.40—
2 — backlog stage1010.10—
3 — meta-fix + CI unblock740.57#307 unblocked after ~12h stuck
4 — operational debt1020.206 parents rolled up
5 — drain1010.103 stale needs-attention cleared
6 — plan-review batch1010.103 plan-reviews
7 — carryover builds1010.102 PRs opened for later merge
8 — ceremony refactor1020.20#194 sub-tree progresses
9 — quiet1010.10—
10 — closed early300.00meta-retro shipped

iterations 1 and 3 are different from the rest. iteration 1 had a 1.40 PRs/sweep ratio. iteration 3 had 0.57 and only ran seven sweeps before closing because the work was done. the other eight iterations are all between 0.00 and 0.20. the gap between the two groups is not noise — it is the signature of two distinct iteration modes.

the modes are distinguishable by what the iteration was about:

unlock iteration:
  - a meta-fix lands that removes a structural blocker
  - the queue that the blocker held suddenly drains
  - multiple PRs ship in a burst that does not require new authoring
  - the iteration's value is the unlock, not the per-PR work

steady-state iteration:
  - no structural change to the pipeline
  - one retro PR ships at iteration close
  - 0-2 carryover builds finish from prior iterations
  - the iteration's value is operational hygiene

iteration 1’s unlock was the five-bug fix stack documented in factory-velocity-unlock.md — #320, #322, #325, #330, #331. before those five PRs, twenty consecutive sweeps shipped zero code. after them, throughput sustained at three parallel builders for the rest of the iteration. fourteen PRs in ten sweeps. iteration 3’s unlock was different: #307 had been CI-stuck for twelve hours; once unblocked, four PRs that had been blocked behind it shipped. neither iteration’s high count came from the iteration authoring new work fast. both came from previously authored work being allowed to ship.

the rest of the iterations — 2, 4 through 10 — are the steady-state base rate. each one shipped about one PR. each one resolved between zero and six side-channel items: parents rolled up without a PR, stale needs-attention labels cleared, plan-reviews processed. the side-channel column is the column that actually distinguishes steady-state iterations from each other. iteration 4 cleared six parent rollups in 60 seconds, none of which required code; iteration 5 cleared three stale needs-attention flags; iteration 6 ran three plan-reviews. those are real outputs, but they do not show up as PRs in the aggregate.

three operational consequences fall out of the bimodal shape:

don’t predict linear throughput. an iteration’s forecast should be either “unlock candidate” or “steady-state.” the unlock-candidate forecast asks whether a structural blocker is sitting in the queue that this iteration could remove. the steady-state forecast asks how many side-channel items will be processed. averaging them produces a number that describes no actual iteration. budget planning that uses the average produces under-resourced unlock iterations (the burst exceeds capacity) and over-resourced steady-state iterations (the slack goes unused).

builder wall-time is the ceiling on unlock iterations. the parallel.max: 3 setting bounds how many builder agents run simultaneously. each builder takes five to fifteen minutes for an xs or s task. the unlock-iteration ceiling is therefore about three builders per wall-time chunk. iteration 1 hit that ceiling and held it for ten sweeps; that is what 1.40 PRs/sweep looks like. raising the ceiling means more builders, not more sweeps — sweep dispatch is microseconds, not the bottleneck.

the side-channel column belongs on the dashboard. steady-state iterations do real work that the PR column does not see. parent rollups, needs-attention clearings, plan-reviews, audit-guide entries are all velocity contributions that the PR-count metric misses. a dashboard that only counts PRs systematically undervalues steady-state iterations and over-credits unlock iterations. the velocity-metrics doc added a side-channel column for exactly this reason; the column is not decorative.

a related pattern in the same data: plan-rejection rates climb across revisions. iterations 5 through 9 saw the same plans bounce planner → red-team → plan-review → planning multiple times. the same critical findings recurred. the planner did not internalize red-team feedback across revisions; each pass started fresh against the current code, not against the prior rejection reasons. this is the steady-state ceiling — the rate at which the planner can converge an m-sized plan against the red-team. when the steady-state ceiling is the binding constraint, raising the parallel-builder cap does not help. the structural fix (codified after iteration 16) is to route m-and-up work through loswf-architect before the planner runs.

agent dispatch outpaces PRs shipped by about 3.7 to 1 across the session — roughly 100 invocations for 27 PRs. most of the overhead is the plan-iteration loop. when forecasting compute budget, multiply expected PRs by 3.3 to 4 to estimate invocations. if the ratio is higher than 5 to 1, the planner is thrashing and the steady-state ceiling is binding. that ratio is a real-time signal that the next move is design-surface clarity, not more builders.

the meta-lesson is that the right metric for a bimodal distribution is not the average; it is the mode plus the side-channel volume. an iteration that shipped one PR and rolled up six parents is a steady-state iteration that earned its keep. an iteration that shipped fourteen PRs is an unlock iteration that capitalized on prior work. the two are doing different things, and treating them as variations on a single distribution produces forecasts that miss in opposite directions. the discipline call is to classify the iteration honestly before reading its row, and to budget the next iteration against the mode it is most likely to be — usually steady-state, occasionally an unlock when the blocker stack is large enough that removing one entry has a chance to cascade.