- InnerLoop definition-of-done gains a cost row: M-D2-CST may no longer
be recorded uncomputable, and composition must be reported, not only a
total.
- InnerLoop Step 5 gains the --self-test contract: every tool that
reports a number exposes one, and it runs before the number does.
Rationale attached, because the case that motivated it is the one
review cannot catch — survey and reviewer both verified the same large
sample and both missed the small one.
- make cost / cost-test / cost-pin on the one command surface; cost-test
in `make all` and in CI.
- Hub now holds the measured figure for CB-WP-0001: 80.6M in / 323.6k
out against the 401,100 it previously estimated, low by ~200x. The
event states plainly that the hub schema cannot represent the 88% of
cost that is cache, and names `make cost-pin` as the authority.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AC-1 $93.32 ($92.21 main + $1.11 subagent), residual $0.000000. No
unmeasured rows. Composition: 88.0% cache, 11.9% output, 249:1 context
re-read to text written.
The question that could not be answered before: what /compact costs.
The transcript records it directly — 542,991 -> 19,974 tokens, a 27x
reduction. Cost per turn fell from $0.457 (136 turns before) to $0.149
(202 turns after). The 202 post-compact turns cost less than half the
136 before them.
This qualifies CB-WP-0003 T04: sessions are bounded-quadratic, not
quadratic. Cost grows with context between compactions and resets at
each one, so the failure mode is a long UNCOMPACTED session. Whether a
fresh session beats a compaction is now measurable, and T04 should
measure it before prescribing one task per session.
Stale $0.66/$92.87 targets updated across survey and workplan.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Collector per ADR-0003: enumerates every transcript including the
subagents/ tree, dedups by requestId, prices per model and per cache
TTL, attributes on (prev_commit, this_commit] intervals, and reconciles
to the cent or aborts.
The positive control caught a real defect on its very first run against
real data, which is the entire argument for writing it:
CA-02 assumed usage is identical across the lines of one requestId.
True in the main transcript (206/206 groups, verified twice — by the
survey and by the adversarial reviewer). FALSE in the subagents/
tree, where output_tokens is a running count: one response reads
5, 5, 195 across its three lines. First-wins scored it at 5.
So CA-02 now splits: input-side counters are charged once and must be
identical (assertion retained); output_tokens resolves to the max
(CA-02a). AC-9 pins the exact 5,5,195 case as a regression test.
The acceptance target moved again as a result, for the third time:
$248.46 -> $92.21 -> $92.87 -> $93.32. AC-1 was computed by the same
first-wins method the tool just disproved, so the tool failing its
target was the tool being correct. Target updated, not the tool.
Measured at the pin: $92.21 main + $1.11 subagent = $93.32, residual
$0.000000. 32.5% UNATTRIBUTED, reported as its own line per CA-10.
make cost / cost-test / cost-pin wired; cost-test added to `make all`
and to CI, where it gates the collector's assertions without needing
transcripts present.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
T05 still named $248.46 as its acceptance test and the Purpose total
still omitted the subagent tree. Both corrected in place, with the
error sequence recorded rather than overwritten.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Normative spec for the cost model (dedup by requestId, per-model and
per-TTL pricing), the attribution contract (ending-at-commit intervals
scoped by sessionId), and the reported shape (composition, not a total).
Every acceptance row names the command that produces its number, per
InnerLoop Step 4. AC-5..AC-8 are the positive control: cb-cost
--self-test must fail on a dedup violation, on zero responses, on a
missing subagent tree, and on 5m cache priced at the 1h rate.
The feasibility check earns its place here: AC-1's $92.87 is reachable
only if CA-06 holds. The earlier $92.21 target was reachable only by a
collector with the exact blind spot the survey documented.
MetricsAndScenarios 1a superseded. Two of its rules struck through
rather than deleted, because both were wrong in instructive ways: "if
the cache split is unknown, count all input at full price" would have
priced 80.5M cache reads at 10x, and "the hub already records tokens"
named as a source what is only ever a lossy sink. M-D2-TOK demoted --
tokens are not comparable across models or cache states.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Instrument: session transcript JSONL, deduped by requestId, priced per
model and per cache TTL. Hub rejected as a source (no cache fields, ~100%
error on the same work), kept as sink. Status bar and billing API
rejected on availability.
Attribution: (prev_commit, this_commit] ending-at-commit, scoped by
sessionId. Explicit session markers rejected because they depend on an
agent remembering a step — the exact failure this workplan exists to fix.
Hub status transitions rejected as an after-the-fact time index.
Stated honestly: we buy accuracy with specification complexity, and D1
gets worse. That trade is only right because the alternative on offer was
not a simpler correct number but one wrong by two orders of magnitude.
Seven failure modes recorded up front, each becoming a T04 test — most
importantly that dedup fails in the under-reporting direction, so the
invariant is asserted at runtime rather than trusted.
Gate satisfied: T04 may now write collector code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adversarial review per InnerLoop Step 2 — separate agent, given only the
survey, one round. Challenge and response in history/.
The arithmetic held: every pinned figure and the whole three-method
correction table reproduced to the cent under ~20 independent spot
checks. The hand-written markdown tables did not. Three blocking
defects:
- the dedup positive-control table failed its own checksum (group
sizes summed to 348 groups / 663 lines, not 346 / 657; multi-line
groups are 206, not 208) — the worst place for a slip, since that
table is the sole evidence for the load-bearing dedup assumption
- the multi-model table was labelled "deduped" while holding per-line
counts, three lines below the paragraph condemning per-line counts
- the $92.21 acceptance target excluded the subagent tree, which ran
INSIDE the pin window. A collector correctly implementing the
survey's own blind-spot finding would have failed the benchmark.
Target is now $92.87, stated as its two components.
Folded in, each re-derived before conceding:
- attribution must key on sessionId, not wall-clock: two sessions
overlap 4h13m on this repo carrying ~$12 (one of them was this
workplan's own background job)
- only 14 of 33 commits name a task; the other 19 hold 33% of cost
- cache writes bill per TTL — pricing the aggregate at 1h inflates
the subagent by 43%
- the "~87% cache" figure matched nothing; 64.6% of cost is cache
read, 88.4% is all cache
- fable is 35% of calls and 60% of dollars; opus is the inverse
Review cost ~$0.60 against a $92 pass, the second consecutive round
where that trade found something material. Evidence for CB-WP-0003 T03.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tier L (structural L, chaos d10=2). Every candidate exercised locally
against the CB-WP-0001 transcript rather than cited.
Verdict: session transcript JSONL is the only exact instrument; git
commits supply the attribution index it lacks; the State Hub is a
durable sink whose schema cannot express cache tokens at all; the
status bar and billing API are eliminated on availability.
Three errors found in this workplan's own Purpose section, all
overstating cost:
- one API response is written as up to six JSONL lines, each
repeating the same usage object (657 lines, 346 responses)
- a multi-model session was priced entirely at Fable 5 rates,
violating a per-model rule MetricsAndScenarios §1a already had
- the transcript is a live file, so an unpinned total is not
repeatable (346 -> 356 responses between two runs minutes apart)
CB-WP-0001 pinned at fc76445: $92.21, not $248.46. The qualitative
finding sharpens rather than weakens — 88.4% of spend is cache, at
256:1 cache-read to output tokens.
Neither error was of the harness-does-nothing class the positive
control was written for; both sums ran over real data. What caught
them was re-deriving a number instead of quoting it. Raised for T07
and for CB-WP-0003 T01.
Also measured: the one subagent (adversarial review) cost $0.66 and is
invisible to a collector reading only the main transcript.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Written back by fix-consistency: workstream and task UUIDs for the ten
CB-WP-0003 tasks, plus the regenerated WORK-RECORDS.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Covers what the immediate CI fixes did not. Framing finding: writing a
rule into InnerLoop did not prevent the next instance of the failure it
was written for; making it a CI step caught one on its first run. Four
instances of the harness-does-nothing class, zero prevented by prose.
Working hypothesis: a loop rule that cannot be executed is not a rule.
Each item is made executable or deleted, default delete.
Phase A makes rules executable: audit every InnerLoop rule as
executable/checkable/decorative, extend the positive-control gate beyond
benchmarks, and point adversarial review at measurement rather than only
research — all four real errors were in measurement and build config,
which the review never reads.
Phase B is session economics, from measurement: 592 turns, mean context
245k tokens, $0.245/turn in cache reads, zero batched tool calls, cache
reads 58% of cost against output at 12%. Cost is turns x context and
context grows with turns, so a long session is quadratic. Also replaces
the dead 8k/10k token budget, which was never enforced and which T08
exceeded by orders of magnitude with no signal.
Phase C removes or fixes the rest: resolve the chaos roll (never fired,
and at 1-in-10 will stay unevaluated), require retargeting to be an ADR
rather than something the implementer does in the commit that measures
the metric, give provisional items an owner and expiry, and strengthen
the coverage gate beyond tag-counting.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The gates existed; CI ran half of them and tolerated the failure case.
- cb-sim no longer has a "tolerable" non-zero exit. An unregistered game
prefix is a failure, and a run in which nothing executed is a failure.
Previously CI carried `|| test $? -eq 2`, so renaming a scenario prefix
would have skipped every scenario while the pipeline stayed green.
Verified with a negative control.
- CI now runs make coverage (AM-1) and make dep-weight (AM-4), both
added after CI was written and neither enforced until now.
- dep-weight enforces its targets instead of only reporting them.
- CI lints the shipped-runtime configuration separately, so the feature
split cannot rot unnoticed.
- Dropped the stale `make deps` target, which still measured the retired
crate-count metric.
The positive-control rule is now executable: CI runs
`cargo bench -- --test`, which executes every benchmark once, so a
workload that stalls fails the build.
That step immediately found a fourth instance of the error class it was
written for. The committed replay benchmark was the broken version — an
earlier patch never applied, leaving a command sequence that omits
Resolve, so every round produced nothing and the log-building loop spun
forever. It had never run to completion; the reported AM-7 replay
numbers came from a probe test instead. Fixed, given the same positive
control as the round loop, and re-measured from the benchmark: 100k
events fold in 2.18ms (95% CI 2.14-2.23), against a 5s budget.
Evidence now reports confidence intervals rather than point estimates,
so the 3% regression rule in MetricsAndScenarios is enforceable.
The finding worth carrying: writing the positive-control rule into
InnerLoop v1.0 did not prevent the next instance. Making it a CI step
did.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adopts both remediations from CB-EV-0001 §4 (maintainer decision).
Option A — serde_yaml is now optional behind cb-game-runtime's
`scenarios` feature. The scenario module, the ScenarioGame impl and the
string parsers behind it are cfg-gated; cb-sim opts in explicitly. Both
configurations compile and lint clean under -D warnings.
A trap worth recording: `default-features = false` on a *member*
dependency is silently ignored when the workspace dependency does not
specify it. The first attempt gated nothing while looking correct — the
build succeeded and cargo tree still showed all six YAML crates. Fixed
by setting it on the workspace dependency. This is the positive-control
failure mode in miniature: success was not evidence the change applied.
Retarget — AM-4 now measures third-party source under audit, split by
build configuration, replacing a crate count that was unreachable
without undoing K5/K7 and that does not compare across ecosystems.
Re-measured via the new `make dep-weight`, whose own positive control
refuses to report when any crate's source cannot be located:
shipped runtime 23 crates 246,250 lines target <=250,000 met
dev toolchain 29 crates 317,021 lines target <=350,000 met
own source 3,408 lines
Scenario tooling costs 70,771 lines a shipped game never compiles —
the split the single number was hiding.
Targets are set at current measurement plus headroom, so they bind on
future growth rather than retroactively passing what had failed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AM-4: measured what each remediation option actually buys, rather than
leaving one recommendation unquantified. serde_yaml optional removes 6
crates, not 5 — ryu belongs to that group, since serde_json now uses
zmij for floats. Full ladder: -6 to 27, serde_json -4 more to 23,
inlining SHA-256 -8 to 19, inlining ChaCha12 -4 to 15. Only
reimplementing a primitive gets under 20, so the target is unreachable
without undoing K5/K7.
Also records that crate count compares badly across ecosystems, and
offers the alternative the count is a proxy for: 307,317 lines of
third-party source under audit against 3,398 of our own.
AM-12: corrected from "uncomputable" to measured. The refusal to
estimate was right; the claim that no instrument existed was wrong.
Session transcripts carry exact per-message usage including the cache
breakdown. This session cost $248.46 at Fable 5 rates, of which 53% is
cache reads — cost is driven by context size times turn count, not by
output volume. What is still missing is per-task attribution, since
nothing marks task boundaries in a transcript.
CB-WP-0002 makes cost measurable and attributable: survey the
instruments, decide the attribution model by ADR, spec metrics that
include cost composition rather than a bare total, build a collector
whose positive control refuses to emit unreconciled numbers, and prove
it by answering a question that could not be answered before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The loop has now survived CB-WP-0001 end to end and is revised from what
that pass actually taught, recorded in
history/260731-inner-loop-retrospective.md.
Held up: the adversarial review (one blocking and three significant
findings on its single use, resolved with new measurements rather than
argument), the parity-cap rule, the provisional U-item mechanism, and
the ADR gate.
The gap: both serious errors in the pass were measurement errors, and
the loop caught neither. Review reads prose; these were claims about
numbers. In both cases a harness ran successfully while doing no work —
a JS harness timing rejected no-ops, and a Rust benchmark computing
throughput for rounds that never completed.
v1.0 adds:
- Measurement validity: a harness must assert it performed the work it
reports. A number from a run that cannot prove it did the work is void.
- Metric feasibility: every metric names its instrument and is checked
reachable against the contracts in its own spec.
- No silently-ignored input; decisions get commands, not defaults;
scaffolds are exercised or marked; coverage gates that count tags say
so.
- Evidence must state what a comparison does not support.
- The chaos roll is recorded even when it changes nothing, so a
mechanism that never fires is visible rather than assumed.
CB-WP-0001 is complete: 9/9 tasks done.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
evidence/CB-EV-0001-game-kernel.md records the acceptance run against
the CB-RES-0001 baseline.
Met: AM-1 rule coverage 58/58; AM-6 throughput 1.65M events/s against a
100k target; AM-7 scaling 1.08x at 20x workload and a 100k-event replay
in 4.13ms against a 5s budget; AM-8 zero divergence over 10 full runs
with fmt and clippy clean; AM-10 zero foreign collection types.
Not met and reported as such: AM-4 at 33 transitive crates against a
<=20 target. Attribution is in the evidence file. The recommended fix
is making serde_yaml optional (-5, a test-only concern), after which
the remainder is sha2 and rand_chacha, which K5 and K7 require. We are
not hand-rolling crypto primitives to win a dependency count.
AM-12 is recorded as uncomputable: per-task token counts were never
instrumented, and inventing a USD figure would defeat the metric.
A measurement error was found and corrected before publication. The
first benchmark reported 9.3M events/s on a flat curve. The workload
had a player selecting SUPPORT while parked at Stress 4, so GR-R03
rejected it, rounds never completed, and throughput was computed for
rounds that never happened. The bench now asserts the per-round event
count and panics rather than measuring a stalled loop. The corrected
figure is 5.6x lower.
The evidence file states plainly what the boardgame.io comparison does
and does not support: the ~450x command-rate ratio is cross-runtime and
cross-feature-set, so it is a direction, not a verdict, per the
InnerLoop parity-cap rule.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- GR-R09: after Round 5's End the game ends and scoring runs instead of
the round advancing.
- GR-E01: claimed Problems sum their printed values against the
player-count threshold (2p 5, 3-4p 7, 5-6p 9 in dataset 0.1).
- GR-E02 SHARED GROUND: shared score, Mastery reduced per Blame token
and per Denied Problem.
- GR-E03 COMMON PROBLEM: personal score is claimed value less Blame,
tiebroken by lower Stress then more Bonds.
- GR-E04 BONDED COALITIONS: connected components over Bonds only, so
Rivalries do not connect and an unbonded player is a coalition of
one; tiebroken by lower combined Stress then fewer Blame.
Ties yield every tied candidate rather than an arbitrary pick, which is
what "shared victory" in GR-E03/E04 asks for.
AM-1 rule coverage is now 58/58 (100%), 21 scenarios, 17 tests.
Caveat recorded rather than papered over: GR-E02's "successes" is not
defined in dataset 0.1. It is implemented as the count of claimed
Problems and both scoring scenarios are marked provisional, so a
ground-game ruling flips a scenario rather than the kernel.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Resolution step 3 (GR-R06), the last unimplemented step:
- GR-D02 binding: one stage per consecutive round, DENY then ATTACK
then REVERSE, regardless of the player's later Stress.
- GR-D03 DENY: turn one face-up, unsolved, unprotected Problem face
down and Deny it. Under the U3 default no legal target is a no-op and
the sequence still advances.
- GR-D04 ATTACK: one extra Attack under the normal relation rules, then
place the Focus token beside the target even if it was cancelled.
- GR-D05 REVERSE: flip Focus to Blame, +1 Stress to the holder, one
Protection to the owner, unless the holder's GROUND-ND rejects it.
Under the U5 default the owner takes -2 either way and the sequence
ends.
- GR-D06 early end: a Support through a Bond that predates this round's
Support step cancels the stage and ends the sequence, and the placed
Focus token is removed. GROUND-GR ends it after the stage resolves.
- GR-D07: the marker returns to OFF, so a later End can re-trigger.
The Attack rules are now one routine shared by the chosen ATTACK Action
and the DARVO extra Attack, so GR-A06..A09 cannot drift between them.
Stage targets are named by their own command during Reveal, validated
against what the stage admits: DENY needs an eligible Problem, ATTACK
another player, and only a player with a live sequence may choose.
18 scenarios pass; AM-1 coverage 47/58 (81%), up from 41/58.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Closes the three rules deferred in iter 2 and 3 because each needed a
decision from a player rather than a default I would have invented:
- GR-A11 GROUND-OU: restore a Denied Problem, protect a face-up one
from Deny, or cancel an Attack aimed at the chooser.
- GR-A12 GROUND-ND: remove a Blame token, break a relation involving
the chooser, or reject a Reverse.
- GR-L02 / GR-A05: the Support target answers after Reveal — accept or
decline a Bond where no relation exists, flip or break an existing
Rivalry. A Bond can no longer form without consent.
Each choice is validated against the state it claims to act on: a
restore needs a Denied Problem, a cancel needs an Attack actually aimed
at the chooser, a Blame removal needs that token present, a break needs
that relation to exist. GR takes no sub-choice, and OU and ND may only
use choices from their own list.
GR-A09 ordering follows the U8 default: an OU cancellation is chosen at
step 1 and applies first, so a Protection token is spent only when it
is what actually cancels.
14 scenarios pass; AM-1 coverage 41/58 (70%), up from 34/58.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
make coverage compares the GR-rule IDs defined in specs/GroundRules.md
against the covers: lists in scenarios/ground/*.yaml. It exits 1 if a
scenario claims a rule the spec does not define, so coverage cannot be
inflated by an invented ID, and exits 2 while rules remain uncovered.
Current reading: 34/58 (58%). AM-1 requires 100%.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fills in resolution steps 1, 4 and 6 (GR-R06):
- GR-R05 mode choice: a player who revealed GROUND picks GR/OU/ND after
Reveal. Resolution refuses to start while any revealed GROUND lacks a
mode, and only that player may choose it.
- GR-A10 GROUND-GR: self -2 Stress and Freedom readied.
- GR-A01 INVESTIGATE: reveal the chosen hidden non-Denied Problem, then
draw one Solution; the draw still happens when nothing is revealable.
- GR-A02 SOLVE: spend a Solution of the Problem's suit and claim it; a
later resolver the same round spends nothing, per Lead order.
- GR-A13 tightened: INVESTIGATE must target a hidden Problem, SOLVE a
face-up non-Denied one. Previously any existing Problem was accepted.
Deck exhaustion (U4) reshuffles the discard, seeded from the game seed
and round so validate stays a pure function of state. The resulting
order travels inside DeckReshuffled, so replay never re-derives it.
Still pending, each because it needs its own decision command rather
than a default: GROUND-OU and GROUND-ND three-way choices (GR-A11/A12)
and the DARVO stage machine (GR-D02..D07). No scenario claims coverage
of them.
Filler picks in existing scenarios moved from GROUND to INVESTIGATE:
GROUND now has a real Stress effect, which was polluting the Support
and Attack assertions.
11 scenarios pass, 34 rules covered; 17 tests, fmt/clippy green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Round machinery, system-driven (GR-R04/R06/R08):
- GR-R06 fixed step order with GR-R07 Lead-first ordering inside a step.
Steps 1 (GROUND), 3 (DARVO stages), 4 (INVESTIGATE) and 6 (SOLVE) are
not implemented yet; their Actions resolve as no-ops and no scenario
claims coverage of them.
- SUPPORT GR-A03/A04/A05 and ATTACK GR-A06..A09, with relation
formation, flip and break per GR-L01/L03/L04 and Protection
cancellation.
- GR-R08 End: DARVO trigger at Stress 5 in Lead order, Lead rotation,
Round advance, per-round flags cleared.
- Stress clamps 0-5 on every application, the U2 default, so a mid-round
spike that is reduced before End does not trigger DARVO.
Two consent-dependent rules are deliberately left out because they need
a decision command rather than a default: Bond formation (GR-L02) and
the target's flip-or-break choice on Support-through-Rivalry (GR-A05).
Both are noted in code and covered by a provisional scenario.
Fixes a defect in the T07 scaffold: relations were keyed by a tuple,
which JSON cannot use as an object key, so state_hash would have
panicked on any state holding a relation. Relation keys are now a Pair
newtype serialized as "a-b", with a regression test.
setup.patch may now create a final key so scenarios can seed open-ended
maps; a typo anywhere earlier in the path is still an error.
8 scenarios pass, 28 rules covered; 17 tests, fmt/clippy green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces the RunOutcome::Unimplemented stub with a real runner:
- ScenarioGame trait: games own setup presets and the command
vocabulary, the runner owns execution, assertions, and determinism.
- K8 double-run: every scenario runs twice on the same seed and fails
on state-hash divergence.
- K4/K11: applied events go through Envelope into EventLog, so seq
monotonicity is enforced on the real path, not just in unit tests.
- setup.patch was parsed and silently dropped; the runner now applies
it generically and errors on a path that does not exist, so a typo
in a scenario can never pass as a no-op.
- Assertions: dot-path state lookup over objects and arrays, ordered
event subsequence matching by field subset, exact rejects-set match.
GROUND rules realized: GR-S01..S04 setup (seeded shuffle, deal, Lead,
Surface Problem face up), GR-R02 Select commit, GR-R03 stress gate and
Freedom spend, GR-A13 targeting legality.
cb-sim dispatches by the scenario's game prefix and reports rule
coverage. 3 scenarios pass, 7 rules covered; fmt/clippy/tests green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>