From 559d7491748bf36ec1dfc2626cecd4ee5f20c358 Mon Sep 17 00:00:00 2001 From: garrytan Date: Fri, 30 Oct 2026 16:36:30 +0000 Subject: [PATCH] feat(ship): variant G orchestration: one call per driver phase (9 for a clean pass, at most 12), gstack-ship-preflight --audit for single reads and deterministic audits, an observe-mode ledger with note and credits, 240 s wait pings, and small-model audit readers (GSTA-167) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- bin/gstack-ship-budget | 15 +- capy/skill/gstack/SKILL.md | 5 +- docs/ship-budget.md | 26 +++- lib/ship-audit.ts | 102 ++++++++++++++ lib/ship-budget-cli.ts | 65 +++++++-- lib/ship-budget.ts | 32 ++++- lib/ship-handoff.ts | 8 +- lib/ship-preflight.ts | 8 ++ ship/DRIVER.md | 160 ++++++++++++---------- ship/DRIVER.md.tmpl | 160 ++++++++++++---------- ship/sections/budget-handoff.md | 11 +- ship/sections/budget-handoff.md.tmpl | 11 +- test/ship-budget-chain.test.ts | 57 ++++++++ test/ship-budget-reader-drift.test.ts | 12 +- test/ship-budget-section.test.ts | 8 ++ test/ship-driver.test.ts | 87 +++++++++--- test/ship-preflight.test.ts | 35 +++++ test/size-ledger/ship/gsta-167-turns.json | 4 +- 18 files changed, 601 insertions(+), 205 deletions(-) create mode 100644 lib/ship-audit.ts diff --git a/bin/gstack-ship-budget b/bin/gstack-ship-budget index f2b8a464e..fb2605f78 100755 --- a/bin/gstack-ship-budget +++ b/bin/gstack-ship-budget @@ -3,10 +3,13 @@ * gstack-ship-budget — the /ship token budget and fresh-context handoff * (docs/ship-budget.md). An admission cap: no step or dispatch starts unless * used + open reservations + unknown + its projection + the tail and stop - * reserve fit under the cap. + * reserve fit under the cap. --mode observe (ship/DRIVER.md) only records: + * note at each program's end, and the cap is one hard stop. Every --reading + * takes --credits beside it. * - * start --record [--token-cap ] [--meter capy|claude|estimate] [--reading ] [--prior-reading ] - * [--session |--nonce ] [--reserve ] [--delegate auto|never|measured] [--calibration ] [--ledger ] + * start --record [--token-cap ] [--mode admit|observe] [--meter capy|claude|estimate] [--reading ] [--credits ] + * [--prior-reading ] [--session |--nonce ] [--reserve ] [--delegate auto|never|measured] [--calibration ] [--ledger ] + * note --run|--record --step [--label ] --reading [--credits ] [--dispatched-at [--dispatched-credits ] --readers ] * check --run |--record --step --next [,...]> [--reading ] [--prior-reading ] [--returned [,...]] [--runtime-lines ] * settle --run|--record --reservation [--unknown] [--reading ] release --run|--record --reservation * handoff --run|--record --from --to --base origin/ [--reviewed-tree ] [--trail --trail-run ] @@ -24,18 +27,18 @@ * lib/ship-handoff.ts. */ import { BudgetError } from '../lib/ship-budget'; -import { cmdCheck, cmdClose, cmdResume, cmdSettle, cmdStart, type Outcome } from '../lib/ship-budget-cli'; +import { cmdCheck, cmdClose, cmdNote, cmdResume, cmdSettle, cmdStart, type Outcome } from '../lib/ship-budget-cli'; import { cmdHandoff, cmdLaunch, cmdReconcile, cmdReturn, cmdVerifyHandoff } from '../lib/ship-handoff'; const [command, ...rest] = process.argv.slice(2); const ctx = { env: process.env, cwd: process.cwd() }; const commands: Record Outcome> = { - start: () => cmdStart(rest, ctx), check: () => cmdCheck(rest, ctx), settle: () => cmdSettle(rest, ctx), release: () => cmdSettle(rest, ctx, true), + start: () => cmdStart(rest, ctx), note: () => cmdNote(rest, ctx), check: () => cmdCheck(rest, ctx), settle: () => cmdSettle(rest, ctx), release: () => cmdSettle(rest, ctx, true), handoff: () => cmdHandoff(rest, ctx), launch: () => cmdLaunch(rest, ctx), 'verify-handoff': () => cmdVerifyHandoff(rest, ctx), return: () => cmdReturn(rest, ctx), reconcile: () => cmdReconcile(rest, ctx), resume: () => cmdResume(rest, ctx), close: () => cmdClose(rest, ctx), }; if (!command || command === '--help' || command === '-h' || !commands[command]) { - const text = (await Bun.file(import.meta.path).text()).split('\n').slice(2, 26).map(l => l.replace(/^ \* ?/, '')).join('\n'); + const text = (await Bun.file(import.meta.path).text()).split('\n').slice(2, 27).map(l => l.replace(/^ \* ?/, '')).join('\n'); (command && command !== '--help' && command !== '-h' ? console.error : console.log)(text); process.exit(command && command !== '--help' && command !== '-h' ? 2 : 0); } diff --git a/capy/skill/gstack/SKILL.md b/capy/skill/gstack/SKILL.md index 5fec993f2..adba16ee2 100644 --- a/capy/skill/gstack/SKILL.md +++ b/capy/skill/gstack/SKILL.md @@ -44,10 +44,9 @@ Run directories and scratch on Capy live under `~/.capy/work/` (`--out ~/. Capy checklist for `/autoplan`: drive `gstack-autoplan`, never a hand adaptation presented as a run. Relay `gstack-gate render /gate.json --all` with `rec` (every recommendation, plan changes included) as the one-word default; answer with `--by agent: --owner-reply ""`. Set `GSTACK_AUTOPLAN_CHECKPOINT_DIR` to a private project-user Drive folder; after machine replacement, `gstack-autoplan resume --out --from ` restores the newest valid checkpoint. -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: one execute program per review pass, per Step 11 and for the Step 14.5 audit (`ship/DRIVER.md`, "One program per pass"), in this order: (1) `gstack-review-plan --base ` with the pass's flags (its `DISPATCH:` lines; Step 14.5's one reader is `docs`); (2) `gstack-ship-budget check --run --step --next dispatch: --reading ` (one role per reader; `BEFORE` is this program's counter reading), then `gstack-review-log --start --reservation ` (`` from check's `RESERVATION_ID:` line) once per row the step logs; (3) `tools.subagent.create`, one reader each, `machine: "shared"`, its brief copied verbatim from the section that dispatches it; (4) a poll of `tools.subagent.get` with `transcript: "none"` every ~20 s until every reader settles; (5) the reading `AFTER`, then `gstack-ship-budget settle --run --reservation --dispatched-at --reading --readers `; a later batch of the pass (the Red Team, given the specialists' collected lines) repeats (2)-(5) with no start; (6) return only each reader's final output. The parent makes no model call while the program runs, so only that pair measures the readers, and it never walks the dispatch steps call by call. Give the program `timeout_seconds` for the slowest reader; a poll rerun from a later program settles without `--dispatched-at` and stays `UNMEASURED`. +Token caps on /ship: pass a brief's cap as `--token-cap `. /ship reads `cost.tokens` and `cost.credits` 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 them as `--reading --credits `, never in a call of its own. `ship/DRIVER.md` starts `--mode observe`: each program ends with `note`, and only a note past the cap stops (`docs/ship-budget.md`). +/ship on Capy is about a dozen calls, each one execute program (`ship/DRIVER.md`). A review pass, Step 11 and the Step 14.5 audit each run inside one program, in order: (1) `gstack-review-plan --base ` with the pass's flags (its `DISPATCH:` lines); (2) `gstack-review-log --start ` once per row the step logs (under ship/SKILL.md's admission mode, add `--reservation ` from check's `RESERVATION_ID:` line); (3) `BEFORE`, the counter's tokens and credits; (4) `tools.subagent.create`, one reader each, `machine: "shared"`, its brief copied verbatim from the section that dispatches it; audit readers (Steps 7, 8, 9.2.1, 14.5) cannot block, so they add `model: { model_id: "anthropic/claude-sonnet-5-5", reasoning_mode: "low" }` (`anthropic/claude-haiku-5-5` also fits); (5) a poll of `tools.subagent.get` with `transcript: "none"` every ~20 s for at most 240 s; (6) `gstack-ship-budget note --step --reading --credits --dispatched-at --dispatched-credits --readers `; (7) return only each reader's final output, or what still runs. The parent never walks the dispatch steps call by call. Waits: never let 5 minutes pass between calls. During tests, pregate, readers and CI, re-issue `wait` with `duration_seconds: 240` and the operation ids: a call after a longer gap bills the whole context fresh (about 40x a cached call), and a ping costs a few percent of that. A small waiting child saves nothing: the parent's cache expires during its wait too. ## Plan, approve, ship diff --git a/docs/ship-budget.md b/docs/ship-budget.md index 5eaecf2de..2133962eb 100644 --- a/docs/ship-budget.md +++ b/docs/ship-budget.md @@ -21,6 +21,28 @@ room for the receipt steps, and the stop overhead (calibrated) keeps room to sto the run has measured a step, the tail reserve scales with its measured step cost (a `--reserve` you set stays as given); the stop overhead never scales. +## Observe mode (ship/DRIVER.md) + +`start --mode observe` turns the ledger into a record. No step or dispatch is admitted, reserved or +delegated, so a run spends no call on the meter: each program the driver runs ends with + + gstack-ship-budget note --run --step [--label ] --reading --credits \ + [--dispatched-at --dispatched-credits --readers ] + +which prints `NOTE: [: