Merge commit '16db02a13f143cee232a0abb03ca7e911b41e69f' into HEAD

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

# Conflicts:
#	bin/gstack-config
#	bin/gstack-doctor-components.sh
This commit is contained in:
garrytan committed 2026-11-02 08:30:29 +00:00
commit b3b2e799d5
26 files changed
+1774 -113

No files matched your search

+13
View File
@@ -3389,6 +3389,19 @@ work: they are how a budget-stopped CEO-only run recovers.)
full review in a new directory: `gstack-autoplan next --out <new dir> --plan
<file>` without `--ceo-only`.
<a id="shadow-before-answer"></a>
### `SHADOW_BEFORE_ANSWER`
**Meaning.** `gstack-autoplan shadow` skipped a gate revision whose plan
approval (`p1`) is unanswered or was answered `c` (interrogate). The export
pairs each hidden prediction with the owner's answer, so a revision he has not
answered has nothing to pair it with. Answered revisions of the same run still
export; the command exits 3 only when nothing was exported.
**Fix.** Answer the gate (`gstack-autoplan answer --out <dir> --gate-rev <n>
...`), then run `gstack-autoplan shadow` again. See
[Shadow mode](unattended.md#shadow-mode).
<a id="attempt-mismatch"></a>
### `ATTEMPT_MISMATCH`
+86 -1
View File
@@ -267,7 +267,8 @@ tree no review saw, so /ship runs again;
| File | Schema | Contents |
|------|--------|----------|
| `run.json` | `run` v1 | The manifest: `run` id, `status`, `required_phases`, each phase's `native` and `outside` outcome (`status`, `model`, `provider`), input `snapshot` hashes, `counts`, every artifact's `path` (relative to the run directory) and `sha256`, `session_kind`, `gate_rev`, `deviations`, `consumed_by`. |
| `decisions.jsonl` | `decisions` v1 | One row per decision: `id` (`<run>-<label>` at gate revision 1, `<run>-r<rev>-<label>` after a reopen, since labels restart at each gate), `title`, `options`, `recommended`, `kind` (`auto`, `approval`, `user_challenge`), `status` (`pending`, `approved`, `overridden`, `rejected`, or `auto_kept` in a [CEO-only run](#ceo-only-runs)), `gate_rev`, and once answered `chosen`, `answered_at`, `reply`. |
| `decisions.jsonl` | `decisions` v1 | One row per decision: `id` (`<run>-<label>` at gate revision 1, `<run>-r<rev>-<label>` after a reopen, since labels restart at each gate), `title`, `options`, `recommended`, `kind` (`auto`, `approval`, `user_challenge`), `status` (`pending`, `approved`, `overridden`, `rejected`, or `auto_kept` in a [CEO-only run](#ceo-only-runs)), `gate_rev`, and once answered `chosen`, `answered_at`, `reply`, plus how the owner answered: `owner_mode` (`rec`, `rec_except`, `tokens`, `as_is` or `unparsed`, parsed from the owner's own words), `owner_named` (his words named this item), `rec_hash` (the recommendations answered against) and `answered_role` (`human`, or `agent` relaying his words). |
| `shadow/` | private, not an artifact | [Shadow mode](#shadow-mode) only: `ceo-only.r<rev>.jsonl` (one hidden prediction per gate item, written before the answer), `r<rev>.prompt.md` and one `<call>.reply.txt` per model call, the audit `salt`, and `r<rev>.pending` while a call is in flight. Never in `export --public`, the gate render or the owner brief. |
| `findings.jsonl` | `findings` v1 | One row per reviewer finding: `id`, `phase`, `voice` (`native`/`outside`), `model`, `severity`, `title`, `disposition`, `plan_items`, `native_counterpart` (string or null), `source`. Rows share the review-log row shape, so `gstack-review-log --findings` derives review status from this file. |
| `tasks.jsonl` | `tasks` v2 | Implementation tasks with `id`, `title`, `status`, `depends_on`, `findings` (ids in `findings.jsonl`), `blocked_by`, `acceptance`, optional `pr`/`item`/`tier`. |
| `timing.json` | `timing` v1 | Per-phase `started_at`, `ended_at`, `wall_s`, `outside_s`, `native_s`; `started_at` and `total_wall_s` for the run; the Phase 0 `estimate`. |
@@ -659,6 +660,90 @@ approves, so `gstack-artifact validate` accepts the run as it stands.
does: `budget` then `resume`, or `close --phase ceo --deferred`, which ends
it `reviewed`.
### Shadow mode
Shadow mode measures how often a model decider would pick what the owner
picks, before the CEO-only mode is allowed to decide for him. In a driver run
that turned it on, every gate the driver publishes gets one hidden prediction
per question, written before anyone answers: `action` (`decide`,
`send_back` or `hold`), `pick` (one of that item's options), `confidence`
(0 to 1), `cites` (finding ids from that item) and `reason` (at most 400
characters). The owner still gets the gate and answers it as usual; nothing
routes differently. Taste-replay round 2 (R2, in gstack-evals) compares the
predictions with his answers.
- **Off by default.** A crew turns it on with `gstack-config set
autoplan_shadow on`; `GSTACK_AUTOPLAN_SHADOW=1` or `0` overrides it for one
run. It is read once, at the run's first `next`, and stored in `run.json`
(`shadow`), so a resumed run cannot flip it. The first `next` prints
`SHADOW: on model=<m> ...`, or `SHADOW: off (<reason>)` when it was asked for
but cannot run: no API key for the model's family, a model with no price in
`lib/pricing.ts`, `--no-outside`, or a CEO-only run. R2's collection window
starts at the first recorded row on a crew gate, not at this release: check
one `SHADOW: recorded` line on a real gate before counting days.
- **The model and the cost.** `autoplan_shadow_model` (default
`claude-opus-5-5`) gets one call per gate: about $0.07 on your own API key.
The call sends the reviewed plan's Implementation plan section (cut to 40,000
characters) and the gate questions to that provider, with an egress receipt
of sink `autoplan-shadow` and consent `autoplan_shadow on`. Each call is
reserved against `--spend-cap` under its own ledger id labeled `shadow` and
shows as `shadow=<usd>` on `GATE_SPEND`; it never changes the per-voice price
on `GATE_REC_COST`. When the cap cannot cover a call and its retry, the
shadow is skipped (`SHADOW: failed budget`).
- **The decider.** `autoplan/shadow-decider.md` is a frozen, hand-written
prediction prompt (it is not generated from the CEO-only section). It shows
the reviewers' recommendation as one labeled input and gives the six decision
principles as the owner's tendencies, not as rules, so the model can
disagree with the recommendation. Its sha256 and version are on every row;
an edit bumps both.
- **The reply.** Exactly one JSON object per question. A duplicate, missing
or invalid object marks that item malformed and the page is asked once more;
items still malformed are recorded malformed with no pick. A reply cut at the
4,000-token output limit is asked once more at 8,000 instead. One 90-second
deadline covers both calls.
- **When it runs.** Every driver command that prints a gate (`next`,
`submit`, `close`, `resume`, `revise`, `run` and `check`) runs the
shadow first, prints `SHADOW: recorded model=<m> usd=<x>` or `SHADOW: failed
<code>` (status only, never a pick), then the gate, then checkpoints, so a
checkpoint taken at a gate holds its record. A whole-gate carry asks nothing,
so it gets no shadow. Interactive `/autoplan` and CEO-only runs write none.
A shadow failure (`budget`, `no_key`, `timeout`, `http`, `network`,
`truncated`, `unsafe_dir`, `locked`) never blocks the gate or the answer.
- **The lease.** The run lock is not held during the call: the command takes
a lease (`shadow/r<rev>.pending`, with its pid, host and deadline) under the
lock, calls the model, then retakes the lock (waiting up to 30 seconds) to
publish. Another writer that finds a live lease prints `SHADOW: pending` and
pays nothing, and `answer` waits for the record. A lease whose process died
or whose deadline passed is a crash: its sent calls settle `unknown` at
their reservation (they may have been billed), and `answer` records that
revision first with every row `retrospective: true`, which R2 excludes from
prospective evidence.
- **The record.** `shadow/ceo-only.r<rev>.jsonl`, one row per gate item: the
frozen item, `item_class` (`challenge2`, `challenge1`, `disagree`, `agent`,
`cap_deferred`, `cap_blocker`, `cap_delta`, `finding`, `p1`), `ceo_eligible`
(false for a `finding` item, which a CEO-only run leaves to the owner), the
prediction, `rule_pick` (the driver's keep-direction pick, the free
baseline), `rec_hash`, the plan and prompt hashes, the model, the gstack
version, the page's tokens and cost, and `audit`. It is written once per
revision with a link, so a second writer never overwrites it, and its sha256
is journaled (`shadow_recorded`). `gstack-artifact validate <run.json>`
checks each record against that hash and its schema.
- **The 10% audit mark.** A `decide` row carries `audit: true` when
sha256(salt + decision id) mod 1000 < 100, with the salt drawn once per run
into `shadow/salt`. Nothing routes on it yet; the live switch that lets the
mode decide alone will send these items to the owner.
- **The reader.** `gstack-autoplan shadow --out <dir> [--json]`, or `--runs
<file>` with one run directory per line, exports one row per item of each
answered gate revision: the shadow status (`recorded`, `malformed`,
`failed` with its code, `missing`, `retrospective`, or `off` when the run
never turned it on), the prediction, and the owner's answer with its
provenance. It prints counts by status, item class, `owner_mode` and
`ceo_eligible` and computes no agreement number. A revision whose plan
approval is unanswered or answered `c` (interrogate) refuses
`SHADOW_BEFORE_ANSWER`, and a CEO-only run `CEO_ONLY_NO_GATE`. The refusal is
a courtesy for honest tools, not access control: the journaled hash is the
tamper evidence.
## Owner brief
`gstack-owner-brief render --out <dir>` is what the owner sees at the final