ship-budget resume opens a new meter epoch for a successor thread (GST-1655) (#4088)

* ship-budget resume opens a new meter epoch for a successor thread's counter (GST-1655)

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

* test: ship-budget successor cases exercise lib/ship-budget directly (test-of-test ratchet, GST-1655)

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

* fix: pre-landing review fixes

Review-Finding: p1/red-team#1 reader=red-team
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: refuse a successor epoch while a handoff child is open (pre-landing review)

Review-Finding: p1/outside-voice#1 reader=outside-voice
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: successor refusal points at reconcile --return; a reconcile without it writes the child off (GST-1655 fix-check#1)

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

* fix: refuse a successor epoch while a dispatch reservation still has readers on the previous counter (pre-landing review)

Review-Finding: p1/outside-voice#1 reader=outside-voice
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: changelog fragment names the successor refusals (GST-1655)

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>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-11-06 18:14:51 +00:00
1 parent be173d52bf
commit a19130d66a
8 files changed
+291 -24

No files matched your search

+35
View File
@@ -72,6 +72,8 @@ costs, including the full-context re-bill after a gap over five minutes (about 4
handoffs/GSTA-167/cache-probe.md). The first credits reading is the baseline; the `BUDGET:`,
`BUDGET_FINAL:` and record-block lines print `credits=<used>` once one exists, and a reading below
the last is refused like a token reading ([BUDGET_READING_REJECTED](troubleshooting.md#budget-reading-rejected)).
A successor thread's readings open a new epoch for credits as well as tokens
([Successor handoff](#successor-handoff)).
## Quickstart
@@ -262,6 +264,39 @@ 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.
### Successor handoff
When another thread takes over a /ship run (a successor handoff, routine under the 75%-of-hard-stop
rule), its `cost.tokens` and `cost.credits` are a different counter: they start near zero, or at an
unrelated cumulative value, so the run's last readings mean nothing to them. The successor resumes
the same run and names the thread its readings come from:
```bash
gstack-ship-budget resume --record <record> --meter-source <successor's jam id> --reading <its cost.tokens> --credits <its cost.credits>
```
A `--meter-source` that differs from the run's (`start --meter-source <jam id>` records one; a run
started without it has none, so any named source counts as a successor) closes the current epoch and
opens the next at the successor's readings:
EPOCH: <n> meter=capy unit=capy-tokens carried_used=<tokens> carried_credits=<credits> source=<new> previous_source=<old|unrecorded>
Spend already metered carries, so `used`, `credits`, the observe hard stop and admission all count
every epoch; `epochs[]` in the ledger keeps each closed epoch's baseline, last reading, usage and
source. Spend between the previous thread's last reading and the handoff, and the successor's spend
before its first reading, is not metered. Both readings are required (`BUDGET_READING_MISSING`
otherwise). A handoff child still open is refused (`BUDGET_READING_REJECTED`): its spend lands on
the previous thread's counter, which the new epoch cannot see. Wait for the child's `RETURN:`, then
`reconcile --return <its return file>` with a reading of that counter (`tools.thread.get` on the
previous jam id), then resume. A reconcile without `--return` writes the child off as interrupted:
its later spend is never charged. A dispatch reservation still open or consumed is refused the same way: its readers
spend on the previous counter, so settle it with `settle --reservation <id> --dispatched-at <n> --reading <n>`
on that counter once they finish, then resume. `resume --rebuild-from-record` takes the same flags: with the ledger intact it opens the
epoch as above, and a rebuilt ledger starts at the successor's readings with the record's `used` and
`credits` carried. Within one epoch nothing changes: a reading below the last is still refused
([BUDGET_READING_REJECTED](troubleshooting.md#budget-reading-rejected)), and the same thread resumes
without `--meter-source` (or with the source the run recorded).
## Calibration
`lib/ship-budget-calibration.json` holds per-meter constants (per call, per dispatch, stop
+5 -2
View File
@@ -2114,7 +2114,9 @@ of the same epoch, `--prior-reading` or `--dispatched-at` is above `--reading`,
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:
A successor thread's counter opens a new epoch that keeps prior usage:
`gstack-ship-budget resume --run <id> --meter-source <its jam id> --reading <n> --credits <c>`
([Successor handoff](ship-budget.md#successor-handoff)). A different meter opens one too:
`gstack-ship-budget resume --run <id> --meter <m>`.
<a id="budget-meter-unavailable"></a>
@@ -2137,7 +2139,8 @@ 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.
`resume --record <path> --rebuild-from-record --reading <n>`, plus `--credits <c>` when the run
metered credits (every `ship/DRIVER.md` run does); 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)`