mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-14 20:37:14 +01:00
* feat(captain): every ask to the owner uses the lettered ask format (R41) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(captain-brief): render the named heading through the brief helper (typecheck:test) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore(changelog): add the R41 ask-format fragment Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(captain-brief): tell the relaying agent how a named option answers (rec except <token>) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore(changelog): name the rec except form in the R41 fragment Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(captain): an ask that needs the owner to act carries the exact link and simple steps (R43) 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>
158 lines
11 KiB
Markdown
158 lines
11 KiB
Markdown
# Working under a Captain: approvals, plan asks and merge asks
|
|
|
|
This file belongs to gstack's Capy skill (the folder the owner copies into their Drive). It describes how a
|
|
Capy thread running gstack asks for decisions, records answers and asks for a merge. The rules apply whether
|
|
the thread reports to a Captain (a crew thread) or straight to a person. `gstack-skill-start` prints the
|
|
short form as the `capy-asks` block whenever `GSTACK_CAPY=1` (the Capy launch line sets it).
|
|
`GSTACK_CAPY_ASKS=0` drops that block for one session. This is the lane's side. The controller's side (the
|
|
READY contract, merge trains, the digest, the daily flaky pass, and the templates for the crew rules file
|
|
and the Captain's drive) is the `/captain` skill and `docs/captain.md` in the gstack checkout.
|
|
|
|
## 1. How approvals work, per thread
|
|
|
|
- **One path.** A crew thread asks its Captain: a needs-you report through Capy's agent channel, never a
|
|
question to the person. The Captain relays it in plain language and sends back the person's exact words.
|
|
A thread with no Captain asks the person directly with a Capy question. Either way, never use a bare
|
|
question, gate codes or slot numbers.
|
|
- **Scope.** An answer covers the thread and scope it was asked for. It is not a blanket approval for
|
|
later work.
|
|
- **Standing approvals** are the ones the Captain lists in its own rules, for example "merge when green,
|
|
through a merge train", "paid eval gates are pre-approved" or "a single-case flaky rerun". Cite the rule
|
|
(with its date and words) instead of asking again. gstack ships no standing approvals of its own.
|
|
- **Always a fresh ask:**
|
|
- a product or behaviour decision;
|
|
- spend over a stated cap;
|
|
- anything published outside the repository (advisories, releases, posts);
|
|
- a change to a threshold, budget, trial count or test kind;
|
|
- closing someone's contribution without landing it;
|
|
- anything destructive or irreversible.
|
|
- **Observable questions are not the person's to answer.** A question a command, a render or a measurement
|
|
can settle locally, read-only and for free ("is the new path faster", "does the test endpoint take this
|
|
payload", "does the test still fail") is settled by the thread: it runs the thing and reports the result.
|
|
A call that changes state, reaches a live system or spends money is still an ask. Only a product or preference call
|
|
goes up, with the observation already made. A Captain that receives an observable question sends it
|
|
back with the check that answers it instead of relaying it.
|
|
- **What a gstack workflow does in Capy.** Launched with `GSTACK_SESSION_KIND=unattended`, a workflow takes
|
|
each recommended option, writes everything else as a pending gate item, and stops. Your job is to carry
|
|
those pending items to the Captain or person in the shape below, then feed the answer back.
|
|
|
|
## 2. The approval record
|
|
|
|
Every answer gets a row: the date, what was asked, the person's exact words, who said them (and who relayed
|
|
them), what the answer covers, what is still pending, and the outcome (approved, rejected, pending, or a
|
|
cited standing approval).
|
|
|
|
```bash
|
|
gstack-approvals add --ask "Spend up to \$40 on a paid eval rerun for the ship cases" \
|
|
--words "yes go" --by "Garry (via GStack Minder)" --scope "one rerun of the 6 ship cases, cap \$40"
|
|
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
|
|
```
|
|
|
|
- **Where it lives.** `<state root>/projects/<slug>/approvals/<branch key>.jsonl`, one file per branch,
|
|
because one thread ships one branch. On Capy the state root is thrown away with the machine, so every write
|
|
is also mirrored to `GSTACK_APPROVALS_DIR`, else to the `approvals/` directory in the mounted Captain
|
|
drive (`<captain drive>/approvals/<slug>/`, readable by the whole crew), else to the private project-user
|
|
Drive folder. `gstack-approvals path` prints the chosen mirror and `READ_ALSO:` roots, which reads still
|
|
merge. `GSTACK_APPROVALS_MIRROR=0` turns the mirror off, and `gstack-approvals sync` restores the local copy.
|
|
- **The durable copies** are the `## Approvals` sections in the thread's report and in the PR body. Paste
|
|
`render` output into them; never type rows by hand.
|
|
- **Secret-free.** Every field passes gstack's secret scanner before it is written. A hit refuses the row
|
|
(`APPROVAL_RECORD_SECRET`, which names the field and rule, never the text). Write `[redacted]` in place of
|
|
the secret, or ask the person to restate.
|
|
- **Display only.** The rendered table escapes mentions, links, images, HTML and anything shaped like an
|
|
`Approved by … via …` citation. gstack's enforced approval classes still need their own citation line
|
|
(docs/ship-policy.md#approval-classes).
|
|
- An empty table means "No approvals recorded". On a fresh Capy machine with no record and no mirror it says
|
|
"Record unavailable" instead (`APPROVAL_RECORD_UNAVAILABLE`), because a lost record is not an empty one.
|
|
|
|
## 3. Plan approvals: the shape
|
|
|
|
Under a Captain, a thread that builds something plans before any product code. A `/autoplan` run on its
|
|
brief, sized to the work, is its first act, the gate goes up in the shape below, and the build starts only
|
|
when the answer is in the run and it reports `APPROVED`. The short form every Capy thread follows, with the
|
|
exempt work (read-only threads, flaky-pass repairs, slot stamps, CHANGELOG resolves, cap re-pins under a
|
|
grant), is the "Plan, approve, ship" section of SKILL.md in this folder; the rule in full is `docs/captain.md`,
|
|
Plan before build.
|
|
|
|
Every plan or gate you bring for approval has these parts, in this order, written so someone outside the
|
|
project can follow it:
|
|
|
|
1. **Before → after.** What the user or maintainer sees today and after, with concrete examples: a command
|
|
and its output, a screen, a cost or time number.
|
|
2. **Why it matters.** Who is hurt today and how much, with numbers where possible.
|
|
3. **What caused this work.** The trigger: an issue or PR link, a failed run, a user report, or the
|
|
person's request, quoted.
|
|
4. **Big decisions.** For each: the options considered, the pick and why, and what would flip it.
|
|
5. **What we need from you.** The ask format (`docs/captain.md`, The ask format): the Captain's name, then one
|
|
lettered line per decision, letters unique across the list: "A: <code> hit problem p, which happens when h.
|
|
We could fix it in these ways: 1) x; 2) y; 3) z. Recommendation: x because why. A1 to take recommendation."
|
|
The recommendation is option 1. Never offer "the plan as first written" as the default.
|
|
6. **"do it".** Every ask ends with "do it": take every recommendation in the list at full scope, the follow-ons
|
|
the plan names (docs, trains, cleanups) included, and carry them through to done with no further ask. A named
|
|
option ("A2") takes that option instead. For an autoplan gate, "do it" answers as `rec`; record the words.
|
|
|
|
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> --captain-name "<Captain name>"
|
|
```
|
|
|
|
It prints a header line ("Decision needed … Reply **do it** …"), the `### ELI5` summary (on a run whose gate
|
|
carries `eli5`), the four plan sections (each capped, with a pointer to the plan for the rest), and "What we need from you", one lettered
|
|
line per choice in the ask format, closed by "do it", then a `### Cost` section (spent so far, worst case, and the estimate for
|
|
the phases `rec` can reopen). The last line, "For the relaying agent", carries the run, `gate_rev`, `rec_hash` and the exact
|
|
`gstack-autoplan answer` command. Relay everything above that line. When the reply comes back:
|
|
|
|
```bash
|
|
gstack-autoplan answer --out <run dir> --gate-rev <n> --reply rec --rec-hash <h> \
|
|
--by agent:<your thread> --owner-reply "<their exact words>"
|
|
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 named option ("B2")
|
|
answers as `--reply "rec except <token>"`, with the token from the last line's letter map. A plan missing a
|
|
section renders "Not ready for approval" with no reply invitation, and exits 1 (`CAPTAIN_BRIEF_INCOMPLETE`):
|
|
add the sections, run `gstack-autoplan revise --plan <file>`, and render again. For a decision that is not an
|
|
autoplan gate, write the same five parts by hand.
|
|
|
|
## 4. Merge asks: the shape
|
|
|
|
Every PR you send READY on, and the READY report itself, has:
|
|
|
|
1. **What changed.** One to three before/after images, highest-signal first, each with a one-line caption
|
|
(what changed and why) and a collapsed text version under it.
|
|
- UI: screenshots at the base and on the branch, the same size.
|
|
- Skill or prompt: `gstack-pr-shots --path <file> --heading "<section>" --caption "<…>"`.
|
|
- CLI: `gstack-pr-shots --before <capture> --after <capture> --kind terminal --caption "<…>"`, where the
|
|
captures are files inside the repository.
|
|
- Internal: the source section or test output that best shows the change.
|
|
`gstack-pr-shots` reads text only, runs nothing, refuses secret-matching input, and prints `SHOT_PNG:`.
|
|
With no browser that line says `unavailable`: screenshot the printed `shot.html` with your own browser.
|
|
Upload each PNG with Capy's PR asset tool (`tools.pr.uploadAsset`), only for public repositories, and embed
|
|
the URL it returns with the caption as alt text.
|
|
2. **Why.** The problem in the person's words first, then the cause and the effect the user will see,
|
|
in two or three sentences.
|
|
3. **Blast radius.** What else the change touches and the one fact that makes that safe, with the
|
|
evidence-ladder rung that fact reached.
|
|
4. **Approvals.** `gstack-approvals render`.
|
|
5. **Evidence.** Checks green at the head, gate or train results, flaky cases.
|
|
|
|
The READY report attaches the key image (Capy message attachment), so the Captain can show it inline.
|
|
/ship does all of this when `GSTACK_CAPY=1` (its `capy-pr` section). Outside Capy, the PR body is unchanged.
|
|
|
|
A token cap in a brief goes to /ship as `--token-cap <n>`. When /ship stops on it (`STOP:
|
|
reason=budget_exhausted`, `status=incomplete`), the lane reports the BUDGET and RECOVER lines and asks the
|
|
owner; it never raises the cap itself. The owner's answer runs the RECOVER line with
|
|
`--approved-by <who> --words "<their words>"`, and `gstack-approvals add` records the same words.
|