From b6cb70aea162e2245b28fb29859f349c0cf8781b Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sat, 31 Oct 2026 00:22:56 -0700 Subject: [PATCH] v1.91.355.0 ship: poll readers in-program, 32 KB returns, gated fix program, unattended push defers the guard offer, calls meter (GSTA-167 calls PR 1) (#3662) * ship: poll readers in-program, never wait on wakes; 32 KB returns; gated fix program; unattended push defers the guard offer; calls meter (GSTA-167 calls PR 1) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes (inventory lib/ship-release.ts as a GSTACK_SESSION_KIND reader) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(ship-budget): document calls and dotted --step notes Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ship: judge treats a missing severity as invalid; Capy passes rendered briefs via cat (replay C1-34 breaks) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: keep the Capy skill under its context budget after the main merge Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- CHANGELOG.md | 19 +++++++++++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- bin/gstack-review-scope | 2 ++ bin/gstack-ship-budget | 5 +-- capy/skill/gstack/SKILL.md | 2 +- docs/ship-budget.md | 11 ++++++ lib/review-ledger.ts | 3 +- lib/ship-budget-cli.ts | 21 ++++++++++-- lib/ship-calls.ts | 40 ++++++++++++++++++++++ lib/ship-release.ts | 5 +-- package.json | 2 +- ship/DRIVER.md | 35 ++++++++++--------- ship/DRIVER.md.tmpl | 35 ++++++++++--------- test/helpers/ship-bin-fixture.ts | 2 +- test/review-dispatch.test.ts | 2 ++ test/review-scope.test.ts | 4 +++ test/session-kind-marker.test.ts | 1 + test/ship-budget-chain.test.ts | 15 +++++++++ test/ship-calls.test.ts | 58 ++++++++++++++++++++++++++++++++ test/ship-driver.test.ts | 26 ++++++++++---- test/ship-release.test.ts | 12 +++++++ 22 files changed, 249 insertions(+), 55 deletions(-) create mode 100644 lib/ship-calls.ts create mode 100644 test/ship-calls.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 208c44baf..06fcf426f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [1.91.355.0] - 2026-10-31 + +**A driven `/ship` on Capy no longer pays one agent turn per finished reviewer, and it can count its own calls.** Replays of gstack-evals #34 and #36 (GSTA-167) made 30 to about 92 parent calls against a target of 12. In the 9-reviewer replay, 23 of 25 waits were the parent waking once per finished reviewer, because the driver told it to end its turn on `wait` while reviewers ran. What reviewers see, which reviewers run, their depth and the blocking floor are unchanged. + +### Changed +- **Readers are polled inside the program** (`ship/DRIVER.md`, Capy skill). A program returns 200 s after its first line and the next call resumes the poll; the parent never ends its turn while readers run. Capy polls with one `tools.subagent.list` per tick, starts readers a create call rejected once slots free, and reports a reader stuck for 20 minutes instead of dropping it. `wait` (now 200 s) is only for tests, pregate and CI when no reader runs. Lanes, pregate and `finish` run in the background. +- **A program returns at most 32 KB**; longer reads such as `review-army.md` split at headings, each byte once, before first use. Phase 2 used to return about 98 KB of sections at once, and truncated reads cost extra calls. +- **A fix runs as one gated program**: the failing test, the fix, `--tests-only` and the next pass, each only if the last step passed. Step 7's audit, which may write tests, settles before the review pass reads the tree. +- **An unattended `push` defers the one-time pre-push guard offer** (`REDACT_PREPUSH: … deferred=unattended`) instead of stopping. It writes no marker and installs no hook, so the next interactive session still gets the offer. + +### Added +- **`gstack-ship-budget calls --events `** counts a run's parent calls from the host's transcript items: `CALLS: total= decision= keepalive= idle_gaps_over_300s= max_gap_s=`. A wait, or a program that only reads status (plus at most one bash for its note), counts as a keep-alive. + +### Fixed +- **`gstack-ship-budget note --step 9.1`** records under step 9 with the sub-step as its label. Before, it was refused, and replays lost two notes to that error. +- **`gstack-review-scope render`** accepts `{"deferred": [...]}` as well as the bare array. +- **`gstack-review-dispatch judge`** treats a judged line with no severity, or `"severity": null`, as invalid and prints `JUDGMENT_REJECTED`. Before, it crashed with a TypeError, and a replay spent three calls recovering. +- **The Capy skill passes a rendered brief as bash `cat` prints it.** A file read cut the long lines of a 231,589-byte outside-voice brief, so `sent` reported `BRIEF_MISMATCH` and the reader had to be sent again. + ## [1.91.354.0] - 2026-10-31 **On Claude Code, `/land-and-deploy` loads about 15 KB less skill text per session.** Step 7's canary instructions (the canary script, third-party dashboard actions, Aside setup and the browser fallback) moved out of `land-and-deploy/SKILL.md` into an on-demand `sections/canary.md`, which the skill reads only when it reaches the canary step or a browser or third-party site action. `SKILL.md` drops from 51,992 to 32,216 bytes. Codex and Factory renders inline every section, so they stay about the same size. In a paid before/after run (Opus 5.5, 10 tasks, 3 trials per arm; GSTA-161), mean loaded skill bytes fell from 109,334 to 93,828 (-14%), the pass rate held (after 97%, before 80%, a second before panel 87%), and mean cost per session stayed flat. Pass rates are graded on the transcript. These numbers are exploratory, from one eval suite. diff --git a/VERSION b/VERSION index 07ecb0cf6..22e4cdb43 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.91.354.0 +1.91.355.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 0be762b2f..210e3d647 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.91.354.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.91.355.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/bin/gstack-review-scope b/bin/gstack-review-scope index 569aba495..3d4c843b6 100755 --- a/bin/gstack-review-scope +++ b/bin/gstack-review-scope @@ -224,6 +224,8 @@ function cmdRender(rest: string[]): number { let noted: ScopedFinding[] = []; try { if (deferredFile) deferred = JSON.parse(fs.readFileSync(deferredFile, 'utf8')); + const wrapped = deferred as unknown as { deferred?: unknown }; + if (!Array.isArray(deferred) && Array.isArray(wrapped?.deferred)) deferred = wrapped.deferred as DeferredRecord[]; const findingsFile = flag(rest, '--findings'); if (findingsFile) noted = (readFindings(findingsFile, 'classified findings') as ScopedFinding[]).filter(f => f.scope === 'note'); } catch (e: any) { return refuse('render', 'REVIEW_SCOPE_INPUT', e.message); } diff --git a/bin/gstack-ship-budget b/bin/gstack-ship-budget index 83831d97f..fd9c5119f 100755 --- a/bin/gstack-ship-budget +++ b/bin/gstack-ship-budget @@ -23,13 +23,14 @@ * resume --record |--run [--reading ] [--meter ] [--session ] [--token-cap --approved-by --words ] * [--rebuild-from-record] * close --run|--record [--reading ] [--approved-by --words ] + * calls --events [--since ] [--until ] (the host's transcript items: one CALLS: line, lib/ship-calls.ts) * * Exit: 0 continue/delegate · 3 stop (the next step does not start) · 2 usage * or validation (stderr). Logic: lib/ship-budget.ts, lib/ship-budget-cli.ts, * lib/ship-handoff.ts. */ import { BudgetError } from '../lib/ship-budget'; -import { cmdCheck, cmdClose, cmdNote, cmdResume, cmdSettle, cmdStart, type Outcome } from '../lib/ship-budget-cli'; +import { cmdCalls, 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); @@ -37,7 +38,7 @@ const ctx = { env: process.env, cwd: process.cwd() }; const commands: Record Outcome> = { 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), + reconcile: () => cmdReconcile(rest, ctx), resume: () => cmdResume(rest, ctx), close: () => cmdClose(rest, ctx), calls: () => cmdCalls(rest), }; if (!command || command === '--help' || command === '-h' || !commands[command]) { const lines = (await Bun.file(import.meta.path).text()).split('\n'); diff --git a/capy/skill/gstack/SKILL.md b/capy/skill/gstack/SKILL.md index 9b7e22232..8ef728bd1 100644 --- a/capy/skill/gstack/SKILL.md +++ b/capy/skill/gstack/SKILL.md @@ -46,7 +46,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` 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"`, reasoning from its `effort=` (`full` the lane's, `medium` medium, `light` low), 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, where a reader is settled only at status `done` or `failed` or with a `summary` (a just-created `idle` reader has not run); (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. +/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 (admission mode: `--reservation` from `RESERVATION_ID:`); (3) `BEFORE`; (4) `tools.subagent.create`, one reader each, `machine: "shared"`, reasoning from its `effort=` (`full` the lane's, `medium` medium, `light` low), its brief copied verbatim from the section that dispatches it (a rendered file via bash `cat`, never a file read); 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: one `tools.subagent.list` per ~20 s tick (a `tools.subagent.get` with `transcript: "none"` only on a status change), returning 200 s after the program's first line, where a reader is settled only at status `done` or `failed` or with a `summary` (a just-created `idle` reader has not run); start readers listed under `rejected` with `tools.subagent.start` as slots free; report one stuck 20 minutes; (6) `gstack-ship-budget note --record --step --reading --credits --dispatched-at --dispatched-credits --readers `; (7) return only each reader's final output. The parent never walks the dispatch steps call by call. Waits: never let 5 minutes pass between calls. While readers run, never end the turn on `wait`; rerun the poll program. Otherwise re-issue `wait` with `duration_seconds: 200` and the operation ids. At Step 22 report the `CALLS:` line of `gstack-ship-budget calls --events ` on your jam's transcript since /ship started. A small waiting child saves nothing. ## Plan, approve, ship diff --git a/docs/ship-budget.md b/docs/ship-budget.md index 928132644..3de382f18 100644 --- a/docs/ship-budget.md +++ b/docs/ship-budget.md @@ -36,6 +36,17 @@ is written, so `gstack-review-log --start` takes no `--reservation`, and `close` unreserved dispatch. `close` prints `BUDGET_TIMELINE:`, one entry per note. The admission mode below is what `ship/SKILL.md` runs; it is unchanged. +A dotted `--step` such as `9.1` records under its parent step (`9`) with the sub-step as its label, +unless `--label` is given. + +**Counting calls.** `gstack-ship-budget calls --events [--since ] [--until ]` reads +the host's transcript items (a JSON array, or `{ "items": [...] }` as Capy's `tools.thread.get` +transcript returns) and prints +`CALLS: total= decision= keepalive= idle_gaps_over_300s= max_gap_s= first= last= source=`. Every tool +item is one parent call; a `wait`, or a program that only reads status (plus at most one bash for its +note), is a keep-alive, and a gap over 300 s between calls outlives the prompt cache +(`lib/ship-calls.ts`). + **Credits.** Every command that takes `--reading` also takes `--credits ` from the same `thread.get`. Tokens count a cached read the same as a fresh one; credits are what the run costs, including the full-context re-bill after a gap over five minutes (about 40x a cached call, diff --git a/lib/review-ledger.ts b/lib/review-ledger.ts index e8a5633a6..994a5a13d 100644 --- a/lib/review-ledger.ts +++ b/lib/review-ledger.ts @@ -43,7 +43,8 @@ export function recordSent(ledger: string, reader: string, sentText: string): { /** Severity ranks across the voices' vocabularies; a label with no severity (FIXABLE, INVESTIGATE) ranks 0. */ const RANKS: Record = { critical: 4, p0: 4, high: 3, p1: 3, medium: 2, p2: 2, informational: 2, low: 1, p3: 1, fixable: 0, investigate: 0 }; -export function rank(label: string): number | null { +export function rank(label: unknown): number | null { + if (typeof label !== 'string') return null; const r = RANKS[label.trim().replace(/^\[|\]$/g, '').toLowerCase()]; return r === undefined ? null : r; } diff --git a/lib/ship-budget-cli.ts b/lib/ship-budget-cli.ts index afab469e9..848450ef5 100644 --- a/lib/ship-budget-cli.ts +++ b/lib/ship-budget-cli.ts @@ -18,6 +18,7 @@ import { runDirFor, settleIn, stepProjection, tally, unitConstants, unreservedDispatches, usageError, writeLedger, type Ledger, type MeterKind, type Next, type Note, } from './ship-budget'; +import { callsLine, countCalls } from './ship-calls'; import { claudeConfigDir, loadMeterState, newMeterState, nonceSessions, readClaudeUsage, saveMeterState, sessionJournals } from './claude-usage'; export type Opts = Record; @@ -377,8 +378,10 @@ const noteText = (n: Note) => `${n.step}${n.label ? `:${n.label}` : ''} used=${n */ export function cmdNote(argv: string[], ctx: Ctx): Outcome { const o = parseArgs(argv, ['run', 'record', 'step', 'label', 'reading', 'credits', 'dispatched-at', 'dispatched-credits', 'readers']); - const step = parseStep(str(o, 'step')); - const label = str(o, 'label'); + const raw = str(o, 'step'); + const parent = raw && !(SHIP_STEPS as readonly string[]).includes(raw) ? /^(\d+)(?:\.\d+)+$/.exec(raw)?.[1] : undefined; + const step = parseStep(parent && (SHIP_STEPS as readonly string[]).includes(parent) ? parent : raw); + const label = str(o, 'label') ?? (step !== raw ? raw : undefined); return mutateLedger(ledgerFileFor({ run: str(o, 'run'), record: str(o, 'record') }, ctx.env), ctx.env, l => { if (l.closed_at) throw usageError(`run ${l.run} is closed`); if (l.stopped) return { stdout: [budgetLine(l, { call: null, next: l.stopped.next, projected: 0, verdict: 'stop' }), `STOP: reason=${l.stopped.reason} at Step ${l.stopped.step}; resume first: gstack-ship-budget resume --record ${l.record}`, `GSTACK_RESULT: skill=ship status=incomplete run=${l.dir}`], exit: 3 }; @@ -396,6 +399,20 @@ export function cmdNote(argv: string[], ctx: Ctx): Outcome { }); } +/** calls: count parent calls from the host's transcript items (lib/ship-calls.ts). */ +export function cmdCalls(argv: string[]): Outcome { + const o = parseArgs(argv, ['events', 'since', 'until']); + const file = str(o, 'events'); + if (!file) throw usageError('calls needs --events '); + let doc: unknown; + try { doc = JSON.parse(fs.readFileSync(file, 'utf8')); } catch (e: any) { throw usageError(`calls --events ${file}: ${e.message}`); } + const d = doc as { items?: unknown; cursor?: unknown }; + const items = Array.isArray(doc) ? doc : Array.isArray(d.items) ? d.items : undefined; + if (!items) throw usageError(`calls --events ${file}: expected a JSON array of transcript items or { items: [...] }`); + const c = countCalls(items, { since: str(o, 'since'), until: str(o, 'until') }); + return { stdout: [callsLine(c, typeof d.cursor === 'string' ? d.cursor : 'none')], exit: 0 }; +} + /** resume: re-attach after a stop or a machine change; rebuild or restore a lost ledger; journal a cap raise. */ export function cmdResume(argv: string[], ctx: Ctx): Outcome { const o = parseArgs(argv, ['record', 'run', 'reading', 'credits', 'prior-reading', 'meter', 'session', 'nonce', 'token-cap', 'approved-by', 'words'], ['rebuild-from-record']); diff --git a/lib/ship-calls.ts b/lib/ship-calls.ts new file mode 100644 index 000000000..159ce3c1c --- /dev/null +++ b/lib/ship-calls.ts @@ -0,0 +1,40 @@ +/** + * ship-calls — a driven /ship's parent calls, counted from the host's transcript events + * (Capy: tools.thread.get transcript items), so acceptance never rests on the agent's own + * count (GSTA-167 calls plan, C9). Every tool item is one parent call. A keep-alive is a + * wait, or a program that only reads status (subagent, operation or thread reads) plus at + * most one bash for its budget note; everything else is a decision call. A gap over 300 s + * between calls outlives the prompt cache, so the next call is billed fresh. + */ +export interface TranscriptItem { source?: string; tool?: string; calls?: Array<{ tool?: string }>; createdAt?: string } +export interface CallCount { total: number; decision: number; keepalive: number; idleGaps: number; maxGapS: number; first: string | null; last: string | null } + +const STATUS_READS = new Set(['subagent_get', 'subagent_list', 'operation_get', 'operation_list', 'thread_get']); +export const CACHE_TTL_S = 300; + +function isKeepalive(item: TranscriptItem): boolean { + if (item.tool === 'wait') return true; + if (item.tool !== 'execute') return false; + const calls = (item.calls ?? []).map(c => c.tool ?? ''); + const reads = calls.filter(t => STATUS_READS.has(t)).length; + const bash = calls.filter(t => t === 'bash').length; + return reads > 0 && reads + bash === calls.length && bash <= 1; +} + +export function countCalls(items: readonly TranscriptItem[], window: { since?: string; until?: string } = {}): CallCount { + const calls = items.filter(i => i.source === 'tool' && i.createdAt + && (!window.since || i.createdAt >= window.since) && (!window.until || i.createdAt <= window.until)) + .sort((a, b) => a.createdAt!.localeCompare(b.createdAt!)); + let idleGaps = 0, maxGapS = 0; + for (let k = 1; k < calls.length; k++) { + const gap = Math.round((Date.parse(calls[k]!.createdAt!) - Date.parse(calls[k - 1]!.createdAt!)) / 1000); + if (gap > CACHE_TTL_S) idleGaps++; + maxGapS = Math.max(maxGapS, gap); + } + const keepalive = calls.filter(isKeepalive).length; + return { total: calls.length, decision: calls.length - keepalive, keepalive, idleGaps, maxGapS, + first: calls[0]?.createdAt ?? null, last: calls[calls.length - 1]?.createdAt ?? null }; +} + +export const callsLine = (c: CallCount, source: string) => + `CALLS: total=${c.total} decision=${c.decision} keepalive=${c.keepalive} idle_gaps_over_300s=${c.idleGaps} max_gap_s=${c.maxGapS} first=${c.first ?? 'none'} last=${c.last ?? 'none'} source=${source}`; diff --git a/lib/ship-release.ts b/lib/ship-release.ts index 9e379303d..a1bf533fc 100644 --- a/lib/ship-release.ts +++ b/lib/ship-release.ts @@ -221,8 +221,9 @@ export function push(argv: readonly string[], cwd: string): Report { if (branch !== flag('--branch')) return r.stop(`HEAD is on ${branch}, not the branch Step 1 saved (${flag('--branch')}): check out the saved branch, then rerun push`); const guard = bin(cwd, 'gstack-config', ['get', 'redact_prepush_hook'], 30_000).stdout.trim() || 'false'; const prompted = fs.existsSync(path.join(resolveStateRoot(process.env), '.redact-prepush-prompted')); - r.add(`REDACT_PREPUSH: ${guard} prompted=${prompted ? 'yes' : 'no'}`); - if (guard !== 'true' && !prompted) return r.stop('Step 17\'s one-time pre-push guard offer is unanswered: run the guard block and ask it, then rerun push'); + const deferred = guard !== 'true' && !prompted && process.env.GSTACK_SESSION_KIND === 'unattended'; + r.add(`REDACT_PREPUSH: ${guard} prompted=${prompted ? 'yes' : 'no'}${deferred ? ' deferred=unattended' : ''}`); + if (guard !== 'true' && !prompted && !deferred) return r.stop('Step 17\'s one-time pre-push guard offer is unanswered: run the guard block and ask it, then rerun push'); const gateOpen = Number(flag('--gate-open') ?? '0'); if (gateOpen > 0) { if (!hostedOrigin(cwd)) return r.stop('open-PR lookup unavailable: origin is a local path, not a hosted remote gh can map; it must succeed before pushing open gate findings'); diff --git a/package.json b/package.json index 553138ea6..5ee04070f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.91.354", + "version": "1.91.355", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/ship/DRIVER.md b/ship/DRIVER.md index 4221cc0b1..1e9c0cedc 100644 --- a/ship/DRIVER.md +++ b/ship/DRIVER.md @@ -7,7 +7,7 @@ Read this instead of `ship/SKILL.md` when a parent agent on any host runs /ship the `GSTACK_LAUNCH` assignments your installer printed (`. `), never `export $(...)`, `env`, `printenv` or a bare `set` in a tool fence. -**Rules.** Each phase below is one call: one program (Capy: an execute program; else a shell +**Rules.** Each phase below is one call: one program (Capy: an execute program; else a script) that runs the bins, reads, dispatches and waits, returning only result lines and reader outputs; print them verbatim. The model keeps only lead judgment, fixes, CHANGELOG and PR prose, the gates and the report. `STOP:` (exit 3) means nothing after it ran; `NEXT:` names @@ -29,17 +29,19 @@ file, unchanged; its findings reach `merged.jsonl` only via `collect`; `judge` r small model at low effort; their findings go to the PR body. **Calls.** One pass with no fixes is 9 calls: Phases 1-8 and the report; at most 12 with a STOP's -section read or a fix-check. A pass that fixes something repeats Phases 3-4. +section read or a fix-check, plus a keep-alive per 200 s waited. A fix repeats Phases 3-4. **Budget: observe.** `gstack-ship-budget start --mode observe` records, never admits: each program ends -with `gstack-ship-budget note --step --reading R --credits C`, R and C read in that program -(Capy: your own jam's `cost.tokens` and `cost.credits`). No check per step, no reservation, no meter-only +with `gstack-ship-budget note --step --reading R --credits C --record `, R and C read in that program +(Capy: your jam's `cost.tokens`, `cost.credits`). No check per step, no reservation, no meter-only call, no handoff; under `--token-cap` a note past the cap prints `verdict=stop`, the one stop check. A dispatching program takes `BEFORE` (R and C) as its readers start and passes `--dispatched-at --dispatched-credits --readers ` with the reading after the last settles. -**Waits.** No call waits past 240 s: a program returns whatever still runs at 240 s; the next call resumes (Capy: `wait` 240 s on it). A small -waiting child saves nothing. Past 100 minutes, stop. +**Waits.** No call waits past 200 s: a program returns what still runs 200 s after its first line; the next +call resumes the poll. Lanes, pregate and `finish` run in background. Never end the turn while +readers run (each wakes it). A small waiting child saves nothing. Past 100 minutes, stop. Returns: +≤32 KB per program; split longer reads at headings, each byte once, before first use. **One program per pass.** Each review pass, Step 11 and the Step 14.5 audit is one program, and the parent makes no other call while it runs: (1) `gstack-review-plan --base ` with the pass's @@ -49,8 +51,7 @@ flags (its `DISPATCH:` lines; Step 14.5 has one reader, `docs`); (2) `gstack-rev (5) a poll until every reader settles (status `done` or `failed`, or a final summary; a fresh `idle` has not run); (6) `note` with the reading pair; a later batch (the Red Team, given the specialists' collected lines) repeats (3)-(6); (7) the collect: return only each reader's -final output and status, via `collect`. The next call applies the section's merge, judgment, fixes and persistence; -it never walks the dispatch steps call by call. +final output and status, via `collect`; it never walks the dispatch steps call by call. ## 1. Load: Step 0 @@ -60,16 +61,15 @@ The install check and this file. `gstack-ship-budget start --record --mode observe [--token-cap ] --reading R --credits C`, then `gstack-ship-preflight --base --lane