Files
gstack/autoplan/DRIVER.md
T
garrytanandcapy-ai[bot] 34c2411cf5 fix(autoplan): checkpoints never touch the live run; generations, newest-valid restore, validated staging (integration items 2-6)
2. checkpointRun canonicalizes the collection and the run directory (symlinks
   resolved through the deepest existing ancestor) and refuses, before
   touching anything, when they are the same or one contains the other
   (CHECKPOINT_OVERLAP); restore applies the same check to source, target and
   staging. Before, <root>/<run> equal to the live run replaced it.
3. Publication writes an immutable generation (generations/g<revision>-…),
   renames it into place complete, then switches current.json with one
   rename; the previous generation is kept, so an interruption at the publish
   boundary (fault hook checkpoint-before-pointer) leaves the last checkpoint
   current. A generation never replaces a newer one (higher run revision):
   "CHECKPOINT: skipped … newer". Publication and restore hold the
   collection's .lock; restore also holds <out>.lock.
4. Restore checks every listed file's real path stays inside the generation
   (an ancestor symlink no longer copies outside files), parses run.json and
   runs validateRun on the staged run before publishing; a refusal leaves
   --out absent. readState reroots plan_input and plan_source for moved runs,
   and a restore with the owner's plan file gone points both at the restored
   copy, so a second restore cycle and a later revise work.
5. A checkpoint failure after a committed command prints
   "CHECKPOINT: failed for <run>: … ; the command's result stands" on stderr
   and keeps the command's exit code.
6. `gstack-autoplan checkpoints --from <dir>` lists generations newest first
   (run, gate_rev, status, revision, time, hash ok|bad, current).
   `resume --from <collection or root>` restores the newest generation that
   verifies, printing each corrupt newer one it skipped; a root holding
   several runs must name one.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-15 01:35:41 +00:00

13 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. 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. --developer-tool and --agent-primary only enable DX. The first next prints SCOPE: ui=… (terms; visual; files) dx=… (detected) source=ui:<s>,dx:<s> (each axis is flag, marker or detected). UI needs a rendered file path 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.

The loop

  1. next prints one ATTEMPT: <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 (or gstack-outside-voice run). Each writes result=.
  2. Submit each voice exactly once (BOUND:). Both voices in closes the phase (PHASE_CLOSED:).
  3. Repeat next until status=gate_pending (GATE: gate_rev=<n>); render it with gstack-gate render <dir>/gate.json --all and relay every page. You never approve the gate for the owner. Offer rec (every recommendation, plan changes included) as the default.
  4. answer with the owner's reply and --by human (the owner typed the tokens) or --by agent:<id> --owner-reply "<their words>" (you relayed them); without --by an unattended run refuses (ANSWER_BY_REQUIRED). APPLY: <id> <title> -> <change> lines are accepted plan changes (status amend_pending): edit the plan, then revise (the printed NEXT: names it), or revise --settle <id> when the line says no plan edit is needed. Until then next refuses with PLAN_AMEND_PENDING and export will not mark the run complete.
  5. REOPENED: <phases> means loop next again (Eng last). APPROVED: means export.

On Capy

  • Always drive gstack-autoplan; never hand-adapt autoplan/SKILL.md. A plan you reviewed by hand is not an autoplan run: do not present it as one.
  • Set GSTACK_AUTOPLAN_CHECKPOINT_DIR to the private project-user Drive folder so a run at rest survives machine replacement; restore with resume --out <new dir> --from <dir>/<run>.

Commands

  • gstack-autoplan next --out <dir> --plan <plan.md> [scope flags] [--light] [--spend-cap <usd>] [--scaffold] first call: init the run, print PHASE:, SCOPE:, DRIVER: and one ATTEMPT: per voice.
  • 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, or No 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> bind a canonical result (the RESULT FORMAT below).
  • 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>|-] [--rec-hash <h>] answer the gate with the reply grammar below; an agent answer stores the owner's words verbatim.
  • gstack-autoplan recommend --out <dir> --gate-rev <n> --item <id> --option <k> --basis "<why>" change one recommendation before relay; then render and relay again (a rec needs 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> discharge a packaging or ordering change with no plan edit; reopens nothing.
  • gstack-autoplan revise --out <dir> --withdraw <decision id> drop a pending amendment instead of applying it.
  • gstack-autoplan export --out <dir> write the artifact set; then gstack-artifact validate <dir>/run.json.
  • 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 record shapes above.

RESULT FORMAT (required by gstack-autoplan; the parent binds your file by this contract):

  1. The first line of your result is exactly: INPUT:
  2. 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:); all is an alias.
  • as-is: keep every direction; no plan change.
  • d3b uc1a: one token per item you set; unanswered auto items take their default.
  • 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 with gstack-autoplan next --plan <file> --out <dir> (the first call initializes the run)
  • PLAN_SECTIONS_INVALID: add the missing heading, or rerun the first gstack-autoplan next with --scaffold to review a scaffolded copy (your plan file is never edited); duplicate, reversed or empty sections are fixed by hand
  • RUN_LOCKED: wait for it to exit, or if its pid is gone run gstack-autoplan resume --out <dir> to take ownership
  • EXECUTION_UNKNOWN: submit the result file if the reviewer finished (gstack-autoplan submit ... --attempt <id>), or redispatch explicitly with gstack-autoplan next --redispatch <id>; nothing is redispatched automatically
  • SPEND_CAP_EXCEEDED: raise --spend-cap, settle or release unsettled attempts, or stop; unknown charges count as their reservation
  • PHASE_NOT_AWAITING: run gstack-autoplan status --out <dir> and submit for the attempt next printed
  • ATTEMPT_MISMATCH: pass the attempt id next printed for this voice; a result is bound exactly once
  • RESULT_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 refused
  • RESULT_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 findings
  • MODEL_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 voice
  • CONSENSUS_MISSING: resume the run so the phase closes through reconciliation; the Eng prompt is never sent without every prior phase summary
  • GATE_REV_STALE: render the current gate list, read it, and reply with its gate_rev
  • GATE_REPLY_UNPARSED: reply with rec, as-is, <id><option> tokens (for example d3b uc1a), or rec except <tokens>; bare yes/no is ambiguous
  • GATE_REC_STALE: render the gate again (gstack-gate render <dir>/gate.json --all), relay it, and answer with the printed --rec-hash
  • OWNER_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 disagree
  • PLAN_AMEND_PENDING: edit the plan, then gstack-autoplan revise --out <dir> --plan <file> --incorporates <decision id,...>; settle a packaging or ordering change with gstack-autoplan revise --out <dir> --settle <id>; or drop one with gstack-autoplan revise --out <dir> --withdraw <decision id>
  • AMENDMENT_NOT_SETTLEABLE: edit the plan, then gstack-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 reviews
  • CHECKPOINT_INVALID: run gstack-autoplan checkpoints --from <dir> to see each generation's hash status and restore an intact one; nothing was written to --out
  • GSTACK_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 opens with the gstack-ship-receipt block; 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 with status=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 runs gstack-pregate run --allow-repo-commands on its branch (the unit tier on the changed files plus the repo's guards) and reports done only after it passes. In a repo whose .gstack/pregate.json has a sealed key, it runs gstack-pregate sealed before any push or PR creation; a refusal stops the push, because a PR tool may not run local hooks.

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.