Files
gstack/eval-plan/SKILL.md.tmpl
Garry Tanandcapy-ai[bot] 7edaf4f50a v1.91.98.0 fix: plan obligations meet repo gates; priced paid gates; machine-proved restamps (field wave C, items 14-17) (#3166)
* docs(designs): gstack field wave plan (2026-10-12) and the shared simulated Capy host fixture

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

* feat(pregate): price_command config key for the non-executing call plan a paid gate is priced from

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

* feat(pregate): repo guards a lane must pass, verified launch Bun and per-file serial runs in lanes, unknown flags exit 2

The guards check runs each .gstack/pregate.json guard whose globs match a
touched file in its own process with its own timeout (PREGATE_GUARD_FAILED,
never downgradable, incomplete without --allow-repo-commands). The lanes
runner and guards get the first Bun in $BUN_INSTALL/bin or ~/.bun/bin that
satisfies gstack's and the project's engines.bun; serial_patterns files run
one process each. price_file replaces the unreleased price_command key: a
paid gate is priced from a call-plan data file, never by running a command.

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

* fix(pricing): Sonnet 5.5, Gemini 3.x and gbrain embedding rows from fetched pages; Opus 4.7 at its current list price

Each new row carries its source URL and fetch date (2026-10-12). Embedding
models bill input only, so they live in EMBEDDING_PRICING beside PRICING
(eval arms never resolve to them); pricingRow also accepts gbrain's
provider:model ids (voyage:voyage-4). claude-opus-4-7 moves from 15/75 to the
5/25 the Anthropic pricing page lists; benchmark-runner's expected costs follow.

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

* feat(eval-plan): price-gate prices a paid gate from a call-plan data file, never executing it

gstack-eval-plan price-gate --gate "<exact command>" reads a GSTACK_EVAL_CALLS
v1 file (--plan-file, else the base policy's price_file at HEAD), checks its
binding (gate command string equality, head with only the plan file changed
since, every input's sha256 at HEAD), prices each call purpose (aggregate
totals) and each one-call reservation, and writes a receipt binding the price
to the gate, head, inputs and plan bytes. Unpriced models print unknown, never
zero (EVAL_PRICE_MISSING); a missing plan or no call lines is
EVAL_GATE_UNPRICED unless --allow-unpriced records an owner override; over
--cap-usd / --smoke-cap-usd is EVAL_GATE_OVER_CAP; malformed or partial files
refuse EVAL_DRY_RUN_FAILED and stale ones EVAL_PLAN_STALE. dry-run --cap adds
the same reservation-fits-cap check. Wire format, receipt and the reference
emitter (formatEvalCalls): docs/eval-calls.md.

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

* feat(eval-plan): the approval step prices every paid gate with price-gate

Step 4 runs gstack-eval-plan price-gate for each paid gate the plan names and
puts its GATE_PRICE line in the ask; the dry-run checklist documents
reservation-fits-cap under --cap.

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

* feat(sealed): sealed-output guard over every published commit, in the pre-push hook and gstack-pregate sealed

A repo with a sealed key in .gstack/pregate.json (base branch union the
pushed commit, so a push can tighten but never relax) refuses publishing
sealed paths, transcripts, quoted answers, seeds or local home paths to a
public or unknown destination (SEALED_OUTPUT_REFUSED); private warns.
Path rules see every added, renamed or copied path in each published
commit (git rev-list <local> --not --remotes=<remote>), text rules each
commit's added lines; waivers only from the base branch allow file; a
malformed policy fails closed; visibility caches public 24 h, private 1 h.

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

* fix(eval-plan): only an undeclared plan is unpriced; a missing price_file is stale, a bad base policy refuses

price-gate's EVAL_GATE_UNPRICED (and --allow-unpriced) now covers only no
plan declared at all or a plan with zero call lines. A declared price_file
missing from the HEAD commit is EVAL_PLAN_STALE (exit 1, not overridable); an
unresolvable origin/<base> is POLICY_SOURCE_UNAVAILABLE and an invalid
.gstack/pregate.json is PREGATE_CONFIG_INVALID (both exit 3).

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

* feat(restamp): the repo's stamp rule (stamp_files, literal stamp_patterns) and restamp_command in the staging worktree

stamp_files is an allowlist applied before every stamp rule; stamp_patterns
are bounded literal {version} templates shared by the restamp writer and the
tree receipt. restamp_command runs first in the staging worktree with the
target version (argument and GSTACK_RESTAMP_VERSION), needs a clean caller
tree, skips gstack's version writer and keeps output confinement; a failure
or a wrong version writes nothing. migrations { dir, id } is the policy key
gstack-pregate regate reads.

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

* feat(pregate): gstack-pregate regate --against <gated-head> with per-lane reuse and migration collisions

Reuse comes only from a tree receipt evaluated now, with a bundle produced
on the gated tree, the current execution identity and an unmoved, known
predecessor head. Each lane prints reusable/rerun/not-reusable; REGATE:
not-needed needs stamp-only, every bundle and --require lane reusable (a
stamp-sensitive lane reruns) and a finished migration lookup. Migration
numbers the branch adds are checked against the base tree and every other
open PR (gh pr list --limit 1000; 100-file lists refetched with gh api
--paginate); any lookup that cannot finish is unknown and blocks
not-needed (MIGRATION_COLLISION). Bare --against routes to the verb.
docs/pregate.md gains the regate section and the gbrain worked configs.

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

* feat(contract): lanes run gstack-pregate (and the sealed guard where configured) before done; handoff carries the gstack trailer and PREGATE: line

The coordinator contract gains a 'Before done' paragraph and the handoff
order GSTACK_RESULT, gstack: ok|fail <rev>, PREGATE:, gate summary; the
spec section is regenerated and docs/unattended.md states the same rule.
A new test pins the terms and their order; the byte-identity pins stay.

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

* v1.91.98.0: pregate guards, priced paid gates, regate and the sealed-output guard

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

* test: drop the helper-only Capy host fixture test (test-of-test ratchet); lane tests exercise the helper

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

* fix: price-gate --gate-file keeps free text out of the /eval-plan fence; shortest dependency traces; bounded test spawns

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

* ci(windows): curate out the new pregate/restamp tests that spawn bin/ shebang scripts through runBin

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

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-13 15:38:41 +00:00

177 lines
8.9 KiB
Cheetah

---
name: eval-plan
preamble-tier: 2
version: 1.0.0
description: |
Preregister a paid eval (bars, arms, decoys, stop rules), dry-run it at zero cost,
then run a three-item priced pilot whose spend table gates approval. (gstack)
triggers:
- plan an eval
- preregister an eval
- how much will this eval cost
- pilot the eval
allowed-tools:
- Bash
- Read
- Write
- Edit
- Glob
- Grep
- AskUserQuestion
---
{{PREAMBLE}}
# /eval-plan: Preregistered, paid evals
Three steps stand between an eval idea and the first priced call, in this order:
the preregistration (what counts, what stops the run), the zero-cost dry run (the
pipeline works on the real input shapes, with prices in the table), and the
priced pilot (three items per arm, settled against the invoice, extrapolated
with an error band). Only then is the owner asked to approve the full run. Every
step prints one grep-able line; a parent agent reads the lines, not this prose.
Usage: `/eval-plan [<prereg.md>] [--input <rows.jsonl>] [--arms <model,model>] [--cap <usd>] [--total <items>]`
## Boundaries
- Spend nothing before Step 3, and in Step 3 only what the ledger admits. A reservation the
ledger refuses (`EVAL_BUDGET_EXHAUSTED`) ends the step; never retry to make room, never
split a call to slip under the cap.
- Preregistered text is frozen once the pilot starts: a bar, arm, decoy or stop rule
changed after a priced call is a new eval with a new preregistration.
- Held-out sets named in the preregistration are never read by this skill, pasted into a
prompt, or summarized; the dry run reads the development rows only.
- Admission is an estimate. `lib/pricing.ts` prices standard text tokens and excludes
cache writes, tool fees, service-tier and long-context multipliers, media tokens and
seat-metered runs; actual cost reconciles at settle time and `unknown` charges stay
visible in every spend table.
- Under `SESSION_KIND: unattended`, the approval in Step 4 is written as a gate item and
the run ends `gate_pending`; this skill never approves a priced run by itself.
## Step 1: Preregistration
Write (or open) the preregistration file. Each section is required and must be filled;
`<!-- -->` guidance does not count as content:
| Section | What it holds |
|---|---|
| Bars | one line per metric: name, pass bar, direction, what a miss means for the decision |
| Arms | one line per arm: model id (in the price table), settings, per-item cost from Step 1b |
| Decoys | inputs the scorer must reject or score low; how many, how they were made, what a decoy pass proves |
| Stop rules | the spend cap, a bar missed on the pilot, a scorer below its bar, `budget_exhausted` |
| Held-out exposure | every held-out set the plan touches: prior exposure (which runs, which models), the reservation on it |
```bash
~/.claude/skills/gstack/bin/gstack-eval-plan prereg --init <path>
~/.claude/skills/gstack/bin/gstack-eval-plan prereg --check <path>
```
`PREREG: ok` continues. `PREREG: incomplete` names the missing or empty sections
(`PREREG_INCOMPLETE`, exit 1); fill them and rerun. Do not continue on an incomplete
preregistration.
### Step 1b: Arms and their price
```bash
~/.claude/skills/gstack/bin/gstack-eval-plan arms
```
Arms come from the project's model rules (`gstack-models resolve --role eval-arms`),
falling back to the repo's own policy document (`--policy-doc <path>`), or from
`--arms <picked-models>` when the user named them. Each `ARM:` line carries its source
and the per-item cost at the stated token estimate; `price=MISSING` (`EVAL_PRICE_MISSING`)
means the model has no row in `lib/pricing.ts`: add the row from the provider's pricing
page or drop the arm. Copy the priced arms into the Arms section.
## Step 2: zero-cost stub-model dry run
Run the pipeline over the real development rows with a stub model that costs nothing:
```bash
~/.claude/skills/gstack/bin/gstack-eval-plan dry-run --input <path> --arms <picked-models> --retry-prompt <path> --out <outdir>
```
Add `--fields <id>` when the rows' prompt field is not `prompt`, and `--output-budget` with
the budget the run will use. The checklist printed must be all `ok`:
- `CHECK: price-table` — every arm has a price row.
- `CHECK: output-budget` — at or above the reasoning floor (8,192 tokens); a reasoning model
spends output tokens thinking, and a smaller budget truncates the answer the scorer reads.
- `CHECK: retry-prompt` — the exact text the run sends on a retry, printed verbatim; a retry
with no text is a repeat of the same failure at the same price.
- `CHECK: reservation-fits-cap` (with `--cap <cap-usd>`) — one call, the largest row plus the
output budget, priced, fits under the cap; a provider refuses a reservation the cap cannot hold.
- `DRY_RUN_ROW:` lines name rows missing a field; fix the rows, not the prompt.
`DRY_RUN_RESULT: pass` writes `dry-run.json` to `--out`; the pilot refuses to start on
anything else (`EVAL_DRY_RUN_FAILED`).
## Step 3: Priced pilot (n≈3)
```bash
~/.claude/skills/gstack/bin/gstack-eval-plan pilot start --out <outdir> --cap <cap-usd> --arms <picked-models> --dry-run <path>
```
`pilot start` reserves three attempts per arm through the spend ledger (`spend.json` in
`--out`): admission is `spent + reserved + unknown + estimate <= cap`, under a file lock
shared with any concurrent worker. Run each `PILOT_ATTEMPT:` through the project's own
eval command, then settle it with the actual cost from the provider's usage field, or
`unknown` when the call was seat-metered or CLI-metered:
```bash
~/.claude/skills/gstack/bin/gstack-eval-plan pilot settle --out <outdir> --attempt <attempt-id> --usd <usd|unknown>
~/.claude/skills/gstack/bin/gstack-eval-plan pilot report --out <outdir> --total <total-items> --workers <workers>
```
`pilot report` prints the spend table (`spent`, `reserved`, `unknown charges`, the worst case
with one in-flight call per worker, the worst-case overrun against the cap) and
`PILOT_PROJECTION:` — mean cost per item, its sample deviation, the projection to
`--total` items across the arms, and a 95% band. Regenerate the table at every later gate
of the run. Stop and report, without asking for approval, when the pilot missed a bar from
the preregistration, when `status=budget_exhausted`, or when `verdict=exceeds`.
## Step 4: The approval ask
Price every paid gate the plan names (a benchmark or eval command that spends) from the
repo's call-plan file; nothing is executed (`docs/eval-calls.md` in gstack). The gate
command is free text, so it reaches the pricer only as a file:
{{FREE_TEXT_FILE:GATE_FILE=gate-command}}
Write the exact gate command, one line, into the printed file, then price it:
```bash
GATE_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<gate-file-name>"
~/.claude/skills/gstack/bin/gstack-eval-plan price-gate --gate-file "$GATE_FILE" --cap-usd <cap-usd>
```
Each `GATE_PRICE:` line goes into the ask. `EVAL_GATE_UNPRICED`, `EVAL_PLAN_STALE`,
`EVAL_PRICE_MISSING` or `EVAL_GATE_OVER_CAP` means that gate is not approvable as priced;
`--allow-unpriced "<reason>"` only on the owner's word.
Present, in this order and nothing before it: the `PILOT_PROJECTION:` line, the spend
table, each gate's `GATE_PRICE:` line, the pilot's per-bar results, and the preregistration's stop rules. Then ask one
question with the projection as the recommended option only when `verdict=within`:
- A) Run `<total-items>` items at the projected cost (recommended when within the cap)
- B) Cut arms or items; recompute the projection first
- C) Stop here; keep the pilot results as the record
Under `SESSION_KIND: unattended` write the question as an `approval` gate item through
`gstack-gate` and end with `GSTACK_RESULT: skill=eval-plan status=gate_pending run=<outdir>`.
The full run, when approved, keeps settling every attempt into the same ledger and reprints
the spend table at each gate; `budget_exhausted` stops it.
## Tier 3, after P7 (not in this skill yet)
Each of these waits for the adoption number in the multi-agent wave plan (P7); one line each so the shape is known:
- Instrument audit: read every failing transcript of one dev round before freezing the scorer; check generated worlds for duplicate identifiers, word-like codes and generic phrases; mutation tests in both directions. (tier 3, after P7)
- Steady state: background queues empty at snapshot time, queue depth recorded as a gate. (tier 3, after P7)
- Sealed paths: declared in the plan; the skill refuses to quote from them, self-checks output for their n-grams and adds a provenance line. (tier 3, after P7)
- Off-machine persistence: long runs sync through `gstack-detach --sync-cmd <cmd> --sync-every <min>`. (tier 3, after P7)
- Memory preflight: concurrency times measured per-process memory, refuse above 70% of RAM. (tier 3, after P7)
- Power tables: power and risk sentences cite the committed power output with a sensitivity table over the obvious rule alternatives. (tier 3, after P7)
- QA soak: hours of mixed ingest against a long-lived server with a `CPU pinned, no progress` watchdog. (tier 3, after P7)