fleetd #257: free must stop subtracting leadSeatCount #263

Closed
agent wants to merge 0 commits from worker/fleetd-257-9bf010-7 into main
Member

fleetd #257 — fleet_list's free and the spawn gate disagree by one

The fix

FleetMcp.capacityView no longer subtracts leadSeatCount from free. free now always
equals max(0, maxLoad - live) — exactly what the real spawn gate
(CompositePeerLauncher#enforceMaxLoad) checks (live >= cap), which never read
leadSeatCount at all. leadSeats stays in the row unchanged, reported but no longer
subtracted.

Docs updated

  • FleetMcp's fleet_list tool description: explains free = max(0, maxLoad - live), the
    same check the spawn gate runs, and that leadSeats is informational only.
  • fleetd.example.yaml: both maxLoad's GOTCHA 3 and the lead profile: doc, corrected to
    say free is never reduced by leadSeats.
  • Javadoc on LeadSeatSource and capacityView updated to match.

The test — drives the REAL gate, not a copy of its arithmetic

FleetMcpTest.freeMatchesWhatTheRealPlacementGateActuallyGrants:

  • Builds a real CompositePeerLauncher (via ClaudeCodeLauncher + FakeHerdr) wired with a
    liveCount function that reads the SAME SessionManager.roster() fleet_list's
    CapacitySource also reads — exactly how Fleetd.main wires production, via one
    AtomicReference indirection to break the construction cycle.
  • Spawns 2 members through the real FleetMcp.spawn → real SessionManager.acquire → real
    CompositePeerLauncher.spawn → real enforceMaxLoad path (maxLoad:3, live:2 — the exact
    shape measured in the ticket).
  • Reads whatever free number FleetMcp.listFleet (the real production method) reports, with
    a LeadSeatSource reporting 1 lead seat on the profile.
  • Drives exactly that many more spawns through the REAL gate and asserts every one succeeds,
    then asserts the next spawn — one past what fleet_list promised — is refused by the real
    gate.

This never recomputes cap - live and compares it to free; it compares what the real gate
actually grants against what fleet_list actually reports.

Test-earns-its-place proof: put the leadSeatCount subtraction back
(row.put("free", cap == null ? null : Math.max(0, cap - live - leadSeatCount));) and ran
mvn -Dtest=FleetMcpTest#freeMatchesWhatTheRealPlacementGateActuallyGrants test. It failed red:

org.opentest4j.AssertionFailedError: fleet_list reported free:0 but the real placement gate
granted at least one more spawn than that:
{"sessionId":"term_new_3","paneId":"76b23bfd-1212-4641-9588-d42dc1aff334","role":"dev",
"profile":"sonnet","status":"spawning"} ==> expected: <true> but was: <false>
	at dev.ltms.fleet.mcp.FleetMcpTest.freeMatchesWhatTheRealPlacementGateActuallyGrants(FleetMcpTest.java:918)

i.e. with the old formula, fleet_list said free:0 while the real gate still granted a
spawn (term_new_3 succeeded) — the exact disagreement fleetd #257 reports. Restored the file
afterward; diff against the pre-mutation copy came back identical (no residual change).

Also updated two existing tests (leadSeatSubtractsFromFreeTheSameWayLiveDoes →
leadSeatIsReportedButNeverSubtractedFromFree, and the idle-fleet counterpart) that asserted
the old subtracting behavior — they now assert the new semantics (free:1/free:3 instead of
free:0/free:2).

Build

cd fleetd && mvn clean install (unpiped, full output read):

Tests run: 1259, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

Baseline on main was 1258 tests; net +1 here (1 new test, 2 existing tests renamed/rewritten
in place, no others added or removed).

Scope note

Out of scope, not investigated: no other caller of LeadSeatSource/capacityView exists
besides fleet_list's row and Fleetd.main's wiring, so no other surface needed a change.

## fleetd #257 — `fleet_list`'s `free` and the spawn gate disagree by one ### The fix `FleetMcp.capacityView` no longer subtracts `leadSeatCount` from `free`. `free` now always equals `max(0, maxLoad - live)` — exactly what the real spawn gate (`CompositePeerLauncher#enforceMaxLoad`) checks (`live >= cap`), which never read `leadSeatCount` at all. `leadSeats` stays in the row unchanged, reported but no longer subtracted. ### Docs updated - `FleetMcp`'s `fleet_list` tool description: explains `free` = `max(0, maxLoad - live)`, the same check the spawn gate runs, and that `leadSeats` is informational only. - `fleetd.example.yaml`: both `maxLoad`'s GOTCHA 3 and the lead `profile:` doc, corrected to say `free` is never reduced by `leadSeats`. - Javadoc on `LeadSeatSource` and `capacityView` updated to match. ### The test — drives the REAL gate, not a copy of its arithmetic `FleetMcpTest.freeMatchesWhatTheRealPlacementGateActuallyGrants`: - Builds a real `CompositePeerLauncher` (via `ClaudeCodeLauncher` + `FakeHerdr`) wired with a `liveCount` function that reads the SAME `SessionManager.roster()` fleet_list's `CapacitySource` also reads — exactly how `Fleetd.main` wires production, via one `AtomicReference` indirection to break the construction cycle. - Spawns 2 members through the real `FleetMcp.spawn` → real `SessionManager.acquire` → real `CompositePeerLauncher.spawn` → real `enforceMaxLoad` path (maxLoad:3, live:2 — the exact shape measured in the ticket). - Reads whatever `free` number `FleetMcp.listFleet` (the real production method) reports, with a `LeadSeatSource` reporting 1 lead seat on the profile. - Drives exactly that many more spawns through the REAL gate and asserts every one succeeds, then asserts the next spawn — one past what `fleet_list` promised — is refused by the real gate. This never recomputes `cap - live` and compares it to `free`; it compares what the real gate actually grants against what `fleet_list` actually reports. **Test-earns-its-place proof**: put the `leadSeatCount` subtraction back (`row.put("free", cap == null ? null : Math.max(0, cap - live - leadSeatCount));`) and ran `mvn -Dtest=FleetMcpTest#freeMatchesWhatTheRealPlacementGateActuallyGrants test`. It failed red: ``` org.opentest4j.AssertionFailedError: fleet_list reported free:0 but the real placement gate granted at least one more spawn than that: {"sessionId":"term_new_3","paneId":"76b23bfd-1212-4641-9588-d42dc1aff334","role":"dev", "profile":"sonnet","status":"spawning"} ==> expected: <true> but was: <false> at dev.ltms.fleet.mcp.FleetMcpTest.freeMatchesWhatTheRealPlacementGateActuallyGrants(FleetMcpTest.java:918) ``` i.e. with the old formula, `fleet_list` said `free:0` while the real gate still granted a spawn (`term_new_3` succeeded) — the exact disagreement fleetd #257 reports. Restored the file afterward; `diff` against the pre-mutation copy came back identical (no residual change). Also updated two existing tests (`leadSeatSubtractsFromFreeTheSameWayLiveDoes` → `leadSeatIsReportedButNeverSubtractedFromFree`, and the idle-fleet counterpart) that asserted the old subtracting behavior — they now assert the new semantics (`free:1`/`free:3` instead of `free:0`/`free:2`). ### Build `cd fleetd && mvn clean install` (unpiped, full output read): ``` Tests run: 1259, Failures: 0, Errors: 0, Skipped: 0 BUILD SUCCESS ``` Baseline on `main` was 1258 tests; net +1 here (1 new test, 2 existing tests renamed/rewritten in place, no others added or removed). ### Scope note Out of scope, not investigated: no other caller of `LeadSeatSource`/`capacityView` exists besides `fleet_list`'s row and `Fleetd.main`'s wiring, so no other surface needed a change.
agent added 1 commit 2026-09-03 11:29:02 +02:00
fleetd #257: free must stop subtracting leadSeatCount
CI / build (pull_request) Successful in 1m43s
CI / contract (pull_request) Successful in 1m51s
279d6f5fbd
fleet_list's free row subtracted leadSeatCount, but the real spawn gate
(CompositePeerLauncher#enforceMaxLoad) only ever compares live against
maxLoad and never reads leadSeatCount. So free could report 0 while a
fleet_spawn on that exact profile still succeeded, and a lead trusting
free:0 gave up on capacity the gate would still grant.

free now always equals max(0, maxLoad - live); leadSeats stays in the
row as an informational fact, never subtracted. Documented in the
fleet_list tool description and fleetd.example.yaml.
Owner

Merged to main as 96d8191. Full build on the merge commit: 1262 tests, 0 failures, BUILD SUCCESS.

This is the best test in the batch, and I want to say exactly why, because the thing you avoided is the trap this repo keeps falling into.

The test drives the real gate — I checked, I did not take your word for it

freeMatchesWhatTheRealPlacementGateActuallyGrants builds a real CompositePeerLauncher over a real ClaudeCodeLauncher, then:

  1. reads whatever free the real FleetMcp.listFleet reports,
  2. drives that many spawns through the real FleetMcp.spawn → SessionManager.acquire → CompositePeerLauncher.spawn → enforceMaxLoad, asserting each succeeds,
  3. asserts the next one is refused.

Nowhere does it recompute cap - live and compare. That matters: a test that hand-computes the formula passes even when both sides share the same wrong formula. Yours can only pass when the reported number and the gate's real behaviour actually agree.

It also fails in both directions, which I checked separately. If free overstates, a spawn inside the loop is refused and the loop assertion fires. If free understates, the overflow spawn succeeds and the last assertion fires. A one-direction test would have proved half a guard.

Wiring liveCount as one function read by both the gate and CapacitySource, off the same SessionManager.roster(), is the right call too — it stops the two paths drifting on what "live" even means.

I checked the decision itself, not just the code

I told you to take option 1 without re-opening it. That was the right instruction only if option 1 is actually correct, so I went and checked the live fleetd.yaml (which you cannot see — it is gitignored).

I nearly got this wrong. I was carrying a note saying real sonnet capacity is 2, not 3, because the lead holds a subscription seat. If that were true, your change would make the daemon promise a slot the account cannot carry.

It is not true. The config records the test:

"2026-08-28 this said 'only 2 are usable, the lead holds a subscription seat'. That theory was TESTED on 2026-08-29 and NOT reproduced: three concurrent interactive claude sessions all started fine, and claude -p with the full member flag set returned rc=0. So there is no measured seat limit here."

The real cause of those failed spawns was a long-running fleetd; a restart fixed them. So subtracting the lead's seat described an accounting policy, not a constraint — exactly the ticket's reasoning. Option 1 stands, and my stale note has been corrected.

Rewriting the two old tests was right, not a shortcut

leadSeatSubtractsFromFreeTheSameWayLiveDoes and leadSeatLowersFreeOnAnOtherwiseIdleSubscriptionProfile asserted the behaviour you were removing. Renaming them to state the new semantics, rather than deleting them, keeps the coverage and makes the change visible in the diff. Good instinct.

What I did on top

The live fleetd.yaml carried a long comment describing the subtraction as deliberate — "the two numbers therefore disagree by 1 on purpose". That was written this morning and is now false. You could not see or edit that file, so I corrected it myself, kept the "do NOT raise maxLoad to 4" warning (still true, for a new reason), and recorded that leadSeats is a fact rather than a subtraction.

One check I could not run

ide_diagnostics was unavailable — the fleetd project is not open in IntelliJ and I was not going to change the operator's IDE state to run it. Gated on mvn clean install alone. Saying so rather than implying otherwise.

Your caveat about no other caller of LeadSeatSource/capacityView matched what I found.

Merged to `main` as `96d8191`. Full build on the merge commit: **1262 tests, 0 failures, BUILD SUCCESS**. This is the best test in the batch, and I want to say exactly why, because the thing you avoided is the trap this repo keeps falling into. ## The test drives the real gate — I checked, I did not take your word for it `freeMatchesWhatTheRealPlacementGateActuallyGrants` builds a real `CompositePeerLauncher` over a real `ClaudeCodeLauncher`, then: 1. reads whatever `free` the real `FleetMcp.listFleet` reports, 2. drives that many spawns through the real `FleetMcp.spawn → SessionManager.acquire → CompositePeerLauncher.spawn → enforceMaxLoad`, asserting each succeeds, 3. asserts the next one is refused. Nowhere does it recompute `cap - live` and compare. That matters: a test that hand-computes the formula passes even when both sides share the same wrong formula. Yours can only pass when the reported number and the gate's real behaviour actually agree. **It also fails in both directions**, which I checked separately. If `free` overstates, a spawn inside the loop is refused and the loop assertion fires. If `free` understates, the overflow spawn succeeds and the last assertion fires. A one-direction test would have proved half a guard. Wiring `liveCount` as one function read by both the gate and `CapacitySource`, off the same `SessionManager.roster()`, is the right call too — it stops the two paths drifting on what "live" even means. ## I checked the decision itself, not just the code I told you to take option 1 without re-opening it. That was the right instruction only if option 1 is actually correct, so I went and checked the live `fleetd.yaml` (which you cannot see — it is gitignored). I nearly got this wrong. I was carrying a note saying real sonnet capacity is 2, not 3, because the lead holds a subscription seat. If that were true, your change would make the daemon promise a slot the account cannot carry. It is not true. The config records the test: > "2026-08-28 this said 'only 2 are usable, the lead holds a subscription seat'. That theory was TESTED on 2026-08-29 and NOT reproduced: three concurrent interactive claude sessions all started fine, and `claude -p` with the full member flag set returned rc=0. So there is no measured seat limit here." The real cause of those failed spawns was a long-running `fleetd`; a restart fixed them. So subtracting the lead's seat described an accounting policy, not a constraint — exactly the ticket's reasoning. Option 1 stands, and my stale note has been corrected. ## Rewriting the two old tests was right, not a shortcut `leadSeatSubtractsFromFreeTheSameWayLiveDoes` and `leadSeatLowersFreeOnAnOtherwiseIdleSubscriptionProfile` asserted the behaviour you were removing. Renaming them to state the new semantics, rather than deleting them, keeps the coverage and makes the change visible in the diff. Good instinct. ## What I did on top The live `fleetd.yaml` carried a long comment describing the subtraction as deliberate — "the two numbers therefore disagree by 1 on purpose". That was written this morning and is now false. You could not see or edit that file, so I corrected it myself, kept the "do NOT raise maxLoad to 4" warning (still true, for a new reason), and recorded that `leadSeats` is a fact rather than a subtraction. ## One check I could not run `ide_diagnostics` was unavailable — the fleetd project is not open in IntelliJ and I was not going to change the operator's IDE state to run it. Gated on `mvn clean install` alone. Saying so rather than implying otherwise. Your caveat about no other caller of `LeadSeatSource`/`capacityView` matched what I found.
ltms closed this pull request 2026-09-03 11:35:09 +02:00
Some checks are pending
CI / build (pull_request) Successful in 1m43s
CI / contract (pull_request) Successful in 1m51s

Pull request closed

Sign in to join this conversation.