Files
gstack/docs/unattended.md
Garry Tanandcapy-ai[bot] 9d9d4c31bb v1.93.0.0 wave(browse): #3700 #3773 #3779 #3817 #3842 (#3858)
* feat(loadouts): Opus 5.5 lite loadout with explicit model detection (GSTA-161)

modules/loadouts.json gains the lite loadout (eleven coaching preamble
pieces dropped; upgrade-check and brain-sync kept) and a models map that
sends only claude-opus-5-5 (and its dated snapshots) to it. Detection is
explicit: Capy passes --model to the installer, Claude Code opts in with
./setup --loadout lite; anything unmapped, invalid or unreadable serves full.
The checkout always stays the full render.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(loadouts): Opus 5.5 lite, detection order, Capy SKILL_ROOT, setup --loadout; troubleshooting anchors; changelog fragment (GSTA-161)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(loadouts): claude_loadout read through CONFIG_BIN; capy skill context ceiling for the --model line (GSTA-161)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (prune keeps a reused loadout folder, GSTACK_LOADOUT_SKILL_ROOT, claude_loadout guard test) (GSTA-161)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (Capy skill passes the model as GSTACK_MODEL so an older installer ignores it) (GSTA-161)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (prune only owned, unused folders; full root from the canonical Claude install; fallback reason and source) (GSTA-161)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: loadout source example, install and resolve commands, Capy launch-file note (GSTA-161)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(autoplan): --ceo-only runs the CEO review alone with zero questions (GST-537)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: classify autoplan-ceo-only in the Codex host metadata

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: select autoplan-ceo-only on the skills it runs; say it needs Claude Code

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: run autoplan-ceo-only in the marathon lane

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: changelog entry for /autoplan --ceo-only

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: count the autoplan-ceo-only paid file in the shard inventory

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat: gstack-autoplan next --ceo-only runs the CEO review alone and ends reviewed (GST-537)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: budget and close recover a budget-stopped CEO-only run; reviewed is at rest for checkpoints

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: CEO-only finish leaves owner-only cap items undecided, refuses marker phase flags and a plan at the report path, and replaces ceo-review.md atomically

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: the CEO-only report-path guard compares real paths and refuses at the finish when the report file is the plan

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: CEO_ONLY_CONFLICT covers a plan linked to the report path

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(browse): failing-first device and touch tests (GSTA-227 F3, #3058)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: the report-path guard keeps the lexical check, skips safe hard links and compares bigint file ids

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* browse: per-session tabs for parallel agents; tab pins stop moving the shared active tab (#1961)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* browse: keep the server.ts tab-pin anchor comment for source guards

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(browse): per-tab phone emulation and touch input ($B device, tap, swipe, pinch, touch; #3058, GSTA-227 F3)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: copy lib/result-codes-autoplan.ts into the fixtures that copy the result-code tables, and keep it out of derived closures

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(qa, aside): touch pointer in /qa, Aside touch recipe verified on Aside 1.26, report Phones row (GSTA-227 F3, #3058)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* browse: drop the sessions paragraph from /browse to stay inside the parity cap

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* changelog: F3 phone emulation and touch entry

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* browse: hide the ps window in the session harness walk

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: the decisions.jsonl status list names auto_kept

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(browse): session cap, LRU eviction, headed release and scoped auto-session rows

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* browse: sessions keep tab <id> picks, sidebar claude stays on the user's tab, pinned rebuild and state load rebind the scope

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (touch cancel with no finger held is a no-op)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: Step 11 review fixes (headed tab adoption, pinned viewport reset, CLI retry keeps --session, rebuild id remap, --json notice, evicted-session reset, device kept after annotated captures, render opts out of sessions, session: client ids reserved)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: headed viewport auto keeps the user's active tab; render's closetab opts out of sessions

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: sessions, phone emulation and touch in BROWSER.md; session: client ids reserved; TODOS #1961/#3058 shipped

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: copy lib/result-codes-browse.ts into the fixtures that copy the result-code modules

lib/result-codes.ts imports ./result-codes-browse, so fixtures with an explicit
lib list failed with 'Cannot find module'. pr-dependencies lists it with the other
reason-code modules as selection data. GSTA-227

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat: gstack-clean reclaims per-repo .gstack scratch and logs, dry run by default (#2627)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* browse: name the Aside touch recipe's coordinates <tap-x>/<tap-y> with a numeric grammar

The generated-bash lint (INV-3) rejected the free-text <x>/<y> placeholders. GSTA-227

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(clean): leave hard-linked and unreadable logs alone; cut through a no-follow descriptor checked against the evaluated inode

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(clean): tests that expect tmp removal see a quiet ps, so a codex elsewhere on the host cannot flake them

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(clean): an unlistable tmp/ skips its group; liveness asked once per group on apply; BROWSER.md says which logs reset at daemon start

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(clean): write output synchronously so --json reaches a pipe whole; test fixes for root, wrapper git and the ps stub

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(clean): every change rechecks the verified folder and the measured file; full writes; EPIPE ends output quietly

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(clean): the early-close test checks the bin's own exit status

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: list gstack-clean among the standalone CLIs

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(clean): copied-install fixtures and the dependency list carry lib/result-codes-clean.ts

The remote pregate failed 11 cases in gstack-doctor, plan-tune-cathedral and
timeline-stop-hook-slug: each copies an explicit list of lib/result-codes-*.ts
files into a temp install, and lib/result-codes.ts now imports
./result-codes-clean. Same lists GSTA-168 extended for result-codes-eval.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: regenerate gstack-doctor-components.sh for the gstack-clean result codes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* qa: fold the touch pointer into the Phases sentence to fit the parity size cap after the main merge

qa's union was 95988 bytes (ratio 1.142 > maxSizeRatio 1.142, train 75). Now 95975 (1.1419). GSTA-227

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: #3058 troubleshooting 'Fixed in' lines name v1.92.10.0 after the main merge

GSTA-227

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(clean): classify gstack-tasks rows files (tasks-*) as kept, after main added the producer

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: #3058 troubleshooting 'Fixed in' lines name v1.92.20.0 after the main merge

GSTA-227

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (restore the lib/result-codes-plan.ts ratchet row the base merge dropped)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(clean): the producer scan ends a name at a colon, so prose after a path names the file (train 78)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: #3058 troubleshooting 'Fixed in' lines name v1.92.30.0 after the main merge

GSTA-227

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* outside-voice-api: caller receipts, stop_reason, OpenAI max_completion_tokens

A caller can write its own egress receipt (sink, consent, payload class);
the default stays the outside-voice receipt, so existing callers send and
record the same bytes. The result carries the provider's stop reason, and
an OpenAI request gets max_completion_tokens only when the caller sets an
output limit (GST-537 PR 3).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* autoplan gate: item classes and the owner's answer provenance

Gate items carry item_class, ruleKept() names the keep-direction pick, and
answered decision rows record owner_mode, owner_named, rec_hash and
answered_role parsed from the owner's own words (GST-537 PR 3).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* autoplan: shadow-mode decider module, frozen prompt and spend accounting

The switch (off by default, fixed at init), the lease, one model call per
gate with a page retry and a truncation retry under one deadline, the
once-per-revision record with its journaled sha256, the 10% audit mark,
and shadow-labeled ledger rows that count against the cap, show as
shadow= on GATE_SPEND and stay out of the per-voice price (GST-537 PR 3).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* gstack-autoplan: shadow every published gate, the shadow export, config and docs

Every driver command that prints a gate records the shadow first and
checkpoints after it; answer waits for a live lease and records a dead
one retrospectively; gstack-autoplan shadow exports answered revisions
for taste-replay R2 (SHADOW_BEFORE_ANSWER otherwise); gstack-config
autoplan_shadow / autoplan_shadow_model; gstack-artifact validate checks
the record's hash (GST-537 PR 3).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* changelog: shadow mode for the CEO-only decider (GST-537 PR 3)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* autoplan shadow: /ship review fixes

- register test/fixtures/shadow-fake-model.ts in FREE_FIXTURES (free-fixtures inventory failed)
- withRunLock: count dead-owner reclaims apart from waitMs iterations, so a long wait can still reclaim
- buildPage: fill the decider template in one pass, so slot names inside the plan stay plan text
- revise regate: the shared publish step prints the GATE and ESCALATE lines once

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* autoplan shadow: reply files never follow a link; the retry reserves against the current cap

- a model reply is written by temp file and rename, so a link at the reply path is replaced, not written through
- each shadow reservation reads the run's current spend cap, so a budget change during the call is not undone

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* outside-voice-api: write the egress receipt under the caller's env

callModel resolved the receipt's state root from process.env even when the caller passed env, so the shadow
tests wrote to the real ~/.gstack (the free-suite home-containment guard caught it). The receipt now uses
req.env; the shadow tests give the fake model a private HOME, GSTACK_HOME and GSTACK_STATE_ROOT, and the receipt
test no longer sets process.env to pass.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(autoplan): CEO-only runs leave main's open Critical/High finding items to the owner (no decision row)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(autoplan): shadow pages carry main's finding items; a finding item is never ceo_eligible

After merging #3767 (main 90a170c), open Critical/High findings are gate items
(item_class finding). The shadow records one row per gate item, so its pages now
include them. A CEO-only run leaves those items to the owner, so their rows are
ceo_eligible false (rule_pick was already null). The shadow fixtures follow
main's gate (items, counts, "Do it" tokens); the two merge adapters follow
main's doNext(out, rest, price) and its per-voice reservation (--estimate-usd).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: #3058 troubleshooting 'Fixed in' lines name v1.92.61.0 after the main merge

GSTA-227

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(investigate): drop the five-phase step procedure (GST-938)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* changelog: GST-938 fragment wording

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(investigate): keep the web-search sanitize guard; update BROWSER.md and docs/skills.md (review pass 1)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* changelog: GST-938 wording

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(investigate): pin the web-search guard; changelog size (adversarial review)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(clean): classify restamp-staging.json, the restamp producer main gained after #3779's base (wave browse)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (wave browse): scoped newtab keeps a concurrent tab switch; shadow validate catches a deleted record; a failed shadow publish is journaled

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: a failed shadow publish journals under the run lock and never throws (wave browse review pass 2)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(unattended): shadow publish failures and the missing-record validate check (wave browse docs audit)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* changelog: wave browse integrator fixes fragment

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: shadow reader dedupes by run folder, so two runs started in the same second both count (wave browse CI free-suite 16)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: regenerate doctor result anchors in generator order (wave browse rebuild without #3818)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* changelog: name the wave fragment after the rebuilt wave

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>
2026-11-02 14:49:46 +00:00

899 lines
58 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Unattended runs: the artifact contract
A gstack skill runs *unattended* when a parent agent starts it with
`GSTACK_SESSION_KIND=unattended` and reads its artifacts instead of its prose.
This page is the contract between that parent and the run: what the session
kind changes, which files a run writes, how the parent decides whether the run
finished, and how it reads the result without reading any markdown.
The session kinds are: `interactive` (a human answers prompts), `headless`
(`GSTACK_HEADLESS`, `CI`, the eval harness; a question BLOCKs), `spawned`
(an orchestrator subagent; the recommended option is auto-chosen) and
`unattended` (this page). `unattended` is honored only as the explicit
`GSTACK_SESSION_KIND=unattended` override, which outranks `GSTACK_HEADLESS`
so a parent can drive a run inside CI. Nothing in the test or eval harness sets
it, so `headless` keeps its BLOCK semantics.
## What the session kind changes
`gstack-skill-start` prints `SESSION_KIND: unattended`,
`UNATTENDED_SESSION: true` and the `unattended-session` instruction block
(`GSTACK_INSTRUCTION_BEGIN: unattended-session <session-id>` through
`GSTACK_INSTRUCTION_END`), then applies these rules for the whole skill:
- **No prompts.** The update check, upgrade offer, telemetry and sync consent,
onboarding tips and routing blocks are suppressed exactly as they are for
`spawned`. An unanswered consent is `off`.
- **No publishing or sync, enforced at the writers.** Usage analytics are not
written (`TELEMETRY_WRITE: skipped (unattended session)`), the artifacts
repo is neither pulled nor pushed (`ARTIFACTS_SYNC: skipped (unattended
session; mode=<m> queue=<n> retained)`), the sync spool is not fed
(`gstack-brain-enqueue` is a no-op, and `gstack-review-log` says `review-log:
sync skipped (unattended session)` on stderr), and `gstack-skill-end` drains
nothing. Inherited enabled settings and pending queue work are left for the
next human session. The upgrade offer becomes one line at skill end, read from
the local cache: `UPGRADE_AVAILABLE: <version>; run /gstack-upgrade` (`<version>+<n> (main at <sha>)` when
main moved <n> commits past the checkout without a new version).
- **Questions decide themselves, gates do not.** A question with a
`(recommended)` option takes it and appends the choice to `decisions.jsonl`
with `kind: "auto"`. A question with no recommendation, any consent, any
destructive or irreversible option, and the final approval gate are appended
with `kind: "approval"` and `status: "pending"`. The skill ends
at its next gate with the pending list rendered; it never approves and never
publishes.
- **State root durability is reported.** `STATE_ROOT: <path> durable=yes|no`
(`no` when the host sets `GSTACK_EPHEMERAL=1`). On an ephemeral root the run
says `learnings: skipped (state root is ephemeral; set GSTACK_STATE_ROOT)` and
`timing: analytics skipped (...)` instead of writing to a disk that vanishes.
Point `GSTACK_STATE_ROOT` at a durable directory when you want cross-run
history (see [state-root.md](state-root.md)).
- **The autoplan publication guard is named, not faked.** On hosts that do not
execute Claude Code hooks, and in every unattended session, `gstack-skill-start`
prints `autoplan guard: not enforced by this host; publication order is
unverified (GUARD_NOT_INSTALLED)` once. `gstack-artifact validate` requires
that line in an unattended run's `review-record.md`. Claude Code's
deny-on-absent behavior is unchanged.
An unknown `GSTACK_SESSION_KIND` value coerces to `interactive` and says so:
`SESSION_KIND: interactive (unknown kind '<x>')`.
## The terminal line
A run that writes a record ends with one line the parent greps:
```
GSTACK_RESULT: skill=<name> status=<status> run=<dir>
```
`status` is one of `complete`, `reviewed`, `gate_pending`, `incomplete`,
`interrupted` or `refused`; `run` is where the run's record is. `/autoplan`
always ends `gate_pending` when it reaches the final gate, because the gate is
written and never approved. `/autoplan --ceo-only` ends `reviewed`: it runs the
CEO phase alone, asks nothing, writes no gate and approves nothing (see
[CEO-only runs](#ceo-only-runs)). Pure queries (`gstack-artifact schema`,
`gstack-gate render`) print no result line.
Not every workflow writes this line or a run directory today:
- **`/autoplan`** writes the full run directory below through `gstack-autoplan`,
and `gstack-artifact validate` checks it. It is the only skill with steps
and helpers that write `run.json`, `decisions.jsonl` and `gate.json`.
- **Workflow commands** `gstack-pregate`, `gstack-regen`, `gstack-restamp` and
`gstack-ship-queue` print their own line, with `run=` naming their own record
(the pregate receipt, the regen worktree, the restamp journal, the queue
record), not a run directory.
- **A refused launch.** Any skill whose install check refuses it at startup
prints `GSTACK_RESULT: skill=<name> status=refused run=-` and stops.
- **`/ship`** ends with `gstack-ship-receipt result --pr <n>` (or `--body
.gstack/tmp/ship-receipt.md` after the handoff section), which binds the
receipt to the branch head, writes the run record and prints
`GSTACK_RESULT: skill=ship status=complete run=<dir>`. That line and that
record are the machine-readable READY evidence; [What /ship leaves for a
parent](#ship-evidence) below is the whole contract (the refused and
incomplete lines, the one publication an unattended run may make, the
`review_loop` counters, the outside voice on Capy and the `CARRY:` lines).
- **Every other workflow skill** (`/review`, `/qa`, `/design-review`,
`/spec`, `/land-and-deploy` and the rest) gets the session rules above (no
prompts, approvals stay pending, nothing published), but has no step that
writes a run directory or prints the line, so a parent should not wait for
either. `/eval-plan` asks for a `gate_pending` line at its spend gate, but no
helper writes its `gate.json` yet (`gstack-gate` renders and parses a gate
list; it does not create one). `/cso` skips the shared startup, so these
session rules do not reach it at all. Read each skill's own record instead:
the review-log rows (`gstack-review-read`) for `/review` and the
`qa-report-*.md` file for `/qa`. For a `/ship` run from before the result
line, read the receipt instead: when the run did not push (an unattended run
must not), read the receipt file in the child's checkout with
`gstack-ship-receipt read --body .gstack/tmp/ship-receipt.md --json`;
use `gstack-ship-receipt read --pr <n> --json` only once a PR exists. In both
cases compare the receipt's `head` to the branch SHA (`git rev-parse HEAD` in
the child's checkout): `read` does not check that the receipt is current, so
an older PR's receipt reads as valid; `result` does.
Every workflow command shares one exit table: `0` ok, `1` fail, `2` usage,
`3` refused or needs a flag. Error lines end with a result code in parentheses,
for example `findings.jsonl:12: ... (ARTIFACT_SCHEMA)`; each code has an anchor
in [troubleshooting.md](troubleshooting.md#unattended-runs-and-artifacts) with
a `fix:` clause. Grep the code, not the prose.
<a id="ship-evidence"></a>
## What /ship leaves for a parent
One section for everything a parent (a Captain, a crew thread, a merge queue)
reads after an unattended `/ship`: the READY evidence, the review loop's
counters, the outside voice's record, and the carry across a base merge.
### The READY evidence: `gstack-ship-receipt result`
`/ship` Step 22 runs `gstack-ship-receipt result --pr <n>` (or `--body
.gstack/tmp/ship-receipt.md` after the handoff section). It binds the receipt
in the PR body (or the handoff file) to HEAD, writes
`<state root>/ship/runs/<utc>-<head12>/` and prints the terminal line:
```
GSTACK_RESULT: skill=ship status=complete run=<dir>
```
The run directory holds `ship-receipt.md` (the block, copied) and
`ship-run.json` with `head`, `pr`, `base`, `version` (`"none"` for a
repository with no version source, [ship-policy.md](ship-policy.md#ship-receipt)),
`queue_mode`, `session_kind`, `receipt` (the copied block's path) and
`receipt_source`. It is a /ship record, not the `run` v1 manifest below, so
read it directly; `gstack-artifact validate` does not apply. A parent's merge
check is then mechanical: take `run=` from the last result line, read
`ship-run.json`, compare its `head` to the PR head, and quote the
`SHIP_RECEIPT: pr=<n> head=<head12> …` line in the READY block.
`gstack-ship-queue ready`, `train build` and `verify --train` make the same
comparison from the PR body ([ship-policy.md](ship-policy.md#ship-before-ready)).
The other endings, each greppable: when gh cannot read the PR,
`GSTACK_RESULT: skill=ship status=incomplete run=-` (exit 1); a receipt that is
missing or names another head, `GSTACK_RESULT: skill=ship status=refused run=-`
with `QUEUE_RECEIPT_MISSING` or `QUEUE_RECEIPT_STALE` on stderr (exit 3), the
line a run that stops before publication prints itself; `result --pr <n>`
refuses `QUEUE_RECEIPT_STALE` when HEAD is not the PR's pushed head; and a
receipt with a `base_red` block (Step 5 paused on a red base) prints
`SHIP_PAUSED: base-red …` on stderr, writes the run record and ends
`GSTACK_RESULT: skill=ship status=incomplete run=<dir>` (exit 1): the lane is
waiting on the base, not failed ([base_red](ship-policy.md#base-red)).
The one publication the unattended rules allow: when an open PR for the branch
exists and HEAD is its pushed head, Step 18 takes the pr-body section's
existing-PR path and posts the body (receipt first) and title through
`gstack-post`. It never runs `pr-create`, never pushes a commit and never runs
`gh pr ready`; a branch with no open PR, or whose PR head is not HEAD, takes
the handoff section. When the run committed anything (a CHANGELOG entry,
generated tests), the parent pushes those commits itself, then reruns the
receipt write, the body post and `result` at the pushed head: a receipt for an
unpushed head is `QUEUE_RECEIPT_STALE` to the queue.
### The review loop's counters
The receipt's `review_loop` object, written from `gstack-review-scope summary
--since <REVIEW_SINCE>`, is the loop's machine-readable outcome: `passes`
(Step 9 full passes this ship ran, 1 or 2; up to 3 under `review_scope: all`),
`blocking_found` (every blocker the readers found, rows and dispatch ledgers
both, [review-policy.md](review-policy.md#what-the-receipt-records)), `deferred`
(blocking findings in unchanged code filed as issues, Step 11's late defers
included), `noted`, `fixed` and `scope`. The
summary line a parent reads prints it as `passes=<n> blocking_found=<k>
deferred=<m>`; the PR body lists the deferred issues under `## Deferred
findings` and the below-floor items under `## Review notes`. A pass that finds
nothing is `NO FINDINGS` from every reviewer and a pass with no edits, which
is the loop's exit, never a failed review ([review-policy.md](review-policy.md)).
Three counters appear only when the run recorded them: `fix_checks` (fix-check
rows, never passes), `dispatches` (reviewer dispatches by role) and
`est_input_tokens` (prompt plus diff bytes the dispatches read, / 4, a relative
estimate and never a bill).
### Report length
A parent reads many lanes, so length is a cost it pays per lane. A READY block
holds its required lines plus at most 150 words; a completion report holds at
most 300 words; a subagent brief names the files to read instead of pasting
them. Evidence goes in files the report points to. Waits go on operations,
never on polling wakes.
### Token caps and fresh-context handoffs
A parent bounds a /ship run with `--token-cap <n>` (or `GSTACK_SHIP_TOKEN_CAP`).
It is an admission cap: /ship meters usage, reserves every step and dispatch
batch before it starts, and stops `status=incomplete` with a `RECOVER:` line
instead of starting work that would cross the cap. Long runs hand Steps 9–11.5
and 12–22 to fresh-context children. The flow, the BUDGET line and the result
codes are in [ship-budget.md](ship-budget.md).
### The outside voice on Capy
Step 11's outside adversarial review prefers the Codex CLI. On a Capy machine
the Codex CLI is normally absent (selecting a model in Capy installs and
authenticates no CLI), and the accepted outside voice is then a subagent
running a different model family from the parent: a full outside voice, not
degraded coverage, as long as the model that reviewed is recorded. Its
review-log row is the same row every Step 11 pass writes
(`ship/sections/adversarial.md`): `skill: "adversarial-review"`,
`phase: "adversarial"`, `source` naming the runner that actually reviewed
(the outside provider that reviewed, with the model recorded, for the
cross-family subagent; `codex` for completed Codex CLI output, as
`ship/sections/adversarial.md` prescribes),
`outside_provider` (the provider the host would have used, as the generated
prose names it), `outside_status` (`completed` for a finished outside pass;
`unavailable` when neither the CLI nor a subagent completed, the cause on the
`gstack-outside-voice run:` stderr line and in `<out>.meta.json` rather than
in the row; `disabled` under
`codex_reviews: disabled`), `status`
(`clean`, `issues_found` or `unavailable`), `completed`, `converged`, and the
subagent's model recorded with the pass (`gstack-outside-voice run --runner
host-subagent --result <file> --model <id> --prompt <file> --out <file>` binds
it; the row's text names it where the host reports it). The native in-host adversarial
subagent writes its own row with `source: "in-host"`; native completion never
credits outside coverage. A parent reads the rows with
`gstack-review-read` and treats `outside_status: unavailable` as missing
coverage, not as a clean outside review.
The pass program (`lib/capy-review-pass.js`) picks the outside voice's model
when `input.models['outside-voice']` is empty and the lane model is
`anthropic/`: `openai/gpt-6.1-sol` at medium reasoning, the pairing the no-loss
rounds ran. Any other lane must name a model. An outside voice from the same
provider as the lane model or the native voice stops the pass (`STOP:`) before
either voice is created, including on a rerun, and each outside voice prints an
`OUTSIDE_VOICE:` line saying whether it is the certified configuration.
### The carry across a base merge or a test-only fix
When the parent merges the base in or fixes a test after the receipt was
written, it runs `gstack-ship-receipt carry --pr <n> --base <base> --dry-run`
instead of another /ship; on `CARRY: would carry` it runs `gstack-pregate` at HEAD
and reruns carry with `--pregate .gstack/tmp/pregate.json`. `CARRY: carried from <old> to <new> (<reason>)` plus the
`GSTACK_RESULT: skill=ship status=complete run=<dir>` line means the receipt
now names HEAD with a `carry` object holding the `git range-diff` evidence, and
`ship-run.json` gains `carried_from` and `carry_reason`; the `review_loop`
counters are kept, because the reviewed product diff is unchanged or its only
difference passed a scoped review. `CARRY: current` means nothing moved.
`CARRY: refused (RECEIPT_CARRY_SCOPED_REVIEW)` means the base edited context
next to reviewed hunks. Carry without `--dry-run` writes the packet the
refusal names (a dry run writes none), one fix-check reviewer reads it, and a
clean record reruns carry with `--scoped-review <record.json>`
([troubleshooting](troubleshooting.md#receipt-carry-scoped-review)). `CARRY: refused (RECEIPT_CARRY_PRODUCT_CHANGED)`,
`(RECEIPT_CARRY_TREE_MISMATCH)` or `(RECEIPT_CARRY_REFUSED)` with the refused
result line means the product diff changed, or history was rewritten onto a
tree no review saw, so /ship runs again;
`(RECEIPT_GIT_TOO_OLD)` means the machine's git predates 2.39 and cannot run
`patch-id --verbatim` ([ship-policy.md](ship-policy.md#receipt-carry)).
## The run directory
`/autoplan` writes these files; the next section shows how a parent reads them.
| File | Schema | Contents |
|------|--------|----------|
| `run.json` | `run` v1 | The manifest: `run` id, `status`, `required_phases`, each phase's `native` and `outside` outcome (`status`, `model`, `provider`), input `snapshot` hashes, `counts`, every artifact's `path` (relative to the run directory) and `sha256`, `session_kind`, `gate_rev`, `deviations`, `consumed_by`. |
| `decisions.jsonl` | `decisions` v1 | One row per decision: `id` (`<run>-<label>` at gate revision 1, `<run>-r<rev>-<label>` after a reopen, since labels restart at each gate), `title`, `options`, `recommended`, `kind` (`auto`, `approval`, `user_challenge`), `status` (`pending`, `approved`, `overridden`, `rejected`, or `auto_kept` in a [CEO-only run](#ceo-only-runs)), `gate_rev`, and once answered `chosen`, `answered_at`, `reply`, plus how the owner answered: `owner_mode` (`rec`, `rec_except`, `tokens`, `as_is` or `unparsed`, parsed from the owner's own words), `owner_named` (his words named this item), `rec_hash` (the recommendations answered against) and `answered_role` (`human`, or `agent` relaying his words). |
| `shadow/` | private, not an artifact | [Shadow mode](#shadow-mode) only: `ceo-only.r<rev>.jsonl` (one hidden prediction per gate item, written before the answer), `r<rev>.prompt.md` and one `<call>.reply.txt` per model call, the audit `salt`, and `r<rev>.pending` while a call is in flight. Never in `export --public`, the gate render or the owner brief. |
| `findings.jsonl` | `findings` v1 | One row per reviewer finding: `id`, `phase`, `voice` (`native`/`outside`), `model`, `severity`, `title`, `disposition`, `plan_items`, `native_counterpart` (string or null), `source`. Rows share the review-log row shape, so `gstack-review-log --findings` derives review status from this file. |
| `tasks.jsonl` | `tasks` v2 | Implementation tasks with `id`, `title`, `status`, `depends_on`, `findings` (ids in `findings.jsonl`), `blocked_by`, `acceptance`, optional `pr`/`item`/`tier`. |
| `timing.json` | `timing` v1 | Per-phase `started_at`, `ended_at`, `wall_s`, `outside_s`, `native_s`; `started_at` and `total_wall_s` for the run; the Phase 0 `estimate`. |
| `plan.md` | prose | The `## Implementation plan` section, with `## URGENT, outside this plan` written above it. |
| `review-record.md` | prose | The review record and decision audit trail; carries the `GUARD_NOT_INSTALLED` line in an unattended run. |
| `gate.json` | see `gstack-gate --help` | The final gate list `/autoplan` renders and parses. |
Ids are run-bound: every row's `id` starts with `<run>-`, and `run.json` is the
only place the run id is declared. `gstack-artifact schema <name>` prints the
JSON Schema for any of `findings`, `decisions`, `tasks`, `timing`, `run`,
`pregate` and `ship-receipt`; `schema --list` prints the names.
## How a parent consumes a run
1. **Decide whether the run finished.** For an `/autoplan` run directory,
`gstack-artifact validate <dir>/run.json`
is the deterministic completion check (a `/ship` `run=` directory holds
`ship-run.json`, read directly as described above). It exits `0` and prints
`ARTIFACT_VALID: run=<id> status=<status>` only when the manifest parses and
matches its schema, every required phase and each reviewer voice has a
terminal outcome, every listed artifact exists inside the run directory with
the recorded hash, every JSONL file parses row by row against its schema with
run-bound unique ids, task dependencies and finding references resolve
without cycles, `counts` agree with the files, and (unattended) the guard
line is present. Otherwise it exits `1` with one error line per problem and
`ARTIFACT_INVALID: <n> error(s)`; `--json` returns the same as
`{ valid, run, status, run_dir, errors[] }`. A run that was interrupted shows up here as
`ARTIFACT_RUN_INTERRUPTED`, never as a partial success.
2. **Read the terminal line.** `grep '^GSTACK_RESULT:' <log> | tail -1`. With
`status=gate_pending`, the gate is in the artifacts; nothing was approved.
3. **List what is pending.**
```bash
jq -c 'select(.status=="pending")' <dir>/decisions.jsonl
gstack-gate render <dir>/gate.json --all # the numbered list and reply grammar, every page
```
4. **Answer the gate** by composing the reply grammar (`rec`, `as-is`,
`<id><option>` tokens such as `d3b uc1a`, or `rec except <tokens>`; bare
yes/no is rejected) and feeding it back with the gate revision you read:
```bash
gstack-gate parse <dir>/gate.json --reply "rec except d3b" --gate-rev 1 --json
gstack-gate decisions <dir>/gate.json --reply "rec except d3b" --gate-rev 1 >> <dir>/decisions.jsonl
```
A stale revision is `GATE_REV_STALE` and a reply with unparsed tokens is
`GATE_REPLY_UNPARSED`; both exit `1` and write nothing.
5. **Read findings and tasks without the prose.**
```bash
jq -c 'select(.severity=="Critical" and .disposition!="accepted")' <dir>/findings.jsonl
jq -r '.id + "\t" + .title' <dir>/tasks.jsonl
gstack-artifact urgent <dir>/findings.jsonl # the URGENT block, or "None."
```
6. **Acknowledge consumption** so a second parent (or a retry) can tell the run
was already read: `gstack-artifact ack <dir>/run.json --consumer <id>` is
idempotent and records `consumed_by` in the manifest.
## Driving `/autoplan` from a parent: `gstack-autoplan`
On a host where the parent dispatches the reviewers itself (Capy, any
orchestrator without a Claude Code harness), `bin/gstack-autoplan` is the
explicit, durable state machine for the whole run. The parent never reads the
review record; it runs a loop over two commands and reads the printed lines.
A parent on any host reads `autoplan/DRIVER.md` (its path is printed as
`DRIVER:` by `gstack-autoplan --help` and the first `next`) instead of
autoplan/SKILL.md: the phase order, the artifact contract and the gate protocol
in about a hundred lines, generated from the code.
```bash
AP=bin/gstack-autoplan
$AP next --out <dir> --plan <plan.md> [--ui|--no-ui] [--developer-tool] [--no-dx] [--light] [--spend-cap <usd>]
# PHASE: ceo snapshot=<sha> order=ceo,design,dx,eng
# SCOPE: ui=yes (...) dx=yes (detected: ...) source=ui:detected,dx:detected
# DRIVER: <root>/autoplan/DRIVER.md
# Skipped sections: none (scope: ui=yes,dx=yes,source=ui:detected,dx:detected; checklist=loaded)
# ATTEMPT: <run>-a1 phase=ceo voice=native prompt=<dir>/ceo-native-prompt.md result=<dir>/ceo-native.a1.md model_family=...
# ATTEMPT: <run>-a2 phase=ceo voice=outside prompt=<dir>/ceo-outside-prompt.md result=<dir>/ceo-outside.a2.md model_family=differs from the native reviewer
# SPEND: spent=0.00 reserved=0.00 unknown=0(0.00) worst_case=0.00 cap=none
# GSTACK_RESULT: skill=autoplan status=awaiting_result run=<dir>
```
The first `next` prints `SCOPE: … source=ui:<flag|marker|detected>,dx:<…>`
and, when CLI flags differ from the plan's marker, `SCOPE_PIN: add <!--
gstack-autoplan-scope: … --> as the first line of <file>`; gstack never writes
the plan.
1. **Dispatch each `ATTEMPT:`.** The native voice is a subagent on the parent's
own model given the prompt file; the outside voice is a subagent on another
model family, or `bin/gstack-outside-voice run --runner codex-cli|api
--prompt <file> --out <file> [--model <id>]`, which prints
`OUTSIDE_STATUS: <status> provider=<runner> model=<id> family=<f>`. Every
reviewer writes its result to the printed `result=` path under the RESULT
FORMAT below. `next` is idempotent: it reprints open attempts and never
redispatches on its own (`EXECUTION_UNKNOWN` lists attempts whose process
died without a result; `--redispatch <attempt>` mints a fresh one and
`release --attempt <id>` returns its reservation).
2. **Submit each result exactly once.**
```bash
$AP submit --out <dir> --phase ceo --voice native --result <dir>/ceo-native.a1.md --model <id> --attempt <run>-a1
$AP submit --out <dir> --phase ceo --voice outside --result <dir>/ceo-outside.a2.md --model <id> --attempt <run>-a2 [--runner host-subagent (default)] [--usage-usd <n>]
# BOUND: <run>-a1 phase=ceo voice=native model=<id> findings=7 sha256=<result hash prefix>
# PHASE_CLOSED: ceo confirmed=4 disagree=1 new=2 native_only=1 coverage=both
```
Submit seals the bytes it bound as `<phase>-<voice>.<attempt>.bound.md`
(read-only); a later write to the reviewer's file changes nothing bound,
and another attempt's result file is refused (`ATTEMPT_MISMATCH`).
Binding checks the receipt line against the phase snapshot
(`RESULT_RECEIPT_MISSING`), parses the canonical findings fence
(`RESULT_FINDINGS_MISSING`), refuses an outside model from the native
family (`MODEL_FAMILY_CONFLICT`), and refuses a second bind of the same
attempt or a bind while the phase is not awaiting it (`ATTEMPT_MISMATCH`,
`PHASE_NOT_AWAITING`, exit 3). Both voices in closes the phase: the
reconciliation (`confirmed` / `unverified` / `disagree` / `new` per outside finding, with
disagreement and resolution kept separate) is written to
`<phase>-consensus.{json,md}`, and every later phase's prompts (DX, design
and Eng alike) carry each closed predecessor's consensus as data; a phase
refuses to open without one (`CONSENSUS_MISSING`).
3. **Loop `next` until `status=gate_pending`.** Eng always runs last. The gate
(`gate.json`, `gate_rev`) lists user challenges as approval items,
disagreements as auto items, each open Critical or High finding as an
auto item (a twin pair is one item; `a` applies the reviewer's fix to the
plan and is recommended, `b` carries it into the build as a blocking task)
and `p1` plan approval; nothing is approved. An unanswered auto item whose
recommendation changes the plan keeps the plan's direction, so only `rec`,
`p1b` (approve with the recommended changes) or an explicit `<id>a` applies
a fix; a finding carried with `b` stays
open, so a rerun that raises it again asks again.
4. **Answer the gate** with the reply grammar and the revision you read:
```bash
$AP answer --out <dir> --gate-rev 1 --reply "rec" --by human # every recommendation, plan changes included (APPLY: ...; amend_pending)
$AP answer --out <dir> --gate-rev 1 --reply "as-is" --by human # APPROVED: keeps every direction, no new paid phase
$AP answer --out <dir> --gate-rev 1 --reply "rec except d1b" --by agent:<id> --owner-reply "<owner's words>"
```
`--by human` means the owner typed the tokens; `--by agent:<thread or id>`
means an agent relayed them, and then `--owner-reply` (or `-` for stdin,
`--owner-reply-file <path>`) stores the owner's words verbatim
(`OWNER_REPLY_REQUIRED`); owner words that parse to different choices are
refused (`GATE_OWNER_REPLY_MISMATCH`). The words default to the owner's: an
agent that answers in its own words (a Captain deciding on its own
authority) must pass `--words-by agent:<id>` with its own `--by` id. They
are then recorded as `answer_words` with `words_by`, never as
`owner_reply`; a later gate never carries them (`CARRY: no`), and
`gstack-approvals import` lists that row under the agent, not the owner.
An unattended run refuses an answer
without `--by` (`ANSWER_BY_REQUIRED`, exit 3) and records the value as
`answered_by` on every decision row and in the review record's gate line,
so a gate an agent closed is distinguishable from one the owner closed.
`gstack-autoplan recommend` changes one recommendation before relay; it
bumps the gate's `rec_rev`, and a later `rec` or `p1b` needs the `--rec-hash` from
the render the owner saw (`GATE_REC_STALE`). Every `GATE:` line is followed
by `GATE_SPEND:` and `GATE_REC_COST:` (also `gate_cost` in `--json`; see
`autoplan/DRIVER.md`).
A user challenge both voices raised on a confirmed finding (a title match,
not file:line proximity alone, which is `unverified`) with the same
`changes` value is recommended as `b` only with one shared proposal: a
`--reconcile` record naming it, or the same remedy from both voices.
Different or missing remedies keep `a`, and option b shows both verbatim
with the basis. `rec` (alias `all`) takes every recommendation, so it
accepts the items recommended `b`. Every accepted plan change prints
`APPLY: <decision id> <title> -> <chosen text>` and becomes a pending
amendment: `next` refuses with `PLAN_AMEND_PENDING` and `export` refuses a
`complete` run until `revise --incorporates` names it, or `revise --settle
<id>` settles a packaging or ordering change that needs no plan edit.
A stale revision (`GATE_REV_STALE`, exit 3), bare `yes`/`no`, or a
conflicting reply (`GATE_REPLY_UNPARSED`) writes nothing. A reply that
leaves approval items pending records only the answered auto items and
keeps the gate; a later reply never overwrites an earlier answer. An
accepted change reopens nothing at answer (status `amend_pending`); `revise`
reopens each phase whose reviewed plan changed plus Eng, archives their
prompts and consensus as `*.r<rev>.*` (sealed `*.bound.md` results stay in
place), and the next gate is `gate_rev + 1`; `p1d` reruns every phase on the
current plan; `p1e` ends `incomplete`.
5. **Export and validate.** `$AP export --out <dir>` writes `plan.md` (with
the `## URGENT, outside this plan` block first), `review-record.md`,
`tasks.jsonl`, `decisions.jsonl`, `findings.jsonl`, `timing.json` and
`run.json`; then `gstack-artifact validate <dir>/run.json` and the
consumption steps above apply unchanged. `$AP status --out <dir>` and
`resume` work after the run directory moves (paths are re-rooted; journal
paths are run-relative). With `GSTACK_AUTOPLAN_CHECKPOINT_DIR` set (on Capy,
`gstack-config set autoplan_checkpoint drive` defaults it to the private
project-user Drive folder; unset, the first `next` prints the export line),
a run at rest, after each bound voice and after each phase transition
publishes an immutable, hash-listed generation under
`<dir>/<run>/generations/` and switches `current.json` to it (the previous
generation is kept; a stale copy never replaces a newer one; a directory
overlapping the run is refused with `CHECKPOINT_OVERLAP`; a failure is a
`CHECKPOINT: failed` warning that leaves the command's exit code alone).
`$AP checkpoints --from <dir>` lists generations with their hash status, and
`$AP resume --out <new dir> --from <dir>/<run>` restores the newest valid
one after validating the staged run (`CHECKPOINT_INVALID`).
Concurrency and recovery: every state write is a locked read-modify-write
(`run.lock`; a second writer sees `RUN_LOCKED`), attempts are journaled to
`attempts.jsonl` before dispatch, and a parent killed before or after a
`submit` resumes with `next` without a duplicate charge
(`test/autoplan-run-recovery.test.ts`, `test/autoplan-run-concurrency.test.ts`,
`test/autoplan-answer-invalidation.test.ts`). `--spend-cap` admits each attempt
through `spend.json` (`SPEND_CAP_EXCEEDED` refuses, exit 3); host-subagent
spend is `unknown` and counted in `worst_case`. Under a cap, in any session
kind (or unattended on Capy), each host-subagent attempt, and each api attempt
whose model has no price, reserves the default per voice unless
`next --estimate-usd <n>` sets the reservation, which the run keeps for later
phases (`run` reserves an api attempt's priced worst case, prompt bytes / 3
input tokens plus the output limit, and settles it at its token usage; every
native call that went out and bound no result, whether it failed, was cancelled,
had its result refused, or was in flight when the process died (the next `run`
finds it), settles at its usage, or at its full reservation when the usage is
unknown, while one refused before anything was sent, such as with no API key,
keeps its attempt open for the rerun, and a failed outside call leaves the phase
native-only and stays counted at its reservation); `submit
--usage-usd <n>` settles an attempt at its real cost. `--deadline <min>` makes the
`run` subcommand cancel the children it started and marks the run
`interrupted`.
### RESULT FORMAT (what every reviewer file must contain)
Each prompt file ends with this contract; `submit` enforces it.
1. The first line is exactly `INPUT: <phase> <snapshot sha256>` (the receipt
that binds the result to the snapshot both voices reviewed).
2. The review is prose, then one fenced block of canonical findings, one JSON
object per line: `{"id":"F1","severity":"High","title":"...","file":"...","line":12,"fix":"..."}`
inside a ```` ```gstack-findings ```` fence. `severity` is Critical | High |
Medium | Low | Informational; `"user_challenge": true` marks a recommended
change to the plan's stated direction and needs `"changes": "scope" |
"cost" | "direction"` (what accepting it changes); `"urgent": true` with
`"suggested_owner"` marks a security or data-loss finding the plan does not
own. An empty fence means no findings; a missing fence is refused.
3. The whole result is written to the printed `result=` path.
### Outside-voice runners
| Runner | Access | Record label | Needs |
|---|---|---|---|
| `codex-cli` | repository, read-only, executes here | `repository review` | `codex` on PATH and logged in (`bin/gstack-codex-login` reads `OPENAI_API_KEY` from stdin) |
| `api` | supplied input only | `supplied-input review` | `--model <id>` and `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`; never silently equal to a repository review |
| `host-subagent` | the host's | `host-dispatched review` | `--result <file>` the subagent wrote, `--model <id>`; spend `unknown` |
`bin/gstack-outside-voice runners` prints each runner's availability on this
machine; `family <model>` prints the model family `submit` compares. A completed
`api` or `host-subagent` review is a full outside voice in the record, labeled
by its runner. Result codes: `EXECUTION_UNKNOWN`, `RUN_LOCKED`,
`RUN_NOT_INITIALIZED`, `PHASE_NOT_AWAITING`, `ATTEMPT_MISMATCH`,
`RESULT_RECEIPT_MISSING`, `RESULT_FINDINGS_MISSING`, `MODEL_FAMILY_CONFLICT`,
`SPEND_CAP_EXCEEDED`, `CONSENSUS_MISSING`, `OUTSIDE_RUNNER_UNAVAILABLE`
(anchors in [troubleshooting.md](troubleshooting.md)).
### Hand-run records, revised plans and admission
- **Hand-run reviews.** `submit --record <file>` binds a review that did not
follow the RESULT FORMAT: the `gstack-findings` fence, one findings table
(Severity and Title columns), one severity-tagged list (`- [High] title`) or
a `No findings.` line. Without its `INPUT:` line it binds only with
`--attest "<reason>"` and `--attempt <id>` (receipt `attested`; refused after
a later `revise`), and `gstack-artifact validate` prints `attested=<n>`.
`--attempt` may be omitted when exactly one attempt is open.
`submit --voice outside --unavailable "<reason>"` (or `--failed`) closes the
phase `native_only` once native is bound; `next` and `resume` never
redispatch it.
- **Revising the plan in the same run.** Edit the source plan, then
`$AP revise --out <dir> --plan <file> [--incorporates <decision id,...>]`.
Every phase whose reviewed Implementation-plan hash differs reopens (plus
Eng); newly in-scope phases are added; archives keep `.r<rev>` names with
`.v<k>` only on collision; a revise at `gate_pending` makes the next gate
`gate_rev + 1`. `--incorporates` discharges only the named amendments and
refuses an unchanged plan; `revise --out <dir> --withdraw <decision id>`
drops one as a journaled owner decision. `next` prints `PLAN_CHANGED` and
`answer` prints `PLAN_SOURCE_CHANGED` when the source plan moved since the
snapshot. A plan without both `## Implementation plan` and `## Review record`
refuses with `PLAN_SECTIONS_INVALID`; `next --scaffold` reviews a copy with
the missing heading added. `resume` completes an interrupted revise.
- **Admission and upgrades.** `next`, `submit`, `answer`, `revise`, `export`
and `run` refuse on a Capy host (`GSTACK_CAPY=1`) without an ok install
verdict for this checkout, HEAD and workflow at `$GSTACK_INSTALL_VERDICT`
(`INSTALL_UNVERIFIED`), and in any unattended session when the running Bun
is below gstack's supported minimum (`RUNTIME_BELOW_MINIMUM`); the decision
is the same one `gstack-skill-start` makes (see Install admission);
`GSTACK_ALLOW_UNSUPPORTED_RUNTIME=1` overrides and records a run deviation.
`status` and `resume` are never gated. A run directory written by an older
gstack prints `GSTACK_VERSION_CHANGED` once and moves forward (legacy
decision ids are renamed and journaled); one written by a newer gstack is
refused.
### Carry-forward
A gate revision after the first (`gate_rev` 2 and later, written after a
`revise`) is answered with the owner's recorded words from the revision before
it when nothing new needs their decision. `gstack-autoplan` prints
`CARRIED: gate_rev=<n> <- gate_rev=<m> words="<their words>" by=<relayer> reason=<why>`,
then the usual `APPROVED:` lines, and journals `gate_carried`. The carry needs
all of: the earlier gate approved (`p1a` or `p1b`), no item there left
pending, and the owner's words recorded; that gate was not itself carried; every accepted change incorporated
or settled; the plan file's bytes equal those recorded at the gate or at the
`revise` that took the changes (any later edit refuses, an ELI5-only one
included); no urgent or
outside-plan finding open; and every new gate item one of the cases below.
Anything else prints `CARRY: no (<why>)` (journal `carry_refused`) and the gate
goes to the owner as before. `--no-carry-forward` on the first `next` turns it
off for the run.
An earlier answer given item by item (`<id><option>` tokens, not `rec` or "do
it") carries only what the owner already answered: every item on the new gate
must be carried by origin (the same proposal, so the same choice), and only an
unreviewed post-cap delta (below) may be new. The carry replays the owner's own
`p1` choice and records their tokens verbatim as its basis; any new or changed
item, "do it" cases included, goes to the owner.
- **"do it".** When the owner's words were "do it" (their words, not a
`captain:` relay of them), a new `auto` item that changes no scope, cost or
direction, whose recommended option changes no plan text, and is no round
or budget grant takes its recommendation. The
decision row gets `carried_under: do it`, the output prints
`CARRIED_UNDER_DO_IT:` per item, and the approval record lists them as
"taken under "do it", not seen by the owner". `rec` carries no new item.
- **Unreviewed post-cap delta.** A "plan changed after <phase> cap" item
recommending `a` (accept) is read by one reviewer before it carries. `CARRY_CHECK:` names the attempt and
the prompt (the delta against `plan-at-gate.r<m>.md`, the accepted changes,
the full plan); the run reserves the estimate in `spend.json` and refuses
when it is over `--carry-check-cap <usd>` (default 5) or the run's cap.
Dispatch it to a model family other than the plan reviewers', then
`check --out <dir> --attempt <id> --result <file> --model <id>`
(`--same-family "<why>"` when no other family is available). A High or
Critical finding, or `--unavailable "<reason>"`, leaves the gate for the
owner. The verdict is saved as `carry_check` on `gate.json`, on the
decision rows (`carry_check`) and in `<dir>/decisions.tsv`. A pending
check is void, its reservation released and the gate left for the owner,
when the owner answers the gate, the plan is revised, a new gate is
written, or the delta changed after dispatch.
Carried rows keep `answered_by: agent:gstack-autoplan` and name their source
(`carried_gate_from`, `carried_gate_rev`, `carried_answered_by`);
`gstack-approvals import --autoplan` writes the carried gate as its own row,
and `gstack-owner-brief render --captain` opens with "Nothing pending.
Carried forward from gate_rev <m>", followed by any items taken under "do it"
without being shown to the owner.
## CEO-only runs
`/autoplan --ceo-only` runs the CEO review by itself and asks nothing. It runs
in the session that invokes the skill and needs Claude Code: other hosts inline
autoplan's sections and do not carry this one yet. A parent agent on any host
gets the same run from the driver with `gstack-autoplan next --ceo-only` (see
below). It is for a harness or a crew that wants the strategy pass without the
full gauntlet, such as an eval comparing gstack's CEO review with a plain
prompt.
- **What runs:** Phase 0, the outside-reviewer preflight and the CEO phase.
Design, DX, Eng and the final gate do not run.
- **How it decides:** every question takes its recommendation and is logged in
the Decision Audit Trail. The review mode comes from plan-ceo-review's 0E
rule (file count, then greenfield, added capability or fix), not autoplan's
fixed SELECTIVE EXPANSION, and the mode line says `CEO-only run`. Consent,
destructive and irreversible options take the conservative choice.
- **What it never does:** apply a user challenge (the plan's direction stays;
each challenge is listed), chain to another skill, promote the CEO plan, or
approve anything.
- **One model:** add `--no-outside` to skip the outside voice, so every
reviewer runs on the host model.
- **Where it writes:** the run directory (`.gstack/tmp/autoplan/` in the
repository), the reviewed plan, and CEO plans and task files under
`GSTACK_STATE_ROOT`. Point that variable inside a trial folder to keep every
write there.
- **What it leaves:** the reviewed plan (its original is kept at the restore
point), `<run dir>/ceo-review.md` (user challenges, scope added,
conservative choices, findings and the audit trail), and the last line
`GSTACK_RESULT: skill=autoplan status=reviewed run=<dir>`.
`test/skill-e2e-autoplan-ceo-only.test.ts` runs it on a plan that makes the
interactive CEO review ask a question and fails if this mode asks anything.
**From the driver.** `gstack-autoplan next --out <dir> --plan <file>
--ceo-only` opens only the CEO phase; the parent dispatches and submits its
voices as usual. When CEO closes, the run prints `REVIEWED:` and ends
`GSTACK_RESULT: skill=autoplan status=reviewed run=<dir>`, with
`<dir>/ceo-review.md` in the same sections, hashed in `run.json`. Each item a
gate would have asked becomes a `decisions.jsonl` row of kind `auto` and status
`auto_kept` (nobody answered: the plan's direction is kept, and a round-cap item
only the owner can accept, such as a Critical left open, stays undecided with
no chosen option), and tasks are blocked on no decision. An open Critical or
High finding, which a full gate asks about, gets no row: nobody decides it, and
it stays in the report's Findings and in `tasks.jsonl` for the owner. The driver never edits the plan, writes no `gate.json` and never
approves, so `gstack-artifact validate` accepts the run as it stands.
- The flag goes on the first `next` only. It refuses beside `--ui`,
`--developer-tool` or `--agent-primary` on the command line or in the plan's
scope marker, when the marker names it, and on a plan at the report path
(`CEO_ONLY_CONFLICT`); a directory that holds a full run refuses it
(`CEO_ONLY_AFTER_INIT`).
- `answer`, `recommend` and `revise` refuse a CEO-only run, and so does
`gstack-approvals import` (`CEO_ONLY_NO_GATE`): there is no gate to answer and
nothing was approved. A run stopped by `--spend-cap` recovers as a full run
does: `budget` then `resume`, or `close --phase ceo --deferred`, which ends
it `reviewed`.
### Shadow mode
Shadow mode measures how often a model decider would pick what the owner
picks, before the CEO-only mode is allowed to decide for him. In a driver run
that turned it on, every gate the driver publishes gets one hidden prediction
per question, written before anyone answers: `action` (`decide`,
`send_back` or `hold`), `pick` (one of that item's options), `confidence`
(0 to 1), `cites` (finding ids from that item) and `reason` (at most 400
characters). The owner still gets the gate and answers it as usual; nothing
routes differently. Taste-replay round 2 (R2, in gstack-evals) compares the
predictions with his answers.
- **Off by default.** A crew turns it on with `gstack-config set
autoplan_shadow on`; `GSTACK_AUTOPLAN_SHADOW=1` or `0` overrides it for one
run. It is read once, at the run's first `next`, and stored in `run.json`
(`shadow`), so a resumed run cannot flip it. The first `next` prints
`SHADOW: on model=<m> ...`, or `SHADOW: off (<reason>)` when it was asked for
but cannot run: no API key for the model's family, a model with no price in
`lib/pricing.ts`, `--no-outside`, or a CEO-only run. R2's collection window
starts at the first recorded row on a crew gate, not at this release: check
one `SHADOW: recorded` line on a real gate before counting days.
- **The model and the cost.** `autoplan_shadow_model` (default
`claude-opus-5-5`) gets one call per gate: about $0.07 on your own API key.
The call sends the reviewed plan's Implementation plan section (cut to 40,000
characters) and the gate questions to that provider, with an egress receipt
of sink `autoplan-shadow` and consent `autoplan_shadow on`. Each call is
reserved against `--spend-cap` under its own ledger id labeled `shadow` and
shows as `shadow=<usd>` on `GATE_SPEND`; it never changes the per-voice price
on `GATE_REC_COST`. When the cap cannot cover a call and its retry, the
shadow is skipped (`SHADOW: failed budget`).
- **The decider.** `autoplan/shadow-decider.md` is a frozen, hand-written
prediction prompt (it is not generated from the CEO-only section). It shows
the reviewers' recommendation as one labeled input and gives the six decision
principles as the owner's tendencies, not as rules, so the model can
disagree with the recommendation. Its sha256 and version are on every row;
an edit bumps both.
- **The reply.** Exactly one JSON object per question. A duplicate, missing
or invalid object marks that item malformed and the page is asked once more;
items still malformed are recorded malformed with no pick. A reply cut at the
4,000-token output limit is asked once more at 8,000 instead. One 90-second
deadline covers both calls.
- **When it runs.** Every driver command that prints a gate (`next`,
`submit`, `close`, `resume`, `revise`, `run` and `check`) runs the
shadow first, prints `SHADOW: recorded model=<m> usd=<x>` or `SHADOW: failed
<code>` (status only, never a pick), then the gate, then checkpoints, so a
checkpoint taken at a gate holds its record. A whole-gate carry asks nothing,
so it gets no shadow. Interactive `/autoplan` and CEO-only runs write none.
A shadow failure (`budget`, `no_key`, `timeout`, `http`, `network`,
`truncated`, `unsafe_dir`, `locked`, `internal`) never blocks the gate or the answer. A
failure while publishing the record (`locked` or `internal`) is journaled
(`shadow_failed`) under the run lock and never throws; if the lock stays
taken, the lease expires and the next command settles that revision.
- **The lease.** The run lock is not held during the call: the command takes
a lease (`shadow/r<rev>.pending`, with its pid, host and deadline) under the
lock, calls the model, then retakes the lock (waiting up to 30 seconds) to
publish. Another writer that finds a live lease prints `SHADOW: pending` and
pays nothing, and `answer` waits for the record. A lease whose process died
or whose deadline passed is a crash: its sent calls settle `unknown` at
their reservation (they may have been billed), and `answer` records that
revision first with every row `retrospective: true`, which R2 excludes from
prospective evidence.
- **The record.** `shadow/ceo-only.r<rev>.jsonl`, one row per gate item: the
frozen item, `item_class` (`challenge2`, `challenge1`, `disagree`, `agent`,
`cap_deferred`, `cap_blocker`, `cap_delta`, `finding`, `p1`), `ceo_eligible`
(false for a `finding` item, which a CEO-only run leaves to the owner), the
prediction, `rule_pick` (the driver's keep-direction pick, the free
baseline), `rec_hash`, the plan and prompt hashes, the model, the gstack
version, the page's tokens and cost, and `audit`. It is written once per
revision with a link, so a second writer never overwrites it, and its sha256
is journaled (`shadow_recorded`). `gstack-artifact validate <run.json>`
checks each record against that hash and its schema, and reports a
journaled revision whose record file is missing as `ARTIFACT_STALE`.
- **The 10% audit mark.** A `decide` row carries `audit: true` when
sha256(salt + decision id) mod 1000 < 100, with the salt drawn once per run
into `shadow/salt`. Nothing routes on it yet; the live switch that lets the
mode decide alone will send these items to the owner.
- **The reader.** `gstack-autoplan shadow --out <dir> [--json]`, or `--runs
<file>` with one run directory per line, exports one row per item of each
answered gate revision: the shadow status (`recorded`, `malformed`,
`failed` with its code, `missing`, `retrospective`, or `off` when the run
never turned it on), the prediction, and the owner's answer with its
provenance. It prints counts by status, item class, `owner_mode` and
`ceo_eligible` and computes no agreement number. A revision whose plan
approval is unanswered or answered `c` (interrogate) refuses
`SHADOW_BEFORE_ANSWER`, and a CEO-only run `CEO_ONLY_NO_GATE`. The refusal is
a courtesy for honest tools, not access control: the journaled hash is the
tamper evidence.
## Owner brief
`gstack-owner-brief render --out <dir>` is what the owner sees at the final
gate, interactive or unattended: one page built from `gate.json`,
`decisions.jsonl` and `findings.jsonl` (`BRIEF_INPUT_MISSING` when one is
absent). It opens with the `URGENT, outside this plan` block, then a
plain-language summary (phases closed, findings accepted, how many decisions
take their default, the cycle time; `--summary <file>` or `<dir>/summary.md`
replaces the generated sentence with the run's own), then the numbered
decisions through the gate list: seven per page (`--page N`), stable ids,
each `auto` item annotated with the default taken if unanswered, each
`approval` item `pending until you answer` or `decided <id><letter> on <date>`.
It carries a `Cost so far:` sentence (spent, worst case, cap, and the estimate for
the phases `rec` can reopen); `render --captain` puts it in a `### Cost` section above
the relaying-agent trailer. The last line names `review-record.md`, where the full review lives, and the
`gstack-autoplan answer` call that answers the page. `--write` also saves
`brief.md` in the run directory. `/autoplan` Phase 4 presents this output
verbatim and writes the former long report to `review-record.md`.
After the final gate, `gstack-owner-brief close --out <dir> --plan <file>`
rewrites every `draft direction stands until the owner decides` line in the plan
to the decided option and date (`DRAFT_DIRECTION: line <n> <id> rewritten`);
lines whose decision is still pending stay and are reported as
`DRAFT_DIRECTION_UNRESOLVED` (exit 1). `gstack-autoplan answer` runs the same
rewrite when a reply completes the gate (journal event `draft_directions_closed`).
## Lane done: pregate, the sealed guard and the handoff lines
A lane under a coordinator runs `gstack-pregate run --allow-repo-commands` on
its branch before it reports done (the unit tier on the changed files plus
the repo's guards, [docs/pregate.md#guards](pregate.md#guards)). In a repo
whose `.gstack/pregate.json` has a `sealed` key it runs `gstack-pregate sealed`
before any push or PR creation, since a PR tool may not run local hooks. Its
handoff carries, right after the `GSTACK_RESULT` line, the `gstack: ok|fail
<rev>` line and then the `PREGATE:` line. The coordinator contract
(`gstack-autoplan contract`) states the same rule word for word.
## Timing
`/autoplan` prints `ESTIMATE: ...` at Phase 0 (`gstack-autoplan-timing
estimate --run <id> --out <dir>`, medians of the last ten recorded runs per
phase, or `no history`), records each phase close (`gstack-autoplan-timing
close`), and prints the `CYCLE:` line at the final gate (`gstack-autoplan-timing
summary`). A close without an explicit start measures from the previous phase's
end, else from the run start. The cross-run history lives at
`<state root>/analytics/autoplan-timing.jsonl` and is skipped, with a printed
line, on an ephemeral state root.
## Reference run
`test/fixtures/multi-agent-wave/reference-run/` is a complete unattended
`/autoplan` run (the one that produced this wave's plan) and validates clean.
`test/fixtures/multi-agent-wave/INCIDENTS.md` records what went wrong while it
was produced by hand and which validator rule now catches each incident.
## Install admission
An unattended run, and a `GSTACK_SESSION_KIND=spawned` run with `GSTACK_CAPY=1`,
starts only on a supported Bun and, on a Capy machine (`GSTACK_CAPY=1`, set by
the installer's `GSTACK_LAUNCH` line), with a passing install verdict for this
checkout, its HEAD and the workflow. Otherwise `gstack-skill-start` prints
`INSTALL: fail runtime|verdict — <detail>; fix: <command> (<CODE>)` and
`GSTACK_RESULT: skill=<s> status=refused run=-`, and exits 3; stop and run the
fix. `GSTACK_ALLOW_UNSUPPORTED_RUNTIME=1` admits anyway (`INSTALL_VERDICT:
overridden`), recorded as a deviation. TypeScript entrypoints use
`checkInstallAdmission` from `lib/install-verdict.ts`; the Bash side is
`bin/gstack-install-verdict.sh`. See [capy-install.md](capy-install.md#the-launch-line).
## The decision trail
A long or unattended run keeps one tab-separated trail of its decisions at
`<run dir>/decisions.tsv`, written through `gstack-decision-trail <file>
<phase> <decision> <why> <evidence> <result>`, so the parent reads what the run
chose and why without the transcript. The helper stamps the time, writes the
header once, flattens each cell to one line and quotes a cell that starts
with `=`, `+`, `-` or `@`, so evidence lifted from a PR title or a filename
cannot run as a formula when a reviewer opens the file in a spreadsheet.
Log the forks, not the keystrokes: an approach chosen or dropped, a unit done
with its verification result, a revert and what triggered it, a blocker, one
row per iteration of a loop. Evidence is a pointer that resolves (a commit, a
PR, `file:line`, an artifact path), never prose. A run that joins a trail
other runs wrote (a pickup after a crash, a replacement machine, a later turn
of the same thread) first runs `gstack-decision-trail start <file> <run id>`,
which appends a `start` row naming the earlier rows and the run; appends with
`--run <id>` refuse (`DECISION_TRAIL_START_REQUIRED`) until that row is the
newest start row, and `start` is a no-op when it already is, so a rerun of the
same step writes nothing twice.
Before it hands back, the run audits the trail against what it did: every row
of its own stretch (from its `start` row to the next run's) maps to a real
decision or action, every evidence pointer resolves and shows what the row
claims, and a fork or abandoned approach that shaped the result but is not
logged gets a row. The audit corrects the log, never the story: no row is
edited or removed, an invented or wrong row stays and a new row supersedes it
with what actually happened. Then a reviewer on a different model family from
the one that did the work (a Capy subagent on another family, or the outside
voice) reads the trail and the transcript and flags what the owner should look
at: decisions with weak or missing evidence, verification claimed without
proof, choices that look risky in hindsight, gaps a skim would miss. The run's
final report ends with an `Attention` section that opens with `reviewed by
<model>` on its own line and lists each flag against its rows; `No flags` is a
valid body, a missing model line is not. Self-review does not stand in for
this. `decisions.jsonl` stays the typed record `gstack-artifact` validates; the
trail is the plain one a person reads, and `column -s$'\t' -t decisions.tsv`
renders it in a terminal.
## What an unattended session may do
A parent that starts a skill with `GSTACK_SESSION_KIND=unattended` grants it
exactly this much, and the run asks for nothing beyond it. Anything outside the
list is an approval item: appended to `decisions.jsonl` with `kind: "approval"`
and `status: "pending"`, named in the handoff, never taken.
- **Merge: never.** The run ends at a receipt bound to the pushed head
(`SHIP_RECEIPT`, [ship-policy.md#ship-before-ready](ship-policy.md#ship-before-ready))
or at a pending gate. Merging is the controller's act
([captain.md](captain.md)); a green check list, an approval, or a prompt that
says "merge when green" does not move it to the lane. The same holds for
enabling auto-merge, adding the PR to a merge queue, or `gh pr ready` on a
PR the run did not open.
- **Force-push: its own branch only, after a rebase the plan named, with a
pinned lease.** The branch is one the run created or was handed in its
prompt, never the base. The rebase is one the plan, the slot call or a
`/modernize` disposition names before it happens, not one the run decides
on its own; `git range-diff <old base>..<old head> <new base>..<new head>`
runs before the push and its output goes in the handoff. The push is
`git push --force-with-lease=<branch>:<sha observed before the rebase> origin <branch>`:
a bare `--force-with-lease` protects only against commits this clone never
fetched, and a plain `--force` is never allowed. A rejected lease means the
remote moved: fetch, read what landed, and start the disposition again.
`/ship` itself never rebases or force-pushes, and an unattended `/modernize`
stops at its verdict; this route is for a run whose plan or slot call named
the rebase before it started.
- **Tests: the methods the repository's instructions name, without asking
first.** AGENTS.md or CLAUDE.md, the named Setup commands, `package.json`
scripts and the pregate configuration define the set; running one of them
needs no question. A method outside the set (a framework the repo does not
use, paid work past the stated cap, anything that touches a shared or
production system, a test that writes outside the checkout) is pending.
- **Proof is what the run saw itself.** Command output, a test that failed
before the fix and passes after it, a screenshot, a file read in this
session. A handoff note, an earlier PR comment or a parent's summary is
context to recheck, not evidence; the handoff says which claims were
rechecked and which were not.
`/ship`'s handoff section (`ship/sections/handoff.md`) points here, so a lane
reads the grant where it reports done.