@feigi/fleet-ctl 3.19.7 → 3.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/fleet-tick.test.mjs +1 -1
- package/scripts/ledger-dispatch.test.mjs +34 -9
- package/scripts/ledger-grammar.mjs +10 -4
- package/scripts/ledger.mjs +6 -1
- package/scripts/router-table.json +24 -0
- package/scripts/ticket-router.mjs +543 -0
- package/scripts/ticket-router.test.mjs +981 -0
- package/scripts/within-run-pair-prose.test.mjs +46 -10
- package/skills/run-team/SKILL.md +59 -32
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { test } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import {
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { CELL } from "./ledger-grammar.mjs";
|
|
6
5
|
import { between as section } from "./prose-pin.mjs";
|
|
7
6
|
|
|
8
7
|
// #864's finding is that tier is entangled with calendar date and therefore
|
|
@@ -126,14 +125,16 @@ test("phase 2 counts the rate off the ledger's impl- rows, and a replacement doe
|
|
|
126
125
|
/records\s+`tier=<cell>`\s+in\s+the\s+row,\s+and\s+a\s+replacement\s+inherits\s+the\s+row's\s+tier/,
|
|
127
126
|
"phase 2 no longer records the cell on the row, or no longer says a replacement inherits it rather than counting as a Pull",
|
|
128
127
|
);
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
assert.
|
|
135
|
-
|
|
136
|
-
|
|
128
|
+
// The cell is the router's draw, never one this prose names: a cell
|
|
129
|
+
// written here is a second copy of the routing, free to disagree with the
|
|
130
|
+
// table the router reads.
|
|
131
|
+
assert.match(slice, /the\s+router\s+draws\s+its\s+cell/, "phase 2 no longer says the router draws the exploration cell");
|
|
132
|
+
assert.doesNotMatch(slice, /Until\s+the\s+router\s+draws/, "phase 2 names a stand-in exploration cell again");
|
|
133
|
+
assert.match(
|
|
134
|
+
slice,
|
|
135
|
+
/While\s+the\s+table's\s+`burn_in`\s+is\s+true\s+every\s+Pull\s+draws,\s+over\s+every\s+cell,\s+the\s+policy\s+cell\s+included/,
|
|
136
|
+
"phase 2 no longer says burn-in draws on every Pull, over every cell",
|
|
137
|
+
);
|
|
137
138
|
// The roll: a Pull that lands on a chain head passes the exploration cell to
|
|
138
139
|
// the next Pull rather than skipping it, which is the rule ADR 0013 §6 states
|
|
139
140
|
// and the difficulty caveat below depends on.
|
|
@@ -144,6 +145,41 @@ test("phase 2 counts the rate off the ledger's impl- rows, and a replacement doe
|
|
|
144
145
|
);
|
|
145
146
|
});
|
|
146
147
|
|
|
148
|
+
// The Pull's own write: the router's line decides the row, and `tier=` is
|
|
149
|
+
// the draw's record — on a row exactly when the router drew. Written for a
|
|
150
|
+
// row the router did not draw for, `ledger.mjs dispatch` would print a cell
|
|
151
|
+
// nothing chose.
|
|
152
|
+
const pullStep7 = () => section(RUN_TEAM, "7. **Route, row, then dispatch**", "**The Pull table**", "run-team Pull step 7");
|
|
153
|
+
|
|
154
|
+
test("Pull step 7 routes through ticket-router.mjs and writes tier= exactly when it drew", () => {
|
|
155
|
+
const slice = pullStep7();
|
|
156
|
+
assert.match(slice, /ticket-router\.mjs\s+route\s+--session\s+<session>\s+--ticket\s+<N>\s+--arm\s+<A\|B>\s+--impl-row\s+<k>/, "step 7 no longer calls the router");
|
|
157
|
+
assert.match(slice, /--issue\s+<scratch>\/impl-<N>\/issue\.json/, "step 7 no longer hands the router the Pull's own read");
|
|
158
|
+
assert.match(slice, /`tier=<CELL>`\s+exactly\s+when\s+`DRAW`\s+is\s+not\s+`-`/, "step 7 no longer ties tier= to the draw");
|
|
159
|
+
assert.match(slice, /Router\s+exit\s+2\s+→\s+no\s+row\s+and\s+no\s+dispatch/, "step 7 dispatches on a router usage error");
|
|
160
|
+
assert.match(slice, /--impl-row\s+<k>\[\s+--chain-head\]/, "step 7 no longer passes --chain-head for a ticket another sequences after");
|
|
161
|
+
assert.match(slice, /--guard\s+\.fleet\/cost-guard\.json/, "step 7 no longer hands the router the cost guard");
|
|
162
|
+
assert.match(slice, /\[\s+·\s+route=<REASON>\]/, "step 7 no longer records the router's REASON on the row");
|
|
163
|
+
assert.match(slice, /release\s+the\s+claim\s+\(`release-ticket\.sh`/, "step 7 no longer releases the claim on a router exit 2");
|
|
164
|
+
// The Pull's one read is what the router reads: a second fetch would be a
|
|
165
|
+
// second network call per Pull.
|
|
166
|
+
const step3 = section(RUN_TEAM, "3. **Read the ticket in full, once**", "Record the ticket's real", "run-team Pull step 3");
|
|
167
|
+
assert.match(step3, /--json\s+title,body,comments,labels,createdAt`\s+written\s+to\s+`<scratch>\/impl-<N>\/issue\.json`/, "step 3 no longer writes the file the router reads");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const routerArgs = () =>
|
|
171
|
+
section(RUN_TEAM, "**The router's arguments, and nothing past its line.**", "**Count the rate against the ledger", "run-team router's arguments");
|
|
172
|
+
|
|
173
|
+
test("the router's arguments paragraph pins the roll, the arm parity and the single source of CELL", () => {
|
|
174
|
+
const slice = routerArgs();
|
|
175
|
+
assert.match(slice, /row\s+5k\s+and\s+every\s+row\s+after\s+it\s+carry\s+no\s+`tier=`/, "the --impl-row rolling definition is gone");
|
|
176
|
+
assert.match(slice, /`--chain-head`\s+marks\s+a\s+ticket\s+another\s+open\s+ticket\s+sequences\s+after/, "--chain-head is no longer defined");
|
|
177
|
+
assert.match(slice, /`A`\s+for\s+an\s+even\s+ticket\s+number\s+and\s+`B`\s+for\s+an\s+odd/, "the --arm parity rule changed");
|
|
178
|
+
assert.match(slice, /never\s+re-derive\s+`CELL`/, "the router's line is no longer the whole decision");
|
|
179
|
+
assert.match(slice, /`REASON=guard-missing`/, "the guard-missing route is no longer named");
|
|
180
|
+
assert.match(slice, /`REASON`\s+other\s+than\s+`ok`\s+goes\s+on\s+the\s+row\s+as\s+`route=<REASON>`/, "REASON no longer lands on the row as route=");
|
|
181
|
+
});
|
|
182
|
+
|
|
147
183
|
// The difficulty caveat lives in the counter-evidence section, past this file's
|
|
148
184
|
// dispatch slice, so it needs its own anchor pair.
|
|
149
185
|
const counterEvidence = () =>
|
package/skills/run-team/SKILL.md
CHANGED
|
@@ -515,9 +515,10 @@ terminating in exactly one dispatch or an empty shortlist (ADR 0013 Decision 2):
|
|
|
515
515
|
nothing else; work done directly against the shared checkout, by a
|
|
516
516
|
human or another agent session, stakes no branch-shaped claim and trips
|
|
517
517
|
none of them.
|
|
518
|
-
3. **Read the ticket in full, once** — `gh issue view <N> --json title,body,comments,labels
|
|
519
|
-
|
|
520
|
-
|
|
518
|
+
3. **Read the ticket in full, once** — `gh issue view <N> --json title,body,comments,labels,createdAt`
|
|
519
|
+
written to `<scratch>/impl-<N>/issue.json`, then read off that file with `jq -r
|
|
520
|
+
'.title, .body, ([.labels[].name]|join(",")), (.comments[]|.author.login + ": " + .body)'`.
|
|
521
|
+
One read answers every question the Pull asks; step 7's router reads the same file. Record the ticket's real
|
|
521
522
|
brief — the `## Agent Brief` comment where one exists, otherwise the issue
|
|
522
523
|
body, which is where most tickets actually carry it (a controller probe found only 43/100 open
|
|
523
524
|
`ready-for-agent` issues have a separate Agent Brief comment; the rest,
|
|
@@ -706,9 +707,16 @@ terminating in exactly one dispatch or an empty shortlist (ADR 0013 Decision 2):
|
|
|
706
707
|
two independent tickets.
|
|
707
708
|
6. **Claim** — `~/.fleet/bin/fleet-run claim-ticket.sh <N> impl-<N> implementer
|
|
708
709
|
--apply` (below). Exit 2 → treat it as taken, next entry.
|
|
709
|
-
7. **
|
|
710
|
-
|
|
711
|
-
|
|
710
|
+
7. **Route, row, then dispatch** — `~/.fleet/bin/fleet-run ticket-router.mjs route
|
|
711
|
+
--session <session> --ticket <N> --arm <A|B> --impl-row <k>[ --chain-head] --issue
|
|
712
|
+
<scratch>/impl-<N>/issue.json --guard .fleet/cost-guard.json` prints
|
|
713
|
+
`POLICY=… CELL=… DRAW=… STRATUM=… REASON=…` (phase 2's exploration rule
|
|
714
|
+
says what each argument is); then `ledger.mjs row <N> "impl-<N>[ · tier=<CELL>][ · route=<REASON>]"`,
|
|
715
|
+
`tier=<CELL>` exactly when `DRAW` is not `-`; then phase 2 at once: claim
|
|
716
|
+
what you are about to dispatch; dispatch what you have just claimed. Router
|
|
717
|
+
exit 2 → no row and no dispatch: release the claim (`release-ticket.sh`,
|
|
718
|
+
below) and emit an event, a probe that could not look — a table the router
|
|
719
|
+
refuses refuses every Pull.
|
|
712
720
|
|
|
713
721
|
**The Pull table** — every verdict a Pull can reach, and the one thing each
|
|
714
722
|
does (ADR 0013 Decision 2 and 4):
|
|
@@ -923,8 +931,8 @@ kernel and needs a `wait`, while a `task` job is process-level and its report
|
|
|
923
931
|
auto-delivers on its own, like every other member's.
|
|
924
932
|
|
|
925
933
|
**No per-call tier or effort anywhere on this path**: tier stays the agent
|
|
926
|
-
definition's own frontmatter (ADR 0005), whichever
|
|
927
|
-
Pull names, and a member's Resolved tier stays readable from its transcript.
|
|
934
|
+
definition's own frontmatter (ADR 0005), whichever `fleet-implementer-<cell>`
|
|
935
|
+
definition the Pull names, and a member's Resolved tier stays readable from its transcript.
|
|
928
936
|
|
|
929
937
|
**Claim what you are about to dispatch; dispatch what you have just claimed.**
|
|
930
938
|
Phase 1 makes the claim, serially in the main checkout — its rule is measured —
|
|
@@ -1087,16 +1095,31 @@ dispatched — the fleet reads that config and never writes it (ADR 0003).
|
|
|
1087
1095
|
|
|
1088
1096
|
**Every 5th Pull by ledger count goes at an exploration cell.** Count the `impl-`
|
|
1089
1097
|
rows in `.fleet/ledger.md` at Pull time; the Pull that creates row 5, 10, 15 …
|
|
1090
|
-
|
|
1098
|
+
is an Exploration Pull, and the router draws its cell: `k = 1 +
|
|
1099
|
+
(sha256("<session>\t<ticket>")[0:8] mod K)` over `router-table.json`'s
|
|
1100
|
+
`cells` minus the policy cell, in token order. While the table's `burn_in` is
|
|
1101
|
+
true every Pull draws, over every cell, the policy cell included. An
|
|
1102
|
+
Exploration Pull records `tier=<cell>` in the row, and a replacement inherits the row's tier —
|
|
1091
1103
|
written at the Pull's step 7, ahead of `ledger.mjs dispatch`, which reads it
|
|
1092
|
-
and prints `fleet-implementer-<cell>` as the `agent` to dispatch
|
|
1093
|
-
|
|
1094
|
-
retired alternate tier's own `@task:high` route. The
|
|
1104
|
+
and prints `fleet-implementer-<cell>` as the `agent` to dispatch; a row with
|
|
1105
|
+
no `tier=` runs the policy cell. The
|
|
1095
1106
|
assignment rolls to the next Pull when another open ticket sequences after
|
|
1096
1107
|
it — a ticket the rest of the run
|
|
1097
|
-
depends on. Do not tell the member it is a control: a member that
|
|
1108
|
+
depends on; `class` is not read. Do not tell the member it is a control: a member that
|
|
1098
1109
|
knows it is being measured is not measuring the same thing.
|
|
1099
1110
|
|
|
1111
|
+
**The router's arguments, and nothing past its line.** `--impl-row` is the
|
|
1112
|
+
row count the Pull creates — or, while an assignment is rolling (the latest
|
|
1113
|
+
row 5k and every row after it carry no `tier=`), that row's number;
|
|
1114
|
+
`--chain-head` marks a ticket another open ticket sequences after, which is
|
|
1115
|
+
what rolls it. `--arm` is `A` for an even ticket number and `B` for an odd
|
|
1116
|
+
one; a B Pull routes exactly as an A one until the table opens arm B.
|
|
1117
|
+
`--session` is this run's own session directory. The printed line is the
|
|
1118
|
+
whole routing decision — never re-derive `CELL` from the table, the guard or
|
|
1119
|
+
this paragraph. A missing or unreadable `.fleet/cost-guard.json` routes the
|
|
1120
|
+
default cell only (`REASON=guard-missing`), burn-in included, and `REASON`
|
|
1121
|
+
other than `ok` goes on the row as `route=<REASON>`, nowhere else.
|
|
1122
|
+
|
|
1100
1123
|
**Count the rate against the ledger, because a Pull is the dispatch unit.** One
|
|
1101
1124
|
Pull is one member, so a rate counted per Pull is a rate counted per member —
|
|
1102
1125
|
one implementer in five, however the Pulls fall across the run — and the
|
|
@@ -1353,31 +1376,35 @@ awk -F'\t' '!/^#/ && $4=="routine" {t[$5]++; if($6=="no") f[$5]++} \
|
|
|
1353
1376
|
```
|
|
1354
1377
|
|
|
1355
1378
|
**These rows are the historical corpus, and nothing appended to them fixes the
|
|
1356
|
-
confound.** What fixes it is the
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
which tier history happened to leave in
|
|
1360
|
-
whole-file split, once there
|
|
1379
|
+
confound.** What fixes it is the exploration draw in the dispatch rule above:
|
|
1380
|
+
one implementer in every five — every one, during burn-in — runs at a drawn
|
|
1381
|
+
cell beside the policy cell's, against the same prompts on the same day, so
|
|
1382
|
+
the comparison stops depending on which tier history happened to leave in
|
|
1383
|
+
which week. Read each cell's comparisons, not the whole-file split, once there
|
|
1384
|
+
are enough of them.
|
|
1361
1385
|
|
|
1362
1386
|
**So read what the guard actually established: routine tickets sometimes fail to
|
|
1363
1387
|
close their own ticket. Not that `sonnet` caused it.** The revert is the
|
|
1364
1388
|
pre-committed rule being honoured, not a measurement. Restoring a cheap tier is
|
|
1365
1389
|
still the maintainer's call and still a change to the dispatch rule — but the
|
|
1366
1390
|
deliberate control it used to require is no longer something anyone has to
|
|
1367
|
-
authorize one ticket at a time: the
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1391
|
+
authorize one ticket at a time: the exploration draw above produces one in
|
|
1392
|
+
every five Pulls by construction. **Do not read a cell early.** A cell's
|
|
1393
|
+
comparison is one session holding an admissible row at that cell — its tier
|
|
1394
|
+
check passed and its resolved effort is the cell's level — and one at
|
|
1395
|
+
`slow-high` whose resolved model and effort differ. Count them per cell, never
|
|
1396
|
+
pooled across cells, and stop until
|
|
1372
1397
|
there are at least ten of them across five or more distinct `run_date`s; below
|
|
1373
|
-
that, a
|
|
1374
|
-
n=1 on the control side. **Report
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1398
|
+
that, a comparison count is a number, not evidence, and the last guard fired
|
|
1399
|
+
with n=1 on the control side. **Report the count the per-cell readout prints,
|
|
1400
|
+
never one from any other query:** until that readout exists there is no count
|
|
1401
|
+
to report, the pair query in `member-outcomes.tsv`'s header counts pre-cutover
|
|
1402
|
+
pairs only, which are no cell's comparisons, and the query before that one
|
|
1403
|
+
counted every session whose implementers merely differed, so it cleared this
|
|
1404
|
+
floor by an order of magnitude while the controlled comparison did not exist yet.
|
|
1405
|
+
|
|
1406
|
+
Why the agent body carries what it does — read this before editing any
|
|
1407
|
+
`fleet-implementer-<cell>` file, and keep every cell's body byte-identical. Each rule in the body's
|
|
1381
1408
|
enumerate-and-declare block is load-bearing, for a different reason.
|
|
1382
1409
|
**Enumerate-and-declare** answers a signature measured four times in one run:
|
|
1383
1410
|
each implementer fixed exactly the cases its ticket named and left an adjacent
|
|
@@ -3667,7 +3694,7 @@ naming the token and writing nothing, because every reader would take it as a
|
|
|
3667
3694
|
permanent settle.
|
|
3668
3695
|
|
|
3669
3696
|
`→ PR#M` stays as the human-readable arrow; the tick reads only the `=`
|
|
3670
|
-
tokens. `tier=<cell>` marks
|
|
3697
|
+
tokens. `tier=<cell>` marks an Exploration Pull's cell and `route=<REASON>` a router fallback (phase 2), and `excluded ·
|
|
3671
3698
|
behind-pr:#M | behind-issue:#M` is an Exclusion (phase 1) — a ticket row like
|
|
3672
3699
|
any other, never a section of its own.
|
|
3673
3700
|
|