@feigi/fleet-ctl 3.82.15 → 3.82.17

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.17",
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
@@ -305,6 +304,31 @@ A row with `review=` and no `reviewed=` is a review in flight, and the tick coun
305
304
  - **The merge bot.** Named `merge-bot-<n>`; `ledger.mjs dispatch merge-bot` numbers it. Never two at once. Render `--member merge-bot-<n> --n <n> --scratch <scratch>`. Dispatch with the printed agent, omit `model`, and dispatch on the first `ready-to-merge` label without waiting for the finisher report.
306
305
  - **The finisher.** Its render and receives are in `references/finisher.md`.
307
306
 
307
+ **A fleet member is never dispatched with omp's `isolated: true`.** See references/isolation.md.
308
+
309
+ **A member's tree is the claimed worktree, addressed by absolute path.** See references/isolation.md.
310
+
311
+ **The controller's own cwd stays the main checkout, unchanged.** See references/isolation.md.
312
+
313
+ **Name every member.**
314
+ Names follow the unit of work: `impl-<issue#>`, `fix-pr-<pr#>`,
315
+ `review-pr-<pr#>`, `finisher-pr-<pr#>`, `merge-bot-<n>`. See
316
+ references/member-lifecycle.md.
317
+
318
+ **Fresh context per member.** One member, one unit of work, gone. Refill =
319
+ **new** member, **new** name. See references/member-lifecycle.md.
320
+
321
+ **The scratch root is computed, never invented and never recorded.** You run
322
+ `~/.fleet/bin/fleet-run scratch-root.mjs`; a non-zero exit is an environment to
323
+ fix, not a cue to pick another root.
324
+ `<scratch>` in this file is the path it prints, resolved before you send anything: every
325
+ brief that carries one carries the absolute path you passed it — an
326
+ implementer's `<scratch>/impl-<N>/`, the recipe deriver's
327
+ `<scratch>/derive-recipe/`, a fix-applier's, a finisher's and a merge bot's
328
+ `<scratch>/pr<N>/…`, and a review runner's `scratch` argument, which is the
329
+ root itself. A member whose prompt names no path asks you by name and never
330
+ makes one up. See references/isolation.md.
331
+
308
332
  ### Reading a review result
309
333
 
310
334
  **You read the digest; the findings are the fix-applier's.**
@@ -386,6 +410,29 @@ treat as a live scratch write until you have checked it.
386
410
 
387
411
  **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
412
 
413
+ **Guard: accumulate per PR, never conclude inside one run.** **Append one row to
414
+ `docs/metrics/tier-outcomes.tsv` when you rule each PR's review, with
415
+ `~/.fleet/bin/fleet-run tier-outcomes.mjs append <pr> --closed-own-ticket yes|no --minted-false-claim yes|no --note "<text>"`,
416
+ never by hand** (that file's header carries the column meanings). The script
417
+ fills `run_date`, `pr`, `ticket` and `tier` itself — `tier` from what actually
418
+ ran, off the implementer's `tier-ok=` ledger verdict or else its one
419
+ member-outcomes row (a `tier-unverifiable=` member has no ledger verdict, so
420
+ its one member-outcomes row decides), and BLANK with a WARNING naming why
421
+ when neither settles
422
+ it; never type a tier in over that. That append is the whole duty. See references/pull.md.
423
+
424
+ The row's last four fields are the ticket's difficulty, and they are what lets a
425
+ tier comparison condition on the thing that swamps it. `sizing` is the
426
+ **member's** own `light`/`heavy` verdict from its dispatched `sizing-a-ticket`
427
+ run, which `append` reads itself off the member's report file
428
+ (`<scratch>/reports/impl-<ticket>[-x].json`, its `sizing.verdict`, the highest
429
+ retry letter's when there are several), so you pass no `--sizing`; the member
430
+ also puts it in the PR body's `Sizing:` line, with the signal it turned on;
431
+ `profile`, `loc` and `files` all come from `diff-stats.mjs` over the merged
432
+ diff.
433
+
434
+ **A verdict is a measurement only if it post-dates the run that produced it.** See references/reviewers.md.
435
+
389
436
  ### Nothing to do: arm the beat
390
437
 
391
438
  - **Nothing to do right now** → **do not end your turn.** Arm the heartbeat
@@ -417,6 +464,7 @@ It blocks, then prints one line. Which line it is, is the whole protocol:
417
464
  not a reason to end the run; they are the state a newly triaged ticket arrives
418
465
  into. The run ends when you drain it, when the budget goes, or when this
419
466
  context does — not because there was briefly nothing to do.
467
+ Zeroes on the cockpit are not yet proof the pipeline is idle.
420
468
 
421
469
  **When you stop on purpose, say why — one command, and it is the last thing
422
470
  you owe the next run.** See references/ci-and-staleness.md.
@@ -463,6 +511,18 @@ Act on each line the tick prints as its row reads here, then stop: the next wake
463
511
  ~/.fleet/bin/fleet-run fleet-tick.mjs --apply [--in-flight <n>]
464
512
  ```
465
513
 
514
+ 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.
515
+
516
+ **The review backlog never gates, and the merge-queue depth is still no
517
+ signal.** The only gate on a Pull is the In-flight count. A deep backlog is a
518
+ `DISPATCH review` on the same tick, never a reason to idle an implementer.
519
+ A starved implementer queue never stalls the review or merge side.
520
+ For the merge queue, see references/pull.md.
521
+
522
+ **Reconcile, do not wait for an event.** With the shortlist empty the tick's rows
523
+ govern — refresh, and never Pull a ticket the shortlist does not hold.
524
+ See references/pull.md.
525
+
466
526
  It reads the run itself, and nobody states a count. The In-flight count —
467
527
  every open PR that closes an issue off `gh`, plus every claimed ticket whose
468
528
  newest implementer is live or owed a replacement and has no such PR yet, off
@@ -482,12 +542,11 @@ row with `ledger.mjs row` and tick again. The In-flight bound defaults to 5
482
542
  and is fixed for the run: pass `--in-flight` on every tick when `$ARGUMENTS`
483
543
  set it, and never otherwise.
484
544
 
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:
545
+ It prints `actual/target` and a named ACTION per role. Act on each line as it reads:
487
546
 
488
547
  - `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
548
  - `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.
549
+ - `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
550
  - `specs-dropped=#<N> …` — never actionable: log those tickets as specs awaiting to-tickets, not as no work.
492
551
  - `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
552
  - `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 +1078,35 @@ report recovery, never the exit code alone.
1019
1078
 
1020
1079
  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
1080
 
1081
+ ### A member's claims
1082
+
1083
+ **Cross-check what members report about their environment.**
1084
+
1085
+ **Distrust negative claims hardest.** Make members state their search scope. Negative claim
1086
+ vs specific finding with paths → paths win. See references/reviewers.md.
1087
+
1088
+ Suspect a neighbour before a member's own diff — for unexplained failures, and
1089
+ equally for any **finding** that came from reading source.
1090
+
1091
+ **Never read a member's silence as assent**, and require members to report their *confirmation state*
1092
+ alongside results — "I escalated X, received no ruling, proceeded on default Y".
1093
+ After any ruling
1094
+ you cannot confirm was received, re-check the artifacts it governed. See references/member-lifecycle.md.
1095
+
1096
+ ### Re-check the instruments before a gate
1097
+
1098
+ **You read your instruments out of a tree every member can write to — re-check them before every gate decision.** See references/instruments.md.
1099
+
1100
+ - **Re-check before you act on any instrument reading** — a SHA acceptance, a CI
1101
+ verdict, the reconcile, a reap, a label gate — from the main checkout phase 0 pinned from:
1102
+ `~/.fleet/bin/fleet-run instruments.sh`. **Exit 0 is the only code that
1103
+ lets a gate proceed. Exit 1 (the set changed) and exit 2 (the check could not
1104
+ answer) both refuse: report what it printed and do NOT re-read the
1105
+ instrument** — a guard that fails open on "could not look" protects nothing.
1106
+ - **Re-pin only after a change you made deliberately.** **A mid-run tooling fix**
1107
+ is the one legitimate writer. Re-pinning to clear a refusal you cannot
1108
+ explain discards the check.
1109
+
1022
1110
  ### Relabel causes
1023
1111
 
1024
1112
  - **Implementer bails before implementing** → `fleet-event.mjs impl-report impl-<N>`
@@ -1035,6 +1123,9 @@ Each rule here is a judgement a driver stops for: answer it by the cause it name
1035
1123
  The call comments the cause and releases the claim — never when `fixed` neither closes nor relabels N — then ticks:
1036
1124
  the head the tick names next is a *different* ticket.
1037
1125
 
1126
+ A member that bails demotes nothing itself; demotion by cause is yours
1127
+ (**Implementer bails before implementing**).
1128
+
1038
1129
  ### Resolving a finisher halt
1039
1130
 
1040
1131
  A halt at duty 1 reads identical from a bare SHA mismatch whatever caused it,
@@ -1113,414 +1204,6 @@ and flag the PR for a human. A new review of the PR counts again from none.
1113
1204
  | Merge bot's gate finds the merge would remove lines `main` gained after the PR's work began (`main-gain-removed:<path>`, exit 1) | Relay `main-gain-removed:<path>-#<pr>` to a human with the hit list and the acknowledgement keys the bot printed. PR stays queued, label untouched, nothing written to the ledger, and no member is dispatched to restore the lines — the removal may be deliberate. It clears when the PR stops removing them or its body carries `main-gain-removal: <path> <key> - <why>` for each hit |
1114
1205
  | Merge bot's gate cannot answer the main-gain check (`main-gain-unanswerable`, exit 2) | Report `main-gain-unanswerable-#<pr>` with what it printed, PR stays queued, label untouched |
1115
1206
 
1116
- ## Rules that fail silently
1117
-
1118
- These rules fail silently when they are skipped: check each before you act on a member's silence, and stop reading silence as a report or as assent.
1119
-
1120
- Zeroes on the cockpit are not yet proof the pipeline is idle.
1121
-
1122
- **A report is delivered, not implied by ending a turn.** Every member owes
1123
- the controller one report naming its outcome before it exits — including a
1124
- member whose prompt carries no explicit report line. The "report the SHA",
1125
- "report PR number" and "stop and report" instructions below *are* that
1126
- report, not a second obligation on top of it. A member that does the work
1127
- correctly and ends its turn has reported nothing: the controller learns the
1128
- outcome only by re-reading artifacts and pinging, and a finding held only in
1129
- that member's context is lost. Members did exactly this in one run —
1130
- finishers, a reviewer and merge bots alike, the work done and only the
1131
- delivery missing. Every brief's report paragraph says it. See
1132
- references/member-lifecycle.md.
1133
-
1134
- **The implementer, fix-applier, finisher, merge-bot and review-runner briefs
1135
- each tell the member you do not acknowledge reports; you never write that line
1136
- yourself.** A member cannot see whether its report arrived, so silence
1137
- from you is indistinguishable from loss and it re-sends — three members did in
1138
- one run, each burning a turn, and in all three the original had in fact arrived.
1139
- Each of those templates under `briefs/` carries the line: *"The controller does
1140
- not acknowledge reports. Send once and exit; never re-send unless the controller
1141
- asks by name."*
1142
-
1143
- So when a report is missing, **ask by name — that is what the "unless the controller asks by name" clause is for — and do not attribute the gap to the member until it answers.** See references/member-lifecycle.md.
1144
-
1145
- The ask is the controller's move, not the member's. See references/member-lifecycle.md.
1146
-
1147
- **Your OWN messages can vanish the same way, and that half is not recoverable by
1148
- a re-send.** Two controller rulings were lost in one run, both with a
1149
- `success: true` receipt: a member proceeded on its stated default and filed a
1150
- tracker issue the lost ruling had said not to file. So **never read a member's
1151
- silence as assent**, and require members to report their *confirmation state*
1152
- alongside results — "I escalated X, received no ruling, proceeded on default Y".
1153
- That sentence is the only thing that made either loss visible. After any ruling
1154
- you cannot confirm was received, re-check the artifacts it governed.
1155
-
1156
- **Name every member.** The name is the ledger token and the hub address; omit
1157
- it and the ledger cannot match the member.
1158
- Names follow the unit of work: `impl-<issue#>`, `fix-pr-<pr#>`,
1159
- `review-pr-<pr#>`, `finisher-pr-<pr#>`, `merge-bot-<n>`. See
1160
- references/member-lifecycle.md.
1161
-
1162
- **Fresh context per member.** One member, one unit of work, gone. Refill =
1163
- **new** member, **new** name.
1164
-
1165
- See references/member-lifecycle.md.
1166
-
1167
- **You read your instruments out of a tree every member can write to — re-check them before every gate decision.** See references/instruments.md.
1168
-
1169
- - **Pin once**, at phase 0 step 0 above, from the main checkout you are
1170
- running this from — never from a member's worktree, which is gitignored
1171
- and carries no baseline:
1172
- `~/.fleet/bin/fleet-run instruments.sh --pin`. On a workspace that does not
1173
- track `plugin/…`, the pin names the plugin checkout once — `--pin --audit
1174
- <plugin-checkout>` — and every check after it follows the recorded tree.
1175
- - **Re-check before you act on any instrument reading** — a SHA acceptance, a CI
1176
- verdict, the reconcile, a reap, a label gate — from that same main checkout:
1177
- `~/.fleet/bin/fleet-run instruments.sh`. **Exit 0 is the only code that
1178
- lets a gate proceed. Exit 1 (the set changed) and exit 2 (the check could not
1179
- answer) both refuse: report what it printed and do NOT re-read the
1180
- instrument** — a guard that fails open on "could not look" protects nothing.
1181
- **A label gate and a CI verdict are also taken by dispatched members, not
1182
- only by you** — the finisher labels, and the merge bot merges on its own
1183
- reading — and neither is dispatched to read this file, so neither can be
1184
- reached by stating the rule here. Each carries its own copy: the finisher's
1185
- in the instrument re-check block of `agents/fleet-finisher.agent.md`, the merge bot's inside
1186
- `merge-gate.mjs`, step 3 of `run-merge-bot.md`.
1187
- - **Re-pin only after a change you made deliberately.** The mid-run tooling fix
1188
- below is the one legitimate writer. Re-pinning to clear a refusal you cannot
1189
- explain discards the check.
1190
-
1191
- It covers every tracked file under this repo's own plugin component directories
1192
- — `plugin/commands/`, `plugin/scripts/`, `plugin/skills/`, `plugin/agents/`,
1193
- `plugin/workflows/` — the probes, the libraries they source, and these runbooks,
1194
- which you also read from that tree. Not the whole repo: `docs/metrics/` is
1195
- appended by THIS run, so covering it would refuse on the run's own bookkeeping
1196
- every time. It does not cover refs, deliberately: `claim-ticket.sh` creates a
1197
- branch per ticket and `reap.sh` deletes them in the ref store every worktree
1198
- shares, so ordinary work moves refs several times a run and a per-gate
1199
- refusal on that is noise.
1200
-
1201
- **A member runs its test suite in the foreground and blocks on it.** See references/member-lifecycle.md.
1202
-
1203
- **The scratch root is computed, never invented and never recorded.** No harness
1204
- supplies a scratch directory to a member, so a member left to name its own
1205
- invents one — and the liveness read in **The tick's rows**, which looks at the files under the
1206
- directory a member's own prompt assigned it, then watches a directory nobody
1207
- else can find. You run `~/.fleet/bin/fleet-run scratch-root.mjs`: it prints one
1208
- absolute path, `~/.fleet/scratch/<repo-key>/`, the same from the main checkout
1209
- and from every linked worktree, and the same again when a compacted or
1210
- re-invoked controller runs it, so nothing writes it down. It exits non-zero,
1211
- with its reason, when the root would sit inside a git work tree that does not
1212
- ignore it; that is an environment to fix, not a cue to pick another root.
1213
- `<scratch>` in this file is that path, resolved before you send anything: every
1214
- brief that carries one carries the absolute path you passed it — an
1215
- implementer's `<scratch>/impl-<N>/`, the recipe deriver's
1216
- `<scratch>/derive-recipe/`, a fix-applier's, a finisher's and a merge bot's
1217
- `<scratch>/pr<N>/…`, and a review runner's `scratch` argument, which is the
1218
- root itself. A member whose prompt names no path asks you by name and never
1219
- makes one up.
1220
-
1221
- ## Worktree and claim model
1222
-
1223
- Run none of this by hand: act through `claim-ticket.sh`, `release-ticket.sh`, `reap.sh` and `inflight.sh` as their phases say, and dispatch every member into its claimed worktree by absolute path.
1224
-
1225
- **Claim, release and reap are unchanged on omp.** `claim-ticket.sh`,
1226
- `release-ticket.sh`, `reap.sh` and `inflight.sh` are git-native and operate on
1227
- the single shared ref store every worktree and the main checkout read and
1228
- write directly (**Phase 1**, the tick's reap, and `release-ticket.sh` under
1229
- **Failure handling**); nothing in omp's `task` model touches that
1230
- store, so none of the four scripts carries a dialect branch.
1231
-
1232
- **A fleet member is never dispatched with omp's `isolated: true`.** See references/isolation.md.
1233
-
1234
- **A member's tree is the claimed worktree, addressed by absolute path.** See references/isolation.md.
1235
-
1236
- **The one open gap that probe measured: `task` does not accept a per-dispatch working directory.** See references/isolation.md.
1237
-
1238
- **The same gap reaches past the filesystem: `task`-dispatched members share one `eval` kernel.** See references/isolation.md.
1239
-
1240
- **`release-ticket.sh` is not a no-op on omp, and `inflight.sh`'s probes are unaffected by a running member.** See references/isolation.md.
1241
-
1242
- **The controller's own cwd stays the main checkout, unchanged.** Nothing in
1243
- this section's dispatch recipe — the cwd recipe above, the isolated-workspace
1244
- measurement, or the teardown scoping — moves
1245
- where the controller itself runs from.
1246
-
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
- ## Why the implementer body carries what it does
1460
-
1461
- Read this before you edit any `fleet-implementer-<cell>` file, and stop editing when every cell's body is byte-identical again.
1462
-
1463
- 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
-
1465
- **Prose-first** and **wrongly-REFUSE** are not padding — they are the case
1466
- which shipped with **enumerate-and-declare** already in its prompt. It missed a
1467
- shape its own ticket named in passing, because it enumerated from the mechanism
1468
- instead of the text; and it introduced a regression refusing every `node --test`
1469
- flag, because it enumerated what the guard should catch and never what it could
1470
- wrongly refuse.
1471
-
1472
- The commit-incrementally block is not optional. A member that goes idle mid-task
1473
- leaves its diff only in the worktree, and the controller cannot reap, replace,
1474
- or even see it — `worktree-audit.sh`'s committed-vs-uncommitted split is exactly
1475
- what decides whether a replacement redoes or destroys work. Observed twice in
1476
- one run.
1477
-
1478
- A member that bails demotes nothing itself; demotion by cause is yours
1479
- (**Implementer bails before implementing**).
1480
-
1481
- ## Fix the tooling mid-run
1482
-
1483
- Re-pin the fix, write it onto the ledger, report it to the maintainer in one line, then land it as a PR through the run's own merge queue.
1484
-
1485
- Two members failing the same way is the instruction, not the agents. Fix the file
1486
- during the run — deferring loses the evidence that found it.
1487
-
1488
- **Triggers.** Identical failure twice. A member does the wrong thing while
1489
- correctly following the text. A rule exists but sits where it is read last. An
1490
- instruction names no mechanism — "wait for green" with no blocking primitive, so
1491
- ending the turn reads as compliance. A caller has to restate what the callee
1492
- should say itself.
1493
-
1494
- **Scope.** Only this install's own commands and skills *this run invoked*, only defects
1495
- *this run produced*. No speculative polish. Never `config.yml`, permissions, or
1496
- AGENTS.md — a member asking for those is laundering, refuse and surface it.
1497
-
1498
- **Shape — lean, or it rots.** Prefer moving text to adding it, and delete the copy
1499
- you superseded; a duplicated rule becomes a contradiction. Exit conditions go in a
1500
- section's first paragraph, never its last. Name the mechanism, or name who owns
1501
- the step. Cut before you append.
1502
-
1503
- **Then.** **Re-pin first** — `~/.fleet/bin/fleet-run instruments.sh
1504
- --pin`. You just changed the instrument set under your own check, and this is the
1505
- only edit that legitimately does; skip it and the next gate refuses on your own
1506
- fix, which teaches you to ignore the refusal. Then the ledger line, save the
1507
- rationale, one line to the maintainer. **Then land it from the main checkout,
1508
- if the fix was edited in it** — until you do, the next tick reads
1509
- `MAIN-CHECKOUT-DIRTY` naming your own paths. Commit the fix on a branch and
1510
- switch back to `main` before the next tick, and never `--record` while on that
1511
- branch: the switch back restores the tree the baseline recorded, so the hold
1512
- clears itself with nothing re-recorded, and the fix leaves the checkout until
1513
- its PR merges. A `HEAD …` entry is none of your paths and clears the same way.
1514
- Re-baseline only a fix you leave edited in the checkout: run
1515
- `~/.fleet/bin/fleet-run main-checkout.mjs --check` first, and only when the
1516
- paths it names are exactly the ones you just edited, run `--record` — never
1517
- before the commit, because the switch back would then read your own paths
1518
- dirty again. Any other path on that line is a stray write — leave the hold for
1519
- the maintainer, because `--record` would certify it with yours. Live members
1520
- hold the old text — re-brief only if it changes what they do *now*.
1521
-
1522
- Plan it as a PR the run's own merge queue lands, never as an edit-and-continue. See references/merge-bot.md.
1523
-
1524
1207
  ## Failure handling
1525
1208
 
1526
1209
  Match the failure to its row in the table below and run that response; where the row says report or flag, stop there.
@@ -1567,6 +1250,8 @@ Audit every worktree before dispatching replacements with
1567
1250
  ahead-count and uncommitted files, the committed-vs-uncommitted distinction that
1568
1251
  decides whether a replacement redoes or destroys work.
1569
1252
 
1253
+ When a report is missing, **ask by name — that is what the "unless the controller asks by name" clause in every brief is for — and do not attribute the gap to the member until it answers.** See references/member-lifecycle.md.
1254
+
1570
1255
  **A throw, an empty return, or a notification whose status is not `completed`
1571
1256
  is a failure event, not a clean review.** **Retry once
1572
1257
  under the next runner letter, then stop:** the `review-pr-<pr#>` runner
@@ -1578,10 +1263,6 @@ review-due. A
1578
1263
  runner member that is killed rather than failed follows **Failure
1579
1264
  handling** (fresh name, inherited state stated).
1580
1265
 
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
1266
  **Verify every reported SHA.** `~/.fleet/bin/fleet-run verify-sha.sh
1586
1267
  <branch> <sha>` before enqueueing — a member can commit in a nested worktree,
1587
1268
  leaving the SHA on a stray branch while its report reads normally. Exit codes as
@@ -1597,14 +1278,7 @@ ticket over a failure a re-run would have cleared.
1597
1278
 
1598
1279
  **Never `--delete-branch` — the merge bot deletes the head branch itself.**
1599
1280
  `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.
1281
+ held by another worktree, or strands the feature worktree on `main`.
1608
1282
 
1609
1283
  **Never force a rebase to start.** No `git clean`, `git checkout .`,
1610
1284
  `git reset --hard`, `git stash`. Uncommitted changes may exist nowhere else.
@@ -1614,14 +1288,6 @@ empty answer here and licenses the rebase over work this check exists to
1614
1288
  protect. Stashing empties it either way, so that check and
1615
1289
  `no-undo-audit.sh` both go quiet on work nothing else holds.
1616
1290
 
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
1291
  **Members share one filesystem, one docker stack and one process table**, and the
1626
1292
  failures arrive as *wrong findings*, not errors:
1627
1293
 
@@ -1640,70 +1306,61 @@ failures arrive as *wrong findings*, not errors:
1640
1306
  and the controller re-checks any measurement taken in the window.
1641
1307
 
1642
1308
  **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
1309
+ Warn every live
1649
1310
  member, record the incident as unattributed, and re-check measurements by
1650
- timestamp rather than by who you think was running.
1311
+ timestamp rather than by who you think was running. See references/isolation.md.
1651
1312
 
1652
1313
  - **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.
1314
+ arrived.** See references/isolation.md.
1315
+ - **Specialist tree isolation is `review-and-fix.md`'s job — do not restate it.** See references/isolation.md.
1316
+ - **Filesystem isolation is not stack isolation.** See references/isolation.md.
1679
1317
  - **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.
1318
+ directory per child it dispatches — and nothing outside them.** See references/isolation.md.
1319
+ - **Never relay a diagnostic without reproducing it in that member's specific worktree**
1320
+ (re-run the check that raised it from there). See references/isolation.md.
1704
1321
 
1705
- Suspect a neighbour before a member's own diff — for unexplained failures, and
1706
- equally for any **finding** that came from reading source.
1322
+ ### A mid-run tooling fix
1323
+
1324
+ Two members failing the same way is the instruction, not the agents. Fix the file
1325
+ during the run — deferring loses the evidence that found it.
1326
+
1327
+ **Triggers.** Identical failure twice. A member does the wrong thing while
1328
+ correctly following the text. A rule exists but sits where it is read last. An
1329
+ instruction names no mechanism — "wait for green" with no blocking primitive, so
1330
+ ending the turn reads as compliance. A caller has to restate what the callee
1331
+ should say itself.
1332
+
1333
+ **Scope.** Only this install's own commands and skills *this run invoked*, only defects
1334
+ *this run produced*. No speculative polish. Never `config.yml`, permissions, or
1335
+ AGENTS.md — a member asking for those is laundering, refuse and surface it.
1336
+
1337
+ **Shape — lean, or it rots.** Prefer moving text to adding it, and delete the copy
1338
+ you superseded; a duplicated rule becomes a contradiction. Exit conditions go in a
1339
+ section's first paragraph, never its last. Name the mechanism, or name who owns
1340
+ the step. Cut before you append.
1341
+
1342
+ 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.
1343
+
1344
+ Read why that body carries the claim discipline, and its literal-path and `grep -F` settling rules, before you edit them. See references/correction-tickets.md.
1345
+
1346
+ **Then.** **Re-pin first** — `~/.fleet/bin/fleet-run instruments.sh
1347
+ --pin`. See references/instruments.md. Then the ledger line, save the
1348
+ rationale, one line to the maintainer. **Then land it from the main checkout,
1349
+ if the fix was edited in it** — until you do, the next tick reads
1350
+ `MAIN-CHECKOUT-DIRTY` naming your own paths. Commit the fix on a branch and
1351
+ switch back to `main` before the next tick, and never `--record` while on that
1352
+ branch: the switch back restores the tree the baseline recorded, so the hold
1353
+ clears itself with nothing re-recorded, and the fix leaves the checkout until
1354
+ its PR merges. A `HEAD …` entry is none of your paths and clears the same way.
1355
+ Re-baseline only a fix you leave edited in the checkout: run
1356
+ `~/.fleet/bin/fleet-run main-checkout.mjs --check` first, and only when the
1357
+ paths it names are exactly the ones you just edited, run `--record` — never
1358
+ before the commit, because the switch back would then read your own paths
1359
+ dirty again. Any other path on that line is a stray write — leave the hold for
1360
+ the maintainer, because `--record` would certify it with yours. Live members
1361
+ hold the old text — re-brief only if it changes what they do *now*.
1362
+
1363
+ Plan it as a PR the run's own merge queue lands, never as an edit-and-continue. See references/merge-bot.md.
1707
1364
 
1708
1365
  ## Red flags
1709
1366
 
@@ -1752,3 +1409,15 @@ One running table, updated as events land:
1752
1409
  ```
1753
1410
 
1754
1411
  Plus a queue-depth line: shortlist, supply, whether triage was suggested.
1412
+
1413
+ **Do not read a cell early.** A cell's
1414
+ comparison is one session holding an admissible row at that cell — its tier
1415
+ check passed and its resolved effort is the cell's level — and one at
1416
+ `slow-high` whose resolved model and effort differ. Count them per cell, never
1417
+ pooled across cells, and stop until
1418
+ there are at least ten of them across five or more distinct `run_date`s; below
1419
+ that, a comparison count is a number, not evidence. **Report the count the per-cell readout prints,
1420
+ never one from any other query:** `~/.fleet/bin/fleet-run cell-readout.mjs`
1421
+ prints `<cell> <comparisons> <run_dates> <resolved models>` for each cell past
1422
+ that floor and a cell below it only as a count on stderr; a `mixed (…)` models
1423
+ column means the cell's history spans more than one model behind its role. See references/pull.md.
@@ -64,6 +64,12 @@ under `Verified:` its zero reads as a checked absence.
64
64
 
65
65
  ## The settling command for "which files read X" is never a literal-path grep
66
66
 
67
+ **The settling command for "which files read X" must never be a literal-path
68
+ grep.** Replace the file with `MUTATED` in a throwaway copy, run the suite, and read off
69
+ which suites red: that answers which ones **depend on its contents**, which is
70
+ the question a coverage claim actually needs, and it returns a different set
71
+ from any spelling of the grep.
72
+
67
73
  The path is assembled in more than one spelling here, one of them behind
68
74
  a local `read` helper, so a grep for the path string answers with the files that
69
75
  merely *name* it — comments included — and misses the files that build it.
@@ -1,6 +1,6 @@
1
1
  # Instruments: why they are re-checked
2
2
 
3
- Evidence for the rule SKILL.md states in **Rules that fail silently**, keyed by the rule it justifies.
3
+ Evidence for the rule SKILL.md states in **Re-check the instruments before a gate**, keyed by the rule it justifies.
4
4
 
5
5
  ## Re-check your instruments before every gate decision
6
6
 
@@ -17,3 +17,28 @@ direction — a modified instrument raises no error, shows in no PR and leaves
17
17
  nothing in the ledger, and it still returns a verdict, just not necessarily the
18
18
  right one, so you have no reason to re-check a script you did not know had
19
19
  changed.
20
+
21
+ **A label gate and a CI verdict are also taken by dispatched members, not
22
+ only by you** — the finisher labels, and the merge bot merges on its own
23
+ reading — and neither is dispatched to read SKILL.md, so neither can be
24
+ reached by stating the rule there. Each carries its own copy: the finisher's
25
+ in the instrument re-check block of `agents/fleet-finisher.agent.md`, the merge bot's inside
26
+ `merge-gate.mjs`, step 3 of `run-merge-bot.md`.
27
+
28
+ ## What the instrument set covers
29
+
30
+ It covers every tracked file under this repo's own plugin component directories
31
+ — `plugin/commands/`, `plugin/scripts/`, `plugin/skills/`, `plugin/agents/`,
32
+ `plugin/workflows/` — the probes, the libraries they source, and these runbooks,
33
+ which you also read from that tree. Not the whole repo: `docs/metrics/` is
34
+ appended by THIS run, so covering it would refuse on the run's own bookkeeping
35
+ every time. It does not cover refs, deliberately: `claim-ticket.sh` creates a
36
+ branch per ticket and `reap.sh` deletes them in the ref store every worktree
37
+ shares, so ordinary work moves refs several times a run and a per-gate
38
+ refusal on that is noise.
39
+
40
+ ## Re-pin first after a mid-run tooling fix
41
+
42
+ You just changed the instrument set under your own check, and this is the
43
+ only edit that legitimately does; skip it and the next gate refuses on your own
44
+ fix, which teaches you to ignore the refusal.
@@ -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
 
@@ -120,6 +120,13 @@ One flat namespace, generic filenames (`b.min.js`, `probe.mjs`) — one agent ov
120
120
 
121
121
  The axis is two-sided: the member partitions, and the parent assigns each child's path. `<scratch>` itself is assigned, never invented: the controller resolves it with `scratch-root.mjs` and writes the absolute path into each member's prompt. A member owns one directory — `<scratch>/impl-<N>/` for an implementer, `<scratch>/pr<N>/…` on the review side — and every agent it dispatches gets a directory under that one, unique to that child and never shared, written into the child's prompt as an absolute path; a child never derives its own. The review side is already laid out this way: `review-core.mjs` makes the run root `<scratch>/pr<N>/run-XXXXXXXX/` with `mktemp` — it appends `pr<N>/` to the scratch root it is handed and refuses a scratch already ending in one, so a review runner is given the root, never `<scratch>/pr<N>` — then writes `<run>/<dim>/` into each specialist's prompt and `<run>/verify-<dim>/f<i>-l<j>/` into each refuter's. A fix-applier makes its own run root the same way — `<scratch>/pr<N>/fix-XXXXXXXX/` with `mktemp` — then resolves `<run>/<finding>/` to an absolute path for the refuter it dispatches on a suggestion; the bare `<scratch>/pr<N>/<finding>/` is not that path, since finding ids restart at 1 each fix-applier round too, and a later round on the same PR would reach for the same directory a prior round's refuter left fixtures in. An implementer's child gets `<scratch>/impl-<N>/<childName>/`, named after its dispatch name. Refuters are grandchildren, not members, which is why the member partition alone is not the rule: the agents that actually collided were ones no member rule reaches, and a parent-assigned path is what reaches them. Read from the object store at a pinned ref, write only under own path.
122
122
 
123
+ ## The scratch root is computed, never invented and never recorded
124
+
125
+ No harness supplies a scratch directory to a member, so a member left to name its own
126
+ invents one — and the liveness read in **The tick's rows**, which looks at the files under the
127
+ directory a member's own prompt assigned it, then watches a directory nobody
128
+ else can find.
129
+
123
130
  ## IDE/harness diagnostics attribute by bare filename, with no path
124
131
 
125
132
  Probe copies carry same filenames as real tree, so specialist's throwaway mutation surfaces as errors that read exactly like live worktree's — and line numbers can plausibly line up with real in-flight edits. Never relay diagnostic without reproducing it in that member's specific worktree (re-run the check that raised it — the typechecker, the linter — from there). Ran twice in one session: clean first time (sibling's probe), genuinely broken second. Telling implementer to chase phantom in file it is mid-rewrite on is expensive failure.
@@ -129,6 +136,15 @@ Probe copies carry same filenames as real tree, so specialist's throwaway mutati
129
136
  Measured 2026-09-09 in a throwaway clone (`/tmp/fleet-probe`)
130
137
  that never touched the real checkout.
131
138
 
139
+ ## Claim, release and reap are unchanged on omp
140
+
141
+ **Claim, release and reap are unchanged on omp.** `claim-ticket.sh`,
142
+ `release-ticket.sh`, `reap.sh` and `inflight.sh` are git-native and operate on
143
+ the single shared ref store every worktree and the main checkout read and
144
+ write directly (**Phase 1**, the tick's reap, and `release-ticket.sh` under
145
+ **Failure handling**); nothing in omp's `task` model touches that
146
+ store, so none of the four scripts carries a dialect branch.
147
+
132
148
  ## A fleet member is never dispatched with omp's `isolated: true`
133
149
 
134
150
  **A fleet member is never dispatched with omp's `isolated: true`.** Measured:
@@ -220,6 +236,12 @@ the worktree, `git worktree list` and `git branch --list` from the main
220
236
  checkout reported the claim unchanged before, during and after — `inflight.sh`
221
237
  hides nothing from omp.
222
238
 
239
+ ## The controller's own cwd stays the main checkout
240
+
241
+ Nothing in the dispatch recipe above — the cwd recipe, the
242
+ isolated-workspace measurement, or the teardown scoping — moves
243
+ where the controller itself runs from.
244
+
223
245
  ## A pattern kill reaches siblings
224
246
 
225
247
  **A pattern kill reaches siblings — the implementer, fix-applier and finisher briefs forbid it.** `pkill -f
@@ -262,3 +284,29 @@ get is not yours to choose — members are briefed on `./agent-test` with the
262
284
  arguments the Test entrypoint takes, and the runner hands them on exactly as
263
285
  its caller's shell expanded them. Clearing your own leftover process needs its
264
286
  PID, not a pattern.
287
+
288
+ ## Do not guess the victim — the blast radius is the machine, not the Pull
289
+
290
+ In the `pkill -f` incident "A pattern kill reaches siblings" measures, the controller reasoned from dispatch scope to "almost
291
+ certainly `impl-<N>`" and told the maintainer so; the named member then proved
292
+ it was not the victim (it had never invoked those files standalone, and its
293
+ mutation baselines post-dated the window). Idle members of *previous* runs,
294
+ other omp sessions on the same machine, and the maintainer's own shell are
295
+ all `pkill -f` targets and none of them appear in your ledger.
296
+
297
+ ## A warning acted on silently is indistinguishable from one that never arrived
298
+
299
+ The same `pkill -f` incident: the controller's warning DID reach the member,
300
+ which re-ran, found nothing changed, and said nothing — correctly reading
301
+ "send once, never re-send" as covering it. The controller then had to spend a
302
+ round trip asking whether its own outbox was lossy. So the briefs say that
303
+ **confirmation state includes warnings received and acted on, even
304
+ when the outcome is no change** — one line, in the report the member already
305
+ owes. That is the cheap half of the *never read silence as assent* rule, paid
306
+ by the member rather than by the controller's guesswork.
307
+
308
+ ## Specialist tree isolation is `review-and-fix.md`'s job
309
+
310
+ It owns the object-store rule, the two-tree split, snapshot provisioning, and
311
+ which suites a snapshot cannot validly run. Your only duty is to *not*
312
+ contradict it.
@@ -1,6 +1,6 @@
1
1
  # Member lifecycle: naming, fresh context, recovery
2
2
 
3
- Why member name load-bearing, why every member single-use, how a member's job outcome (`completed`/`failed`/`cancelled`) and peer liveness (`running`/`idle`/`parked`) decide its recovery. Assertions these justify live in SKILL.md's "Rules that fail silently", wake table, Reading a review result, Failure handling sections; evidence here.
3
+ Why member name load-bearing, why every member single-use, how a member's job outcome (`completed`/`failed`/`cancelled`) and peer liveness (`running`/`idle`/`parked`) decide its recovery. Assertions these justify live in SKILL.md's wake table, Dispatching the members a wake names, Reading a review result, A member's claims, Failure handling sections; evidence here.
4
4
 
5
5
  ## Every member is named
6
6
 
@@ -58,6 +58,28 @@ Reviewers go idle waiting on CI, won't resume alone — rebased, pushed, stopped
58
58
 
59
59
  Liveness says nothing: `idle` means "not currently executing", not "done"; finished member's finding may never arrive. In one run every member that looked dead had its work in git or on PR — pushed commits, self-removed label, applied ruling. Read `gh pr view`, `gh run view`, `git -C <worktree> status` before messaging: one command settles what round-trip usually doesn't, and member that ignored one ping tends to ignore second. When you message, send specific next action, never "what is your status".
60
60
 
61
+ ## A report is delivered, not implied by ending a turn
62
+
63
+ **A report is delivered, not implied by ending a turn.** Every member owes
64
+ the controller one report naming its outcome before it exits — including a
65
+ member whose prompt carries no explicit report line. A member that does the work
66
+ correctly and ends its turn has reported nothing: the controller learns the
67
+ outcome only by re-reading artifacts and pinging, and a finding held only in
68
+ that member's context is lost. Members did exactly this in one run —
69
+ finishers, a reviewer and merge bots alike, the work done and only the
70
+ delivery missing. Every brief's report paragraph says it.
71
+
72
+ ## You never write the do-not-acknowledge line yourself
73
+
74
+ **The implementer, fix-applier, finisher, merge-bot and review-runner briefs
75
+ each tell the member you do not acknowledge reports; you never write that line
76
+ yourself.** A member cannot see whether its report arrived, so silence
77
+ from you is indistinguishable from loss and it re-sends — three members did in
78
+ one run, each burning a turn, and in all three the original had in fact arrived.
79
+ Each of those templates under `briefs/` carries the line: *"The controller does
80
+ not acknowledge reports. Send once and exit; never re-send unless the controller
81
+ asks by name."*
82
+
61
83
  ## A missing report is not evidence the member failed to send one
62
84
 
63
85
  **But a missing report is NOT evidence the member failed to send one — the
@@ -84,6 +106,15 @@ Note this cuts against the paragraph above only in appearance: the default stays
84
106
  *send once*, because unprompted re-sends were measured 2-to-1 false. The ask is
85
107
  the controller's move, not the member's.
86
108
 
109
+ ## Never read a member's silence as assent
110
+
111
+ **Your OWN messages can vanish the same way, and that half is not recoverable by
112
+ a re-send.** Two controller rulings were lost in one run, both with a
113
+ `success: true` receipt: a member proceeded on its stated default and filed a
114
+ tracker issue the lost ruling had said not to file. The confirmation-state
115
+ sentence SKILL.md's **A member's claims** requires is the only thing that made
116
+ either loss visible.
117
+
87
118
  ## A member runs its test suite in the foreground and blocks on it
88
119
 
89
120
  **A member runs its test suite in the foreground and blocks on it.**
@@ -1,6 +1,6 @@
1
1
  # Landing a change on main
2
2
 
3
- Evidence for the rules SKILL.md states in **Fix the tooling mid-run** and in the `ready-to-merge` wake, keyed by the rule each justifies.
3
+ Evidence for the rules SKILL.md states in **A mid-run tooling fix** and in the `ready-to-merge` wake, keyed by the rule each justifies.
4
4
 
5
5
  ## A mid-run tooling fix lands through a PR
6
6
 
@@ -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
@@ -452,6 +517,19 @@ mention of the controller is not a statement that the reader is one of its
452
517
  members. That skill keeps its condition, so a solo session still runs the
453
518
  interactive path; this block is what puts a member on the fleet side of it.
454
519
 
520
+ **Prose-first** and **wrongly-REFUSE** are not padding — they are the case
521
+ which shipped with **enumerate-and-declare** already in its prompt. It missed a
522
+ shape its own ticket named in passing, because it enumerated from the mechanism
523
+ instead of the text; and it introduced a regression refusing every `node --test`
524
+ flag, because it enumerated what the guard should catch and never what it could
525
+ wrongly refuse.
526
+
527
+ The commit-incrementally block is not optional. A member that goes idle mid-task
528
+ leaves its diff only in the worktree, and the controller cannot reap, replace,
529
+ or even see it — `worktree-audit.sh`'s committed-vs-uncommitted split is exactly
530
+ what decides whether a replacement redoes or destroys work. Observed twice in
531
+ one run.
532
+
455
533
  ## Default: In-flight bound 5
456
534
 
457
535
  **Default: In-flight bound 5, fixed for the run.** The level is set by load, not
@@ -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
@@ -147,8 +147,8 @@ The full text of each row `fleet-tick.mjs` prints and what it asks of the contro
147
147
  HEAD is off the run's branch. Never revert the
148
148
  paths yourself, and never re-baseline over paths nobody has resolved. The
149
149
  changes of yours that trip it are a mid-run tooling fix edited in this
150
- checkout and the close-out commit's round trip through a branch; **Fix the
151
- tooling mid-run** and the commit section of references/close-out.md say how
150
+ checkout and the close-out commit's round trip through a branch; SKILL.md's
151
+ **A mid-run tooling fix** and the commit section of references/close-out.md say how
152
152
  each is cleared.
153
153
  The maintainer may instead ignore a stray path that is untracked and new since the baseline: an entry in `.git/info/exclude` or in the global excludes file hides it from the check, and the hold clears on the next tick with no re-baseline. Ignoring never clears a tracked or staged file's change, which `git status` reports whatever the excludes say, nor a pattern that also matches a path the baseline holds, which then reads as changed. Ignoring is the maintainer's decision; the controller never writes an exclude or a `.gitignore` entry. Keep the pattern narrow, because an ignored path is invisible to this check, so a member write that matches it is never seen.
154
154
  - `MAIN-CHECKOUT-UNKNOWN …` with `HOLD (main checkout unknown)` — the tick
@@ -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.