@feigi/fleet-ctl 3.82.15 → 3.82.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feigi/fleet-ctl",
3
- "version": "3.82.15",
3
+ "version": "3.82.16",
4
4
  "description": "Agent fleet: run-team controller, merge bot, PR reviewer, and the ticket pipeline they share",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -59,8 +59,8 @@ phase, or in any later one, asks the maintainer which tickets to take.
59
59
  one stopped; before it, a re-run runs every step again.
60
60
 
61
61
  The `tier-roles` step runs `~/.fleet/bin/fleet-run tier-roles.mjs --check
62
- --in-flight <B>`, `<B>` being the `--in-flight` given, else the default
63
- **Defaults** (**Queue depth**) states. What it checks is in the header of `scripts/tier-roles.mjs`, under
62
+ --in-flight <B>`, `<B>` being the `--in-flight` given, else the default.
63
+ **Default: In-flight bound 5, fixed for the run.** See references/pull.md. What it checks is in the header of `scripts/tier-roles.mjs`, under
64
64
  the tier routes. A line it prints on a pass about omp's `task.maxConcurrency`
65
65
  is a notice in the step's output and never holds: read it, report it, and
66
66
  go on — the fleet never writes that setting.
@@ -151,8 +151,7 @@ phase, or in any later one, asks the maintainer which tickets to take.
151
151
  `REFRESHED shortlist: <n> entries; <k> lifted`. A head whose Pull ended
152
152
  this run is not counted, unless on a lifted exclusion. Phase 0 runs it by hand
153
153
  once because a dead run's shortlist is stale by definition.
154
- None of those triggers runs while `HOLD (in-flight <n>/<B>)` stands
155
- (**Queue depth**).
154
+ None of those triggers runs while `HOLD (in-flight <n>/<B>)` stands.
156
155
 
157
156
  **No multi-select, no maintainer tick.** Selection and ordering are batched —
158
157
  one scan, oldest first; admission never is. The invariant this replaces — one
@@ -386,6 +385,29 @@ treat as a live scratch write until you have checked it.
386
385
 
387
386
  **A refutation resting on an injection nothing proved landed is not a refutation — it is a cell that never ran.** See references/reviewers.md.
388
387
 
388
+ **Guard: accumulate per PR, never conclude inside one run.** **Append one row to
389
+ `docs/metrics/tier-outcomes.tsv` when you rule each PR's review, with
390
+ `~/.fleet/bin/fleet-run tier-outcomes.mjs append <pr> --closed-own-ticket yes|no --minted-false-claim yes|no --note "<text>"`,
391
+ never by hand** (that file's header carries the column meanings). The script
392
+ fills `run_date`, `pr`, `ticket` and `tier` itself — `tier` from what actually
393
+ ran, off the implementer's `tier-ok=` ledger verdict or else its one
394
+ member-outcomes row (a `tier-unverifiable=` member has no ledger verdict, so
395
+ its one member-outcomes row decides), and BLANK with a WARNING naming why
396
+ when neither settles
397
+ it; never type a tier in over that. That append is the whole duty. See references/pull.md.
398
+
399
+ The row's last four fields are the ticket's difficulty, and they are what lets a
400
+ tier comparison condition on the thing that swamps it. `sizing` is the
401
+ **member's** own `light`/`heavy` verdict from its dispatched `sizing-a-ticket`
402
+ run, which `append` reads itself off the member's report file
403
+ (`<scratch>/reports/impl-<ticket>[-x].json`, its `sizing.verdict`, the highest
404
+ retry letter's when there are several), so you pass no `--sizing`; the member
405
+ also puts it in the PR body's `Sizing:` line, with the signal it turned on;
406
+ `profile`, `loc` and `files` all come from `diff-stats.mjs` over the merged
407
+ diff.
408
+
409
+ **A verdict is a measurement only if it post-dates the run that produced it.** See references/reviewers.md.
410
+
389
411
  ### Nothing to do: arm the beat
390
412
 
391
413
  - **Nothing to do right now** → **do not end your turn.** Arm the heartbeat
@@ -463,6 +485,18 @@ Act on each line the tick prints as its row reads here, then stop: the next wake
463
485
  ~/.fleet/bin/fleet-run fleet-tick.mjs --apply [--in-flight <n>]
464
486
  ```
465
487
 
488
+ Read the depth off the tick and never derive it by hand: act on the row it prints, and when it prints `SUGGEST /triage, hold idle`, hold the implementer row idle and suggest `/triage` in the report. See references/tick-rows.md.
489
+
490
+ **The review backlog never gates, and the merge-queue depth is still no
491
+ signal.** The only gate on a Pull is the In-flight count. A deep backlog is a
492
+ `DISPATCH review` on the same tick, never a reason to idle an implementer.
493
+ A starved implementer queue never stalls the review or merge side.
494
+ For the merge queue, see references/pull.md.
495
+
496
+ **Reconcile, do not wait for an event.** With the shortlist empty the tick's rows
497
+ govern — refresh, and never Pull a ticket the shortlist does not hold.
498
+ See references/pull.md.
499
+
466
500
  It reads the run itself, and nobody states a count. The In-flight count —
467
501
  every open PR that closes an issue off `gh`, plus every claimed ticket whose
468
502
  newest implementer is live or owed a replacement and has no such PR yet, off
@@ -482,12 +516,11 @@ row with `ledger.mjs row` and tick again. The In-flight bound defaults to 5
482
516
  and is fixed for the run: pass `--in-flight` on every tick when `$ARGUMENTS`
483
517
  set it, and never otherwise.
484
518
 
485
- It prints `actual/target` and a named ACTION per role, with the whole **Queue
486
- depth** guard table applied in code. Act on each line as it reads:
519
+ It prints `actual/target` and a named ACTION per role. Act on each line as it reads:
487
520
 
488
521
  - `PULL #<N> …` — one Pull per named ticket, in the order printed: `pull.mjs next --head <N>`, or the `pull.mjs answer` the row names (phase 1), then the dispatch under **Dispatching the Pull's member**.
489
522
  - `REFRESHED shortlist: <n> entries; <k> lifted` — the tick refreshed the shortlist itself, and its `PULL` row already reads the result; `REFRESH FAILED` is yours, since `shortlist.mjs` could not run.
490
- - `SUGGEST /triage, hold idle` — nothing is admissible after the refresh: suggest `/triage` in the report, never run it.
523
+ - `SUGGEST /triage, hold idle` — nothing is admissible after the refresh: suggest `/triage` in the report, never run it. The suggestion is a report, not a blocking prompt.
491
524
  - `specs-dropped=#<N> …` — never actionable: log those tickets as specs awaiting to-tickets, not as no work.
492
525
  - `DISPATCH fix-pr PR#<M> …` — a fix-applier for that PR (review findings, a dispositions mismatch no retry has answered, or a conflict hold): render its brief with `dispatch-prompt.mjs render fix-applier --kind <kind>`, `ledger.mjs dispatch <M> fix-pr-<M>` (the next suffix when that name is already on record), then the `task` call.
493
526
  - `DISPATCH review PR#<M> …` — a review per PR, oldest first, off your turn: record `review=member:review-pr-<M>` with `ledger.mjs review <M> …`, then dispatch `review-pr-<M>`.
@@ -1019,6 +1052,16 @@ report recovery, never the exit code alone.
1019
1052
 
1020
1053
  Each rule here is a judgement a driver stops for: answer it by the cause it names, then run the tick, and report to the maintainer what the evidence cannot settle.
1021
1054
 
1055
+ ### A member's claims
1056
+
1057
+ **Cross-check what members report about their environment.**
1058
+
1059
+ **Distrust negative claims hardest.** Make members state their search scope. Negative claim
1060
+ vs specific finding with paths → paths win. See references/reviewers.md.
1061
+
1062
+ Suspect a neighbour before a member's own diff — for unexplained failures, and
1063
+ equally for any **finding** that came from reading source.
1064
+
1022
1065
  ### Relabel causes
1023
1066
 
1024
1067
  - **Implementer bails before implementing** → `fleet-event.mjs impl-report impl-<N>`
@@ -1244,224 +1287,20 @@ this section's dispatch recipe — the cwd recipe above, the isolated-workspace
1244
1287
  measurement, or the teardown scoping — moves
1245
1288
  where the controller itself runs from.
1246
1289
 
1247
- ## Queue depth
1248
-
1249
- Read the depth off the tick and never derive it by hand: act on the row it prints, and when it prints `SUGGEST /triage, hold idle`, hold the implementer row idle and suggest `/triage` in the report.
1250
-
1251
- - **shortlist** — `.fleet/shortlist.json`'s entries with no `impl-` row and no
1252
- live `excluded` row: admissible, not yet pulled. The tick's `unclaimed=`.
1253
- Not after a refresh at unclaimed < B (below). A head whose Pull ended this
1254
- run is not counted, unless on a lifted exclusion.
1255
- - **supply** — `scanned`, the open `ready-for-agent` tickets `shortlist.mjs`'s
1256
- candidate scan returned, before any filter. Only a Pull's full read judges
1257
- decided?, so a shortlist of undecided tickets is supply that every Pull
1258
- relabels away — and relabelling is what stops it being counted next scan.
1259
- - **review backlog** — PRs owed a review. `fleet-tick.mjs` counts every
1260
- open PR without `ready-to-merge` **that closes an issue** and carries no `review=`
1261
- token on the ledger — one settled `=failed` is owed a review again, and so is
1262
- one whose latest finisher halted `past-pin` past its latest `reviewed=` head
1263
- (**Resolving a finisher halt**) — and it
1264
- prints them as `review-due=`. Exact, not a wider read: the ledger records
1265
- which PRs are under review or reviewed, so nothing about it is guesswork. The
1266
- closing-issue test is what keeps it from widening: a PR that closes no issue
1267
- — one you author yourself, for instance — is left unlabelled for the
1268
- maintainer, so no member of the fleet will ever review it, and counting it
1269
- floors the backlog at a depth nothing in the run can drain — the reviewer
1270
- row would then report a review owed for the rest of the run.
1271
- GitHub's own linked-issue set decides that, not a keyword regex over the body.
1272
- **That exemption is for a PR THIS run authors, never one a PRIOR run left
1273
- open** — step 0 folds those into this run's review queue, so count one when
1274
- you reason about backlog BY HAND. **The `fleet-tick.mjs` number still
1275
- excludes it**: nothing in its filter distinguishes an inherited PR from this
1276
- run's own, since a PR closing no issue fails the closing-issue test either
1277
- way — folding one in cannot reach the count. So the by-hand judgement and
1278
- the script's output differ here, and such a PR's review is dispatched by
1279
- hand.
1280
- - **In flight** — a ticket between its Claim and its PR's merge: an implementer
1281
- live or owed a replacement on it, or an open PR closing it. A PR closing
1282
- nothing is never in flight, and a ticket whose newest implementer settled
1283
- `blocked` is not either. The tick's `n`; the implementer row's counts read it
1284
- as `implementers=<i> prs=<p>`, n being i + p.
1285
- - **In-flight bound** — the most tickets a run lets be In flight at once, fixed
1286
- for the run, set by `/run-team`'s one argument. Reaching it holds every Pull
1287
- until a merge brings the count below it; reviewers, finishers and the merge
1288
- bot never slow for it. The tick's `B`.
1289
-
1290
- **Default: In-flight bound 5, fixed for the run.** See references/pull.md.
1291
-
1292
- **The review backlog never gates, and the merge-queue depth is still no
1293
- signal.** The only gate on a Pull is the In-flight count. A deep backlog is a
1294
- `DISPATCH review` on the same tick, never a reason to idle an implementer.
1295
- For the merge queue, see references/pull.md.
1296
-
1297
- **Reconcile, do not wait for an event.** With the shortlist empty the table
1298
- below governs — refresh, and never Pull a ticket the shortlist does not hold.
1299
- See references/pull.md.
1300
-
1301
- **Do not re-derive this by hand — `fleet-tick.mjs` computes it.** One invocation
1302
- prints per-role `actual/target` and an explicit ACTION with the backlog gate
1303
- above and every row of the table below already applied; every wake
1304
- ends in it. The deficit is then *computed, not remembered*, which is
1305
- the whole point: a table you must remember to consult is one you will not
1306
- consult under a merge-side event storm, and that is how implementers reached
1307
- 0/target with ~57 `ready-for-agent` in supply while nobody
1308
- noticed (#3). What follows stays here as the explanation of what the script decides —
1309
- never as a second, hand-run copy of it. The table's shortlist counts no head
1310
- whose Pull ended this run, unless on a lifted exclusion. A refresh at unclaimed < B leaves `unclaimed=` at
1311
- that count, so `SUGGEST /triage` can print it above 0.
1312
-
1313
- | shortlist (unclaimed) | implementer row prints |
1314
- |---|---|
1315
- | ≥ 1 | `PULL #N …`, one head per free slot — a Pull each (phase 1) |
1316
- | below the In-flight bound, missing or empty | the tick refreshes first — `REFRESHED shortlist: n entries; k lifted` — and Pulls from what it found |
1317
- | 0 after that refresh | `SUGGEST /triage, hold idle` |
1318
-
1319
- ### Specs, the refresh bound and the holds
1320
-
1321
- Whatever the row prints, a `specs-dropped=#<N> (…)` at the end of its counts
1322
- names the tickets the to-spec filter kept out of the shortlist — specs
1323
- awaiting to-tickets, so an empty shortlist carrying one is not "no work".
1324
-
1325
- The refresh runs at unclaimed < B and, with every other refresh trigger, never
1326
- while the bound holds: a refresh then cannot feed a Pull, and the first tick
1327
- back under the bound refreshes and Pulls in the same tick. Unclaimed there
1328
- counts no head whose Pull ended this run, unless on a lifted exclusion.
1329
-
1330
- Four holds outrank every row: `HOLD (draining)` once `ledger.mjs drain` has
1331
- recorded the drain, `HOLD (tier mismatch impl-<N>)` until a replacement is
1332
- dispatched at the right tier, `HOLD (tier unchecked impl-<N>)` until
1333
- `tier-check.mjs` has written its verdict on the newest implementer —
1334
- `tier-ok=`, `tier-mismatch=` or `tier-unverifiable=` — and
1335
- `HOLD (in-flight <n>/<B>)` when n tickets are between Claim and merge and
1336
- n ≥ B — refreshing to enable a Pull you are holding buys nothing.
1337
-
1338
- `HOLD (in-flight <n>/<B>)` ranks last of the four: the other three print in
1339
- its place. Its detail is the implementer row's counts, `implementers=<i>
1340
- prs=<p> unclaimed=<k> supply=<s>`. It is quiet — a merge brings n below B and
1341
- the next tick Pulls — except for a ticket whose newest implementer settled
1342
- `killed` or `tier-mismatch`: it counts as In flight with no PR that could
1343
- merge, so the row asks for the replacement's dispatch and names it in the
1344
- detail. The reviewer row reads `<units>/<B>`, its live review units against
1345
- the same bound, and never holds on it.
1346
-
1347
- A main checkout that is not `clean` against its Phase 0 baseline outranks all
1348
- of them, and every dispatching row besides: `HOLD (main checkout dirty)`,
1349
- `HOLD (main checkout unknown)` or `HOLD (main checkout no baseline)` on the
1350
- implementer, reviewer and merge-bot rows alike, beside the `MAIN-CHECKOUT-*`
1351
- line that says why.
1352
-
1353
- `/triage` is user-invoked only — suggest, never run. The suggestion is a report,
1354
- not a blocking prompt. Counts come from cheap `gh issue list --search`, no bodies.
1355
- A starved implementer queue never stalls the review or merge side.
1356
-
1357
- ## Tier outcomes guard
1358
-
1359
- This guard is done when you have run `tier-outcomes.mjs append` once for each PR whose review you rule; never conclude a tier result inside one run.
1360
-
1361
- **Guard: accumulate per PR, never conclude inside one run.** The unit is the PR —
1362
- supply is one Pull per free slot, so there are no implementer batches. **Append one row to
1363
- `docs/metrics/tier-outcomes.tsv` when you rule each PR's review, with
1364
- `~/.fleet/bin/fleet-run tier-outcomes.mjs append <pr> --closed-own-ticket yes|no --minted-false-claim yes|no --note "<text>"`,
1365
- never by hand** (that file's header carries the column meanings). The script
1366
- fills `run_date`, `pr`, `ticket` and `tier` itself — `tier` from what actually
1367
- ran, off the implementer's `tier-ok=` ledger verdict or else its one
1368
- member-outcomes row (a `tier-unverifiable=` member has no ledger verdict, so
1369
- its one member-outcomes row decides), and BLANK with a WARNING naming why
1370
- when neither settles
1371
- it; never type a tier in over that. A PR that already has a row is left
1372
- untouched, so re-running it is safe. That append is the whole duty.
1373
-
1374
- The row's last four fields are the ticket's difficulty, and they are what lets a
1375
- tier comparison condition on the thing that swamps it. `sizing` is the
1376
- **member's** own `light`/`heavy` verdict from its dispatched `sizing-a-ticket`
1377
- run, which `append` reads itself off the member's report file
1378
- (`<scratch>/reports/impl-<ticket>[-x].json`, its `sizing.verdict`, the highest
1379
- retry letter's when there are several), so you pass no `--sizing`; the member
1380
- also puts it in the PR body's `Sizing:` line, with the signal it turned on;
1381
- `profile`, `loc` and `files` all come from `diff-stats.mjs` over the merged
1382
- diff. **Phase 0 does not size anything** — it shortlists, a Pull claims, and
1383
- the sizing run happens inside the member after both, which is why the verdict
1384
- has to travel in the member's report rather than being something you already hold.
1385
-
1386
- **A verdict is a measurement only if it post-dates the run that produced it.** See references/reviewers.md.
1387
-
1388
- The member-facts scrape, the drain of the pending ticket-features rows and the
1389
- commit of the metrics files are steps of `closeout.mjs finish`, which **End of
1390
- run** (**What a wake owes beyond its call**) drives. See references/close-out.md.
1391
-
1392
- **Why not decide inside one run.** A run holds 2-3 implementer PRs, and ticket
1393
- difficulty swamps the tier effect — a gojq parity harness and a two-statement
1394
- shell reorder are not comparable units. Finding-counts are not comparable either:
1395
- `selectDimensions` trims the fan-out by diff profile, so one PR fields 29 agents
1396
- over six dimensions and its sibling fields 8 over three. And the fleet is not
1397
- stationary — this prompt gets edited mid-run when a defect earns it. Hence a
1398
- binary outcome per PR, accumulating, rather than a per-run verdict.
1399
-
1400
- **Why the rows this guard fired on could not answer the tier question, and this
1401
- is the load-bearing part.** Until the 2026-08-16 revert, `class` and `tier` were
1402
- perfectly confounded: routine always ran `sonnet`, correction always ran top
1403
- tier. So the comparison the file invites — routine-at-sonnet against
1404
- correction-at-top-tier — measures **class, not tier**, because those classes fail
1405
- in different ways by construction: corrections ship false claims in prose,
1406
- routines ship incomplete or regressing code. A run that "mixes both" does not fix
1407
- it. **Only a same-class comparison across tiers is informative**, and the old
1408
- rule could not produce one, since no routine ticket ever ran at top tier.
1409
-
1410
- **That last sentence is no longer true, and this is the part to re-read.** Since
1411
- the revert every routine ticket runs at top tier, so the file now holds 55
1412
- `routine`/`opus` rows against 11 `routine`/`sonnet` (as of 2026-08-28, 103 rows
1413
- — recount before citing). The same-class comparison the paragraph above calls
1414
- the only informative one therefore EXISTS now. It is still not clean: the two
1415
- groups are split almost entirely by calendar date, so it measures prompt
1416
- evolution at least as much as tier. The counter-evidence paragraph earlier in
1417
- this section carries the split and the three rows that cut against it. Recount
1418
- both, and every other figure in this section, with:
1419
-
1420
- ```bash
1421
- grep -vc '^#' docs/metrics/tier-outcomes.tsv
1422
- awk -F'\t' '!/^#/ && $4=="routine" {n++; d[$1]=1; if($6=="no") no++} \
1423
- END{print n, length(d), no+0}' docs/metrics/tier-outcomes.tsv
1424
- awk -F'\t' '!/^#/ && $4=="routine" {t[$5]++; if($6=="no") f[$5]++} \
1425
- END{for(k in t) print k, t[k], f[k]+0}' docs/metrics/tier-outcomes.tsv
1426
- ```
1427
-
1428
- **These rows are the historical corpus, and nothing appended to them fixes the
1429
- confound.** What fixes it is the exploration draw in the dispatch rule above:
1430
- one implementer in every five — every one, during burn-in — runs at a drawn
1431
- cell beside the policy cell's, against the same prompts on the same day, so
1432
- the comparison stops depending on which tier history happened to leave in
1433
- which week. Read each cell's comparisons, not the whole-file split, once there
1434
- are enough of them.
1435
-
1436
- **So read what the guard actually established: routine tickets sometimes fail to
1437
- close their own ticket. Not that `sonnet` caused it.** The revert is the
1438
- pre-committed rule being honoured, not a measurement. Restoring a cheap tier is
1439
- still the maintainer's call and still a change to the dispatch rule — but the
1440
- deliberate control it used to require is no longer something anyone has to
1441
- authorize one ticket at a time: the exploration draw above produces one in
1442
- every five Pulls by construction. **Do not read a cell early.** A cell's
1443
- comparison is one session holding an admissible row at that cell — its tier
1444
- check passed and its resolved effort is the cell's level — and one at
1445
- `slow-high` whose resolved model and effort differ. Count them per cell, never
1446
- pooled across cells, and stop until
1447
- there are at least ten of them across five or more distinct `run_date`s; below
1448
- that, a comparison count is a number, not evidence, and the last guard fired
1449
- with n=1 on the control side. **Report the count the per-cell readout prints,
1450
- never one from any other query:** `~/.fleet/bin/fleet-run cell-readout.mjs`
1451
- prints `<cell> <comparisons> <run_dates> <resolved models>` for each cell past
1452
- that floor and a cell below it only as a count on stderr; a `mixed (…)` models
1453
- column means the cell's history spans more than one model behind its role.
1454
- The pre-cell pair query counted pairs that are no cell's comparisons, and the
1455
- query before that one counted every session whose implementers merely
1456
- differed, so it cleared this floor by an order of magnitude while the
1457
- controlled comparison did not exist yet.
1458
-
1459
1290
  ## Why the implementer body carries what it does
1460
1291
 
1461
1292
  Read this before you edit any `fleet-implementer-<cell>` file, and stop editing when every cell's body is byte-identical again.
1462
1293
 
1463
1294
  Why the agent body carries what it does — read this before editing any `fleet-implementer-<cell>` file, and keep every cell's body byte-identical. See references/pull.md.
1464
1295
 
1296
+ - **Every implementer body carries the claim discipline, because tickets that restate facts ship new wrong claims** — inherited from the ticket, and minted in prose nobody asked for. See references/correction-tickets.md.
1297
+ - **The settling command for "which files read X" must never be a literal-path
1298
+ grep.** Replace the file with `MUTATED` in a throwaway copy, run the suite, and read off
1299
+ which suites red: that answers which ones **depend on its contents**, which is
1300
+ the question a coverage claim actually needs, and it returns a different set
1301
+ from any spelling of the grep. See references/correction-tickets.md.
1302
+ - **A settling command over literal text uses `grep -F`, and its zero counts only after the same command finds a line known to match.** See references/correction-tickets.md.
1303
+
1465
1304
  **Prose-first** and **wrongly-REFUSE** are not padding — they are the case
1466
1305
  which shipped with **enumerate-and-declare** already in its prompt. It missed a
1467
1306
  shape its own ticket named in passing, because it enumerated from the mechanism
@@ -1578,10 +1417,6 @@ review-due. A
1578
1417
  runner member that is killed rather than failed follows **Failure
1579
1418
  handling** (fresh name, inherited state stated).
1580
1419
 
1581
- ## Guards
1582
-
1583
- Each guard here is a check to run before the step it guards: run it, and where it fails, stop and report rather than go on.
1584
-
1585
1420
  **Verify every reported SHA.** `~/.fleet/bin/fleet-run verify-sha.sh
1586
1421
  <branch> <sha>` before enqueueing — a member can commit in a nested worktree,
1587
1422
  leaving the SHA on a stray branch while its report reads normally. Exit codes as
@@ -1597,14 +1432,7 @@ ticket over a failure a re-run would have cleared.
1597
1432
 
1598
1433
  **Never `--delete-branch` — the merge bot deletes the head branch itself.**
1599
1434
  `gh pr merge <n> --merge` alone: the flag's local cleanup errors on a `main`
1600
- held by another worktree, or strands the feature worktree on `main`. After the
1601
- merge, `delete-merged-branch.sh <pr>` deletes the branch from `origin` and reads
1602
- the deletion back; `reap.sh` takes the `[gone]` local branch and its worktree.
1603
- No repo setting is relied on to do it. A branch another open PR still
1604
- uses as its head or base is kept instead — `branch-kept-#<that PR>` in the bot's
1605
- report, a deliberate keep and not a failure.
1606
- The branch-delete step also releases the claim label: it drops `in-progress` from
1607
- every issue the PR closes.
1435
+ held by another worktree, or strands the feature worktree on `main`.
1608
1436
 
1609
1437
  **Never force a rebase to start.** No `git clean`, `git checkout .`,
1610
1438
  `git reset --hard`, `git stash`. Uncommitted changes may exist nowhere else.
@@ -1614,14 +1442,6 @@ empty answer here and licenses the rebase over work this check exists to
1614
1442
  protect. Stashing empties it either way, so that check and
1615
1443
  `no-undo-audit.sh` both go quiet on work nothing else holds.
1616
1444
 
1617
- **Cross-check what members report about their environment.** Wrong often enough
1618
- to matter, and a confident wrong report from a reviewer flips a verdict.
1619
-
1620
- **Distrust negative claims hardest.** "Nothing else references this", "the sweep
1621
- is clean" — most likely false, least likely checked: a grep that found nothing
1622
- looks like a grep never run. Make members state their search scope. Negative claim
1623
- vs specific finding with paths → paths win.
1624
-
1625
1445
  **Members share one filesystem, one docker stack and one process table**, and the
1626
1446
  failures arrive as *wrong findings*, not errors:
1627
1447
 
@@ -1640,70 +1460,18 @@ failures arrive as *wrong findings*, not errors:
1640
1460
  and the controller re-checks any measurement taken in the window.
1641
1461
 
1642
1462
  **Do not guess the victim — the blast radius is the machine, not the Pull.**
1643
- In that incident the controller reasoned from dispatch scope to "almost
1644
- certainly `impl-<N>`" and told the maintainer so; the named member then proved
1645
- it was not the victim (it had never invoked those files standalone, and its
1646
- mutation baselines post-dated the window). Idle members of *previous* runs,
1647
- other omp sessions on the same machine, and the maintainer's own shell are
1648
- all `pkill -f` targets and none of them appear in your ledger. Warn every live
1463
+ Warn every live
1649
1464
  member, record the incident as unattributed, and re-check measurements by
1650
- timestamp rather than by who you think was running.
1465
+ timestamp rather than by who you think was running. See references/isolation.md.
1651
1466
 
1652
1467
  - **A warning acted on silently is indistinguishable from one that never
1653
- arrived.** The same incident: the controller's warning DID reach the member,
1654
- which re-ran, found nothing changed, and said nothing — correctly reading
1655
- "send once, never re-send" as covering it. The controller then had to spend a
1656
- round trip asking whether its own outbox was lossy. So the briefs say that
1657
- **confirmation state includes warnings received and acted on, even
1658
- when the outcome is no change** — one line, in the report the member already
1659
- owes. That is the cheap half of the *never read silence as assent* rule, paid
1660
- by the member rather than by the controller's guesswork.
1661
-
1662
- - **Specialist tree isolation is `review-and-fix.md`'s job — do not restate it.**
1663
- It owns the object-store rule, the two-tree split, snapshot provisioning, and
1664
- which suites a snapshot cannot validly run. Your only duty is to *not*
1665
- contradict it.
1666
- - **Filesystem isolation is not stack isolation.** A private copy of the
1667
- tree and a test command solve different problems; the compose project
1668
- name comes from the environment, not the working directory, so three
1669
- agents on three snapshots still collide on one postgres. "I'm on my own
1670
- copy" is exactly the intuition that skips the command — say both, every
1671
- time. Which command depends on the audience: a member in a worktree
1672
- uses `./agent-test`; a specialist on a snapshot does not, and takes the
1673
- one `review-and-fix.md` hands out. Which runner the worktree holds
1674
- decides why: the runner a claim writes is untracked, so a `git archive`
1675
- snapshot never carries it; one the repository tracks does ride along,
1676
- but no claim exported ports into it — its isolation is whatever that
1677
- repository built, not one triple per snapshot. See
1678
- references/isolation.md.
1468
+ arrived.** See references/isolation.md.
1469
+ - **Specialist tree isolation is `review-and-fix.md`'s job — do not restate it.** See references/isolation.md.
1470
+ - **Filesystem isolation is not stack isolation.** See references/isolation.md.
1679
1471
  - **Scratchpad paths need two levels — the member's own partition, then one
1680
- directory per child it dispatches — and nothing outside them.** An
1681
- implementer partitions at `<scratch>/impl-<N>/`; the review side's partition
1682
- is `<scratch>/pr<N>/…`, which the review core creates itself, so a review
1683
- runner is handed the scratch root, never that partition; any member that
1684
- dispatches a child writes an absolute directory under its own partition into
1685
- that child's prompt, one per child and never shared, and a child never
1686
- derives its own. Finding ids restart at 1 every review, so two fix-appliers
1687
- on different PRs both reach for `unv1`; one
1688
- agent overwrote a sibling's `package.json`, and a probe built a git repo at
1689
- the *checkout root*. Read from the object store at a pinned ref, write only
1690
- under your own path. See references/isolation.md.
1691
- - **IDE/harness diagnostics attribute by bare filename, with no path.** **Never
1692
- relay a diagnostic without reproducing it in that member's specific worktree**
1693
- (re-run the check that raised it from there): probe copies carry the real
1694
- tree's filenames, so a sibling's throwaway mutation reads exactly like a live
1695
- worktree's error.
1696
- See references/isolation.md.
1697
- - **Every implementer body carries the claim discipline, because tickets that restate facts ship new wrong claims** — inherited from the ticket, and minted in prose nobody asked for. See references/correction-tickets.md.
1698
- - **The settling command for "which files read X" must never be a literal-path
1699
- grep.** Replace the file with `MUTATED` in a throwaway copy, run the suite, and read off
1700
- which suites red: that answers which ones **depend on its contents**, which is
1701
- the question a coverage claim actually needs, and it returns a different set
1702
- from any spelling of the grep. See references/correction-tickets.md.
1703
- - **A settling command over literal text uses `grep -F`, and its zero counts only after the same command finds a line known to match.** See references/correction-tickets.md.
1704
-
1705
- Suspect a neighbour before a member's own diff — for unexplained failures, and
1706
- equally for any **finding** that came from reading source.
1472
+ directory per child it dispatches — and nothing outside them.** See references/isolation.md.
1473
+ - **Never relay a diagnostic without reproducing it in that member's specific worktree**
1474
+ (re-run the check that raised it from there). See references/isolation.md.
1707
1475
 
1708
1476
  ## Red flags
1709
1477
 
@@ -1752,3 +1520,15 @@ One running table, updated as events land:
1752
1520
  ```
1753
1521
 
1754
1522
  Plus a queue-depth line: shortlist, supply, whether triage was suggested.
1523
+
1524
+ **Do not read a cell early.** A cell's
1525
+ comparison is one session holding an admissible row at that cell — its tier
1526
+ check passed and its resolved effort is the cell's level — and one at
1527
+ `slow-high` whose resolved model and effort differ. Count them per cell, never
1528
+ pooled across cells, and stop until
1529
+ there are at least ten of them across five or more distinct `run_date`s; below
1530
+ that, a comparison count is a number, not evidence. **Report the count the per-cell readout prints,
1531
+ never one from any other query:** `~/.fleet/bin/fleet-run cell-readout.mjs`
1532
+ prints `<cell> <comparisons> <run_dates> <resolved models>` for each cell past
1533
+ that floor and a cell below it only as a count on stderr; a `mixed (…)` models
1534
+ column means the cell's history spans more than one model behind its role. See references/pull.md.
@@ -1,6 +1,6 @@
1
1
  # Isolation: filesystem vs stack
2
2
 
3
- Why isolation envelope file not briefing, why private filesystem not private stack. Assertions these justify live in references/pull.md Claiming section and SKILL.md Guards section; evidence here.
3
+ Why isolation envelope file not briefing, why private filesystem not private stack. Assertions these justify live in references/pull.md Claiming section and SKILL.md Failure handling section; evidence here.
4
4
 
5
5
  ## Materialize the isolation envelope as a file, not a briefing
6
6
 
@@ -262,3 +262,29 @@ get is not yours to choose — members are briefed on `./agent-test` with the
262
262
  arguments the Test entrypoint takes, and the runner hands them on exactly as
263
263
  its caller's shell expanded them. Clearing your own leftover process needs its
264
264
  PID, not a pattern.
265
+
266
+ ## Do not guess the victim — the blast radius is the machine, not the Pull
267
+
268
+ In the `pkill -f` incident "A pattern kill reaches siblings" measures, the controller reasoned from dispatch scope to "almost
269
+ certainly `impl-<N>`" and told the maintainer so; the named member then proved
270
+ it was not the victim (it had never invoked those files standalone, and its
271
+ mutation baselines post-dated the window). Idle members of *previous* runs,
272
+ other omp sessions on the same machine, and the maintainer's own shell are
273
+ all `pkill -f` targets and none of them appear in your ledger.
274
+
275
+ ## A warning acted on silently is indistinguishable from one that never arrived
276
+
277
+ The same `pkill -f` incident: the controller's warning DID reach the member,
278
+ which re-ran, found nothing changed, and said nothing — correctly reading
279
+ "send once, never re-send" as covering it. The controller then had to spend a
280
+ round trip asking whether its own outbox was lossy. So the briefs say that
281
+ **confirmation state includes warnings received and acted on, even
282
+ when the outcome is no change** — one line, in the report the member already
283
+ owes. That is the cheap half of the *never read silence as assent* rule, paid
284
+ by the member rather than by the controller's guesswork.
285
+
286
+ ## Specialist tree isolation is `review-and-fix.md`'s job
287
+
288
+ It owns the object-store rule, the two-tree split, snapshot provisioning, and
289
+ which suites a snapshot cannot validly run. Your only duty is to *not*
290
+ contradict it.
@@ -426,6 +426,71 @@ is not enough either: the two rows must actually have RUN a different model or
426
426
  effort, because a deliberate dispatch whose two sides resolve to the same model
427
427
  (a `modelRoles` override, measured on omp 2026-09-12) controls nothing.
428
428
 
429
+ ## Accumulate per PR, never conclude a tier result inside one run
430
+
431
+ The unit is the PR —
432
+ supply is one Pull per free slot, so there are no implementer batches. The
433
+ guard this section speaks of is SKILL.md's **Guard: accumulate per PR, never
434
+ conclude inside one run**, under **Reading a review result**.
435
+
436
+ **Why not decide inside one run.** A run holds 2-3 implementer PRs, and ticket
437
+ difficulty swamps the tier effect — a gojq parity harness and a two-statement
438
+ shell reorder are not comparable units. Finding-counts are not comparable either:
439
+ `selectDimensions` trims the fan-out by diff profile, so one PR fields 29 agents
440
+ over six dimensions and its sibling fields 8 over three. And the fleet is not
441
+ stationary — this prompt gets edited mid-run when a defect earns it. Hence a
442
+ binary outcome per PR, accumulating, rather than a per-run verdict.
443
+
444
+ **Why the rows this guard fired on could not answer the tier question, and this
445
+ is the load-bearing part.** Until the 2026-08-16 revert, `class` and `tier` were
446
+ perfectly confounded: routine always ran `sonnet`, correction always ran top
447
+ tier. So the comparison the file invites — routine-at-sonnet against
448
+ correction-at-top-tier — measures **class, not tier**, because those classes fail
449
+ in different ways by construction: corrections ship false claims in prose,
450
+ routines ship incomplete or regressing code. A run that "mixes both" does not fix
451
+ it. **Only a same-class comparison across tiers is informative**, and the old
452
+ rule could not produce one, since no routine ticket ever ran at top tier.
453
+
454
+ **That last sentence is no longer true, and this is the part to re-read.** Since
455
+ the revert every routine ticket runs at top tier, so the file now holds 55
456
+ `routine`/`opus` rows against 11 `routine`/`sonnet` (as of 2026-08-28, 103 rows
457
+ — recount before citing). The same-class comparison the paragraph above calls
458
+ the only informative one therefore EXISTS now. It is still not clean: the two
459
+ groups are split almost entirely by calendar date, so it measures prompt
460
+ evolution at least as much as tier. Recount
461
+ both, and every other figure in this section, with:
462
+
463
+ ```bash
464
+ grep -vc '^#' docs/metrics/tier-outcomes.tsv
465
+ awk -F'\t' '!/^#/ && $4=="routine" {n++; d[$1]=1; if($6=="no") no++} \
466
+ END{print n, length(d), no+0}' docs/metrics/tier-outcomes.tsv
467
+ awk -F'\t' '!/^#/ && $4=="routine" {t[$5]++; if($6=="no") f[$5]++} \
468
+ END{for(k in t) print k, t[k], f[k]+0}' docs/metrics/tier-outcomes.tsv
469
+ ```
470
+
471
+ **These rows are the historical corpus, and nothing appended to them fixes the
472
+ confound.** What fixes it is the exploration draw in the dispatch rule above:
473
+ one implementer in every five — every one, during burn-in — runs at a drawn
474
+ cell beside the policy cell's, against the same prompts on the same day, so
475
+ the comparison stops depending on which tier history happened to leave in
476
+ which week. Read each cell's comparisons, not the whole-file split, once there
477
+ are enough of them.
478
+
479
+ **So read what the guard actually established: routine tickets sometimes fail to
480
+ close their own ticket. Not that `sonnet` caused it.** The revert is the
481
+ pre-committed rule being honoured, not a measurement. Restoring a cheap tier is
482
+ still the maintainer's call and still a change to the dispatch rule — but the
483
+ deliberate control it used to require is no longer something anyone has to
484
+ authorize one ticket at a time: the exploration draw above produces one in
485
+ every five Pulls by construction.
486
+
487
+ The last guard fired with n=1 on the control side. The pre-cell pair query
488
+ counted pairs that are no cell's comparisons, and the query before that one
489
+ counted every session whose implementers merely differed, so it cleared the
490
+ cell floor SKILL.md's **Report** states — at least ten comparisons across five
491
+ or more distinct `run_date`s — by an order of magnitude while the controlled
492
+ comparison did not exist yet.
493
+
429
494
  ## Why the implementer body carries enumerate-and-declare
430
495
 
431
496
  Why the agent body carries what it does — read this before editing any
@@ -27,6 +27,10 @@ in-between width cannot be told apart from a shifted one. The `note` field is
27
27
  free text and sits before those four, so the script refuses a tab or newline in
28
28
  it: one tab would shift all four for that row alone.
29
29
 
30
+ **Phase 0 does not size anything** — it shortlists, a Pull claims, and
31
+ the sizing run happens inside the member after both, which is why the verdict
32
+ has to travel in the member's report rather than being something you already hold.
33
+
30
34
  ## Know the trim before you rely on it
31
35
 
32
36
  **Know the trim before you rely on it — it is wider than `docsOnly` suggests.**
@@ -135,3 +139,12 @@ reviewed and is not.
135
139
  ## The review's scratch is the scratch root, not its partition
136
140
 
137
141
  **`scratch` is the scratch root itself, the absolute path `scratch-root.mjs` prints, never the review side's `<scratch>/pr<N>` partition:** the review creates `pr<N>/` under it, so a `pr<N>` passed in nests every run root at `pr<N>/pr<N>/`, and the review refuses a `scratch` whose last component is `pr` plus digits before dispatching anything.
142
+
143
+ ## Cross-check what members report, and distrust negative claims hardest
144
+
145
+ Wrong often enough
146
+ to matter, and a confident wrong report from a reviewer flips a verdict.
147
+
148
+ "Nothing else references this", "the sweep
149
+ is clean" — most likely false, least likely checked: a grep that found nothing
150
+ looks like a grep never run.
@@ -14,7 +14,7 @@ The full text of each row `fleet-tick.mjs` prints and what it asks of the contro
14
14
  lifted), the implementer row's `unclaimed=` prints that same
15
15
  `<u>`, not the refreshed file's count.
16
16
  - `SUGGEST /triage, hold idle` — nothing is admissible after the refresh.
17
- Suggest, never run (**Queue depth**). A head whose Pull already ended in
17
+ Suggest, never run. A head whose Pull already ended in
18
18
  this run is not counted toward the refresh, so a shortlist of only such
19
19
  heads triggers the refresh before this row prints. It is not printed while the
20
20
  refreshed shortlist holds a head whose Pull ended only on an exclusion whose
@@ -245,3 +245,11 @@ The full text of each row `fleet-tick.mjs` prints and what it asks of the contro
245
245
  could-not-look (exit 2), never a mismatch. The settle runs neither command,
246
246
  and a repository whose Recipe cache is absent or refused has no gate; the
247
247
  record is the member's own word, at the trust level of its report.
248
+
249
+ ## Read the depth off the tick, never derive it by hand
250
+
251
+ **Do not re-derive this by hand — `fleet-tick.mjs` computes it.** The deficit is then *computed, not remembered*, which is
252
+ the whole point: a table you must remember to consult is one you will not
253
+ consult under a merge-side event storm, and that is how implementers reached
254
+ 0/target with ~57 `ready-for-agent` in supply while nobody
255
+ noticed.