@feigi/fleet-ctl 3.82.12 → 3.82.14

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.
@@ -53,7 +53,7 @@ and `cwdAudit` before any result of the shared test run. Every one of those
53
53
  rulings is yours — nobody upstream has made one, and the controller never
54
54
  reads the findings.
55
55
 
56
- **Run `<checkCmd>` from the worktree before committing**, copied verbatim. **Run `<testCmd>` from the worktree before committing**, copied verbatim.
56
+ **Run `recipe-check.mjs` (below) from the worktree before committing.**
57
57
  `tests 0` is a FAILED run, not a pass. Red or zero-test → fix it, or move that
58
58
  finding to defer; never commit over it. **Never `--no-verify`** on a commit you push, or on the push — a failing
59
59
  hook is a finding you report, not an obstacle you route around. Report the test
@@ -67,11 +67,11 @@ claim, never your patch.
67
67
  ## kind: conflict
68
68
  Bring `<branch>` current with `origin/main`, resolving every conflict by the
69
69
  **No-undo audit** in `$(~/.fleet/bin/fleet-run --root)/commands/run-merge-bot.md`.
70
- Run `<checkCmd>` and `<testCmd>` from the worktree before you push, then push with
70
+ Run `recipe-check.mjs` (below) from the worktree before you push, then push with
71
71
  `~/.fleet/bin/fleet-run push-sync.mjs --branch <branch>`. Report the new head, or that no conflict was
72
72
  left.
73
73
  ## kind: red-check
74
- Read the failing job's log and fix the cause. Run `<checkCmd>` and `<testCmd>`
74
+ Read the failing job's log and fix the cause. Run `recipe-check.mjs` (below)
75
75
  from the worktree before you push, then push with `~/.fleet/bin/fleet-run push-sync.mjs --branch <branch>`.
76
76
  Report the new head and **stop**: do not watch, poll or wait for CI — the
77
77
  controller owns the next run on the pushed head. A red that is not yours to
@@ -90,6 +90,16 @@ runs the hook, so every change still unstaged after it is the hook's alone:
90
90
  never stage those, `git restore -- <that path>` for each such path alone, then
91
91
  rebase and re-run.
92
92
 
93
+ **`recipe-check.mjs` is how you run `<checkCmd>` and `<testCmd>`:**
94
+ `~/.fleet/bin/fleet-run recipe-check.mjs --member <your name> --testCmd "<testCmd>" --reports <scratch>/reports`,
95
+ from the worktree, after `git add` and before the commit — `<testCmd>` copied
96
+ verbatim as the one quoted argument. It runs the Recipe's Check step,
97
+ `<checkCmd>`, then `<testCmd>`, against the staged tree and records both beside
98
+ your report. Run it again after any content change or rebase: the controller
99
+ refuses an `applied` report whose head's tree has no green record, and a
100
+ message-only amend keeps the record valid. Exit 1 is red; exit 75 is no test
101
+ result — no slot was free and nothing ran — so run it again.
102
+
93
103
  Your report is a file, `<scratch>/reports/<your name>.json`, `<your name>` the
94
104
  `fix-pr-` name you were dispatched as, in the shape
95
105
  `~/.fleet/bin/fleet-run report-schema.mjs print fix-pr` prints. Write it
@@ -22,6 +22,14 @@ The ticket, pasted verbatim from the controller's own read — its title, the
22
22
 
23
23
  The controller's staleness probe: <staleness>
24
24
 
25
+ Before you commit and push, run
26
+ `~/.fleet/bin/fleet-run recipe-check.mjs --member <your name> --testCmd ./agent-test --reports <scratch>/reports`
27
+ from the worktree, after `git add`: it runs the Recipe's Check step, then
28
+ `./agent-test`, against the staged tree and records both beside your report.
29
+ Run it again after any content change or rebase. The controller refuses a
30
+ report whose head's tree has no green record, and exit 75 from it is no test
31
+ result — run it again.
32
+
25
33
  Your report is a file, `<scratch>/reports/<your name>.json`, `<your name>` the
26
34
  `impl-<N>[-x]` you were dispatched as, in the shape
27
35
  `~/.fleet/bin/fleet-run report-schema.mjs print impl` prints. Write it before
@@ -200,7 +200,7 @@ kernel — is a session setting a child inherits, not something one dispatch can
200
200
  set for one member. Isolating a member's kernel is an omp-side change, so
201
201
  discipline is the whole of the remedy here — unlike the missing `cwd` field
202
202
  above, which `member-write-guard` enforces for `write`/`edit`/`ast_edit` and
203
- `eval` bypasses. **Phase 2** carries all three rules to every member.
203
+ `eval` bypasses. **Dispatching the Pull's member** carries all three rules to every member.
204
204
 
205
205
  ## `release-ticket.sh` is not a no-op on omp, and a running member hides nothing from `inflight.sh`
206
206
 
@@ -99,3 +99,49 @@ warning is the only reason it did not repeat the stall. Its
99
99
  the poll it was waiting on was structurally incapable of firing. This file is
100
100
  yours, not theirs: the implementer, fix-applier and finisher briefs carry the
101
101
  rule, as the line `A member runs its test suite in the foreground and blocks on it.`
102
+
103
+ ## No workpool, and no kernel-resident handle
104
+
105
+ A pool's only refill virtue — handing a queued item to a freed worker without a controller turn — is exactly what a Pull forbids, and its completions never arrive per member. `eval`'s `agent()` is not the omp call either: a kernel-resident handle dies with the kernel and needs a `wait`, while a `task` job is process-level and its report auto-delivers on its own, like every other member's.
106
+
107
+ ## One claim, one dispatch, no queue
108
+
109
+ The In-flight bound is the fleet's own accounting rather than anything the runtime enforces, and the member is fresh and never a wake of one that already ran. Admission is per slot and never batched, so there is nothing to hand a freed slot its next ticket but the next Pull.
110
+
111
+ ## The prompt pastes the Pull's own read
112
+
113
+ The prompt pastes the Pull's own read of the ticket rather than telling the member to re-fetch it, and this still costs one `gh issue view` per ticket, never one per member. The fresh-context design's isolation rationale — "every implementer re-reads its Agent Brief... from scratch" — is about a member never inheriting another ticket's context, not about where the bytes come from. Pasting the Pull's own read into the prompt keeps that: the member still gets the brief verbatim and re-derives nothing from a sibling. What it drops is the unconditional re-fetch of an issue the controller already has open, on every dispatch — the fetch the agent body still carries is the backstop for a brief that turns out to be insufficient, not the default path.
114
+
115
+ ## A member settling with nothing is not a bail
116
+
117
+ Each member's outcome arrives as its own job status. Settle (CONTEXT.md § Coordination) is three states on omp — `completed`, `failed`, `cancelled` — and reading `failed`/`cancelled` the same as a `completed` turn that had nothing to report is the exact collapse this line blocks: a member that died never chose to bail, so demoting its ticket on its silence demotes a ticket its member never got the chance to judge. Confirm death first, exactly as the Member-killed row does (Failure handling in SKILL.md) — its mtime discriminator, never silence alone.
118
+
119
+ ## The implementer's tier is the definition's
120
+
121
+ The tier lives in that definition's frontmatter (`agents/fleet-implementer-<cell>.agent.md`; `slow-high`, the policy cell, for a row with no `tier=`), which is what an omitted `model` takes first — the session's tier applies only when the definition names none, and a member dispatched with `model` set does not get the declared tier back. Omitting `model` is therefore still the mechanism; what changed is that the tier it resolves to is now declared and pinned rather than inherited by accident. Keep `name: impl-<N>`: the name is what makes a member, and both the spend classifier and `member-outcomes.mjs` read it.
122
+
123
+ ## The merge bot's tier buys nothing its backstop does not cover
124
+
125
+ Rebase, wait for green, check the label, merge is checklist work, and a bad merge still needs the label and the per-job CI state to have been read correctly, which is exactly what a wrong merge later surfaces and a human resolves; `no-undo-audit.sh` is the deterministic, script-driven backstop run before every push specifically to catch a wrongly-resolved conflict, so the tier buys nothing that backstop doesn't already cover.
126
+
127
+ ## The red-check fixer's three edges
128
+
129
+ The Monitor is edge-keyed on `<run-id>:<attempt>:<conclusion>`, so a red that fired before the review never fires again, and with no push there is no new run — the other edges are the only route there, so do not wait for an event. Edit one of the three finisher-gate edges without the others and what strands is exactly the red-only PR.
130
+
131
+ Take the common case, the implementer's own first CI firing turning red before any review exists: the review is still dispatched against that head as normal, because a red suite is review evidence and not a blocker.
132
+
133
+ ## The tier check judges the dispatch-time tier
134
+
135
+ The check reads the installed definition and the live `modelRoles` at check time, not at dispatch time, so a late check judges a member against a tier it was never dispatched under.
136
+
137
+ ## The tier check after a /resume
138
+
139
+ A live implementer rotate carried forward with no verdict on its row is unchecked, and once this session has been reopened with `/resume` its transcript sits under the earlier session's directory: the check given this run's `session` finds no `<member>.jsonl` and exits 2, writing nothing.
140
+
141
+ ## Dispatching on an unchecked member multiplies the damage
142
+
143
+ Dispatching the next member on top of an unchecked or mismatched dispatch multiplies whatever silently degraded.
144
+
145
+ ## Ledger reads and their buffer
146
+
147
+ `board.mjs` reads `ledger.mjs read` with an explicit `maxBuffer`, so a payload past node's default is carried rather than killed mid-flight and reported as an unreachable tool — which is how the blind cockpit came back at HTTP 200 even after the pipe cut was gone.
@@ -334,7 +334,7 @@ PROVEN`** → re-run both reads above (`derive-testcmd.sh . install` and
334
334
  below; their output is the Recipe this run uses. **`RECIPE NOT PROVEN`** →
335
335
  stall, below.
336
336
 
337
- **Where `checkCmd` comes from:** the repository's Check step, out of the same Recipe cache — `~/.fleet/bin/fleet-run derive-testcmd.sh . check` prints it. Pass it to the fix-applier beside `testCmd`; the review does not run it.
337
+ **Where `checkCmd` comes from:** the repository's Check step, out of the same Recipe cache — `~/.fleet/bin/fleet-run derive-testcmd.sh . check` prints it. Pass it to the fix-applier beside `testCmd`; the review does not run it. Neither member runs the two by hand: `recipe-check.mjs` runs the Check step it reads off that cache, then the `testCmd` it is handed, on the member's staged tree, and writes the record `ledger.mjs settle --from-report` requires of an implementer's and a fix-applier's report.
338
338
 
339
339
  **Mid-run, a cache that stops running is re-derived once, then the run
340
340
  stalls.** A claim or a review that refuses because the Recipe cache is
@@ -424,7 +424,7 @@ token, row}]}`:
424
424
  A `resume` follows the Member-killed row across the run boundary: a new
425
425
  member under a new name — `impl-<N>-b`, or one letter past the highest
426
426
  `impl-<N>-<x>` in the archive that rotate printed the path of — on the same
427
- ticket, in the printed worktree and branch, its prompt the phase 2 prompt
427
+ ticket, in the printed worktree and branch, its prompt the dispatch prompt
428
428
  off a fresh read of the issue plus what it inherits (`git -C <worktree> log
429
429
  --oneline origin/main..HEAD`, `git -C <worktree> status --short`, and the
430
430
  files under `<scratch>/impl-<N>/`). Record it the ordinary way —
@@ -191,7 +191,7 @@ terminating in exactly one dispatch or an empty shortlist:
191
191
  without a positive and a negative control misattributes causes rather than
192
192
  merely missing defects — one such check needed a purpose-built fixture plus
193
193
  both. A ticket no single string settles is a `could not check`: admitted,
194
- annotated in the dispatch prompt (phase 2), never quietly dropped.
194
+ annotated in the dispatch prompt (the Pull's dispatch), never quietly dropped.
195
195
 
196
196
  **This is a consumption gate**, priced for the seat where implementer
197
197
  divergence costs a claim, a worktree and a dispatch; at filing time every
@@ -199,7 +199,7 @@ terminating in exactly one dispatch or an empty shortlist:
199
199
  `ready-for-agent`.
200
200
 
201
201
  No sizing agent here. `sizing-a-ticket` picks the *process path*, and that is
202
- phase 2's call, after the ticket is claimed.
202
+ the dispatch's call, after the ticket is claimed.
203
203
  4. **Collision scan against open PRs and live branches** — `pull.mjs` runs it
204
204
  before Q3 and prints one line per open PR or live worktree branch that shares
205
205
  a path with the brief, `no overlap` when none, and `could not look: <which>`
@@ -227,14 +227,26 @@ terminating in exactly one dispatch or an empty shortlist:
227
227
  7. **Route, row, then dispatch** — `~/.fleet/bin/fleet-run ticket-router.mjs route
228
228
  --session <session> --ticket <N> --arm <A|B> --impl-row <k>[ --chain-head] --issue
229
229
  <scratch>/impl-<N>/issue.json --guard .fleet/cost-guard.json` prints
230
- `POLICY=… CELL=… DRAW=… STRATUM=… REASON=…` (phase 2's exploration rule
230
+ `POLICY=… CELL=… DRAW=… STRATUM=… REASON=…` (the router's arguments paragraph below the list
231
231
  says what each argument is); then `ledger.mjs row <N> "impl-<N>[ · tier=<CELL>][ · route=<REASON>]"`,
232
- `tier=<CELL>` exactly when `DRAW` is not `-`; then phase 2 at once: claim
232
+ `tier=<CELL>` exactly when `DRAW` is not `-`; then the dispatch at once: claim
233
233
  what you are about to dispatch; dispatch what you have just claimed. Router
234
234
  exit 2 → no row and no dispatch: release the claim (`release-ticket.sh`, SKILL.md's
235
235
  Failure handling row for a claim abandoned mid-run) and emit an event, a probe that could not look — a table the router
236
236
  refuses refuses every Pull.
237
237
 
238
+ **The router's arguments, and nothing past its line.** `--impl-row` is the
239
+ row count the Pull creates — or, while an assignment is rolling (the latest
240
+ row 5k and every row after it carry no `tier=`), that row's number;
241
+ `--chain-head` marks a ticket another open ticket sequences after, which is
242
+ what rolls it. `--arm` is `A` for an even ticket number and `B` for an odd
243
+ one; a B Pull routes exactly as an A one until the table opens arm B.
244
+ `--session` is this run's own session directory. The printed line is the
245
+ whole routing decision — never re-derive `CELL` from the table, the guard or
246
+ this paragraph. A missing or unreadable `.fleet/cost-guard.json` routes the
247
+ default cell only (`REASON=guard-missing`), burn-in included, and `REASON`
248
+ other than `ok` goes on the row as `route=<REASON>`, nowhere else.
249
+
238
250
  **The Pull table** — every verdict a Pull can reach, and the one thing each
239
251
  does:
240
252
 
@@ -250,7 +262,7 @@ does:
250
262
  | a construct the brief names, or a brief `path:line` reference, lands in or next to a hunk of an open PR or a live branch | exclude `behind-pr:#M`, next entry |
251
263
  | `Out of scope` sequences it after an open ticket | exclude `behind-issue:#M`, next entry |
252
264
  | `claim-ticket.sh` exit 2 | taken, next entry |
253
- | decided, live, free, claimed | row, then dispatch (phase 2) |
265
+ | decided, live, free, claimed | row, then dispatch |
254
266
 
255
267
  **The shortlist runs out → run `shortlist.mjs` once and Pull again; still
256
268
  empty → hold the slot idle until the next refresh trigger.** Within a turn,
@@ -467,3 +479,17 @@ whatever woke you: a member finishing, a member bailing before implementing, a
467
479
  merge landing. An edge-triggered loop that only Pulls on completion stalls
468
480
  silently the moment the shortlist empties, because 0 implementers emit no
469
481
  completion event.
482
+
483
+ ## The rate counts Pulls, not members
484
+
485
+ **Count the rate against the ledger, because a Pull is the dispatch unit.** One Pull is one member, so a rate counted per Pull is a rate counted per member — one implementer in five, however the Pulls fall across the run — and the ledger, not your memory, is what still holds the count after a compaction.
486
+
487
+ ## Why the exploration pairing is within-run
488
+
489
+ **Why one in five within the run, not a week of one tier then a week of the other:** tier would then be confounded with calendar date and therefore with prompt evolution, which is exactly the state the rows already on disk are in, and why they cannot answer the question they were collected for.
490
+
491
+ The raw split still favours the top tier — 3 failures in 11 `routine`/`sonnet` rows against 3 in 55 `routine`/`opus`. **Do not read that as a tier result.** Tier remains largely confounded with calendar date and therefore with prompt evolution: 8 of the 11 `sonnet` rows fall on 2026-08-13 to 08-17, before the window in which 54 of the 55 `opus` rows were run, and the dispatch prompts gained rules throughout. The confound is **weakened, not resolved.** Three `sonnet` rows now break it rather than one — a maintainer-authorized deliberate control (2026-08-20) and two more (2026-08-21) — all three run against prompts of the same vintage as the `opus` rows, and **all three passed**, which is the direction opposite the raw split. Three is still not a result. **What replaces this argument going forward is the within-run pairing above**: one implementer in five at the alternate tier makes tier orthogonal to date by construction, so the question stops depending on whichever rows history happened to leave. **Orthogonal to date, and to nothing else** — the alternate member is whichever ticket the 5th Pull draws, rolling past every ticket another open one sequences after, while the top tier absorbs all of those, so the pairing trades the calendar confound for a difficulty one that runs in a known direction. That is why the four covariates exist: condition a pair comparison on `sizing`/`profile`/`loc`/`files` before reading it as a tier result, never on the raw split.
492
+
493
+ ## What minted_false_claim reads
494
+
495
+ `minted_false_claim` **discriminates and no longer reads "always yes"** — 20 of the 66 `routine` rows carry `no`, 18 at `opus` and 2 at `sonnet`. Read it as a property of the TICKET before the tier: a pure code simplification need not add prose, while a correction ticket adds prose by construction. The honest summary is unchanged and is the reason this paragraph exists: the guard fired on the criterion the maintainer chose in advance, not on a demonstration that the cheaper tier is worse.
@@ -1,6 +1,6 @@
1
1
  # Reviewers: the evidence behind the review-side rules
2
2
 
3
- Evidence for rules SKILL.md states in Phase 2 and **Reading a review result**, keyed by the rule each justifies.
3
+ Evidence for rules SKILL.md states in **Dispatching the Pull's member**, **Dispatching the members a wake names** and **Reading a review result**, keyed by the rule each justifies.
4
4
 
5
5
  ## A sizing verdict is a measurement only if it post-dates the run that produced it
6
6
 
@@ -131,3 +131,7 @@ The review throws on missing `args.pr`/`args.worktree` and on a snapshot agent
131
131
  that returned no tree, and a failure surfaces mid-loop, where "react, never
132
132
  block" makes it easy to log and carry on — leaving a PR that *reads* as
133
133
  reviewed and is not.
134
+
135
+ ## The review's scratch is the scratch root, not its partition
136
+
137
+ **`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.
@@ -10,6 +10,8 @@ one it runs in. **Own the CI waits**'s monitor paragraph points here instead of
10
10
  restating any of it. The merge bot's agent body is the one place this block is
11
11
  copied whole (see **The merge bot**); the other deliberate copy is named at the end.
12
12
 
13
+ A bot already looping cannot be corrected — the loop consumes the turns a correction would land in — so `agents/fleet-merge-bot.agent.md` carries **every entry in `references/shell-traps.md`'s Shell traps**, each written out in full: the rules with the measurements from that file beside them, and the bot has them before its first turn. An edit to `references/shell-traps.md` lands in that body in the same change.
14
+
13
15
  **zsh word-splits an unquoted command substitution's result; it does NOT split an unquoted parameter expansion.**
14
16
 
15
17
  **Word the rule against the first row.**
@@ -3,19 +3,22 @@
3
3
  The full text of each row `fleet-tick.mjs` prints and what it asks of the controller. SKILL.md's **The tick's rows** carries one line for each.
4
4
 
5
5
  - `PULL #<N> …` — one Pull per named ticket, in the order printed: phase 1's
6
- read, then phase 2's dispatch. A ticket that fails the read is relabelled or
6
+ read, then the dispatch. A ticket that fails the read is relabelled or
7
7
  excluded (phase 1), and the next tick names the next head.
8
8
  - `REFRESHED shortlist: <n> entries; <k> lifted` — the tick refreshed the
9
9
  shortlist itself, and its `PULL` row already reads the result. `REFRESH
10
10
  FAILED` is yours: `shortlist.mjs` could not run, and the implementer row is
11
11
  blind until it can. When the refresh ran because fewer than B heads were
12
12
  unclaimed (`unclaimed <u> < in-flight bound <B>`), a head whose Pull ended
13
- this run not counted, the implementer row's `unclaimed=` prints that same
13
+ this run not counted (unless it ended on an exclusion whose premise has
14
+ lifted), the implementer row's `unclaimed=` prints that same
14
15
  `<u>`, not the refreshed file's count.
15
16
  - `SUGGEST /triage, hold idle` — nothing is admissible after the refresh.
16
17
  Suggest, never run (**Queue depth**). A head whose Pull already ended in
17
18
  this run is not counted toward the refresh, so a shortlist of only such
18
- heads triggers the refresh before this row prints.
19
+ heads triggers the refresh before this row prints. It is not printed while the
20
+ refreshed shortlist holds a head whose Pull ended only on an exclusion whose
21
+ premise has lifted and a slot is free: that head gets its `PULL` row.
19
22
  - `specs-dropped=#<N> … (to-spec: User Stories heading)` at the end of the
20
23
  implementer row's counts, whatever its ACTION — the tickets the to-spec
21
24
  filter kept out of `supply`, read off the shortlist's `dropped`. Never
@@ -25,7 +28,7 @@ The full text of each row `fleet-tick.mjs` prints and what it asks of the contro
25
28
  ahead of reviews on purpose: finishing what is started beats starting more.
26
29
  - `DISPATCH review PR#<M> …` — a review per PR, oldest first, off your turn
27
30
  (**The reviewer**).
28
- - `DISPATCH merge-bot` — the next `merge-bot-<n>` (**The merge bot**).
31
+ - `DISPATCH merge-bot` — the next `merge-bot-<n>` (**The merge bot**). The `<scratch>` its brief takes is the absolute `scratch-root.mjs` output, and the bot writes only under `<scratch>/pr<N>/merge-bot-<n>/`.
29
32
  - `DISPATCH finisher PR#<M> …` — a finisher settled `labelled`, yet the open
30
33
  list shows PR M without `ready-to-merge`, and no `label-off=` (**Run ledger**)
31
34
  accounts for the removal. Apply the finisher gate (**The finisher gate**: the
@@ -104,7 +107,7 @@ The full text of each row `fleet-tick.mjs` prints and what it asks of the contro
104
107
  the hold stays while the check writes no verdict (its `tier-ok=`, or
105
108
  `tier-unverifiable=` for a member settled `killed` or `released` with no
106
109
  transcript, clears it); a mismatch is yours to clear, by dispatching the
107
- replacement at the right tier (phase 2). Each prints its clearing step in
110
+ replacement at the right tier. Each prints its clearing step in
108
111
  the row's detail. For a mismatch that is the replacement's name —
109
112
  `impl-<N>-b` on a first retry, else one letter past the highest the ticket
110
113
  has used, or that no retry letter is left. For an unchecked tier, which
@@ -112,6 +115,24 @@ The full text of each row `fleet-tick.mjs` prints and what it asks of the contro
112
115
  by hand when the tick ran without `--apply` or the check wrote no verdict,
113
116
  with a batch entry per member it names (fill in `<file>`; `<session>` too
114
117
  when no receipt of this run records one).
118
+ **The batch file is a JSON array, one entry per dispatched member** — under
119
+ Pull, a batch of one, the member just dispatched: `{member}`.
120
+ `member` is the AgentId the dispatch returns (`impl-<N>`). An entry that
121
+ names neither `session` nor `transcript` takes the session directory phase
122
+ 0's receipt `.fleet/phase0.json` of `--ledger`'s workspace (else the cwd's,
123
+ resolved as the default ledger is) records, and is exit 2 naming the receipt
124
+ when it records none; the check reads `<session>/<member>.jsonl`, so a
125
+ member with no assistant turn yet still resolves off its dispatch-time
126
+ record. A `session` the entry gives
127
+ wins over the receipt's; a caller already holding the exact transcript path
128
+ may give `transcript` instead. The agent type an implementer was dispatched
129
+ as lives only in its transcript. `resolvedModel`
130
+ **and** `resolvedThinkingLevel` together — the dispatch's own job record —
131
+ may ride beside it and are preferred for the model and level. An `impl-`
132
+ entry carries no `agentFile`: its definition is the row's, never the
133
+ caller's. `transcript`/`session` resolve relative to `--repo` (defaults to
134
+ the plugin's own root), and the check reads and writes the run's own ledger
135
+ unless `--ledger` names another.
115
136
  - `AT CAP`, `IDLE OK` — nothing to do on that row.
116
137
  - `MAIN-CHECKOUT-DIRTY <paths> …`, printed above the rows, with every
117
138
  dispatching row (implementers, reviewers, merge-bot) reading
@@ -213,3 +234,14 @@ The full text of each row `fleet-tick.mjs` prints and what it asks of the contro
213
234
  one that names its own member and fails its family's schema, with the first
214
235
  violation. A review runner has no report schema and files none, so none of
215
236
  these lines names one.
237
+ **The settle of an implementer's `PR#M` and of a fix-applier's
238
+ `applied:<head>` also checks the Check-step record** beside the report,
239
+ `<scratch>/reports/<member>.check.json`, which the member writes with
240
+ `recipe-check.mjs`: the Recipe's Check step, then the Test entrypoint it was
241
+ handed, run against its staged tree. The settle requires that the record
242
+ exists and parses, that both commands exited 0 in it, and that its tree is
243
+ the reported head's tree — so a message-only amend keeps it valid, and a
244
+ content change or a rebase does not. A head whose tree git cannot read is
245
+ could-not-look (exit 2), never a mismatch. The settle runs neither command,
246
+ and a repository whose Recipe cache is absent or refused has no gate; the
247
+ record is the member's own word, at the trust level of its report.