docs: carry-forward, ELI5 section and stacked approvals import

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-29 04:22:12 +00:00
1 parent 8e06646775
commit f5bcdf71a7
8 files changed
+85 -16

No files matched your search

+10 -3
View File
@@ -27,6 +27,7 @@ line yourself; gstack never writes the plan.
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=`.
The first `next` also prints `REVIEW_ESTIMATE:` (most attempts and reservations the review can take).
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 with the two cost lines printed under `GATE:`: `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) and `GATE_REC_COST:` (the phases `rec` can reopen and the estimate for them, an upper bound; `gate_cost` in `--json`).
You never approve the gate for the owner. Offer `rec` (every recommendation, plan changes included) as the default.
@@ -38,6 +39,11 @@ line yourself; gstack never writes the plan.
5. `REOPENED: <phases>` means loop `next` again (Eng last). `APPROVED:` means `export` (`DISPATCHES: <n> est_input_tokens=<k>`, relative).
6. Rounds: one by default; a second only for High or Critical findings; cap 2 (`--max-rounds`). A `revise` whose decisions all resolve below
High prints `APPLIED: <ids> without re-review (below High)`. At the cap with High open, `ESCALATE: evidence sufficient, decision needed`: relay it, never ask for another round.
7. Carry-forward: a later gate that asks nothing new takes the owner's words from the gate before it
(`CARRIED: gate_rev=<n> <- gate_rev=<m>`, then `APPROVED:`); `CARRY: no (<why>)` leaves it for the owner. `CARRY_CHECK:`
means one reviewer from another model family reads the printed delta prompt, then `check --out <dir> --attempt <id>
--result <file> --model <id>` (or `--unavailable "<reason>"`). `--no-carry-forward` on the first `next` opts out
([docs/unattended.md#carry-forward](../docs/unattended.md#carry-forward)).
### On Capy
@@ -51,9 +57,10 @@ line yourself; gstack never writes the plan.
with `gstack-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
with `resume --out <new dir> --from <dir>/<run>`, which never redispatches a bound voice.
- Plans carry `## Before → after`, `## Why it matters`, `## What caused this work`, `## Big decisions`;
relay the printed `RELAY: gstack-owner-brief render --captain` output to your Captain (or the person),
then `gstack-approvals import --autoplan <dir>` after `answer` (`capy/skill/gstack/captain.md`).
- Plans carry `## ELI5` (5-10 plain sentences), `## Before → after`, `## Why it matters`, `## What caused this work`,
`## Big decisions`; relay the printed `RELAY: gstack-owner-brief render --captain` output to your Captain (or the person),
then `gstack-approvals import --autoplan <dir>` after `answer`, with `--stack <b1,b2,...>` when the plan ships as a
stack (`capy/skill/gstack/captain.md`).
### Commands
+10 -3
View File
@@ -24,6 +24,7 @@ line yourself; gstack never writes the plan.
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=`.
The first `next` also prints `REVIEW_ESTIMATE:` (most attempts and reservations the review can take).
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 with the two cost lines printed under `GATE:`: `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) and `GATE_REC_COST:` (the phases `rec` can reopen and the estimate for them, an upper bound; `gate_cost` in `--json`).
You never approve the gate for the owner. Offer `rec` (every recommendation, plan changes included) as the default.
@@ -35,6 +36,11 @@ line yourself; gstack never writes the plan.
5. `REOPENED: <phases>` means loop `next` again (Eng last). `APPROVED:` means `export` (`DISPATCHES: <n> est_input_tokens=<k>`, relative).
6. Rounds: one by default; a second only for High or Critical findings; cap 2 (`--max-rounds`). A `revise` whose decisions all resolve below
High prints `APPLIED: <ids> without re-review (below High)`. At the cap with High open, `ESCALATE: evidence sufficient, decision needed`: relay it, never ask for another round.
7. Carry-forward: a later gate that asks nothing new takes the owner's words from the gate before it
(`CARRIED: gate_rev=<n> <- gate_rev=<m>`, then `APPROVED:`); `CARRY: no (<why>)` leaves it for the owner. `CARRY_CHECK:`
means one reviewer from another model family reads the printed delta prompt, then `check --out <dir> --attempt <id>
--result <file> --model <id>` (or `--unavailable "<reason>"`). `--no-carry-forward` on the first `next` opts out
([docs/unattended.md#carry-forward](../docs/unattended.md#carry-forward)).
### On Capy
@@ -48,9 +54,10 @@ line yourself; gstack never writes the plan.
with `gstack-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
with `resume --out <new dir> --from <dir>/<run>`, which never redispatches a bound voice.
- Plans carry `## Before → after`, `## Why it matters`, `## What caused this work`, `## Big decisions`;
relay the printed `RELAY: gstack-owner-brief render --captain` output to your Captain (or the person),
then `gstack-approvals import --autoplan <dir>` after `answer` (`capy/skill/gstack/captain.md`).
- Plans carry `## ELI5` (5-10 plain sentences), `## Before → after`, `## Why it matters`, `## What caused this work`,
`## Big decisions`; relay the printed `RELAY: gstack-owner-brief render --captain` output to your Captain (or the person),
then `gstack-approvals import --autoplan <dir>` after `answer`, with `--stack <b1,b2,...>` when the plan ships as a
stack (`capy/skill/gstack/captain.md`).
### Commands
+3 -2
View File
@@ -14,7 +14,7 @@ Spend cap: <focused paid runs per PR, from SPEND>
1. Plan (when Plan: required). After the session-start install check, install for autoplan
and follow autoplan/DRIVER.md "On Capy" (launch file sourced, GSTACK_AUTOPLAN_CHECKPOINT_DIR
set, run dir under ~/.capy/work). Write the plan with its ## Before → after,
set, run dir under ~/.capy/work). Write the plan with its ## ELI5, ## Before → after,
## Why it matters, ## What caused this work, ## Big decisions and ## Implementation plan
sections, then run gstack-autoplan next --out <run dir> --plan <plan.md> <scope flags>
sized to this brief (a one-file fix: --no-ui --no-dx --max-rounds 1 --estimate-usd <n>
@@ -25,7 +25,8 @@ Spend cap: <focused paid runs per PR, from SPEND>
(gstack-autoplan answer --out <run dir> --gate-rev <n> --reply <tokens> --rec-hash <h>
--by agent:<your thread> --owner-reply "<their words>"), incorporate every accepted plan
change (revise), finish every reopened phase, and start product edits only when the run
reports APPROVED. Then gstack-approvals import --autoplan <run dir> --by "<owner>".
reports APPROVED (a later gate that asks nothing new prints CARRIED: and needs no second
ask). Then gstack-approvals import --autoplan <run dir> --by "<owner>" (--stack b1,b2 for a stack).
New scope you take on part-way goes back to step 1 with its own plan.
3. Ship. CHANGELOG entry under [Unreleased]; GUARD_CHECKS, pregate and /ship at the final
head; one unstamped READY block (captain/templates/READY.md) to me; the PR body carries
+3
View File
@@ -7,6 +7,9 @@ shape from an autoplan run; write it by hand for any other plan.
```text
Decision needed on <thread>: <one-line title>. Reply **rec** to take every recommendation.
## ELI5
<5-10 plain sentences: what changes, for whom, and what it costs>
## Before → after
<what the user or maintainer sees today; what they will see after; a command and its
output, a screen, a cost or time number>
+8 -2
View File
@@ -48,6 +48,7 @@ gstack-approvals add --ask "Spend up to \$40 on a paid eval rerun for the ship c
gstack-approvals add --outcome standing --ask "Merge #3330 when green" \
--words "yes merge when green" --by "Garry, 2026-10-16 (Captain rule)" --scope "every crew PR, through a train"
gstack-approvals import --autoplan <run dir> --by Garry # after gstack-autoplan answer; idempotent
gstack-approvals import --autoplan <run dir> --by Garry --stack b1,b2 # a stacked PR set: every branch, or none
gstack-approvals render # the ## Approvals section
```
@@ -93,8 +94,8 @@ project can follow it:
the plan names (docs, trains, cleanups) included, and carry them through to done with no further ask.
Say in one line what it covers. For an autoplan gate, "do it" answers as `rec`; record the words.
For an autoplan run, write parts 1 to 4 as `## Before → after`, `## Why it matters`,
`## What caused this work` and `## Big decisions` sections in the plan. Then, at the gate:
For an autoplan run, open the plan with `## ELI5` (5-10 plain sentences), then write parts 1 to 4 as
`## Before → after`, `## Why it matters`, `## What caused this work` and `## Big decisions` sections. Then, at the gate:
```bash
gstack-owner-brief render --captain --out <run dir>
@@ -112,6 +113,11 @@ gstack-autoplan answer --out <run dir> --gate-rev <n> --reply rec --rec-hash <h>
gstack-approvals import --autoplan <run dir> --by <name>
```
A later gate that asks nothing new is answered for you with the words you recorded (`CARRIED:`, then `APPROVED:`);
the brief says "Nothing pending. Carried forward", so import and build without a second ask. `CARRY_CHECK:` means
one reviewer from another model family reads the unreviewed delta first (`gstack-autoplan check`). Anything
else prints `CARRY: no (<why>)` and goes to the person as today (`docs/unattended.md#carry-forward`).
If the person answers in words ("split it into two PRs"), map the words to tokens, answer, and confirm in one
line what you recorded ("Recorded: split into two PRs; everything else as recommended"). A plan missing a
section renders "Not ready for approval" with no reply invitation, and exits 1 (`CAPTAIN_BRIEF_INCOMPLETE`):
+8 -5
View File
@@ -138,8 +138,9 @@ this rule shipped cites this section by name.
**What the lane sends.** The plan and its gate in plain language a person outside the
project can follow: what changes, why now, what the change does, what it costs, the lane's
pick, and the recommended one-word reply. `gstack-owner-brief render --captain --out <run dir>`
prints that from the plan's `## Before → after`, `## Why it matters`,
`## What caused this work` and `## Big decisions` sections, with the cost lines
prints that from the plan's `## ELI5`, `## Before → after`, `## Why it matters`,
`## What caused this work` and `## Big decisions` sections (also saved as `eli5` in
`gate.json`, so `gstack-gate render` opens with them), with the cost lines
(`GATE_SPEND:`, `GATE_REC_COST:`) and a last line for the controller; a plan missing a
section exits `CAPTAIN_BRIEF_INCOMPLETE`, so the lane adds the section and renders again
([capy/skill/gstack/captain.md](../capy/skill/gstack/captain.md) § 3 lists them). The lane
@@ -156,10 +157,12 @@ controller and been answered into the run
every accepted plan change has been incorporated (`revise`), every reopened phase has
finished, and the run reports `APPROVED` (`status=complete` after `export`). An answered
gate that still prints `PLAN_AMEND_PENDING` or `REOPENED:` is not a completed plan, and an
imported approval row alone is not completion.
imported approval row alone is not completion. A later gate revision that asks nothing new
is answered with the owner's recorded words and says `CARRIED:`; that is the owner's word,
not a new ask ([carry-forward](unattended.md#carry-forward)).
**The record.** `gstack-approvals import --autoplan <run dir> --by "<owner>"` writes the
approval row (date, the ask, the owner's exact words, the lane that answered as relayer,
**The record.** `gstack-approvals import --autoplan <run dir> --by "<owner>"` (with
`--stack <b1,b2,...>` for a stacked PR set) writes the approval row (date, the ask, the owner's exact words, the lane that answered as relayer,
what it covers); `gstack-approvals render` puts it in the report and the PR body. Before
training the PR the controller compares that row's words and `gate_rev` with the ask it
relayed and the reply it carried back; a row that does not match is not READY (shape A).
+3 -1
View File
@@ -1769,7 +1769,9 @@ import again. Imports are idempotent, so a retry never records an answer twice.
### `gstack-owner-brief render --captain: ... (CAPTAIN_BRIEF_INCOMPLETE)`
**Meaning.** The plan is missing at least one section the brief needs: `## Before → after`,
`## Why it matters`, `## What caused this work`, `## Big decisions`. The brief still prints,
`## Why it matters`, `## What caused this work`, `## Big decisions`, and `## ELI5` (alias
`## In plain words`) on a run whose `gate.json` carries an `eli5` field (gstack 1.91.305 and
later; older runs render as before). The brief still prints,
but it opens with "Not ready for approval" and has no reply invitation. Exit 1.
**Fix.** Add the named sections to the plan (`gstack-autoplan revise --plan <file>` if
+40
View File
@@ -494,6 +494,46 @@ by its runner. Result codes: `EXECUTION_UNKNOWN`, `RUN_LOCKED`,
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`) with 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 (an ELI5-only edit counts); 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.
- **"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 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 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`.
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>".
## Owner brief
`gstack-owner-brief render --out <dir>` is what the owner sees at the final