mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-10 18:50:18 +01:00
Merge remote-tracking branch 'origin/main' into fix/pregate-sealed-push-verify
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> # Conflicts: # CHANGELOG.md
This commit is contained in:
commit
2b782a48cb
104 files changed
+5294
-266
No files matched your search
+4
-3
@@ -181,9 +181,10 @@ The agent should:
|
||||
4. Verify with a test spawn
|
||||
|
||||
**Native skills or ACP.** `./setup --host openclaw` (or `./setup` run inside an
|
||||
OpenClaw session) gives OpenClaw's own agent gstack's skills: one `gstack`
|
||||
router skill in `$OPENCLAW_STATE_DIR/skills`, rendered with OpenClaw's tool
|
||||
names and pointing at the installed gstack, so the agent runs gstack's
|
||||
OpenClaw session) gives OpenClaw's own agent gstack's skills: the five task
|
||||
skills (review, investigate, qa, ship, plan-eng-review) listed directly and one
|
||||
`gstack` router skill for the rest in `$OPENCLAW_STATE_DIR/skills`, rendered
|
||||
with OpenClaw's tool names and pointing at the installed gstack, so the agent runs gstack's
|
||||
workflows and helpers itself. Use ACP when the work should happen in a Claude
|
||||
Code session with the full install (hooks, Claude tool names); use native
|
||||
skills when the OpenClaw agent should do it. Both can be installed together.
|
||||
|
||||
+8
-5
@@ -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).
|
||||
|
||||
+10
-1
@@ -155,7 +155,16 @@ and `/proc` start time; elsewhere `ps -o lstart`), because a pid is reused after
|
||||
a reboot or a machine wake. `bin/gstack-upgrade`, and so the session-start
|
||||
self-upgrade, refuses with [UPGRADE_BUSY](troubleshooting.md#upgrade-busy)
|
||||
while a lease held by a live process outside its own chain exists, naming each
|
||||
holder as `pid <p> (<age>m <command>)`. The team-mode SessionStart hook
|
||||
holder as `pid <p> (<age>m <command>)`. The install-mode self-upgrade also
|
||||
defers while its own chain holds a live lease under 120 minutes old. An install
|
||||
run from inside a workflow (/ship's docs step running `install.sh --for
|
||||
document-release`, #3571) prints `SELF_UPGRADE: skipped (a gstack run in this
|
||||
session holds a workflow lease on this installation: ... (UPGRADE_BUSY))`,
|
||||
leaves the checkout where the run started and stays ok with `behind=`. Each
|
||||
`gstack-skill-start` renews the lease. On Capy the leased pid is the machine's
|
||||
long-lived `capy-server`, so the deferral ends when the lease turns 120
|
||||
minutes old, not when the process exits; `bin/gstack-upgrade` run by hand
|
||||
moves the checkout sooner. The team-mode SessionStart hook
|
||||
(`bin/gstack-session-update`) honors the same leases: it logs
|
||||
`SKIP leased pid=<p>/<age>m` to `analytics/session-update.log` and tries again at
|
||||
the next session start, without writing its hourly throttle stamp. A lease whose
|
||||
|
||||
@@ -69,3 +69,26 @@ Raw transcripts stay under `<out>/runs/`.
|
||||
- **Trials are fixed before the run.** No trials are added after a result. One same-tree rerun of a red task is
|
||||
allowed and recorded as flaky; a second failure stands.
|
||||
- **Blinded adjudication.** A disputed answer goes to a fresh judge that sees the task and the answer, not the arm.
|
||||
|
||||
## Notes for implementers (2026-10-28)
|
||||
|
||||
Offered to gstack-evals' Lane A (interface v0.4). Nothing above changes; these are conventions gstack's task files
|
||||
already assume, and one optional field.
|
||||
|
||||
- **State root.** gstack skills keep state under one root (docs/state-root.md): `GSTACK_STATE_ROOT` wins, then
|
||||
`GSTACK_HOME`. Set both to `<work dir>/.gstack-home` for the agent, `setup` and `check`; `GSTACK_HOME` alone loses
|
||||
to a `GSTACK_STATE_ROOT` inherited from the caller's shell, and the agent then writes the caller's real state. A
|
||||
task's `seed.sh` creates `.gstack-home` and lists it in `.git/info/exclude`, so `git diff` checks ignore it and
|
||||
`check` reads state there (/learn's `add` and `prune-stale`, /office-hours' design-doc and mockup checks).
|
||||
- **`resumable`** (optional, in `verdict.json` and any error record): `true` when an INCOMPLETE run can be finished
|
||||
with `--resume <out>`, which runs only cells that never ran, under the same lock and trial count, so it adds no
|
||||
trials after a result; `false` for a terminal stop, which needs a fix and a fresh run. A missing field reads as
|
||||
`false`.
|
||||
- **Lock.** A paid run needs `tasks.lock.json` committed beside the tasks file; `/write-skill`'s diet step 6 runs
|
||||
`--lock` and commits it.
|
||||
- **Sidecar.** Extensions live in `tasks.ext.json` beside the tasks file, keyed by task id, so this format stays
|
||||
unchanged. gstack's free test checks only that each entry names a task in `tasks.json` and uses known keys.
|
||||
- **Interactive tasks.** /office-hours' `must` patterns grade a headless run that ends where the skill stops for
|
||||
input, as upstream skill-diet's runs do. On a host that answers questions from the sidecar's `user` replies, the final answer is
|
||||
the session's last message, so the sidecar's `judge` line carries the behavior there. Open for Lane A: grade
|
||||
`must` over the run's assistant messages on such hosts, or keep it on the final answer and rely on `judge`.
|
||||
+24
-2
@@ -24,6 +24,14 @@ neither does `./setup --local`.
|
||||
|
||||
**What it writes.** Copies in the harness's own skills folder, never edits to
|
||||
`openclaw.json`, Hermes's `config.yaml` or any context file you did not name.
|
||||
A context block is opt-in: `gstack-harness register openclaw|hermes|generic
|
||||
--context <file>` writes the marked block into that file only, after printing
|
||||
the file and its scope (`profile` inside the harness's own home, else
|
||||
`project`). Setup, upgrades and refresh never add one, a block you delete stays
|
||||
deleted, a second `--context` file keeps the first (the ledger keys blocks by
|
||||
file), and uninstall restores each file byte for byte. It refuses `SOUL.md`,
|
||||
and for Hermes a new file that would outrank rules Hermes loads now
|
||||
(`.hermes.md`/`HERMES.md` load before `AGENTS.md`): `HARNESS_CONTEXT_REFUSED`.
|
||||
Every file goes into a ledger, `$GSTACK_STATE_ROOT/harness/<id>.ledger`, with
|
||||
its sha256. Registering again changes only what changed; a folder or skill name
|
||||
gstack did not write stops it (`HARNESS_UNOWNED_ENTRY`); `gstack-uninstall`
|
||||
@@ -35,10 +43,24 @@ when it is the one gstack wrote; an edited block is left for you. `./setup
|
||||
`gstack-harness status` lists them and runs the harness's own skill list where
|
||||
it has one (`visible to harness: yes`). Errors: [troubleshooting](troubleshooting.md#agent-harnesses-gstack-harness).
|
||||
|
||||
**Registered, listed, used.** These are three separate facts, and this page
|
||||
claims only the first two. *Registered*: gstack's files are in the harness's
|
||||
skills folder and its ledger (`gstack-harness status`). *Listed*: the harness
|
||||
puts the skill in the skills section of the model's prompt (`visible to
|
||||
harness: yes`, from the harness's own skill list). *Used*: in a task that fits,
|
||||
the model opens the matching skill and runs its first step. Use rates are
|
||||
pending HARN-5's fixed panel (OpenClaw and Hermes chat mode, five trials per
|
||||
cell). When they land they are measured under the eval adapter's
|
||||
`GSTACK_SESSION_KIND=spawned` marker, which auto-decides gstack's questions, so
|
||||
they say nothing yet about interactive sessions. Two levers aim at use:
|
||||
each router line opens with the skill's own trigger sentence ("Use when asked
|
||||
to ..."), and each Hermes description opens with a trigger list of at most 57
|
||||
characters, the part of a description Hermes's skills index shows.
|
||||
|
||||
| Harness | What gstack registers | Install | Verify | Remove |
|
||||
|---|---|---|---|---|
|
||||
| OpenClaw | one `gstack` router skill (one description line in every request; its body lists every skill and its path) in `${OPENCLAW_STATE_DIR:-~/.openclaw}/skills/gstack`; `--skills all` registers every skill as its own top-level folder | `./setup --host openclaw`, or `./setup` inside OpenClaw | `openclaw skills list --json` shows `gstack` (source `openclaw-managed`, eligible) | `gstack-harness unregister openclaw` |
|
||||
| Hermes | every skill, as `gstack-<skill>` in one `gstack` category, in `${HERMES_HOME:-~/.hermes}/skills/gstack` (Hermes opens only skills it has indexed) | `./setup --host hermes`, or `./setup` inside Hermes | `hermes skills list` shows the `gstack-*` skills in category `gstack` | `gstack-harness unregister hermes` |
|
||||
| OpenClaw | default `--skills hybrid`: `gstack-review`, `gstack-investigate`, `gstack-qa`, `gstack-ship` and `gstack-plan-eng-review` as top-level skills plus one `gstack` router skill whose body lists every other skill and its path, in `${OPENCLAW_STATE_DIR:-~/.openclaw}/skills`. With OpenClaw 2026.10.6's 19 eligible bundled skills, hybrid's skills section is 10,290 characters, full format, under its 18,000-character budget; `--skills all` (17,971, compact format, descriptions cut) lists every skill as its own folder, `--skills router` (6,701) only the router | `./setup --host openclaw`, or `./setup` inside OpenClaw | `openclaw skills list --json` shows `gstack` and the five task skills (source `openclaw-managed`, eligible) | `gstack-harness unregister openclaw` |
|
||||
| Hermes | every skill, as `gstack-<skill>` in one `gstack` category, in `${HERMES_HOME:-~/.hermes}/skills/gstack`. Hermes's skills index lists only skills registered as folders, so a router would leave the rest out of the listing (`skill_view` still loads an unlisted skill by name, but nothing tells the model it exists) | `./setup --host hermes`, or `./setup` inside Hermes | `hermes skills list` shows the `gstack-*` skills in category `gstack` | `gstack-harness unregister hermes` |
|
||||
| dots (feder-cr) | the `gstack` router in `/home/dot/skills/gstack`, which the Dot lists every turn | `./setup` on the Dot's computer, or `gstack-harness register dots` | the Dot's Skills page, or `GET /skills` | `gstack-harness unregister dots` |
|
||||
| QM | **not available yet.** QM imports a skill pack by cloning the whole repository and caps it at 32 MiB and 5,000 files; gstack's repository is about 73 MiB, so `gstack-harness register qm --yes` stops before sending anything ([why](troubleshooting.md#harness-qm-pack-too-large)) | meanwhile, paste `gstack-harness register generic --print` into the agent's instructions | ask it for a gstack skill | delete the pasted text |
|
||||
| Muse Code, Grok Build | nothing: both read `~/.claude/skills`, so the Claude install (`./setup`) is what they see, with Claude tool names | `./setup` | `gstack-harness status` runs `muse skills list` / `grok inspect` and reports *discovered (Claude-rendered, unverified)* | `gstack-uninstall` |
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
# /ship token budget and fresh-context handoffs
|
||||
|
||||
`/ship --token-cap 25M` (or `GSTACK_SHIP_TOKEN_CAP=25M`) bounds what one /ship run spends, and
|
||||
long runs hand their heavy segments to fresh-context children. The helper is
|
||||
`bin/gstack-ship-budget`; the skill's procedure is `ship/sections/budget-handoff.md`.
|
||||
There is no default cap: without one, /ship meters usage when it can and prints it, and
|
||||
nothing else changes.
|
||||
|
||||
## What the cap guarantees
|
||||
|
||||
It is an **admission cap**, not a hard ceiling. Before every step and every dispatch batch,
|
||||
`check` admits the work only when
|
||||
|
||||
used + open reservations + unknown charges + projection + tail reserve + stop overhead <= cap
|
||||
|
||||
Exactly at the cap is admitted; one token over is not. A parallel review batch is reserved as
|
||||
one sum before any of it starts. Admitted work can still cost more than its projection, and
|
||||
gstack cannot stop a host mid-turn from bash, so a run can finish above the cap by the
|
||||
misprojection of work already admitted. The tail reserve (default 2M, `--reserve <n>`) keeps
|
||||
room for the receipt steps, and the stop overhead (calibrated) keeps room to stop cleanly.
|
||||
|
||||
## Quickstart
|
||||
|
||||
**Capy.** Read the root counter in one execute program that returns only that number:
|
||||
|
||||
```js
|
||||
return (await tools.thread.get({ thread_id: "this" })).cost.tokens;
|
||||
```
|
||||
|
||||
then
|
||||
|
||||
```bash
|
||||
gstack-ship-budget start --record <record> --token-cap 25M --reading <n>
|
||||
gstack-ship-budget check --run <id> --step 6 --next 7 --reading <n> [--prior-reading <n from your previous call>]
|
||||
```
|
||||
|
||||
The counter rolls up every subagent of the thread, outside voices included; Capy exposes no
|
||||
per-subagent counter, so other work in the same thread counts too (it only stops earlier).
|
||||
|
||||
**Claude Code.** `gstack-ship-budget start --record <record> --token-cap 25M --session <id>`. Without the
|
||||
session id, `start --meter claude` prints a nonce: echo it once, then rerun with `--nonce <value>`.
|
||||
The helper reads the session journal, its subagent journals and the journals of headless
|
||||
children it launched; you never pass `--reading`.
|
||||
|
||||
**Anywhere else.** `--meter estimate` projects with the calibration and prints `meter=estimate`.
|
||||
With a cap set and no readable meter, start refuses
|
||||
([BUDGET_METER_UNAVAILABLE](troubleshooting.md#budget-meter-unavailable)).
|
||||
|
||||
## The BUDGET line
|
||||
|
||||
BUDGET: used=<n> reserved=<n> unknown=<n> admission_cap=<n|none> tail=<n> unit=<u> meter=<m> epoch=<n> call=<n|unknown> next=<id> projected=<n> verdict=continue|delegate|stop
|
||||
|
||||
| Field | Meaning |
|
||||
|---|---|
|
||||
| `used` | Metered usage since start (all epochs), or settled estimates under `meter=estimate` |
|
||||
| `reserved` | Open reservations: admitted steps, started dispatches, and each open child's sub-cap counted as max(sub_cap, child_used), never both |
|
||||
| `unknown` | Charges the meter cannot see (an external-CLI outside voice on Claude Code), counted at their reservation |
|
||||
| `admission_cap` | The cap, or `none` |
|
||||
| `tail` | Tail reserve plus stop overhead, held back from every admission |
|
||||
| `unit`, `meter`, `epoch` | The meter contract: `capy-tokens` (root counter), `claude-usage-tokens` (journal usage), `estimate-tokens`; a meter change opens a new epoch |
|
||||
| `call` | The per-call charge: two Capy readings in consecutive calls, or the last Claude Code call's usage |
|
||||
| `next`, `projected` | What the check was for and its calibrated projection |
|
||||
| `verdict` | `continue` (exit 0, prints `RESERVATION:` under a cap), `delegate` (exit 0), `stop` (exit 3) |
|
||||
|
||||
Any exit other than 0 means the next step does not start. Exit 2 is a usage or validation
|
||||
error on stderr with the code, the value and the repair. GSTACK_RESULT appears only on a stop,
|
||||
an incomplete close and a child's return; /ship's real terminal line stays Step 22's.
|
||||
|
||||
## Dispatches
|
||||
|
||||
Under a cap, `gstack-review-log --start` refuses a dispatch without an unspent, one-shot
|
||||
reservation ([BUDGET_CHECK_SKIPPED](troubleshooting.md#budget-check-skipped)):
|
||||
|
||||
```bash
|
||||
gstack-ship-budget check --run <id> --step 9 --next dispatch:specialist,red-team --reading <n>
|
||||
gstack-review-log --start review --reservation <id>
|
||||
```
|
||||
|
||||
Roles are review-depth's: `specialist`, `red-team`, `adversarial-native`, `outside-voice`,
|
||||
`fix-check`. `--finish <token>` settles the reservation at the dispatch's completion receipt.
|
||||
`close` audits after the fact: each review-log row of the run with no reservation gets one
|
||||
BUDGET_CHECK_SKIPPED line and the run ends status=incomplete for the owner to accept
|
||||
(`close --approved-by <who> --words "<their words>"`); a reservation is never created after the fact.
|
||||
|
||||
## Delegation
|
||||
|
||||
At start, a per-call charge of 100k or more delegates the whole /ship. Before Step 7 and before
|
||||
Step 12, 150k or more (or an unknown charge) delegates the next segment (7–11.5, 12–22):
|
||||
|
||||
1. `gstack-ship-budget handoff --run <id> --from 12 --to 22 --base origin/<base> --reading <n>` writes a
|
||||
brief (mode 0600, under the run directory) with the pins (HEAD, merge base, working tree,
|
||||
reviewed tree), the record with its six headings and start tokens, the decisions in scope
|
||||
with the owner's words, the decision trail, the budget and the return contract. It reserves
|
||||
the child's sub-cap: min(cap − used − reserved − tail, the segment estimate), or `--sub-cap`.
|
||||
2. The child is a Capy subagent on the shared machine, or on Claude Code
|
||||
`gstack-ship-budget launch --in <brief>`: a background `claude -p` (resolved through lib/claude-bin.ts)
|
||||
with explicit permission flags and the return file named.
|
||||
3. The child runs `verify-handoff` first; a moved pin refuses
|
||||
([HANDOFF_PIN_MOVED](troubleshooting.md#handoff-pin-moved)). It admits against its sub-cap
|
||||
with child_used = reading − reading at handoff, is the only writer of the record, and never
|
||||
delegates again: a delegate rule stops it. It ends with `return` (at most 40 lines).
|
||||
4. `gstack-ship-budget reconcile --run <id> --return <file> --reading <n>`; idempotent per handoff hash,
|
||||
so a duplicate return charges nothing twice. A refused, incomplete or interrupted child
|
||||
exits 3: regenerate the handoff or stop.
|
||||
|
||||
`--delegate never` turns delegate into stop for hosts without children.
|
||||
|
||||
## Stop and resume
|
||||
|
||||
A stop prints
|
||||
|
||||
STOP: reason=budget_exhausted spent=<n> reserved=<n> unknown=<n> admission_cap=<n> next=<id> projected=<n>
|
||||
RECOVER: /ship --resume <record> --token-cap <used + reserved + unknown + projected tail>
|
||||
GSTACK_RESULT: skill=ship status=incomplete run=<dir>
|
||||
|
||||
Raising the cap is the owner's call; the resume journals who raised it and their words:
|
||||
|
||||
```bash
|
||||
gstack-ship-budget resume --record <record> --token-cap <n> --approved-by Garry --words "raise it to finish" --reading <n>
|
||||
```
|
||||
|
||||
Every command takes `--run` or `--record`; runs are never found by branch or directory, and a
|
||||
/ship without `--resume` or `--continue` always starts a new run. The ledger and record are
|
||||
mirrored to a private Drive checkpoint (`GSTACK_SHIP_BUDGET_CHECKPOINT_DIR`, else the
|
||||
project-user Drive folder on Capy) bound to the run id and their hashes, so after a machine
|
||||
change `resume --run <id>` restores both. A corrupt ledger
|
||||
([BUDGET_LEDGER_INVALID](troubleshooting.md#budget-ledger-invalid)) is rebuilt from the
|
||||
record's block with `--rebuild-from-record`; open reservations are then charged as unknown.
|
||||
|
||||
## Calibration
|
||||
|
||||
`lib/ship-budget-calibration.json` holds per-meter constants (per call, per dispatch, stop
|
||||
overhead, calls and dispatches per step) fitted from metered runs: each constant is the
|
||||
largest observation times 1.5, rounded up to 10,000 tokens. The leave-one-out test
|
||||
(test/ship-budget-calibration.test.ts) covers the per-call constant only, the one observed
|
||||
across three runs; per-dispatch has two observations and stop overhead one. Only the Capy
|
||||
meter has observations; the Claude Code and estimate units are seeded from it until a
|
||||
Claude-metered /ship completes.
|
||||
|
||||
## Token cap and USD spend cap
|
||||
|
||||
A lane's `--spend-cap` (USD) bounds paid gates and evals through lib/spend-ledger.ts; `--token-cap`
|
||||
bounds the /ship run's model tokens through its own ledger. They are independent: set both
|
||||
when a lane runs paid gates inside /ship, and neither converts into the other.
|
||||
|
||||
## Claiming savings
|
||||
|
||||
Savings count completed ships only. Preregister the comparison with `/eval-plan` (arms, bars,
|
||||
stop rules) before claiming a saving from the cap or from delegation.
|
||||
|
||||
## Result codes
|
||||
|
||||
[BUDGET_CAP_REACHED](troubleshooting.md#budget-cap-reached) ·
|
||||
[BUDGET_READING_MISSING](troubleshooting.md#budget-reading-missing) ·
|
||||
[BUDGET_READING_REJECTED](troubleshooting.md#budget-reading-rejected) ·
|
||||
[BUDGET_METER_UNAVAILABLE](troubleshooting.md#budget-meter-unavailable) ·
|
||||
[BUDGET_LEDGER_INVALID](troubleshooting.md#budget-ledger-invalid) ·
|
||||
[BUDGET_CHECK_SKIPPED](troubleshooting.md#budget-check-skipped) ·
|
||||
[HANDOFF_PIN_MOVED](troubleshooting.md#handoff-pin-moved)
|
||||
@@ -953,6 +953,22 @@ receipt when the base moves:
|
||||
| a merge of the base whose resolution changed product code | a fresh /ship (`RECEIPT_CARRY_PRODUCT_CHANGED`) |
|
||||
| a rebase or force-push onto a tree the receipt covers, product diff against the new base unchanged | carries (`carry.ancestry: rewritten`) |
|
||||
| any other rebase or force-push | a fresh /ship (`RECEIPT_CARRY_TREE_MISMATCH`) |
|
||||
| a stacked PR whose parent landed (squash or merge), retargeted to the base with the base merged in, HEAD's product diff exactly the one reviewed against the parent branch | carries (reason `stack parent <branch> landed`) |
|
||||
|
||||
**Stacked PRs (#3570).** A child PR's receipt is written against its parent
|
||||
branch, so its `base` names that branch and its `product_patch_id` is the
|
||||
child's own range. After the parent lands, the PR is retargeted to the base, and
|
||||
/ship's carry check runs with `--base <base>`. When that base differs from the
|
||||
receipt's, the carry compares the reviewed range (the recorded
|
||||
`product_patch_id`, or for an older receipt the range against
|
||||
`origin/<parent>` while that branch still resolves) with HEAD's whole product
|
||||
diff against the base, computed the way `write` records it, so a parent's
|
||||
release-metadata change that never reached the base counts too. Equal means
|
||||
the parent fully landed and the child is unchanged, so the receipt carries
|
||||
and from then on names the base it was carried to. A parent that has not landed, a child edited
|
||||
since, or a parent that landed with changes the child never merged all leave a
|
||||
different diff and refuse with `RECEIPT_CARRY_PRODUCT_CHANGED`. An older
|
||||
receipt whose parent branch is gone refuses with `RECEIPT_CARRY_REFUSED`.
|
||||
|
||||
Since the release that added `review_scope`. A `/ship` receipt names the head it
|
||||
reviewed, so a conflict-only merge of the base or a test-only fix used to
|
||||
|
||||
+4
-2
@@ -906,8 +906,10 @@ gstack's own templates and for any skill folder you own.
|
||||
- **Drift.** `test/skill-bin-drift.test.ts` fails when a template or rendered skill calls a gstack bin or flag that
|
||||
does not exist (`bun run scripts/skill-bin-drift.ts --skill <name>` for one skill). Its allowlist only shrinks.
|
||||
- **Full diet.** Report first, write the eval tasks before any cut
|
||||
(`test/fixtures/skill-evals/<skill>/tasks.json`), cut, then prove before vs after vs no skill with the shared skill
|
||||
eval (`docs/designs/SKILL_EVAL_CONTRACT.md`). A diet ships only when no task that passed before fails after.
|
||||
(`test/fixtures/skill-evals/<skill>/tasks.json`), lock them (`gstack-evals skill-eval --lock`, then commit
|
||||
`tasks.lock.json`; the shared eval refuses a paid run without it), cut, then prove before vs after vs no skill with
|
||||
the shared skill eval (`docs/designs/SKILL_EVAL_CONTRACT.md`). A diet ships only when no task that passed before
|
||||
fails after.
|
||||
|
||||
`/write-skill` is not `/skillify`: `/skillify` codifies a browser scrape into a browser-skill. Adapted from
|
||||
[nicobailon/skill-diet](https://github.com/nicobailon/skill-diet) (MIT). The first diet, /learn, cut its template from
|
||||
|
||||
+144
-15
@@ -1566,11 +1566,19 @@ process has exited never blocks, and neither does a stale one: each lease
|
||||
records its process's start identity, so a pid reused after a reboot or a
|
||||
machine wake (GBRA-105's lease named pid 379, which had become dbus-daemon)
|
||||
is reclaimed instead of blocking. The upgrader's own session (the pid chain it
|
||||
runs under) never counts.
|
||||
runs under) never counts for `bin/gstack-upgrade` run by hand. A Capy install
|
||||
in install mode (`bin/gstack-capy-session`) counts it while that lease is under
|
||||
120 minutes old: an install run from inside a workflow that holds the lease
|
||||
(/ship's docs step, #3571) prints `SELF_UPGRADE: skipped (a gstack run in this
|
||||
session holds a workflow lease on this installation: pid <p> (<age>m
|
||||
<command>), so the upgrade waits until the lease is 120 minutes old; fix:
|
||||
<checkout>/bin/gstack-upgrade once the run ends (UPGRADE_BUSY))` and leaves the
|
||||
checkout where the run started. Each `gstack-skill-start` renews the lease.
|
||||
|
||||
**Fix.** Wait for those sessions to finish (`bin/gstack-lease list` shows
|
||||
every lease with its age and `alive|dead|stale`; a holder is named as
|
||||
`pid <p> (<age>m <command>)`), then re-run `bin/gstack-upgrade`.
|
||||
`pid <p> (<age>m <command>)`), then re-run `bin/gstack-upgrade`. For the
|
||||
session's own run, run the printed `bin/gstack-upgrade` after the run ends.
|
||||
When you know they are idle, `GSTACK_UPGRADE_FORCE=1 bin/gstack-upgrade`
|
||||
proceeds. At a Capy session start the same refusal appears as
|
||||
`SELF_UPGRADE: skipped (live gstack sessions hold this installation: ...)`,
|
||||
@@ -1729,8 +1737,11 @@ restate them. Rotate the secret if it was real.
|
||||
|
||||
**Meaning.** The record is kept per branch (one Capy thread ships one branch), and
|
||||
HEAD is detached or the working directory is not a git repository. Exit 3.
|
||||
From `import --autoplan <dir> --stack <b1,b2,...>` it means a listed branch exists
|
||||
neither locally nor on origin; the line names it, and no branch's record was written.
|
||||
|
||||
**Fix.** Check out the branch the work ships from, or pass `--branch <name>`.
|
||||
**Fix.** Check out the branch the work ships from, or pass `--branch <name>`. For
|
||||
`--stack`, correct or fetch the named branch and import again.
|
||||
|
||||
<a id="approval-record-unavailable"></a>
|
||||
### `gstack-approvals: ... (APPROVAL_RECORD_UNAVAILABLE)`
|
||||
@@ -1769,7 +1780,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
|
||||
@@ -1810,6 +1823,92 @@ all the command prints `SHOT_PNG: unavailable (...)` and exits 0 instead.
|
||||
|
||||
**Fix.** Screenshot `shot.html` with your own browser at 880 px wide, or use `text.md`.
|
||||
|
||||
## /ship token budget and fresh-context handoffs (gstack-ship-budget)
|
||||
|
||||
The flow, the BUDGET line field by field and the meters are in [ship-budget.md](ship-budget.md).
|
||||
Exit 2 lines go to stderr; exit 3 lines (a stop) go to stdout with the STOP, RECOVER and
|
||||
GSTACK_RESULT lines.
|
||||
|
||||
<a id="budget-cap-reached"></a>
|
||||
### `gstack-ship-budget: ... (BUDGET_CAP_REACHED)`
|
||||
|
||||
**Meaning.** The next step or dispatch batch would push used + open reservations + unknown
|
||||
charges + its projection + the tail and stop reserve past the admission cap, so it did not
|
||||
start. Exit 3. `gstack-review-log --start` prints the same code (exit 1) for a dispatch
|
||||
after the run stopped. The cap admits work; it cannot stop a turn already running, so the
|
||||
overshoot is bounded by the misprojection of work admitted before the stop.
|
||||
|
||||
**Fix.** The run ended status=incomplete with a resumable record. Raising the cap is the
|
||||
owner's call: run the printed `RECOVER:` line; its resume needs
|
||||
`--approved-by <who> --words "<their words>"`.
|
||||
|
||||
<a id="budget-reading-missing"></a>
|
||||
### `gstack-ship-budget: ... (BUDGET_READING_MISSING)`
|
||||
|
||||
**Meaning.** The run's meter is the Capy root counter, a cap is set, and the command got no
|
||||
`--reading`. Exit 2. No gstack binary can call Capy's API, so the agent reads it.
|
||||
|
||||
**Fix.** In one execute program return only
|
||||
`(await tools.thread.get({ thread_id: "this" })).cost.tokens` and pass it as `--reading <n>`.
|
||||
|
||||
<a id="budget-reading-rejected"></a>
|
||||
### `gstack-ship-budget: ... (BUDGET_READING_REJECTED)`
|
||||
|
||||
**Meaning.** The reading breaks the run's meter contract: it is lower than the last reading
|
||||
of the same epoch, `--prior-reading` is above `--reading`, or `--reading` went to a meter that
|
||||
reads its own source (Claude Code journals, the estimate). Exit 2; nothing changed.
|
||||
|
||||
**Fix.** Pass the counter the run started with (the BUDGET line names meter, unit and epoch).
|
||||
A different meter opens a new epoch that keeps prior usage:
|
||||
`gstack-ship-budget resume --run <id> --meter <m>`.
|
||||
|
||||
<a id="budget-meter-unavailable"></a>
|
||||
### `gstack-ship-budget: ... (BUDGET_METER_UNAVAILABLE)`
|
||||
|
||||
**Meaning.** A cap is set and no meter is readable: no `--reading`, no bound Claude Code
|
||||
session, no `--meter estimate`; or a `--session`/`--nonce` matched zero or several journals.
|
||||
The line names what it tried. Exit 2.
|
||||
|
||||
**Fix.** Capy: `--reading <n>`. Claude Code: `--session <id>`, or echo the nonce the line prints
|
||||
once and rerun with `--nonce <value>`. Elsewhere, with the owner's consent, `--meter estimate`.
|
||||
|
||||
<a id="budget-ledger-invalid"></a>
|
||||
### `gstack-ship-budget: ... (BUDGET_LEDGER_INVALID)`
|
||||
|
||||
**Meaning.** The ledger (`<state root>/ship-runs/<run>/budget.json`), the record's
|
||||
gstack-budget block, a handoff brief's block or a checkpoint is missing, unparseable, from
|
||||
another schema version, or not bound to the run (identity or sha256 mismatch). The line names
|
||||
the path, the schema version and the parse location. Exit 2.
|
||||
|
||||
**Fix.** After a machine change, `gstack-ship-budget resume --run <id>` restores the ledger and
|
||||
record from the Drive checkpoint. Otherwise rebuild:
|
||||
`resume --record <path> --rebuild-from-record --reading <n>`; open reservations are charged as unknown.
|
||||
|
||||
<a id="budget-check-skipped"></a>
|
||||
### `gstack-review-log: ... (BUDGET_CHECK_SKIPPED)` / `gstack-ship-budget: ... role=<r> step=<s> started=<t> spend=unknown (BUDGET_CHECK_SKIPPED)`
|
||||
|
||||
**Meaning.** Under a cap, `gstack-review-log --start` got no reservation id, an id from another run
|
||||
or a step reservation (mismatched), or an id already used (one-shot); it refused before the
|
||||
dispatch (exit 1, nothing captured). From `close`: a review-log row of this run had no
|
||||
reservation before it started; one line per dispatch, and the run ends status=incomplete (exit 3).
|
||||
|
||||
**Fix.** Run `gstack-ship-budget check --run <id> --step <s> --next dispatch:<roles>` before the
|
||||
batch and pass the printed id once with `--reservation`. A reservation is never created after the
|
||||
fact: the owner accepts the unreserved spend with `close --approved-by <who> --words "<their words>"`
|
||||
or the dispatches rerun under reservations.
|
||||
|
||||
<a id="handoff-pin-moved"></a>
|
||||
### `gstack-ship-budget: ... (HANDOFF_PIN_MOVED)`
|
||||
|
||||
**Meaning.** `verify-handoff` found the brief no longer matches its tree: HEAD, the merge base
|
||||
with the base ref, the working-tree fingerprint (`gstack-wtree`) or the reviewed tree moved, or the
|
||||
brief's sha256 is not the one the parent recorded. Each line names the pin with its expected
|
||||
and actual value. Main moving alone keeps the merge base and never refuses. Exit 3; the child
|
||||
wrote its return with status=refused.
|
||||
|
||||
**Fix.** The parent reconciles the refusal and regenerates the handoff at the current tree
|
||||
(`gstack-ship-budget handoff ...`) or stops.
|
||||
|
||||
## Browser
|
||||
|
||||
<a id="browse-runtime-version-skew"></a>
|
||||
@@ -2388,6 +2487,14 @@ and an E.164 `+14155551234` still report, so the finding means the id stands
|
||||
without a run word (`the id is 40269992077`, `40269992077 failed`) or follows
|
||||
a word that is not one (`see 40323989742`).
|
||||
|
||||
Separately, the scanner skips a bare number that is exactly a power of two
|
||||
from 2^30 through 2^49, one less than such a power, or one of eleven 32-bit
|
||||
hash and PRNG constants (Knuth's multiplicative hash, the golden-ratio
|
||||
constant, the glibc and Numerical Recipes LCGs, FNV-1a, MurmurHash2 and 3, the
|
||||
MT19937 seeder and the largest 32-bit prime; `isNumericConstant` in
|
||||
`lib/redact-patterns.ts`). Only the exact digits with no separator are exempt:
|
||||
the same digits with a `+`, spaces, dashes, dots or parentheses still report.
|
||||
|
||||
**Fix.** Write the id next to a run word (`run 40269992077`) or as an
|
||||
`/actions/runs/` link. For anything else, rerun with `--confirm <token>`
|
||||
using the `TOKEN:` line the refusal printed; MEDIUM findings do not block
|
||||
@@ -2428,6 +2535,15 @@ An address only another remote knows still reports. If the hook also printed
|
||||
`existing-email suppression was limited for this push`, it could not read the
|
||||
history in 5 seconds, so known authors may be listed too.
|
||||
|
||||
A filename with an `@` in the package@version style is not an address and
|
||||
never reports: the part after the `@` is a version or tag (one dotless label
|
||||
or a dotted version, never a domain name) followed by file extensions no
|
||||
top-level domain uses, or a dotted all-digit version followed by
|
||||
an extension that doubles as a top-level domain (`looksLikeAtFilename` in
|
||||
`lib/redact-patterns.ts`). A file named after a real address, an address at an
|
||||
all-digit domain and an address whose top-level domain doubles as a file
|
||||
extension still report.
|
||||
|
||||
**Fix.** Remove the address, or allow it for this repo:
|
||||
`git config --add gstack.redact.allowEmail <address>`. If it is your own
|
||||
address, check `git config user.email`. The allowlist only covers `pii.email`;
|
||||
@@ -4740,8 +4856,10 @@ to HEAD: the PR body has no valid receipt, the working tree has uncommitted or
|
||||
untracked changes (a carry certifies a commit, never a tree that differs from
|
||||
it), the receipt's head left HEAD's history and the receipt records no tree to
|
||||
compare (a receipt written before `head_tree`, whose head is not in this
|
||||
clone), or git could not compare the two product diffs or read the working
|
||||
tree's status. It also refuses when an owner exception the receipt records
|
||||
clone), the PR was retargeted to the base after its stack parent landed but
|
||||
the receipt records no `product_patch_id` and `origin/<parent>` no longer
|
||||
resolves, so the reviewed range cannot be recomputed, or git could not
|
||||
compare the two product diffs or read the working tree's status. It also refuses when an owner exception the receipt records
|
||||
no longer stands, and, for a receipt from a gstack that records review
|
||||
depth, when HEAD needs a deeper review than the receipt records, `--head` is
|
||||
not the checked-out HEAD, or the receipt records `gstack_version` without
|
||||
@@ -4752,9 +4870,10 @@ ready` refuses `QUEUE_RECEIPT_STALE`.
|
||||
**Fix.** Commit or stash every working-tree change, then rerun
|
||||
`gstack-ship-receipt carry --pr <n>` at the new head. When the history is
|
||||
already rewritten and the receipt predates `head_tree`, run /ship on the new
|
||||
head; next time merge the base in instead of rebasing. For a depth or
|
||||
exception refusal, run /ship on the new head (or check out `--head` and carry
|
||||
there).
|
||||
head; next time merge the base in instead of rebasing. For a retargeted stacked
|
||||
PR whose older receipt lost its parent branch, or a depth or exception
|
||||
refusal, run /ship on the new head. When `--head` is not the checked-out HEAD,
|
||||
check it out and carry there.
|
||||
|
||||
<a id="review-loop-exception-invalid"></a>
|
||||
### `REVIEW_LOOP_EXCEPTION_INVALID`
|
||||
@@ -4793,13 +4912,18 @@ registered output the pregate's regen check proved (listed under `proven`,
|
||||
checked against the same base registry), nor part of the base merge has
|
||||
a different patch than the reviewed head had. After a rebase it means HEAD has
|
||||
the reviewed tree but the base moved under it, so the diff a reviewer would see
|
||||
is not the reviewed one. The reviewed verdict does not cover HEAD, so the
|
||||
receipt is not carried. The refusal names the changed product paths.
|
||||
is not the reviewed one. For a stacked PR retargeted to the base after its
|
||||
parent landed, it means HEAD's product diff against the base is not the range
|
||||
reviewed against the parent branch: the parent has not fully landed, or the
|
||||
branch changed since. The reviewed verdict does not cover HEAD, so the
|
||||
receipt is not carried. The refusal names the changed product paths, or for a
|
||||
retargeted stacked PR the parent branch and the base.
|
||||
|
||||
**Fix.** Run /ship on the new head. The receipt carries only across a
|
||||
conflict-only base merge, a test-only change, release metadata, a registered
|
||||
output the pregate's regen check proved, or a rebase onto a covered tree with
|
||||
the recorded product diff, which the
|
||||
output the pregate's regen check proved, a rebase onto a covered tree with
|
||||
the recorded product diff, or a stacked PR's retarget once its parent fully
|
||||
landed on the base, which the
|
||||
`carry` object in the carried receipt records with the product patch-id and
|
||||
the range-diff evidence ([unattended.md](unattended.md#the-terminal-line)).
|
||||
|
||||
@@ -5180,7 +5304,7 @@ The name is not one of openclaw, hermes, dots, generic, qm, muse or grok. For a
|
||||
<a id="harness-mode-unsupported"></a>
|
||||
### `gstack-harness: Hermes does not support --skills router` (HARNESS_MODE_UNSUPPORTED)
|
||||
|
||||
Hermes opens only skills it has indexed, so a router that points at unlisted files would not load. Drop `--skills` to take the harness's default (Hermes: all; OpenClaw, dots and generic: router).
|
||||
Hermes lists in its skills index only the skills registered as folders in its skills tree, so a router would leave every other gstack skill out of the listing the model chooses from (Hermes's `skill_view` can still load an unlisted skill by name, but nothing tells the model it exists). Drop `--skills` to take the harness's default (Hermes: all; OpenClaw: hybrid; dots and generic: router). OpenClaw takes router, hybrid or all.
|
||||
|
||||
<a id="harness-unsafe-destination"></a>
|
||||
### `gstack-harness: refusing to write into <dir>: <why>` (HARNESS_UNSAFE_DESTINATION)
|
||||
@@ -5192,7 +5316,12 @@ The destination is your home folder, the filesystem root, inside the gstack chec
|
||||
|
||||
gstack only replaces files its ledger says it wrote. A folder or a skill with the same name is yours (or another tool's), and harnesses resolve skills by name, so gstack stops instead of shadowing it. Rename or move that entry, then register again.
|
||||
|
||||
The same code stops `register generic --context <file>` when the file holds a `<!-- gstack:begin harness-routing -->` or `<!-- gstack:end harness-routing -->` line without exactly one partner, or a gstack block someone edited. Delete those marker lines (and the lines between them, once you have kept what you want), then register again.
|
||||
The same code stops `register <harness> --context <file>` when the file holds a `<!-- gstack:begin harness-routing -->` or `<!-- gstack:end harness-routing -->` line without exactly one partner, or a gstack block someone edited. Delete those marker lines (and the lines between them, once you have kept what you want), then register again.
|
||||
|
||||
<a id="harness-context-refused"></a>
|
||||
### `gstack-harness: refusing to write the gstack block into <file>: <why>` (HARNESS_CONTEXT_REFUSED)
|
||||
|
||||
`--context` writes the routing block only into a file you name, and two kinds of file are refused before anything is written. `SOUL.md` (in any letter case) is the harness's identity file (Hermes and OpenClaw both load it every session), and gstack never edits it. For Hermes, a new `.hermes.md`, `HERMES.md`, `AGENTS.override.md`, `AGENTS.md` or `CLAUDE.md` that would outrank a rules file Hermes loads now is refused too: Hermes loads one project context type, first match wins (`.hermes.md`/`HERMES.md`, then the `AGENTS.md` chain, then `CLAUDE.md`, then `.cursorrules`), takes the first name it finds in a folder (`.hermes.md` before `HERMES.md`, `AGENTS.override.md` before `AGENTS.md`), and takes the nearest `.hermes.md`/`HERMES.md` walking up to the git root, so creating it would hide those rules. Name the file Hermes already loads instead (the error names it), or a file in `$HERMES_HOME` for every session.
|
||||
|
||||
<a id="harness-render-failed"></a>
|
||||
### `gstack-harness: could not render <harness> skills: ...` (HARNESS_RENDER_FAILED)
|
||||
|
||||
@@ -187,6 +187,15 @@ most 300 words; a subagent brief names the files to read instead of pasting
|
||||
them. Evidence goes in files the report points to. Waits go on operations,
|
||||
never on polling wakes.
|
||||
|
||||
### Token caps and fresh-context handoffs
|
||||
|
||||
A parent bounds a /ship run with `--token-cap <n>` (or `GSTACK_SHIP_TOKEN_CAP`).
|
||||
It is an admission cap: /ship meters usage, reserves every step and dispatch
|
||||
batch before it starts, and stops `status=incomplete` with a `RECOVER:` line
|
||||
instead of starting work that would cross the cap. Long runs hand Steps 7–11.5
|
||||
and 12–22 to fresh-context children. The flow, the BUDGET line and the result
|
||||
codes are in [ship-budget.md](ship-budget.md).
|
||||
|
||||
### The outside voice on Capy
|
||||
|
||||
Step 11's outside adversarial review prefers the Codex CLI. On a Capy machine
|
||||
@@ -495,6 +504,53 @@ 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 `rec` or "do it"
|
||||
(not item tokens), no item there left pending, and 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 (any later edit refuses, an ELI5-only one
|
||||
included); 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, whose recommended option changes no plan text, 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
|
||||
recommending `a` (accept) 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`. A pending
|
||||
check is void, its reservation released and the gate left for the owner,
|
||||
when the owner answers the gate, the plan is revised, a new gate is
|
||||
written, or the delta changed after dispatch.
|
||||
|
||||
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>", followed by any items taken under "do it"
|
||||
without being shown to the owner.
|
||||
|
||||
## Owner brief
|
||||
|
||||
`gstack-owner-brief render --out <dir>` is what the owner sees at the final
|
||||
|
||||
Reference in new issue
Block a user