diff --git a/capy/skill/gstack/SKILL.md b/capy/skill/gstack/SKILL.md index 05be314ce..291797ce9 100644 --- a/capy/skill/gstack/SKILL.md +++ b/capy/skill/gstack/SKILL.md @@ -44,7 +44,7 @@ Capy checklist for `/autoplan`: drive `gstack-autoplan`, never a hand adaptation Token caps on /ship: pass a brief's cap as `--token-cap `. /ship reads `cost.tokens` from `tools.thread.get({ thread_id: "" })` (its own jam, subagents included; `"this"` names the root thread, so only a root run may pass it) inside the same execute program as the `gstack-ship-budget` command that takes it as `--reading `, never in a call of its own, and obeys `verdict=` (`docs/ship-budget.md`). -/ship reviewers on Capy (`docs/review-policy.md#reviewer-packets`): one execute program per review pass, in this order: (1) `gstack-review-packet build` with the pass's plan flags (a later build in the same pass, the Red Team or Step 11's packets, is `build --like `, never hand-copied flags); (2) under a token cap, `gstack-ship-budget check --next "$BUDGET_NEXT"` (build's `BUDGET_NEXT:` value, `role@bytes` per packet, so each reader is priced from its packet), then `gstack-review-log --start --reservation ` once per row the step logs; (3) the counter reading `BEFORE` (a check in this program takes this same reading); (4) `tools.subagent.create`, one subagent per `PACKET:` line (at most 10 per call), `machine: "shared"`, on the lane's model at reasoning `medium` (red-team and adversarial keep the lane's reasoning; a cross-family outside voice stays as it is), starting at most the free slots of the running-subagent cap and retrying refused starts as slots free; (5) a poll of `tools.subagent.get` with `transcript: "none"` every ~20 s until every reviewer settles; (6) the reading `AFTER`, then `gstack-ship-budget settle --run --reservation --dispatched-at --reading --readers `; (7) `gstack-review-packet collect --dir `, returning only its output. Each brief is the packet brief with that packet's `read=` ranges, plus one more parallel call in turn 1: `install.sh --check --for review-voice` in bash; a reviewer then finishes in about three calls (the parallel reads, at most one turn of extra ranges, one write). The parent makes no model call while the program runs, so only that pair measures the readers; a batch settled from a later program (a rerun poll) omits `--dispatched-at` and stays `UNMEASURED`. Keep one program under ~8 minutes and rerun the poll until collect has run (a lost program loses no reviewer); never read a reviewer's transcript. `gstack-review-log --finish` stays at the step that persists the pass: it records the parent's verdict. +/ship reviewers on Capy (`docs/review-policy.md#reviewer-packets`): one execute program per review pass, in this order: (1) `gstack-review-packet build` with the pass's plan flags (a later build in the same pass, the Red Team or Step 11's packets, is `build --like `, never hand-copied flags); (2) under a token cap, `gstack-ship-budget check --next "$BUDGET_NEXT"` (build's `BUDGET_NEXT:` value, `role@bytes` per packet, so each reader is priced from its packet), then `gstack-review-log --start --reservation ` (`` from check's `RESERVATION_ID:` line) once per row the step logs; (3) the counter reading `BEFORE` (a check in this program takes this same reading); (4) `tools.subagent.create`, one subagent per `PACKET:` line (at most 10 per call), `machine: "shared"`, on the lane's model at reasoning `medium` (red-team and adversarial keep the lane's reasoning; a cross-family outside voice stays as it is), starting at most the free slots of the running-subagent cap and retrying refused starts as slots free; (5) a poll of `tools.subagent.get` with `transcript: "none"` every ~20 s until every reviewer settles; (6) the reading `AFTER`, then `gstack-ship-budget settle --run --reservation --dispatched-at --reading --readers `; (7) `gstack-review-packet collect --dir `, returning only its output. Each brief is the packet brief with that packet's `read=` ranges, plus one more parallel call in turn 1: `install.sh --check --for review-voice` in bash; a reviewer then finishes in about three calls (the parallel reads, at most one turn of extra ranges, one write). The parent makes no model call while the program runs, so only that pair measures the readers; a batch settled from a later program (a rerun poll) omits `--dispatched-at` and stays `UNMEASURED`. Keep one program under ~8 minutes and rerun the poll until collect has run (a lost program loses no reviewer); never read a reviewer's transcript. `gstack-review-log --finish` stays at the step that persists the pass: it records the parent's verdict. ## Plan, approve, ship diff --git a/docs/ship-budget.md b/docs/ship-budget.md index f1c4d4291..e61d0b59e 100644 --- a/docs/ship-budget.md +++ b/docs/ship-budget.md @@ -71,7 +71,7 @@ With a cap set and no readable meter, start refuses | `call_floor`, `above_floor` | The host's per-call floor (see Delegation) and the charge above it, max(0, call − floor); the delegate rule compares `above_floor` | | `next`, `projected` | What the check was for and its projection: the prior times the run's factor for that kind | | `calibration` | The run's step and dispatch factors (see Calibration), or `prior` before the first measurement | -| `verdict` | `continue` (exit 0, prints `RESERVATION:` under a cap), `delegate` (exit 0), `stop` (exit 3) | +| `verdict` | `continue` (exit 0, prints `RESERVATION:` and the bare id as `RESERVATION_ID: ` under a cap), `delegate` (exit 0), `stop` (exit 3) | Any exit other than 0 means the next step does not start. Exit 2 is a usage or validation error on stderr with the code, the value and the repair. GSTACK_RESULT appears only on a stop, diff --git a/lib/ship-budget-cli.ts b/lib/ship-budget-cli.ts index a34932cfc..b87030144 100644 --- a/lib/ship-budget-cli.ts +++ b/lib/ship-budget-cli.ts @@ -165,7 +165,7 @@ export function decide(l: Ledger, step: string, next: Next, call: number | null, if (hold) lines.push(`CONTEXT_HIGH: call=${call ?? 'unknown'} call_floor=${p.floor} above_floor=${above ?? 'unknown'} threshold=${threshold} next=${nextId} depth=${depth}; continuing: ${hold}`); if (l.cap !== null) { const r = reserveIn(l, { kind: 'step', step, next: nextId, tokens: projected, prior }); - lines.push(`RESERVATION: id=${r.id} next=${nextId} tokens=${projected}`); + lines.push(`RESERVATION: id=${r.id} next=${nextId} tokens=${projected}`, `RESERVATION_ID: ${r.id}`); } return { stdout: lines, exit: 0 }; } @@ -197,7 +197,7 @@ function admitBatch(l: Ledger, step: string, roles: string[], call: number | nul } const kept = bytes?.slice(0, k); const r = reserveIn(l, { kind: 'dispatch', step, next: nextId, tokens: cost(k), prior: p.project({ kind: 'dispatch', roles: admitted, ...(kept ? { bytes: kept } : {}) }).prior, roles: admitted, ...(kept ? { bytes: kept } : {}), ...(remaining.length ? { remaining } : {}) }); - lines.push(`RESERVATION: id=${r.id} next=${nextId} tokens=${cost(k)}`); + lines.push(`RESERVATION: id=${r.id} next=${nextId} tokens=${cost(k)}`, `RESERVATION_ID: ${r.id}`); return { stdout: lines, exit: 0 }; } diff --git a/ship/sections/adversarial.md b/ship/sections/adversarial.md index 76c99ffeb..20a30522b 100644 --- a/ship/sections/adversarial.md +++ b/ship/sections/adversarial.md @@ -6,7 +6,7 @@ above 0 the plan lists it beside the batched specialist), print `ADVERSARIAL: skipped (review plan)`; with neither line, take decision 3 of Finish the adversarial phase. Under a token cap, reserve the planned voices as one batch (`gstack-ship-budget check --next `, never packet names); every `--start adversarial-review` below (the native pass's and each outside voice's PASS_START) takes `--reservation `, one start per voice on that reservation. +Step 11's packet build prints>`, never packet names); every `--start adversarial-review` below (the native pass's and each outside voice's PASS_START) takes `--reservation ` (check's `RESERVATION_ID:` line), one start per voice on that reservation. ## Step 11: Adversarial review diff --git a/ship/sections/adversarial.md.tmpl b/ship/sections/adversarial.md.tmpl index cfbca3884..5587229d2 100644 --- a/ship/sections/adversarial.md.tmpl +++ b/ship/sections/adversarial.md.tmpl @@ -4,7 +4,7 @@ above 0 the plan lists it beside the batched specialist), print `ADVERSARIAL: skipped (review plan)`; with neither line, take decision 3 of Finish the adversarial phase. Under a token cap, reserve the planned voices as one batch (`gstack-ship-budget check --next `, never packet names); every `--start adversarial-review` below (the native pass's and each outside voice's PASS_START) takes `--reservation `, one start per voice on that reservation. +Step 11's packet build prints>`, never packet names); every `--start adversarial-review` below (the native pass's and each outside voice's PASS_START) takes `--reservation ` (check's `RESERVATION_ID:` line), one start per voice on that reservation. {{ADVERSARIAL_STEP}} diff --git a/ship/sections/budget-handoff.md b/ship/sections/budget-handoff.md index be5a7f1cb..de425fa27 100644 --- a/ship/sections/budget-handoff.md +++ b/ship/sections/budget-handoff.md @@ -19,7 +19,7 @@ Field-by-field reference: docs/ship-budget.md. Exit 0 continue or delegate, 3 st **Every boundary.** `gstack-ship-budget check --run --step --next --reading `. Before each dispatch batch, reserve the batch as one sum, one role per `DISPATCH:` line, then pass the printed id once -(`READING` is the current counter reading; `RESERVATION_ID` is the id the check prints; +(`READING` is the current counter reading; `RESERVATION_ID` is the check's `RESERVATION_ID:` line, copied whole; `--next` is the `BUDGET_NEXT:` value the batch's `gstack-review-packet build` printed, one role per packet, never packet names): diff --git a/ship/sections/budget-handoff.md.tmpl b/ship/sections/budget-handoff.md.tmpl index 71dec5c51..d9b51c198 100644 --- a/ship/sections/budget-handoff.md.tmpl +++ b/ship/sections/budget-handoff.md.tmpl @@ -17,7 +17,7 @@ Field-by-field reference: docs/ship-budget.md. Exit 0 continue or delegate, 3 st **Every boundary.** `gstack-ship-budget check --run --step --next --reading `. Before each dispatch batch, reserve the batch as one sum, one role per `DISPATCH:` line, then pass the printed id once -(`READING` is the current counter reading; `RESERVATION_ID` is the id the check prints; +(`READING` is the current counter reading; `RESERVATION_ID` is the check's `RESERVATION_ID:` line, copied whole; `--next` is the `BUDGET_NEXT:` value the batch's `gstack-review-packet build` printed, one role per packet, never packet names): diff --git a/ship/sections/documentation.md b/ship/sections/documentation.md index 83b15eccd..447d8f4e0 100644 --- a/ship/sections/documentation.md +++ b/ship/sections/documentation.md @@ -45,7 +45,7 @@ never writes a doc. Under a token cap the dispatch is one program, like a review `gstack-ship-budget check --run --step 14.5 --next "$BUDGET_NEXT"` (build's line), the reading `BEFORE` right before the reader starts, `tools.subagent.create`, a `tools.subagent.get` poll until it settles, the reading `AFTER`, then `gstack-ship-budget settle --run --reservation --dispatched-at --reading --readers 1`, -all in that same program; a settle from a later program is unmeasured. +all in that same program (`--reservation` is check's `RESERVATION_ID:` line); a settle from a later program is unmeasured. ````text You are the /ship documentation auditor (Step 14.5). Read-only: edit no file, run no command, never commit, push or start another workflow. The parent applies your proposed edits mechanically and owns every decision you return. Audit id: . Mode: . diff --git a/ship/sections/documentation.md.tmpl b/ship/sections/documentation.md.tmpl index 0a1d0f3a1..44520cf57 100644 --- a/ship/sections/documentation.md.tmpl +++ b/ship/sections/documentation.md.tmpl @@ -43,7 +43,7 @@ never writes a doc. Under a token cap the dispatch is one program, like a review `gstack-ship-budget check --run --step 14.5 --next "$BUDGET_NEXT"` (build's line), the reading `BEFORE` right before the reader starts, `tools.subagent.create`, a `tools.subagent.get` poll until it settles, the reading `AFTER`, then `gstack-ship-budget settle --run --reservation --dispatched-at --reading --readers 1`, -all in that same program; a settle from a later program is unmeasured. +all in that same program (`--reservation` is check's `RESERVATION_ID:` line); a settle from a later program is unmeasured. ````text You are the /ship documentation auditor (Step 14.5). Read-only: edit no file, run no command, never commit, push or start another workflow. The parent applies your proposed edits mechanically and owns every decision you return. Audit id: . Mode: . diff --git a/ship/sections/review-army.md b/ship/sections/review-army.md index 0c2f0a8f9..3b56b09aa 100644 --- a/ship/sections/review-army.md +++ b/ship/sections/review-army.md @@ -105,7 +105,7 @@ This pass is static; defer product probes to Step 9.2.1. 1. Read `~/.claude/skills/gstack/review/checklist.md`. If the file cannot be read, **STOP** and report the error. -2. Before reading the diff, run `~/.claude/skills/gstack/bin/gstack-review-log --start review` and save its token as REVIEW_START (under a token cap, run Step 9.1's `gstack-review-packet build` and `gstack-ship-budget check` first, then `gstack-review-log --start review --reservation `; 9.1's program then dispatches that batch with no second check or start; a later batch of the same Step 9 pass (the Red Team or an ADMIT remainder) takes no start, and `--finish REVIEW_START` settles it). Then run `git diff origin/` (`git diff $RANGE_BASE` from pass 2 on). Read non-ignored untracked source files too (`git ls-files --others --exclude-standard`); the snapshot includes them. +2. Before reading the diff, run `~/.claude/skills/gstack/bin/gstack-review-log --start review` and save its token as REVIEW_START (under a token cap, run Step 9.1's `gstack-review-packet build` and `gstack-ship-budget check` first, then `gstack-review-log --start review --reservation ` (`` is check's `RESERVATION_ID:` line); 9.1's program then dispatches that batch with no second check or start; a later batch of the same Step 9 pass (the Red Team or an ADMIT remainder) takes no start, and `--finish REVIEW_START` settles it). Then run `git diff origin/` (`git diff $RANGE_BASE` from pass 2 on). Read non-ignored untracked source files too (`git ls-files --others --exclude-standard`); the snapshot includes them. Every pass captures a full token (no `--fix-range`), so the record binds the whole tree even when reviewers read only the range. diff --git a/ship/sections/review-army.md.tmpl b/ship/sections/review-army.md.tmpl index 5a36881a3..1ecd6983e 100644 --- a/ship/sections/review-army.md.tmpl +++ b/ship/sections/review-army.md.tmpl @@ -50,7 +50,7 @@ This pass is static; defer product probes to Step 9.2.1. 1. Read `~/.claude/skills/gstack/review/checklist.md`. If the file cannot be read, **STOP** and report the error. -2. Before reading the diff, run `~/.claude/skills/gstack/bin/gstack-review-log --start review` and save its token as REVIEW_START (under a token cap, run Step 9.1's `gstack-review-packet build` and `gstack-ship-budget check` first, then `gstack-review-log --start review --reservation `; 9.1's program then dispatches that batch with no second check or start; a later batch of the same Step 9 pass (the Red Team or an ADMIT remainder) takes no start, and `--finish REVIEW_START` settles it). Then run `git diff origin/` (`git diff $RANGE_BASE` from pass 2 on). Read non-ignored untracked source files too (`git ls-files --others --exclude-standard`); the snapshot includes them. +2. Before reading the diff, run `~/.claude/skills/gstack/bin/gstack-review-log --start review` and save its token as REVIEW_START (under a token cap, run Step 9.1's `gstack-review-packet build` and `gstack-ship-budget check` first, then `gstack-review-log --start review --reservation ` (`` is check's `RESERVATION_ID:` line); 9.1's program then dispatches that batch with no second check or start; a later batch of the same Step 9 pass (the Red Team or an ADMIT remainder) takes no start, and `--finish REVIEW_START` settles it). Then run `git diff origin/` (`git diff $RANGE_BASE` from pass 2 on). Read non-ignored untracked source files too (`git ls-files --others --exclude-standard`); the snapshot includes them. Every pass captures a full token (no `--fix-range`), so the record binds the whole tree even when reviewers read only the range. diff --git a/test/ship-budget-review-log.test.ts b/test/ship-budget-review-log.test.ts index c5b2f3a6a..65877f004 100644 --- a/test/ship-budget-review-log.test.ts +++ b/test/ship-budget-review-log.test.ts @@ -139,6 +139,22 @@ describe('gstack-review-log --start under a token cap', () => { expect(close.code, close.out).toBe(0); }); + // Value: protects=the reservation id is printed alone on its own line, so a child copies it without parsing the RESERVATION: line (gstack-evals #36 round 3: a misparsed id gave UNMEASURED, then BUDGET_CHECK_SKIPPED at close); fails_when=check prints no RESERVATION_ID: line, or it differs from RESERVATION: id=; + // why_new=every test reads the id out of the RESERVATION: line; seam=none; no_claim=that the agent copies the right line + test('check prints RESERVATION_ID: alone beside the RESERVATION: line, for a dispatch and a step, and the id starts a review', () => { + const h = fresh(); + const run = capped(h); + const dispatch = h.sb(['check', '--run', run.run, '--step', '9', '--next', 'dispatch:specialist', '--reading', '0']); + const step = h.sb(['check', '--run', run.run, '--step', '1', '--next', '2', '--reading', '0']); + for (const out of [dispatch.out, step.out]) { + const ids = out.split('\n').filter(l => l.startsWith('RESERVATION_ID:')); + expect(ids).toHaveLength(1); + expect(ids[0]).toBe(`RESERVATION_ID: ${h.field(out, 'RESERVATION', 'id')}`); + } + const id = /^RESERVATION_ID: (\S+)$/m.exec(dispatch.out)![1]!; + expect(h.reviewLog(['--start', 'review', '--reservation', id]).code).toBe(0); + }); + test('close prints one BUDGET_CHECK_SKIPPED line per unreserved dispatch and ends incomplete', () => { const h = fresh(); const run = capped(h); diff --git a/test/ship-budget-section.test.ts b/test/ship-budget-section.test.ts index 094bf4464..c83458181 100644 --- a/test/ship-budget-section.test.ts +++ b/test/ship-budget-section.test.ts @@ -69,6 +69,14 @@ describe('ship budget skill structure', () => { expect(capy).toMatch(/all in that same (?:execute )?program/); }); + // Value: protects=every prose site that takes a reservation id reads the RESERVATION_ID: line, never a field cut from RESERVATION:; fails_when=a dispatch section or the Capy skill names the id without that line; + // why_new=the line is new; seam=none; no_claim=the agent copies it + test('the review pass, Steps 9, 11 and 14.5 and the Capy skill read the id from the RESERVATION_ID: line', () => { + for (const rel of ['ship/sections/budget-handoff.md.tmpl', 'ship/sections/review-army.md.tmpl', 'ship/sections/adversarial.md.tmpl', 'ship/sections/documentation.md.tmpl', 'capy/skill/gstack/SKILL.md']) { + expect(read(rel), rel).toContain('`RESERVATION_ID:`'); + } + }); + test('every review-log --start in the dispatch sections takes a reservation when a cap is set', () => { for (const rel of ['ship/sections/review-army.md.tmpl', 'ship/sections/adversarial.md.tmpl']) expect(read(rel)).toContain('--reservation'); });