* v1.91.70.0 feat(unattended): artifact contract, unattended session kind, findings-derived review log, gate list Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: register test/fixtures/multi-agent-wave as a free fixture (consumer: headless-artifacts.test.ts) 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>
112 KiB
gstack multi-agent wave (October 10, 2026): honest install, unattended autoplan, queue-aware ship, cheap-first gates
URGENT, outside this plan
None. Six reviewer passes (CEO, DX, Eng; native Claude and outside gpt-6-astra each) raised no security or data-loss finding the plan does not own. The one live defect found during the run, the user-scope installer's --check certifying an absent install (~/.capy/drive/user-garry-tan/skills/gstack/scripts/install.sh:7-16, if --check; then check_install; exit 0; fi), is owned by A2/D3 and is worked around today by running the full install on every Capy machine (86 s).
Implementation plan
Context
Base: origin/main 91bbd9e (v1.91.69.0). Input: feedback from 28 Capy threads that built gbrain and gbrain-evals over a month on cloud machines with no Claude Code harness, a coordinator thread merging PRs one at a time, full test gates on ephemeral Ubicloud VMs, and preregistered paid evals. 13 of the 28 threads ran no gstack workflow beyond the installer; several times the skills were not installed and the check did not say so. The others used each repo's own release tooling because /ship does not fit a shared serial merge queue.
Evidence gathered for this plan (2026-10-10, this machine, a fresh Capy cloud box):
- The install check lies by omission (P0-1 reproduced). The user-scope Capy installer's
--check(~/.capy/drive/user-garry-tan/skills/gstack/scripts/install.sh, a baretest -xchain) printed nothing and exited 1 in 0.004 s with nothing installed. The cause isif --check; then check_install; exit 0; fiunderset -e: a failed probe exits 1 with no message, and through a pipe (... | tail) the caller sees the pipe's status, 0, which is the "exited 0 with nothing installed" report. A full install then took 86 s, most of it Chromium (114.7 MiB headless shell plus fonts). After it,bin/gstack-doctorreported7 ok, 2 warn, 3 not configured, 0 failand exited 0 with Bun 1.3.14 against the repo's tested 1.4.2 (warn, notfail), no Node row, no telemetry row. - What exists already is more than the feedback assumes.
bin/gstack-doctor(456 lines) has onecheck_*row per component with a fix line and exits 1 onfail(bin/gstack-doctor:3-16, :455-456).bin/gstack-session-kindalready classifiesspawned | headless | interactive(GSTACK_HEADLESS,CI,GSTACK_SESSION_KIND=spawned), and the preamble's AskUserQuestion rules branch on it (scripts/resolvers/preamble/generate-ask-user-format.ts:15-29). The snapshot tool wraps a raw plan in## Implementation planautomatically (bin/gstack-autoplan-snapshot.ts:415), so no literal heading is required of the user.gstack-wtreeandgstack-evidencealready compute reviewed-tree fingerprints (bin/gstack-evidence:13, :392).bin/gstack-next-versionalready reads the live PR queue and sibling worktrees (bin/gstack-next-version:80-105).ship/sections/measure.md.tmpl:3-16already holds a never-loosen rerun discipline.codex_reviewsdefaults toenabled(bin/gstack-config:251), so the outside voice is on by default. - Where the gaps actually are.
headlessmeans BLOCK on any question (generate-ask-user-format.ts:28); onlyspawnedauto-continues, andspawnedwould auto-approve autoplan's final gate. Upgrade, telemetry, lake and proactive prompts are suppressed forspawnedonly (bin/gstack-skill-start:394). Review-logstatus/unresolvedare model-written free text; nothing derives them from findings (bin/gstack-review-log:92-97, lib/review-evidence.ts:226-243).bin/gstack-version-bump writestamps VERSION, package.json and npm lockfiles only, with one hard-coded post-write hook (scripts/gen-agents-digest.ts, :374-402); there is norelease:restamphook, no--after <pr>, no tree-equality receipt in the PR body, and no CHANGELOG re-heading tool. Paid evals in/ship(Step 6) run before Step 16's cheap checks./investigatehas no flake, polluter, race or backlog content (investigate/SKILL.md.tmpl, 278 lines). No user-facing eval workflow exists; preregistration, panels, admission budgets and dev/held-out splits are all gstack-internal (scripts/ship-measure.ts:22-28, scripts/judge-calibration.ts:42, test/helpers/periodic-exclude-data.ts:79-90). Codex outside voice from an API key works today (printenv OPENAI_API_KEY | codex login --with-api-keythengstack-codex-probe role-ready execprintedCODEX_SEL: gpt-6-astra,AUTH_OK,MODEL_OKin 3.7 s) but no helper, doctor fix line or doc names it (grep: only TODOS.md:1640 and CI tests). - Owner input received during review (binding). Garry's second message adds: accept a subagent on another model family as a full outside voice and never report a missing Codex CLI as degraded; store learnings and telemetry durably on ephemeral machines or say plainly they are skipped; allow guarded tool calls when no publication guard is installed and say so; list the linked issue's deferred asks in the gate template; name the ship mode
--queue-mode stamp-at-merge. These are folded into A3, B1, B3, C2 and D1 below. - Repo constraints that shape every item. SKILL.md is generated from
.tmpl(edit templates, runbun run gen:skill-docs). New logic goes inlib/*.tsbehind thinbin/entrypoints;test/module-size-ratchet.test.tscaps registered owner modules at 800 lines and 150 per function (test/helpers/module-size.ts:202), so every new module this wave adds is registered intest/fixtures/module-size-ratchet.json, andbin/gstack-next-version(822 lines),bin/gstack-autoplan-snapshot.ts(865) andsetup(4662), unregistered today, are not grown. Four skills have no parity headroom at all (ship 1.539/1.539, review 1.234/1.2345, autoplan 1.129/1.1305, qa 1.127/1.1272 union-byte ratios againsttest/helpers/parity-harness.tsguards, measured during this review), so prose added to them must be replaced byte for byte or printed by a bin instead. State paths resolve only throughlib/state-root.ts/bin/gstack-paths. Prose in templates cannot be pinned by sentence (docs/test-value-bar.md); new rows get structural tests plus eval cases. Every template edit must clearbun test test/parity-suite.test.tsand the context-budget ratchets.
The wave has one thesis: gstack must be a tool a parent agent can drive and verify without reading prose. Every artifact below is machine-readable first (one grep-able line, a JSONL file, a fenced receipt) and human-readable second.
Release promises (the finish line)
- P1. The install check cannot lie. On a machine with nothing installed, one command prints one PASS/FAIL line per component with the exact fix command, ends with
gstack: fail <components>and exits non-zero. On a healthy machine it ends withgstack: ok <revision>. A parent agent confirms a subagent machine by grepping that one line. Planning-only machines skip Chromium. - P2. /autoplan, the plan reviews and /cso run unattended on a host with no Claude Code harness. Under the new
unattendedsession kind they need no hooks, no AskUserQuestion, no Skill or Agent tool, prompt for nothing, and writeplan.md,review-record.md,tasks.jsonl,decisions.jsonl,findings.jsonl,timing.jsonand arun.jsonmanifest, all under one versioned schema with a validator, that a parent agent consumes without reading prose. The approval gate is never auto-approved: it is written as a gate list and the run ends. - P3. "Ship, next in queue at " works. The stamp happens when the PR is next, from main's VERSION at that moment, through the repo's own release tool when it has one, across every stamp location at once. The PR carries a tree-equality receipt and a handoff record with identifiers first. Zero manual stamp edits; a pure restamp of a gated tree is reported as
gate-reuse: eligiblewith the identity it rests on (tree modulo stamp and generated paths, runtime pins, lockfile, CI config), and the coordinator reuses the gate instead of paying for it again. - P4. Cheap checks run before any paid or remote gate. A two-minute pre-gate replays every mechanical-drift incident in the feedback (stale goldens, scratch files, pipe-hidden failures, unrun platform lanes, old constants in nightly-only tests, dropped vendored hunks) and refuses to start a gate while any fails.
- P5. Plans, reviews and investigations carry the gbrain lessons as required rows, each one tied to the incident that earned it, each failing the review when missing.
- P6. Measurement, coordination and the owner's view get first-class support: a preregistered-eval workflow with a budget ledger, lane-ownership and evidence-bundle tools, and a one-page owner brief with numbered decisions.
- P7. Adoption is measured, not assumed. The durable carriers are the ones that already leave the machine:
run.json.consumed_byin the parent-chosen--outdirectory and the ship receipt'ssession_kindandartifacts_consumed: <n>/<m>fields in merged PR bodies;gstack-ship-receipt census --repo <r> --since <tag>computes the number from merged PRs throughgh, deduplicated by run and thread id, against an owner-maintained thread cohort (threads that never invoke gstack count in the denominator); state-root analytics stay a bonus labeleddurable=no. The tier-3 items in PRs D and E start only after the first ten Capy threads on B-runner show at least six unattended runs whose artifacts a parent consumed (see UC1).
Delivery shape
Seven pull requests, merged in order; each is shippable alone and each later one assumes the earlier one merged. Every item below carries a tier: tier 1 ships in its PR; tier 2 ships in the same PR only if green and otherwise moves to the next PR without blocking; tier 3 waits for the P7 adoption number. The Eng review sized B and C at two review sittings each (3,500–6,000 LOC [estimated]), so each is split at its natural seam; the halves can be built in parallel on fresh machines, stacked. Queue cost: six extra full gates over a single PR, two of them made cheaper by the pre-gate they ship. The stack dogfoods itself: C-stamp's restamp tool stamps every PR behind it. (See decisions D1, D12 and UC1.)
| PR | Items | Theme | Depends on |
|---|---|---|---|
| A | A1–A3 | Honest install (small, lands first) | — |
| B-core | B0, B1, B7, B9, B10, B12 | Artifact contract, unattended kind, gate list, derived review log |
A (GSTACK_LAUNCH) |
| B-runner | B2, B3, B4, B8, B11 | Unattended runner, outside-voice runner, checklists, spend ledger | B-core |
| C-stamp | C1, C2, C3, C5, C7 | Ship policy, restamp, receipt, handoff, history policy | B-core (schemas) |
| C-pregate | C4, C6, C8 | Regen registry, issue links, pre-gate | C-stamp (trust boundary) |
| D | D1–D3 | Plan rows, sideways review, investigate recipes | B-runner (tier 2 rows need scope) |
| E | E1–E3 | /eval-plan, coordination tools, owner brief |
B-runner (ledger), C-stamp (bundle) |
Every PR's acceptance lists the union byte delta per skill it touches and the CARVE_GUARDS/capture-context-budget refresh commit, because ship, review, autoplan and qa have zero parity headroom today. Shared files have one owner each: bin/gstack-skill-start (B-core), scripts/resolvers/outside-voice*.ts (B-runner), ship/SKILL.md.tmpl and ship/sections/* (C-stamp, then C-pregate), the ratchet fixture (one commit per PR by that PR's author).
PR A: honest install (P0-1)
A1. gstack-doctor --check: the one-line verdict. Add a --check mode to bin/gstack-doctor that prints one line per component as PASS <component> or FAIL <component> — fix: <exact command> and ends with gstack: ok <revision> or gstack: fail <component,component>, exit 1 on any FAIL, exit 0 only when every required component passes. Components: Claude skills, Codex skills, the Astra behavioral patch (grep of the generated patch heading, as the Capy installer does today), browse bundle, Chromium launch and render (reuse setup's launch probe plus a setContent smoke render through bin/gstack-render.ts), runtime pins, privacy state. Readiness is relative to a requested workflow: --for <skill> (or --require <csv>) names the components that skill needs (planning skills: both host profiles and the patch; browser skills: plus the browse bundle and Chromium; /cso: plus the native helper) and the trailer names the workflow it certified (gstack: ok 1.91.70.0 for=autoplan). Without either flag the required set is the installed hosts, and the trailer says so. Wrong-host, missing-authentication and incompatible-runtime fixtures are tested, not just missing files. One component table feeds the row ids, --for resolution, --require, the trailer and the --json keys, so a parent never maps between vocabularies. The doctor stays Bash 3.2 and reports without Bun (bin/gstack-doctor:22; the P1 scenario is a machine with Bun absent or below floor), so the table's source of truth is lib/doctor-components.ts and the file the doctor sources, bin/gstack-doctor-components.sh, is generated from it at build time with a freshness test, the way the agents digest is. --check is fixtured with bun removed from PATH, with an empty installed-host set (never ok), and with an unmet --revision (never ok). The gstack runtime row distinguishes gstack's security floor (1.3.3) from its supported minimum (1.4.2, bin/gstack-bun-version.sh:15); 1.3.14 is FAIL in --check. --for <skill> prints required: claude codex patch before the rows, and each row is PASS | FAIL | SKIP <id> where SKIP marks a component the selected workflow, host or runner does not need (not_applicable) or an optional one that is absent (optional). A healthy Capy machine with Claude subagents as the outside voice and no Codex CLI passes --for autoplan. --json emits the same rows as objects; --check never runs the paid Codex probe unless --live is also passed. Silent success becomes impossible: every path prints the trailer. ./setup --status exits 1 when any install row reports broken section links instead of always 0 (setup:136-146).
- New rows:
gstack runtime(Bun against gstack's own floor and tested version, today'sbunrow) andproject pins, which reads the target repository's (named by--project <path>, default the git toplevel of the cwd, never the gstack checkout)packageManager,engines,.tool-versions,.nvmrc,.bun-versionand the CI matrix (bun-version:/node-version:in.github/workflows/*.yml) through newlib/runtime-pins.ts, compares with the running Bun and Node, distinguishes a range (>=1.4.2) from an exact pin, keeps platform-specific CI pins with their lane (the Windows workflow pins Bun and Node separately, .github/workflows/windows-free-tests.yml:83, and must not block a Linux run), prints each effective constraint with its source, and reportsFAIL project pins — bun 1.3.14 outside engines.bun >=1.4.2 (package.json:80; CI windows-free-tests.yml:89 pins 1.4.2); fix: bun upgrade --version 1.4.2. A mismatch against the project's pin is FAIL in--check(a gate on an unpinned runtime is the incident),warnin the dashboard.privacyprintstelemetry=<mode> artifacts_sync=<mode> codex_reviews=<v> update_check=<v>fromgstack-config get, values only (doctor's clamp rule).coresprints the CPU count and, under 8 cores, the remote-gate recommendation (consumed by E2). - Tests:
test/gstack-doctor.test.tsgains the--checkfixtures (healthy, each missing component, runtime mismatch), pinned on the trailer tokens and exit codes, not prose.
A2. The Capy installer lives in the repo. New bin/gstack-capy-install (bash, Capy cloud Ubuntu only, under 200 lines): clone or reuse ~/.local/share/gstack under the existing lock (never reset, never auto-pulled), ./setup --host claude --no-prefix, ./setup --host codex --model gpt-6-astra, then gstack-doctor --check --for <skill> (default --require claude,codex,patch). Chromium is lazy by default: the installer skips it (GSTACK_SKIP_PLAYWRIGHT=1 GSTACK_SKIP_FONTS=1), new bin/gstack-browser-ensure installs it on first use from the browse preflight in scripts/resolvers/browse.ts, and --with-browser installs it up front for machines that want the 86 s now; --only <skill> installs the host profiles and checks only that skill's declared dependencies. ./setup gains the documented --no-browser flag (today only the undocumented env var, setup:2072). Runtime preflight runs before ./setup, because setup exits at once when Bun is absent or below its floor (setup:296): the installer checks Bun, Node and jq, installs or upgrades Bun through setup's own checksum-verified recipe (BUN_VERSION=<v> bash install, setup:296-305; bun upgrade --version does not exist on 1.3.14) when gstack's supported minimum or the project's pin requires it and the two agree, and stops with both constraints printed when they conflict. The default Capy image ships 1.3.14, so P1 is unmeetable there otherwise. --revision <ref> pins the checkout for a fleet; an existing checkout at a different revision is reused and reported as gstack: ok <installed> (requested <ref>; run gstack-upgrade to move), never pulled. Chromium skip precedence is one sentence in setup --help: --no-browser sets GSTACK_SKIP_PLAYWRIGHT=1 internally; the installer passes it unless --with-browser. Planning-only install time is measured separately from browser bootstrap and printed. The user-scope Capy skill becomes a thin wrapper: clone or reuse the repo under the same lock, then run bin/gstack-capy-install from the clone (no raw-content URL), and grep gstack: ok. Its SKILL.md tells a parent agent to confirm each subagent machine with that grep and never to pipe --check through tail.
- Acceptance: a fresh machine with nothing installed fails loudly, naming each missing component; the default install finishes without downloading Chromium and reports
SKIP browser (lazy; gstack-browser-ensure installs on first use)rather than PASS; the first browser skill run installs it.
A3. Durable state on ephemeral machines. The state-root chain already exists (GSTACK_STATE_ROOT > GSTACK_HOME > GSTACK_STATE_DIR > CLAUDE_PLUGIN_DATA > ~/.gstack, lib/state-root.ts), so learnings, decisions, review logs and timing are a defaults-and-honesty fix: gstack-skill-start prints STATE_ROOT: <path> durable=<yes|no> once per session, detecting an ephemeral root (a Capy cloud home, a tmpfs, or GSTACK_EPHEMERAL=1), and in unattended runs prints learnings: skipped (state root is ephemeral; set GSTACK_STATE_ROOT) instead of writing silently to a disk that will vanish. Ephemerality is detected from an environment marker the host sets (GSTACK_EPHEMERAL=1, which the Capy installer exports into the skill's launch configuration), not a path heuristic. A durable root is opt-in: the owner sets GSTACK_STATE_ROOT (the Capy drive is governed by Capy's own drive policy, which excludes memory and verification evidence, so the installer only prints the option and never writes there by default). Because a child shell's export cannot configure the parent's later commands (lib/state-root.ts:22 reads each process's own environment), the installer prints a machine-readable launch configuration (GSTACK_LAUNCH: GSTACK_SESSION_KIND=unattended GSTACK_STATE_ROOT=<path> GSTACK_EPHEMERAL=1) that the parent supplies to every gstack command it launches; the doctor state root row shows the durability verdict.
PR B (B-core, B-runner): unattended autoplan and the artifact contract (P0-2)
Item ids are stable across review phases, so B5 and B6 (folded into B2 and B0 during the CEO phase) are not reused.
B0. One versioned artifact contract. New lib/headless-artifacts.ts owns every schema this wave writes (tasks, decisions, findings, timing, run manifest, pregate, ship-receipt), each with schema_version, exported TypeScript types, and bin/gstack-artifact validate <file>, which phase-close, gstack-ship-receipt and gstack-pregate call before writing. run.json is the manifest: required phases, each reviewer's outcome and model, input hashes, decision and finding counts, artifact paths and hashes; gstack-artifact validate run.json is the deterministic completion check a parent runs first, and it fails on an interrupted run, a missing reviewer, a stale artifact or a findings total that disagrees with the file. findings.jsonl rows share the review-log row shape so B7's derivation is one function over one type, and every finding carries a run-bound id (<run>-<phase>-F<n>) that decisions, tasks and the URGENT block reference. tasks.jsonl rows carry run-bound ids, depends_on, acceptance (the command or check that proves the task), findings (ids) and blocked_by (decision ids); validate checks referential integrity across the files and binds each reviewer's immutable output file (path and hash) into run.json, so a count that merely matches cannot hide an omitted finding. One transport contract for every new command: --json means JSON only on stdout and diagnostics on stderr; without it, one terminal line GSTACK_RESULT: skill=<name> status=<complete|gate_pending|incomplete|interrupted|refused> run=<dir> where the run directory holds fixed file names; a shared exit-code table (0 ok, 1 fail, 2 usage, 3 refused or needs a flag) printed in every new bin's --help, with a --help coverage test; every error line carries a reason code with a docs/troubleshooting.md anchor and a fix: clause from a new sibling table lib/result-codes.ts (same {anchor, summary, fix} shape as lib/gate-outcomes.ts, whose state enum is for gate outcomes only; test/troubleshooting-anchors.test.ts covers both tables; the artifact errors are an enumerated list, not a wildcard): GUARD_NOT_INSTALLED, REPO_COMMANDS_NOT_AUTHORIZED, POLICY_SOURCE_UNAVAILABLE, RESTAMP_PREDECESSOR_MOVED, GATE_REUSE_NOT_ELIGIBLE, QUEUE_STALE, REVIEW_STATUS_MISMATCH, EXECUTION_UNKNOWN, and the ARTIFACT_* list. The terminal-line rule applies to workflow commands (run, next, submit, answer, restamp, pregate); pure query commands (schema, validate --json, the doctor trailer) keep their own documented output. Every reviewer returns canonical structured findings (the findings.jsonl row shape) beside its prose, and the aggregate reconciles their exact ids, keeping disagreements and resolutions as separate fields; a hash of the prose proves which file was kept, not completeness, so the reconciliation is the completeness check. validate also rejects duplicate ids, dependency cycles, malformed JSONL tails, and artifact paths that escape the run directory (symlinks included). Formats are versioned from day one with every reader and writer enumerated (tasks v2 adds depends_on, acceptance, findings, blocked_by over today's source_finding rows, scripts/resolvers/tasks-section.ts:77; v1 rows still aggregate), and bindReview validates a --findings file before it consumes the start token (lib/review-evidence.ts:188). gstack-artifact schema <name> prints the JSON Schema for the version it validates, so a parent on another machine can read the contract; gstack-artifact ack <run.json> --consumer <thread-id> is the idempotent consumption report behind P7 (it sets run.json.consumed_by and appends to analytics/autoplan-timing.jsonl); validate --json returns {valid, errors:[{code, path, message}]}.
B1. A real unattended session kind. A fourth kind, unattended, honored only as the explicit GSTACK_SESSION_KIND=unattended override (bin/gstack-session-kind:17-23). headless (set by GSTACK_HEADLESS, CI, GITHUB_ACTIONS, and by the eval harness) keeps today's BLOCK semantics, so no paid eval or E2E case changes meaning. unattended gets its own semantics in the preamble and gstack-skill-start:
unattendedis added to the kind whitelist ingstack-skill-start(bin/gstack-skill-start:93 coerces any unknown kind tointeractive, the inverse of the intent), an unknown kind printsSESSION_KIND: interactive (unknown kind '<x>')so a mixed-version fleet is loud, the explicit override outranksGSTACK_HEADLESS, and the eval harness never sets it.- No prompts of any kind: update check, upgrade offer, lake intro, telemetry, proactive and routing blocks are suppressed as they are for
spawned(bin/gstack-skill-start:96, :394). Privacy is enforced at the writers and sync entrypoints, not only at the prompts: the analytics and artifact-sync calls that run outside the onboarding guard (bin/gstack-skill-start:179, :310) and the review-log sync enqueue (bin/gstack-review-log:99) all check the session kind and skip with an explicit line, inherited enabled settings and pending queue work included; the test instruments the egress sinks and asserts no subprocess or network call, not merely the absence of a prompt. The upgrade offer becomes one line at skill end (UPGRADE_AVAILABLE: <version>; run /gstack-upgrade). An unanswered consent isoff; the preamble never records consent in headless. - Questions: a
(recommended)option is taken and the decision appended todecisions.jsonlwithkind: "auto"; a question with no recommendation, a consent, a destructive option or an approval gate is appended withkind: "gate"andstatus: "pending", and the skill stops at its next gate with the pending list printed. Unattended never approves and never publishes or syncs data.spawnedandheadlesskeep today's behavior. - The Capy adaptation text in the preamble (no Skill tool, no slash commands, plain text is not delivered) moves from the user-scope skill into a
{{HOST_ADAPTATION:unattended}}resolver so every host's render carries it. - The autoplan frontmatter
PreToolUsehooks deny every matched call withAutoplan publication guard is unavailablewhenautoplan/bin/phase-publication-hookis absent. A host that honors the frontmatter but lacks the file is blocked outright. The hook renders only for the Claude host (scripts/resolvers/composition.ts:11) and on Capy it is present but never executed, so "file absent" is the wrong condition: on Claude Code, deny-on-absent stays (a broken install must not pass as an absent one, autoplan/bin/phase-publication-hook:4-9); on every host that does not execute hooks, and wheneverSESSION_KIND=unattended,gstack-skill-startprintsautoplan guard: not enforced by this host; publication order is unverified (GUARD_NOT_INSTALLED)once, and B0's validator requires that line inreview-record.mdfor an unattended run. That satisfies the owner's ask (guarded calls proceed and the run says so) without weakening the one host that enforces the guard. - Tests:
test/gstack-session-kind.test.ts,test/gstack-skill-start.test.ts,test/spawned-consent-rule.test.tsgain the unattended cases; negative controls prove unattended never emits the telemetry prompt and thatGSTACK_HEADLESSstill blocks.
B2. autoplan unattended profile and the output files. A new lazy section autoplan/sections/unattended.md.tmpl (loaded when SESSION_KIND: unattended) and a bun gstack-autoplan-snapshot export step implemented in new lib/autoplan-export.ts (the snapshot bin stays at 865 lines), writing through B0's schemas. Export writes, beside ACTIVE_PLAN: plan.md (the ## Implementation plan section only; warn above 30 KB), review-record.md (the ## Review record section and decision audit trail), tasks.jsonl (the aggregated per-phase tasks, same schema as today's tasks-<phase>-*.jsonl), decisions.jsonl (every auto, taste, user-challenge and gate item with id, phase, kind, recommended, options, cost, status), findings.jsonl (every finding with phase, voice, model, severity, file, line, title, resolution), timing.json (per-phase start/end, outside-voice wall time, total; see B10) and run.json. One executable owns the unattended loop as an explicit, durable state machine: bin/gstack-autoplan (thin, over lib/autoplan-run.ts). On a host where the parent dispatches the native reviewer itself (Capy: the native voice is a subagent the parent starts from the prompt file, exactly how this review ran), the loop is next and submit: next --out <dir> prepares the current phase (snapshot, methodology, prompt), persists the dispatch intent with an attempt id, prints the voice, the prompt path, the expected result path and the model family required, and exits with GSTACK_RESULT: status=awaiting_result; submit --out <dir> --phase <p> --voice native|outside --result <file> --model <id> --attempt <id> validates the result (full-read receipt, canonical findings, model family), binds its hash into run.json atomically, runs the B11 reconciliation and phase-close, and advances. run --plan <file> --out <dir> [--runner ...] [--outside-model <id>] [--deadline <min>] [--spend-cap <usd>] [--plan-heading <text>] is next in a loop and exists only when a runner can execute both voices itself. status --out <dir> prints the manifest. answer --out <dir> --gate-rev <rev> --reply "<grammar>" rejects a stale, conflicting or partially parsed reply atomically with no writes; applies the choices; and when a non-recommended choice changes reviewed content (scope, architecture, test plan), recomputes the affected review-input identities and reopens the dependent phases with Eng last, the same rule today's option D follows, instead of finalizing a plan no reviewer evaluated. Only a reply that changes nothing reviewed finalizes plan.md and run.json.status without another paid phase. resume --out <dir> takes exclusive ownership of the run (a second writer is refused), reconciles any attempt whose dispatch intent exists without a terminal record as EXECUTION_UNKNOWN (the provider may have finished and charged; snapshot hashes prove input identity, not completion), and never redispatches an ambiguous attempt automatically. --deadline cancels every child the runner started; spend by a host-dispatched subagent is reported as unknown in the ledger, not as zero. The minimal shared spend ledger (lib/spend-ledger.ts: atomic spent + reserved + next_estimate <= cap under a file lock, reservations retained for unsettled attempts after a crash) lands here, not in PR E, because --spend-cap needs it. --out must be a directory the parent chose (durable or not, reported per artifact class); nothing is written beside ACTIVE_PLAN by default. The final approval gate in unattended runs is written as a gate list (B9) into decisions.jsonl and printed; the run ends with GSTACK_RESULT: skill=autoplan status=gate_pending run=<dir>. Lanes read tasks.jsonl, not the plan. docs/unattended.md is the parent-agent quickstart: install, run, validate, answer, ack, five copy-paste commands, linked from the user-scope Capy skill. Each run also appends session_kind and, when the parent reports it, consumer to analytics/autoplan-timing.jsonl (P7).
csoin unattended runs printsGSTACK_RESULT: skill=cso status=<...> run=<dir>at exit (the run directory holdsfindings.jsonland the run id), andgstack-doctor --checklists the CSO native helper as an optional component (SKIP cso (toolchain absent), never silent).- A plan with any layout is accepted (already true: snapshot
initwraps raw text; document it in the headless section) and--plan-heading <text>names a custom heading when a repo's plans use one. - Acceptance: this plan, reviewed on this machine with no Claude Code harness, produces the files; a parent agent runs
gstack-artifact validate run.json, lists pending decisions withjq 'select(.status=="pending")' decisions.jsonl, and never reads the record. Interrupted-run, missing-reviewer and omitted-finding fixtures fail validation;test/autoplan-run-recovery.test.tskills the runner before and after result persistence and asserts no automatic duplicate charge;test/autoplan-run-concurrency.test.tscovers two writers, revision conflicts and exactly-onceack;test/autoplan-answer-invalidation.test.tsproves a scope-changing answer reopens only the affected phases and a stale reply writes nothing. New bash bins and their tests are classified for the Windows subset (scripts/test-free-shards.ts --windows-only).
B3. A pluggable outside voice, with a model parameter. The outside voice becomes a runtime choice, not a render-time one: outsideVoiceFor(ctx) picks the provider when gen:skill-docs renders (scripts/resolvers/outside-voice.ts:8-12) and the rendered prose shells out to codex directly (scripts/resolvers/outside-voice-steps.ts:177), so a per-run --runner cannot live there without rendering three branches into every zero-headroom skill. New bin/gstack-outside-voice run --runner <r> --prompt <file> --out <file> [--model <id>] [--result <file>] over lib/outside-voice-runner.ts; the resolver names the bin and the runner table once. Every runner implements one adapter interface: declared capabilities (repository access or supplied-input only), input digest, actual model metadata, cancellation, terminal result and usage; the api runner either runs a bounded repository tool loop or is labeled supplied-input review in the record, never silently equal to a repo-reading review. Runners: codex-cli (today's path), api (a direct call with OPENAI_API_KEY or ANTHROPIC_API_KEY through new lib/outside-voice-api.ts, model from --outside-model), or host-subagent (the host runs a subagent on another model family from the paste-ready prompt and returns its output file; the parent passes --outside-result <file> --outside-model <id>). A completed host-subagent or api review is a full outside voice: OUTSIDE_STATUS: completed provider=<runner> model=<id>, never [single-model] or degraded, and the model id is recorded in findings.jsonl and the voices log row. A missing Codex CLI is reported as codex-cli unavailable; using <runner> only when another runner is configured; with none, today's unavailable text stands. New bin/gstack-codex-login: printenv OPENAI_API_KEY | codex login --with-api-key, then codex login status, exit non-zero with the probe's hint on failure; outsideVoiceFailurePolicy() (outside-voice.ts:217) and the doctor codex row (bin/gstack-doctor:186) name it as the unattended remedy. --outside-model <id> sets GSTACK_CODEX_MODEL for the CLI runner (bin/gstack-codex-probe:144-147 already honors it). The outside voice stays on by default (codex_reviews: enabled). The same-family rule holds for every runner: the outside model must differ in family from the native reviewer, and the record says which was which. Runner selection is decoupled from host identity (today outsideVoiceFor picks the provider from the harness, scripts/resolvers/outside-voice.ts:8-12), so a Codex-host render can still receive an outside voice from a Claude subagent and a Capy run can use either. The Capy dispatch path (a Capy subagent reading the native prompt file with its own file-reading tool) is the one this plan's own review ran on; the PR body records it.
B4. Condensed operative checklists. Each review skill gets sections/checklist.md.tmpl, at most 200 lines, holding the executable steps and required outputs of that phase; the deep methodology stays in review-sections.md. In unattended runs and under an explicit light mode the phase loads the checklist plus only the deep sections the detected scope needs (the manifest gains a scope field per section: always, ui, dx, db, perf, security, incident), and prints one index line: Skipped sections: <ids> (scope: <detected>). Interactive runs are unchanged. nativeDispatchPrompt already exists in snapshot.json; bun gstack-autoplan-snapshot prompt <phase> prints it so a parent can paste it to a subagent. The manifest's scope field is optional with always as the default, its "passive, no machine predicate" note is updated with the schema, and the only consumer is the runner (interactive renders ignore it; test/qa-lazy-sections.test.ts:127 keeps asserting passive resolution for them). A checklist is "replace, never add": the methodology bytes it summarizes leave the eager text, and an obligation-to-section map (every required output names the section that produces it) proves nothing required was dropped, with a conservative fallback that loads the deep section whenever scope detection is uncertain and behavioral negative controls for omitted obligations. A test caps every checklist at 200 lines and checks the obligation map, not just the section count.
B7. Review-log status derived from findings. reviewFreshness already downgrades a clean row with open issues to UNVERIFIED at read time (lib/review-evidence.ts:238-240); the gap is at write time. bindReview (lib/review-evidence.ts:178-186, the single choke point) accepts --findings <path>: it derives status (clean when no finding is open, else issues_open), unresolved, issues_found and critical_gaps from the file and rejects a row whose claimed values disagree. The row carries separate findings_total, findings_open and findings_resolved; reviewFreshness (lib/review-evidence.ts:238) is updated in the same change to read findings_open, since today it rejects any row with issues_found > 0 even when every finding is resolved. A rejected row exits 1 with review-log: status mismatch: claimed clean, findings.jsonl has 176 open (REVIEW_STATUS_MISMATCH) on stderr (the wrapper drops the 2>/dev/null at bin/gstack-review-log:95 and stops converting binding failures into invalid JSON); usage becomes 2 (today 1, :81-84); the legacy critical and Codex counters keep their conservative reading. Rows written without --findings are tagged status_source: claimed. Unattended autoplan always logs through --findings. Phase subagents that cannot write the JSONL return findings to the parent, who writes findings.jsonl and logs from it; the hand-written status: clean beside issues_found: 176 is rejected at write time.
B8. Spec-review loop at the cap. The loop stops today on PASS, repeated issues or the third launch, and a missing score alone does not force another launch (scripts/resolvers/spec-review.ts:182-192); the 1-10 score is reported but not a stop rule. At the cap it now prints the unresolved gaps as a numbered list and takes one of two recorded paths, never a resample of unchanged input: a deterministic issue-verification pass that checks only the listed issues after their fixes are applied and is recorded as issues verified: k of N, never promoted to a full-review verdict and never a fourth model launch past the cap (unattended default), or N fixes unconfirmed with the approval gate blocked by a spec not re-verified decision. An interactive user may raise --max-rounds, and each extra launch requires a changed input hash. DONE_WITH_CONCERNS always names the missing points next to the score.
B9. One gate list instead of four-option sequences. New lib/gate-list.ts with bin/gstack-gate (render, parse): a gate list is numbered items each with a recommended default and lettered options, and one reply grammar: all (every item takes its recommendation), or <id><option> tokens (d3b uc1a), or all except <tokens> (every item takes its recommendation except the listed overrides; the two forms are the same operation, so there is no exclusion-vs-override ambiguity). Bare yes/no are rejected as ambiguous across multi-option items and reported as unparsed. Every item has a stable id that survives pagination (a list longer than seven items is rendered in pages with the same ids), items are marked auto (taken if unanswered) or approval (stays pending until answered; the owner's direction stands), and a reply binds to the gate revision it answers (gate_rev in decisions.jsonl); a reply against a stale revision is refused with the current one. The rendered list prints the exact reply shape it accepts and the resolved JSON it will produce; parse returns the resolved choices as JSON and lists unparsed tokens instead of guessing. autoplan's Phase 4 renders the gate through it (interactive and unattended); CEO Step 0 taste decisions join the same list instead of being asked mid-run. The owner replies quoted in the feedback (d10 yes, 1a 2a, a 33-decision approve-all) are parse fixtures.
B10. Wall time and a pre-run estimate. phase-close records phase start and end, outside-voice wall time and native wall time to timing.json and to $GSTACK_STATE_ROOT/analytics/autoplan-timing.jsonl. Phase 0 prints an estimate from plan bytes and detected scope using the recorded history (median of the last 10 runs per phase, no history when empty), and the final report prints the actual whole-cycle time next to it.
B11. Reconciliation when outside passes run beside native ones. The outside Bash dispatch may start while the native reviewer runs (both bind to the same snapshot), and phase-close gains an explicit numbered operation: for each outside finding record confirmed, disagree or new against the native result, and for the Eng dispatch verify the prior-phase consensus summaries are present in the prompt before sending (a test fixture with a missing CEO summary is refused). The completion barrier and phase order are unchanged.
B12. URGENT, outside this plan. The final report and plan.md get a fixed first block ## URGENT, outside this plan listing security or data-loss findings any voice raised that the plan does not own, each with a suggested owner and the finding id; None when empty. A test asserts the block precedes the summary.
PR C (C-stamp, C-pregate): queue-aware ship and the pre-gate (P0-3, P0-4)
C1. A repo ship policy, machine-readable. New pin file .gstack/ship-policy.json (schema in lib/ship-policy.ts, containment-checked like .gstack/version-path): bump (patch | ask), release_tool (for example bun run release:restamp), stamp_paths (globs where (was X), since: and version mentions are rewritten), changelog (CHANGELOG.md), mirror (path of a paired repo to restamp identically), history (merge-only | squash-ok), preregistration_shas (must be ancestors of HEAD), pr_count (default_pr_count, split_requires_owner; consumed by D1). gstack-version-bump classify returns bumpPolicy from it, and Step 12 skips the MINOR/MAJOR prompts under bump: patch. Without the file nothing changes.
- Trust boundary for every pin file that names a command (
release_tool,.gstack/generated.json,.gstack/pregate.json): file presence never authorizes execution.gstack-restamp,gstack-regenandgstack-pregaterun repo-declared commands only when invoked from/shipor/land-and-deploy, or with--allow-repo-commands; standalone they list the commands and exit 3 withrepo commands not executed (pass --allow-repo-commands). Commands are read from the committed file onorigin/<base>, not the working tree, so a PR cannot smuggle a command into its own gate;mirrorwrites are confined to the declared path and refused outside it. This is the opt-in posturegstack-version-bump --regen-digestalready takes (bin/gstack-version-bump:373-385). Pinning the command string does not freeze its implementation (bun run release:restampstill executes the PR's package script), so the record names the policy revision, the command, its working directory and the trust scope. Each tool hasinit(writes a commented example with$schemaand every key at its default),validate, andshow-effective(the resolved configuration with the source revision of each value);docs/ship-policy.mdshows gstack's own files. A PR that introduces or changes a policy file runs under the reviewed bootstrap path:--policy-from headis accepted only with--allow-repo-commands, and the receipt recordspolicy: head (unreviewed)so the coordinator sees it. A refusal happens before any write and saysnothing written. Whenorigin/<base>is unresolvable (shallow or detached gate checkouts), the tool exits 3 withpolicy source unavailable — fix: git fetch origin <base> --depth=1 (POLICY_SOURCE_UNAVAILABLE)and never falls back to the working tree silently. Every repo-controlled input (mirror,stamp_paths,generated.jsoncommands,pregate.jsonguard patterns,banned-terms.json) has named containment fixtures: no.., no symlink escape, globs confined to the repo, regex size caps;gstack-regen --checkis code execution even in a scratch worktree and sits behind the same opt-in. The policy schema includesqueue_mode, and examples live in sibling*.example.jsonfiles orinit --explain, since JSON carries no comments.
C2. gstack-restamp: the whole stamp in one step. New bin/gstack-restamp over lib/restamp.ts and lib/changelog-check.ts. Modes (--queue-mode stamp-at-merge is the umbrella: no claim at PR-open time, stamp only when told the PR is next): --version X, --next (under stamp-at-merge: the configured version source at the base revision through lib/version-source.ts (explicit version paths, pinned manifests, three- vs four-component versions and no-version repos are the resolver's existing cases, lib/version-source.ts:135) plus the policy bump, nothing else; the claim reader stays for the legacy claim-at-open mode and the output says claims: ignored (stamp-at-merge)), with --expect-base <sha> and --expect-order <pr,pr,...> so the allocation records the base SHA and queue order it assumed and re-validates both immediately before merge, returning a structured QUEUE_STALE result with the exact recomputation command when either moved; the allocation and the receipt bind to repository, base SHA, the ordered predecessor chain, the PR head and the synthetic integration tree, and the merge itself is serialized with server-enforced conditions (the GitHub merge API's expected head sha) so a check-to-merge race is rejected by the server rather than assumed away, and where the host cannot enforce the condition the coordinator recomputes after the merge and the receipt says so, --after <pr> (gate-ahead: build the merge of the branch and the predecessor's head in a throwaway worktree, stamp N+1 relative to the predecessor's version there, gate there, and record the predecessor head in .gstack/tmp/gate-ahead.json; the PR branch's history is never touched, so squash-history PRs stay clean; gate-ahead.json records the evidence branch key, because review and evidence ledgers are keyed by branch (bin/gstack-review-log:18-22, bin/gstack-evidence:13) and a synthetic worktree branch would otherwise grade MISSING on the real PR), --base <ref>. It runs gstack-version-bump write, the policy's release_tool, re-heads the CHANGELOG (the PR's section moves to the top under the new version, above its predecessor's; duplicate headings and a top entry not equal to VERSION are errors), rewrites stamp_paths mentions (exact (was X), since: X and vX patterns only, within the declared globs, shown as a dry-run diff before writing), regenerates the generated-file registry (C4), repeats everything in mirror, and prints the diff limited to stamp files. Idempotent: a second run with the same version is a no-op; --dry-run prints the full diff and writes nothing. The release tool runs under a contract: in an isolated staging worktree, with an enumerated allowed-output set, recording preimages and postimages, applied to the branch only after validation; an output outside the allowed set is a refusal, and rollback refuses to overwrite a file whose current bytes differ from the recorded postimage (a later user edit) and stops with a conflict. Every restamp journals its intended inputs and completed writes (.gstack/tmp/restamp-journal.json) so a failure after any stage resumes or rolls back without touching unrelated edits (the version writer already has a half-write state, bin/gstack-version-bump:489; the journal extends it across the release tool, the CHANGELOG, stamp_paths and the mirror), with injected-failure tests after every stage. --queue-mode defaults from ship-policy.json, else legacy, so the flag is not required on every call. Tier 1: --version, --next, --after (the gate-ahead is what makes a reorder cheap, so the P3 promise needs it), release_tool, CHANGELOG re-heading, stamp_paths. Tier 2: mirror. Considered and rejected: stamping on main after merge with a release commit. The gate must run on the stamped tree (CHANGELOG and version live in the PR, and a post-merge commit is itself unreviewed), which is why the owner's requirement is stamp-at-merge.
C3. The tree-equality receipt. New bin/gstack-tree-receipt (lib/tree-receipt.ts): prints gated head, current head, both HEAD^{tree} hashes, both gstack-wtree fingerprints, the diff limited to stamp files, the predecessor head the gate assumed, and one verdict line. Inputs are explicit: --gated <sha> and --predecessor <sha>, or --from .gstack/tmp/gate-ahead.json; --json emits the receipt as an object. Gates compare committed heads only (a working-tree fingerprint can name objects unreachable once the VM is gone, bin/gstack-wtree:24). The diff is classified hunk by hunk, not path by path, with per-file-type rules: lockfile rules match only the root package's version keys (where syncNpmLockfiles writes, bin/gstack-version-bump:486); stamp_paths rules match (was X), since: X and vX only when X equals the old version and the replacement equals the new one, so a dependency pin that happens to equal the old version classifies as changed (a named negative fixture); a CHANGELOG heading moving is permitted; any other changed byte counts as a change (bin/gstack-evidence:505 compares allowed filenames today, which would let a changed generated prompt pass; npm lockfile metadata changes on every stamp, bin/gstack-version-bump:486, which would otherwise fail every restamp). Equivalence is defined per reusable gate, not once: a gate that consumes VERSION or generated bytes (the agents-digest freshness test does, test/agents-digest.test.ts:22) is stamp-sensitive and always reruns on the final stamped tree; the receipt lists which gates are reusable and which reran, keeping both the raw and the normalized identity. The verdict names the residual hunks: TREE: same-modulo-stamps or TREE: changed (<residual paths>). Tree identity alone is not gate evidence: the receipt also records the identity the earlier gate ran under (runtime pins, lockfile hash, CI workflow hashes, the gate command's cmd_sha256 from gstack-evidence) plus the executed test selection and the container or runner image when known) and prints gate-reuse: eligible only when every one matches, else gate-reuse: not-eligible (<what changed>) — fix: <rerun command> (GATE_REUSE_NOT_ELIGIBLE). The minimum portable evidence lands in this PR, not in E: gstack-evidence bundle exports the comparison material itself (the committed tree id and the stamped-file normalization inputs), the complete required-lane manifest, executed selections, source run URLs, outcomes, artifact hashes and producer-bound run metadata, distinguishes imported assertions from independently verified evidence, and refuses reuse when execution identity is unknown or comparison objects are missing; the receipt reads a bundle from a fresh coordinator machine after the gate VM is gone, without rerunning the gate (gstack-evidence is machine-local by design, bin/gstack-evidence:27-38, so the bundle is the portable form). It refuses eligible when the recorded predecessor head is no longer the predecessor PR's head (fetch and compare) and prints PREDECESSOR MOVED <old> -> <new> — fix: gstack-restamp --after <pr> again (RESTAMP_PREDECESSOR_MOVED). /ship Step 16 and /land-and-deploy consult it; when the receipt says eligible, a repeated full gate needs --force-gate <reason>, and the coordinator makes the call.
C4. Generated files regenerate, never hand-merge. New pin .gstack/generated.json ([{paths, command}]) and bin/gstack-regen (lib/regen.ts) with two verbs: --check runs each command in a scratch copy and reports stale entries without touching the tree (the pre-gate's freshness check), and --write regenerates in place (what the restamp and the conflict rule use, where changed output is the point, not staleness). Step 3 conflict rule: for a path in the registry take either side, then gstack-regen; the agent never hand-merges a generated file. Used by C2, the pre-gate (C8) and Step 16 stage 1. gstack's own entry is bun run gen:skill-docs --host all plus the agents digest and llms files.
C5. The handoff record, identifiers first. New bin/gstack-ship-receipt renders a fenced ```gstack-ship-receipt block: pr, head, version, base, gated_tree, gate (lanes with pass/fail/skip counts), ci (check counts), spend_usd, predecessor. The block carries schema_version and is validated by gstack-artifact. It is the first section of the PR body and the first lines of the completion message to the coordinator, so truncation loses prose, not identifiers. /ship Step 18 writes it; /land-and-deploy reads it with gstack-ship-receipt read --pr <n> --json.
C6. Issue links and flake issues. Step 18 adds Fixes #N only on explicit closure intent: a Fixes #N/Closes #N trailer in a commit or an explicit --issue N, never a bare mention (a context reference, a partial fix or a deferred ask is Refs #N); it reads each through gstack-issue-guard, and maps failing CI run ids to bot-opened flake issues (search issues whose body names the run id) as Flake: #N (run <id>). Spec-archive links keep today's rules.
C7. History policy and preregistration ancestry. Under history: merge-only /ship refuses a squash or rebase plan and says why; gstack-evidence ancestor <sha> checks each preregistration_shas entry is an ancestor of HEAD before push, and the receipt records the result.
C8. gstack-pregate: cheap checks before anything paid. New bin/gstack-pregate over lib/pregate/*.ts (one file per check, each under 150 lines, registry in lib/pregate/index.ts). It runs at the entry of ship Step 5, before Step 6's paid evals and before any remote gate, under set -o pipefail, aggregates exit codes itself, and prints a table plus pregate.json. A failing check blocks; a repo downgrades a named check to warn in .gstack/pregate.json, except secrets and required-lane obligations, which cannot be downgraded. Every check records the input identity it ran against (the tree, the generated outputs, the selection) with the same before/after fingerprinting gstack-evidence uses (bin/gstack-evidence:394), and publication requires results that are current after final generation and stamping; a timed-out or tool-unavailable check is incomplete, never pass. The lane list and platform tiers come from the repo's .github/workflows/*.yml (the same parser as project pins), never from a pre-gate-owned taxonomy, so a changed CI matrix changes the pre-gate. Two stages with separate clocks: the mechanical preflight (regen --check, secrets, strays, literals, links, lint, patches, ratchets) is the two-minute promise, measured on gstack and gbrain and printed per check; test execution (lanes, guards, hermetic) is the second stage with its own measured time, never folded into the first. Lane discovery reads what the workflows actually encode and defers to runner-owned selection manifests where the matrix is computed by a planner (gstack's Windows matrix comes from scripts/test-free-shards.ts, .github/workflows/windows-free-tests.yml:62, and exclusive classification lives in TypeScript, scripts/test-free-shards.ts:243); --explain prints each selected test's provenance and each uncertain mapping. A requires-remote entry names the workflow and job to dispatch and the receipt that clears it. Tier 1 (this PR, each with a named incident fixture): regen, secrets, strays, literals, lanes with the pipefail fix. Tier 2 (same PR if green, else a follow-up pre-gate PR): guards, ratchets, lint, links, hermetic, patches, integrator. Checks:
regen: every.gstack/generated.jsonentry fresh (C4,--checkin agit worktreescratch, never a copy of a tree withnode_modules), including goldens outside the repo's regen script when listed there.guards: new exported interface members, CLI commands, error codes and migrations detected from the diff (patterns in.gstack/pregate.jsonguards), each mapped to the guard tests it must run (CLI contract rows,--helpcoverage, canonical-writers census, upgrade replay, serial-files manifest); runs them and reports which ran.ratchets: module-size and any repo-declared ratchet test file; on a failure it prints the exact fixture entry the repo's convention would need (an interface-member growth needs a registeredmovedFrom/reasonline; the documented exception is for data tables and generated literals, test/helpers/module-size.ts:225) and leaves the append to the author, so the ratchet is never auto-exempted.lint: semgrep, gitleaks, actionlint and shellcheck when installed and when touched paths match; absent tools printnot installed, never PASS.secretsrunsgstack-redactover the branch's added lines regardless.links: relative links and anchors in changed markdown resolve to tracked files (lib/md-links.ts).lanes: maps touched source files to the test files that depend on them throughlib/pregate/import-graph.ts, which unions static imports, repo-path string literals (test/gstack-doctor.test.tsspawnsbin/gstack-doctor; no import names it), thebin/<name>→test/<name>*.test.tsconvention and explicit.gstack/pregate.jsondependency declarations for subprocess targets, runtime file reads and generated inputs; an uncertain mapping selects conservatively (more tests, never fewer) and a touched file with no lane isFAIL lanes — <file> has no test lane, not a pass (test/helpers/touchfile-closure.tsfollows only helpers and fixtures and calls itself a lower bound, so it is the precedent, not the implementation).requires-remoteobligations are persisted inpregate.jsonand the ship receipt, and publication requires their matching receipts; printing the line is not enforcement. It classifies them serial, E2E, platform or unit from the workflow files, runs the local ones, and printsTiers ran: unit serial; requires-remote: windows. It blocks only on lanes that can run here (a serial file under the touched area that did not run); a platform lane this machine cannot run becomes arequires-remoteentry the gate must include, so a Windows-affecting change on a Linux box has a forward path and never merges with that lane unrun.literals: for every changed exported constant, grep the whole test tree, nightly-only and Heavy-only files included, for the old literal; for every changed spawn, retry or timeout call, grep for spies and assertions on it; for every migration, grep SQL fixtures that DROP or ALTER the objects it touches.hermetic: new and changed test files run once through newbin/gstack-hermetic-runoverlib/hermetic-env.ts(the builder moves out oftest/helpers/hermetic-env.ts:112, which keeps a re-export, because a shipped bin must not import test code), which allocates an isolatedHOME, setsGIT_CONFIG_GLOBAL=/dev/nullandGIT_CONFIG_NOSYSTEM=1(today's helper keepsHOMEand accepts overrides after scrubbing, test/helpers/hermetic-env.ts:49, :147), and distinguishes an isolation failure from a fixture that legitimately needs an identity; a planted home file and a global git identity are the fixtures.strays: files added at the repo root or outside the usual directories, warning onzz-*,tmp*,scratch*.patches: forpatchedDependencies, the key matches the installed version and every hunk in the committed patch is present in a regenerated one; dropped hunks are listed and each needs explicit confirmation.integrator(--after-merge): doc size caps, ratchets, registry goldens and manifest freshness after each lane merge, with the ratchet-conflict rule printed: re-sum to the measured count and keep both notes.- The Step 5 lane example in
ship/sections/tests.md.tmpl:26-30collects PIDs andwait $pid || failper lane; every recorded command carriespipefail; nothing judges success fromtail. - Acceptance: a fixture per incident in the feedback (stale goldens, scratch script, Heavy-only old constant, Windows-only change without its lane, dropped vendored hunk,
| tail-hidden verify failure) is caught locally before any remote gate starts.
PR D: plan rows, sideways review, investigate recipes (P1-5, P1-6, P1-7)
D1. Reality rows in plan reviews. New resolver scripts/resolvers/plan-reality.ts renders {{PLAN_REALITY_ROWS}} into plan-eng-review's Scope Challenge, plan-ceo-review 0A/0B, the autoplan CEO and Eng phase files and the Pre-Gate table. In unattended runs a missing applicable row sets the phase incomplete (not a warning); interactive runs keep the two-repair-then-warn rule. Tier 1 (no scope detection needed, demonstrated by this plan's own review): the premise table, already done, surface check, binding decisions, numbers, deferred asks, PR count. Tier 2 (need a scope signal the manifest's scope field (B4) supplies: db, perf, search, incident): transaction semantics, recall, units, perf table, falsification box, stop reasons, reporter environment, prior art, contract assumptions, dependency fallback, obligation tiers. Rows, each with a one-line incident note in the deep section:
- Premise table:
premise | claim | file:line receipt | verdict; every factual claim in the draft gets a receipt or the tagunverified, and the count of unverified claims is a scored line in the Completion Summary. - Prior art: for a vendored or patched dependency or a known library behavior, search the upstream tracker and open PRs, then record
adopt <link>ordiverge because <reason>. - Transaction semantics (driver, pool or protocol change): what happens to the surrounding transaction on idle, pooled and reserved connections; crash robot reruns after the change.
- Binding decisions: read
gstack-decision-search --scope branch --recent 50and a## Binding decisionssection of the plan; they outrank project defaults and CLAUDE.md, and a reintroduced overruled default is a finding. - Already done:
git fetch origin <base>thengit log origin/<base> --since=<ask date> -- <named files>and--grepfor the named symbols; a hit is reported before any task is written. - Surface check: every named CLI command or HTTP route verified against the registry or
--help; a miss is a finding. - Contract assumptions: a row per default or flag assumed from another repo, rechecked against merged code before the next stage.
- Numbers: every number tagged
[measured: <source>]or[estimated]; cited baselines tagged[re-measure before gate]. - Falsification box (incident or outage plans): top hypothesis, the evidence that kills it, a time box, and the plan's action in each branch.
- Stop reasons: every new stop reason, hold code and progress line names the test that triggers it.
- Reporter environment: a repro that does not match the reporter's environment is
class not confirmedand may not sayruled out. - Units: a diff that changes an input's units or accuracy lists every threshold that consumes it and shows the re-tune.
- Perf table: machine, engine, data size, N, p50/p95, statistics state, base and branch interleaved, pathological and steady-state cases,
reproduced on main?. - Recall: a search or query-plan change reports recall against exact search across scope buckets.
- PR count: read
pr_countfrom.gstack/ship-policy.json(C1); default one PR per wave; a proposed split shows the queue cost per extra PR and checks user-facing references across the split. - Dependency fallback: what ships without each predecessor and by when.
- Deferred asks: the linked issue's requests the plan consciously leaves out, each with its reason, listed in the gate template so a reviewer sees them.
- Obligation tiers: must-ship, next PR, deferred, under a stated size budget; the ledger status column carries the tier.
D2. Review and cso look sideways. In review/checklist.md and review/SKILL.md.tmpl Step 4: for every finding grep the same pattern at every other ingress, command and call site and rate severity on the worst sibling; a forced-CRITICAL list (data loss, trust-boundary bypass, uncapped spend) that no lane can downgrade; for a new cache in front of a guarded or metered call, name the policy checks a hit skips; when imports move from static to dynamic, list top-level side effects no longer loaded and who reads each registry; when a function changes, find modules that claim parity with it (docstrings, parity tests) and flag unchanged ones; flag pool queries awaited inside a caller's transaction on hot read paths. The plan-completion resolver (scripts/resolvers/plan-gates.ts:464-490) adds an Undeclared behavior changes line listing diff behavior the author's summary omits, and the approval step refuses approved without a recorded read of the full diff. Test-only flake fixes require a number: the probability the test still fails on a real regression, before and after (shared resolver with D3). /cso adopts the sibling sweep for its findings and nothing else.
D3. Investigate recipes and backlog mode. investigate/SKILL.md.tmpl gains lazy sections (new investigate/sections/ with a manifest): flake (reproduce on main; failure rate over N runs; cause inside or outside the PR; compare the metric with the last N green runs of branch and main via gh run list; forced probe that fails before and passes after; never loosen; a rerun only when the cause is known and outside the PR; a required ## Flake evidence PR-body section with the probe's before and after output and a routing note for the owner; reruns through ship-measure's discipline, paid evals never retried), polluter (file order from the CI log, bisect the earlier files with ship-measure free --files, report the polluter and what it leaked), race (timing probes, forced interleaving, concurrent-writer template), eval-bisect (distinct revisions, a cached cheap scorer, detached runs), and backlog (investigate --backlog: each issue classified still_open, already_fixed <commit>, partially_fixed or owned_elsewhere, a reproduction required for still_open, contributor PRs counted as evidence only, read-only output table, no issue closing). Phase 1 adds: read call sites before calling two queries duplicates; git update-index --refresh before timing anything that spawns git in a benchmark worktree (benchmark/SKILL.md.tmpl too).
PR E: /eval-plan, coordination tools, owner brief (P1-8, P2-9, P2-10)
E1. /eval-plan: preregistered, paid evals for the user's product. New skill eval-plan/SKILL.md.tmpl with sections, generalizing gstack's internal machinery:
- Preregistration checklist: bars, arms, decoys, stop rules, and a held-out exposure section listing every held-out set the plan touches, its prior exposure and reservations.
- Scorer dry run on dev data with measured accuracy per scorer and a stop when any is below its bar (lib generalization of
scripts/judge-calibration.tscompare()over user JSONL). $0stub-model dry run over real input shapes, plus the checklist: price-table entry present, output budget sized for reasoning models, the retry prompt's actual text.PRICINGmoves tolib/pricing.ts(re-exported for tests).- Priced pilot (n≈3) with extrapolation and an error band before the approval ask.
- Budget ledger
lib/spend-ledger.ts, extracted fromscripts/ship-measure.ts's admission logic and made honest about what it can promise: admission is estimated (the price table excludes some billing categories, test/helpers/pricing.ts:4, and actual cost reconciles after each call), the ledger refuses a new call whenspent + reserved >= capunder a file lock shared by concurrent workers, and the spend-vs-cap table regenerated at every gate showsspent,reserved,unknown chargesand the worst-case overrun (one in-flight call per worker).budget_exhaustedstops the run. - Arms from the project's model rules (
gstack-models resolve --role eval-arms --json, falling back to the repo's own policy doc) with per-arm cost shown. - Instrument audit: read every failing transcript of one dev round before freezing the scorer; check generated worlds for duplicate identifiers, word-like codes and generic phrases; mutation tests in both directions.
- Steady state: background queues empty at snapshot time, queue depth recorded as a gate.
- Sealed paths declared in the plan; the skill refuses to quote from them, self-checks output for n-grams from them and adds a provenance line.
- Long runs persist off the machine:
gstack-detach --sync-cmd <cmd> --sync-every <min>. - Memory preflight: concurrency times measured per-process memory, refuse above 70% of RAM.
- Power and risk sentences cite the committed power output, with a sensitivity table over the obvious rule alternatives (lib generalization of
scripts/lib/measure-bar.ts). - QA soak:
qa/sections/soak.md.tmplruns hours of mixed ingest against a long-lived server with a watchdog forCPU pinned, no progress. - Tiers: tier 1 is the preregistration checklist, the
$0stub dry run and the priced pilot with the ledger (the three steps behind the spend incidents); tier 3 (after P7) is the instrument audit, steady state, sealed paths, off-machine persistence, memory preflight, power tables and QA soak.
E2. Coordination tools. bin/gstack-lane-check (lib/lane-ownership.ts): given in-flight branches and PRs, diff each and flag planned files that intersect, with the PR number and hunk count. gstack-evidence bundle (shipped in C-stamp, C3) gains gstack-evidence verify <bundle> here, which reruns the probes or marks each claim verified or unverified; E1's priced pilot consumes the lib/spend-ledger.ts that B-runner ships (B2), adding the pricing table extraction (lib/pricing.ts from test/helpers/pricing.ts:4, whose exclusions are documented) rather than a second ledger. {{COORDINATOR_CONTRACT}} resolver: a block in the plan template (merge conditions, behavior-change notices, spend caps, handoff format) that every lane's subagent prompt inherits word for word; the prompt printer (B4) includes it. Cloud VMs: the repo's scripts/ubicloud/ubi-runner.sh adopts the user-scope skill's owner tag, list --mine, usage, own-VMs-only gc, record-before-create teardown, plus a VM-id scratch file under ~/.capy/work and a resume checklist in docs/TESTING_INTERNALS.md; long benchmarks and gates documented as watched background operations, one before/after pair per machine, with a warning before profiling very large imports. test_backend config (local | ubicloud) with the doctor cores row (A1) recommending ubicloud under 8 cores for full unit and E2E tiers. ship/sections/handoff.md.tmpl: the no-PR automation handoff (verify-only checks, branch, SHA, inputs, before/after metrics, escalation rule). The Capy skill says to store and use full thread ids in cross-thread messages.
E3. What the owner sees. autoplan Phase 4 defaults to a one-page brief: plain-language summary, the URGENT, outside this plan block (B12), and the numbered decisions rendered through the gate list (B9): seven per page with stable ids, auto items showing the default taken if unanswered, approval items staying pending until answered; the full review goes to review-record.md. After the final gate, every draft direction stands until the owner decides line in the plan is rewritten to the decided option and date (phase-close operation). Contributor-PR mode in /review (--contributor, auto-detected when the PR author is outside the maintainer set, read from an explicit maintainers list in .gstack/ship-policy.json, else repository push permission through the collaborators API, else off, with the source recorded; CODEOWNERS is file ownership, not maintenance authority, and is not used): outside PRs are evidence first; the output is a table (evidence value, risk surfaces, accept, supersede or decline), where accept keeps a direct path for a correct outside implementation judged by the same bar as a maintainer change, supersede rewrites from our own plan after a plain-language approval, and credit lines (Contributed by @handle, Co-Authored-By:, Supersedes #N) are emitted only when a diagnosis or test carried over. Banned terms: .gstack/banned-terms.json (terms, allowed_paths) checked by bin/gstack-banned-terms on every diff from /review and /document-release; findings name the term, file and the allowed locations.
Not in this wave
- A
capyhost target inhosts/. No resolver branches oncapabilities.questions(scripts/host-config.ts:178 only validates it, and every host declares it); a new host means regenerating every skill for one more render with no behavior behind it. Headless behavior keys on the session kind so every host gets it. Revisit if a host-specific tool rewrite is needed (decision D2). - Installing semgrep or gitleaks for the user. The pre-gate runs them when present and says
not installedwhen not. - Native Windows for
/csoand the Windows lane itself. The pre-gate reports a Windows lane asnot run here; running it stays CI's job. - Closing issues from
investigate --backlog. Read-only by design. - Stamping on main after merge. Considered under C2 and rejected: the gate must run on the stamped tree.
- A pre-gate that runs platform lanes this machine lacks. It records
requires-remoteand the gate includes the lane (C8).
Validation
- Incident fixtures. Each incident in the feedback becomes a fixture or scripted scenario under
test/fixtures/multi-agent-wave/, named by section and incident (p0-4-stale-goldens,p0-3-queue-reorder,p1-6-cache-skips-guard, ...), and a test shows the tool catching it or producing the artifact. The incident-to-test map is committed astest/fixtures/multi-agent-wave/INCIDENTS.md, not left in a PR body. - Unattended autoplan end to end. PR B-runner is proven by running
/autoplanon this plan from a Capy machine with no Claude Code harness, with the outside voice from either a Codex login made fromOPENAI_API_KEYor a cross-family host subagent (the gate accepts either and records which ran); the PR body links the output files, thegstack-artifact validate run.jsonresult, and a parent-agent transcript that consumes them withjqandgrepalone. This is PR B-runner's merge gate. - Queue replay.
test/gstack-restamp-queue.test.tsbuilds three PRs on a fixture repo with arelease:restampscript, a mirror repo, a CHANGELOG,(was X)doc mentions andsince:fields, reorders the queue twice, and asserts zero manual edits, correct(was X)text, CHANGELOG order, mirror parity, and tree receipts that saysame-modulo-stamps/gate-reuse: eligibleafter a pure restamp andchangedafter a real edit, including thePREDECESSOR MOVEDrefusal, a squash-history PR under gate-ahead (branch history untouched), arelease_toolthat regenerates a path outsidestamp_paths(classified throughgenerated.json), a metadata-only lockfile change (eligible) versus a dependency change (not eligible), and an environment change under an identical tree (gate-reuse: not-eligible). - Acceptance matrix.
docs/designs/multi-agent-wave-2026-10-10/ACCEPTANCE.mdlists every feature with its PR, tier, commands, artifact capabilities and deferred asks; the owner's gate approves that matrix, each PR body quotes its rows, andgstack-doctor --jsonexposes which capabilities the installed revision shipped. - Repo gates per AGENTS.md.
bun run typecheck,bun run typecheck:test(the test-code type ratchet CI runs, .github/workflows/free-tests.yml:223),bun run build,bun run format:cso:check, the credential scan,bun run gen:skill-docs --host allwith a clean diff,bun test test/parity-suite.test.tswith each PR's byte delta andCARVE_GUARDSrefresh in the same commit, the context-budget, skill-size-floor and module-size ratchets (every new module registered),test/spawnsync-timeout-tripwire.test.ts, the selected quality judges for changed prompts, then onebun run teston the frozen tree per PR, with incomplete platform and provider coverage reported separately;bun run eval:bg:prfor the changed prompts (autoplan, plan reviews, review, investigate, ship) with every trial reported.
Effort
Estimated per component ([estimated]; the CEO review found the first table uncredible, the Eng review re-counted B and C from what the text implies rather than the table's bin count, and these numbers are re-measured after PR A ships, [re-measure before gate]). Byte-budget work on ship, review, autoplan and qa (zero parity headroom) is the long pole for D, not the code.
| PR | New bins / libs / fixtures | Est. LOC | Human team | CC+gstack | Compression |
|---|---|---|---|---|---|
| A | 3 / 2 / 10 | 800–1,200 | 1 week | 0.5 day | ~10x |
| B-core | 3 / 3 / 10 | 1,500–2,500 | 1.5 weeks | 1 day | ~8x |
| B-runner | 2 / 4 / 12 | 2,000–3,500 | 2 weeks | 1.5 days | ~8x |
| C-stamp | 3 / 5 / 10 (tier 1) | 2,000–3,000 | 2 weeks | 1.5 days | ~8x |
| C-pregate | 3 / 8 / 10 (tier 1) | 2,000–3,000 | 2 weeks | 1.5 days | ~8x |
| D | 0 / 1 / 12 (tier 1) | 600–900 | 1.5 weeks | 1 day | ~8x |
| E | 3 / 4 / 10 (tier 1) | 1,500–2,500 | 2 weeks | 1.5 days | ~7x |
Gate decisions (2026-10-10; owner replied all at 21:30 UTC: every item approved on its recommended option)
Reply grammar: all approves every recommendation; <id><option> overrides one item (d1b uc1a); all except d9b combines them.
User challenges (both reviewers recommended changing the stated direction; the owner accepted the recommendation):
- UC1. Scope of the committed wave. You asked for every section, P0 first. Both the native (Claude) and outside (gpt-6-astra) CEO voices recommend committing P0 now and gating the breadth on adoption evidence, because the premise that tooling gaps (not low perceived value) kept 13 of 28 threads off gstack is unmeasured, and the first effort table was not credible. (a) Recommended: commit the whole stacked wave (seven PRs per D12) with the tiering above; tier 1 of every PR is funded now, tier 3 waits for the P7 number (six of the next ten Capy threads run an unattended workflow a parent consumed). Cost: the full breadth lands weeks later than a single push, and two PRs may ship thinner. (b) P0 only (A, B-core, B-runner, C-stamp, C-pregate), then decide. (c) Everything as first written, no tiers. If wrong about (a): tier-3 items the fleet needed wait one measurement cycle.
Decisions (recommendation first):
- D1. Delivery shape. (a) Recommended: a stacked wave merged in order, install alone first, restamp tool dogfooded on every PR behind it (count per D12) [cost: one full gate per PR]. (b) One PR [one gate, a diff nobody can review]. (c) One PR per section, ten [nine extra gates].
- D2. Unattended as a session kind, not a host. (a) Recommended: a fourth kind
unattended, explicit override only;headlesskeeps BLOCK so no eval case changes meaning; the outside-voice runner is decoupled from host identity (B3) [cost: none beyond B1]. (b) A newhosts/capy.tstarget [one more render of every skill, no behavior behind it today]. - D3. The Capy installer moves into the repo as
bin/gstack-capy-install; the user-scope skill clones first, then runs it [cost: one drive-skill edit after PR A merges]. (b) Keep the logic in the drive skill [untested, invisible to CI]. - D4. Repo policy lives in
.gstack/*.jsonpin files, with command execution opt-in (--allow-repo-commandsor under/ship), read from the committed base-branch copy [cost: one flag in/ship]. (b)gstack-configkeys [per machine, not per repo]. (c) CLAUDE.md prose [the current failure mode]. - D5. The pre-gate blocks by default for lanes it can run; platform lanes it cannot run become
requires-remote; a repo downgrades a named check towarn[cost: a first-run surprise in repos with existing drift]. (b) Advisory by default [the incidents recur]. - D6. Unattended never approves. The gate is written and the run ends. (b) Auto-approve the recommended option as
spawneddoes [a production brain was blocked on exactly this kind of unread decision]. - D7. Eval skill name: (a) Recommended
/eval-plan[measurecollides withship-measure]. (b)/measure. (c)/preregister. - D8. Review severity vocabulary: (a) Recommended keep CRITICAL/INFORMATIONAL and add a forced-CRITICAL list [no collision with Greptile reply tiers or
preamble-tier]. (b) Introduce Tier A/B. - D9. Chromium: (a) Recommended lazy by default through
gstack-browser-ensure,--with-browserto front-load,--only <skill>kept [cost: one helper; first browser skill run pays 86 s]. (b) Eager by default with--lightto skip [every planning machine pays 86 s unless it remembers the flag]. - D10. Contributor-PR mode is on for outside authors by default, emits the table with a direct
acceptpath, maintainer set from the policy'smaintainerslist, else repository push permission (Eng amendment:CODEOWNERSis file ownership, not maintenance authority). (b) Opt-in per repo. - D11. Gate reuse rests on identity, not tree equality alone (tree modulo stamps and generated paths, plus runtime pins, lockfile, CI config, gate command hash); the receipt says
eligibleor names what changed, and the coordinator decides [cost: a slightly longer receipt]. (b) Tree equality alone refuses a second gate [unsound: dependencies and runtime can change under an identical tree]. - D12. PR shape after the Eng sizing. (a) Recommended: seven PRs: A, B-core, B-runner, C-stamp, C-pregate, D, E; B and C split at their natural seams, halves built in parallel and stacked [cost: two more gates than five PRs, each diff one review sitting]. (b) Keep five PRs and accept two review sittings for B and C.
Accepted review amendments
Recorded by the autoplan run in the Review record below; the Implementation plan above already reflects accepted ones.
Review record
Autoplan run log (2026-10-10, base 91bbd9e, Capy cloud machine, no Claude Code harness)
Host: Capy (Claude model, no Skill tool, no AskUserQuestion, no hooks). Session kind: unattended in practice (every question auto-decided on the recommended option and logged; the final gate is written, not approved). Outside voice: Codex CLI, logged in from OPENAI_API_KEY with codex login --with-api-key (6 s), gstack-codex-probe role-ready exec selected gpt-6-astra (3.7 s). Native voice: a Capy subagent on the Claude family reading native-prompt.md with its own file-reading tool. Snapshot tool: ~/.claude/skills/gstack/bin/gstack-autoplan-snapshot.ts (init, methodology, create); restore point .gstack/tmp/autoplan/main-autoplan-restore-20261010-203029.md. Scope: UI no; DX yes (52 term matches, threshold 2). Methodology loaded for CEO: 2,484 lines / 160,290 bytes (the P0-2 incident, measured). Deviations from the Claude Code driver: the native and outside passes ran in parallel against the same snapshot (sha256 2937ab4e…), reconciled below (the plan's B11); the phase-publication-hook guard is not installed on this host, so publication order is unverified (the plan's B1 hook note); review-log rows were not written because this machine's state root is ephemeral (the plan's A3). Owner input arrived mid-phase (Garry's second message) and was folded into the plan before the DX phase as binding decisions.
Timing (UTC, from artifact mtimes): CEO phase 20:30 → 20:45 wall (outside voice 169 s; native 6 min 24 s; amendments 8 min).
Phase 1 CEO review: Step 0
Premise table: the native reviewer checked 27 premises against 91bbd9e: 23 VALID, 1 FALSE as worded (bin/gstack-hermetic-run described as reused; it is new over the existing buildHermeticEnv()), 3 UNVERIFIED (the causal reading of "13 of 28 threads", the effort table, and range-vs-pin on engines.bun). The outside reviewer checked 11: 6 VALID, 4 FALSE (Codex auth supplies every host's outside reviewer; the spec loop requires 7/10; touchfile-closure.ts is a general source-to-test map; tree equality alone is gate evidence), 1 UNVERIFIED (two-minute pre-gate and effort). Every FALSE premise is corrected in the plan above (B3, B8, C8 lanes, C3/D11). Full tables: docs/designs/multi-agent-wave-2026-10-10/ceo-native.md, ceo-outside.md.
What already exists: see Context. Mode: SELECTIVE EXPANSION. Spec Review Loop: not run (no office-hours spec; the plan is its own spec; the CEO voices served as the review).
Phase 1 CEO review: dual voices
CEO DUAL VOICES — CONSENSUS TABLE:
Dimension Claude(native) gpt-6-astra(Codex) Consensus
1. Premises valid? 8 5 DISAGREE → taste (repo facts hold; wave premise unmeasured)
2. Right problem to solve? 8 6 CONFIRMED with caveat (artifact contract is the product)
3. Scope calibration correct? 4 2 CONFIRMED: too wide → UC1 + tiers
4. Alternatives sufficiently explored? 6 2 CONFIRMED: trim-and-measure, unattended kind, post-merge stamping, single schema now weighed
5. Competitive/market risks covered? 5 3 CONFIRMED: contract/schema is the hedge (B0)
6. 6-month trajectory sound? 6 3 DISAGREE → resolved by B0 + P7
CONFIRMED = completed subagent + outside. Both completed. Provider codex, model gpt-6-astra, status completed.
Reconciliation of the parallel passes (each outside finding against the native result):
| # | Outside finding (gpt-6-astra) | Native | Disposition |
|---|---|---|---|
| 1 | One environment treated as a market; adoption unmeasured | 1.1 same | confirmed → UC1, P7 |
| 2 | "Never re-gate an equal tree" unsound (Critical) | 6.3 partial | confirmed → C3 rewritten, D11 |
| 3 | Machine-readable ≠ verifiable; needs run manifest + validator | 2.1 same | confirmed → B0 |
| 4 | Session mode doesn't solve host compatibility; outside reviewer is host-bound | not raised | new → B3 decouples runner from host; Capy dispatch proven by this run |
| 5 | PR A hostage to twelve initiatives | 3.1/4.1 same | confirmed → A split from B; tiers |
| 6 | Queue design manages churn instead of eliminating it; alternatives unweighed | not raised | disagree in part: post-merge stamping rejected (gate must run on the stamped tree, owner requirement); accepted: constrain stamp_paths rewriting, mirror tier 2 |
| 7 | Pre-gate can block the gate it needs (Windows lane) | 3.2 adjacent | confirmed → requires-remote, lanes from workflow files |
| 8 | Pin files acquire undefined authority | 6.2 same | confirmed → trust boundary in C1 |
| 9 | /eval-plan second product; ledger not a hard cap |
4.4 same | confirmed → tiers; ledger honesty |
| 10 | Row compliance ≠ review quality; spec loop 7+ misread | 3.3 adjacent | confirmed → tiers on rows; B8 rewritten (no resampling) |
| 11 | Install verdict lacks intended-readiness definition | 2.3 adjacent | confirmed → --for <skill>, two runtime rows, Bun upgrade |
| 12 | Contributor rewriting undermines maintenance | 6.5 adjacent | confirmed → accept path, maintainer set defined |
| — | (native only) headless redefinition changes eval semantics (Critical) |
6.1 | accepted → unattended fourth kind (B1) |
| — | (native only) gate-ahead merge pollutes squash PRs | 6.4 | accepted → throwaway worktree (C2) |
| — | (native only) lazy Chromium by default; one gate grammar; wrapper clones first; INCIDENTS.md committed | 4.2, 4.3, 6.6, 9 | accepted (A2, B9, A2, Validation) |
Single-voice criticals: native 6.1 (accepted, mechanical: the eval harness sets GSTACK_HEADLESS); outside #2 (accepted, D11).
Phase 1 CEO review: sections 1-11
- Right problem: both voices agree the thesis (machine-readable first) is right and that the artifact contract, not the tool count, is the product. Accepted: B0. 2. Premises: table above; four corrected. 3. Six-month regret: effort table replaced with per-binary estimates tagged
[estimated]; tiers added. 4. Alternatives: four alternatives now recorded (trim-and-measure → UC1;unattendedkind → B1; post-merge stamping → rejected with reason; single schema → B0). 5. Competitive: schema module is the hedge. 6. Uncovered risks:headlesscollision, repo-command trust, receipt residuals, squash history, maintainer set, wrapper bootstrap: all accepted. 7. Scope: tiers; exclusions extended. 8. Promises: P2 reworded forunattended, P3 for identity-based reuse, P7 added. 9. Validation: INCIDENTS.md, squash and residual-path replay cases, lockfile-change case. 10. Gate decisions: D1-D10 re-stated, D11 and UC1 added. 11. Design: skipped (no UI scope).
Phase 1 CEO review: required outputs
NOT in scope, What already exists, Error & Rescue registry (11 rows) and Failure Modes registry (9 rows), dream state delta and Completion Summary: in docs/designs/multi-agent-wave-2026-10-10/ceo-native.md (native) and ceo-outside.md (outside); the plan's "Not in this wave" absorbs the additions. Completion: premises 27+11 checked; findings 2 Critical, 10 High, 9 Medium, 4 Low across both voices; 2 unverified claims remain in the plan (adoption cause, effort), both tagged.
- B0 versioned artifact contract with
run.jsonmanifest andgstack-artifact validate; validation fixtures for interrupted run, missing reviewer, omitted finding. - B1 fourth session kind
unattended(explicit override only);headlesssemantics unchanged; negative control thatGSTACK_HEADLESSstill blocks. - B3 pluggable outside-voice runner (
codex-cli|api|host-subagent), decoupled from host identity; cross-family subagent is a full voice; model recorded. - B8 spec loop at cap: no resample of unchanged input; cheap pass on listed issues or
spec not re-verifiedblock; score shown with missing points. - B9 one gate-list reply grammar; quoted owner replies are parse fixtures.
- C1 trust boundary: repo-declared commands run only under
/ship//land-and-deployor--allow-repo-commands, read from the committed base-branch copy; standalone exit 3. - C2 gate-ahead in a throwaway worktree;
stamp_pathsrewriting limited to exact patterns with a dry-run diff;--afterandmirrortier 2. - C3 receipt classifies against
stamp_paths+generated.json;gate-reuse: eligiblerequires runtime pins, lockfile, CI config and gate command identity; coordinator decides. - C8 pre-gate tiers; lanes and platform tiers derived from workflow files;
requires-remotefor lanes this machine cannot run; per-check time budget printed. - A1
--for <skill>readiness;gstack runtimeandproject pinsrows; range vs exact pins; wrong-host/auth/runtime fixtures. A2 lazy Chromium default,--with-browser, Bun upgrade when satisfiable, wrapper clones then runs. - D1 rows tiered (7 tier 1, 11 tier 2 behind manifest scope). E1
/eval-plantiered; ledger states estimated admission, lock-shared refusal, worst-case overrun. E3 contributor table includesaccept; maintainer set from CODEOWNERS → collaborators API → off. - P7 adoption metric; effort table per binary,
[estimated]and[re-measure before gate];test/fixtures/multi-agent-wave/INCIDENTS.mdcommitted.
Phase 1 close and Phase 2
Phase 1 closed with the amendments above applied to the Implementation plan before the DX snapshot was taken. Phase 2 skipped: no UI scope detected (the plan's "component" means an install component, not a view).
Phase 2.5 DX review (snapshot ffe83107…f11753, product type: developer tool, primary user: an AI agent)
Timing (UTC, from artifact mtimes): 20:45 → 20:54 wall (outside voice 173 s; native 6 min 7 s; amendments 3 min). Both voices ran in parallel against the same snapshot.
Personas traced by both voices: (A) a parent Capy agent installing gstack on a subagent machine, running autoplan unattended and consuming the outputs; (B) a coordinator running "ship, next in queue" for three PRs that reorder twice. Native TTHW: current unbounded (the check cannot be trusted) and nonexistent (needs the harness); target ≈ 25 s to gstack: ok … for=autoplan plus one run and one jq, closed only with an answer step. Outside TTHW estimate for the plan as snapshotted: 45–90 min of manual glue for persona A, 30–60 min plus a gate for persona B.
DX DUAL VOICES — CONSENSUS TABLE (0-10, plan as snapshotted → with amendments):
Dimension Claude(native) gpt-6-astra(Codex) Consensus
1. Onboarding / TTHW 2 → 8 3 CONFIRMED: Bun preflight before setup, lazy Chromium, quickstart
2. Discoverability 3 → 7 3 CONFIRMED: one component table, init scaffolds, show-effective
3. Error messages 4 → 7 5 CONFIRMED: reason codes + anchors + fix clauses
4. CLI / flag consistency 3 → 7 3 CONFIRMED: one result grammar, exit table, --json everywhere
5. Machine-readability 5 → 9 4 CONFIRMED: run-bound ids, referential integrity, envelope
6. Docs / examples 2 → 6 3 CONFIRMED: docs/unattended.md, docs/ship-policy.md, ACCEPTANCE.md
7. Owner gate experience 4 → 8 3 CONFIRMED: stable ids, pagination, auto vs approval, gate_rev
8. Recoverability 2 → 7 2 CONFIRMED: answer/resume, restamp journal, all-or-nothing refusal
Native initial 3.1 → target 7.6. Outside recommendation: revise before implementation. Both completed; provider codex, model gpt-6-astra.
Reconciliation (outside finding → native → disposition): #1 gate reuse can certify changed generated behavior / lockfile metadata (Critical) → not raised natively → accepted, C3 now classifies hunks and permits only version transforms; #2 installer fails before its repair step (setup:296), no jq check, no revision pin → native F8 adjacent → accepted (A2); #3 readiness contradicts runners → native F2 (Critical, two vocabularies) → accepted, component table with required/optional/not_applicable (A1); #4 pins can repair the wrong environment → accepted, --project, per-lane pins, no Bun change on conflict (A1/A2); #5 no unattended invocation or continuation contract → native F1 (Critical, no answer step) → accepted, bin/gstack-autoplan run|status|answer|resume (B2); #6 task file cannot replace the plan → accepted, run-bound ids, deps, acceptance, finding refs (B0); #7 no common transport → native F7/F12/F16 → accepted, GSTACK_RESULT, exit table, --json (B0); #8 state root does not cross the parent–child boundary → native F11 (drive conflicts with policy) → accepted, launch configuration line, durable --out, drive opt-in, ack (A3, B0, B2); #9 no authoritative version-selection rule under reorder → native F3 → accepted, claims ignored, --expect-base/--expect-order, QUEUE_STALE (C2); #10 cross-machine reuse before portable evidence → accepted, minimal gstack-evidence bundle moves into PR C (C3); #11 restamp check/apply semantics and recovery → native F14 → accepted, regen --check|--write, journal, all-or-nothing (C2, C4); #12 base-only pin files have no first-use path → native F13 → accepted, init|validate|show-effective, reviewed bootstrap path (C1); #13 two-minute pre-gate assumes CI info YAML lacks → accepted, two stages, runner-owned selection manifests, --explain (C8); #14 gate can conceal decisions or misread approval → accepted, pagination with stable ids, auto vs approval, gate_rev, no bare yes/no (B9, E3); #15 findings-derived status collides with the freshness reader and the wrapper → native F16 → accepted (B7); #16 tiers do not define merge gates → native F4 → accepted, --after tier 1, ACCEPTANCE.md matrix (C2, Validation 4). Native-only: F5 reason codes with anchors (accepted, B0), F6 ack (accepted), F9 --for over --require (accepted), F10 hook-absent vs hook-failed (accepted, B1), F15 capability sentence (corrected). No disagreements remained after reconciliation.
Required outputs: developer journey map, DX Implementation Checklist (15 items), magical moments (4), competitive benchmark (Claude Code --output-format json, Codex exec JSONL, Capy subagent summaries: gstack's run.json + validator is the only deterministic completion check of the four), empathy narratives and both persona traces are in docs/designs/multi-agent-wave-2026-10-10/dx-native.md and dx-outside.md.
- B2
bin/gstack-autoplan run|status|answer|resumewith--out <dir>; answer validates againstgate_rev, finalizesplan.mdandrun.json.status, never repeats a completed paid phase; acceptance test covers answer and resume. - A1 one component table (
lib/doctor-components.ts); rowsPASS|FAIL|SKIP <id>;--for <skill>prints the required set;--checknever runs the paid probe without--live; healthy Capy + Claude-subagent + no-Codex fixture passes--for autoplan;--project <path>, per-lane CI pins with sources. - A2 runtime preflight before
./setup(Bun, Node, jq); Bun upgrade only when gstack floor and project pin agree;--revision; stale-checkout line; Chromium precedence sentence; planning-only install time measured. - A3
GSTACK_EPHEMERALmarker; durable root opt-in;GSTACK_LAUNCH:configuration line the parent supplies to every command. - B0 run-bound ids across findings/tasks/decisions;
depends_on,acceptance,blocked_by; referential integrity and reviewer-output binding invalidate;GSTACK_RESULT:grammar; exit-code table with--helpcoverage test; reason codes with anchors andfix:for every new error line;gstack-artifact schema|ack|validate --json. - B1 hook-absent renders an inline allow command (
GUARD_NOT_INSTALLED); hook-present-but-broken keeps denying. - B7
findings_total/open/resolved;reviewFreshnessupdated with the writer; rejected row exits 1 with a typed reason. - B9 one grammar (
all,<id><option>,all except <tokens>), no bare yes/no, stable ids across pages,autovsapproval,gate_revbinding. - C1
init|validate|show-effective; reviewed bootstrap path for new policies (policy: head (unreviewed)in the receipt); refusal before any write,nothing written. - C2
--nextignores claims under stamp-at-merge;--expect-base/--expect-orderwithQUEUE_STALE; restamp journal with resume/rollback and injected-failure tests;--dry-run;--queue-modedefault from policy;--aftertier 1,mirrortier 2. - C3 explicit inputs and
--json; hunk-level classification with permitted version transforms only; executed selection and runner image in the identity; minimalgstack-evidence bundlein PR C; fix clauses on refusals. - C4
regen --checkvs--write. C5schema_versionandread --pr. C8 two stages with separate clocks; runner-owned selection manifests;--explain;requires-remotenames workflow, job and clearing receipt. - E3 pagination,
autovsapprovalsemantics. Validation 4ACCEPTANCE.mdmatrix.docs/unattended.mdquickstart anddocs/ship-policy.md.
Phase 3 Eng review (snapshot d7365d92…fd14b17, methodology 2223 lines)
Timing (UTC, from artifact mtimes): 20:54 → 21:18 wall (outside voice 227 s, Codex gpt-6-astra, RC=3 OUTSIDE_STATUS completed; native Claude subagent 11 min 22 s with a verified full read, 230 lines / 67384 bytes / SHA-256 77ee24a4…3511f; reconciliation and amendments 12 min). Both voices ran in parallel against the same snapshot, taken from the plan as amended by the DX phase. Phase order held: Eng ran last.
ENG DUAL VOICES — CONSENSUS TABLE (0-10, plan as snapshotted):
Dimension Claude(native) gpt-6-astra(Codex) Consensus
1. Architecture soundness 6 5 CONFIRMED: right layering; runner, outside voice and doctor table misplaced (F1/F2/F5 ↔ #5/#8/#11)
2. Edge-case coverage 6 4 CONFIRMED: queue/receipt edges strong; recovery, race, privacy, shallow-clone, lane edges missing
3. Test plan 7 5 CONFIRMED: incident fixtures right; add recovery, concurrency, invalidation, portability, stamp-sensitive tests
4. Right-sized diff 4 3 CONFIRMED: B and C are each two review sittings; split at natural seams (D12)
5. Reversibility 8 4 DISAGREE → native position kept with the outside caveat: new kinds/flags/pins are additive, but
journaling alone cannot make arbitrary release commands reversible (#4 accepted into C2)
6. Maintainability under ratchets 5 4 CONFIRMED: ratchet premise wrong (unregistered bins), four zero-headroom skills unbudgeted
Native: revise-then-proceed. Outside: revise before approval. Both completed; provider codex, model gpt-6-astra.
Reconciliation (outside finding → native → disposition): #1 gate reuse confuses permitted edits with equivalent behavior (High; test/agents-digest.test.ts:22, bin/gstack-version-bump:486) → native F14 (per-file-type rules, negative fixture) → accepted, C3 defines equivalence per reusable gate, reruns stamp-sensitive gates, keeps raw and normalized identity, replaces the impossible "changed lockfile, identical tree" fixture; #2 portable bundle lacks data and provenance (bin/gstack-wtree:24, bin/gstack-evidence:23) → native F13 adjacent (branch key) → accepted, C3 compares committed heads only, bundle carries comparison material and producer-bound metadata, refuses reuse on unknown identity; #3 check-to-merge race and integration-tree identity → not raised natively → accepted, C2 binds allocation to base SHA, ordered predecessor chain, PR head and synthetic tree, merge serialized with server-enforced head condition; #4 journaling cannot make arbitrary release commands reversible (bin/gstack-version-bump:475) → not raised natively → accepted, C2 release-tool contract (isolated staging worktree, allowed outputs, pre/postimages, conflict-aware rollback); #5 durable state machine, attempt ids, execution_unknown (bin/gstack-autoplan-snapshot.ts:659) → native F1 (Critical: no verb for the host-dispatched reviewer) → accepted together, B2 is next/submit over lib/autoplan-run.ts with attempt ids, exclusive ownership, EXECUTION_UNKNOWN reconciliation, no automatic redispatch; #6 gate answers can finalize an unreviewed plan (bin/gstack-autoplan-snapshot.ts:777) → not raised natively → accepted, answer --gate-rev rejects stale replies atomically and reopens dependent phases, Eng last, when a choice changes reviewed content; #7 hashing prose cannot detect omitted findings (:677) → not raised natively → accepted, canonical structured findings per reviewer reconciled by id, validator rejects duplicates, cycles, malformed tails, path escapes; #8 runner adapters not equivalent (lib/outside-review-result.ts:4) → native F2 (runtime choice in a render-time resolver; scripts/resolvers/outside-voice.ts:8-12) → accepted together, bin/gstack-outside-voice with one adapter interface, api labeled supplied-input review unless it runs a repo tool loop; #9 spend controls promised in B, implemented in E (scripts/ship-measure.ts:409, test/helpers/pricing.ts:4) → native dependency map agrees (E1 ledger) → accepted, minimal lib/spend-ledger.ts moves to B-runner, host-subagent spend reported unknown, --deadline cancels children; #10 suppressing prompts does not enforce privacy (bin/gstack-skill-start:179, :310; bin/gstack-review-log:99) → native F7 adjacent (kind whitelist, :93) → both accepted, B1 whitelists unattended, enforces at writers and sync entrypoints, tests egress sinks; #11 installer bootstrap and false-success paths (bin/gstack-doctor:22, setup:296, bin/gstack-bun-version.sh:15) → native F5 (table language) and F11 (bun upgrade --version absent on 1.3.14) → all accepted, A1 table generated into sourced bash, floor vs supported minimum, empty host set and unmet --revision never ok; A2 uses setup's checksum recipe; #12 CI parsing and import closure cannot establish lane coverage (test/gstack-doctor.test.ts:25, scripts/test-free-shards.ts:235) → native F15 (bash bins unmapped) → accepted, lanes unions imports, path literals, the bin→test convention and declared dependencies, conservative on uncertainty, persisted requires-remote obligations; #13 pre-gate lacks late-change invalidation (bin/gstack-evidence:394) → not raised natively → accepted, every check records input identity, results must be current after stamping, secrets and required lanes cannot be downgraded, timeout is incomplete; #14 ratchet premise inaccurate, --add-rationale weakens the convention (test/helpers/module-size.ts:202, :225) → native premise correction agrees → accepted, Context corrected, every new module registered, --add-rationale removed (pre-gate prints the entry, author appends); #15 artifact and review-log migration (scripts/resolvers/tasks-section.ts:77, lib/review-evidence.ts:188) → native F8 (exit codes contradict) and F9 (codes do not fit gate-outcomes) → all accepted, B0 tasks v2 enumerated, bindReview validates before consuming the token, lib/result-codes.ts sibling table, B7 exit matrix fixed (usage 2, rejected row 1); #16 --next bypasses the version-source contract (lib/version-source.ts:135) → not raised natively → accepted, C2 reads the configured source at the base revision; #17 hermetic runner does not isolate (test/helpers/hermetic-env.ts:49, :118, :147) → native F16 (bin would import test code) → both accepted, lib/hermetic-env.ts, isolated HOME, GIT_CONFIG_GLOBAL=/dev/null, GIT_CONFIG_NOSYSTEM=1; #18 checklist reduction and fourth spec pass lack an acceptance criterion (scripts/resolvers/spec-review.ts:188) → native F10 (scope reverses the passive manifest, test/qa-lazy-sections.test.ts:127) → both accepted, obligation-to-section map with conservative fallback, B8 pass is deterministic issue verification never promoted to a verdict; #19 P7 unmeasurable (lib/state-root.ts:22) → native F3 (Critical, same diagnosis) → accepted, P7 rides run.json.consumed_by and the ship receipt in merged PR bodies, gstack-ship-receipt census over gh against an owner-maintained cohort; "assign a tier to every item" → already true after the CEO phase, rechecked; #20 automatic issue closure exceeds evidence; CODEOWNERS is not a maintainer list → not raised natively → accepted, C6 closes only on explicit intent (Refs #N otherwise), E3 reads maintainers from policy, else push permission; #21 command contract inconsistent (terminal-line rule vs query commands, exit 1 vs 2, comments in JSON, missing queue_mode key) → native F8 → accepted, B0 scopes the terminal-line rule to workflow commands, C1 schema gains queue_mode with *.example.json; #22 delivery estimate omits work, validation omits typecheck:test (.github/workflows/free-tests.yml:223) → native Scope Challenge and complexity table (B and C two sittings each) → accepted, seven-PR shape (D12), Effort re-counted with LOC, Validation 5 adds typecheck:test, build, format:cso:check, credential scan, quality judges.
Native-only findings: F4 guard clause targets the wrong host (scripts/resolvers/composition.ts:11; Claude Code deny-on-absent must stay) → accepted, B1 rewritten, GUARD_NOT_INSTALLED printed by gstack-skill-start under hosts that do not execute hooks and under unattended, validator requires the line; F6 zero parity headroom on ship/review/autoplan/qa → accepted, Context and Delivery shape carry the measured ratios and each PR's byte-delta acceptance; F12 shallow-clone policy path → accepted (POLICY_SOURCE_UNAVAILABLE, exit 3); F17 Validation 2 tied to Codex → accepted (either runner); F18 pin-file containment fixtures → accepted (C1); F19 Windows classification → accepted (B2 acceptance). Rejected: none. Deferred: the outside architecture's gstack-capy-install → dependency-free bootstrap box is already A2's preflight and needs no new component.
Required outputs: Scope Challenge, architecture diagrams, failure-mode registries (14 outside, 19 native rows), test-plan maps (20 named new or extended test files from the outside voice; folded into B2, C2, C3, C8 acceptance above), size analysis and the per-PR complexity table are in docs/designs/multi-agent-wave-2026-10-10/eng-native.md and eng-outside.md. New decision D12 (seven PRs) is recorded in Gate decisions with the recommended option.
- Context: ratchet premise corrected (registered modules only; new modules registered; parity headroom ratios).
- Delivery shape: seven PRs (A, B-core, B-runner, C-stamp, C-pregate, D, E) with dependency column, byte-delta acceptance, one owner per shared file; Effort re-counted with LOC; D12 added; D1 reworded.
- A1 table generated into sourced bash; no-Bun, empty-host and unmet-revision fixtures; floor vs supported minimum. A2 Bun via setup's checksum recipe.
- B0
lib/result-codes.ts; terminal-line rule scoped to workflow commands; canonical findings reconciled by id; validator rejects duplicates, cycles, malformed tails, path escapes; versionedtasksv2;bindReviewvalidates before consuming the token. - B1 guard line printed by
gstack-skill-starton non-executing hosts and underunattended;unattendedwhitelisted at skill-start:93; privacy enforced at writers and sync entrypoints with egress-sink tests. - B2
next/submitstate machine with attempt ids, exclusive ownership,EXECUTION_UNKNOWN,answer --gate-revwith phase reopening;lib/spend-ledger.tsin B-runner; recovery, concurrency and invalidation tests; Windows classification. - B3
bin/gstack-outside-voiceruntime runner with one adapter interface;apilabeled when supplied-input only. B4scopeoptional/always, obligation map, replace-never-add. B7 exit matrix (usage 2, rejected 1). B8 deterministic issue verification, no fourth launch. - C1
POLICY_SOURCE_UNAVAILABLE, containment fixtures,queue_modein schema, example files. C2 version source via resolver; release-tool contract; race binding and conditional merge; evidence branch key. C3 committed heads only; per-gate equivalence; per-file-type rules; bundle provenance; split lockfile fixtures. C6 explicit closure intent. C8 non-downgradable checks, input identity and invalidation, ratchet entry printed not appended, lanes union and conservative selection, persisted remote obligations,lib/hermetic-env.ts, regen in agit worktree. - E1 consumes B-runner's ledger; E2
verifyover C3's bundle; E3 maintainers from policy or push permission. P7 measured throughrun.json.consumed_by, the ship receipt andgstack-ship-receipt census. Validation 2 accepts either outside runner; Validation 5 addstypecheck:test,build,format:cso:check, credential scan, quality judges.
Phase 4 close (21:18 UTC; gate written, approved by the owner at 21:30 UTC)
Whole cycle: 20:30 → 21:18 UTC, 48 min wall for three dual-voice phases on a 4-core Capy machine, no Claude Code harness. Outside voice total 569 s of Codex gpt-6-astra (three runs, spend not metered by the CLI); native reviewers were Claude subagents on this thread's model, spend unmetered. 65 reviewer findings reconciled (64 accepted, 1 partially accepted, 0 rejected); 13 gate items (UC1, D1–D12), all pending the owner.
Files beside this plan in docs/designs/multi-agent-wave-2026-10-10/: decisions.jsonl (13 rows, kind approval or auto, recommended, gate_rev 1), findings.jsonl (65 rows: phase, voice, model, severity, disposition, plan items, native counterpart), tasks.jsonl (28 rows, schema v2: pr, tier, depends_on, findings, blocked_by the gate), timing.json, run.json (manifest with artifact hashes, status: gate_pending), and the six reviewer reports. These are the B0 file set, written by hand this once because the tooling that writes and validates them is what PR B-core builds; their shapes are the v1 contract B0 freezes.
Deviations from the skill as written, stated rather than implied: the Spec Review Loop (B8's subject) was not run on this plan; no review-log JSONL rows were written because the state root on this machine is ephemeral (A3's subject) and nothing durable was configured; the phase-publication-hook guard was not enforced because this host executes no hooks (B1's subject), so publication order rests on the snapshot hashes recorded per phase; every mid-run question was auto-decided on the recommended option and logged in the phase records. The final gate below is the one decision the run does not take.
Gate: the owner replied all ("Take recommendations and approve"), so UC1 and D1–D12 are decided on option (a). Nothing reviewed changed, so no phase reopens. Implementation starts with PR A and PR B-core in parallel on fresh machines (disjoint files; A owns bin/gstack-doctor, setup, bin/gstack-capy-install; B-core owns lib/headless-artifacts.ts, bin/gstack-skill-start, bin/gstack-session-kind, bin/gstack-review-log), B-runner and C-stamp stacked on B-core.