@feigi/fleet-ctl 3.82.13 → 3.82.15
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/agents/fleet-implementer-slow-high.agent.md +1 -1
- package/agents/fleet-implementer-slow-medium.agent.md +1 -1
- package/agents/fleet-implementer-smol-high.agent.md +1 -1
- package/agents/fleet-implementer-task-high.agent.md +1 -1
- package/agents/fleet-implementer-task-max.agent.md +1 -1
- package/commands/review-and-fix.md +2 -1
- package/package.json +1 -1
- package/scripts/ledger.mjs +52 -4
- package/scripts/recipe-check.mjs +260 -0
- package/scripts/report-files.mjs +67 -8
- package/skills/next-ticket/SKILL.md +12 -2
- package/skills/run-team/SKILL.md +49 -718
- package/skills/run-team/briefs/fix-applier.md +13 -3
- package/skills/run-team/briefs/implementer.md +8 -0
- package/skills/run-team/references/isolation.md +1 -1
- package/skills/run-team/references/ledger.md +47 -1
- package/skills/run-team/references/member-lifecycle.md +46 -0
- package/skills/run-team/references/phase-0.md +2 -2
- package/skills/run-team/references/pull.md +31 -5
- package/skills/run-team/references/reviewers.md +5 -1
- package/skills/run-team/references/shell-traps.md +2 -0
- package/skills/run-team/references/tick-rows.md +33 -4
package/skills/run-team/SKILL.md
CHANGED
|
@@ -60,8 +60,8 @@ phase, or in any later one, asks the maintainer which tickets to take.
|
|
|
60
60
|
|
|
61
61
|
The `tier-roles` step runs `~/.fleet/bin/fleet-run tier-roles.mjs --check
|
|
62
62
|
--in-flight <B>`, `<B>` being the `--in-flight` given, else the default
|
|
63
|
-
**Defaults** (**Queue depth**) states. What it checks is
|
|
64
|
-
tier routes. A line it prints on a pass about omp's `task.maxConcurrency`
|
|
63
|
+
**Defaults** (**Queue depth**) states. What it checks is in the header of `scripts/tier-roles.mjs`, under
|
|
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.
|
|
67
67
|
|
|
@@ -185,7 +185,7 @@ it prints. There are no hand-run
|
|
|
185
185
|
refill or dispatch edges left. Record first, because the tick reads the run off
|
|
186
186
|
`.fleet/ledger.md` and nothing else of yours: a wake you have not written down is
|
|
187
187
|
one it cannot see, and it names the same dispatch again. Every record goes
|
|
188
|
-
through `ledger.mjs` (
|
|
188
|
+
through `ledger.mjs` (references/ledger.md), which `fleet-event.mjs` drives — never a
|
|
189
189
|
hand edit. What each kind writes is in the header comment of `fleet-event.mjs`.
|
|
190
190
|
See references/ci-and-staleness.md.
|
|
191
191
|
|
|
@@ -219,7 +219,7 @@ owes beyond its call:
|
|
|
219
219
|
- **Finisher reports halted** → the per-cause rule (**Resolving a finisher
|
|
220
220
|
halt**, below).
|
|
221
221
|
- **A finisher failed or was killed** → the **Member confirmed dead or never dispatched** wake (`member-died finisher-pr-<M>`), which settles it `failed` or `killed`; no label, and the tick prints no dispatch for it. The cockpit flags the PR `finisher:failed` / `finisher:killed` at severity 4 and you resolve it by hand (**Resolving a finisher that died**, below), then run the tick.
|
|
222
|
-
- **Implementer bails before implementing** → `fleet-event.mjs impl-report impl-<N>`; the causes and the issue action each takes are under **Relabel causes**.
|
|
222
|
+
- **Implementer bails before implementing** → `fleet-event.mjs impl-report impl-<N>`; the causes and the issue action each takes are under **Relabel causes**. **Only an explicit bail report — the member itself naming the cause — reaches Implementer bails before implementing**; a died or merely-quiet member's silence never does.
|
|
223
223
|
- **A review result lands** → see the result file written (by you or by the
|
|
224
224
|
runner — **The reviewer** says which, per harness), then
|
|
225
225
|
`fleet-event.mjs review-result <pr> <path>`: a PR with survived or unverified findings prints as
|
|
@@ -283,6 +283,28 @@ owes beyond its call:
|
|
|
283
283
|
context exhaustion caused (references/close-out.md).
|
|
284
284
|
- **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
285
|
|
|
286
|
+
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.
|
|
287
|
+
|
|
288
|
+
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.
|
|
289
|
+
|
|
290
|
+
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.
|
|
291
|
+
|
|
292
|
+
Never rotate the ledger mid-run: it is phase 0's step alone.
|
|
293
|
+
|
|
294
|
+
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.
|
|
295
|
+
|
|
296
|
+
**Write the ledger line before dispatching, not after.** Record the member with `ledger.mjs dispatch`, or `review` for a review, before its `task` call.
|
|
297
|
+
|
|
298
|
+
### Dispatching the members a wake names
|
|
299
|
+
|
|
300
|
+
- **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.
|
|
301
|
+
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.
|
|
302
|
+
- **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**.
|
|
303
|
+
- **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.
|
|
304
|
+
- **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).
|
|
305
|
+
- **The merge bot.** Named `merge-bot-<n>`; `ledger.mjs dispatch merge-bot` numbers it. Never two at once. Render `--member merge-bot-<n> --n <n> --scratch <scratch>`. Dispatch with the printed agent, omit `model`, and dispatch on the first `ready-to-merge` label without waiting for the finisher report.
|
|
306
|
+
- **The finisher.** Its render and receives are in `references/finisher.md`.
|
|
307
|
+
|
|
286
308
|
### Reading a review result
|
|
287
309
|
|
|
288
310
|
**You read the digest; the findings are the fix-applier's.**
|
|
@@ -463,7 +485,7 @@ set it, and never otherwise.
|
|
|
463
485
|
It prints `actual/target` and a named ACTION per role, with the whole **Queue
|
|
464
486
|
depth** guard table applied in code. Act on each line as it reads:
|
|
465
487
|
|
|
466
|
-
- `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
|
|
488
|
+
- `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**.
|
|
467
489
|
- `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.
|
|
468
490
|
- `SUGGEST /triage, hold idle` — nothing is admissible after the refresh: suggest `/triage` in the report, never run it.
|
|
469
491
|
- `specs-dropped=#<N> …` — never actionable: log those tickets as specs awaiting to-tickets, not as no work.
|
|
@@ -488,7 +510,7 @@ depth** guard table applied in code. Act on each line as it reads:
|
|
|
488
510
|
|
|
489
511
|
## Phase 1 — Pull: judge, then claim or relabel
|
|
490
512
|
|
|
491
|
-
A Pull is done when `pull.mjs next` exits 0 with its `DISPATCH` line, which
|
|
513
|
+
A Pull is done when `pull.mjs next` exits 0 with its `DISPATCH` line, which the dispatch under **Dispatching the Pull's member** acts on, or exits 1 with the ticket gone from the Pull: answer each question it stops at, and take the next entry after an exit 1.
|
|
492
514
|
|
|
493
515
|
Serial, main checkout, one Pull at a time. Never parallel, never inside a member —
|
|
494
516
|
concurrent `worktree add` and label writes race.
|
|
@@ -498,7 +520,7 @@ main checkout (without `--head`, the next shortlist entry). It runs the
|
|
|
498
520
|
mechanical steps in order and stops at each judgement with a `QUESTION
|
|
499
521
|
<qid>:` block and exit 10; `pull.mjs answer <N> <qid> <token> [--path <p>] [--]
|
|
500
522
|
[<text>…]` records the answer and resumes it. Exit 0 prints `DISPATCH {name,
|
|
501
|
-
agent, brief}`, the `task` call
|
|
523
|
+
agent, brief}`, the `task` call that the dispatch makes; exit 1 means the ticket left the
|
|
502
524
|
Pull, so take the next entry; exit 2 names what could not be looked at —
|
|
503
525
|
`next --head <N>` again retries a step that failed, the same `answer` again
|
|
504
526
|
retries one that failed while applying an answer (the question stands), and
|
|
@@ -595,7 +617,7 @@ worktree re-created for an already-open PR is in references/isolation.md.
|
|
|
595
617
|
|
|
596
618
|
| `pull.mjs` | At the Pull | Then |
|
|
597
619
|
|---|---|---|
|
|
598
|
-
| 0 `DISPATCH {…}` | decided, live, free, claimed, routed; row, brief and dispatch recorded | the `task` call (
|
|
620
|
+
| 0 `DISPATCH {…}` | decided, live, free, claimed, routed; row, brief and dispatch recorded | the `task` call (**Dispatching the Pull's member**) |
|
|
599
621
|
| 1 `LEFT #N: taken …` | in-flight hit, an in-flight check that could not answer, or `claim-ticket.sh` exit 2 | next entry |
|
|
600
622
|
| 1 `LEFT #N: relabelled elsewhere` | no longer labelled `ready-for-agent` | next entry |
|
|
601
623
|
| 1 `LEFT #N: closed: fixed by …` | `close` answered `yes` | next entry |
|
|
@@ -606,508 +628,18 @@ worktree re-created for an already-open PR is in references/isolation.md.
|
|
|
606
628
|
| 3 | a usage mistake, or a token the question does not take | correct it; nothing was recorded |
|
|
607
629
|
| 10 `QUESTION <qid>:` | a question pending | answer it |
|
|
608
630
|
|
|
609
|
-
|
|
631
|
+
### Dispatching the Pull's member
|
|
610
632
|
|
|
611
633
|
A dispatch is done when its `task` call is made, in the background, with the `agent` and the name `ledger.mjs dispatch` printed, and — for an implementer — the tier check has written its verdict: dispatch nothing on top of an unchecked one.
|
|
612
634
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
**
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
the
|
|
621
|
-
|
|
622
|
-
a Pull dispatches its member with one more `task` call — `agent` the definition `ledger.mjs dispatch` printed for it (below); name `impl-<N>`; in the background, so its report arrives on its own.
|
|
623
|
-
|
|
624
|
-
**No workpool, and no kernel-resident handle.** A pool's only refill virtue —
|
|
625
|
-
handing a queued item to a freed worker without a controller turn — is exactly
|
|
626
|
-
what a Pull forbids, and its completions never arrive per member. `eval`'s
|
|
627
|
-
`agent()` is not the omp call either: a kernel-resident handle dies with the
|
|
628
|
-
kernel and needs a `wait`, while a `task` job is process-level and its report
|
|
629
|
-
auto-delivers on its own, like every other member's.
|
|
630
|
-
|
|
631
|
-
**No per-call tier or effort anywhere on this path**: tier stays the agent
|
|
632
|
-
definition's own frontmatter, whichever `fleet-implementer-<cell>`
|
|
633
|
-
definition the Pull names, and a member's Resolved tier stays readable from its transcript.
|
|
634
|
-
|
|
635
|
-
**Claim what you are about to dispatch, and dispatch it at once**: `pull.mjs`'s
|
|
636
|
-
claim step runs `claim-ticket.sh`, its route, row, render and dispatch steps
|
|
637
|
-
follow in the same command, and your `task` call follows its `DISPATCH` line.
|
|
638
|
-
|
|
639
|
-
**The dispatch is recorded before you make it**: `pull.mjs`'s row step writes the
|
|
640
|
-
row, its render step renders the brief and its dispatch step runs `ledger.mjs
|
|
641
|
-
dispatch`, whose `agent` and `brief.path` its `DISPATCH` line prints as `agent`
|
|
642
|
-
and `brief`.
|
|
643
|
-
|
|
644
|
-
**The prompt carries only what varies**: `pull.mjs`'s render step fills the
|
|
645
|
-
implementer brief's placeholders from the Pull's own read, and the agent body
|
|
646
|
-
carries everything shared, so never re-paste it.
|
|
647
|
-
|
|
648
|
-
**The prompt pastes the Pull's own read of the ticket rather than telling the
|
|
649
|
-
member to re-fetch it, and this still costs one `gh issue view` per ticket,
|
|
650
|
-
never one per member.** The fresh-context design's isolation rationale — "every
|
|
651
|
-
implementer re-reads its Agent Brief... from scratch"
|
|
652
|
-
— is about a member
|
|
653
|
-
never inheriting another ticket's context, not about where the bytes come from.
|
|
654
|
-
Pasting the Pull's own read into the prompt keeps that: the member still gets the
|
|
655
|
-
brief verbatim and re-derives nothing from a sibling. What it drops is the
|
|
656
|
-
unconditional re-fetch of an issue the controller already has open, on every
|
|
657
|
-
dispatch — the fetch the agent body still carries is the backstop for a brief
|
|
658
|
-
that turns out to be insufficient, not the default path.
|
|
659
|
-
|
|
660
|
-
**A member settling with nothing is not, by itself, a bail.** Each member's
|
|
661
|
-
outcome arrives as its own job status. Settle (CONTEXT.md § Coordination) is
|
|
662
|
-
three states on omp — `completed`, `failed`, `cancelled` — and reading
|
|
663
|
-
`failed`/`cancelled` the same as a `completed` turn that had nothing to report
|
|
664
|
-
is the exact collapse this line blocks: a member that died never chose to bail,
|
|
665
|
-
so demoting its ticket on its silence demotes a ticket its member never got the
|
|
666
|
-
chance to judge. Confirm death first, exactly as the Member-killed row does
|
|
667
|
-
(Failure handling, below) — its mtime discriminator, never silence alone.
|
|
668
|
-
**Only an explicit bail report — the member itself naming the cause — reaches
|
|
669
|
-
Implementer bails before implementing** (**What a wake owes beyond its call**, above); a died or
|
|
670
|
-
merely-quiet member's silence never does. A confirmed-dead member recovers
|
|
671
|
-
exactly as the Member-killed row says — new member, new name, the SAME ticket —
|
|
672
|
-
never a demotion.
|
|
673
|
-
|
|
674
|
-
**Dispatch every implementer with the `agent` that `ledger.mjs dispatch`
|
|
675
|
-
printed, and still omit `model` on the `task` call.** The
|
|
676
|
-
tier lives in that definition's frontmatter (`agents/fleet-implementer-<cell>.agent.md`;
|
|
677
|
-
`slow-high`, the policy cell, for a row with no `tier=`), which is what an
|
|
678
|
-
omitted `model` takes first — the session's tier applies only when the definition
|
|
679
|
-
names none, and a member dispatched with `model` set does not get the declared
|
|
680
|
-
tier back. Omitting `model` is therefore still the mechanism; what changed is
|
|
681
|
-
that the tier it resolves to is now declared and pinned rather than inherited by
|
|
682
|
-
accident. Keep `name: impl-<N>`: the name is what makes a member, and both the
|
|
683
|
-
spend classifier and `member-outcomes.mjs` read it.
|
|
684
|
-
|
|
685
|
-
### The reviewer
|
|
686
|
-
|
|
687
|
-
**Dispatch the review, never wait on it, and act when its result lands. That is the default path.** See references/reviewers.md.
|
|
688
|
-
The tick names every PR owed one — `DISPATCH review PR#<M>`, oldest first —
|
|
689
|
-
and each gets one review, once per PR:
|
|
690
|
-
|
|
691
|
-
a `task` member named `review-pr-<pr#>`, agent `fleet-review-runner`, its prompt the five args `pr`, `branch`, `worktree`, `testCmd` and `scratch` — it loads `review-eval.mjs` through the Resolver (`~/.fleet/bin/fleet-run --path review-eval.mjs`), awaits `runReviewOnOmp` in its own kernel, writes the result file into the review's own run root and reports its path and `reviewed=` token; record `review=member:review-pr-<pr#>` on the PR's row before the dispatch call. **`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.
|
|
692
|
-
|
|
693
|
-
Write the `review=` token with `ledger.mjs review <pr> <token>`, which appends
|
|
694
|
-
it to the **PR's row** — the ticket row once `settle impl-<N>=PR#M` has run,
|
|
695
|
-
else `#M`, created when neither exists — and prints that row's key. `review`
|
|
696
|
-
is the only writer of `review=`/`reviewed=` tokens, so a PR's tokens sit on
|
|
697
|
-
one row in the order you wrote them. The tick counts in-flight reviews off the
|
|
698
|
-
ledger and nowhere else, so a review you launched and never recorded is one it
|
|
699
|
-
names again. `review` reads the PR's review record across every row that maps
|
|
700
|
-
to the PR, in ledger order, and refuses a write that adds a `reviewed=` with no
|
|
701
|
-
open launch before it, a launch identity the PR already used, a second launch
|
|
702
|
-
while one is open, or an unparseable `review=`/`reviewed=` (the ledger's token
|
|
703
|
-
grammar, below), or any token that is not a review token. `row` refuses a
|
|
704
|
-
write that adds a review token (one the row holds is carried), drops an open
|
|
705
|
-
launch, names a member twice on the line, or makes a PR map to a second row. A
|
|
706
|
-
bad token the ledger already holds is carried, never re-judged.
|
|
707
|
-
|
|
708
|
-
### The fix-applier
|
|
709
|
-
|
|
710
|
-
**Then dispatch a fix-applier** — on `DISPATCH fix-pr PR#<M>`, which the tick
|
|
711
|
-
prints for a PR whose `reviewed=` counts survived or unverified findings that
|
|
712
|
-
no review fix-applier
|
|
713
|
-
has landed `applied:`/`no-op` on since, with no fix-applier live on it — one
|
|
714
|
-
named member per PR, `fix-pr-<pr#>` (the next suffix when that name is already
|
|
715
|
-
on record), never the PR's
|
|
716
|
-
implementer, its brief rendered with `dispatch-prompt.mjs render fix-applier
|
|
717
|
-
--kind review --member fix-pr-<pr#> …` and then recorded with `ledger.mjs
|
|
718
|
-
dispatch <pr#> fix-pr-<pr#>` before the call. Its brief carries the PR number, the worktree abs path, the PR's branch,
|
|
719
|
-
the same `testCmd` you passed the review, and the **path** `<scratch>/pr<pr>/<run>/review.json`,
|
|
720
|
-
`<run>` off the PR's latest `reviewed=` — never the findings. Every `<scratch>`
|
|
721
|
-
in that brief is the absolute path `scratch-root.mjs` printed, the
|
|
722
|
-
`dispositions-<its name>.json` record's included. The fix-applier owns every per-PR ruling you once made
|
|
723
|
-
before dispatch: both mutual-exclusion scans, the suggested-fix re-derivation,
|
|
724
|
-
`refuted=false` ≠ apply, per-site measurement for a sibling-site extension, and
|
|
725
|
-
`testEnvironment`/`cwdAudit` before any result of the shared test run. It
|
|
726
|
-
holds every finding and you hold none, so there is nothing for you to rank,
|
|
727
|
-
relay or pre-rule — and a report from a refuter it spawns that surfaces to you
|
|
728
|
-
is its to retrieve, never yours to scan or pass on.
|
|
729
|
-
|
|
730
|
-
**A `DISPATCH fix-pr PR#<M>` on a conflict hold** — the row carries an
|
|
731
|
-
unresolved `conflict-hold:#<M>`, the merge bot's (`run-merge-bot.md` step 1's
|
|
732
|
-
fallback) or one you recorded off the tick's `CONFLICT` line —
|
|
733
|
-
is the same slot with a different job, and no review file to hand
|
|
734
|
-
over. Name it `fix-pr-<M>`, or the next suffix (`-b`, `-c` …) when that name is
|
|
735
|
-
already on record — `dispatch` refuses a reused one — render its brief with
|
|
736
|
-
`dispatch-prompt.mjs render fix-applier --kind conflict --member <name> …`,
|
|
737
|
-
and dispatch it with the
|
|
738
|
-
`agent` that `ledger.mjs dispatch` printed, in the PR's existing worktree.
|
|
739
|
-
`dispatch` prints this member's definition, `fleet-fix-applier` whatever the
|
|
740
|
-
row's `tier=` — read it off that output, never recall it. Its `conflict` brief carries the
|
|
741
|
-
same worktree and branch identity paragraph, but in place of the review paragraphs:
|
|
742
|
-
bring `<branch>` current with `origin/main`, resolving every conflict by
|
|
743
|
-
`run-merge-bot.md`'s **No-undo audit**, run `<checkCmd>`/`<testCmd>`, push, report the new
|
|
744
|
-
head. Settle it `applied:<head>` — `no-op` if no conflict was left — which is
|
|
745
|
-
what lifts the tick's merge hold; a dispatched-but-live one does not, and
|
|
746
|
-
`failed`/`killed` leave the PR fix-due for a replacement. It answers the
|
|
747
|
-
hold and nothing else: survived or unverified findings of a returned review the row also carries
|
|
748
|
-
stay unanswered through its settle, so the tick prints `DISPATCH fix-pr
|
|
749
|
-
PR#<M>` again once it lands, and that one is a review fix-applier, dispatched
|
|
750
|
-
as above. On a PR carrying
|
|
751
|
-
`ready-to-merge`, the push moves the head after the label, so the next merge
|
|
752
|
-
bot refuses it `head-moved-after-label-#<M>`: a fresh finisher, per **Failure
|
|
753
|
-
handling**.
|
|
754
|
-
|
|
755
|
-
**Then dispatch a red-check fixer** — on a `check`-job `failure` bound to the
|
|
756
|
-
PR's current head that is not **solely** staleness: a `rebase-check` red, or
|
|
757
|
-
heavy jobs `skipped`, that comes only off the behind-count gets no fixer (the
|
|
758
|
-
five-condition note in the fix-applier body states how to establish
|
|
759
|
-
that). The tick prints no `DISPATCH` for it, so you reconcile it yourself, and
|
|
760
|
-
it always goes after the review, never before. Take the common case, the
|
|
761
|
-
implementer's own first CI firing turning red before any review exists: the
|
|
762
|
-
review is still dispatched against that head as normal, because a red suite is
|
|
763
|
-
review evidence and not a blocker. While a review or a review fix-applier on
|
|
764
|
-
the PR is live or still owes its report — a dispositions mismatch awaiting its
|
|
765
|
-
retry and a conflict hold awaiting its fix-applier included — the red-check
|
|
766
|
-
fixer does not fire: their push starts a new run, and a fixer racing them
|
|
767
|
-
would work on a SHA that is still moving. These edges dispatch it, each the
|
|
768
|
-
finisher's own: **Monitor: CI run completes** binding a red on a head
|
|
769
|
-
whose review has returned and whose fix-applier, if any, has reported;
|
|
770
|
-
**A fix-applier reports `no-op`**, or a SHA you have already bound, when that
|
|
771
|
-
head's bound conclusion is red; and **A review result lands** with no finding
|
|
772
|
-
and nothing to file, when that head's bound conclusion is red. The Monitor is
|
|
773
|
-
edge-keyed on `<run-id>:<attempt>:<conclusion>`, so a red that fired before the
|
|
774
|
-
review never fires again, and with no push there is no new run — the other
|
|
775
|
-
edges are the only route there, so do not wait for an event. Edit one of these
|
|
776
|
-
bullets without the others and what strands is exactly the red-only PR.
|
|
777
|
-
|
|
778
|
-
Name it `fix-pr-<M>`, or the next free suffix (`-b`, `-c` …) — `dispatch`
|
|
779
|
-
refuses a reused name — render its brief with `dispatch-prompt.mjs render
|
|
780
|
-
fix-applier --kind red-check --member <name> …`, record it with `ledger.mjs dispatch <pr#> <name>`
|
|
781
|
-
before the call, and dispatch it with the `agent` that command printed
|
|
782
|
-
(`fleet-fix-applier`, as for a review fix-applier), in the PR's
|
|
783
|
-
existing worktree. Its `red-check` brief carries the same worktree and branch
|
|
784
|
-
identity paragraph, but in place of the review paragraphs: read the failing job's
|
|
785
|
-
log, fix the cause, run `<checkCmd>`/`<testCmd>`, push, report the new head, and **stop**.
|
|
786
|
-
It is told outright not to watch, poll or wait for CI: your persistent Monitor
|
|
787
|
-
owns the next run on the pushed head, and the finisher gate — review returned
|
|
788
|
-
AND fix-applier report sent — applies to this member the same way. Settle it
|
|
789
|
-
`applied:<head>` once a fix is pushed. Settle it `no-op` only when the red
|
|
790
|
-
turns out not to be this member's to fix — a flake that passes on rerun, or
|
|
791
|
-
staleness — with nothing pushed; `no-op` counts as landed, so it never stands
|
|
792
|
-
for "could not fix". Settle it `failed` when it cannot reach green, which the
|
|
793
|
-
**Failure handling** row "Reviewer, fix-applier or red-check fixer cannot
|
|
794
|
-
reach green" covers.
|
|
795
|
-
|
|
796
|
-
**What a fix-applier receives.** Its standing text — the worktree and
|
|
797
|
-
session-root discipline, the review result file and every ruling on it, the
|
|
798
|
-
claim discipline, the refuter prompt, the kill rule, the stash prohibition, the
|
|
799
|
-
dispositions record and the report rules — is the body of
|
|
800
|
-
`agents/fleet-fix-applier.agent.md`, which the harness injects as its system
|
|
801
|
-
prompt, so none of it is pasted. The brief carries what varies, and the
|
|
802
|
-
renderer prints it: `~/.fleet/bin/fleet-run dispatch-prompt.mjs render
|
|
803
|
-
fix-applier --kind <review|conflict|dispositions-retry|red-check> --member <name>
|
|
804
|
-
--worktree <abs-path> --branch <branch> --testCmd <testCmd> --checkCmd <checkCmd> --scratch <scratch>`,
|
|
805
|
-
plus `--pr <pr#> --run <run>` for a review or a dispositions retry and
|
|
806
|
-
`--mismatched-record <path> --violations <list>` for a retry. Then
|
|
807
|
-
`ledger.mjs dispatch <pr#> <name>` records it before the call.
|
|
808
|
-
|
|
809
|
-
### The finisher
|
|
810
|
-
|
|
811
|
-
**What a finisher receives.** The instrument re-check, the test-run verdict
|
|
812
|
-
rule, duty 1's audit script and what each of its exits means, duty 3's script
|
|
813
|
-
and the halt-cause rule are the body of `agents/fleet-finisher.agent.md`, which
|
|
814
|
-
the harness injects as its system prompt; none of them is pasted. The
|
|
815
|
-
instrument-set rule is stated once, in **Rules that fail silently**, which no finisher
|
|
816
|
-
is dispatched to read, so it reaches one through that body. The brief carries
|
|
817
|
-
the PR, the worktree, the dispatch pin, `<testCmd>` and the scratch root:
|
|
818
|
-
`~/.fleet/bin/fleet-run dispatch-prompt.mjs render finisher --kind <fresh|repair>
|
|
819
|
-
--member finisher-pr-<pr#> --pr <pr#> --pin <sha> --worktree <abs-path>
|
|
820
|
-
--testCmd <testCmd> --scratch <scratch>`, which `ledger.mjs dispatch <pr#>
|
|
821
|
-
finisher-pr-<pr#>` records. See references/finisher.md.
|
|
822
|
-
|
|
823
|
-
### The merge bot
|
|
824
|
-
|
|
825
|
-
Per pass, named `merge-bot-<n>`, never two at once. `ledger.mjs dispatch
|
|
826
|
-
merge-bot` names it before the dispatch call — `n` is 1 + the highest
|
|
827
|
-
`merge-bot-<n>` the session holds, in the ledger's `## Dispatched` list, on a
|
|
828
|
-
row or in `## Retired`, so it restarts at 1 only when none was carried or
|
|
829
|
-
retired (a `dead` or `none` rotate, or a fresh start), a replacement for a
|
|
830
|
-
dead bot takes the next `n`, and nobody counts. With no brief rendered for
|
|
831
|
-
that name it refuses, naming the bot and the `dispatch-prompt.mjs render
|
|
832
|
-
merge-bot --member merge-bot-<n> …` to run — render it, then run `ledger.mjs
|
|
833
|
-
dispatch merge-bot` again. Dispatch
|
|
834
|
-
it on the tick's `DISPATCH merge-bot`: the first `ready-to-merge` label is
|
|
835
|
-
enough, and it never waits for the finisher's report. Its body
|
|
836
|
-
(`agents/fleet-merge-bot.agent.md`) tells it to read
|
|
837
|
-
`$(~/.fleet/bin/fleet-run --root)/commands/run-merge-bot.md` and run **one**
|
|
838
|
-
pass — drain the queue, re-evaluating after each merge, hold a 15-minute grace
|
|
839
|
-
for late labels, then report once and exit — and that you dispatched it,
|
|
840
|
-
which is what makes it hold that grace instead of arming its own watcher. Its
|
|
841
|
-
brief is `~/.fleet/bin/fleet-run dispatch-prompt.mjs render merge-bot --member
|
|
842
|
-
merge-bot-<n> --n <n> --scratch <scratch>`, `<scratch>` the absolute
|
|
843
|
-
`scratch-root.mjs` output: the bot writes only under `<scratch>/pr<N>/merge-bot-<n>/`. **Dispatch
|
|
844
|
-
it with the `agent` that `ledger.mjs dispatch merge-bot` printed and omit
|
|
845
|
-
`model` on the call** — the tier lives in that definition's frontmatter,
|
|
846
|
-
`agents/fleet-merge-bot.agent.md`, as the `@smol:low` route, which resolves
|
|
847
|
-
through the operator's `modelRoles`.
|
|
848
|
-
|
|
849
|
-
Rebase, wait for green, check the
|
|
850
|
-
label, merge is checklist work, and a bad merge still needs the label and the
|
|
851
|
-
per-job CI state to have been read correctly, which is exactly what a wrong
|
|
852
|
-
merge later surfaces and a human resolves; `no-undo-audit.sh` is the
|
|
853
|
-
deterministic, script-driven backstop run before every push specifically to
|
|
854
|
-
catch a wrongly-resolved conflict, so the tier buys nothing that backstop
|
|
855
|
-
doesn't already cover.
|
|
856
|
-
|
|
857
|
-
**The shell traps live in the merge bot's agent body, not in its brief and not
|
|
858
|
-
in a follow-up message.** A bot already looping cannot be corrected — the loop
|
|
859
|
-
consumes the turns a correction would land in — so
|
|
860
|
-
`agents/fleet-merge-bot.agent.md` carries **every entry in
|
|
861
|
-
`references/shell-traps.md`'s Shell traps**, each written out in full: the rules
|
|
862
|
-
with the measurements from that file beside them, and the bot has them before its first
|
|
863
|
-
turn. The bot reads an exit code off `gh pr update-branch`, `merge-gate.mjs` and
|
|
864
|
-
`prove-merge.sh`, which is the `PIPESTATUS` and read-only-`status` pair, and its
|
|
865
|
-
grace poll captures `gh pr list` output and loops over it, which is the
|
|
866
|
-
assigned-variable trap. Its brief names the bot and `<scratch>`, plus the
|
|
867
|
-
one-line no-acknowledgement notice; do not paste the traps there. An edit to
|
|
868
|
-
`references/shell-traps.md` lands in that body in the same change.
|
|
869
|
-
|
|
870
|
-
### The tier check
|
|
871
|
-
|
|
872
|
-
**After every Pull's dispatch, the tier check runs — a scripted step, never a
|
|
873
|
-
prose reminder.** `tick --apply` runs it itself, a batch of one apiece, on every
|
|
874
|
-
implementer it would print `HOLD (tier unchecked impl-<N>)` for, `session` off
|
|
875
|
-
the phase-0 receipt, and prints `APPLIED tier-check impl-<N>: <verdict>`. By
|
|
876
|
-
hand it is `~/.fleet/bin/fleet-run tier-check.mjs --batch <path-to-batch.json>`,
|
|
877
|
-
which judges the implementer just dispatched on the ledger's terms. The definition it
|
|
878
|
-
should have run under comes off its ticket row's `tier=` token: none means the
|
|
879
|
-
policy cell, `fleet-implementer-slow-high`; `tier=<cell>` means
|
|
880
|
-
`fleet-implementer-<cell>` — the same mapping `ledger.mjs dispatch` prints its
|
|
881
|
-
`agent` from, so a call that names the printed `agent` passes this half. It
|
|
882
|
-
fails the member two ways, one line each —
|
|
883
|
-
`member: dispatched <agent>, expected <definition>` when its transcript says
|
|
884
|
-
it was dispatched as anything else (a generic `task` dispatch fails even when
|
|
885
|
-
its model happens to match), and `member: declared <m>/<l> resolved <m>/<l>`
|
|
886
|
-
when the harness resolved a different model or level than that definition
|
|
887
|
-
declares. The verdict lands on the ledger either way, written by the check
|
|
888
|
-
itself: exit 0 appends `tier-ok=impl-<N>:<definition>` to the row, once
|
|
889
|
-
however often it runs; exit 1 runs `ledger.mjs settle impl-<N> tier-mismatch`
|
|
890
|
-
— or, for a member already settled another way, which `settle` will not
|
|
891
|
-
re-settle, appends `tier-mismatch=impl-<N>:<definition>` instead. Exit 3 is
|
|
892
|
-
the third verdict, `tier-unverifiable=impl-<N>:no-transcript`, appended once
|
|
893
|
-
for a member settled `killed` or `released` anywhere on the ledger — a row
|
|
894
|
-
or `## Dispatched` — whose `session` names an existing directory holding no
|
|
895
|
-
`<member>.jsonl`: a dispatch that failed before a transcript was written. It
|
|
896
|
-
says nothing about the tier and only clears the unchecked hold. Exit 2
|
|
897
|
-
writes nothing — a usage error, which includes a LIVE member with no
|
|
898
|
-
transcript; a member settled `PR#M`, `bailed` or `tier-mismatch` with none,
|
|
899
|
-
which ran, so the `session` is the wrong directory — pass the one that
|
|
900
|
-
dispatched it; a session directory that cannot be searched; and a `session`
|
|
901
|
-
that is omitted, empty, or not an existing directory, live member or settled.
|
|
902
|
-
A batch holding both a mismatch and an unverifiable member exits 1.
|
|
903
|
-
|
|
904
|
-
**The tick holds the next Pull until that verdict exists.** Dispatching the
|
|
905
|
-
next member on top of an unchecked or mismatched dispatch multiplies whatever
|
|
906
|
-
silently degraded. The tick prints `HOLD (tier unchecked impl-<N>)` for the
|
|
907
|
-
newest `impl-` member of a ticket whose row carries none of the three
|
|
908
|
-
verdicts, whether that member is still running or already settled `bailed`,
|
|
909
|
-
`killed`, `released` or `blocked`, and `HOLD (tier mismatch impl-<N>)` for a mismatched
|
|
910
|
-
one. Only the newest member counts, so a replacement (`impl-<N>-b`) is what
|
|
911
|
-
clears a mismatch — and is then owed its own check. A mismatch on a ticket
|
|
912
|
-
whose issue is already CLOSED holds nothing: the tick probes that issue and
|
|
913
|
-
drops it, since there is nothing left to replace. The tick reads no transcript
|
|
914
|
-
itself: `tick --apply` runs the check, and an unchecked hold it leaves is
|
|
915
|
-
cleared by running the check on that member by hand. A
|
|
916
|
-
settled member's check works only when its transcript exists;
|
|
917
|
-
without one, the verdict is `tier-unverifiable` for a member settled `killed`
|
|
918
|
-
or `released`, and exit 2 naming the wrong session for any other. Clear a
|
|
919
|
-
mismatch by fixing the definition or the dispatch and dispatching the
|
|
920
|
-
replacement at the right tier.
|
|
921
|
-
|
|
922
|
-
**After `/resume`, a carried implementer can hold the whole tick.** A live
|
|
923
|
-
implementer rotate carried forward with no verdict on its row is unchecked,
|
|
924
|
-
and once this session has been reopened with `/resume` its transcript sits
|
|
925
|
-
under the earlier session's directory: the check given this run's `session`
|
|
926
|
-
finds no `<member>.jsonl` and exits 2, writing nothing. `HOLD (tier unchecked
|
|
927
|
-
impl-<N>)` then stops the whole tick, not only that member's ticket. It
|
|
928
|
-
clears once the member settles: one settled `killed` or `released` gets
|
|
929
|
-
`tier-unverifiable=` from the same check, and one settled any other way ran,
|
|
930
|
-
so pass the directory that dispatched it, as above. Until it settles,
|
|
931
|
-
follow the carried token's reconciliation in references/phase-0.md, the
|
|
932
|
-
"Resume the claims a dead run left without a PR" section, under its
|
|
933
|
-
`prior: ancestor` branch: `proc://` reads `running` → wait; dead → the
|
|
934
|
-
Member-killed row.
|
|
935
|
-
|
|
936
|
-
**Run the check right after the dispatch, and never reinstall the plugin or
|
|
937
|
-
change `modelRoles` while any member is unchecked.** The check reads the
|
|
938
|
-
installed definition and the live `modelRoles` at check time, not at
|
|
939
|
-
dispatch time, so a late check judges a member against a tier it was never
|
|
940
|
-
dispatched under.
|
|
941
|
-
|
|
942
|
-
**The batch file is a JSON array, one entry per dispatched member** — under
|
|
943
|
-
Pull, a batch of one, the member just dispatched: `{member}`.
|
|
944
|
-
`member` is the AgentId the dispatch returns (`impl-<N>`). An entry that
|
|
945
|
-
names neither `session` nor `transcript` takes the session directory phase
|
|
946
|
-
0's receipt `.fleet/phase0.json` of `--ledger`'s workspace (else the cwd's,
|
|
947
|
-
resolved as the default ledger is) records, and is exit 2 naming the receipt
|
|
948
|
-
when it records none; the check reads `<session>/<member>.jsonl`, so a
|
|
949
|
-
member with no assistant turn yet still resolves off its dispatch-time
|
|
950
|
-
record. A `session` the entry gives
|
|
951
|
-
wins over the receipt's; a caller already holding the exact transcript path
|
|
952
|
-
may give `transcript` instead. The agent type an implementer was dispatched
|
|
953
|
-
as lives only in its transcript. `resolvedModel`
|
|
954
|
-
**and** `resolvedThinkingLevel` together — the dispatch's own job record —
|
|
955
|
-
may ride beside it and are preferred for the model and level. An `impl-`
|
|
956
|
-
entry carries no `agentFile`: its definition is the row's, never the
|
|
957
|
-
caller's. `transcript`/`session` resolve relative to `--repo` (defaults to
|
|
958
|
-
the plugin's own root), and the check reads and writes the run's own ledger
|
|
959
|
-
unless `--ledger` names another.
|
|
960
|
-
|
|
961
|
-
**The declaration is `model: "@<role>:<level>"` — a fleet tier route, never
|
|
962
|
-
a vendor id.** `@slow`, `@task`, `@smol` resolve through the operator's
|
|
963
|
-
`modelRoles`, so an install with no Anthropic model still runs the fleet
|
|
964
|
-
at whatever each role points at; the explicit `:<level>` suffix wins over
|
|
965
|
-
the role's baked one, and the tier check judges a member against the
|
|
966
|
-
role's own target (`modelRoles.<role>`), never a model family.
|
|
967
|
-
|
|
968
|
-
Before the run's first dispatch run `~/.fleet/bin/fleet-run tier-roles.mjs
|
|
969
|
-
--check --in-flight <B>`, `<B>` the run's In-flight bound (phase 0's
|
|
970
|
-
`tier-roles` step runs it); exit 1 names every definition whose `model:` is not a route,
|
|
971
|
-
every unset `modelRoles.<role>` a definition needs, every definition whose
|
|
972
|
-
`:<level>` its role's target does not run at (omp's model catalog — omp
|
|
973
|
-
would clamp it silently), except an implementer definition whose cell the
|
|
974
|
-
router never selects (not in `router-table.json`'s `cells` or `rows` values,
|
|
975
|
-
and not the policy cell), which is a notice and never stops the run; every
|
|
976
|
-
`task.agentModelOverrides` entry that would shadow a fleet definition, and
|
|
977
|
-
an `--agents` directory holding no `*.agent.md` definition,
|
|
978
|
-
prints the exact remedy, and **stops the run** before any member is
|
|
979
|
-
dispatched — the fleet reads that config and never writes it. omp's
|
|
980
|
-
`task.maxConcurrency` is not the bound (it counts live members, not tickets,
|
|
981
|
-
queues at its ceiling rather than refusing, and is session-wide): below
|
|
982
|
-
2·B+2, or when it cannot be read, the check prints a notice line and still
|
|
983
|
-
exits 0, so the notice is the operator's to read and report, never a reason to
|
|
984
|
-
stop the run.
|
|
985
|
-
|
|
986
|
-
### The exploration rule
|
|
987
|
-
|
|
988
|
-
**Every 5th Pull by ledger count goes at an exploration cell.** See references/pull.md.
|
|
989
|
-
|
|
990
|
-
**The router's arguments, and nothing past its line.** `--impl-row` is the
|
|
991
|
-
row count the Pull creates — or, while an assignment is rolling (the latest
|
|
992
|
-
row 5k and every row after it carry no `tier=`), that row's number;
|
|
993
|
-
`--chain-head` marks a ticket another open ticket sequences after, which is
|
|
994
|
-
what rolls it. `--arm` is `A` for an even ticket number and `B` for an odd
|
|
995
|
-
one; a B Pull routes exactly as an A one until the table opens arm B.
|
|
996
|
-
`--session` is this run's own session directory. The printed line is the
|
|
997
|
-
whole routing decision — never re-derive `CELL` from the table, the guard or
|
|
998
|
-
this paragraph. A missing or unreadable `.fleet/cost-guard.json` routes the
|
|
999
|
-
default cell only (`REASON=guard-missing`), burn-in included, and `REASON`
|
|
1000
|
-
other than `ok` goes on the row as `route=<REASON>`, nowhere else.
|
|
1001
|
-
|
|
1002
|
-
**Count the rate against the ledger, because a Pull is the dispatch unit.** One
|
|
1003
|
-
Pull is one member, so a rate counted per Pull is a rate counted per member —
|
|
1004
|
-
one implementer in five, however the Pulls fall across the run — and the
|
|
1005
|
-
ledger, not your memory, is what still holds the count after a compaction.
|
|
1006
|
-
|
|
1007
|
-
**Do not label it anywhere — the dispatch record already carries it.** A
|
|
1008
|
-
cell's comparisons are a query, and `cell-readout.mjs` is that query: it joins
|
|
1009
|
-
`docs/metrics/ticket-features.tsv`, whose `chosen_cell` names the cell the
|
|
1010
|
-
router chose, to `docs/metrics/member-outcomes.tsv` on `session`+`agent`, and
|
|
1011
|
-
admits a row only when its `subagent_type` is that cell's
|
|
1012
|
-
`fleet-implementer-<cell>` definition and its `effort` is the cell's level.
|
|
1013
|
-
That column is the record of what each member was dispatched AS, scraped like
|
|
1014
|
-
every other column, so it survives the file's regeneration and mislabels no
|
|
1015
|
-
historical row — the pre-cutover `fleet-implementer` and
|
|
1016
|
-
`fleet-implementer-alt` rows are no cell's, and the readout never admits them.
|
|
1017
|
-
A hand-set column would fail both of those tests, and one derived against
|
|
1018
|
-
today's declared tiers would mislabel every historical row.
|
|
1019
|
-
|
|
1020
|
-
**The readout counts DELIBERATE comparisons.** See references/pull.md.
|
|
1021
|
-
|
|
1022
|
-
**Why one in five within the run, not a week of one tier then a week of the other:**
|
|
1023
|
-
tier would then be confounded with calendar date and therefore with prompt
|
|
1024
|
-
evolution, which is exactly the state the rows already on disk are in, and why
|
|
1025
|
-
they cannot answer the question they were collected for.
|
|
1026
|
-
|
|
1027
|
-
The raw split still favours the top tier — 3 failures in 11 `routine`/`sonnet`
|
|
1028
|
-
rows against 3 in 55 `routine`/`opus`. **Do not read that as a tier result.**
|
|
1029
|
-
Tier remains largely confounded with calendar date and therefore with prompt
|
|
1030
|
-
evolution: 8 of the 11 `sonnet` rows fall on 2026-08-13 to 08-17, before the
|
|
1031
|
-
window in which 54 of the 55 `opus` rows were run, and the dispatch prompts
|
|
1032
|
-
gained rules throughout. The confound is **weakened, not resolved.** Three
|
|
1033
|
-
`sonnet` rows now break it rather than one — a maintainer-authorized
|
|
1034
|
-
deliberate control (2026-08-20) and two more (2026-08-21) —
|
|
1035
|
-
all three run against prompts of the same vintage as the `opus`
|
|
1036
|
-
rows, and **all three passed**, which is the direction opposite the raw split.
|
|
1037
|
-
Three is still not a result. **What replaces this argument going forward is the
|
|
1038
|
-
within-run pairing above**: one implementer in five at the alternate tier makes
|
|
1039
|
-
tier orthogonal to date by construction, so the question stops depending on
|
|
1040
|
-
whichever rows history happened to leave. **Orthogonal to date, and to nothing
|
|
1041
|
-
else** — the alternate member is whichever ticket the 5th Pull draws, rolling
|
|
1042
|
-
past every ticket another open one sequences after, while
|
|
1043
|
-
the top tier absorbs all of those, so the pairing trades the calendar confound
|
|
1044
|
-
for a difficulty one that runs in a known direction. That is why the four covariates
|
|
1045
|
-
exist: condition a pair comparison on `sizing`/`profile`/`loc`/`files` before
|
|
1046
|
-
reading it as a tier result, never on the raw split.
|
|
1047
|
-
|
|
1048
|
-
`minted_false_claim` **discriminates and no longer reads "always yes"** — 20 of
|
|
1049
|
-
the 66 `routine` rows carry `no`, 18 at `opus` and 2 at `sonnet`.
|
|
1050
|
-
Read it as a property of the TICKET before the tier: a pure code simplification
|
|
1051
|
-
need not add prose, while a correction ticket adds prose by construction. The
|
|
1052
|
-
honest summary is unchanged and is the reason this paragraph exists: the guard
|
|
1053
|
-
fired on the criterion the maintainer chose in advance, not on a demonstration
|
|
1054
|
-
that the cheaper tier is worse.
|
|
1055
|
-
|
|
1056
|
-
`row` **replaces the whole line**, it does not append. To add a token to a row
|
|
1057
|
-
that exists — `ci=`, `held-behind:#M`, `conflict-hold:#M`, `label-off=`, `ports=`
|
|
1058
|
-
— run `ledger.mjs token <key> <token>…`, which keeps every token already on the
|
|
1059
|
-
row and takes a PR's number as the key. A `row` over a row that already carries
|
|
1060
|
-
`KILLED`, `ports=` or `→ PR#` must repeat those tokens or they are gone, and its
|
|
1061
|
-
stderr, `rewrote row #N; dropped: <token>…`, names each token it lost.
|
|
1062
|
-
|
|
1063
|
-
`ledger.mjs read` is safe on a pipe as well as a redirect: its payload used to
|
|
1064
|
-
be abandoned at the pipe buffer and still exit 0, which is what left `board.mjs`
|
|
1065
|
-
reporting `ledger read parse failed` and serving a blind cockpit for a whole run.
|
|
1066
|
-
Every use above is a recovery path, so a payload that arrives short
|
|
1067
|
-
lands exactly where a settled `ruled:` is unrecoverable — which is why the
|
|
1068
|
-
script's own suite pins that `read`, `row`, `filed` and `ruled` each reach a
|
|
1069
|
-
pipe whole.
|
|
1070
|
-
|
|
1071
|
-
`check` reaches a pipe whole at both of its exits too. Its ALREADY FILED exit
|
|
1072
|
-
sits mid-branch, where falling through would run the near-miss ranking and the
|
|
1073
|
-
tracker search that exit exists to skip — so `check`'s branch is a function now,
|
|
1074
|
-
where `process.exitCode` carries the code and a `return` is what skips them.
|
|
1075
|
-
No payload this script prints is abandoned to `process.exit()` any more,
|
|
1076
|
-
and the suite drives both of `check`'s arms through a pipe on a payload the
|
|
1077
|
-
ledger sizes rather than argv. So trust that payload — and read `verdict`,
|
|
1078
|
-
because a searched-and-clean tracker and one that was never read share exit 0.
|
|
1079
|
-
|
|
1080
|
-
The consumer side raised its cliff rather than removing it. `board.mjs` reads
|
|
1081
|
-
`ledger.mjs read` with an explicit `maxBuffer`, so a payload past node's
|
|
1082
|
-
default is carried rather than killed mid-flight and reported as an
|
|
1083
|
-
unreachable tool — which is how the blind cockpit came back at HTTP 200 even
|
|
1084
|
-
after the pipe cut was gone. That cap is deliberately bounded, so a
|
|
1085
|
-
payload past it lands in the same blind cockpit — and so does a ledger that
|
|
1086
|
-
cannot be read at all, at any size. The read fails open to an empty ledger, and
|
|
1087
|
-
`board.json`'s `ledgerState` field now distinguishes a read that failed or
|
|
1088
|
-
didn't parse from one that succeeded (`unread`/`unparsed`/`read`) —
|
|
1089
|
-
`spend` in the same payload carries its own error the same way. Zeroes
|
|
1090
|
-
on the cockpit are not yet proof the pipeline is idle.
|
|
1091
|
-
|
|
1092
|
-
### The test command every gate runs
|
|
1093
|
-
|
|
1094
|
-
**Where `testCmd` comes from:** the repository's Test entrypoint, out of the
|
|
1095
|
-
Recipe cache phase 0's Recipe derivation step proved —
|
|
1096
|
-
`~/.fleet/bin/fleet-run derive-testcmd.sh . test --at origin/main` prints it — and the one you
|
|
1097
|
-
hand specialists per `review-and-fix.md`'s **Give specialists a stack-free test command**. Pass
|
|
1098
|
-
the same command to the review, to the fix-applier, and to the finisher — whose
|
|
1099
|
-
duty-2 mutation
|
|
1100
|
-
gate runs it too — so every gate runs one command. Omit it from the review args
|
|
1101
|
-
and the review (`review-core.mjs`, in a runner) reads the same cache itself,
|
|
1102
|
-
refusing outright when there is none; the fix-applier has no such fallback, so
|
|
1103
|
-
substituting `<testCmd>` with nothing leaves it no gate at all.
|
|
1104
|
-
|
|
1105
|
-
The review wraps its own one run, so its args carry the bare
|
|
1106
|
-
command; a wrapped string handed to it anyway is harmless, the inner wrapper
|
|
1107
|
-
being the nested case. The fix-applier and the finisher run what you hand them,
|
|
1108
|
-
so hand them the wrapped string as `<testCmd>` —
|
|
1109
|
-
`~/.fleet/bin/fleet-run suite-slot.mjs '<command>'`, each `'` inside the command
|
|
1110
|
-
written `'\''` — and it is still one command for every gate.
|
|
635
|
+
- **One claim, one dispatch, no queue.** Make one claim and then one dispatch of a new member under a name no member has held; nothing holds a queue. Claim what you dispatch and dispatch at once. Record `ledger.mjs dispatch` before the call.
|
|
636
|
+
- **The implementer.** Dispatch with the printed `agent`, omit `model`, keep `name: impl-<N>`. **No per-call tier or effort anywhere on this path**: tier stays the agent definition's own frontmatter, whichever `fleet-implementer-<cell>` definition the Pull names, and a member's Resolved tier stays readable from its transcript. The prompt carries only what varies, and the Pull's own read of the ticket is pasted, never a request to re-fetch. A member settling with nothing is not a bail. Confirm death first, exactly as the Member-killed row does (Failure handling, below) — its mtime discriminator, never silence alone. Only the member's own bail report counts. A confirmed-dead member recovers exactly as the Member-killed row says — new member, new name, the SAME ticket — never a demotion.
|
|
637
|
+
- **The tier check.** **Run the check right after the dispatch, and never reinstall the plugin or change `modelRoles` while any member is unchecked.** `tick --apply`, or `tier-check.mjs --batch <path>`, runs it. Dispatch nothing on top of an unchecked or mismatched member. **The tick holds the next Pull until that verdict exists.** Clear a mismatch by fixing the definition or the dispatch and dispatching the replacement at the right tier. A mismatch on a ticket whose issue is already CLOSED holds nothing: the tick probes that issue and drops it, since there is nothing left to replace. **After `/resume`, a carried implementer can hold the whole tick.** It holds when the check given this run's `session` finds no `<member>.jsonl` and exits 2, writing nothing. `HOLD (tier unchecked impl-<N>)` then stops the whole tick, not only that member's ticket. It clears once the member settles: one settled `killed` or `released` gets `tier-unverifiable=` from the same check, and one settled any other way ran, so pass the directory that dispatched it, as above. Until it settles, follow the carried token's reconciliation in references/phase-0.md, the "Resume the claims a dead run left without a PR" section, under its `prior: ancestor` branch: `proc://` reads `running` → wait; dead → the Member-killed row.
|
|
638
|
+
- **The exploration rule.** **Every 5th Pull by ledger count goes at an exploration cell.** See references/pull.md. The printed CELL line is the whole routing decision: never re-derive `CELL`, never label a cell by hand. **Do not label it anywhere — the dispatch record already carries it.** **The readout counts DELIBERATE comparisons.** See references/pull.md.
|
|
639
|
+
|
|
640
|
+
Pointers: the `/resume` rules are in `references/phase-0.md`; the tick HOLD tier rules are in `references/tick-rows.md`; shell traps are in `references/shell-traps.md`; the fix-applier's per-PR rulings are in `agents/fleet-fix-applier.agent.md`. Rationale for these rules: `references/member-lifecycle.md`.
|
|
641
|
+
|
|
642
|
+
**The test command every gate runs.** Pass the same command to the review, to the fix-applier, and to the finisher — whose duty-2 mutation gate runs it too — so every gate runs one command. The fix-applier and the finisher run what you hand them, so hand them the wrapped string as `<testCmd>` — `~/.fleet/bin/fleet-run suite-slot.mjs '<command>'`, each `'` inside the command written `'\''` — and it is still one command for every gate. The review wraps its own one run, so its args carry the bare command; a wrapped string handed to it anyway is harmless, the inner wrapper being the nested case. Omit it from the review args and the review (`review-core.mjs`, in a runner) reads the same cache itself, refusing outright when there is none; the fix-applier has no such fallback, so substituting `<testCmd>` with nothing leaves it no gate at all. See `references/isolation.md` (the stack-free test command).
|
|
1111
643
|
|
|
1112
644
|
**Where `<branch>` comes from:** the PR's head branch — `gh pr view <N> --json headRefName -q .headRefName`, or the same branch you cut the worktree from. See references/reviewers.md.
|
|
1113
645
|
|
|
@@ -1156,7 +688,7 @@ Dispatch a finisher only when every test here passes at once — the review retu
|
|
|
1156
688
|
same goes for a red one: a red that fired before the review never fires
|
|
1157
689
|
again, so the **fix-applier reports `no-op`** and **review result lands**
|
|
1158
690
|
bullets are also the only routes to the red-check fixer there.
|
|
1159
|
-
Then run the tick.
|
|
691
|
+
Then run the tick. These edges dispatch it, each the finisher's own: **Monitor: CI run completes** binding a red on a head whose review has returned and whose fix-applier, if any, has reported; **A fix-applier reports `no-op`**, or a SHA you have already bound, when that head's bound conclusion is red; and **A review result lands** with no finding and nothing to file, when that head's bound conclusion is red.
|
|
1160
692
|
- **A fix-applier reports `no-op`, or a SHA you have already bound** → dispatch
|
|
1161
693
|
the finisher **now**, against the existing head — when that head's bound
|
|
1162
694
|
conclusion is red, the red-check fixer instead (**Then dispatch a red-check
|
|
@@ -1172,8 +704,7 @@ Dispatch a finisher only when every test here passes at once — the review retu
|
|
|
1172
704
|
whole route to a finisher**: a `no-op` report satisfies the gate above by
|
|
1173
705
|
itself — the review has returned, because a fix-applier exists only after it
|
|
1174
706
|
did, and the member has reported — so nothing further is owed and no further
|
|
1175
|
-
event is coming. Edit either bullet without the other and what strands is
|
|
1176
|
-
exactly the PR that needed no fixes.
|
|
707
|
+
event is coming. Edit either bullet without the other and what strands is exactly the PR that needed no fixes. Edit one of these bullets without the others and what strands is exactly the red-only PR.
|
|
1177
708
|
- **`ci-state.mjs --pr <N>` reads `verdict: "no-ci"`** → no workflow run will ever
|
|
1178
709
|
complete for this repo, so the CI-run-completion edge above never fires and
|
|
1179
710
|
waiting for it stalls the whole PR — the same silent-stall shape reported
|
|
@@ -1306,7 +837,7 @@ merge. `review-and-fix.md` states them; the body says they apply.
|
|
|
1306
837
|
|
|
1307
838
|
**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
|
|
1308
839
|
(`ledger.mjs token <PR number> label-off=<latest finisher attempt>`, which keeps
|
|
1309
|
-
every token the row already has
|
|
840
|
+
every token the row already has, see references/ledger.md), then
|
|
1310
841
|
remove `ready-to-merge`, then approve. Then
|
|
1311
842
|
dispatch a **fresh** finisher against the new head; the first audit does not
|
|
1312
843
|
transfer, since it verified a different tree. The merge bot refuses that head on
|
|
@@ -1586,6 +1117,8 @@ and flag the PR for a human. A new review of the PR counts again from none.
|
|
|
1586
1117
|
|
|
1587
1118
|
These rules fail silently when they are skipped: check each before you act on a member's silence, and stop reading silence as a report or as assent.
|
|
1588
1119
|
|
|
1120
|
+
Zeroes on the cockpit are not yet proof the pipeline is idle.
|
|
1121
|
+
|
|
1589
1122
|
**A report is delivered, not implied by ending a turn.** Every member owes
|
|
1590
1123
|
the controller one report naming its outcome before it exits — including a
|
|
1591
1124
|
member whose prompt carries no explicit report line. The "report the SHA",
|
|
@@ -1840,7 +1373,7 @@ untouched, so re-running it is safe. That append is the whole duty.
|
|
|
1840
1373
|
|
|
1841
1374
|
The row's last four fields are the ticket's difficulty, and they are what lets a
|
|
1842
1375
|
tier comparison condition on the thing that swamps it. `sizing` is the
|
|
1843
|
-
**member's** own `light`/`heavy` verdict from its
|
|
1376
|
+
**member's** own `light`/`heavy` verdict from its dispatched `sizing-a-ticket`
|
|
1844
1377
|
run, which `append` reads itself off the member's report file
|
|
1845
1378
|
(`<scratch>/reports/impl-<ticket>[-x].json`, its `sizing.verdict`, the highest
|
|
1846
1379
|
retry letter's when there are several), so you pass no `--sizing`; the member
|
|
@@ -1988,212 +1521,6 @@ hold the old text — re-brief only if it changes what they do *now*.
|
|
|
1988
1521
|
|
|
1989
1522
|
Plan it as a PR the run's own merge queue lands, never as an edit-and-continue. See references/merge-bot.md.
|
|
1990
1523
|
|
|
1991
|
-
## Run ledger
|
|
1992
|
-
|
|
1993
|
-
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.
|
|
1994
|
-
|
|
1995
|
-
One git-ignored `.fleet/ledger.md` per run, updated at **every** state change via
|
|
1996
|
-
`~/.fleet/bin/fleet-run ledger.mjs` subcommands (`row`, `filed`, `ruled`,
|
|
1997
|
-
`check`, `read`, `dispatch`, `settle`, `review`, `drain`, `rotate`). Your context is the least
|
|
1998
|
-
durable thing in the run: it compacts, and a controller that loses the dispatch
|
|
1999
|
-
map or the filed list redoes finished work. Two duplicate tickets shipped in one
|
|
2000
|
-
run from exactly that.
|
|
2001
|
-
Compaction parks every in-flight member and nothing reports it, so on resume
|
|
2002
|
-
take the registry census first (**Member unaccounted**, in **Failure
|
|
2003
|
-
handling**) before you trust a live token.
|
|
2004
|
-
|
|
2005
|
-
**The file lives one run.** Phase 0 rotates it before anything reads it, so
|
|
2006
|
-
no exclusion, filing, ruling or drain marker outlives the run that wrote it,
|
|
2007
|
-
and no row does either — except, when the previous run was this session's
|
|
2008
|
-
own, a row still carrying a live token, which rotate carries forward with
|
|
2009
|
-
its live `## Dispatched` entries (phase 0). Never rotate mid-run: it is
|
|
2010
|
-
phase 0's step alone.
|
|
2011
|
-
|
|
2012
|
-
One line per ticket, rewritten in place (`ledger.mjs row <ticket> <text>`):
|
|
2013
|
-
|
|
2014
|
-
```
|
|
2015
|
-
#<t1> impl-<t1>=PR#<p1> → PR#<p1> → MERGED 73b356de
|
|
2016
|
-
#<t2> impl-<t2>=PR#<p2> → PR#<p2> · fix-pr-<p2> · ports=16<t2> · ruled:6-applies · held-behind:#<t3>
|
|
2017
|
-
#<t3> impl-<t3> · tier=task-high
|
|
2018
|
-
#<t4> excluded · behind-pr:#<p2>
|
|
2019
|
-
#<t5> excluded · behind-issue:#<t3>
|
|
2020
|
-
```
|
|
2021
|
-
|
|
2022
|
-
**A member's token is `<member>` while it is live and `<member>=<outcome>` once
|
|
2023
|
-
it settles** — the grammar is `ledger-grammar.mjs`'s, and the tick derives every
|
|
2024
|
-
live count from it, so nobody states one. Write neither by hand:
|
|
2025
|
-
|
|
2026
|
-
- **`ledger.mjs dispatch <ticket|pr> <member>`** — before the dispatch call,
|
|
2027
|
-
marks the member live on its row and appends it to `## Dispatched` with
|
|
2028
|
-
`brief=<first 8 hex of its brief's sha256>`. `dispatch
|
|
2029
|
-
merge-bot` names the next bot itself. It prints `{member, agent, ticket,
|
|
2030
|
-
line, created, total, brief}`, `brief` the `{path, sha256}` of the rendered
|
|
2031
|
-
brief the `task` call is handed; `agent` is the definition the `task` call names — an
|
|
2032
|
-
implementer's comes off its row's `tier=` (phase 2's tier check states the
|
|
2033
|
-
mapping), a finisher's and a merge bot's are their own definitions, and a
|
|
2034
|
-
fix-applier's is `fleet-fix-applier` for every kind of work. It
|
|
2035
|
-
refuses a member whose `briefs/<member>.json` beside the ledger is missing,
|
|
2036
|
-
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
|
|
2037
|
-
`## Retired` — and names the replacement (`-b`, `-c` …), a
|
|
2038
|
-
second live member on one ticket or PR, any implementer once the run is
|
|
2039
|
-
draining, an implementer whose row's `tier=` names no single definition
|
|
2040
|
-
or one with no file under `agents/`, and a finisher on a ledger the tick
|
|
2041
|
-
itself refuses.
|
|
2042
|
-
- **`ledger.mjs settle <member> <outcome>`** — rewrites the token to
|
|
2043
|
-
`<member>=<outcome>` in both places. Outcomes: `impl-N` = `PR#M | bailed |
|
|
2044
|
-
released | killed | tier-mismatch | blocked`; `fix-pr-M` = `applied:<head> | no-op |
|
|
2045
|
-
failed | killed`; `finisher-pr-M` = `labelled | failed | killed |
|
|
2046
|
-
halted:<cause>`, `<cause>` one of `live-editor | rebase | past-pin |
|
|
2047
|
-
unreadable | missing | absent | other` (**Resolving a finisher halt**);
|
|
2048
|
-
`merge-bot-n` = `done | killed`. A settled member stays settled. `settle
|
|
2049
|
-
impl-N=PR#M` also folds a `#M` row a PR-bound dispatch or a `review` made
|
|
2050
|
-
before the settle into `#N`'s row, so the PR's row is the one row naming
|
|
2051
|
-
each PR from then on — but only when `#N`'s row named no PR before this
|
|
2052
|
-
write (a second `PR#` on it would misattribute the fold, or bury `#N`'s own
|
|
2053
|
-
fresher `review=` state under an older one) and no implementer claims `#M`'s
|
|
2054
|
-
row as its own. `settle impl-N=PR#M` refuses a settle after which a PR maps
|
|
2055
|
-
to more than one row when it did not before, judged after the fold — a PR
|
|
2056
|
-
has one row, the PR's row; a split already on disk is carried.
|
|
2057
|
-
- **`ledger.mjs settle <member> --from-report [--reports <dir>]`** — the
|
|
2058
|
-
settle for a member that wrote a report: reads `<dir>/<member>.json`
|
|
2059
|
-
(`<dir>` defaults to `<scratch>/reports`), derives the outcome from it and,
|
|
2060
|
-
before writing, checks what that outcome claims — `PR#M`: `verify-sha.sh`
|
|
2061
|
-
on the report's branch and head, and PR M OPEN and closing N; `bailed`: a
|
|
2062
|
-
`bailCause`, which the receipt names; `applied:<head>`: origin's branch at
|
|
2063
|
-
that head; `no-op`: origin's branch at the head the PR's latest
|
|
2064
|
-
`reviewed=` names; `labelled`: `ready-to-merge` on the PR and in the
|
|
2065
|
-
read-back; `halted:<cause>`: a cause and non-empty evidence; `done`: every
|
|
2066
|
-
PR the bot merged reads MERGED. It then records each `unrecorded` filing in
|
|
2067
|
-
**filed** and, for a fix-applier, each refutation the report defers in
|
|
2068
|
-
**ruled**, once however often it runs, and prints `{member, outcome,
|
|
2069
|
-
checks, filed, ruled}`. Exit 3: a check failed — the member's claim and the
|
|
2070
|
-
world disagree — and nothing is written; you rule. Exit 2: the report is
|
|
2071
|
-
absent or invalid, or a check could not look, and nothing is written; a
|
|
2072
|
-
check that could not look is run again once `gh` and `git` answer. The bare
|
|
2073
|
-
form above, for an outcome a report carries, warns `settled without
|
|
2074
|
-
--from-report` on stderr.
|
|
2075
|
-
- **`ledger.mjs review <pr> <token>…`** — the only writer of `review=` and
|
|
2076
|
-
`reviewed=` (below): appends each to the PR's row — the ticket row once
|
|
2077
|
-
`settle impl-<N>=PR#M` has run, else `#M`, created when neither exists —
|
|
2078
|
-
and prints `{pr, row, line, created, next}`, `row` the key written and
|
|
2079
|
-
`next` the runner name the PR's next launch takes, null with every letter
|
|
2080
|
-
taken. A
|
|
2081
|
-
`review=<launch>=failed` whose `review=<launch>` the row holds settles that
|
|
2082
|
-
launch in place, alone or with the next launch in the same call. Any other
|
|
2083
|
-
token is refused.
|
|
2084
|
-
- **`ledger.mjs drain "<reason>"`** — at most one standing drain marker; supply
|
|
2085
|
-
stops, the review and merge sides keep going. It lives in this run's
|
|
2086
|
-
ledger, so the next run's rotation retires it with everything else.
|
|
2087
|
-
- **`ledger.mjs undrain "<reason>"`** — clears the drain marker and refuses when
|
|
2088
|
-
no marker is set. `closeout.mjs reopen` runs it.
|
|
2089
|
-
- **`ledger.mjs rotate`** — phase 0's first ledger write, every run: moves
|
|
2090
|
-
the file as-is to `ledger.<UTC YYYY-MM-DDTHHMMSSZ>.md` beside it under the
|
|
2091
|
-
same `<file>.lock`, and the next write starts the empty skeleton — unless
|
|
2092
|
-
the previous run was this session's own, when it first writes the rows and
|
|
2093
|
-
`## Dispatched` entries still live into a new ledger, and every other
|
|
2094
|
-
settled name, or name given an out-of-vocabulary outcome, into its
|
|
2095
|
-
`## Retired` (references/phase-0.md, "Rotate the ledger", says which). Refused
|
|
2096
|
-
while the recorded controller is alive and not this run's ancestor, ledger
|
|
2097
|
-
or none — or, with no record and a ledger to move, while the heartbeat mark
|
|
2098
|
-
is still beating, or a tick or ledger write is inside its window with no
|
|
2099
|
-
beat — or when the archive name already exists, the process
|
|
2100
|
-
table cannot be read, or `.fleet/heartbeat.json` exists but cannot be used
|
|
2101
|
-
(unreadable, not JSON, not an object, or a `controller` it cannot judge);
|
|
2102
|
-
with no ledger and no such refusal it moves nothing and exits 0. Then it
|
|
2103
|
-
records this run's controller in `.fleet/heartbeat.json`. The guard is
|
|
2104
|
-
stated in references/phase-0.md, "Rotate the ledger".
|
|
2105
|
-
|
|
2106
|
-
`row` refuses a malformed member token anywhere in its text — one whose outcome
|
|
2107
|
-
is outside the vocabulary above, e.g. `merge-bot-1=dispatched` — at exit 2,
|
|
2108
|
-
naming the token and writing nothing, because every reader would take it as a
|
|
2109
|
-
permanent settle. It refuses a write that adds a `review=`/`reviewed=` token
|
|
2110
|
-
(one the row already holds is carried), and one after which a PR maps to more
|
|
2111
|
-
than one row when it did not before — `row <M>` creating `#M` for a PR a ticket
|
|
2112
|
-
row maps, or a rewrite adding a `PR#M` mention to another row — naming the
|
|
2113
|
-
PR's row (`settle impl-N=PR#M` applies the same rule after its fold).
|
|
2114
|
-
|
|
2115
|
-
`→ PR#M` stays as the human-readable arrow; the tick reads only the `=`
|
|
2116
|
-
tokens. `tier=<cell>` marks an Exploration Pull's cell and `route=<REASON>` a router fallback (phase 2), and `excluded ·
|
|
2117
|
-
behind-pr:#M | behind-issue:#M` is an Exclusion (phase 1) — a ticket row like
|
|
2118
|
-
any other, never a section of its own.
|
|
2119
|
-
|
|
2120
|
-
A PR's review is not a member, so it carries its own pair of row tokens,
|
|
2121
|
-
written with `review` onto the PR's row (**The reviewer**): `review=wf:<runId>` |
|
|
2122
|
-
`review=member:<name>` at launch, `<name>` being
|
|
2123
|
-
`review-pr-<n>` for the PR's first runner and the next letter its rows do not
|
|
2124
|
-
carry yet (`review-pr-<n>-b`, `-c`, …) for every later one,
|
|
2125
|
-
settled dead as `…=failed`, then `reviewed=<head>:<survived>/<refuted>/<unverified>:<run>`
|
|
2126
|
-
when the result lands, `<run>` the review's `run-XXXXXXXX` run root under
|
|
2127
|
-
`<scratch>/pr<n>/`, which holds its `review.json`. A row with `review=` and no `reviewed=` is a review in
|
|
2128
|
-
flight, and the tick counts it as a live review unit unless gh reports its PR
|
|
2129
|
-
MERGED or CLOSED. `ci=<run-id>:<attempt>:<conclusion>`
|
|
2130
|
-
and `held-behind:#<lower>` are row tokens the same way, and so is
|
|
2131
|
-
`conflict-hold:#<pr>` — written by the merge bot itself (`run-merge-bot.md`
|
|
2132
|
-
step 1), or by you off the tick's `CONFLICT` line, naming the row's own PR. It
|
|
2133
|
-
is not an Exclusion: that gates a ticket's claim, this a PR's merge. `label-off=<finisher-pr-M[-x]>`
|
|
2134
|
-
is yours, written before you take `ready-to-merge` off PR M on purpose (taking
|
|
2135
|
-
it off before you approve a push, or clearing one left on a moved head),
|
|
2136
|
-
naming that PR's latest finisher attempt: the tick reads a missing label as a
|
|
2137
|
-
finisher's miss only after the last attempt a `label-off=` names, so without
|
|
2138
|
-
it your own removal reads as one. `row` and `token` refuse a `label-off=` naming no
|
|
2139
|
-
`finisher-pr` member that `## Dispatched` or some row already carries.
|
|
2140
|
-
|
|
2141
|
-
`review` holds a PR's review record to these rules, read across every row that
|
|
2142
|
-
maps to the PR — its settled `impl-<N>=PR#<M>` token, else its first `PR#`
|
|
2143
|
-
mention, else its key — in ledger order. Every `reviewed=` answers the nearest
|
|
2144
|
-
earlier launch that is neither answered nor `=failed`. A launch identity (a
|
|
2145
|
-
workflow's runId, or a runner's name, `=failed` aside) appears once, and no launch takes a runner name the
|
|
2146
|
-
ledger's `## Retired` holds — an earlier run of this session launched it —
|
|
2147
|
-
naming the next free reviewer name instead. No launch appears
|
|
2148
|
-
while an earlier one is open — unanswered and not `=failed`. Every
|
|
2149
|
-
`review=`/`reviewed=` parses. `row` holds the rest: a launch open before the
|
|
2150
|
-
write is still on one of the PR's rows after it, `=failed` allowed, and a
|
|
2151
|
-
member name appears once on the line, whatever its outcome. A write that adds
|
|
2152
|
-
a violation is refused, exit 2, nothing written; one already on the ledger and
|
|
2153
|
-
carried forward is not. A second launch refused for an open one waits for
|
|
2154
|
-
that one's `reviewed=`, or — once the Member-killed row confirms its runner
|
|
2155
|
-
dead — settles it `=failed` in the same `review` call as the new launch.
|
|
2156
|
-
|
|
2157
|
-
Plus two append-only lists:
|
|
2158
|
-
|
|
2159
|
-
- **filed** (`ledger.mjs filed <issue> <subject>`, checked with `ledger.mjs check
|
|
2160
|
-
<subject>` before every `gh issue create`) — so a finding already recorded
|
|
2161
|
-
this run is not filed twice. That is not a guarantee against duplicates at
|
|
2162
|
-
large; the tracker query below is what covers those. The FILER writes this
|
|
2163
|
-
one, not you: `review-and-fix.md` step 5 and finisher duty 2 each run `filed`
|
|
2164
|
-
immediately after their own `gh issue create`, with the subject they passed
|
|
2165
|
-
to `check`, and report `filed: #N <subject>`. A filer whose `filed` failed
|
|
2166
|
-
twice reports `unrecorded: #N <subject>` instead — an `unrecorded` entry of
|
|
2167
|
-
its report file — and `settle <member> --from-report` runs `filed` for it.
|
|
2168
|
-
- **ruled** (`ledger.mjs ruled <pr> <decision>`) — PR + decision + one-line reason,
|
|
2169
|
-
so a replacement controller does not re-litigate a settled call.
|
|
2170
|
-
|
|
2171
|
-
**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.
|
|
2172
|
-
|
|
2173
|
-
**The suite slots wedge the same ways, and clear the same way.**
|
|
2174
|
-
`~/.fleet/suite-slots/<i>.lock` is built on the ledger's lock primitive, so an empty or
|
|
2175
|
-
unparseable `<i>.lock` and a leftover `<i>.lock.reap` are permanent, a zombie
|
|
2176
|
-
holder is transient, and a dead holder is taken over. A wait that expired with
|
|
2177
|
-
exit 75 names each wedged file — remove it by hand once no suite run is
|
|
2178
|
-
running, the way references/ledger.md removes `<file>.lock`.
|
|
2179
|
-
|
|
2180
|
-
**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.
|
|
2181
|
-
|
|
2182
|
-
`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.
|
|
2183
|
-
|
|
2184
|
-
The ledger half reports its own readability as its own field, `ledger.ok`, the
|
|
2185
|
-
peer of `tracker.ok`: false when the file did not exist, and false too when the
|
|
2186
|
-
path led to something that did not parse as a ledger — which is what tells a
|
|
2187
|
-
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/`
|
|
2188
|
-
is git-ignored and created lazily by the first write — so it stays `clean` at
|
|
2189
|
-
exit 0, and `ledger.ok` is the field that says why rather than the verdict
|
|
2190
|
-
moving. The stderr warning is deliberately narrower than the field: it fires on
|
|
2191
|
-
a path with no file, so a `--file` that landed on the wrong existing file is a
|
|
2192
|
-
silent `ok:false` with nothing on stderr. Gate on the field, not on the warning.
|
|
2193
|
-
|
|
2194
|
-
**Write the ledger line before dispatching, not after.** A member that dies
|
|
2195
|
-
between spawn and write is invisible — and members die in batches.
|
|
2196
|
-
|
|
2197
1524
|
## Failure handling
|
|
2198
1525
|
|
|
2199
1526
|
Match the failure to its row in the table below and run that response; where the row says report or flag, stop there.
|
|
@@ -2216,6 +1543,10 @@ Match the failure to its row in the table below and run that response; where the
|
|
|
2216
1543
|
| 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 |
|
|
2217
1544
|
| 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 |
|
|
2218
1545
|
| 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 |
|
|
1546
|
+
| 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. |
|
|
1547
|
+
| `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. |
|
|
1548
|
+
| `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. |
|
|
1549
|
+
| 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. |
|
|
2219
1550
|
|
|
2220
1551
|
### Recovery
|
|
2221
1552
|
|