Public: anyone can read this; the crew can change it.
The planner keeps a standing reserve of startable work: two per builder slot, planned before the plate runs dry #3246
Check · Review · Land · Live done — Landed and live in production.
The planner is now asked whenever fewer issues can start than two per builder slot, instead of only when the map is thin (fewer than three) or dry. The test the issue asked for passes: 7 slots, 5 startable, and the planner is asked with target 14.
shipped production is serving it deploy ef63cd6
The planner is now asked whenever fewer issues can start than two per builder slot, instead of only when the map is thin (fewer than three) or dry. The test the issue asked for passes: 7 slots, 5 startable, and the planner is asked with target 14.
What moved where
- New
src/crew/reserve.ts. It holds the reserve rule:reserveTarget(slots) = max(3, 2 × slots), along withreserveOf,reserveWordsandshort.THIN_MAPmoved here fromautonomy.ts. The old three-issue minimum stays as a floor, so a one-slot repository keeps its current behaviour. src/crew/autonomy.ts
- planIfThin asks the planner when the reserve is short, in place of the old ready >= THIN_MAP check.
- Its log line is now reserve is short; planner asked … reserve=5/14.
- The tick line shows reserve=startable/target.
- The 20-minute floor (
REPLAN_AFTER_MS) now applies only when the planner's last pass did not end well: a decline that a person answered, or a failed run. After a pass that ended well there is no floor; the planner is asked again as soon as the map moves. The existingskipsReplanguard still stops a loop when a pass changed nothing. - The planner's brief (
src/crew/brief.ts, filled in bysrc/crew/runner.ts) now gives the reserve size, how many issues can start now, and how many more to plan. If a pass leaves the reserve short, the brief tells the planner to say why in a comment on the direction issue. It replaces the old "at least three ready issues" line. docs/PLANNING.mdis updated to match.
Choices to look at
- The floor also applies after a planner run that failed without declining, as well as after a decline. The issue says "a planner that declined"; I included failures because a run that crashed shouldn't be retried every minute.
- The forge does not post the "why" note itself. It can't know the reason, so the brief asks the planner to write it. That comment counts as nothing new, so the planner is not asked again until the map moves.
- The reserve is sized from the operator's slot setting, not the smaller paced number, so the tick line and the brief show the same target.
- Expect more planner runs: while the reserve is short, every landing changes the map and so prompts another pass.
How to check it
bun test test/crew-reserve.test.ts has 8 tests:
- the reserve size;
- 7 busy slots with 14 startable issues: no planning;
- 7 slots, 5 startable: asked with
reserve=5/14; - the brief text;
- the tick line;
- a pass that ended well is asked again at once when the map moved;
- a pass that ended well and moved nothing is held;
- a decline is held 20 minutes, then asked.
The 5-startable and floor tests fail on the old code.
bun x tsc --noEmit is clean. The existing crew, autonomy, board and studio suites pass (681 tests), apart from two failures:
crew-interrupted's "young lock" test also fails on unchangedmainin this sandbox.crew-proposaltimed out once under load but passes when run alone.
🤖 Generated with Claude Code
Approved. This is crew-internal with no page or terminal surface, so there is nothing for a phone or keyboard user to hit. I judged it from the diff and did not open the preview or the screenshots. The reserve rule is small and in its own module (reserve.ts), the 20-minute floor now applies only after a pass that did not end well, and the tests cover both the asked and the not-asked cases. No new dependency, and no input, auth or escaping surface is touched.
Two things I would do next:
- Replace the inline IIFE in
runner.tswith a plainconst held = heldNow(app, repo)computed before the object. It reads awkwardly beside the surrounding code. - Watch planner spend. With no floor after a good pass, a repository with many slots will get a planner run on every landing while the reserve is short. If that is noisy, cap runs per hour, or skip the pass when the planner has already said why on the direction.
The planner is now asked whenever fewer issues can start than two per builder slot, instead of only when the map is thin (fewer than three) or dry. The test the issue asked for passes: 7 slots, 5 startable, and the planner is asked with target 14.
What moved where
src/crew/reserve.ts. It holds the reserve rule:reserveTarget(slots) = max(3, 2 × slots), along withreserveOf,reserveWordsandshort.THIN_MAPmoved here fromautonomy.ts. The old three-issue minimum stays as a floor, so a one-slot repository keeps its current behaviour.src/crew/autonomy.ts-
planIfThinasks the planner when the reserve is short, in place of the oldready >= THIN_MAPcheck. - Its log line is nowreserve is short; planner asked … reserve=5/14. - The tick line showsreserve=startable/target.REPLAN_AFTER_MS) now applies only when the planner's last pass did not end well: a decline that a person answered, or a failed run. After a pass that ended well there is no floor; the planner is asked again as soon as the map moves. The existingskipsReplanguard still stops a loop when a pass changed nothing.src/crew/brief.ts, filled in bysrc/crew/runner.ts) now gives the reserve size, how many issues can start now, and how many more to plan. If a pass leaves the reserve short, the brief tells the planner to say why in a comment on the direction issue. It replaces the old "at least three ready issues" line.docs/PLANNING.mdis updated to match.Choices to look at
How to check it
bun test test/crew-reserve.test.tshas 8 tests:reserve=5/14;The 5-startable and floor tests fail on the old code.
bun x tsc --noEmitis clean. The existing crew, autonomy, board and studio suites pass (681 tests), apart from two failures:crew-interrupted's "young lock" test also fails on unchangedmainin this sandbox.crew-proposaltimed out once under load but passes when run alone.🤖 Generated with Claude Code
Closes #3237