feat(captain): AGENTS.md and NOTES.md templates, roles, ship lane sequence, owner device and lessons loop in docs/captain.md; owner-brief closes with the do-it reply

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-26 14:15:50 +00:00
1 parent a28bf56c2f
commit 30caa1fd71
10 files changed
+147 -12

No files matched your search

+3 -2
View File
@@ -6,11 +6,12 @@
### Added
- **`captain/SKILL.md.tmpl`**: the skill, tier 2, routed from the root router on "captain", "crew", "merge train", "call the slot", "validate this ready", "write the digest" and "flaky pass".
- **`captain/templates/`**: `CREW.md` (the 22 lane rules, parametrized), `DIGEST.md`, `flaky-ledger.md`, `report.md` (Outcome, PR, Needs, Cost, Lesson, Approvals), `READY.md` (the block, the optional lines, the refusal list), `slot-call.md`, `plan-ask.md`, `merge-ask.md` and `automation-flaky-pass.md` (the schedule, what it reads, what it starts).
- **`docs/captain.md`**: how a team sets up a Captain crew on Capy with gstack: the drive layout, the crew rules file, the READY contract, trains, approvals, the digest, the flaky pass, and the setup steps; linked from the README docs table, `docs/skills.md`, `docs/capy-install.md` and the skill.
- **`captain/templates/`**: `CREW.md` (the 22 lane rules, parametrized), `AGENTS.md` (the controller's standing rules with the owner's dated words), `NOTES.md` (the append-only timestamped log), `DIGEST.md`, `flaky-ledger.md`, `report.md` (Outcome, PR, Needs, Cost, Lesson, Approvals), `READY.md` (the block, the optional lines, the refusal list), `slot-call.md`, `plan-ask.md`, `merge-ask.md` and `automation-flaky-pass.md` (the schedule, what it reads, what it starts).
- **`docs/captain.md`**: how a team sets up a Captain crew on Capy with gstack: the roles, the drive layout, the crew rules file, the READY contract, the lane's /ship sequence before READY, trains with the slot-2 recipe, approvals, the digest, the flaky pass, the owner's device, the lessons loop, and the setup steps; linked from the README docs table, `docs/skills.md`, `docs/capy-install.md` and the skill.
- **`test/captain-skill.test.ts`**: pins the contract the skill teaches, the template set, the generic wording (no crew codes, names or repository literals) and the registration points.
### Changed
- **`gstack-owner-brief render --captain`** closes "What we need from you" with the **do it** reply (every recommendation at full scope, the plan's follow-ons included, carried through with no further ask) and invites `rec` or `do it`; `capy/skill/gstack/captain.md` documents it as step 6 and points at the controller's side.
- `README.md` (power tools), `AGENTS.md`, `docs/skills.md`, `docs/PROJECT_STRUCTURE.md`, the root router and the doctor component table register the skill; `test/fixtures/context-budget.json` gains the `captain` ceiling and the router's, grown by its one new rule; `test/catalog-budget.test.ts` and `CLAUDE.md` carry the catalog ceiling the new skill ratchets to.
## [1.91.248.0] - 2026-10-26
+4 -1
View File
@@ -629,7 +629,10 @@ occurrence escalates. Every rerun's spend is reported.
A lane that finishes writes `reports/<code>.md` (`captain/templates/report.md`): Outcome,
PR (number, head, version, checks), Needs (what the owner must do), Cost, Lesson, and
the Approvals table. The controller reads reports before it reads chat.
the Approvals table. The controller reads reports before it reads chat, and turns every
Lesson into a rule in the rules file, a line in the standing-rules file
(`captain/templates/AGENTS.md`), or a filed tool fix. The controller's own log is
append-only and timestamped (`captain/templates/NOTES.md`); the digest is derived from it.
Serial slots cost tens of minutes each, so small fixes in one area travel together: when
a lane's diff is small and open PRs touch the same area, fold them into one wave PR with
+4 -1
View File
@@ -307,7 +307,10 @@ occurrence escalates. Every rerun's spend is reported.
A lane that finishes writes `reports/<code>.md` (`captain/templates/report.md`): Outcome,
PR (number, head, version, checks), Needs (what the owner must do), Cost, Lesson, and
the Approvals table. The controller reads reports before it reads chat.
the Approvals table. The controller reads reports before it reads chat, and turns every
Lesson into a rule in the rules file, a line in the standing-rules file
(`captain/templates/AGENTS.md`), or a filed tool fix. The controller's own log is
append-only and timestamped (`captain/templates/NOTES.md`); the digest is derived from it.
Serial slots cost tens of minutes each, so small fixes in one area travel together: when
a lane's diff is small and open PRs touch the same area, fold them into one wave PR with
+38
View File
@@ -0,0 +1,38 @@
# <CREW_NAME> standing rules
The controller's own file. Scope instructions: every thread in the crew reads it. Each
rule that came from the owner keeps the date and the exact words, so lanes can cite it as
a standing approval instead of asking again. Append; never rewrite an owner's words.
## Mandate (<owner>, <date>)
"<the owner's words for what this crew is for>"
- Every <code prefix>-* thread in <project> is in the crew. New threads the owner starts
are added too.
- Watch <REPO> issues and PRs. Start lanes for severe user-reported bugs no live thread
has claimed (the severity bar is below).
- Severity first: S1 is a broken install or upgrade, data loss, a security result that is
wrong, or `BASE` red. S2 is a broken workflow with no workaround. Everything else is
batched.
## Merging (<owner>, <date>: "<words>")
- One controller merges, through a train (`/captain`, the merge-train section). Lanes send
READY and never merge. Standing approval to merge when green: "<words>".
- A merge outside a train costs a backstop gate; none without the owner's words.
## Spend (<owner>, <date>: "<words>")
- Pre-approved: train gates, bisects, the backstop, screens, focused paid cases. Cap per
rerun: <amount>. Over the cap is a fresh ask.
## Asks to the owner (<owner>, <date>: "<words>")
- Plain language, each option with what it is, why it matters, what each answer does and
the cost. The recommended one-word reply is the default. Every ask ends with "do it":
every recommendation at full scope, follow-ons included, no further ask.
## Parallelism (<owner>, <date>: "<words>")
- Keep about <N> lanes working on disjoint files whenever there is backlog. Serial steps
stay serial only where the tooling requires it: one train's slots, version stamps, edits
to a shared file.
## Decisions log
- <date> <time> · <decision> · "<owner's exact words>" · covers: <scope>
+17
View File
@@ -0,0 +1,17 @@
# <CREW_NAME> notes (append-only, newest at the bottom)
The controller's working log. One line per event with a UTC timestamp. Never edited
after the fact; a correction is a new line. The digest is derived from here, never the
other way round.
Shapes:
- `<YYYY-MM-DD HH:MMZ> READY #<n> <sha12> from <thread> (<--screen|--no-screen>; flaky: <none|cases>)`
- `<ts> TRAIN <nonce> built: <order>; gate <run id>`
- `<ts> TRAIN <nonce> verdict GREEN (FLAKY <k>: <cases>) | RED (<cases>) | INCOMPLETE (<why>)`
- `<ts> SLOT <i> called: #<n> v<X> base <sha12>` · `<ts> MERGED #<n> as v<X> (<sha12>)`
- `<ts> FREEZE <why>; backstop <run id>` · `<ts> FREEZE cleared by #<n>`
- `<ts> OWNER "<exact words>" on <what was asked> (via <who>)`
- `<ts> LANE <thread> started: <scope>, owns <files>` · `<ts> LANE <thread> done: reports/<thread>.md`
- `<ts> INCIDENT <what>; <what was done>; <lesson or rule it produced>`
---
+7 -2
View File
@@ -4,7 +4,9 @@ This file belongs to gstack's Capy skill (the folder the owner copies into their
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.
`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
@@ -73,6 +75,9 @@ project can follow it:
5. **What we need from you.** One decision, the options and their costs, the pick, and the one-word reply.
The recommended reply is the default (`rec` for an autoplan gate). Never offer "the plan as first
written" as the default.
6. **"do it".** Every ask ends with a "do it" reply: take every recommendation at full scope, the follow-ons
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:
@@ -83,7 +88,7 @@ gstack-owner-brief render --captain --out <run dir>
It prints a header line ("Decision needed … Reply **rec** …"), the four plan sections (each capped, with a
pointer to the plan for the rest), and "What we need from you", which lists every choice `rec` takes, in
words. The last line, "For the relaying agent", carries the run, `gate_rev`, `rec_hash` and the exact
words, and closes with the "do it" reply. 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
+68 -2
View File
@@ -22,6 +22,20 @@ rebuilt at most once. Every decision the owner makes travels through the control
fixed shape and is recorded in the owner's words. Flakes are logged in a ledger and fixed
by a daily pass; nothing is retried into green.
## Roles
- **The owner** decides what only the owner can decide: product and behaviour, spend over
a cap, anything published outside the repository, thresholds and budgets, closing a
contribution, anything destructive. Every answer is recorded in the owner's words.
- **The controller** (the Captain) runs the crew: starts lanes on disjoint files, keeps the
rules file, the digest and the notes, validates READYs, builds and merges trains, calls
slots, relays asks to the owner in the three shapes, runs the flaky pass, and decides
everything that needs no access, credentials, spend over cap or a cited approval.
- **A lane** (a crew thread) owns named files, builds on its own branch and machine,
runs /ship, sends one READY block, stamps on call, writes one report, and never merges.
- **The controller never commits to a lane's branch**, and a lane never pushes to another
lane's. One committer per shared tree.
## Drive layout
The Captain's drive is mounted read-write on every crew thread, so it is the handoff
@@ -29,10 +43,10 @@ surface. Keep it small and predictable:
| Path | Who writes | What it holds |
|---|---|---|
| `AGENTS.md` | the controller | standing rules and the owner's decisions, each with date and words |
| `AGENTS.md` | the controller | standing rules and the owner's decisions, each with date and words ([template](../captain/templates/AGENTS.md)) |
| `CREW.md` | the controller | the lanes' contract ([template](../captain/templates/CREW.md)) |
| `DIGEST.md` | the controller | Needs you / New / In progress, rewritten every turn ([template](../captain/templates/DIGEST.md)) |
| `NOTES.md` | the controller | the controller's own plan and working notes |
| `NOTES.md` | the controller | the append-only timestamped log the digest is derived from ([template](../captain/templates/NOTES.md)) |
| `skills/<name>/SKILL.md` | the controller | crew procedures the lanes load (the runbooks) |
| `reports/<code>.md` | each lane, once | Outcome, PR, Needs, Cost, Lesson, Approvals ([template](../captain/templates/report.md)) |
| `handoffs/` | lanes | datasets and specs the next lane needs; `handoffs/flaky-ledger.md` is the flaky ledger ([template](../captain/templates/flaky-ledger.md)) |
@@ -73,6 +87,34 @@ cases are already in the ledger; and the lane declares the head frozen. A green
is not a READY. A cheap test tier is not merge evidence. The controller refuses a block
that misses any of this and sends the lane back, never into a train.
### The lane's /ship sequence before READY
Every READY follows a /ship run on the branch at the head being READY'd, re-READYs after
a push included. The sequence that works unattended on Capy:
1. Install for ship (`bash scripts/install.sh --for ship` from the project directory; a
planning-only install does not admit /ship).
2. Push the final head first. Everything below binds to it; a later push makes the
receipt, the pregate record and the review binding stale.
3. Launch with the printed `GSTACK_LAUNCH` assignments (unattended session kind, state
root, Capy flag) and follow ship/SKILL.md with the Capy adaptations: take every
recommended option, leave consents and gates pending, never push or merge from the run.
4. Pregate on the still tree: preflight, then the test tier on a remote runner when the
machine is small; clear the macOS and Windows lanes with this head's own green CI run
URLs. Never run a bare pregate afterwards (it resets the regen check).
5. Reviews: mint tokens, run both passes read-only, finish them so the review log binds
to the current tree.
6. Docs audit: snapshot the documentation candidates read-only and record the vetted
section.
7. Body and receipt: compose the PR body (What changed, Why, Approvals first on Capy),
write the ship receipt into it, post body and title.
8. Terminal line: `gstack-ship-receipt result --pr <n>` prints
`GSTACK_RESULT: skill=ship status=complete`.
9. READY: `gstack-ship-queue ready --pr <n> --pregate <pregate.json> --screen|--no-screen`;
send the printed block.
10. After the slot call, the stamp commit keeps the pin's receipt; re-READY with the same
receipt, no second /ship.
## Merge trains
gstack's queue tool drives trains when the repository's `.gstack/ship-policy.json` carries
@@ -95,6 +137,14 @@ anything else goes to a repair lane. An infra-only census earns one re-dispatch.
backstop gate runs on every push to the base branch and freezes the queue when it finds
a tree no green train covered; one repair PR at its exact head clears the freeze.
**Slot 2 and later.** The base branch already carries the earlier slots' stamps, so a
stamp on the bare pin leaves the PR conflicting on the CHANGELOG. The lane merges the base
into its pin first (expect a CHANGELOG-only conflict; resolve it with the changelog merge
helper, grep for conflict markers before stamping), then restamps, then commits only the
merge and the stamp files. In practice slot 1 needs the same merge whenever the base moved
after the pin was taken. A stamp commit with one extra byte freezes the queue for every
lane behind it.
Options the controller writes into CREW.md rather than inheriting: squash versus merge
commits; lane stamps versus controller stamps; gate-ahead (slot i+1 gates on slot i's
head while i merges); the severe race (a slot called for several severe PRs, first
@@ -136,6 +186,22 @@ failure red before green. It never reruns a gate and never touches a threshold,
count or case kind. On Capy, create it with the automation tool, scheduled in the owner's
timezone, running as a thread in the crew so its report lands in `reports/`.
## The owner's device
Some actions exist only in the owner's logged-in browser or on the owner's machine:
approving a deployment review, switching the GitHub account a run used, a local-only
build. A lane that needs one asks the controller; the controller does it through the
owner's device (on Capy, a paired device and the owner's browser) and records what it
did. Before approving a deployment, check the run's pending deployments list; approve
only the run the lane named.
## The lessons loop
Every report ends with one Lesson. The controller reads lessons before chat, and each
one becomes a rule in CREW.md, a line in AGENTS.md, or a tool fix filed as an issue;
nothing stays a lesson for long. Rules carry the thread that produced them, so a new lane
can read the incident behind the rule.
## Setting it up on Capy
1. Create the Captain and give it the drive layout above; paste the filled `CREW.md`
+3 -2
View File
@@ -4,7 +4,8 @@
* one-word reply, then Before → after, Why it matters, What caused this work,
* Big decisions (each read from the reviewed plan through the shared heading
* parser) and What we need from you (built from gate.json: every choice `rec`
* takes, in words). Gate ids, gate_rev and rec_hash appear only on the last
* takes, in words, closed by the "do it" reply: rec at full scope, follow-ons
* included, carried through with no further ask). Gate ids, gate_rev and rec_hash appear only on the last
* line, which is for the relaying agent. A plan missing a section yields a
* brief that says it is not ready for approval and has no reply invitation
* (CAPTAIN_BRIEF_INCOMPLETE). Called by bin/gstack-owner-brief render --captain.
@@ -98,7 +99,7 @@ export function renderCaptainBrief(inputs: BriefInputs, o: { outDir: string }):
const alt = example?.options.find(x => x.key !== example.recommended);
need.push(`- **Change something:** say which choice and what you want instead, in words${example && alt ? ` (for example "${clip(stripRef(example.title), 60)}: ${plainOption(alt.text)}")` : ''}. A change that alters the plan reopens its review before anything is built.`);
need.push('- **Reject:** nothing is built.', '');
if (!missing.length) need.push('Reply: **rec**', '');
if (!missing.length) need.push(`- **do it:** rec at full scope: every recommendation above, the follow-ons the plan names (docs, trains, cleanups) included, carried through to done with no further ask.`, '', 'Reply: **rec**, or **do it**', '');
}
const tokens = list.items.map(i => `${i.id}${i.recommended}`).join(' ');
const trailer = `_For the relaying agent, not the person:_ run ${list.run}, gate_rev=${list.gate_rev}, rec_hash=${hash}; rec resolves to ${tokens}; plan ${plan.file ?? '(missing)'} (sha256 ${plan.sha}). Answer with ` +
+2 -1
View File
@@ -87,7 +87,7 @@ describe('renderCaptainBrief', () => {
expect(b.missing).toEqual([]);
const t = b.text;
expect(t.split('\n')[0]).toStartWith('**Decision needed:** approve the plan "Widget export fix". Reply **rec**');
const order = ['### Before → after', '### Why it matters', '### What caused this work', '### Big decisions', '### What we need from you', 'Reply: **rec**'].map(s => t.indexOf(s));
const order = ['### Before → after', '### Why it matters', '### What caused this work', '### Big decisions', '### What we need from you', '- **do it:** rec at full scope', 'Reply: **rec**, or **do it**'].map(s => t.indexOf(s));
expect(order.every((v, i) => v > 0 && (i === 0 || v > order[i - 1]!))).toBe(true);
expect(t).toContain('1. Split the exporter into its own module: keep the plan as written (a reviewer suggested changing it).');
expect(t).toContain('2. Cap memory at 256 MB: cap at 256 MB (changes the plan).');
@@ -104,6 +104,7 @@ describe('renderCaptainBrief', () => {
expect(b.missing).toEqual(['Why it matters']);
expect(b.text).toStartWith('**Not ready for approval:** the plan "Widget export fix" is missing Why it matters.');
expect(b.text).not.toContain('Reply: **rec**');
expect(b.text).not.toContain('**do it:**');
expect(b.text).toContain('_Not written for this plan yet._');
});
+1 -1
View File
@@ -15,7 +15,7 @@ const read = (rel: string) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
const skill = read('captain/SKILL.md');
const tmpl = read('captain/SKILL.md.tmpl');
const flat = skill.replace(/\s+/g, ' ');
const TEMPLATES = ['CREW.md', 'DIGEST.md', 'flaky-ledger.md', 'report.md', 'READY.md', 'slot-call.md', 'plan-ask.md', 'merge-ask.md', 'automation-flaky-pass.md'];
const TEMPLATES = ['CREW.md', 'AGENTS.md', 'NOTES.md', 'DIGEST.md', 'flaky-ledger.md', 'report.md', 'READY.md', 'slot-call.md', 'plan-ask.md', 'merge-ask.md', 'automation-flaky-pass.md'];
describe('/captain skill', () => {
test('frontmatter names the skill and its /crew alias trigger', () => {