@feigi/fleet-ctl 3.82.13 → 3.82.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +1 -0
- 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 +35 -514
- 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/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 +32 -3
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
|
|
|
@@ -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,15 @@ 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
|
+
### Dispatching the members a wake names
|
|
287
|
+
|
|
288
|
+
- **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.
|
|
289
|
+
- **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
|
+
- **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
|
+
- **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).
|
|
292
|
+
- **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.
|
|
293
|
+
- **The finisher.** Its render and receives are in `references/finisher.md`.
|
|
294
|
+
|
|
286
295
|
### Reading a review result
|
|
287
296
|
|
|
288
297
|
**You read the digest; the findings are the fix-applier's.**
|
|
@@ -463,7 +472,7 @@ set it, and never otherwise.
|
|
|
463
472
|
It prints `actual/target` and a named ACTION per role, with the whole **Queue
|
|
464
473
|
depth** guard table applied in code. Act on each line as it reads:
|
|
465
474
|
|
|
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
|
|
475
|
+
- `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
476
|
- `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
477
|
- `SUGGEST /triage, hold idle` — nothing is admissible after the refresh: suggest `/triage` in the report, never run it.
|
|
469
478
|
- `specs-dropped=#<N> …` — never actionable: log those tickets as specs awaiting to-tickets, not as no work.
|
|
@@ -488,7 +497,7 @@ depth** guard table applied in code. Act on each line as it reads:
|
|
|
488
497
|
|
|
489
498
|
## Phase 1 — Pull: judge, then claim or relabel
|
|
490
499
|
|
|
491
|
-
A Pull is done when `pull.mjs next` exits 0 with its `DISPATCH` line, which
|
|
500
|
+
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
501
|
|
|
493
502
|
Serial, main checkout, one Pull at a time. Never parallel, never inside a member —
|
|
494
503
|
concurrent `worktree add` and label writes race.
|
|
@@ -498,7 +507,7 @@ main checkout (without `--head`, the next shortlist entry). It runs the
|
|
|
498
507
|
mechanical steps in order and stops at each judgement with a `QUESTION
|
|
499
508
|
<qid>:` block and exit 10; `pull.mjs answer <N> <qid> <token> [--path <p>] [--]
|
|
500
509
|
[<text>…]` records the answer and resumes it. Exit 0 prints `DISPATCH {name,
|
|
501
|
-
agent, brief}`, the `task` call
|
|
510
|
+
agent, brief}`, the `task` call that the dispatch makes; exit 1 means the ticket left the
|
|
502
511
|
Pull, so take the next entry; exit 2 names what could not be looked at —
|
|
503
512
|
`next --head <N>` again retries a step that failed, the same `answer` again
|
|
504
513
|
retries one that failed while applying an answer (the question stands), and
|
|
@@ -595,7 +604,7 @@ worktree re-created for an already-open PR is in references/isolation.md.
|
|
|
595
604
|
|
|
596
605
|
| `pull.mjs` | At the Pull | Then |
|
|
597
606
|
|---|---|---|
|
|
598
|
-
| 0 `DISPATCH {…}` | decided, live, free, claimed, routed; row, brief and dispatch recorded | the `task` call (
|
|
607
|
+
| 0 `DISPATCH {…}` | decided, live, free, claimed, routed; row, brief and dispatch recorded | the `task` call (**Dispatching the Pull's member**) |
|
|
599
608
|
| 1 `LEFT #N: taken …` | in-flight hit, an in-flight check that could not answer, or `claim-ticket.sh` exit 2 | next entry |
|
|
600
609
|
| 1 `LEFT #N: relabelled elsewhere` | no longer labelled `ready-for-agent` | next entry |
|
|
601
610
|
| 1 `LEFT #N: closed: fixed by …` | `close` answered `yes` | next entry |
|
|
@@ -606,508 +615,18 @@ worktree re-created for an already-open PR is in references/isolation.md.
|
|
|
606
615
|
| 3 | a usage mistake, or a token the question does not take | correct it; nothing was recorded |
|
|
607
616
|
| 10 `QUESTION <qid>:` | a question pending | answer it |
|
|
608
617
|
|
|
609
|
-
|
|
618
|
+
### Dispatching the Pull's member
|
|
610
619
|
|
|
611
620
|
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
621
|
|
|
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.
|
|
622
|
+
- **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.
|
|
623
|
+
- **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.
|
|
624
|
+
- **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.
|
|
625
|
+
- **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.
|
|
626
|
+
|
|
627
|
+
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`.
|
|
628
|
+
|
|
629
|
+
**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
630
|
|
|
1112
631
|
**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
632
|
|
|
@@ -1156,7 +675,7 @@ Dispatch a finisher only when every test here passes at once — the review retu
|
|
|
1156
675
|
same goes for a red one: a red that fired before the review never fires
|
|
1157
676
|
again, so the **fix-applier reports `no-op`** and **review result lands**
|
|
1158
677
|
bullets are also the only routes to the red-check fixer there.
|
|
1159
|
-
Then run the tick.
|
|
678
|
+
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
679
|
- **A fix-applier reports `no-op`, or a SHA you have already bound** → dispatch
|
|
1161
680
|
the finisher **now**, against the existing head — when that head's bound
|
|
1162
681
|
conclusion is red, the red-check fixer instead (**Then dispatch a red-check
|
|
@@ -1172,8 +691,7 @@ Dispatch a finisher only when every test here passes at once — the review retu
|
|
|
1172
691
|
whole route to a finisher**: a `no-op` report satisfies the gate above by
|
|
1173
692
|
itself — the review has returned, because a fix-applier exists only after it
|
|
1174
693
|
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.
|
|
694
|
+
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
695
|
- **`ci-state.mjs --pr <N>` reads `verdict: "no-ci"`** → no workflow run will ever
|
|
1178
696
|
complete for this repo, so the CI-run-completion edge above never fires and
|
|
1179
697
|
waiting for it stalls the whole PR — the same silent-stall shape reported
|
|
@@ -1586,6 +1104,8 @@ and flag the PR for a human. A new review of the PR counts again from none.
|
|
|
1586
1104
|
|
|
1587
1105
|
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
1106
|
|
|
1107
|
+
Zeroes on the cockpit are not yet proof the pipeline is idle.
|
|
1108
|
+
|
|
1589
1109
|
**A report is delivered, not implied by ending a turn.** Every member owes
|
|
1590
1110
|
the controller one report naming its outcome before it exits — including a
|
|
1591
1111
|
member whose prompt carries no explicit report line. The "report the SHA",
|
|
@@ -1840,7 +1360,7 @@ untouched, so re-running it is safe. That append is the whole duty.
|
|
|
1840
1360
|
|
|
1841
1361
|
The row's last four fields are the ticket's difficulty, and they are what lets a
|
|
1842
1362
|
tier comparison condition on the thing that swamps it. `sizing` is the
|
|
1843
|
-
**member's** own `light`/`heavy` verdict from its
|
|
1363
|
+
**member's** own `light`/`heavy` verdict from its dispatched `sizing-a-ticket`
|
|
1844
1364
|
run, which `append` reads itself off the member's report file
|
|
1845
1365
|
(`<scratch>/reports/impl-<ticket>[-x].json`, its `sizing.verdict`, the highest
|
|
1846
1366
|
retry letter's when there are several), so you pass no `--sizing`; the member
|
|
@@ -2009,7 +1529,7 @@ own, a row still carrying a live token, which rotate carries forward with
|
|
|
2009
1529
|
its live `## Dispatched` entries (phase 0). Never rotate mid-run: it is
|
|
2010
1530
|
phase 0's step alone.
|
|
2011
1531
|
|
|
2012
|
-
One line per ticket, rewritten in place (`ledger.mjs row <ticket> <text
|
|
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):
|
|
2013
1533
|
|
|
2014
1534
|
```
|
|
2015
1535
|
#<t1> impl-<t1>=PR#<p1> → PR#<p1> → MERGED 73b356de
|
|
@@ -2029,7 +1549,7 @@ live count from it, so nobody states one. Write neither by hand:
|
|
|
2029
1549
|
merge-bot` names the next bot itself. It prints `{member, agent, ticket,
|
|
2030
1550
|
line, created, total, brief}`, `brief` the `{path, sha256}` of the rendered
|
|
2031
1551
|
brief the `task` call is handed; `agent` is the definition the `task` call names — an
|
|
2032
|
-
implementer's comes off its row's `tier=` (
|
|
1552
|
+
implementer's comes off its row's `tier=` (the tier check under **Dispatching the Pull's member** states the
|
|
2033
1553
|
mapping), a finisher's and a merge bot's are their own definitions, and a
|
|
2034
1554
|
fix-applier's is `fleet-fix-applier` for every kind of work. It
|
|
2035
1555
|
refuses a member whose `briefs/<member>.json` beside the ledger is missing,
|
|
@@ -2063,7 +1583,8 @@ live count from it, so nobody states one. Write neither by hand:
|
|
|
2063
1583
|
that head; `no-op`: origin's branch at the head the PR's latest
|
|
2064
1584
|
`reviewed=` names; `labelled`: `ready-to-merge` on the PR and in the
|
|
2065
1585
|
read-back; `halted:<cause>`: a cause and non-empty evidence; `done`: every
|
|
2066
|
-
PR the bot merged reads MERGED.
|
|
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
|
|
2067
1588
|
**filed** and, for a fix-applier, each refutation the report defers in
|
|
2068
1589
|
**ruled**, once however often it runs, and prints `{member, outcome,
|
|
2069
1590
|
checks, filed, ruled}`. Exit 3: a check failed — the member's claim and the
|
|
@@ -2113,7 +1634,7 @@ row maps, or a rewrite adding a `PR#M` mention to another row — naming the
|
|
|
2113
1634
|
PR's row (`settle impl-N=PR#M` applies the same rule after its fold).
|
|
2114
1635
|
|
|
2115
1636
|
`→ 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 (
|
|
1637
|
+
tokens. `tier=<cell>` marks an Exploration Pull's cell and `route=<REASON>` a router fallback (the dispatch's CELL line), and `excluded ·
|
|
2117
1638
|
behind-pr:#M | behind-issue:#M` is an Exclusion (phase 1) — a ticket row like
|
|
2118
1639
|
any other, never a section of its own.
|
|
2119
1640
|
|