@feigi/fleet-ctl 3.82.14 → 3.82.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/review-and-fix.md +1 -1
- package/package.json +1 -1
- package/skills/run-team/SKILL.md +98 -508
- package/skills/run-team/references/isolation.md +27 -1
- package/skills/run-team/references/ledger.md +47 -1
- package/skills/run-team/references/pull.md +65 -0
- package/skills/run-team/references/reviewers.md +13 -0
- package/skills/run-team/references/tick-rows.md +10 -2
|
@@ -15,7 +15,7 @@ argument-hint: [pr-number]
|
|
|
15
15
|
3. **Run `checkCmd` and `testCmd` before you commit**, from the worktree, each copied verbatim — not a runner you picked. `checkCmd` is the Recipe's Check step and `testCmd` is the command your dispatcher handed you; **standalone, with no dispatcher to hand you them, they are the repo's own Check step and Test entrypoint**, read off the repo's Recipe cache (`derive-testcmd.sh <repo> check` prints the first, `derive-testcmd.sh <repo> test` the second). Read them off the repo, never invent one: "not a runner you picked" is what this step forbids, and on the standalone path nothing else defines the referent. With no usable Recipe cache (`derive-testcmd.sh` refuses), run the Recipe derivation step first — dispatch a `task` member, agent `fleet-recipe-deriver`, its prompt naming the main checkout's absolute path as `<repo>` and a scratch directory of its own; it proves all three commands in a throwaway worktree and writes the cache only on proof, and its `RECIPE NOT PROVEN` is a stop, reported with its reason. Never fall back to a runner of your own choosing. `tests 0` is a FAILED run, not a pass: a glob matching nothing still exits 0. **Zero is not the only no-work count.** A run reporting **0 passes with no failures** skipped everything it collected, and a count **materially below the whole suite** means you ran a partial tree — this suite reported `tests 937` when this line was written, so compare what you get against a full run rather than against zero. The number drifts; that is the safe direction, because a stale one produces a loud false red while its absence produces a silent false green. Red, zero-test, or either no-work count → fix it or move the finding to defer; never commit over it. Then commit and push — `~/.fleet/bin/fleet-run push-sync.mjs --branch <branch>`, which pushes the explicit refspec `HEAD:refs/heads/<branch>` and then moves the worktree holding `<branch>` to the pushed SHA, or names why it did not and exits 3 with the push landed: this step also runs from a `--detach` worktree (`run-team/SKILL.md`'s worktree-recreation form), where a bare `git push` exits 128 (`fatal: You are not currently on a branch`), any push whose destination is `HEAD` rather than a full or branch refname (`-u` or not, forced or not) exits 1 (`error: The destination you provided is not a full refname`), and a refspec push alone leaves the claim worktree's `refs/heads/<branch>` at its old tip — and report the test result alongside the SHA. **Then reconcile the PR body with what you actually did.** It merges into the permanent record and nothing downstream re-derives it: your commit messages are yours to fix and usually get fixed, the body is neither. Removing a change, or reversing one, leaves the body asserting work the diff no longer contains. Measured twice in one run — one body kept asserting a calibration its own fix had just deleted as false, another kept claiming a hunk the fix had removed as "this diff owns it" — both caught by a finisher's reading rather than by any rule, and both would otherwise have merged intact. `gh pr edit <pr> --body-file <path>`, and re-read the body against the final diff rather than against your memory of it. **Never `--no-verify` on a commit you push, or on the push itself.** The fleet runs in repos it knows nothing about: it must not assume a pre-commit hook exists, and must never bypass one that does on what reaches the branch — a hook that fails is a finding to report, not an obstacle. Nothing re-reviews what you commit here, so this run is the only gate between your edit and a merge.
|
|
16
16
|
**Under the fleet, run the two through `recipe-check.mjs`, not one by one:** from the worktree, after `git add` and before the commit, `~/.fleet/bin/fleet-run recipe-check.mjs --member <your name> --testCmd "<testCmd>" --reports <scratch>/reports` — `<your name>` the `fix-pr-<pr>[-x]` you were dispatched as, and `<testCmd>` the whole command you were handed, one quoted argument. It runs the Recipe's Check step, which is `checkCmd`, then `<testCmd>` exactly as handed, against the staged tree, and records both results beside your report. The record is keyed to the tree, so run it again after any content change or rebase — a message-only amend keeps it valid — and the controller refuses an `applied` report whose head's tree has no green record. A non-zero exit is red; exit 75 is no test result — no slot was free and nothing ran — so run it again. **Standalone, with no report to settle against,** run `checkCmd` and `testCmd` yourself, each copied verbatim.
|
|
17
17
|
4. **Under the fleet, do not hold this wait.** The controller owns a persistent CI Monitor and dispatches a finisher once your report has arrived and CI is green — so push, then report to the controller a **final verdict** (every specialist you dispatched + its outcome + your apply/defer adjudication + how many refuters you dispatched against out-of-scope suggestions + the pushed SHA) and stop. A specialist that has not reported to you is not a verdict line — `hub send` it by name for its report, then ask the controller, before ruling; see **Specialists**. The controller keys "review complete" on that verdict, **never on you merely going idle** — a slow specialist can still be running when your turn ends, and finishing off idle-alone ships a premature label (observed: a PR labelled while its contract-test specialist was still running and about to surface a vacuous pin). A turn-based reviewer re-reading `gh pr checks` each idle cycle rebuilds its whole context for nothing the Monitor lacks. **Standalone (no controller): watch** the run until the diff-validating jobs are green (bind via `ci-state.mjs` below); fix a **genuine** failure and repeat from 3. **Staleness is not a failure:** a `rebase-check` red or heavy jobs `skipped` **solely from a non-zero behind-count** never clears by waiting and must not be rebased away here — see **`skipped` is not `passed`** and **Do NOT rebase to label** below for what it looks like and why. Stop watching and go to 6. A turn-based reviewer that "waits for green" on a behind PR stalls forever and ends its turn without labelling. **`ci-state.mjs` returns `verdict: "no-ci"`** → there is no run to watch, so don't: go straight to 6 and let that step's `--declare-no-ci` branch decide.
|
|
18
|
-
5. File each deferred finding: `gh issue create --label needs-triage` with the finding, `file:line`, why deferred, `Deferred from PR #<pr> review`, and **the finding's `dimension` in the issue body** — the review returns it on every finding, and a backlog that does not carry it cannot be attributed to the specialist that generated it. **The body also carries the evidence triage needs: the refuter's verdict — the `verdictPath` reason, or the review's votes — and the candidate remedy.** **Every issue you create is labelled `needs-triage`; only triage sets `ready-for-agent`** — and the label is never omitted; an unlabelled issue is invisible to both `candidates.mjs --require-label` and the cockpit's pool query, reachable only by a human. **A confirmed defect does not settle its remedy**, so confirming one earns it no other label: a confirmed defect with the remedy still open files `needs-triage`, and so does a finding you are unsure is broken at all. **A finding a refuter CHECKED and did not survive is neither** — it is not a confirmed defect, and you are not unsure whether anything is broken: you measured that nothing is. Refutations go as **one issue per review, titled `PR #<pr> review: the suggestion band, checked`, labelled `wontfix`, and CLOSED** — a run that files them open instead has to be corrected afterwards, as one was on 2026-08-27. Left open they read as queued agent work and rot into filing artifacts; a measured triage pass found a quarter of its `needs-triage` backlog was refuted records, bulk-closable by title shape. Closed is still a durable home — `check` reads the tracker at `--state all`, so a future run re-deriving the finding lands on the evidence rather than re-litigating it. Split the band only where an entry is **not** a refutation: a residual the refuter surfaced, or an out-of-scope finding its refuter let survive or never decided, is real deferred work — and real deferred work earns its own open issue only past a second bar. **Worth a claim: a finding whose own claim is that correct code could be shaped better — style, naming, layout, redundancy, a micro-simplification — never gets its own open issue.** **Neither does a gap in a prose guard — a test holding prose to its wording — unless something acts on the guarded text: an agent follows it as an instruction (a skill, a command, an agent definition, a script's header or usage text, `AGENTS.md`), or a program other than its own guard reads it.** Whether anything does is your judgement, as `claimKind` is, and a gap below this bar is recorded `claimKind: shape`. A gap in a guard's own code — a slow pattern, a dead branch, an unchecked spawn result — takes the stakes of the prose it guards, and a helper several guards share takes the highest of theirs. A missed wording already in the tree is no guard gap — that prose is wrong — and neither is an input already in the tree that trips a guard's code; each files on its own merits. Once the prose is corrected, the observed wording may join the guard's literal ban, never a reader built to catch wordings nobody has written. A below-bar finding's only open question is "is this worth doing?", which answers itself under the repriced triage bar: measured 2026-08-15→31, 133 issues were triage-closed `wontfix` in sixteen days, 121 of them review deferrals, mostly this shape. Append it to the same closed record issue under a `Below the claim bar` heading — `file:line`, dimension, the claim, and a re-open trigger per entry. Closed is a durable home for these too: `check` reads `--state all`, so a run re-deriving the finding lands on the record instead of re-filing — and a later review returning the same finding as `survived`, or a maintainer hitting it, is the promotion signal: file it open then, citing the record. **An in-scope rediscovery step 2 applied is that same promotion signal, and it files nothing** — so the record carries no mark unless you write one: run `check` over each finding step 2 applied as well, and where its tracker rows put one on a `Below the claim bar` entry, comment on that record issue, opening `Promoted — applied, not filed`, and name the entry, that its refuter let it through, and the commit carrying the fix. That comment is the promotion mark this path leaves, and it is **not** a cross-reference — measured on this tracker, a comment mints `cross-referenced` on the issue its own text *names*, never on the issue it sits under (a comment citing a second issue landed on that issue's timeline and nowhere else), so the fixed opening is what carries the signal instead. **The bar reads the finding's claim, never its review band** — a finding that alleges wrong behavior, whatever band it sat in, `unverified`-with-crashed-refuters included, is above this bar and files open, `needs-triage`, which is why a band cannot carry this decision. Do **not** reach for phase 0's **decided?** test here. That is a *consumption* gate, and whether a filed issue is decided enough to reach it is triage's call, never the filer's. **The cost of this rule is watched:** open `needs-triage` issues citing `Deferred from PR` above 40 on 3 consecutive run dates send the filing rule back to the maintainer — batch triage, a fleet triage role, or a different bar. **Dedupe through `~/.fleet/bin/fleet-run ledger.mjs check "<subject>"`, never a bare `gh issue list --search`** — the duplicate class that guard exists to catch was filed from *here*: the ledger row for the finding rediscovered five times in one measured run carries a reviewer's own source tag, `(review-pr-108)`. `check` reads this run's filed list *and* the tracker at `--state all`, so a closed duplicate still counts. Exit **1** is already filed in this run; exit **3** is a clean ledger with matching tracker rows that score against the subject — read the ones it prints and comment on the existing follow-up rather than filing a second issue. **Exit 0 is not automatically "safe to file"**: it covers `clean`, `soft-hit` and `unverified` alike, and an unreachable `gh` degrades to a ledger-only answer that says `TRACKER NOT CHECKED`, so read the stdout `verdict` to tell a tracker that was searched and clean from one nobody read — under `unverified`, search it yourself before filing — or, for the applied-promotion comment above, before treating a tracker nobody could read as a reason to skip it. **`soft-hit` is the answer that costs most to misread**: it means related rows were found and none of them was established as a duplicate — a near-miss above the floor, or tracker rows the scorer rates 0.00 — so read the rows it printed and decide, and never treat that exit 0 as permission. Exit **2** is a usage error, meaning nothing was checked at all: fix the invocation and re-run, never read it as a duplicate. Full exit-code and `verdict` semantics live in `$(~/.fleet/bin/fleet-run --root)/skills/run-team/references/ledger.md`, which `SKILL.md`'s
|
|
18
|
+
5. File each deferred finding: `gh issue create --label needs-triage` with the finding, `file:line`, why deferred, `Deferred from PR #<pr> review`, and **the finding's `dimension` in the issue body** — the review returns it on every finding, and a backlog that does not carry it cannot be attributed to the specialist that generated it. **The body also carries the evidence triage needs: the refuter's verdict — the `verdictPath` reason, or the review's votes — and the candidate remedy.** **Every issue you create is labelled `needs-triage`; only triage sets `ready-for-agent`** — and the label is never omitted; an unlabelled issue is invisible to both `candidates.mjs --require-label` and the cockpit's pool query, reachable only by a human. **A confirmed defect does not settle its remedy**, so confirming one earns it no other label: a confirmed defect with the remedy still open files `needs-triage`, and so does a finding you are unsure is broken at all. **A finding a refuter CHECKED and did not survive is neither** — it is not a confirmed defect, and you are not unsure whether anything is broken: you measured that nothing is. Refutations go as **one issue per review, titled `PR #<pr> review: the suggestion band, checked`, labelled `wontfix`, and CLOSED** — a run that files them open instead has to be corrected afterwards, as one was on 2026-08-27. Left open they read as queued agent work and rot into filing artifacts; a measured triage pass found a quarter of its `needs-triage` backlog was refuted records, bulk-closable by title shape. Closed is still a durable home — `check` reads the tracker at `--state all`, so a future run re-deriving the finding lands on the evidence rather than re-litigating it. Split the band only where an entry is **not** a refutation: a residual the refuter surfaced, or an out-of-scope finding its refuter let survive or never decided, is real deferred work — and real deferred work earns its own open issue only past a second bar. **Worth a claim: a finding whose own claim is that correct code could be shaped better — style, naming, layout, redundancy, a micro-simplification — never gets its own open issue.** **Neither does a gap in a prose guard — a test holding prose to its wording — unless something acts on the guarded text: an agent follows it as an instruction (a skill, a command, an agent definition, a script's header or usage text, `AGENTS.md`), or a program other than its own guard reads it.** Whether anything does is your judgement, as `claimKind` is, and a gap below this bar is recorded `claimKind: shape`. A gap in a guard's own code — a slow pattern, a dead branch, an unchecked spawn result — takes the stakes of the prose it guards, and a helper several guards share takes the highest of theirs. A missed wording already in the tree is no guard gap — that prose is wrong — and neither is an input already in the tree that trips a guard's code; each files on its own merits. Once the prose is corrected, the observed wording may join the guard's literal ban, never a reader built to catch wordings nobody has written. A below-bar finding's only open question is "is this worth doing?", which answers itself under the repriced triage bar: measured 2026-08-15→31, 133 issues were triage-closed `wontfix` in sixteen days, 121 of them review deferrals, mostly this shape. Append it to the same closed record issue under a `Below the claim bar` heading — `file:line`, dimension, the claim, and a re-open trigger per entry. Closed is a durable home for these too: `check` reads `--state all`, so a run re-deriving the finding lands on the record instead of re-filing — and a later review returning the same finding as `survived`, or a maintainer hitting it, is the promotion signal: file it open then, citing the record. **An in-scope rediscovery step 2 applied is that same promotion signal, and it files nothing** — so the record carries no mark unless you write one: run `check` over each finding step 2 applied as well, and where its tracker rows put one on a `Below the claim bar` entry, comment on that record issue, opening `Promoted — applied, not filed`, and name the entry, that its refuter let it through, and the commit carrying the fix. That comment is the promotion mark this path leaves, and it is **not** a cross-reference — measured on this tracker, a comment mints `cross-referenced` on the issue its own text *names*, never on the issue it sits under (a comment citing a second issue landed on that issue's timeline and nowhere else), so the fixed opening is what carries the signal instead. **The bar reads the finding's claim, never its review band** — a finding that alleges wrong behavior, whatever band it sat in, `unverified`-with-crashed-refuters included, is above this bar and files open, `needs-triage`, which is why a band cannot carry this decision. Do **not** reach for phase 0's **decided?** test here. That is a *consumption* gate, and whether a filed issue is decided enough to reach it is triage's call, never the filer's. **The cost of this rule is watched:** open `needs-triage` issues citing `Deferred from PR` above 40 on 3 consecutive run dates send the filing rule back to the maintainer — batch triage, a fleet triage role, or a different bar. **Dedupe through `~/.fleet/bin/fleet-run ledger.mjs check "<subject>"`, never a bare `gh issue list --search`** — the duplicate class that guard exists to catch was filed from *here*: the ledger row for the finding rediscovered five times in one measured run carries a reviewer's own source tag, `(review-pr-108)`. `check` reads this run's filed list *and* the tracker at `--state all`, so a closed duplicate still counts. Exit **1** is already filed in this run; exit **3** is a clean ledger with matching tracker rows that score against the subject — read the ones it prints and comment on the existing follow-up rather than filing a second issue. **Exit 0 is not automatically "safe to file"**: it covers `clean`, `soft-hit` and `unverified` alike, and an unreachable `gh` degrades to a ledger-only answer that says `TRACKER NOT CHECKED`, so read the stdout `verdict` to tell a tracker that was searched and clean from one nobody read — under `unverified`, search it yourself before filing — or, for the applied-promotion comment above, before treating a tracker nobody could read as a reason to skip it. **`soft-hit` is the answer that costs most to misread**: it means related rows were found and none of them was established as a duplicate — a near-miss above the floor, or tracker rows the scorer rates 0.00 — so read the rows it printed and decide, and never treat that exit 0 as permission. Exit **2** is a usage error, meaning nothing was checked at all: fix the invocation and re-run, never read it as a duplicate. Full exit-code and `verdict` semantics live in `$(~/.fleet/bin/fleet-run --root)/skills/run-team/references/ledger.md`, which `SKILL.md`'s wake section points at — read them there rather than restating them here. **The guard dedupes, it never drops:** a duplicate becomes a comment on the issue that already covers it, never a dropped finding. **Record every issue you create, at once:** immediately after each `gh issue create`, run `~/.fleet/bin/fleet-run ledger.mjs filed <N> "<subject>"` — `<N>` the number `gh issue create` printed, and **the same subject string you passed to `check`**, so the next `check` for that finding meets it in the filed list instead of scoring a rewording. Without it the guard's ledger half never holds a row, and its exit **1** can never fire. If `filed` fails, retry it once; if it fails again, leave the issue alone — it exists and is correct — and report it as `unrecorded: #N <subject>`, so the controller runs `filed` for it. Report `filed: #N <subject>` for every issue you created — under the fleet, in your report to the controller. **The one window the guard cannot see:** `check` → `gh issue create` → `filed` is three commands, and the ledger's lock covers each alone, so two filers can both read `clean` in the seconds before either records. That gap is accepted, never a reason to skip `filed`. Post the numbers as one PR comment.
|
|
19
19
|
**A duplicate that should hold an open deferral is a comment, never a relabel.** Where the guard lands a finding whose home is an open issue on an existing one that is open, cite the refuters' verdict — or the review's votes — in the comment you leave there, and change no label. Never relabel `ready-for-agent` down to `needs-triage`: a `ready-for-agent` issue already holds a `needs-triage` finding, because triage already promoted it, and a later run's crashed refuters are no reason to send it back. A closed one cannot hold it: file a new open issue citing it, as the promotion rule above does for the closed record.
|
|
20
20
|
**Name where each deferral went, in the disposition record.** Every entry you defer carries `issue`: the number of the issue you filed it to, commented it onto, or appended it to — the closed `PR #<pr> review: the suggestion band, checked` record included. A `suggestion`'s entry — an `unverified` finding with `refutersDispatched` of zero — also carries `verdictPath`, applied or deferred, in scope and, above the claim bar, out of scope: the absolute path of a file holding its refuter's verdict as JSON, `{"refuted": <boolean>, "reason": "<string>"}`, under your own `<scratch>/pr<N>/fix-XXXXXXXX/<finding>/` run root. An out-of-scope one whose refuter crashed before any verdict reached you still names the path its verdict would have taken there; out of scope, that file missing or holding no decision files the finding open `needs-triage`, while in scope it is a mismatch. `dispositions-check.mjs` reads each named issue back through `gh issue view --json labels,state,title` and each verdict file off disk; a mismatch names the row of its filing table the entry broke, so move or relabel that filing to the home the row names and run the check again. With `gh` unreachable, or an issue it cannot read, that filing goes unchecked: with nothing else broken the verdict is `unchecked` — never `ok`, and it exits 1 — so run the check again once `gh` answers.
|
|
21
21
|
6. Diff-check green (run-bound per `ci-state.mjs`; under staleness see **`skipped` is not `passed`** for what the label then rests on) **and** deferrals filed **and** the PR carrying exactly one release label (`patch`/`minor`/`major`, counting only those three; **required on every PR** — `release-label.yml` auto-adds `patch` when none is present and hard-fails on more than one, so this never needs to check whether the repo defines the gate at all, and `gh label list` settles nothing here any more) **and** `dispositions-check.mjs --member fix-pr-<pr> --scratch <scratch>` run over your own record exiting 0 (standalone that is yours to run, from the worktree, after step 2 writes `<scratch>/dispositions-fix-pr-<pr>.json`; `--review <path>` names the review file, which you write yourself from the findings step 1 collected, in the shape **The review result file** describes — `pr`, `head`, `snapshot`, `counts` and the `survived`, `refuted` and `unverified` buckets; standalone you pass `--no-ledger` with it, so it writes no token and the exit status is the verdict — a `.fleet/ledger.md` the repository already holds is a previous run's, and without the flag it exits 2 for a ledger with no row for you; under the fleet `ledger.mjs dispatch` has already refused a finisher without it) → `gh pr comment <pr> --body "ready-to-merge-head: <prHead>"`, `prHead` being the full SHA your green `ci-state.mjs` reading reported, which the merge bot reads as the head the label was applied to, then `gh pr edit <pr> --add-label ready-to-merge`. Do not merge. A non-zero dispositions exit halts you before the label, and you report the violations it printed. Zero or more than one release label halts you before the label, naming which you found — a timed-out `gh pr create` opens the PR with its `--label` unapplied and no exit status to react to, and nothing after this step re-derives the release label. **Under the fleet a finisher does this** — a fresh small agent the controller dispatches (after your final verdict, never on your bare idle). Gate it on the diff-validating **`check`** job, **not** on `ci-state --quiet` exit 0, which a behind PR — the normal case once any sibling merges — never reaches: a `rebase-check` red or heavy jobs `skipped` off the behind-count hold it short of full green, so an exit-0 gate strands every behind PR unlabelled. Label when `check` is present and green **and no heavy job (the diff-validating suites — *not* the `rebase-check` currency gate) is in `failure`** — a `skipped` heavy job is behind-count staleness and fine — **solely** off that count, which is a condition to establish rather than infer, see the five-condition note in `$(~/.fleet/bin/fleet-run --root)/skills/run-team/SKILL.md`'s fix-applier prompt block. A `failure` is the diff's own fault and blocks the label, else a genuinely broken PR gets labelled off a green `check` and burns a merge-bot rebase+CI cycle before the failure resurfaces. Read per-job state for this: run `ci-state.mjs` **without** `--quiet`, or read its `jobs` — the `--quiet` payload drops the per-job `jobs`/`missing` and the `dropped` list, keeping `reasons` (which still distinguishes `is failure` from `is skipped`). You reach step 6 yourself only when green is already in hand at push time.
|
package/package.json
CHANGED
package/skills/run-team/SKILL.md
CHANGED
|
@@ -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
|
-
**
|
|
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
|
|
@@ -185,7 +184,7 @@ it prints. There are no hand-run
|
|
|
185
184
|
refill or dispatch edges left. Record first, because the tick reads the run off
|
|
186
185
|
`.fleet/ledger.md` and nothing else of yours: a wake you have not written down is
|
|
187
186
|
one it cannot see, and it names the same dispatch again. Every record goes
|
|
188
|
-
through `ledger.mjs` (
|
|
187
|
+
through `ledger.mjs` (references/ledger.md), which `fleet-event.mjs` drives — never a
|
|
189
188
|
hand edit. What each kind writes is in the header comment of `fleet-event.mjs`.
|
|
190
189
|
See references/ci-and-staleness.md.
|
|
191
190
|
|
|
@@ -283,9 +282,22 @@ owes beyond its call:
|
|
|
283
282
|
context exhaustion caused (references/close-out.md).
|
|
284
283
|
- **The finisher-gate wakes** — a CI run reaching a terminal state, a fix-applier reporting `no-op`, a `no-ci` verdict → each is a bullet of **The finisher gate**.
|
|
285
284
|
|
|
285
|
+
Write each state change through `ledger.mjs` before you act on it, never by hand: a wake is done when no row, filing or ruling lives only in your context.
|
|
286
|
+
|
|
287
|
+
Compaction parks every in-flight member and nothing reports it, so on resume take the registry census first (**Member unaccounted**, in **Failure handling**) before you trust a live token.
|
|
288
|
+
|
|
289
|
+
Token additions go through `ledger.mjs token`. `ledger.mjs token <key> <token>…` adds a token and keeps every token already on the row. Re-typing the row with `row` replaces the whole line, so it drops the tokens you do not restate. Write member tokens and `review=` tokens only through `ledger.mjs`, never by hand.
|
|
290
|
+
|
|
291
|
+
Never rotate the ledger mid-run: it is phase 0's step alone.
|
|
292
|
+
|
|
293
|
+
Record each ruling with `ledger.mjs ruled <pr> <decision>`, one line with its reason. Gate on the `ledger.ok` field of `check`, not on the stderr warning. The writer policy, the gap the lock does not close, and `check`'s exits: See references/ledger.md.
|
|
294
|
+
|
|
295
|
+
**Write the ledger line before dispatching, not after.** Record the member with `ledger.mjs dispatch`, or `review` for a review, before its `task` call.
|
|
296
|
+
|
|
286
297
|
### Dispatching the members a wake names
|
|
287
298
|
|
|
288
299
|
- **The reviewer.** **Dispatch the review, never wait on it, and act when its result lands. That is the default path.** `DISPATCH review PR#<M>`: a `task` member named `review-pr-<pr#>`, agent `fleet-review-runner`, its prompt the five args `pr`, `branch`, `worktree`, `testCmd` and `scratch`. Record `review=member:review-pr-<pr#>` on the PR's row before the call. `scratch` is the scratch root itself, never a `pr<N>` partition (`references/reviewers.md`). Write the `review=` token with `ledger.mjs review <pr> <token>`; `ledger.mjs` refuses a bad grammar. The tick names every PR owed one, oldest first, and each gets one review, once per PR. The tick counts in-flight reviews off the ledger and nowhere else, so a review you launched and never recorded is one it names again. The runner writes the result file and reports its path and `reviewed=` token.
|
|
300
|
+
A row with `review=` and no `reviewed=` is a review in flight, and the tick counts it as a live review unit unless gh reports its PR MERGED or CLOSED.
|
|
289
301
|
- **The fix-applier.** `DISPATCH fix-pr PR#<M>` on a `reviewed=` count that survived or unverified findings, with no fix-applier live: name `fix-pr-<pr#>` (the next suffix if that name is on record), never the PR's implementer; render with `dispatch-prompt.mjs render fix-applier --kind review --member fix-pr-<pr#> …`, then record `ledger.mjs dispatch <pr#> fix-pr-<pr#>` before the call. Its brief carries the PR number, the worktree abs path, the PR's branch, the same `testCmd` you passed the review, and the **path** `<scratch>/pr<pr>/<run>/review.json`, `<run>` off the PR's latest `reviewed=` — never the findings. Every `<scratch>` in that brief is the absolute path `scratch-root.mjs` printed. Its rulings are the fix-applier body's (`agents/fleet-fix-applier.agent.md`); you hold none. It holds every finding and you hold none, so there is nothing for you to rank, relay or pre-rule — and a report from a refuter it spawns that surfaces to you is its to retrieve, never yours to scan or pass on. It answers the hold and nothing else: survived or unverified findings of a returned review the row also carries stay unanswered through its settle, so the tick prints `DISPATCH fix-pr PR#<M>` again once it lands, and that one is a review fix-applier, dispatched as above. On a PR carrying `ready-to-merge`, the push moves the head after the label, so the next merge bot refuses it `head-moved-after-label-#<M>`: a fresh finisher, per **Failure handling**.
|
|
290
302
|
- **The conflict hold.** `DISPATCH fix-pr PR#<M>` on a conflict hold: the row carries an unresolved `conflict-hold:#<M>`, the merge bot's or one you recorded off the tick's `CONFLICT` line. It is the same slot with a different job, and no review file to hand over. Name it `fix-pr-<M>`, or the next suffix (`-b`, `-c` …) when that name is on record; render with `dispatch-prompt.mjs render fix-applier --kind conflict --member <name> …`; dispatch with the `agent` `ledger.mjs dispatch` printed, in the PR's existing worktree. Read the definition off that output (`fleet-fix-applier` whatever the row's `tier=`), never recall it. Settle `applied:<head>`, or `no-op` if no conflict was left, which lifts the merge hold; a dispatched-but-live one does not, and `failed`/`killed` leave the PR fix-due for a replacement.
|
|
291
303
|
- **Then dispatch a red-check fixer** — on a `check`-job `failure` bound to the PR's current head that is not **solely** staleness: a `rebase-check` red, or heavy jobs `skipped`, that comes only off the behind-count gets no fixer (the five-condition note in `agents/fleet-fix-applier.agent.md` establishes that). Name `fix-pr-<M>`, or the next free suffix; render with `dispatch-prompt.mjs render fix-applier --kind red-check --member <name> …`, record with `ledger.mjs dispatch <pr#> <name>` before the call, and dispatch with the printed `agent` in the PR's existing worktree. While a review or a review fix-applier on the PR is live or still owes its report — a dispositions mismatch awaiting its retry and a conflict hold awaiting its fix-applier included — the red-check fixer does not fire: their push starts a new run, and a fixer racing them would work on a SHA that is still moving. The common case, the implementer's own first CI firing turning red before any review exists, still dispatches the review against that head as normal. The tick prints no `DISPATCH` for it, so you reconcile it yourself, and it always goes after the review, never before. It is told outright not to watch, poll or wait for CI: your persistent Monitor owns the next run on the pushed head, and the finisher gate — review returned AND fix-applier report sent — applies to this member the same way. Settle it `applied:<head>` once a fix is pushed. Settle it `no-op` only when the red turns out not to be this member's to fix — a flake that passes on rerun, or staleness — with nothing pushed; `no-op` counts as landed, so it never stands for "could not fix". Settle it `failed` when it cannot reach green, which the **Failure handling** row "Reviewer, fix-applier or red-check fixer cannot reach green" covers. Its three dispatch edges are under **The finisher gate**; their rationale is in `references/member-lifecycle.md` (The red-check fixer's three edges).
|
|
@@ -373,6 +385,29 @@ treat as a live scratch write until you have checked it.
|
|
|
373
385
|
|
|
374
386
|
**A refutation resting on an injection nothing proved landed is not a refutation — it is a cell that never ran.** See references/reviewers.md.
|
|
375
387
|
|
|
388
|
+
**Guard: accumulate per PR, never conclude inside one run.** **Append one row to
|
|
389
|
+
`docs/metrics/tier-outcomes.tsv` when you rule each PR's review, with
|
|
390
|
+
`~/.fleet/bin/fleet-run tier-outcomes.mjs append <pr> --closed-own-ticket yes|no --minted-false-claim yes|no --note "<text>"`,
|
|
391
|
+
never by hand** (that file's header carries the column meanings). The script
|
|
392
|
+
fills `run_date`, `pr`, `ticket` and `tier` itself — `tier` from what actually
|
|
393
|
+
ran, off the implementer's `tier-ok=` ledger verdict or else its one
|
|
394
|
+
member-outcomes row (a `tier-unverifiable=` member has no ledger verdict, so
|
|
395
|
+
its one member-outcomes row decides), and BLANK with a WARNING naming why
|
|
396
|
+
when neither settles
|
|
397
|
+
it; never type a tier in over that. That append is the whole duty. See references/pull.md.
|
|
398
|
+
|
|
399
|
+
The row's last four fields are the ticket's difficulty, and they are what lets a
|
|
400
|
+
tier comparison condition on the thing that swamps it. `sizing` is the
|
|
401
|
+
**member's** own `light`/`heavy` verdict from its dispatched `sizing-a-ticket`
|
|
402
|
+
run, which `append` reads itself off the member's report file
|
|
403
|
+
(`<scratch>/reports/impl-<ticket>[-x].json`, its `sizing.verdict`, the highest
|
|
404
|
+
retry letter's when there are several), so you pass no `--sizing`; the member
|
|
405
|
+
also puts it in the PR body's `Sizing:` line, with the signal it turned on;
|
|
406
|
+
`profile`, `loc` and `files` all come from `diff-stats.mjs` over the merged
|
|
407
|
+
diff.
|
|
408
|
+
|
|
409
|
+
**A verdict is a measurement only if it post-dates the run that produced it.** See references/reviewers.md.
|
|
410
|
+
|
|
376
411
|
### Nothing to do: arm the beat
|
|
377
412
|
|
|
378
413
|
- **Nothing to do right now** → **do not end your turn.** Arm the heartbeat
|
|
@@ -450,6 +485,18 @@ Act on each line the tick prints as its row reads here, then stop: the next wake
|
|
|
450
485
|
~/.fleet/bin/fleet-run fleet-tick.mjs --apply [--in-flight <n>]
|
|
451
486
|
```
|
|
452
487
|
|
|
488
|
+
Read the depth off the tick and never derive it by hand: act on the row it prints, and when it prints `SUGGEST /triage, hold idle`, hold the implementer row idle and suggest `/triage` in the report. See references/tick-rows.md.
|
|
489
|
+
|
|
490
|
+
**The review backlog never gates, and the merge-queue depth is still no
|
|
491
|
+
signal.** The only gate on a Pull is the In-flight count. A deep backlog is a
|
|
492
|
+
`DISPATCH review` on the same tick, never a reason to idle an implementer.
|
|
493
|
+
A starved implementer queue never stalls the review or merge side.
|
|
494
|
+
For the merge queue, see references/pull.md.
|
|
495
|
+
|
|
496
|
+
**Reconcile, do not wait for an event.** With the shortlist empty the tick's rows
|
|
497
|
+
govern — refresh, and never Pull a ticket the shortlist does not hold.
|
|
498
|
+
See references/pull.md.
|
|
499
|
+
|
|
453
500
|
It reads the run itself, and nobody states a count. The In-flight count —
|
|
454
501
|
every open PR that closes an issue off `gh`, plus every claimed ticket whose
|
|
455
502
|
newest implementer is live or owed a replacement and has no such PR yet, off
|
|
@@ -469,12 +516,11 @@ row with `ledger.mjs row` and tick again. The In-flight bound defaults to 5
|
|
|
469
516
|
and is fixed for the run: pass `--in-flight` on every tick when `$ARGUMENTS`
|
|
470
517
|
set it, and never otherwise.
|
|
471
518
|
|
|
472
|
-
It prints `actual/target` and a named ACTION per role
|
|
473
|
-
depth** guard table applied in code. Act on each line as it reads:
|
|
519
|
+
It prints `actual/target` and a named ACTION per role. Act on each line as it reads:
|
|
474
520
|
|
|
475
521
|
- `PULL #<N> …` — one Pull per named ticket, in the order printed: `pull.mjs next --head <N>`, or the `pull.mjs answer` the row names (phase 1), then the dispatch under **Dispatching the Pull's member**.
|
|
476
522
|
- `REFRESHED shortlist: <n> entries; <k> lifted` — the tick refreshed the shortlist itself, and its `PULL` row already reads the result; `REFRESH FAILED` is yours, since `shortlist.mjs` could not run.
|
|
477
|
-
- `SUGGEST /triage, hold idle` — nothing is admissible after the refresh: suggest `/triage` in the report, never run it.
|
|
523
|
+
- `SUGGEST /triage, hold idle` — nothing is admissible after the refresh: suggest `/triage` in the report, never run it. The suggestion is a report, not a blocking prompt.
|
|
478
524
|
- `specs-dropped=#<N> …` — never actionable: log those tickets as specs awaiting to-tickets, not as no work.
|
|
479
525
|
- `DISPATCH fix-pr PR#<M> …` — a fix-applier for that PR (review findings, a dispositions mismatch no retry has answered, or a conflict hold): render its brief with `dispatch-prompt.mjs render fix-applier --kind <kind>`, `ledger.mjs dispatch <M> fix-pr-<M>` (the next suffix when that name is already on record), then the `task` call.
|
|
480
526
|
- `DISPATCH review PR#<M> …` — a review per PR, oldest first, off your turn: record `review=member:review-pr-<M>` with `ledger.mjs review <M> …`, then dispatch `review-pr-<M>`.
|
|
@@ -824,7 +870,7 @@ merge. `review-and-fix.md` states them; the body says they apply.
|
|
|
824
870
|
|
|
825
871
|
**Once the label is on, take it off before you approve any push.** Write `label-off=<latest finisher attempt>` onto the PR's row first
|
|
826
872
|
(`ledger.mjs token <PR number> label-off=<latest finisher attempt>`, which keeps
|
|
827
|
-
every token the row already has
|
|
873
|
+
every token the row already has, see references/ledger.md), then
|
|
828
874
|
remove `ready-to-merge`, then approve. Then
|
|
829
875
|
dispatch a **fresh** finisher against the new head; the first audit does not
|
|
830
876
|
transfer, since it verified a different tree. The merge bot refuses that head on
|
|
@@ -1006,6 +1052,16 @@ report recovery, never the exit code alone.
|
|
|
1006
1052
|
|
|
1007
1053
|
Each rule here is a judgement a driver stops for: answer it by the cause it names, then run the tick, and report to the maintainer what the evidence cannot settle.
|
|
1008
1054
|
|
|
1055
|
+
### A member's claims
|
|
1056
|
+
|
|
1057
|
+
**Cross-check what members report about their environment.**
|
|
1058
|
+
|
|
1059
|
+
**Distrust negative claims hardest.** Make members state their search scope. Negative claim
|
|
1060
|
+
vs specific finding with paths → paths win. See references/reviewers.md.
|
|
1061
|
+
|
|
1062
|
+
Suspect a neighbour before a member's own diff — for unexplained failures, and
|
|
1063
|
+
equally for any **finding** that came from reading source.
|
|
1064
|
+
|
|
1009
1065
|
### Relabel causes
|
|
1010
1066
|
|
|
1011
1067
|
- **Implementer bails before implementing** → `fleet-event.mjs impl-report impl-<N>`
|
|
@@ -1231,224 +1287,20 @@ this section's dispatch recipe — the cwd recipe above, the isolated-workspace
|
|
|
1231
1287
|
measurement, or the teardown scoping — moves
|
|
1232
1288
|
where the controller itself runs from.
|
|
1233
1289
|
|
|
1234
|
-
## Queue depth
|
|
1235
|
-
|
|
1236
|
-
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.
|
|
1237
|
-
|
|
1238
|
-
- **shortlist** — `.fleet/shortlist.json`'s entries with no `impl-` row and no
|
|
1239
|
-
live `excluded` row: admissible, not yet pulled. The tick's `unclaimed=`.
|
|
1240
|
-
Not after a refresh at unclaimed < B (below). A head whose Pull ended this
|
|
1241
|
-
run is not counted, unless on a lifted exclusion.
|
|
1242
|
-
- **supply** — `scanned`, the open `ready-for-agent` tickets `shortlist.mjs`'s
|
|
1243
|
-
candidate scan returned, before any filter. Only a Pull's full read judges
|
|
1244
|
-
decided?, so a shortlist of undecided tickets is supply that every Pull
|
|
1245
|
-
relabels away — and relabelling is what stops it being counted next scan.
|
|
1246
|
-
- **review backlog** — PRs owed a review. `fleet-tick.mjs` counts every
|
|
1247
|
-
open PR without `ready-to-merge` **that closes an issue** and carries no `review=`
|
|
1248
|
-
token on the ledger — one settled `=failed` is owed a review again, and so is
|
|
1249
|
-
one whose latest finisher halted `past-pin` past its latest `reviewed=` head
|
|
1250
|
-
(**Resolving a finisher halt**) — and it
|
|
1251
|
-
prints them as `review-due=`. Exact, not a wider read: the ledger records
|
|
1252
|
-
which PRs are under review or reviewed, so nothing about it is guesswork. The
|
|
1253
|
-
closing-issue test is what keeps it from widening: a PR that closes no issue
|
|
1254
|
-
— one you author yourself, for instance — is left unlabelled for the
|
|
1255
|
-
maintainer, so no member of the fleet will ever review it, and counting it
|
|
1256
|
-
floors the backlog at a depth nothing in the run can drain — the reviewer
|
|
1257
|
-
row would then report a review owed for the rest of the run.
|
|
1258
|
-
GitHub's own linked-issue set decides that, not a keyword regex over the body.
|
|
1259
|
-
**That exemption is for a PR THIS run authors, never one a PRIOR run left
|
|
1260
|
-
open** — step 0 folds those into this run's review queue, so count one when
|
|
1261
|
-
you reason about backlog BY HAND. **The `fleet-tick.mjs` number still
|
|
1262
|
-
excludes it**: nothing in its filter distinguishes an inherited PR from this
|
|
1263
|
-
run's own, since a PR closing no issue fails the closing-issue test either
|
|
1264
|
-
way — folding one in cannot reach the count. So the by-hand judgement and
|
|
1265
|
-
the script's output differ here, and such a PR's review is dispatched by
|
|
1266
|
-
hand.
|
|
1267
|
-
- **In flight** — a ticket between its Claim and its PR's merge: an implementer
|
|
1268
|
-
live or owed a replacement on it, or an open PR closing it. A PR closing
|
|
1269
|
-
nothing is never in flight, and a ticket whose newest implementer settled
|
|
1270
|
-
`blocked` is not either. The tick's `n`; the implementer row's counts read it
|
|
1271
|
-
as `implementers=<i> prs=<p>`, n being i + p.
|
|
1272
|
-
- **In-flight bound** — the most tickets a run lets be In flight at once, fixed
|
|
1273
|
-
for the run, set by `/run-team`'s one argument. Reaching it holds every Pull
|
|
1274
|
-
until a merge brings the count below it; reviewers, finishers and the merge
|
|
1275
|
-
bot never slow for it. The tick's `B`.
|
|
1276
|
-
|
|
1277
|
-
**Default: In-flight bound 5, fixed for the run.** See references/pull.md.
|
|
1278
|
-
|
|
1279
|
-
**The review backlog never gates, and the merge-queue depth is still no
|
|
1280
|
-
signal.** The only gate on a Pull is the In-flight count. A deep backlog is a
|
|
1281
|
-
`DISPATCH review` on the same tick, never a reason to idle an implementer.
|
|
1282
|
-
For the merge queue, see references/pull.md.
|
|
1283
|
-
|
|
1284
|
-
**Reconcile, do not wait for an event.** With the shortlist empty the table
|
|
1285
|
-
below governs — refresh, and never Pull a ticket the shortlist does not hold.
|
|
1286
|
-
See references/pull.md.
|
|
1287
|
-
|
|
1288
|
-
**Do not re-derive this by hand — `fleet-tick.mjs` computes it.** One invocation
|
|
1289
|
-
prints per-role `actual/target` and an explicit ACTION with the backlog gate
|
|
1290
|
-
above and every row of the table below already applied; every wake
|
|
1291
|
-
ends in it. The deficit is then *computed, not remembered*, which is
|
|
1292
|
-
the whole point: a table you must remember to consult is one you will not
|
|
1293
|
-
consult under a merge-side event storm, and that is how implementers reached
|
|
1294
|
-
0/target with ~57 `ready-for-agent` in supply while nobody
|
|
1295
|
-
noticed (#3). What follows stays here as the explanation of what the script decides —
|
|
1296
|
-
never as a second, hand-run copy of it. The table's shortlist counts no head
|
|
1297
|
-
whose Pull ended this run, unless on a lifted exclusion. A refresh at unclaimed < B leaves `unclaimed=` at
|
|
1298
|
-
that count, so `SUGGEST /triage` can print it above 0.
|
|
1299
|
-
|
|
1300
|
-
| shortlist (unclaimed) | implementer row prints |
|
|
1301
|
-
|---|---|
|
|
1302
|
-
| ≥ 1 | `PULL #N …`, one head per free slot — a Pull each (phase 1) |
|
|
1303
|
-
| below the In-flight bound, missing or empty | the tick refreshes first — `REFRESHED shortlist: n entries; k lifted` — and Pulls from what it found |
|
|
1304
|
-
| 0 after that refresh | `SUGGEST /triage, hold idle` |
|
|
1305
|
-
|
|
1306
|
-
### Specs, the refresh bound and the holds
|
|
1307
|
-
|
|
1308
|
-
Whatever the row prints, a `specs-dropped=#<N> (…)` at the end of its counts
|
|
1309
|
-
names the tickets the to-spec filter kept out of the shortlist — specs
|
|
1310
|
-
awaiting to-tickets, so an empty shortlist carrying one is not "no work".
|
|
1311
|
-
|
|
1312
|
-
The refresh runs at unclaimed < B and, with every other refresh trigger, never
|
|
1313
|
-
while the bound holds: a refresh then cannot feed a Pull, and the first tick
|
|
1314
|
-
back under the bound refreshes and Pulls in the same tick. Unclaimed there
|
|
1315
|
-
counts no head whose Pull ended this run, unless on a lifted exclusion.
|
|
1316
|
-
|
|
1317
|
-
Four holds outrank every row: `HOLD (draining)` once `ledger.mjs drain` has
|
|
1318
|
-
recorded the drain, `HOLD (tier mismatch impl-<N>)` until a replacement is
|
|
1319
|
-
dispatched at the right tier, `HOLD (tier unchecked impl-<N>)` until
|
|
1320
|
-
`tier-check.mjs` has written its verdict on the newest implementer —
|
|
1321
|
-
`tier-ok=`, `tier-mismatch=` or `tier-unverifiable=` — and
|
|
1322
|
-
`HOLD (in-flight <n>/<B>)` when n tickets are between Claim and merge and
|
|
1323
|
-
n ≥ B — refreshing to enable a Pull you are holding buys nothing.
|
|
1324
|
-
|
|
1325
|
-
`HOLD (in-flight <n>/<B>)` ranks last of the four: the other three print in
|
|
1326
|
-
its place. Its detail is the implementer row's counts, `implementers=<i>
|
|
1327
|
-
prs=<p> unclaimed=<k> supply=<s>`. It is quiet — a merge brings n below B and
|
|
1328
|
-
the next tick Pulls — except for a ticket whose newest implementer settled
|
|
1329
|
-
`killed` or `tier-mismatch`: it counts as In flight with no PR that could
|
|
1330
|
-
merge, so the row asks for the replacement's dispatch and names it in the
|
|
1331
|
-
detail. The reviewer row reads `<units>/<B>`, its live review units against
|
|
1332
|
-
the same bound, and never holds on it.
|
|
1333
|
-
|
|
1334
|
-
A main checkout that is not `clean` against its Phase 0 baseline outranks all
|
|
1335
|
-
of them, and every dispatching row besides: `HOLD (main checkout dirty)`,
|
|
1336
|
-
`HOLD (main checkout unknown)` or `HOLD (main checkout no baseline)` on the
|
|
1337
|
-
implementer, reviewer and merge-bot rows alike, beside the `MAIN-CHECKOUT-*`
|
|
1338
|
-
line that says why.
|
|
1339
|
-
|
|
1340
|
-
`/triage` is user-invoked only — suggest, never run. The suggestion is a report,
|
|
1341
|
-
not a blocking prompt. Counts come from cheap `gh issue list --search`, no bodies.
|
|
1342
|
-
A starved implementer queue never stalls the review or merge side.
|
|
1343
|
-
|
|
1344
|
-
## Tier outcomes guard
|
|
1345
|
-
|
|
1346
|
-
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.
|
|
1347
|
-
|
|
1348
|
-
**Guard: accumulate per PR, never conclude inside one run.** The unit is the PR —
|
|
1349
|
-
supply is one Pull per free slot, so there are no implementer batches. **Append one row to
|
|
1350
|
-
`docs/metrics/tier-outcomes.tsv` when you rule each PR's review, with
|
|
1351
|
-
`~/.fleet/bin/fleet-run tier-outcomes.mjs append <pr> --closed-own-ticket yes|no --minted-false-claim yes|no --note "<text>"`,
|
|
1352
|
-
never by hand** (that file's header carries the column meanings). The script
|
|
1353
|
-
fills `run_date`, `pr`, `ticket` and `tier` itself — `tier` from what actually
|
|
1354
|
-
ran, off the implementer's `tier-ok=` ledger verdict or else its one
|
|
1355
|
-
member-outcomes row (a `tier-unverifiable=` member has no ledger verdict, so
|
|
1356
|
-
its one member-outcomes row decides), and BLANK with a WARNING naming why
|
|
1357
|
-
when neither settles
|
|
1358
|
-
it; never type a tier in over that. A PR that already has a row is left
|
|
1359
|
-
untouched, so re-running it is safe. That append is the whole duty.
|
|
1360
|
-
|
|
1361
|
-
The row's last four fields are the ticket's difficulty, and they are what lets a
|
|
1362
|
-
tier comparison condition on the thing that swamps it. `sizing` is the
|
|
1363
|
-
**member's** own `light`/`heavy` verdict from its dispatched `sizing-a-ticket`
|
|
1364
|
-
run, which `append` reads itself off the member's report file
|
|
1365
|
-
(`<scratch>/reports/impl-<ticket>[-x].json`, its `sizing.verdict`, the highest
|
|
1366
|
-
retry letter's when there are several), so you pass no `--sizing`; the member
|
|
1367
|
-
also puts it in the PR body's `Sizing:` line, with the signal it turned on;
|
|
1368
|
-
`profile`, `loc` and `files` all come from `diff-stats.mjs` over the merged
|
|
1369
|
-
diff. **Phase 0 does not size anything** — it shortlists, a Pull claims, and
|
|
1370
|
-
the sizing run happens inside the member after both, which is why the verdict
|
|
1371
|
-
has to travel in the member's report rather than being something you already hold.
|
|
1372
|
-
|
|
1373
|
-
**A verdict is a measurement only if it post-dates the run that produced it.** See references/reviewers.md.
|
|
1374
|
-
|
|
1375
|
-
The member-facts scrape, the drain of the pending ticket-features rows and the
|
|
1376
|
-
commit of the metrics files are steps of `closeout.mjs finish`, which **End of
|
|
1377
|
-
run** (**What a wake owes beyond its call**) drives. See references/close-out.md.
|
|
1378
|
-
|
|
1379
|
-
**Why not decide inside one run.** A run holds 2-3 implementer PRs, and ticket
|
|
1380
|
-
difficulty swamps the tier effect — a gojq parity harness and a two-statement
|
|
1381
|
-
shell reorder are not comparable units. Finding-counts are not comparable either:
|
|
1382
|
-
`selectDimensions` trims the fan-out by diff profile, so one PR fields 29 agents
|
|
1383
|
-
over six dimensions and its sibling fields 8 over three. And the fleet is not
|
|
1384
|
-
stationary — this prompt gets edited mid-run when a defect earns it. Hence a
|
|
1385
|
-
binary outcome per PR, accumulating, rather than a per-run verdict.
|
|
1386
|
-
|
|
1387
|
-
**Why the rows this guard fired on could not answer the tier question, and this
|
|
1388
|
-
is the load-bearing part.** Until the 2026-08-16 revert, `class` and `tier` were
|
|
1389
|
-
perfectly confounded: routine always ran `sonnet`, correction always ran top
|
|
1390
|
-
tier. So the comparison the file invites — routine-at-sonnet against
|
|
1391
|
-
correction-at-top-tier — measures **class, not tier**, because those classes fail
|
|
1392
|
-
in different ways by construction: corrections ship false claims in prose,
|
|
1393
|
-
routines ship incomplete or regressing code. A run that "mixes both" does not fix
|
|
1394
|
-
it. **Only a same-class comparison across tiers is informative**, and the old
|
|
1395
|
-
rule could not produce one, since no routine ticket ever ran at top tier.
|
|
1396
|
-
|
|
1397
|
-
**That last sentence is no longer true, and this is the part to re-read.** Since
|
|
1398
|
-
the revert every routine ticket runs at top tier, so the file now holds 55
|
|
1399
|
-
`routine`/`opus` rows against 11 `routine`/`sonnet` (as of 2026-08-28, 103 rows
|
|
1400
|
-
— recount before citing). The same-class comparison the paragraph above calls
|
|
1401
|
-
the only informative one therefore EXISTS now. It is still not clean: the two
|
|
1402
|
-
groups are split almost entirely by calendar date, so it measures prompt
|
|
1403
|
-
evolution at least as much as tier. The counter-evidence paragraph earlier in
|
|
1404
|
-
this section carries the split and the three rows that cut against it. Recount
|
|
1405
|
-
both, and every other figure in this section, with:
|
|
1406
|
-
|
|
1407
|
-
```bash
|
|
1408
|
-
grep -vc '^#' docs/metrics/tier-outcomes.tsv
|
|
1409
|
-
awk -F'\t' '!/^#/ && $4=="routine" {n++; d[$1]=1; if($6=="no") no++} \
|
|
1410
|
-
END{print n, length(d), no+0}' docs/metrics/tier-outcomes.tsv
|
|
1411
|
-
awk -F'\t' '!/^#/ && $4=="routine" {t[$5]++; if($6=="no") f[$5]++} \
|
|
1412
|
-
END{for(k in t) print k, t[k], f[k]+0}' docs/metrics/tier-outcomes.tsv
|
|
1413
|
-
```
|
|
1414
|
-
|
|
1415
|
-
**These rows are the historical corpus, and nothing appended to them fixes the
|
|
1416
|
-
confound.** What fixes it is the exploration draw in the dispatch rule above:
|
|
1417
|
-
one implementer in every five — every one, during burn-in — runs at a drawn
|
|
1418
|
-
cell beside the policy cell's, against the same prompts on the same day, so
|
|
1419
|
-
the comparison stops depending on which tier history happened to leave in
|
|
1420
|
-
which week. Read each cell's comparisons, not the whole-file split, once there
|
|
1421
|
-
are enough of them.
|
|
1422
|
-
|
|
1423
|
-
**So read what the guard actually established: routine tickets sometimes fail to
|
|
1424
|
-
close their own ticket. Not that `sonnet` caused it.** The revert is the
|
|
1425
|
-
pre-committed rule being honoured, not a measurement. Restoring a cheap tier is
|
|
1426
|
-
still the maintainer's call and still a change to the dispatch rule — but the
|
|
1427
|
-
deliberate control it used to require is no longer something anyone has to
|
|
1428
|
-
authorize one ticket at a time: the exploration draw above produces one in
|
|
1429
|
-
every five Pulls by construction. **Do not read a cell early.** A cell's
|
|
1430
|
-
comparison is one session holding an admissible row at that cell — its tier
|
|
1431
|
-
check passed and its resolved effort is the cell's level — and one at
|
|
1432
|
-
`slow-high` whose resolved model and effort differ. Count them per cell, never
|
|
1433
|
-
pooled across cells, and stop until
|
|
1434
|
-
there are at least ten of them across five or more distinct `run_date`s; below
|
|
1435
|
-
that, a comparison count is a number, not evidence, and the last guard fired
|
|
1436
|
-
with n=1 on the control side. **Report the count the per-cell readout prints,
|
|
1437
|
-
never one from any other query:** `~/.fleet/bin/fleet-run cell-readout.mjs`
|
|
1438
|
-
prints `<cell> <comparisons> <run_dates> <resolved models>` for each cell past
|
|
1439
|
-
that floor and a cell below it only as a count on stderr; a `mixed (…)` models
|
|
1440
|
-
column means the cell's history spans more than one model behind its role.
|
|
1441
|
-
The pre-cell pair query counted pairs that are no cell's comparisons, and the
|
|
1442
|
-
query before that one counted every session whose implementers merely
|
|
1443
|
-
differed, so it cleared this floor by an order of magnitude while the
|
|
1444
|
-
controlled comparison did not exist yet.
|
|
1445
|
-
|
|
1446
1290
|
## Why the implementer body carries what it does
|
|
1447
1291
|
|
|
1448
1292
|
Read this before you edit any `fleet-implementer-<cell>` file, and stop editing when every cell's body is byte-identical again.
|
|
1449
1293
|
|
|
1450
1294
|
Why the agent body carries what it does — read this before editing any `fleet-implementer-<cell>` file, and keep every cell's body byte-identical. See references/pull.md.
|
|
1451
1295
|
|
|
1296
|
+
- **Every implementer body carries the claim discipline, because tickets that restate facts ship new wrong claims** — inherited from the ticket, and minted in prose nobody asked for. See references/correction-tickets.md.
|
|
1297
|
+
- **The settling command for "which files read X" must never be a literal-path
|
|
1298
|
+
grep.** Replace the file with `MUTATED` in a throwaway copy, run the suite, and read off
|
|
1299
|
+
which suites red: that answers which ones **depend on its contents**, which is
|
|
1300
|
+
the question a coverage claim actually needs, and it returns a different set
|
|
1301
|
+
from any spelling of the grep. See references/correction-tickets.md.
|
|
1302
|
+
- **A settling command over literal text uses `grep -F`, and its zero counts only after the same command finds a line known to match.** See references/correction-tickets.md.
|
|
1303
|
+
|
|
1452
1304
|
**Prose-first** and **wrongly-REFUSE** are not padding — they are the case
|
|
1453
1305
|
which shipped with **enumerate-and-declare** already in its prompt. It missed a
|
|
1454
1306
|
shape its own ticket named in passing, because it enumerated from the mechanism
|
|
@@ -1508,213 +1360,6 @@ hold the old text — re-brief only if it changes what they do *now*.
|
|
|
1508
1360
|
|
|
1509
1361
|
Plan it as a PR the run's own merge queue lands, never as an edit-and-continue. See references/merge-bot.md.
|
|
1510
1362
|
|
|
1511
|
-
## Run ledger
|
|
1512
|
-
|
|
1513
|
-
Write each state change through `ledger.mjs` before you act on it, never by hand: this section is done when no row, filing or ruling lives only in your context.
|
|
1514
|
-
|
|
1515
|
-
One git-ignored `.fleet/ledger.md` per run, updated at **every** state change via
|
|
1516
|
-
`~/.fleet/bin/fleet-run ledger.mjs` subcommands (`row`, `filed`, `ruled`,
|
|
1517
|
-
`check`, `read`, `dispatch`, `settle`, `review`, `drain`, `rotate`). Your context is the least
|
|
1518
|
-
durable thing in the run: it compacts, and a controller that loses the dispatch
|
|
1519
|
-
map or the filed list redoes finished work. Two duplicate tickets shipped in one
|
|
1520
|
-
run from exactly that.
|
|
1521
|
-
Compaction parks every in-flight member and nothing reports it, so on resume
|
|
1522
|
-
take the registry census first (**Member unaccounted**, in **Failure
|
|
1523
|
-
handling**) before you trust a live token.
|
|
1524
|
-
|
|
1525
|
-
**The file lives one run.** Phase 0 rotates it before anything reads it, so
|
|
1526
|
-
no exclusion, filing, ruling or drain marker outlives the run that wrote it,
|
|
1527
|
-
and no row does either — except, when the previous run was this session's
|
|
1528
|
-
own, a row still carrying a live token, which rotate carries forward with
|
|
1529
|
-
its live `## Dispatched` entries (phase 0). Never rotate mid-run: it is
|
|
1530
|
-
phase 0's step alone.
|
|
1531
|
-
|
|
1532
|
-
One line per ticket, rewritten in place (`ledger.mjs row <ticket> <text>`; `ledger.mjs token <key> <token>…` adds a token and keeps every token already on the row):
|
|
1533
|
-
|
|
1534
|
-
```
|
|
1535
|
-
#<t1> impl-<t1>=PR#<p1> → PR#<p1> → MERGED 73b356de
|
|
1536
|
-
#<t2> impl-<t2>=PR#<p2> → PR#<p2> · fix-pr-<p2> · ports=16<t2> · ruled:6-applies · held-behind:#<t3>
|
|
1537
|
-
#<t3> impl-<t3> · tier=task-high
|
|
1538
|
-
#<t4> excluded · behind-pr:#<p2>
|
|
1539
|
-
#<t5> excluded · behind-issue:#<t3>
|
|
1540
|
-
```
|
|
1541
|
-
|
|
1542
|
-
**A member's token is `<member>` while it is live and `<member>=<outcome>` once
|
|
1543
|
-
it settles** — the grammar is `ledger-grammar.mjs`'s, and the tick derives every
|
|
1544
|
-
live count from it, so nobody states one. Write neither by hand:
|
|
1545
|
-
|
|
1546
|
-
- **`ledger.mjs dispatch <ticket|pr> <member>`** — before the dispatch call,
|
|
1547
|
-
marks the member live on its row and appends it to `## Dispatched` with
|
|
1548
|
-
`brief=<first 8 hex of its brief's sha256>`. `dispatch
|
|
1549
|
-
merge-bot` names the next bot itself. It prints `{member, agent, ticket,
|
|
1550
|
-
line, created, total, brief}`, `brief` the `{path, sha256}` of the rendered
|
|
1551
|
-
brief the `task` call is handed; `agent` is the definition the `task` call names — an
|
|
1552
|
-
implementer's comes off its row's `tier=` (the tier check under **Dispatching the Pull's member** states the
|
|
1553
|
-
mapping), a finisher's and a merge bot's are their own definitions, and a
|
|
1554
|
-
fix-applier's is `fleet-fix-applier` for every kind of work. It
|
|
1555
|
-
refuses a member whose `briefs/<member>.json` beside the ledger is missing,
|
|
1556
|
-
names another role or carries no sha256, a brief file beside it that is missing or does not hash to that sha256, a name this session already used — on the ledger or in its
|
|
1557
|
-
`## Retired` — and names the replacement (`-b`, `-c` …), a
|
|
1558
|
-
second live member on one ticket or PR, any implementer once the run is
|
|
1559
|
-
draining, an implementer whose row's `tier=` names no single definition
|
|
1560
|
-
or one with no file under `agents/`, and a finisher on a ledger the tick
|
|
1561
|
-
itself refuses.
|
|
1562
|
-
- **`ledger.mjs settle <member> <outcome>`** — rewrites the token to
|
|
1563
|
-
`<member>=<outcome>` in both places. Outcomes: `impl-N` = `PR#M | bailed |
|
|
1564
|
-
released | killed | tier-mismatch | blocked`; `fix-pr-M` = `applied:<head> | no-op |
|
|
1565
|
-
failed | killed`; `finisher-pr-M` = `labelled | failed | killed |
|
|
1566
|
-
halted:<cause>`, `<cause>` one of `live-editor | rebase | past-pin |
|
|
1567
|
-
unreadable | missing | absent | other` (**Resolving a finisher halt**);
|
|
1568
|
-
`merge-bot-n` = `done | killed`. A settled member stays settled. `settle
|
|
1569
|
-
impl-N=PR#M` also folds a `#M` row a PR-bound dispatch or a `review` made
|
|
1570
|
-
before the settle into `#N`'s row, so the PR's row is the one row naming
|
|
1571
|
-
each PR from then on — but only when `#N`'s row named no PR before this
|
|
1572
|
-
write (a second `PR#` on it would misattribute the fold, or bury `#N`'s own
|
|
1573
|
-
fresher `review=` state under an older one) and no implementer claims `#M`'s
|
|
1574
|
-
row as its own. `settle impl-N=PR#M` refuses a settle after which a PR maps
|
|
1575
|
-
to more than one row when it did not before, judged after the fold — a PR
|
|
1576
|
-
has one row, the PR's row; a split already on disk is carried.
|
|
1577
|
-
- **`ledger.mjs settle <member> --from-report [--reports <dir>]`** — the
|
|
1578
|
-
settle for a member that wrote a report: reads `<dir>/<member>.json`
|
|
1579
|
-
(`<dir>` defaults to `<scratch>/reports`), derives the outcome from it and,
|
|
1580
|
-
before writing, checks what that outcome claims — `PR#M`: `verify-sha.sh`
|
|
1581
|
-
on the report's branch and head, and PR M OPEN and closing N; `bailed`: a
|
|
1582
|
-
`bailCause`, which the receipt names; `applied:<head>`: origin's branch at
|
|
1583
|
-
that head; `no-op`: origin's branch at the head the PR's latest
|
|
1584
|
-
`reviewed=` names; `labelled`: `ready-to-merge` on the PR and in the
|
|
1585
|
-
read-back; `halted:<cause>`: a cause and non-empty evidence; `done`: every
|
|
1586
|
-
PR the bot merged reads MERGED. `PR#M` and `applied:<head>` also need a
|
|
1587
|
-
green Check-step record (`SETTLE`). It then records each `unrecorded` filing in
|
|
1588
|
-
**filed** and, for a fix-applier, each refutation the report defers in
|
|
1589
|
-
**ruled**, once however often it runs, and prints `{member, outcome,
|
|
1590
|
-
checks, filed, ruled}`. Exit 3: a check failed — the member's claim and the
|
|
1591
|
-
world disagree — and nothing is written; you rule. Exit 2: the report is
|
|
1592
|
-
absent or invalid, or a check could not look, and nothing is written; a
|
|
1593
|
-
check that could not look is run again once `gh` and `git` answer. The bare
|
|
1594
|
-
form above, for an outcome a report carries, warns `settled without
|
|
1595
|
-
--from-report` on stderr.
|
|
1596
|
-
- **`ledger.mjs review <pr> <token>…`** — the only writer of `review=` and
|
|
1597
|
-
`reviewed=` (below): appends each to the PR's row — the ticket row once
|
|
1598
|
-
`settle impl-<N>=PR#M` has run, else `#M`, created when neither exists —
|
|
1599
|
-
and prints `{pr, row, line, created, next}`, `row` the key written and
|
|
1600
|
-
`next` the runner name the PR's next launch takes, null with every letter
|
|
1601
|
-
taken. A
|
|
1602
|
-
`review=<launch>=failed` whose `review=<launch>` the row holds settles that
|
|
1603
|
-
launch in place, alone or with the next launch in the same call. Any other
|
|
1604
|
-
token is refused.
|
|
1605
|
-
- **`ledger.mjs drain "<reason>"`** — at most one standing drain marker; supply
|
|
1606
|
-
stops, the review and merge sides keep going. It lives in this run's
|
|
1607
|
-
ledger, so the next run's rotation retires it with everything else.
|
|
1608
|
-
- **`ledger.mjs undrain "<reason>"`** — clears the drain marker and refuses when
|
|
1609
|
-
no marker is set. `closeout.mjs reopen` runs it.
|
|
1610
|
-
- **`ledger.mjs rotate`** — phase 0's first ledger write, every run: moves
|
|
1611
|
-
the file as-is to `ledger.<UTC YYYY-MM-DDTHHMMSSZ>.md` beside it under the
|
|
1612
|
-
same `<file>.lock`, and the next write starts the empty skeleton — unless
|
|
1613
|
-
the previous run was this session's own, when it first writes the rows and
|
|
1614
|
-
`## Dispatched` entries still live into a new ledger, and every other
|
|
1615
|
-
settled name, or name given an out-of-vocabulary outcome, into its
|
|
1616
|
-
`## Retired` (references/phase-0.md, "Rotate the ledger", says which). Refused
|
|
1617
|
-
while the recorded controller is alive and not this run's ancestor, ledger
|
|
1618
|
-
or none — or, with no record and a ledger to move, while the heartbeat mark
|
|
1619
|
-
is still beating, or a tick or ledger write is inside its window with no
|
|
1620
|
-
beat — or when the archive name already exists, the process
|
|
1621
|
-
table cannot be read, or `.fleet/heartbeat.json` exists but cannot be used
|
|
1622
|
-
(unreadable, not JSON, not an object, or a `controller` it cannot judge);
|
|
1623
|
-
with no ledger and no such refusal it moves nothing and exits 0. Then it
|
|
1624
|
-
records this run's controller in `.fleet/heartbeat.json`. The guard is
|
|
1625
|
-
stated in references/phase-0.md, "Rotate the ledger".
|
|
1626
|
-
|
|
1627
|
-
`row` refuses a malformed member token anywhere in its text — one whose outcome
|
|
1628
|
-
is outside the vocabulary above, e.g. `merge-bot-1=dispatched` — at exit 2,
|
|
1629
|
-
naming the token and writing nothing, because every reader would take it as a
|
|
1630
|
-
permanent settle. It refuses a write that adds a `review=`/`reviewed=` token
|
|
1631
|
-
(one the row already holds is carried), and one after which a PR maps to more
|
|
1632
|
-
than one row when it did not before — `row <M>` creating `#M` for a PR a ticket
|
|
1633
|
-
row maps, or a rewrite adding a `PR#M` mention to another row — naming the
|
|
1634
|
-
PR's row (`settle impl-N=PR#M` applies the same rule after its fold).
|
|
1635
|
-
|
|
1636
|
-
`→ PR#M` stays as the human-readable arrow; the tick reads only the `=`
|
|
1637
|
-
tokens. `tier=<cell>` marks an Exploration Pull's cell and `route=<REASON>` a router fallback (the dispatch's CELL line), and `excluded ·
|
|
1638
|
-
behind-pr:#M | behind-issue:#M` is an Exclusion (phase 1) — a ticket row like
|
|
1639
|
-
any other, never a section of its own.
|
|
1640
|
-
|
|
1641
|
-
A PR's review is not a member, so it carries its own pair of row tokens,
|
|
1642
|
-
written with `review` onto the PR's row (**The reviewer**): `review=wf:<runId>` |
|
|
1643
|
-
`review=member:<name>` at launch, `<name>` being
|
|
1644
|
-
`review-pr-<n>` for the PR's first runner and the next letter its rows do not
|
|
1645
|
-
carry yet (`review-pr-<n>-b`, `-c`, …) for every later one,
|
|
1646
|
-
settled dead as `…=failed`, then `reviewed=<head>:<survived>/<refuted>/<unverified>:<run>`
|
|
1647
|
-
when the result lands, `<run>` the review's `run-XXXXXXXX` run root under
|
|
1648
|
-
`<scratch>/pr<n>/`, which holds its `review.json`. A row with `review=` and no `reviewed=` is a review in
|
|
1649
|
-
flight, and the tick counts it as a live review unit unless gh reports its PR
|
|
1650
|
-
MERGED or CLOSED. `ci=<run-id>:<attempt>:<conclusion>`
|
|
1651
|
-
and `held-behind:#<lower>` are row tokens the same way, and so is
|
|
1652
|
-
`conflict-hold:#<pr>` — written by the merge bot itself (`run-merge-bot.md`
|
|
1653
|
-
step 1), or by you off the tick's `CONFLICT` line, naming the row's own PR. It
|
|
1654
|
-
is not an Exclusion: that gates a ticket's claim, this a PR's merge. `label-off=<finisher-pr-M[-x]>`
|
|
1655
|
-
is yours, written before you take `ready-to-merge` off PR M on purpose (taking
|
|
1656
|
-
it off before you approve a push, or clearing one left on a moved head),
|
|
1657
|
-
naming that PR's latest finisher attempt: the tick reads a missing label as a
|
|
1658
|
-
finisher's miss only after the last attempt a `label-off=` names, so without
|
|
1659
|
-
it your own removal reads as one. `row` and `token` refuse a `label-off=` naming no
|
|
1660
|
-
`finisher-pr` member that `## Dispatched` or some row already carries.
|
|
1661
|
-
|
|
1662
|
-
`review` holds a PR's review record to these rules, read across every row that
|
|
1663
|
-
maps to the PR — its settled `impl-<N>=PR#<M>` token, else its first `PR#`
|
|
1664
|
-
mention, else its key — in ledger order. Every `reviewed=` answers the nearest
|
|
1665
|
-
earlier launch that is neither answered nor `=failed`. A launch identity (a
|
|
1666
|
-
workflow's runId, or a runner's name, `=failed` aside) appears once, and no launch takes a runner name the
|
|
1667
|
-
ledger's `## Retired` holds — an earlier run of this session launched it —
|
|
1668
|
-
naming the next free reviewer name instead. No launch appears
|
|
1669
|
-
while an earlier one is open — unanswered and not `=failed`. Every
|
|
1670
|
-
`review=`/`reviewed=` parses. `row` holds the rest: a launch open before the
|
|
1671
|
-
write is still on one of the PR's rows after it, `=failed` allowed, and a
|
|
1672
|
-
member name appears once on the line, whatever its outcome. A write that adds
|
|
1673
|
-
a violation is refused, exit 2, nothing written; one already on the ledger and
|
|
1674
|
-
carried forward is not. A second launch refused for an open one waits for
|
|
1675
|
-
that one's `reviewed=`, or — once the Member-killed row confirms its runner
|
|
1676
|
-
dead — settles it `=failed` in the same `review` call as the new launch.
|
|
1677
|
-
|
|
1678
|
-
Plus two append-only lists:
|
|
1679
|
-
|
|
1680
|
-
- **filed** (`ledger.mjs filed <issue> <subject>`, checked with `ledger.mjs check
|
|
1681
|
-
<subject>` before every `gh issue create`) — so a finding already recorded
|
|
1682
|
-
this run is not filed twice. That is not a guarantee against duplicates at
|
|
1683
|
-
large; the tracker query below is what covers those. The FILER writes this
|
|
1684
|
-
one, not you: `review-and-fix.md` step 5 and finisher duty 2 each run `filed`
|
|
1685
|
-
immediately after their own `gh issue create`, with the subject they passed
|
|
1686
|
-
to `check`, and report `filed: #N <subject>`. A filer whose `filed` failed
|
|
1687
|
-
twice reports `unrecorded: #N <subject>` instead — an `unrecorded` entry of
|
|
1688
|
-
its report file — and `settle <member> --from-report` runs `filed` for it.
|
|
1689
|
-
- **ruled** (`ledger.mjs ruled <pr> <decision>`) — PR + decision + one-line reason,
|
|
1690
|
-
so a replacement controller does not re-litigate a settled call.
|
|
1691
|
-
|
|
1692
|
-
**Writer policy: the controller owns run state (`row`/`token`/`settle`/`dispatch`/`review`/`drain`/`ruled`); filers append filings with `filed`; all writes are serialized by `<file>.lock`.** See references/ledger.md.
|
|
1693
|
-
|
|
1694
|
-
**The suite slots wedge the same ways, and clear the same way.**
|
|
1695
|
-
`~/.fleet/suite-slots/<i>.lock` is built on the ledger's lock primitive, so an empty or
|
|
1696
|
-
unparseable `<i>.lock` and a leftover `<i>.lock.reap` are permanent, a zombie
|
|
1697
|
-
holder is transient, and a dead holder is taken over. A wait that expired with
|
|
1698
|
-
exit 75 names each wedged file — remove it by hand once no suite run is
|
|
1699
|
-
running, the way references/ledger.md removes `<file>.lock`.
|
|
1700
|
-
|
|
1701
|
-
**The gap the lock does not close:** `check` → `gh issue create` → `filed` is three commands, and the lock covers each one alone, so two filers can both read `clean` in the seconds before either records. See references/ledger.md.
|
|
1702
|
-
|
|
1703
|
-
`check` exits **0** clean, **1** already in this run's filed list, **2** usage error (no JSON on stdout), **3** the ledger is clean but open or closed tracker issues match — read those and decide. See references/ledger.md.
|
|
1704
|
-
|
|
1705
|
-
The ledger half reports its own readability as its own field, `ledger.ok`, the
|
|
1706
|
-
peer of `tracker.ok`: false when the file did not exist, and false too when the
|
|
1707
|
-
path led to something that did not parse as a ledger — which is what tells a
|
|
1708
|
-
ledger that was read and held nothing filed from one there was nothing to read. The absent case is the normal one for a run's first `check` — `.fleet/`
|
|
1709
|
-
is git-ignored and created lazily by the first write — so it stays `clean` at
|
|
1710
|
-
exit 0, and `ledger.ok` is the field that says why rather than the verdict
|
|
1711
|
-
moving. The stderr warning is deliberately narrower than the field: it fires on
|
|
1712
|
-
a path with no file, so a `--file` that landed on the wrong existing file is a
|
|
1713
|
-
silent `ok:false` with nothing on stderr. Gate on the field, not on the warning.
|
|
1714
|
-
|
|
1715
|
-
**Write the ledger line before dispatching, not after.** A member that dies
|
|
1716
|
-
between spawn and write is invisible — and members die in batches.
|
|
1717
|
-
|
|
1718
1363
|
## Failure handling
|
|
1719
1364
|
|
|
1720
1365
|
Match the failure to its row in the table below and run that response; where the row says report or flag, stop there.
|
|
@@ -1737,6 +1382,10 @@ Match the failure to its row in the table below and run that response; where the
|
|
|
1737
1382
|
| Member **killed** (spend limit, API error, crash) | Confirm it is dead first — a frozen transcript does not establish that; see below — then run the **Member confirmed dead or never dispatched** wake, then new member, new name, prompt carries inherited state |
|
|
1738
1383
|
| Member **unaccounted** (a live token the tick flags `CHECK-LIVENESS`, or any live token nothing has reported on) | Registry census first: `read history://` once. A row other than `running` → `read agent://<member>` and the artifacts (PR head, worktree, labels), settle the real outcome with the existing vocabulary, and resume a `parked` member with `write agent://<member>` when its work is worth continuing. A `running` row → the mtime discriminator below, two polls: moved means BLOCKED and alive; frozen at both polls **and** no artifact movement → `write proc://<member>/kill`, the **Member confirmed dead or never dispatched** wake, fresh name. Three independent alive-signals — the first poll, the second poll, the artifacts — must all be silent before a kill; the tick settles and kills nothing |
|
|
1739
1384
|
| A claim abandoned mid-run (a collision found after the claim) | `~/.fleet/bin/fleet-run release-ticket.sh <N> impl-<N> implementer --apply`, the pair the claim took. Run it over every claimed ticket with no PR when the run ends or the maintainer drains, and on the spot for a claim abandoned mid-run (a bail before implementing, a collision found after the claim). **That refusal is the finding, never an obstacle**: a claim carrying commits or a pushed branch is not auto-released, ever — run `worktree-audit.sh` (it audits every worktree; find this claim's row in the output) and decide by hand. Update the released tickets' ledger rows in the same step. See references/reaping.md |
|
|
1385
|
+
| A second review launch refused for an open one | Wait for that one's `reviewed=`, or, once the **Member killed** row has confirmed its runner dead, settle it `=failed` in the same `ledger.mjs review` call as the new launch. |
|
|
1386
|
+
| `ledger.mjs settle <member> --from-report` exits 3 | A check failed: the member's claim and the world disagree, and nothing is written. You rule. |
|
|
1387
|
+
| `ledger.mjs settle <member> --from-report` exits 2 | The report is absent or invalid, or a check could not look; nothing is written. A check that could not look is run again once `gh` and `git` answer. |
|
|
1388
|
+
| A suite wait exits 75 naming a wedged `~/.fleet/suite-slots/<i>.lock` | Remove each named file by hand once no suite run is running, the way references/ledger.md removes `<file>.lock`, then run the suite again. |
|
|
1740
1389
|
|
|
1741
1390
|
### Recovery
|
|
1742
1391
|
|
|
@@ -1768,10 +1417,6 @@ review-due. A
|
|
|
1768
1417
|
runner member that is killed rather than failed follows **Failure
|
|
1769
1418
|
handling** (fresh name, inherited state stated).
|
|
1770
1419
|
|
|
1771
|
-
## Guards
|
|
1772
|
-
|
|
1773
|
-
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.
|
|
1774
|
-
|
|
1775
1420
|
**Verify every reported SHA.** `~/.fleet/bin/fleet-run verify-sha.sh
|
|
1776
1421
|
<branch> <sha>` before enqueueing — a member can commit in a nested worktree,
|
|
1777
1422
|
leaving the SHA on a stray branch while its report reads normally. Exit codes as
|
|
@@ -1787,14 +1432,7 @@ ticket over a failure a re-run would have cleared.
|
|
|
1787
1432
|
|
|
1788
1433
|
**Never `--delete-branch` — the merge bot deletes the head branch itself.**
|
|
1789
1434
|
`gh pr merge <n> --merge` alone: the flag's local cleanup errors on a `main`
|
|
1790
|
-
held by another worktree, or strands the feature worktree on `main`.
|
|
1791
|
-
merge, `delete-merged-branch.sh <pr>` deletes the branch from `origin` and reads
|
|
1792
|
-
the deletion back; `reap.sh` takes the `[gone]` local branch and its worktree.
|
|
1793
|
-
No repo setting is relied on to do it. A branch another open PR still
|
|
1794
|
-
uses as its head or base is kept instead — `branch-kept-#<that PR>` in the bot's
|
|
1795
|
-
report, a deliberate keep and not a failure.
|
|
1796
|
-
The branch-delete step also releases the claim label: it drops `in-progress` from
|
|
1797
|
-
every issue the PR closes.
|
|
1435
|
+
held by another worktree, or strands the feature worktree on `main`.
|
|
1798
1436
|
|
|
1799
1437
|
**Never force a rebase to start.** No `git clean`, `git checkout .`,
|
|
1800
1438
|
`git reset --hard`, `git stash`. Uncommitted changes may exist nowhere else.
|
|
@@ -1804,14 +1442,6 @@ empty answer here and licenses the rebase over work this check exists to
|
|
|
1804
1442
|
protect. Stashing empties it either way, so that check and
|
|
1805
1443
|
`no-undo-audit.sh` both go quiet on work nothing else holds.
|
|
1806
1444
|
|
|
1807
|
-
**Cross-check what members report about their environment.** Wrong often enough
|
|
1808
|
-
to matter, and a confident wrong report from a reviewer flips a verdict.
|
|
1809
|
-
|
|
1810
|
-
**Distrust negative claims hardest.** "Nothing else references this", "the sweep
|
|
1811
|
-
is clean" — most likely false, least likely checked: a grep that found nothing
|
|
1812
|
-
looks like a grep never run. Make members state their search scope. Negative claim
|
|
1813
|
-
vs specific finding with paths → paths win.
|
|
1814
|
-
|
|
1815
1445
|
**Members share one filesystem, one docker stack and one process table**, and the
|
|
1816
1446
|
failures arrive as *wrong findings*, not errors:
|
|
1817
1447
|
|
|
@@ -1830,70 +1460,18 @@ failures arrive as *wrong findings*, not errors:
|
|
|
1830
1460
|
and the controller re-checks any measurement taken in the window.
|
|
1831
1461
|
|
|
1832
1462
|
**Do not guess the victim — the blast radius is the machine, not the Pull.**
|
|
1833
|
-
|
|
1834
|
-
certainly `impl-<N>`" and told the maintainer so; the named member then proved
|
|
1835
|
-
it was not the victim (it had never invoked those files standalone, and its
|
|
1836
|
-
mutation baselines post-dated the window). Idle members of *previous* runs,
|
|
1837
|
-
other omp sessions on the same machine, and the maintainer's own shell are
|
|
1838
|
-
all `pkill -f` targets and none of them appear in your ledger. Warn every live
|
|
1463
|
+
Warn every live
|
|
1839
1464
|
member, record the incident as unattributed, and re-check measurements by
|
|
1840
|
-
timestamp rather than by who you think was running.
|
|
1465
|
+
timestamp rather than by who you think was running. See references/isolation.md.
|
|
1841
1466
|
|
|
1842
1467
|
- **A warning acted on silently is indistinguishable from one that never
|
|
1843
|
-
arrived.**
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
round trip asking whether its own outbox was lossy. So the briefs say that
|
|
1847
|
-
**confirmation state includes warnings received and acted on, even
|
|
1848
|
-
when the outcome is no change** — one line, in the report the member already
|
|
1849
|
-
owes. That is the cheap half of the *never read silence as assent* rule, paid
|
|
1850
|
-
by the member rather than by the controller's guesswork.
|
|
1851
|
-
|
|
1852
|
-
- **Specialist tree isolation is `review-and-fix.md`'s job — do not restate it.**
|
|
1853
|
-
It owns the object-store rule, the two-tree split, snapshot provisioning, and
|
|
1854
|
-
which suites a snapshot cannot validly run. Your only duty is to *not*
|
|
1855
|
-
contradict it.
|
|
1856
|
-
- **Filesystem isolation is not stack isolation.** A private copy of the
|
|
1857
|
-
tree and a test command solve different problems; the compose project
|
|
1858
|
-
name comes from the environment, not the working directory, so three
|
|
1859
|
-
agents on three snapshots still collide on one postgres. "I'm on my own
|
|
1860
|
-
copy" is exactly the intuition that skips the command — say both, every
|
|
1861
|
-
time. Which command depends on the audience: a member in a worktree
|
|
1862
|
-
uses `./agent-test`; a specialist on a snapshot does not, and takes the
|
|
1863
|
-
one `review-and-fix.md` hands out. Which runner the worktree holds
|
|
1864
|
-
decides why: the runner a claim writes is untracked, so a `git archive`
|
|
1865
|
-
snapshot never carries it; one the repository tracks does ride along,
|
|
1866
|
-
but no claim exported ports into it — its isolation is whatever that
|
|
1867
|
-
repository built, not one triple per snapshot. See
|
|
1868
|
-
references/isolation.md.
|
|
1468
|
+
arrived.** See references/isolation.md.
|
|
1469
|
+
- **Specialist tree isolation is `review-and-fix.md`'s job — do not restate it.** See references/isolation.md.
|
|
1470
|
+
- **Filesystem isolation is not stack isolation.** See references/isolation.md.
|
|
1869
1471
|
- **Scratchpad paths need two levels — the member's own partition, then one
|
|
1870
|
-
directory per child it dispatches — and nothing outside them.**
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
runner is handed the scratch root, never that partition; any member that
|
|
1874
|
-
dispatches a child writes an absolute directory under its own partition into
|
|
1875
|
-
that child's prompt, one per child and never shared, and a child never
|
|
1876
|
-
derives its own. Finding ids restart at 1 every review, so two fix-appliers
|
|
1877
|
-
on different PRs both reach for `unv1`; one
|
|
1878
|
-
agent overwrote a sibling's `package.json`, and a probe built a git repo at
|
|
1879
|
-
the *checkout root*. Read from the object store at a pinned ref, write only
|
|
1880
|
-
under your own path. See references/isolation.md.
|
|
1881
|
-
- **IDE/harness diagnostics attribute by bare filename, with no path.** **Never
|
|
1882
|
-
relay a diagnostic without reproducing it in that member's specific worktree**
|
|
1883
|
-
(re-run the check that raised it from there): probe copies carry the real
|
|
1884
|
-
tree's filenames, so a sibling's throwaway mutation reads exactly like a live
|
|
1885
|
-
worktree's error.
|
|
1886
|
-
See references/isolation.md.
|
|
1887
|
-
- **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.
|
|
1888
|
-
- **The settling command for "which files read X" must never be a literal-path
|
|
1889
|
-
grep.** Replace the file with `MUTATED` in a throwaway copy, run the suite, and read off
|
|
1890
|
-
which suites red: that answers which ones **depend on its contents**, which is
|
|
1891
|
-
the question a coverage claim actually needs, and it returns a different set
|
|
1892
|
-
from any spelling of the grep. See references/correction-tickets.md.
|
|
1893
|
-
- **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.
|
|
1894
|
-
|
|
1895
|
-
Suspect a neighbour before a member's own diff — for unexplained failures, and
|
|
1896
|
-
equally for any **finding** that came from reading source.
|
|
1472
|
+
directory per child it dispatches — and nothing outside them.** See references/isolation.md.
|
|
1473
|
+
- **Never relay a diagnostic without reproducing it in that member's specific worktree**
|
|
1474
|
+
(re-run the check that raised it from there). See references/isolation.md.
|
|
1897
1475
|
|
|
1898
1476
|
## Red flags
|
|
1899
1477
|
|
|
@@ -1942,3 +1520,15 @@ One running table, updated as events land:
|
|
|
1942
1520
|
```
|
|
1943
1521
|
|
|
1944
1522
|
Plus a queue-depth line: shortlist, supply, whether triage was suggested.
|
|
1523
|
+
|
|
1524
|
+
**Do not read a cell early.** A cell's
|
|
1525
|
+
comparison is one session holding an admissible row at that cell — its tier
|
|
1526
|
+
check passed and its resolved effort is the cell's level — and one at
|
|
1527
|
+
`slow-high` whose resolved model and effort differ. Count them per cell, never
|
|
1528
|
+
pooled across cells, and stop until
|
|
1529
|
+
there are at least ten of them across five or more distinct `run_date`s; below
|
|
1530
|
+
that, a comparison count is a number, not evidence. **Report the count the per-cell readout prints,
|
|
1531
|
+
never one from any other query:** `~/.fleet/bin/fleet-run cell-readout.mjs`
|
|
1532
|
+
prints `<cell> <comparisons> <run_dates> <resolved models>` for each cell past
|
|
1533
|
+
that floor and a cell below it only as a count on stderr; a `mixed (…)` models
|
|
1534
|
+
column means the cell's history spans more than one model behind its role. See references/pull.md.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Isolation: filesystem vs stack
|
|
2
2
|
|
|
3
|
-
Why isolation envelope file not briefing, why private filesystem not private stack. Assertions these justify live in references/pull.md Claiming section and SKILL.md
|
|
3
|
+
Why isolation envelope file not briefing, why private filesystem not private stack. Assertions these justify live in references/pull.md Claiming section and SKILL.md Failure handling section; evidence here.
|
|
4
4
|
|
|
5
5
|
## Materialize the isolation envelope as a file, not a briefing
|
|
6
6
|
|
|
@@ -262,3 +262,29 @@ get is not yours to choose — members are briefed on `./agent-test` with the
|
|
|
262
262
|
arguments the Test entrypoint takes, and the runner hands them on exactly as
|
|
263
263
|
its caller's shell expanded them. Clearing your own leftover process needs its
|
|
264
264
|
PID, not a pattern.
|
|
265
|
+
|
|
266
|
+
## Do not guess the victim — the blast radius is the machine, not the Pull
|
|
267
|
+
|
|
268
|
+
In the `pkill -f` incident "A pattern kill reaches siblings" measures, the controller reasoned from dispatch scope to "almost
|
|
269
|
+
certainly `impl-<N>`" and told the maintainer so; the named member then proved
|
|
270
|
+
it was not the victim (it had never invoked those files standalone, and its
|
|
271
|
+
mutation baselines post-dated the window). Idle members of *previous* runs,
|
|
272
|
+
other omp sessions on the same machine, and the maintainer's own shell are
|
|
273
|
+
all `pkill -f` targets and none of them appear in your ledger.
|
|
274
|
+
|
|
275
|
+
## A warning acted on silently is indistinguishable from one that never arrived
|
|
276
|
+
|
|
277
|
+
The same `pkill -f` incident: the controller's warning DID reach the member,
|
|
278
|
+
which re-ran, found nothing changed, and said nothing — correctly reading
|
|
279
|
+
"send once, never re-send" as covering it. The controller then had to spend a
|
|
280
|
+
round trip asking whether its own outbox was lossy. So the briefs say that
|
|
281
|
+
**confirmation state includes warnings received and acted on, even
|
|
282
|
+
when the outcome is no change** — one line, in the report the member already
|
|
283
|
+
owes. That is the cheap half of the *never read silence as assent* rule, paid
|
|
284
|
+
by the member rather than by the controller's guesswork.
|
|
285
|
+
|
|
286
|
+
## Specialist tree isolation is `review-and-fix.md`'s job
|
|
287
|
+
|
|
288
|
+
It owns the object-store rule, the two-tree split, snapshot provisioning, and
|
|
289
|
+
which suites a snapshot cannot validly run. Your only duty is to *not*
|
|
290
|
+
contradict it.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The run ledger: the evidence behind its rules
|
|
2
2
|
|
|
3
|
-
Evidence for the rules SKILL.md
|
|
3
|
+
Evidence for the rules SKILL.md's wake and dispatch sections state, keyed by the rule each justifies.
|
|
4
4
|
|
|
5
5
|
## Writer policy: every write is serialized by `<file>.lock`
|
|
6
6
|
|
|
@@ -55,3 +55,49 @@ them before filing. The stdout JSON names all of that in one field:
|
|
|
55
55
|
`unverified`. Only `already-filed` and `tracker-hit` carry a non-zero code, so
|
|
56
56
|
`verdict` is the only thing that tells a searched-and-clean tracker from one
|
|
57
57
|
that was never read and from one whose rows you have to read yourself.
|
|
58
|
+
|
|
59
|
+
## Why the ledger outlives your context
|
|
60
|
+
|
|
61
|
+
One git-ignored `.fleet/ledger.md` per run is updated at every state change through the `~/.fleet/bin/fleet-run ledger.mjs` subcommands (`row`, `filed`, `ruled`, `check`, `read`, `dispatch`, `settle`, `review`, `drain`, `rotate`). Your context is the least durable thing in the run: it compacts, and a controller that loses the dispatch map or the filed list redoes finished work. Two duplicate tickets shipped in one run from exactly that.
|
|
62
|
+
|
|
63
|
+
## One line per ticket
|
|
64
|
+
|
|
65
|
+
One line per ticket, rewritten in place (`ledger.mjs row <ticket> <text>`; `ledger.mjs token <key> <token>…` adds a token and keeps every token already on the row):
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
#<t1> impl-<t1>=PR#<p1> → PR#<p1> → MERGED 73b356de
|
|
69
|
+
#<t2> impl-<t2>=PR#<p2> → PR#<p2> · fix-pr-<p2> · ports=16<t2> · ruled:6-applies · held-behind:#<t3>
|
|
70
|
+
#<t3> impl-<t3> · tier=task-high
|
|
71
|
+
#<t4> excluded · behind-pr:#<p2>
|
|
72
|
+
#<t5> excluded · behind-issue:#<t3>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Writing before dispatch
|
|
76
|
+
|
|
77
|
+
A member that dies between spawn and write is invisible — and members die in batches.
|
|
78
|
+
|
|
79
|
+
## filed and ruled
|
|
80
|
+
|
|
81
|
+
`filed` is checked with `ledger.mjs check <subject>` before every `gh issue create`, so a finding already recorded this run is not filed twice. That is not a guarantee against duplicates at large; the tracker query, described under `check`'s exits and its `verdict` above, is what covers those.
|
|
82
|
+
|
|
83
|
+
`ruled` records PR + decision + one-line reason, so a replacement controller does not re-litigate a settled call.
|
|
84
|
+
|
|
85
|
+
## Label-off and conflict holds
|
|
86
|
+
|
|
87
|
+
`label-off=<finisher-pr-M[-x]>` is written before you take `ready-to-merge` off PR M on purpose (taking it off before you approve a push, or clearing one left on a moved head), naming that PR's latest finisher attempt: the tick reads a missing label as a finisher's miss only after the last attempt a `label-off=` names, so without it your own removal reads as one.
|
|
88
|
+
|
|
89
|
+
`ci=<run-id>:<attempt>:<conclusion>` and `held-behind:#<lower>` are row tokens the same way, and so is `conflict-hold:#<pr>` — written by the merge bot itself (`run-merge-bot.md` step 1), or by you off the tick's `CONFLICT` line, naming the row's own PR. It is not an Exclusion: that gates a ticket's claim, this a PR's merge.
|
|
90
|
+
|
|
91
|
+
## The review pair on a PR's row
|
|
92
|
+
|
|
93
|
+
A PR's review is not a member, so it carries its own pair of row tokens, written with `review` onto the PR's row (**The reviewer**): `review=wf:<runId>` | `review=member:<name>` at launch, `<name>` being `review-pr-<n>` for the PR's first runner and the next letter its rows do not carry yet (`review-pr-<n>-b`, `-c`, …) for every later one, settled dead as `…=failed`, then `reviewed=<head>:<survived>/<refuted>/<unverified>:<run>` when the result lands, `<run>` the review's `run-XXXXXXXX` run root under `<scratch>/pr<n>/`, which holds its `review.json`.
|
|
94
|
+
|
|
95
|
+
## The ledger's readability field: `ledger.ok`
|
|
96
|
+
|
|
97
|
+
The ledger half reports its own readability as its own field, `ledger.ok`, the peer of `tracker.ok`: false when the file did not exist, and false too when the path led to something that did not parse as a ledger — which is what tells a ledger that was read and held nothing filed from one there was nothing to read. The absent case is the normal one for a run's first `check` — `.fleet/` is git-ignored and created lazily by the first write — so it stays `clean` at exit 0, and `ledger.ok` is the field that says why rather than the verdict moving. The stderr warning is deliberately narrower than the field: it fires on a path with no file, so a `--file` that landed on the wrong existing file is a silent `ok:false` with nothing on stderr.
|
|
98
|
+
|
|
99
|
+
## Suite slots
|
|
100
|
+
|
|
101
|
+
**The suite slots wedge the same ways, and clear the same way.**
|
|
102
|
+
|
|
103
|
+
`~/.fleet/suite-slots/<i>.lock` is built on the ledger's lock primitive, so an empty or unparseable `<i>.lock` and a leftover `<i>.lock.reap` are permanent, a zombie holder is transient, and a dead holder is taken over. A wait that expired with exit 75 names each wedged file — remove it by hand once no suite run is running, the way the Writer policy section above removes `<file>.lock`.
|
|
@@ -426,6 +426,71 @@ is not enough either: the two rows must actually have RUN a different model or
|
|
|
426
426
|
effort, because a deliberate dispatch whose two sides resolve to the same model
|
|
427
427
|
(a `modelRoles` override, measured on omp 2026-09-12) controls nothing.
|
|
428
428
|
|
|
429
|
+
## Accumulate per PR, never conclude a tier result inside one run
|
|
430
|
+
|
|
431
|
+
The unit is the PR —
|
|
432
|
+
supply is one Pull per free slot, so there are no implementer batches. The
|
|
433
|
+
guard this section speaks of is SKILL.md's **Guard: accumulate per PR, never
|
|
434
|
+
conclude inside one run**, under **Reading a review result**.
|
|
435
|
+
|
|
436
|
+
**Why not decide inside one run.** A run holds 2-3 implementer PRs, and ticket
|
|
437
|
+
difficulty swamps the tier effect — a gojq parity harness and a two-statement
|
|
438
|
+
shell reorder are not comparable units. Finding-counts are not comparable either:
|
|
439
|
+
`selectDimensions` trims the fan-out by diff profile, so one PR fields 29 agents
|
|
440
|
+
over six dimensions and its sibling fields 8 over three. And the fleet is not
|
|
441
|
+
stationary — this prompt gets edited mid-run when a defect earns it. Hence a
|
|
442
|
+
binary outcome per PR, accumulating, rather than a per-run verdict.
|
|
443
|
+
|
|
444
|
+
**Why the rows this guard fired on could not answer the tier question, and this
|
|
445
|
+
is the load-bearing part.** Until the 2026-08-16 revert, `class` and `tier` were
|
|
446
|
+
perfectly confounded: routine always ran `sonnet`, correction always ran top
|
|
447
|
+
tier. So the comparison the file invites — routine-at-sonnet against
|
|
448
|
+
correction-at-top-tier — measures **class, not tier**, because those classes fail
|
|
449
|
+
in different ways by construction: corrections ship false claims in prose,
|
|
450
|
+
routines ship incomplete or regressing code. A run that "mixes both" does not fix
|
|
451
|
+
it. **Only a same-class comparison across tiers is informative**, and the old
|
|
452
|
+
rule could not produce one, since no routine ticket ever ran at top tier.
|
|
453
|
+
|
|
454
|
+
**That last sentence is no longer true, and this is the part to re-read.** Since
|
|
455
|
+
the revert every routine ticket runs at top tier, so the file now holds 55
|
|
456
|
+
`routine`/`opus` rows against 11 `routine`/`sonnet` (as of 2026-08-28, 103 rows
|
|
457
|
+
— recount before citing). The same-class comparison the paragraph above calls
|
|
458
|
+
the only informative one therefore EXISTS now. It is still not clean: the two
|
|
459
|
+
groups are split almost entirely by calendar date, so it measures prompt
|
|
460
|
+
evolution at least as much as tier. Recount
|
|
461
|
+
both, and every other figure in this section, with:
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
grep -vc '^#' docs/metrics/tier-outcomes.tsv
|
|
465
|
+
awk -F'\t' '!/^#/ && $4=="routine" {n++; d[$1]=1; if($6=="no") no++} \
|
|
466
|
+
END{print n, length(d), no+0}' docs/metrics/tier-outcomes.tsv
|
|
467
|
+
awk -F'\t' '!/^#/ && $4=="routine" {t[$5]++; if($6=="no") f[$5]++} \
|
|
468
|
+
END{for(k in t) print k, t[k], f[k]+0}' docs/metrics/tier-outcomes.tsv
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**These rows are the historical corpus, and nothing appended to them fixes the
|
|
472
|
+
confound.** What fixes it is the exploration draw in the dispatch rule above:
|
|
473
|
+
one implementer in every five — every one, during burn-in — runs at a drawn
|
|
474
|
+
cell beside the policy cell's, against the same prompts on the same day, so
|
|
475
|
+
the comparison stops depending on which tier history happened to leave in
|
|
476
|
+
which week. Read each cell's comparisons, not the whole-file split, once there
|
|
477
|
+
are enough of them.
|
|
478
|
+
|
|
479
|
+
**So read what the guard actually established: routine tickets sometimes fail to
|
|
480
|
+
close their own ticket. Not that `sonnet` caused it.** The revert is the
|
|
481
|
+
pre-committed rule being honoured, not a measurement. Restoring a cheap tier is
|
|
482
|
+
still the maintainer's call and still a change to the dispatch rule — but the
|
|
483
|
+
deliberate control it used to require is no longer something anyone has to
|
|
484
|
+
authorize one ticket at a time: the exploration draw above produces one in
|
|
485
|
+
every five Pulls by construction.
|
|
486
|
+
|
|
487
|
+
The last guard fired with n=1 on the control side. The pre-cell pair query
|
|
488
|
+
counted pairs that are no cell's comparisons, and the query before that one
|
|
489
|
+
counted every session whose implementers merely differed, so it cleared the
|
|
490
|
+
cell floor SKILL.md's **Report** states — at least ten comparisons across five
|
|
491
|
+
or more distinct `run_date`s — by an order of magnitude while the controlled
|
|
492
|
+
comparison did not exist yet.
|
|
493
|
+
|
|
429
494
|
## Why the implementer body carries enumerate-and-declare
|
|
430
495
|
|
|
431
496
|
Why the agent body carries what it does — read this before editing any
|
|
@@ -27,6 +27,10 @@ in-between width cannot be told apart from a shifted one. The `note` field is
|
|
|
27
27
|
free text and sits before those four, so the script refuses a tab or newline in
|
|
28
28
|
it: one tab would shift all four for that row alone.
|
|
29
29
|
|
|
30
|
+
**Phase 0 does not size anything** — it shortlists, a Pull claims, and
|
|
31
|
+
the sizing run happens inside the member after both, which is why the verdict
|
|
32
|
+
has to travel in the member's report rather than being something you already hold.
|
|
33
|
+
|
|
30
34
|
## Know the trim before you rely on it
|
|
31
35
|
|
|
32
36
|
**Know the trim before you rely on it — it is wider than `docsOnly` suggests.**
|
|
@@ -135,3 +139,12 @@ reviewed and is not.
|
|
|
135
139
|
## The review's scratch is the scratch root, not its partition
|
|
136
140
|
|
|
137
141
|
**`scratch` is the scratch root itself, the absolute path `scratch-root.mjs` prints, never the review side's `<scratch>/pr<N>` partition:** the review creates `pr<N>/` under it, so a `pr<N>` passed in nests every run root at `pr<N>/pr<N>/`, and the review refuses a `scratch` whose last component is `pr` plus digits before dispatching anything.
|
|
142
|
+
|
|
143
|
+
## Cross-check what members report, and distrust negative claims hardest
|
|
144
|
+
|
|
145
|
+
Wrong often enough
|
|
146
|
+
to matter, and a confident wrong report from a reviewer flips a verdict.
|
|
147
|
+
|
|
148
|
+
"Nothing else references this", "the sweep
|
|
149
|
+
is clean" — most likely false, least likely checked: a grep that found nothing
|
|
150
|
+
looks like a grep never run.
|
|
@@ -14,7 +14,7 @@ The full text of each row `fleet-tick.mjs` prints and what it asks of the contro
|
|
|
14
14
|
lifted), the implementer row's `unclaimed=` prints that same
|
|
15
15
|
`<u>`, not the refreshed file's count.
|
|
16
16
|
- `SUGGEST /triage, hold idle` — nothing is admissible after the refresh.
|
|
17
|
-
Suggest, never run
|
|
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
|
|
@@ -30,7 +30,7 @@ The full text of each row `fleet-tick.mjs` prints and what it asks of the contro
|
|
|
30
30
|
(**The reviewer**).
|
|
31
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>/`.
|
|
32
32
|
- `DISPATCH finisher PR#<M> …` — a finisher settled `labelled`, yet the open
|
|
33
|
-
list shows PR M without `ready-to-merge`, and no `label-off=` (
|
|
33
|
+
list shows PR M without `ready-to-merge`, and no `label-off=` (references/ledger.md)
|
|
34
34
|
accounts for the removal. Apply the finisher gate (**The finisher gate**: the
|
|
35
35
|
`check` job green on the current head), then dispatch the next-suffix
|
|
36
36
|
finisher at that head, one letter past the highest the PR has used: render
|
|
@@ -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.
|