Merge origin/main (90a170c, v1.92.50.0) into gsta215-ceo-only-driver

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

# Conflicts:
#	autoplan/DRIVER.md
#	bin/gstack-autoplan
#	docs/unattended.md
#	lib/headless-artifacts.ts
#	scripts/resolvers/autoplan-driver.ts
This commit is contained in:
garrytan committed 2026-11-02 01:40:16 +00:00
commit 81160bab70
114 files changed
+2477 -471

No files matched your search

+2 -2
View File
@@ -37,8 +37,8 @@ unknown is reserved for existing behavior you could not inspect. In a live audit
you never triggered are unknown, not extrapolated.
**Caps.** Name the path and item behind every cap and show the corrected error line.
- **4/10 (Critical finding, title `CAP4:<path>:<item>`):** a path leaks a secret, signals failure as success, gives an unsafe fix, or blocks waiting for a terminal (hangs, or fails for lack of a terminal without naming a non-interactive alternative such as `--yes`).
- **6/10 (High finding, title `CAP6:<path>:<item>`):** a path lacks an applicable fix, recovery, context or trace item.
- **4/10 (Critical finding, title `CAP4:<path>:<item> <what fails>`):** a path leaks a secret, signals failure as success, gives an unsafe fix, or blocks waiting for a terminal (hangs, or fails for lack of a terminal without naming a non-interactive alternative such as `--yes`).
- **6/10 (High finding, title `CAP6:<path>:<item> <what fails>`):** a path lacks an applicable fix, recovery, context or trace item.
Every mode, DX TRIAGE included, reports every CAP4 finding.
+7
View File
@@ -257,6 +257,13 @@ the same three paths. An errored init is skipped, so a later successful init
still binds. This denial applies when no init ever succeeded, or when an
errored init follows a good one.
An init whose output is not its JSON result (extra text, or no single text
block) also counts as failed, and the message adds "without a leading cd" to
the re-run hint. The one exception is the `Shell cwd was reset to <dir>` line
Claude Code adds as the last line of a Bash result when the command's `cd`
left the session's folder: the guard removes exactly that line before reading
an init or checkpoint result (#3651).
<a id="init-unbindable"></a>
### `init_unbindable`
Init ran through a shell variable, substitution, chaining, a pipe or a
+1 -1
View File
@@ -172,7 +172,7 @@ not a new ask ([carry-forward](unattended.md#carry-forward)).
**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
what it covers; a gate answered with `--words-by agent:<id>` is listed under that agent as its own decision, not the owner's words); `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).
The brief's done line names the row. A plan approval is not a READY field; the
+7 -2
View File
@@ -38,8 +38,8 @@ cause, the held files and the fix.
| State | Trigger | Attention line (after `ARTIFACTS_SYNC: attention: `) | Fix |
|---|---|---|---|
| `held` | The secret scan held back flagged files and synced the rest | `status=held held=<N>. The secret scan is holding back <N> file(s); everything else still syncs. See which files and how to fix them: <bin> --status` | Edit the file so the scan no longer matches, or `<bin> --skip-file <path>` (permanent; undo with `--unskip-file <path>`) |
| `blocked` | A re-scan still flagged after the hold, so nothing was committed | `status=blocked. The secret scan flagged a file it could not hold back on its own, so nothing is syncing. See the fix: <bin> --status` | Edit or skip the files `--status` names, or clear the batch with `<bin> --drop-queue --yes` |
| `held` | The secret scan or the JSON check held back flagged files and synced the rest | `status=held held=<N>. Sync is holding back <N> file(s) that the secret scan flagged or that are not valid JSON; everything else still syncs. See which files and how to fix them: <bin> --status` | Edit the file so the scan no longer matches, or `<bin> --skip-file <path>` (permanent; undo with `--unskip-file <path>`), or repair a file that is not valid JSON |
| `blocked` | A re-scan still flagged after the hold, or a file would be held during an unfinished git merge, so nothing was committed | `status=blocked. Sync committed nothing: a flagged file could not be held back on its own, or held files are part of an unfinished git merge. See the fix: <bin> --status` | Edit or skip the files `--status` names, or clear the batch with `<bin> --drop-queue --yes`. For an unfinished merge, repair the held files or finish the merge; skipping files or dropping the queue does not clear it |
| `push_failed` | A commit is saved locally but the push failed | `status=push_failed. Synced files are committed locally but the push failed; it retries at skill start. See the cause: <bin> --status` | The fix `--status` names (usually `gh auth status`) |
| `error` | The drain stopped before committing and kept the queue | `status=error. The last sync stopped before committing; the queue is kept and retried. See the cause: <bin> --status` | The cause `--status` names: a git index lock, a commit hook, a `git add` failure |
| `unknown` | The status file holds a status code outside this table | `status=unknown. The sync status file is unreadable. See it: <bin> --status` | Run `<bin> --once` to rewrite it |
@@ -76,6 +76,11 @@ both fixes:
~/.claude/skills/gstack/bin/gstack-brain-sync --drop-queue --yes
```
A synced `.json` file that does not parse (often git conflict markers from a
merge) is held the same way under rule `invalid-json`. Its only fix is to
repair the file; `--skip-file` is not offered, because it would stop that
file syncing for good. The next sync re-checks it.
**Git index lock.** A git process killed mid-write leaves
`~/.gstack/.git/index.lock`. The sync removes it once it is older than 10
minutes, because every gstack writer to `~/.gstack` takes the same sync lock
+9 -3
View File
@@ -91,8 +91,8 @@ gstack-brain-sync --status
```
Shows: the status (`ok`, `idle`, `held`, `collision`, `blocked`,
`push_failed`, `error`), files held back by the secret scan with their
fixes, files held because their path differs only by letter case from
`push_failed`, `error`), files held back by the secret scan or because
they are not valid JSON, with their fixes, files held because their path differs only by letter case from
another path (`case_held`, `case_names`; see
[troubleshooting](troubleshooting.md#artifact-case-collision)), the
`drainable` count, the last sync and push times, pending queue depth, and
@@ -156,7 +156,7 @@ to it); everything else syncs. The held file stays queued and is re-scanned
at every sync, and skill start prints:
```
ARTIFACTS_SYNC: attention: status=held held=1. The secret scan is holding back 1 file(s); everything else still syncs. See which files and how to fix them: <bin> --status
ARTIFACTS_SYNC: attention: status=held held=1. Sync is holding back 1 file(s) that the secret scan flagged or that are not valid JSON; everything else still syncs. See which files and how to fix them: <bin> --status
```
To remediate:
@@ -200,6 +200,12 @@ is kept in a git stash; `gstack-brain-sync --status` names the stash and the
`git -C ~/.gstack --literal-pathspecs restore --source=<stash> -- <path>`
command that brings it back.
JSON files such as `developer-profile.json` have no merge driver, so a
merge can leave conflict markers in them. The sync parses every staged
`.json` file and holds back one that does not parse (status `held`, rule
`invalid-json`) until you repair it, and `gstack-doctor` warns when a synced profile or
decision snapshot holds conflict markers.
## Cross-machine pull cadence
The preamble runs `git fetch` + `git merge --ff-only` once per 24 hours
+8 -1
View File
@@ -200,7 +200,14 @@ Two stages with separate clocks, both printed:
scratch name (`zz-*`, `tmp*`, `scratch*`) anywhere.
- `literals`: for every changed exported constant or named timeout/retry
literal, the whole test tree (nightly- and Heavy-only files included) is
searched for the old value; a consumer the branch did not update fails with
searched for the old value in files that name the constant or, when the old
value is longer than one character, its module (the module's file name not
followed by another extension, or a path ending
in its stem followed by its own extension, for a JS/TS module also any JS/TS
extension or `.d.ts`, a sentence-ending `.`, or anything but a word
character, `.` or `-`; so a sibling `<stem>.txt`, `<stem>-v2.ts` or
`<file>.snap`, or `<stem>.ts` beside a shell module, is not the module); a
consumer the branch did not update fails with
`file:line`. A version-history row, a line that starts a bracketed row with
the old value (`[45, "<digest>"]`), is skipped when the same table (a run of
consecutive lines starting with `[`) also holds a row for the new value that
+43 -16
View File
@@ -93,11 +93,14 @@ incomplete coverage, which blocks until the reviewer is rerun.
for every reviewer (the gate scan and QA probes keep the whole candidate) and
classifies with `--range-base`. Only a blocking finding in that range or in
the diff is `fix`; every below-floor finding is a `note`.
3. **Two full passes is the cap**, plus at most one fix-check; a scoped pass
([below](#scoped-pass)) is a full pass. A blocking
finding still open after the last allowed step stops the run and names the
finding (path, severity, problem) for the owner; a third full pass is the
owner's call, never the loop's. The cap approves nothing.
3. **Two rounds is the cap** (R51): a full pass, a scoped pass
([below](#scoped-pass)) or a fix-check is a round. After round 2 the run
finishes with its open notes listed in the PR body and receipt, or stops for
the owner when a blocking finding is open, or was fixed in round 2 and is
unverified, or a round-2 fix edited a gate-class path, naming it (path,
severity, problem). A third round is the owner's
grant of one scoped fix-check (recorded in `review_loop_exception`), never the
loop's. The cap approves nothing.
<a id="transitions"></a>
### Transitions
@@ -112,10 +115,12 @@ incomplete coverage, which blocks until the reviewer is rerun.
| a full pass that fixed a blocking finding, below the cap, on a diff below full depth with a fix range of at most 150 runtime lines | scoped pass on the range |
| a full pass that fixed a blocking finding, below the cap (full depth, a larger range, or a range or depth it could not read) | full pass on the range |
| a full pass whose fix touched a gate-class path, below the cap | full pass on the range |
| a full pass that fixed only below-floor findings | one fix-check |
| a full pass at the cap that fixed a blocking finding | one fix-check |
| a full pass that fixed only below-floor findings | one fix-check (round 2) |
| a light pass 1 whose below-floor fixes change no runtime lines | done |
| round 2 (a full pass or a fix-check) that fixed a blocking finding or edited a gate-class path | stop: name it for the owner, who may grant one scoped fix-check |
| round 2 (a full pass or a fix-check) that fixed only below-floor findings | done: notes in the PR body |
| a full pass that leaves a blocking finding open (fixed or not) | stop: name it for the owner |
| a fix-check that found nothing blocking (below-floor fixes before it included) | done |
| a fix-check that found nothing blocking | done |
| a fix-check that found a blocking finding | stop: name it for the owner |
A **fix-check** is one reviewer reading the touched hunks, the fix list, the
@@ -186,10 +191,27 @@ in a data file still schedules the outside voice.
| Depth | When | Pass 1 dispatches |
|---|---|---|
| **light**, runtime 0 | docs and tests only, or only registered outputs | the core checklist only; the outside voice when the diff publishes numbers |
| **light**, runtime 1–50 | `SMALL_DIFF_LINES` or fewer runtime lines | core, one **batched** specialist (the batched checklists below plus the Red Team prompt in one context), the native adversarial pass (kept: backtest inconclusive, GSTA-147 D5) and the outside voice on numbers |
| **standard** | more than 50 runtime lines | one batched specialist at `medium` and the Red Team at 200 runtime lines (or on a CRITICAL); the batched reader leaves docs and registered outputs out of its diff; adversarial and outside voice read everything |
| **full** | gate class, or any classifier error | one batched specialist on the whole diff, the Red Team always, no hit-rate gating |
| **light**, runtime 0 | docs, tests, eval write-ups, or only registered outputs | one reader: the core checklist; the outside voice instead reads a diff that publishes numbers |
| **light**, runtime above 0 | code with no risk class, at any size (R51: size never raises the depth) | core, one **batched** specialist (the batched checklists below plus the Red Team prompt in one context), the native adversarial pass (kept: backtest inconclusive, GSTA-147 D5) and the outside voice on numbers |
| **standard** | `--depth-floor standard`, or a light diff raised by a fixed blocker or a CRITICAL | one batched specialist at `medium` and the Red Team at 200 runtime lines (or on a CRITICAL or a raise); the batched reader leaves docs and registered outputs out of its diff; adversarial and outside voice read everything |
| **full** | gate class, a [risk class](#risk-classes), or any classifier error | one batched specialist on the whole diff, the Red Team always, no hit-rate gating |
<a id="risk-classes"></a>
**Risk classes (R51).** `lib/review-risk.ts` reads a code diff's paths and its
added and deleted lines; any class makes the depth full (`risk:<class>` in
`reasons=`, `review_depth.risk` in the receipt). money, auth and secret are the
[targeted pass](#targeted-pass-on-money-gate-auth-and-secret-code)'s matchers.
**security** is a trust-boundary path (trust, redact, sanitize, neutralize,
consent, escape) or line (`shell: true`, `execSync(`, `eval`, `new Function(`,
raw HTML sinks, a shell run through argv, recursive deletes). **data** is user data: a path naming pii, gdpr, ccpa, privacy,
personal, account, customer, cookie, persistence, storage or database, or a line
that drops a table or column, deletes rows or bulk-deletes records. Auth scope,
migrations and deleted code paths were already gate class. `RISK_RULES` hashes
the patterns and `DEPTH_RULES` includes it.
**UI text and SVGs** stay code (light unless a risk class applies). A docs path
for UI copy, locale values and chart SVGs waits for an allowlist classifier: three
rounds of R51 review found bypasses in line-pattern versions of both.
**Batched checklists (the lean ensemble, GSTA-167 part 2).** One specialist
subagent reads Testing and Security on every code or gate diff, plus
@@ -230,7 +252,7 @@ and lockfiles stay runtime. To adopt it, commit an entry like this on the base b
**Gate paths for the machinery itself.** An edit to `lib/review-depth.ts`,
`lib/review-collect.ts`, `bin/gstack-review-plan`, `lib/review-scope.ts`,
`lib/review-targeted.ts`, `review/specialists/targeted.md`, `lib/ship-queue*.ts`, `lib/regen.ts`, `.gstack/generated.json`,
`lib/review-targeted.ts`, `lib/review-risk.ts`, `review/specialists/targeted.md`, `lib/ship-queue*.ts`, `lib/regen.ts`, `.gstack/generated.json`,
`ship/sections/review-army.md.tmpl`, `ship/sections/adversarial.md.tmpl` or
`scripts/resolvers/review-army.ts` is gate class: changing how much review runs gets
the most review. `rules=` (`DEPTH_RULES`) hashes the deciding constants and patterns,
@@ -282,6 +304,10 @@ the specialists, audits and watchers at the host default too
prompt. At runtime 0 light runs no adversarial pass either.
- Standard reads thresholds on runtime lines, not the whole diff, so a test-heavy
diff with few runtime lines dispatches fewer specialists.
- R51: size never raises the depth; non-risky code is light at any size, and a risk
class makes it full. Two rounds is the cap, a fix-check
included: a blocker fixed in round 2 stops for the owner instead of earning a
fix-check, and a light pass 1 whose below-floor fixes change no runtime lines is done.
- A JSON, CSV, TSV or raster data file no code names is docs, not runtime (GSTA-167
part 4a). A binary image no code names is no longer a `binary_code_path` error
that forces full depth; one that code names still is.
@@ -354,6 +380,7 @@ puts in the receipt:
| `review_loop.fixed` | findings fixed in this PR |
| `review_loop.scope` | the `review_scope` the loop ran under |
| `review_loop.fix_checks` | fix-checks this ship ran (never passes) |
| `review_loop.rounds` | passes plus fix-checks (R51 caps it at 2; the queue's `REVIEW_DEPTH:` line prints it) |
| `review_loop.dispatches` | reviewer dispatches by role: `specialist`, `red-team`, `adversarial-native`, `outside-voice`, `fix-check`, `targeted` |
| `review_loop.est_input_tokens` | prompt plus diff bytes the dispatches read, divided by 4: a relative estimate for comparing runs, not a bill |
| `review_targeted` | the [targeted pass](#targeted-pass-on-money-gate-auth-and-secret-code): the `classes` it covered, `rules`, `brief_sha`, the receipt's own `product_patch_id`, `status` (`done` only with its output collected and the committed diff it was rendered on still the one at verify, else `failed`; absent when write got no record, which the queue reports as `legacy`), `findings` and `raised_by`; a carry keeps it while the product patch is unchanged |
@@ -366,7 +393,7 @@ specialist stats they record). The summary line appends `fix_checks=`,
loop prints the old line.
Beside the counters, `write` records `review_depth` (the depth, runtime lines,
rules hash, base and registry commits, and the gate scan bound to the head) and
rules hash, base and registry commits, the gate scan bound to the head and the risk classes) and
the queue refuses a receipt below the depth its head needs
([ship-policy.md#ship-receipt](ship-policy.md#ship-receipt)). A stop the owner
accepted after a fix-check edit goes in `review_loop_exception`, never in prose
@@ -385,8 +412,8 @@ difference passed a scoped review.
| Value | Pass 1 | Blocking finding in unchanged code | Pass 2+ | Cap |
|---|---|---|---|---|
| `frozen` (default) | the diff | filed as an issue, listed as deferred | the range since the previous pass, blocking only | 2 full passes + 1 fix-check |
| `diff-plus-blocking` | the diff | fixed in this PR | the range since the previous pass, blocking only | 2 full passes + 1 fix-check |
| `frozen` (default) | the diff | filed as an issue, listed as deferred | the range since the previous pass, blocking only | 2 rounds (a fix-check is one) |
| `diff-plus-blocking` | the diff | fixed in this PR | the range since the previous pass, blocking only | 2 rounds (a fix-check is one) |
| `all` | everything | fixed in this PR | the whole diff, every finding | 3 fix cycles |
`diff-plus-blocking` is the fallback for a repository where deferred issues pile
+4 -4
View File
@@ -1221,13 +1221,13 @@ construction:
fix-range`) or in the diff is `scope: fix`; nothing below the floor earns a
pass. `NO FINDINGS` is the expected reviewer result for a clean range, never a
failed review.
3. **Two full passes plus one fix-check is the cap**, stepped by the
[transition table](review-policy.md#transitions); a
[scoped pass](review-policy.md#scoped-pass) counts as a full pass. A pass that applied no edits
3. **Two rounds is the cap** (R51), stepped by the
[transition table](review-policy.md#transitions); a full pass, a
[scoped pass](review-policy.md#scoped-pass) and a fix-check each count as a round. A pass that applied no edits
continues to Step 10: the loop ships with its deferred and noted items named in
the PR body. A blocking finding still open after the last allowed step stops
the run and names the finding (path, severity, problem) for the owner instead
of a third full pass.
of a third round; the owner may grant one scoped fix-check.
`gstack-review-scope summary --since <REVIEW_SINCE>` reads the branch's review-log
rows for this invocation and writes the counters the ship receipt carries
+6 -2
View File
@@ -962,8 +962,12 @@ blank (`PREREG_INCOMPLETE`). `arms` resolves the `eval-arms` model role against
`lib/pricing.ts` and stops on an unpriced model (`EVAL_PRICE_MISSING`); `dry-run`
runs every row through the stub model for $0 and flags malformed rows
(`EVAL_DRY_RUN_FAILED`); `pilot start|settle|release|report` runs three priced
items, records actual spend, extrapolates to the full set against the cap
(`EVAL_BUDGET_EXHAUSTED`) and prints the spend table the approval question shows.
items, records actual spend, extrapolates each priced arm to the full set against
the cap (`EVAL_BUDGET_EXHAUSTED`) and prints the spend table the approval question
shows. An arm on a subscription with no extra charge is declared with
`pilot start --seat-metered <arm,...>`: it reserves $0, settles as `unknown`, and
stays out of the projection until one of its attempts settles a real charge.
A run stops at the first quota or rate-limit error on such an arm.
`results --check <prereg> <results.md>` refuses a finished results file unless one
table holds a filled row for every arm, each `<model>#no-gstack` row beside its
variants (`EVAL_REPORT_NO_GSTACK_ROW`); no number leaves the run before it passes.
+31
View File
@@ -0,0 +1,31 @@
# Support tiers
gstack has two support tiers. Full support gets the whole test suite, paid evals and new work. Minimal support means gstack is not totally broken, and nothing more. Garry Tan set this on 2026-11-01: "we don't want it to be totally broken, but we just don't need to invest that much into it."
## Full support
- **Operating systems:** macOS and Linux.
- **Models:** Claude and OpenAI models, both as the host agent and as a reviewer.
- **Testing:** the full free suite and the paid evals.
## Minimal support
- **Windows** (Git Bash or MSYS): with a default Git for Windows install, `./setup` installs, and the core skills start without errors. The core skills are `/ship`, `/review`, `/qa-only`, `/investigate` and `/browse` on gstack's own browser. Once the `windows-smoke` CI job lands, it checks this on every pull request (see Windows CI below).
- **Gemini models:** the core workflows run to the end without errors when Gemini is the host agent or a reviewer. We check this by hand or with one cheap smoke run, not with an eval cell.
That is the whole promise. Other skills, optional parts such as `/cso`'s native helper, non-default shell settings and speed are not covered.
## What we stop doing
- No Windows-only or Gemini-only features, parity work, tuning or polish.
- No new Windows or Gemini CI jobs or eval cells. Windows CI tuning stops.
- A Windows-only or Gemini-only report that doesn't break install, startup or a core workflow closes as won't fix ([triage rules](triage.md#closing-an-issue)), with a link to this page. A report that does break one of them gets a small fix.
## Windows CI
- **Today:** the six-shard Windows free suite (the `Windows Free Tests` workflow, `windows-free-tests`) runs on every pull request and every push to main, and it is a required check.
- **Once `windows-smoke` lands:** `windows-smoke` becomes the required Windows check on pull requests. The six-shard suite then runs nightly on main, or when someone starts it by hand, and never blocks a pull request. A red nightly run gets a fix only when it shows an install, startup or core-workflow break.
## What doesn't change
These tiers don't change which models gstack's own evals test, or which cross-family review voice gstack uses.
+14
View File
@@ -314,6 +314,20 @@ network.
**Expected result.** `OUTSIDE_STATUS: completed`.
<a id="outside-review-truncated"></a>
### `... outside review unavailable: the reviewer hit its output token limit and the response is cut off (...)`
**Meaning.** The api runner's provider stopped at the output token limit
(Anthropic `stop_reason: max_tokens`, OpenAI `finish_reason: length`), so the
review ends mid-sentence and its findings may be missing.
**Fix.** Re-run with a larger `--max-tokens` (`gstack-outside-voice run` or
`gstack-autoplan run`), or shorten the prompt. `gstack-autoplan run` sends a
default of 16384 to both providers, and OpenAI counts reasoning tokens toward
it; `gstack-outside-voice run` sends that default to Anthropic only.
**Expected result.** `OUTSIDE_STATUS: completed`.
<a id="outside-review-missing-markers"></a>
### `... outside review unavailable: the response lacks the markers this gate requires (...)`
+38 -7
View File
@@ -376,7 +376,14 @@ the plan.
refuses to open without one (`CONSENSUS_MISSING`).
3. **Loop `next` until `status=gate_pending`.** Eng always runs last. The gate
(`gate.json`, `gate_rev`) lists user challenges as approval items,
disagreements as auto items and `p1` plan approval; nothing is approved.
disagreements as auto items, each open Critical or High finding as an
auto item (a twin pair is one item; `a` applies the reviewer's fix to the
plan and is recommended, `b` carries it into the build as a blocking task)
and `p1` plan approval; nothing is approved. An unanswered auto item whose
recommendation changes the plan keeps the plan's direction, so only `rec`,
`p1b` (approve with the recommended changes) or an explicit `<id>a` applies
a fix; a finding carried with `b` stays
open, so a rerun that raises it again asks again.
4. **Answer the gate** with the reply grammar and the revision you read:
```bash
$AP answer --out <dir> --gate-rev 1 --reply "rec" --by human # every recommendation, plan changes included (APPLY: ...; amend_pending)
@@ -387,12 +394,18 @@ the plan.
means an agent relayed them, and then `--owner-reply` (or `-` for stdin,
`--owner-reply-file <path>`) stores the owner's words verbatim
(`OWNER_REPLY_REQUIRED`); owner words that parse to different choices are
refused (`GATE_OWNER_REPLY_MISMATCH`). An unattended run refuses an answer
refused (`GATE_OWNER_REPLY_MISMATCH`). The words default to the owner's: an
agent that answers in its own words (a Captain deciding on its own
authority) must pass `--words-by agent:<id>` with its own `--by` id. They
are then recorded as `answer_words` with `words_by`, never as
`owner_reply`; a later gate never carries them (`CARRY: no`), and
`gstack-approvals import` lists that row under the agent, not the owner.
An unattended run refuses an answer
without `--by` (`ANSWER_BY_REQUIRED`, exit 3) and records the value as
`answered_by` on every decision row and in the review record's gate line,
so a gate an agent closed is distinguishable from one the owner closed.
`gstack-autoplan recommend` changes one recommendation before relay; it
bumps the gate's `rec_rev`, and a later `rec` needs the `--rec-hash` from
bumps the gate's `rec_rev`, and a later `rec` or `p1b` needs the `--rec-hash` from
the render the owner saw (`GATE_REC_STALE`). Every `GATE:` line is followed
by `GATE_SPEND:` and `GATE_REC_COST:` (also `gate_cost` in `--json`; see
`autoplan/DRIVER.md`).
@@ -442,7 +455,19 @@ Concurrency and recovery: every state write is a locked read-modify-write
(`test/autoplan-run-recovery.test.ts`, `test/autoplan-run-concurrency.test.ts`,
`test/autoplan-answer-invalidation.test.ts`). `--spend-cap` admits each attempt
through `spend.json` (`SPEND_CAP_EXCEEDED` refuses, exit 3); host-subagent
spend is `unknown` and counted in `worst_case`. `--deadline <min>` makes the
spend is `unknown` and counted in `worst_case`. Under a cap, in any session
kind (or unattended on Capy), each host-subagent attempt, and each api attempt
whose model has no price, reserves the default per voice unless
`next --estimate-usd <n>` sets the reservation, which the run keeps for later
phases (`run` reserves an api attempt's priced worst case, prompt bytes / 3
input tokens plus the output limit, and settles it at its token usage; every
native call that went out and bound no result, whether it failed, was cancelled,
had its result refused, or was in flight when the process died (the next `run`
finds it), settles at its usage, or at its full reservation when the usage is
unknown, while one refused before anything was sent, such as with no API key,
keeps its attempt open for the rerun, and a failed outside call leaves the phase
native-only and stays counted at its reservation); `submit
--usage-usd <n>` settles an attempt at its real cost. `--deadline <min>` makes the
`run` subcommand cancel the children it started and marks the run
`interrupted`.
@@ -522,9 +547,8 @@ A gate revision after the first (`gate_rev` 2 and later, written after a
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
all of: the earlier gate approved (`p1a` or `p1b`), 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
@@ -533,6 +557,13 @@ 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.
An earlier answer given item by item (`<id><option>` tokens, not `rec` or "do
it") carries only what the owner already answered: every item on the new gate
must be carried by origin (the same proposal, so the same choice), and only an
unreviewed post-cap delta (below) may be new. The carry replays the owner's own
`p1` choice and records their tokens verbatim as its basis; any new or changed
item, "do it" cases included, goes to the owner.
- **"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