* Make the autoplan docs say what lean and full runs actually load (GST-1985) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Keep the autoplan description at its size pin and say when the eng checklist is sent 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>
21 KiB
/autoplan driver: run the review loop from a parent agent
Read this file instead of autoplan/SKILL.md when you (a parent agent on any host) drive an
unattended /autoplan run through gstack-autoplan: the reviewers are subagents you dispatch,
and the runner keeps the state. The commands live in <root>/bin/, where <root> is the
folder that holds this autoplan/ directory; gstack-autoplan --help and the first next
print DRIVER: <root>/autoplan/DRIVER.md. Run every command with the GSTACK_LAUNCH assignments your installer printed: . <the GSTACK_LAUNCH_FILE it printed> in the shell that runs the command,
never export $(...), env, printenv or a bare set in a tool fence (a bare export prints every variable, credentials included). Read printed lines and files, never prose: every workflow
command ends with GSTACK_RESULT: skill=autoplan status=<s> run=<dir>, and every error line
ends with (<CODE>).
Phases and scope
Order: ceo → design → dx → eng. Design runs only with UI scope, DX only with DX scope, Eng always and last.
Scope flags on the first next: --ui, --no-ui, --no-dx, --developer-tool, --agent-primary, --docs-only. --developer-tool and --agent-primary only enable DX.
The first next prints SCOPE: ui=… (terms; visual; files) dx=… (detected) docs_only=yes|no (<s>) source=ui:<s>,dx:<s>
(each is flag, marker or detected). Docs-only (--docs-only, or every Implementation-plan path is README*, CHANGELOG*,
TODOS*, docs/** but not docs/*-policy.md, or other markdown outside a skill dir) runs CEO on the native voice only, then Eng. UI needs a rendered file path (including a named non-test .ts/.js file that holds a tagged html, css or svg template, such as Lit or htm) or a UI term next
to a visual noun in the Implementation plan. To keep flags for later runs on the same plan, add
the SCOPE_PIN: marker it prints (<!-- gstack-autoplan-scope: … -->) as the plan's first
line yourself; gstack never writes the plan. Kinds (R65), first next only. --kind fix reviews nothing: it prints AUTOPLAN: not needed (bug fix; R65), a PROOF: line and GSTACK_RESULT: skill=autoplan status=skipped run=-, writes nothing and needs no --out or --plan; the PR still carries the bug's proof, a repro on current main or a failing test written first. --kind feature, the default for an unattended run on Capy (GSTACK_CAPY=1) that names no kind, is the lean run. Its phase_order is eng alone with one native reviewer, whose prompt merges the CEO and Eng lenses (Design or DX only when scope detects them), each a short role or brief plus that review skill's checklist, never the skill's own method, always asks "Any outside terms, licences or rights involved?" and shows the draft code. The draft is git diff from the merge base with --base <ref> (default: gstack-diff-scope's base, never an origin/HEAD naming the checked-out branch), untracked files included and the run directory left out, written to the snapshot as draft.diff and hashed; with no code yet it says DRAFT: none (<why>). The first next prints LEAN: and each opened phase DRAFT:; the reviewer's line after INPUT must be DRAFT: <sha256> or DRAFT: none, or submit refuses it (RESULT_RECEIPT_MISSING). No outside voice, --max-rounds 1, no carry check and one gate revision: revise --incorporates applies the accepted changes without re-review and approves. --full brings back every phase and both voices: each native reviewer gets a five-question brief and its phase's checklist, each outside reviewer a one-paragraph role, and neither the skill's own method. Off Capy, and in an interactive session, the default stays full.
The loop
nextprints oneATTEMPT: <id> phase=<p> voice=native|outside prompt=<file> result=<file>per voice. Dispatch the native prompt to a subagent on your model and the outside prompt to a subagent on another model family (orgstack-outside-voice run). Each writesresult=. The firstnextalso printsREVIEW_ESTIMATE:(most attempts and reservations the review can take).- Submit each voice exactly once (
BOUND:). Both voices in closes the phase (PHASE_CLOSED:). - Repeat
nextuntilstatus=gate_pending(GATE: gate_rev=<n>); render it withgstack-gate render <dir>/gate.json --alland relay every page with the two cost lines printed underGATE::GATE_SPEND:(spent, worst case, cap, headroom, attempts; host-subagent attempts are priced unknown and counted at their reservation, so quote their real figure from the host's cost view) andGATE_REC_COST:(the phasesreccan reopen and the estimate for them, an upper bound;gate_costin--json). You never approve the gate for the owner. Offerrec(every recommendation, plan changes included) as the default. answerwith the owner's reply and--by human(the owner typed the tokens) or--by agent:<id> --owner-reply "<their words>"(you relayed them); without--byan unattended run refuses (ANSWER_BY_REQUIRED).APPLY: <id> <title> -> <change>lines are accepted plan changes (statusamend_pending): edit the plan, thenrevise(the printedNEXT:names it), orrevise --settle <id>when the line says no plan edit is needed. Until thennextrefuses withPLAN_AMEND_PENDINGandexportwill not mark the run complete.REOPENED: <phases>means loopnextagain (Eng last).APPROVED:meansexport(DISPATCHES: <n> est_input_tokens=<k>, relative).- Rounds: one by default; a second only for High or Critical findings; cap 2 (
--max-rounds). Arevisewhose decisions all resolve below High printsAPPLIED: <ids> without re-review (below High). At the cap with High open,ESCALATE: evidence sufficient, decision needed: relay it, never ask for another round. - Carry-forward (docs/unattended.md#carry-forward;
--no-carry-forwardon the firstnextopts out): a later gate that asks nothing new takes the owner's words from the gate before it (CARRIED:, thenAPPROVED:); a plan edit that only adds plain ELI5 lines carries as K1'sautoplan-regateno-stop (NO_STOP:; before /ship rungstack-no-stop import --from <dir>/no-stop.json);CARRY: no (<why>)leaves it for the owner;CARRY_CHECK:means one reviewer from another model family reads the printed delta prompt, thencheck --out <dir> --attempt <id> --result <file> --model <id>(or--unavailable "<reason>").
On Capy
- Always drive
gstack-autoplan; never hand-adaptautoplan/SKILL.md. A plan you reviewed by hand is not an autoplan run: do not present it as one. - Every prompt ends with a
GSTACK TRAILERblock: the reviewer runs the--for review-voiceinstall on its own machine (no ./setup, no skills; seconds when warm) and starts its reply with thegstack:line. Never tell a reviewer that no install is needed; relay each reviewer's line with its result, and treat a missing line asgstack: fail not-installed. - Set
GSTACK_AUTOPLAN_CHECKPOINT_DIRto the private project-user Drive folder (or opt every run in withgstack-config set autoplan_checkpoint drive) so a run survives machine replacement: a generation is published at rest, after each bound voice and after each phase transition; restore withresume --out <new dir> --from <dir>/<run>, which never redispatches a bound voice. - Plans carry
## ELI5(5-10 plain sentences),## Before → after,## Why it matters,## What caused this work,## Big decisions; relay the printedRELAY: gstack-owner-brief render --captainoutput to your Captain (or the person), thengstack-approvals import --autoplan <dir>afteranswer(--stack <b1,b2,...>for a stack;capy/skill/gstack/captain.md).
Commands
gstack-autoplan next --out <dir> --plan <plan.md> [--kind feature|--full] [--base <ref>] [scope flags] [--light] [--spend-cap <usd>] [--max-rounds <n>] [--estimate-usd <n>] [--scaffold] [--ceo-only]first call: init the run, print PHASE:, SCOPE:, DRIVER: and one ATTEMPT: per voice; --kind feature (the default for an unattended Capy run) is the lean run above and prints LEAN: and DRAFT:, --full keeps every phase and voice, andnext --kind fixalone (no --out or --plan) reviews nothing and endsstatus=skipped run=-; --max-rounds caps review rounds per phase (default 2 unattended); a run with --spend-cap, in any session kind (or any unattended Capy run), reserves 5.00 per voice for each attempt, so the cap binds; --estimate-usd sets that reservation and the run keeps it for later phases; --ceo-only (command line only, never a scope marker) reviews CEO alone and endsstatus=reviewedwith<dir>/ceo-review.md(REVIEWED:): no gate, and the gate verbs refuse it (CEO_ONLY_NO_GATE).gstack-autoplan run --out <dir> --plan <plan.md> --native-model <id> --outside-model <id> --native-runner api --runner api [--max-tokens <n>] [--spend-cap <usd>]hosts without subagents: one command calls both models directly (ANTHROPIC_API_KEY, OPENAI_API_KEY) and loops next/submit to the gate; a response cut off at the output limit is reported truncated, so raise --max-tokens (default 16384 for both providers; OpenAI counts reasoning tokens toward it); each api call reserves its priced worst case (a model with no price reserves the per-voice default under a cap), a native call that went out but bound no result settles at its usage, or its full reservation when the usage is unknown, and a failed outside call stays counted at its reservation.gstack-autoplan next --out <dir> [--redispatch <attempt>]later calls: open the next phase or reprint open attempts; never redispatches on its own.gstack-autoplan submit --out <dir> --phase <p> --voice native|outside --record <file.md> --model <id> [--attempt <id>]bind a free-form review record (canonical fence, one findings table or list, orNo findings.).gstack-autoplan submit ... --record <file.md> --attest "<reason>" --attempt <id>bind a record with no INPUT: line as receipt: attested (a wrong INPUT: always refuses).gstack-autoplan submit --out <dir> --phase <p> --voice native|outside --result <file> --model <id> --attempt <id> [--usage-usd <n>] [--reconcile <jsonl>]bind a canonical result (the RESULT FORMAT below);--usage-usdsettles the attempt at what the reviewer actually cost (else it stays priced unknown at its reservation); on the submit that closes the phase,--reconciletakes your own pairing of findings both voices raised, one{"outside_id","native_id","disposition","resolution"}row each, over the structural one.gstack-autoplan submit --out <dir> --phase <p> --voice outside --unavailable|--failed "<reason>"close the phase native-only when no outside voice can run.gstack-autoplan answer --out <dir> --gate-rev <n> --reply "<reply>" --by human|agent:<id> [--owner-reply <text>|-] [--words-by agent:<id>] [--rec-hash <h>]answer the gate with the reply grammar below; an agent answer stores the owner's words verbatim, and passes --words-by agent: whenever the words are its own (the default is owner).gstack-autoplan recommend --out <dir> --gate-rev <n> --item <id> --option <k> --basis "<why>"change one recommendation before relay; then render and relay again (arecneeds the new--rec-hash).gstack-autoplan revise --out <dir> --plan <plan v2> --incorporates <decision ids>bind the amended plan; reopens every phase whose reviewed plan changed, Eng last.gstack-autoplan revise --out <dir> --settle <decision id|d4> | --withdraw <decision id>--settle discharges a packaging or ordering change with no plan edit and reopens nothing; --withdraw drops a pending amendment instead of applying it.gstack-autoplan close --out <dir> --phase <p> --deferred "<reason>" --by human|agent:<id>close a phase accepted-with-findings: its open findings go to tasks.jsonl, and Critical, urgent or user-challenge findings become gate items; also the recovery fromSTOP: reason=budget_exhausted.gstack-autoplan budget --out <dir> [--spend-cap <usd>] [--max-rounds <n>] --by human|agent:<id>the owner's journaled cap raise; after a budget stop,resumecontinues the run.gstack-autoplan export --out <dir>write the artifact set; thengstack-artifact validate <dir>/run.json.gstack-autoplan export --out <dir> --public <pub>a machine-path-free copy to commit or share (recorded roots rewritten, plan.md code kept, provenance digests) without the owner's own words (every owner_reply the run recorded, carried words and a human's deferral reason dropped, quotes and gstack's renderings of them redacted; refused when one sits in plan.md code); the private run is not touched; thengstack-artifact validate --public <pub> --private <dir>/run.jsonon this machine.gstack-autoplan status|resume --out <dir>read the manifest (PENDING: lines name open attempts); take ownership after a crash (EXECUTION_UNKNOWN lists unfinished attempts).gstack-autoplan resume --out <new dir> --from <checkpoint>restore the newest valid checkpoint generation (GSTACK_AUTOPLAN_CHECKPOINT_DIR) into an empty directory and take ownership;checkpoints --from <dir>lists them.
Reviewer result format
Every prompt ends with this contract; submit --result enforces it, submit --record accepts the looser shapes above.
RESULT FORMAT (required by gstack-autoplan; the parent binds your file by this contract):
- The first line of your result is exactly: INPUT:
- Write the complete review as prose, then END with one fenced block of canonical findings, one JSON object per line:
{"id":"F1","severity":"High","title":"<one line>","file":"<path or omit>","line":12,"fix":"<the fix>"}
severity is Critical | High | Medium | Low | Informational. Add "user_challenge": true only when you recommend changing the plan's stated direction AND the change alters its scope, cost or direction (then also add "changes": "scope" | "cost" | "direction") or only its delivery packaging or ordering, with no Implementation-plan edit (then "changes": "packaging" | "ordering"). Naming and other reversible choices are ordinary findings, not user challenges. Add "urgent": true and "suggested_owner": "" for a security or data-loss finding this plan does not own. An empty fence means no findings; a missing fence is refused. 3. Write the whole result to this file:
Gate replies
A reply binds to the gate_rev you read (--gate-rev). Bare yes/no is unparsed, never guessed.
rec: every item takes its recommendation, plan changes included (each prints APPLY:);allanddo itare aliases.as-is: keep every direction; no plan change.d3b uc1a: one token per item you set; unanswered auto items take their default (a finding item keeps the plan as written;p1bapplies it).rec except d3b: recommendations, with the listed overrides.
Run directory
Runner state: run.json, attempts.jsonl, run.lock, spend.json, plan-active.md, restore.md, plan-source.md. Reviewer files: <phase>-native-prompt.md,
<phase>-outside-prompt.md, <phase>-<voice>.<attempt>.md (the reviewer writes it), its sealed
<phase>-<voice>.<attempt>.bound.md (what submit bound), <phase>-consensus.{json,md},
gate.json, decisions.jsonl. export writes plan.md, review-record.md, findings.jsonl,
tasks.jsonl, timing.json and rehashes run.json; gstack-artifact validate <dir>/run.json
is the completion check. Copy the whole directory, never single files.
Result codes
RUN_NOT_INITIALIZED: start withgstack-autoplan next --plan <file> --out <dir>(the first call initializes the run)PLAN_SECTIONS_INVALID: add the missing heading, or rerun the firstgstack-autoplan nextwith --scaffold to review a scaffolded copy (your plan file is never edited); duplicate, reversed or empty sections are fixed by handRUN_LOCKED: wait for it to exit, or if its pid is gone rungstack-autoplan resume --out <dir>to take ownershipEXECUTION_UNKNOWN: submit the result file if the reviewer finished (gstack-autoplan submit ... --attempt <id>), or redispatch explicitly withgstack-autoplan next --redispatch <id>; nothing is redispatched automaticallySPEND_CAP_EXCEEDED: raise--spend-cap, settle or release unsettled attempts, or stop; unknown charges count as their reservationPHASE_NOT_AWAITING: rungstack-autoplan status --out <dir>and submit for the attemptnextprintedATTEMPT_MISMATCH: pass the attempt idnextprinted for this voice; a result is bound exactly onceRESULT_RECEIPT_MISSING: the reviewer must read the prompt file through EOF and begin its result with the INPUT line the prompt names; a result for another snapshot is refusedRESULT_FINDINGS_MISSING: end the review with one ```gstack-findings fence, one JSON object per line with at least severity and title;[]rows are not findings, an empty fence means no findingsMODEL_FAMILY_CONFLICT: pass--model <id>of a different family (anthropic, openai, google, xai, deepseek, qwen, zai, moonshot, meta, mistral); a same-family result is not an outside voiceCONSENSUS_MISSING: resume the run so the phase closes through reconciliation; the Eng prompt is never sent without every prior phase summaryGATE_REV_STALE: render the current gate list, read it, and reply with its gate_revGATE_REPLY_UNPARSED: reply withrec,as-is,<id><option>tokens (for exampled3b uc1a), orrec except <tokens>; bare yes/no is ambiguousGATE_REC_STALE: render the gate again (gstack-gate render <dir>/gate.json --all), relay it, and answer with the printed--rec-hashOWNER_REPLY_REQUIRED: pass the owner's words exactly with--owner-reply "<text>",--owner-reply -(stdin) or--owner-reply-file <path>GATE_OWNER_REPLY_MISMATCH: relay exactly what the owner chose (or their reply itself, e.g.--reply rec); ask the owner when their words and the tokens disagreePLAN_AMEND_PENDING: edit the plan, thengstack-autoplan revise --out <dir> --plan <file> --incorporates <decision id,...>; settle a packaging or ordering change withgstack-autoplan revise --out <dir> --settle <id>; or drop one withgstack-autoplan revise --out <dir> --withdraw <decision id>AMENDMENT_NOT_SETTLEABLE: edit the plan, thengstack-autoplan revise --out <dir> --plan <file> --incorporates <decision id>(add--settle <id>to settle packaging/ordering changes in the same call); settle alone only on the plan the run already reviewsCHECKPOINT_INVALID: rungstack-autoplan checkpoints --from <dir>to see each generation's hash status and restore an intact one; nothing was written to --outGSTACK_VERSION_CHANGED: nothing to do for an older run (journaled forward migrations ran); a run written by a newer gstack needs that gstack: upgrade with bin/gstack-upgrade
Coordinator contract (inherited by every lane)
Merge conditions. A lane's PR merges only when: the required gate lanes are green on the stamped tree or the tree receipt says gate-reuse: eligible; gstack-lane-check reports clear for its planned files against every other open lane; the PR body carries the gstack-ship-receipt block in its Agent notes section; and the coordinator, not the lane, says merge. A lane never merges, rebases another lane, or force-pushes.
Behavior-change notices. Every user-visible behavior change outside the plan's own items is listed in the PR body under ## Behavior changes with the file, the old behavior and the new one, before the review starts. An omitted change found in review is a CRITICAL finding, whatever its size.
Spend caps. Each lane runs under the --spend-cap the coordinator names; a lane that reaches it stops incomplete (STOP: reason=budget_exhausted) and reports spent, reserved and unknown charges. Paid evals are never retried to change a verdict; a rerun needs a named repair. Host-subagent spend is reported as unknown, never zero.
Before done. A lane pushes a WIP commit to its branch after each step and gates a pushed SHA. It is done only when gstack-pregate run --allow-repo-commands passes; requires-remote needs --stage tests --remote --spend-cap <cap>. Where .gstack/pregate.json has a sealed key, it runs gstack-pregate sealed before any push or PR creation; a refusal stops the push.
Handoff format. A lane reports in this order, identifiers first: PR URL (or no PR with branch and SHA), GSTACK_RESULT line, the gstack: ok|fail <rev> line, the PREGATE: line, gate summary (lanes passed/failed/skipped), behavior changes (or none), spend (spent/reserved/unknown), then what it could not verify. Full thread ids (jam_…), never short codes, in every cross-thread message. Prose after the identifiers may be truncated; nothing before them may be.
Decision trail. A lane keeps <run dir>/decisions.tsv through gstack-decision-trail <file> <phase> <decision> <why> <evidence> <result> (one row per fork, unit, revert or blocker; evidence a pointer that resolves) and opens it with gstack-decision-trail start <file> <run id> when another run wrote it first. Before done it audits the trail against the transcript, superseding a wrong row with a new one rather than editing it, has a reviewer on another model family read the trail, and ends its report with an Attention section (reviewed by <model>, then the flags, or No flags). The full rule is docs/unattended.md.