T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""M-D2-CST: USD cost of agentic work, attributed to workplan tasks.
|
|
|
|
|
|
|
|
|
|
Normative spec: specs/CostAccounting.md. Decision: decisions/ADR-0003.
|
|
|
|
|
|
|
|
|
|
Reads Claude Code session transcripts, deduplicates by requestId, prices
|
|
|
|
|
each response at its own model's rate and its own cache TTL, and attributes
|
|
|
|
|
it to the task named by the next commit at or after it within the same
|
|
|
|
|
session.
|
|
|
|
|
|
|
|
|
|
Positive control (InnerLoop v1.0 §Step 5, CostAccounting CA-02/CA-14):
|
|
|
|
|
this tool asserts it did the work it reports. `--self-test` exercises four
|
|
|
|
|
assertions against fixtures with known answers; every reporting run checks
|
|
|
|
|
the dedup invariant and reconciles attributed + unattributed + open +
|
|
|
|
|
unpriced against the raw total, aborting rather than printing a number that
|
|
|
|
|
does not add up.
|
|
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
|
python3 tools/cb-cost.py # whole repo, no pin
|
|
|
|
|
python3 tools/cb-cost.py --pin fc76445 # pinned at a commit
|
|
|
|
|
python3 tools/cb-cost.py --by-task # per-task attribution
|
|
|
|
|
python3 tools/cb-cost.py --composition # cost split by component
|
|
|
|
|
python3 tools/cb-cost.py --self-test # positive control
|
|
|
|
|
python3 tools/cb-cost.py --json
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
import collections
|
|
|
|
|
import datetime
|
|
|
|
|
import glob
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
import tomllib
|
|
|
|
|
except ModuleNotFoundError: # pragma: no cover - py<3.11
|
|
|
|
|
print("ERROR: needs Python 3.11+ for tomllib", file=sys.stderr)
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
2026-07-31 10:13:52 +02:00
|
|
|
from repo import ROOT as REPO # noqa: E402 (single source of fact, T01)
|
|
|
|
|
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
PRICES = os.path.join(REPO, "benchmarks", "baselines", "model-prices.toml")
|
|
|
|
|
COMPONENTS = ("input", "output", "cache_read", "write_5m", "write_1h")
|
CB-WP-0004 T02: make task-done — close a task on measured numbers
Replaces the three hand-done steps of a task close (46 turns, $11.52 per
CB-RES-0003): the heredoc flipping status in the workplan file, the
hand-written hub call, and the hand-typed token counts.
The third is the reason this task exists. Every update_task_status this
repo produced carried estimated tokens_in/tokens_out — in a project whose
central finding is that estimated token counts are worthless. task-done
reads the measured figure from the transcripts, or refuses; there is no
path through it that emits an estimate.
cb-cost gains by_task_detail: cost, response count, model histogram and
token components per task. task-done imports cb-cost rather than parsing
its printed table, so the hub figure is not a copy that can drift from
its source.
The positive control found a real defect before the tool ran once.
Attribution keyed on a bare T\d\d from the commit subject, so CB-WP-0002
T01, CB-WP-0003 T01 and CB-WP-0004 T01 shared a bucket: the self-test
reported $12.10 for "T01" where the qualified figure is $2.33. That 5.2x
overstatement would have been pushed to the hub as a *measured* number —
the same fiction in a new form. task_label() now keys qualified subjects
on the full id and leaves unqualified ones bare rather than
retro-assigning them to a workplan. The pinned $93.15 benchmark is
unchanged, so historical attribution was not disturbed.
Fourth instance of trusted arithmetic: a number believed because a
program produced it rather than a hand.
Refusals, all exercised by --self-test: unknown id, typo'd id,
already-done task, missing state_hub_task_id, no measured spend, and a
status flip that produced no change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:17:51 +02:00
|
|
|
# CA-08 attribution key. Qualified first: a bare `T\d\d` collides across
|
|
|
|
|
# workplans — CB-WP-0002 T01, CB-WP-0003 T01 and CB-WP-0004 T01 all matched
|
|
|
|
|
# the same bucket, so a per-task figure summed three unrelated tasks. Found
|
|
|
|
|
# by T02's positive control, which reported $12.10 for "T01".
|
|
|
|
|
TASK_QUALIFIED_RE = re.compile(r"\b(CB-WP-\d+)[ -](T\d\d)\b")
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
TASK_RE = re.compile(r"\bT\d\d\b")
|
CB-WP-0004 T02: make task-done — close a task on measured numbers
Replaces the three hand-done steps of a task close (46 turns, $11.52 per
CB-RES-0003): the heredoc flipping status in the workplan file, the
hand-written hub call, and the hand-typed token counts.
The third is the reason this task exists. Every update_task_status this
repo produced carried estimated tokens_in/tokens_out — in a project whose
central finding is that estimated token counts are worthless. task-done
reads the measured figure from the transcripts, or refuses; there is no
path through it that emits an estimate.
cb-cost gains by_task_detail: cost, response count, model histogram and
token components per task. task-done imports cb-cost rather than parsing
its printed table, so the hub figure is not a copy that can drift from
its source.
The positive control found a real defect before the tool ran once.
Attribution keyed on a bare T\d\d from the commit subject, so CB-WP-0002
T01, CB-WP-0003 T01 and CB-WP-0004 T01 shared a bucket: the self-test
reported $12.10 for "T01" where the qualified figure is $2.33. That 5.2x
overstatement would have been pushed to the hub as a *measured* number —
the same fiction in a new form. task_label() now keys qualified subjects
on the full id and leaves unqualified ones bare rather than
retro-assigning them to a workplan. The pinned $93.15 benchmark is
unchanged, so historical attribution was not disturbed.
Fourth instance of trusted arithmetic: a number believed because a
program produced it rather than a hand.
Refusals, all exercised by --self-test: unknown id, typo'd id,
already-done task, missing state_hub_task_id, no measured spend, and a
status flip that produced no change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:17:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def task_label(subject):
|
|
|
|
|
"""Attribution label for a commit subject, or None.
|
|
|
|
|
|
|
|
|
|
Qualified ids (`CB-WP-0004 T01`) become `CB-WP-0004-T01`. Bare ids
|
|
|
|
|
(`T01: ...`) stay bare — commits predating this convention did not name
|
|
|
|
|
their workplan and must not be retroactively assigned to one.
|
|
|
|
|
"""
|
|
|
|
|
m = TASK_QUALIFIED_RE.search(subject)
|
|
|
|
|
if m:
|
|
|
|
|
return f"{m.group(1)}-{m.group(2)}"
|
|
|
|
|
m = TASK_RE.search(subject)
|
|
|
|
|
return m.group(0) if m else None
|
CB-WP-0004 T05: control loop — 6 points recovered, not 25-30
cb-cost gains --since, so the baseline (--pin 578dcbe, 662 responses,
$135.60) and this pass (--since 578dcbe, 83 responses, $7.82) are two
disjoint windows over the same transcripts. Every verdict is on share of
pass, since the windows differ 17x in size.
Test 1 — did mechanical turns disappear? Partly. Mechanical share fell
38.2% -> 32.4%. Six points against a predicted 25-30; reported unmet, and
no target was moved in this commit.
environment setup 11.5% -> 0.6% (85 turns -> 1) met, decisively
hub + workplan 8.5% -> 0.0% (46 turns -> 0) met
text patching 10.4% -> 9.6% not met
orientation 5.1% -> 21.2% not met, worse
The confound is stated before any defence of the numbers: this is the
pass that built the tools, and the two categories that missed are exactly
the two whose tools were under construction. The clean test is the next
pass, and it is carried forward rather than waived.
Test 2 — did the work relocate? Not into prose. Output tokens per
response fell 896 -> 681. Output's rising share of cost is a shrinking
denominator, not more writing. Cost per response halved and the evidence
refuses to claim it: that is compaction (mean context 232,982 ->
117,822), and attributing it to tooling would repeat CB-WP-0002's
original error in a new direction.
Test 3 — did quality hold? Yes, recorded as explicit judgment. make all
green with two gates that did not exist before, and four findings
surfaced this pass, three caught by controls written this pass — one of
them a 5.2x attribution error that would have reached the hub as a
measured number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:27:09 +02:00
|
|
|
|
|
|
|
|
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
UNATTRIBUTED = "UNATTRIBUTED"
|
|
|
|
|
OPEN_REMAINDER = "OPEN (uncommitted)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Abort(Exception):
|
|
|
|
|
"""A positive-control failure. Never degrades to a printed number."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------- pricing
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_prices(path=PRICES):
|
|
|
|
|
with open(path, "rb") as fh:
|
|
|
|
|
return tomllib.load(fh)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def components(usage):
|
|
|
|
|
"""Token counts per billable component (CA-04: writes split by TTL)."""
|
|
|
|
|
cc = usage.get("cache_creation") or {}
|
|
|
|
|
return {
|
|
|
|
|
"input": usage.get("input_tokens", 0),
|
|
|
|
|
"output": usage.get("output_tokens", 0),
|
|
|
|
|
"cache_read": usage.get("cache_read_input_tokens", 0),
|
|
|
|
|
"write_5m": cc.get("ephemeral_5m_input_tokens", 0),
|
|
|
|
|
"write_1h": cc.get("ephemeral_1h_input_tokens", 0),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
T11: dated rates and staleness become data, ahead of the 2026-08-31 flip
The price sheet had two defects of one shape -- a schema that could not
hold the fact it needed, the same criticism the cost survey levelled at
the State Hub.
CA-16 time-boxed rates are DATA. Sonnet's intro price lived in a
`# intro ...` comment and was invisible to the collector that
reads the file. Now promo_input/promo_output/promo_until,
applied per response at its own timestamp.
CA-17 the 90-day staleness rule was prose in MetricsAndScenarios 1a
that every M-D2-CST verdict silently inherited. Now `recorded`
+ `max_age_days` in the sheet, and a stale sheet ABORTS.
Both are exercised by make cost-test: the promo rate must apply before
2026-08-31 and lapse after, and a 102-day-old sheet must trip.
Applying CA-16 moved AC-1 from $93.32 to $93.15 -- the $0.17 CB-EV-0002
predicted, now collected rather than noted. That is a legitimate
retarget under T07's distinction: the instrument disproved the target,
and its output is in this commit. The number has now been stated five
times ($248.46, $92.21, $92.87, $93.32, $93.15), each correction from a
different mechanism.
Evidence tables regenerated from the tool rather than hand-patched,
per CA-15 -- which is the rule that exists because hand-typed tables
were the only thing the adversarial review found wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:23:29 +02:00
|
|
|
def rates_at(prices, model, when=None):
|
|
|
|
|
"""Input/output rate in force for `model` at ISO instant `when` (CA-16).
|
|
|
|
|
|
|
|
|
|
Promotional rates are data, not comments. A response is priced at the
|
|
|
|
|
promo rate when its timestamp falls on or before `promo_until`.
|
|
|
|
|
"""
|
|
|
|
|
pr = prices.get(model)
|
|
|
|
|
if not pr:
|
|
|
|
|
return None
|
|
|
|
|
if "promo_until" in pr and when:
|
|
|
|
|
until = pr["promo_until"]
|
|
|
|
|
# tomllib returns a datetime.date for a bare TOML date.
|
|
|
|
|
if str(when)[:10] <= str(until)[:10]:
|
|
|
|
|
return pr["promo_input"], pr["promo_output"]
|
|
|
|
|
return pr["input"], pr["output"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_price_sheet_age(prices, today=None):
|
|
|
|
|
"""CA-17: a stale sheet invalidates verdicts, so it fails a command."""
|
|
|
|
|
import datetime as _dt
|
|
|
|
|
|
|
|
|
|
recorded = prices.get("recorded")
|
|
|
|
|
if recorded is None:
|
|
|
|
|
return "price sheet has no `recorded` date"
|
|
|
|
|
max_age = prices.get("max_age_days", 90)
|
|
|
|
|
today = today or _dt.date.today()
|
|
|
|
|
if isinstance(recorded, _dt.datetime):
|
|
|
|
|
recorded = recorded.date()
|
|
|
|
|
age = (today - recorded).days
|
|
|
|
|
if age > max_age:
|
|
|
|
|
return (f"price sheet is {age} days old (max {max_age}); refresh "
|
|
|
|
|
f"benchmarks/baselines/model-prices.toml or new M-D2-CST "
|
|
|
|
|
f"`better` verdicts are invalid")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def price_of(prices, model, toks, when=None):
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
"""USD for one response. Returns None when the model is unpriced (CA-05)."""
|
|
|
|
|
pr = prices.get(model)
|
|
|
|
|
if not pr:
|
|
|
|
|
return None
|
T11: dated rates and staleness become data, ahead of the 2026-08-31 flip
The price sheet had two defects of one shape -- a schema that could not
hold the fact it needed, the same criticism the cost survey levelled at
the State Hub.
CA-16 time-boxed rates are DATA. Sonnet's intro price lived in a
`# intro ...` comment and was invisible to the collector that
reads the file. Now promo_input/promo_output/promo_until,
applied per response at its own timestamp.
CA-17 the 90-day staleness rule was prose in MetricsAndScenarios 1a
that every M-D2-CST verdict silently inherited. Now `recorded`
+ `max_age_days` in the sheet, and a stale sheet ABORTS.
Both are exercised by make cost-test: the promo rate must apply before
2026-08-31 and lapse after, and a 102-day-old sheet must trip.
Applying CA-16 moved AC-1 from $93.32 to $93.15 -- the $0.17 CB-EV-0002
predicted, now collected rather than noted. That is a legitimate
retarget under T07's distinction: the instrument disproved the target,
and its output is in this commit. The number has now been stated five
times ($248.46, $92.21, $92.87, $93.32, $93.15), each correction from a
different mechanism.
Evidence tables regenerated from the tool rather than hand-patched,
per CA-15 -- which is the rule that exists because hand-typed tables
were the only thing the adversarial review found wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:23:29 +02:00
|
|
|
rin, rout = rates_at(prices, model, when)
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
cache = prices["cache"]
|
T11: dated rates and staleness become data, ahead of the 2026-08-31 flip
The price sheet had two defects of one shape -- a schema that could not
hold the fact it needed, the same criticism the cost survey levelled at
the State Hub.
CA-16 time-boxed rates are DATA. Sonnet's intro price lived in a
`# intro ...` comment and was invisible to the collector that
reads the file. Now promo_input/promo_output/promo_until,
applied per response at its own timestamp.
CA-17 the 90-day staleness rule was prose in MetricsAndScenarios 1a
that every M-D2-CST verdict silently inherited. Now `recorded`
+ `max_age_days` in the sheet, and a stale sheet ABORTS.
Both are exercised by make cost-test: the promo rate must apply before
2026-08-31 and lapse after, and a 102-day-old sheet must trip.
Applying CA-16 moved AC-1 from $93.32 to $93.15 -- the $0.17 CB-EV-0002
predicted, now collected rather than noted. That is a legitimate
retarget under T07's distinction: the instrument disproved the target,
and its output is in this commit. The number has now been stated five
times ($248.46, $92.21, $92.87, $93.32, $93.15), each correction from a
different mechanism.
Evidence tables regenerated from the tool rather than hand-patched,
per CA-15 -- which is the rule that exists because hand-typed tables
were the only thing the adversarial review found wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:23:29 +02:00
|
|
|
unit = rin / 1e6
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
return (
|
|
|
|
|
toks["input"] * unit
|
T11: dated rates and staleness become data, ahead of the 2026-08-31 flip
The price sheet had two defects of one shape -- a schema that could not
hold the fact it needed, the same criticism the cost survey levelled at
the State Hub.
CA-16 time-boxed rates are DATA. Sonnet's intro price lived in a
`# intro ...` comment and was invisible to the collector that
reads the file. Now promo_input/promo_output/promo_until,
applied per response at its own timestamp.
CA-17 the 90-day staleness rule was prose in MetricsAndScenarios 1a
that every M-D2-CST verdict silently inherited. Now `recorded`
+ `max_age_days` in the sheet, and a stale sheet ABORTS.
Both are exercised by make cost-test: the promo rate must apply before
2026-08-31 and lapse after, and a 102-day-old sheet must trip.
Applying CA-16 moved AC-1 from $93.32 to $93.15 -- the $0.17 CB-EV-0002
predicted, now collected rather than noted. That is a legitimate
retarget under T07's distinction: the instrument disproved the target,
and its output is in this commit. The number has now been stated five
times ($248.46, $92.21, $92.87, $93.32, $93.15), each correction from a
different mechanism.
Evidence tables regenerated from the tool rather than hand-patched,
per CA-15 -- which is the rule that exists because hand-typed tables
were the only thing the adversarial review found wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:23:29 +02:00
|
|
|
+ toks["output"] * rout / 1e6
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
+ toks["cache_read"] * unit * cache["read"]
|
|
|
|
|
+ toks["write_5m"] * unit * cache["write_5m"]
|
|
|
|
|
+ toks["write_1h"] * unit * cache["write_1h"]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------- transcripts
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def transcript_paths(slug):
|
|
|
|
|
"""Every transcript for the repo, including the subagent tree (CA-06)."""
|
|
|
|
|
base = os.path.expanduser(f"~/.claude/projects/{slug}")
|
|
|
|
|
return sorted(glob.glob(f"{base}/*.jsonl")) + sorted(
|
|
|
|
|
glob.glob(f"{base}/*/subagents/agent-*.jsonl")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def read_responses(path, pin=None):
|
|
|
|
|
"""Deduplicate one transcript by requestId, asserting CA-02."""
|
|
|
|
|
groups = collections.defaultdict(list)
|
|
|
|
|
for line in open(path):
|
|
|
|
|
try:
|
|
|
|
|
d = json.loads(line)
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
continue
|
|
|
|
|
if d.get("type") != "assistant":
|
|
|
|
|
continue
|
|
|
|
|
usage = (d.get("message") or {}).get("usage")
|
|
|
|
|
if usage is None:
|
|
|
|
|
continue
|
|
|
|
|
ts = d.get("timestamp") or ""
|
|
|
|
|
if pin and ts > pin:
|
|
|
|
|
continue
|
|
|
|
|
groups[d.get("requestId")].append(d)
|
|
|
|
|
|
|
|
|
|
out = []
|
|
|
|
|
for rid, rows in groups.items():
|
|
|
|
|
# CA-02 positive control. Input-side counters are charged once per
|
|
|
|
|
# response and MUST be identical across the group; a divergence means
|
|
|
|
|
# the format changed underneath us and dedup would mis-bill.
|
|
|
|
|
#
|
|
|
|
|
# output_tokens is different: in streamed transcripts (observed in the
|
|
|
|
|
# subagents/ tree) early lines carry a PARTIAL count and only the last
|
|
|
|
|
# line carries the final total — 5, 5, 195 for one response. Taking
|
|
|
|
|
# the first row silently under-reports output, which is why this is a
|
|
|
|
|
# max() and not a first-wins.
|
|
|
|
|
seen_model = {r["message"].get("model") for r in rows}
|
|
|
|
|
if len(seen_model) != 1:
|
|
|
|
|
raise Abort(
|
|
|
|
|
f"{os.path.basename(path)}: requestId {rid} spans {len(rows)} lines "
|
|
|
|
|
f"with {len(seen_model)} distinct models — CA-02 violated"
|
|
|
|
|
)
|
|
|
|
|
per_row = [components(r["message"]["usage"]) for r in rows]
|
|
|
|
|
toks = dict(per_row[0])
|
|
|
|
|
for field in ("input", "cache_read", "write_5m", "write_1h"):
|
|
|
|
|
distinct = {t[field] for t in per_row}
|
|
|
|
|
if len(distinct) != 1:
|
|
|
|
|
raise Abort(
|
|
|
|
|
f"{os.path.basename(path)}: requestId {rid} has {len(distinct)} "
|
|
|
|
|
f"distinct values for {field} across {len(rows)} lines "
|
|
|
|
|
f"({sorted(distinct)}) — CA-02 violated; input-side counters "
|
|
|
|
|
f"are charged once per response and must not vary"
|
|
|
|
|
)
|
|
|
|
|
toks["output"] = max(t["output"] for t in per_row)
|
|
|
|
|
head = rows[0]
|
T04: specs/SessionShape.md — compaction is the lever, not session length
The task's original premise was wrong and is recorded rather than
deleted. It was written to prescribe one task per session; measurement
says the variable is context, not turn count.
Measured:
- compaction cut context 27x (542,991 -> 19,974) and cost/turn 3.1x;
the 202 turns after C1 cost less than half the 136 before it
- a turn costs $0.010 at 20k context and $0.270 at 540k
- break-even for a compaction is 2-11 turns, so: compact whenever
context exceeds ~300k and work remains
- a fresh session is NOT free -- cold start floors at ~51k and must
then re-read the artifacts a compact summary already holds (~66k).
Prefer compaction to continue work; prefer a fresh session when the
task changes, because then prior context is pure overhead.
cb-cost now emits SH-1..SH-3 so the targets come from the instrument
rather than from analysis, per InnerLoop v1.1. All three are UNMET
(mean context 232,982 vs 200,000; p90 492,042 vs 300,000; batching 7.8%
vs 20%) and are reported unmet rather than retargeted -- retargeting in
the commit that first measures is precisely what T07 exists to prevent.
Eighth error instance found while writing this: CB-WP-0001's claim that
"0 of 330 tool calls were batched" is wrong. 330 was the count of
single-call responses, not the total; 31 responses batched, covering 76
calls. It was carried into this workplan unverified. Trusted-arithmetic
class -- the one the T01 audit flagged as having no executable defence,
confirming that finding within hours of making it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:19:32 +02:00
|
|
|
# Tool calls are spread across the group's lines, so they are counted
|
|
|
|
|
# over the whole group — one response may carry several (SS-05).
|
CB-RES-0003 + CB-WP-0004: 38% of pass cost is mechanical turns
Review of where token-priced turns did work a deterministic tool could
do. Method: classify every turn in both transcripts by the tool calls it
made. The classifier is committed in tools/cb-cost.py and emitted by
`make cost-mix`, so the baseline is reproducible and the same command
can later falsify the predictions.
mech environment setup 84 turns $15.33
mech ad-hoc text patching 75 turns $13.86
git 37 turns $13.85
mech hub task status 25 turns $ 7.46
mech orientation / inspect 49 turns $ 6.87
hub other 32 turns $ 6.22
mech ad-hoc transcript 39 turns $ 4.56
mech workplan status edit 21 turns $ 4.06
MECHANICAL (dedup) 290 turns $51.26 = 38% of pass
Largest category is `cd` and `export PATH` -- pure friction, and
dep-weight.py already patched it at the leaf, which is evidence it was
noticed and fixed in the wrong place. Second is heredocs string-patching
markdown, which is also the mechanism behind duplicated-fact drift, the
error class InnerLoop v1.2 names and cannot gate.
Explicitly NOT automated: git (37 turns, $13.85) is mostly commit
message authorship -- the highest-output turns in the corpus and the
project's reasoning record. Automating it would save money and destroy
what makes corrections cheap.
CB-WP-0004 implements five candidates and predicts $33-41 recovery
(25-30%), below the 38% measured share on purpose: some inspection and
patching is genuinely exploratory.
The control loop is the deliverable, not a formality. T05 tests three
things and must report all: did mechanical turns disappear, did they
RELOCATE into prose, and did quality hold. If mechanical turns fall and
prose rises by as much, the saving is zero and that is the result to
publish.
Also a self-indictment worth recording: every hub update_task_status in
this project carried hand-typed token estimates, in a repo whose central
finding is that estimated token counts are worthless. T02 fixes it by
reading measured values from cb-cost.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:49:57 +02:00
|
|
|
blocks = [c for r in rows for c in (r["message"].get("content") or [])
|
|
|
|
|
if c.get("type") == "tool_use"]
|
|
|
|
|
tool_calls = len(blocks)
|
|
|
|
|
cats = sorted({
|
|
|
|
|
c for c in (classify_tool(b.get("name"), b.get("input") or {})
|
|
|
|
|
for b in blocks) if c
|
|
|
|
|
})
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
out.append(
|
|
|
|
|
{
|
|
|
|
|
"request_id": rid,
|
|
|
|
|
"model": head["message"].get("model"),
|
|
|
|
|
"timestamp": head.get("timestamp") or "",
|
|
|
|
|
"session": head.get("sessionId") or os.path.basename(path),
|
|
|
|
|
"toks": toks,
|
T04: specs/SessionShape.md — compaction is the lever, not session length
The task's original premise was wrong and is recorded rather than
deleted. It was written to prescribe one task per session; measurement
says the variable is context, not turn count.
Measured:
- compaction cut context 27x (542,991 -> 19,974) and cost/turn 3.1x;
the 202 turns after C1 cost less than half the 136 before it
- a turn costs $0.010 at 20k context and $0.270 at 540k
- break-even for a compaction is 2-11 turns, so: compact whenever
context exceeds ~300k and work remains
- a fresh session is NOT free -- cold start floors at ~51k and must
then re-read the artifacts a compact summary already holds (~66k).
Prefer compaction to continue work; prefer a fresh session when the
task changes, because then prior context is pure overhead.
cb-cost now emits SH-1..SH-3 so the targets come from the instrument
rather than from analysis, per InnerLoop v1.1. All three are UNMET
(mean context 232,982 vs 200,000; p90 492,042 vs 300,000; batching 7.8%
vs 20%) and are reported unmet rather than retargeted -- retargeting in
the commit that first measures is precisely what T07 exists to prevent.
Eighth error instance found while writing this: CB-WP-0001's claim that
"0 of 330 tool calls were batched" is wrong. 330 was the count of
single-call responses, not the total; 31 responses batched, covering 76
calls. It was carried into this workplan unverified. Trusted-arithmetic
class -- the one the T01 audit flagged as having no executable defence,
confirming that finding within hours of making it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:19:32 +02:00
|
|
|
"tool_calls": tool_calls,
|
CB-RES-0003 + CB-WP-0004: 38% of pass cost is mechanical turns
Review of where token-priced turns did work a deterministic tool could
do. Method: classify every turn in both transcripts by the tool calls it
made. The classifier is committed in tools/cb-cost.py and emitted by
`make cost-mix`, so the baseline is reproducible and the same command
can later falsify the predictions.
mech environment setup 84 turns $15.33
mech ad-hoc text patching 75 turns $13.86
git 37 turns $13.85
mech hub task status 25 turns $ 7.46
mech orientation / inspect 49 turns $ 6.87
hub other 32 turns $ 6.22
mech ad-hoc transcript 39 turns $ 4.56
mech workplan status edit 21 turns $ 4.06
MECHANICAL (dedup) 290 turns $51.26 = 38% of pass
Largest category is `cd` and `export PATH` -- pure friction, and
dep-weight.py already patched it at the leaf, which is evidence it was
noticed and fixed in the wrong place. Second is heredocs string-patching
markdown, which is also the mechanism behind duplicated-fact drift, the
error class InnerLoop v1.2 names and cannot gate.
Explicitly NOT automated: git (37 turns, $13.85) is mostly commit
message authorship -- the highest-output turns in the corpus and the
project's reasoning record. Automating it would save money and destroy
what makes corrections cheap.
CB-WP-0004 implements five candidates and predicts $33-41 recovery
(25-30%), below the 38% measured share on purpose: some inspection and
patching is genuinely exploratory.
The control loop is the deliverable, not a formality. T05 tests three
things and must report all: did mechanical turns disappear, did they
RELOCATE into prose, and did quality hold. If mechanical turns fall and
prose rises by as much, the saving is zero and that is the result to
publish.
Also a self-indictment worth recording: every hub update_task_status in
this project carried hand-typed token estimates, in a repo whose central
finding is that estimated token counts are worthless. T02 fixes it by
reading measured values from cb-cost.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:49:57 +02:00
|
|
|
"categories": cats,
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
"subagent": "/subagents/" in path,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
CB-RES-0003 + CB-WP-0004: 38% of pass cost is mechanical turns
Review of where token-priced turns did work a deterministic tool could
do. Method: classify every turn in both transcripts by the tool calls it
made. The classifier is committed in tools/cb-cost.py and emitted by
`make cost-mix`, so the baseline is reproducible and the same command
can later falsify the predictions.
mech environment setup 84 turns $15.33
mech ad-hoc text patching 75 turns $13.86
git 37 turns $13.85
mech hub task status 25 turns $ 7.46
mech orientation / inspect 49 turns $ 6.87
hub other 32 turns $ 6.22
mech ad-hoc transcript 39 turns $ 4.56
mech workplan status edit 21 turns $ 4.06
MECHANICAL (dedup) 290 turns $51.26 = 38% of pass
Largest category is `cd` and `export PATH` -- pure friction, and
dep-weight.py already patched it at the leaf, which is evidence it was
noticed and fixed in the wrong place. Second is heredocs string-patching
markdown, which is also the mechanism behind duplicated-fact drift, the
error class InnerLoop v1.2 names and cannot gate.
Explicitly NOT automated: git (37 turns, $13.85) is mostly commit
message authorship -- the highest-output turns in the corpus and the
project's reasoning record. Automating it would save money and destroy
what makes corrections cheap.
CB-WP-0004 implements five candidates and predicts $33-41 recovery
(25-30%), below the 38% measured share on purpose: some inspection and
patching is genuinely exploratory.
The control loop is the deliverable, not a formality. T05 tests three
things and must report all: did mechanical turns disappear, did they
RELOCATE into prose, and did quality hold. If mechanical turns fall and
prose rises by as much, the saving is zero and that is the result to
publish.
Also a self-indictment worth recording: every hub update_task_status in
this project carried hand-typed token estimates, in a repo whose central
finding is that estimated token counts are worthless. T02 fixes it by
reading measured values from cb-cost.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:49:57 +02:00
|
|
|
# CB-RES-0003: which turns are mechanical (a deterministic tool could do
|
|
|
|
|
# them) versus judgment (only an agent can). The baseline for measuring
|
|
|
|
|
# whether automation actually removes turns rather than relocating them.
|
|
|
|
|
def classify_tool(name, inp):
|
|
|
|
|
if name == "mcp__dev-hub__update_task_status":
|
|
|
|
|
return "hub task status"
|
|
|
|
|
if name.startswith("mcp__dev-hub__"):
|
|
|
|
|
return "hub other"
|
|
|
|
|
if name != "Bash":
|
|
|
|
|
return None
|
|
|
|
|
cmd = (inp.get("command") or "").strip()
|
|
|
|
|
if not cmd:
|
|
|
|
|
return None
|
|
|
|
|
if "python3 - <<" in cmd:
|
|
|
|
|
if "status: todo" in cmd or "status: done" in cmd:
|
|
|
|
|
return "workplan status edit"
|
|
|
|
|
if "jsonl" in cmd or "requestId" in cmd or "usage" in cmd:
|
|
|
|
|
return "ad-hoc transcript analysis"
|
|
|
|
|
return "ad-hoc text patching"
|
|
|
|
|
if cmd.startswith(("cd ", "export ")):
|
|
|
|
|
return "environment setup"
|
|
|
|
|
if cmd.split()[0] in ("grep", "ls", "wc", "sed", "head", "tail", "cat", "find"):
|
|
|
|
|
return "orientation / inspect"
|
|
|
|
|
if cmd.startswith("git "):
|
|
|
|
|
return "git"
|
|
|
|
|
if "make " in cmd:
|
|
|
|
|
return "make (gates)"
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Categories a deterministic tool could plausibly own. Judgment-bearing
|
|
|
|
|
# categories (git commit messages, make gates) are excluded deliberately.
|
|
|
|
|
MECHANICAL = frozenset({
|
|
|
|
|
"environment setup", "ad-hoc text patching", "orientation / inspect",
|
|
|
|
|
"ad-hoc transcript analysis", "hub task status", "workplan status edit",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
T04: specs/SessionShape.md — compaction is the lever, not session length
The task's original premise was wrong and is recorded rather than
deleted. It was written to prescribe one task per session; measurement
says the variable is context, not turn count.
Measured:
- compaction cut context 27x (542,991 -> 19,974) and cost/turn 3.1x;
the 202 turns after C1 cost less than half the 136 before it
- a turn costs $0.010 at 20k context and $0.270 at 540k
- break-even for a compaction is 2-11 turns, so: compact whenever
context exceeds ~300k and work remains
- a fresh session is NOT free -- cold start floors at ~51k and must
then re-read the artifacts a compact summary already holds (~66k).
Prefer compaction to continue work; prefer a fresh session when the
task changes, because then prior context is pure overhead.
cb-cost now emits SH-1..SH-3 so the targets come from the instrument
rather than from analysis, per InnerLoop v1.1. All three are UNMET
(mean context 232,982 vs 200,000; p90 492,042 vs 300,000; batching 7.8%
vs 20%) and are reported unmet rather than retargeted -- retargeting in
the commit that first measures is precisely what T07 exists to prevent.
Eighth error instance found while writing this: CB-WP-0001's claim that
"0 of 330 tool calls were batched" is wrong. 330 was the count of
single-call responses, not the total; 31 responses batched, covering 76
calls. It was carried into this workplan unverified. Trusted-arithmetic
class -- the one the T01 audit flagged as having no executable defence,
confirming that finding within hours of making it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:19:32 +02:00
|
|
|
def session_shape(responses):
|
|
|
|
|
"""SH-1..SH-3 from specs/SessionShape.md."""
|
|
|
|
|
import statistics
|
|
|
|
|
|
|
|
|
|
ctx = sorted(r["toks"]["cache_read"] for r in responses)
|
|
|
|
|
with_tools = [r for r in responses if r["tool_calls"] > 0]
|
|
|
|
|
batched = [r for r in with_tools if r["tool_calls"] > 1]
|
|
|
|
|
calls = sum(r["tool_calls"] for r in with_tools)
|
|
|
|
|
pct = statistics.quantiles(ctx, n=100) if len(ctx) > 1 else [ctx[0]] * 99
|
|
|
|
|
return {
|
|
|
|
|
"SH-1_mean_context": statistics.mean(ctx) if ctx else 0,
|
|
|
|
|
"SH-2_p90_context": pct[89],
|
|
|
|
|
"SH-3_batching_rate": (len(batched) / len(with_tools)) if with_tools else 0.0,
|
|
|
|
|
"p50_context": pct[49],
|
|
|
|
|
"responses_with_tools": len(with_tools),
|
|
|
|
|
"tool_calls": calls,
|
|
|
|
|
"calls_in_batched_turns": calls - (len(with_tools) - len(batched)),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
# ------------------------------------------------------------- attribution
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def commit_index(pin=None):
|
|
|
|
|
"""(utc_time, subject) for each commit, oldest first (CA-09)."""
|
|
|
|
|
fmt = subprocess.run(
|
|
|
|
|
["git", "-C", REPO, "log", "--format=%cI|%s", "--reverse"],
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
check=True,
|
|
|
|
|
).stdout.splitlines()
|
|
|
|
|
rows = []
|
|
|
|
|
for line in fmt:
|
|
|
|
|
iso, _, subject = line.partition("|")
|
|
|
|
|
# CA-09: convert, never subtract a fixed offset — this repo has two.
|
|
|
|
|
utc = (
|
|
|
|
|
datetime.datetime.fromisoformat(iso)
|
|
|
|
|
.astimezone(datetime.timezone.utc)
|
|
|
|
|
.isoformat()
|
|
|
|
|
.replace("+00:00", "Z")
|
|
|
|
|
)
|
|
|
|
|
rows.append((utc, subject))
|
|
|
|
|
if pin:
|
|
|
|
|
rows = [r for r in rows if r[0] <= pin]
|
|
|
|
|
return rows
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def resolve_pin(ref):
|
|
|
|
|
"""A commit-ish pin becomes the UTC instant of that commit."""
|
|
|
|
|
if not ref:
|
|
|
|
|
return None
|
|
|
|
|
if ref.endswith("Z"):
|
|
|
|
|
return ref
|
|
|
|
|
iso = subprocess.run(
|
|
|
|
|
["git", "-C", REPO, "log", "-1", "--format=%cI", ref],
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
check=True,
|
|
|
|
|
).stdout.strip()
|
|
|
|
|
return (
|
|
|
|
|
datetime.datetime.fromisoformat(iso)
|
|
|
|
|
.astimezone(datetime.timezone.utc)
|
|
|
|
|
.isoformat()
|
|
|
|
|
.replace("+00:00", "Z")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def attribute(responses, commits):
|
|
|
|
|
"""Assign each response a bucket per CA-08/CA-10/CA-11.
|
|
|
|
|
|
|
|
|
|
Intervals are ending-at-commit: (prev_commit, this_commit]. Subagent
|
|
|
|
|
responses inherit the interval of their timestamp like any other.
|
|
|
|
|
"""
|
|
|
|
|
label_for = []
|
|
|
|
|
prev = ""
|
|
|
|
|
for ts, subject in commits:
|
CB-WP-0004 T02: make task-done — close a task on measured numbers
Replaces the three hand-done steps of a task close (46 turns, $11.52 per
CB-RES-0003): the heredoc flipping status in the workplan file, the
hand-written hub call, and the hand-typed token counts.
The third is the reason this task exists. Every update_task_status this
repo produced carried estimated tokens_in/tokens_out — in a project whose
central finding is that estimated token counts are worthless. task-done
reads the measured figure from the transcripts, or refuses; there is no
path through it that emits an estimate.
cb-cost gains by_task_detail: cost, response count, model histogram and
token components per task. task-done imports cb-cost rather than parsing
its printed table, so the hub figure is not a copy that can drift from
its source.
The positive control found a real defect before the tool ran once.
Attribution keyed on a bare T\d\d from the commit subject, so CB-WP-0002
T01, CB-WP-0003 T01 and CB-WP-0004 T01 shared a bucket: the self-test
reported $12.10 for "T01" where the qualified figure is $2.33. That 5.2x
overstatement would have been pushed to the hub as a *measured* number —
the same fiction in a new form. task_label() now keys qualified subjects
on the full id and leaves unqualified ones bare rather than
retro-assigning them to a workplan. The pinned $93.15 benchmark is
unchanged, so historical attribution was not disturbed.
Fourth instance of trusted arithmetic: a number believed because a
program produced it rather than a hand.
Refusals, all exercised by --self-test: unknown id, typo'd id,
already-done task, missing state_hub_task_id, no measured spend, and a
status flip that produced no change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:17:51 +02:00
|
|
|
label_for.append((prev, ts, task_label(subject) or UNATTRIBUTED))
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
prev = ts
|
|
|
|
|
last = commits[-1][0] if commits else ""
|
|
|
|
|
|
|
|
|
|
for r in responses:
|
|
|
|
|
ts = r["timestamp"]
|
|
|
|
|
if last and ts > last:
|
|
|
|
|
r["task"] = OPEN_REMAINDER # CA-11
|
|
|
|
|
continue
|
|
|
|
|
r["task"] = UNATTRIBUTED
|
|
|
|
|
for lo, hi, label in label_for:
|
|
|
|
|
if lo < ts <= hi:
|
|
|
|
|
r["task"] = label
|
|
|
|
|
break
|
|
|
|
|
return responses
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------- report
|
|
|
|
|
|
|
|
|
|
|
CB-WP-0004 T05: control loop — 6 points recovered, not 25-30
cb-cost gains --since, so the baseline (--pin 578dcbe, 662 responses,
$135.60) and this pass (--since 578dcbe, 83 responses, $7.82) are two
disjoint windows over the same transcripts. Every verdict is on share of
pass, since the windows differ 17x in size.
Test 1 — did mechanical turns disappear? Partly. Mechanical share fell
38.2% -> 32.4%. Six points against a predicted 25-30; reported unmet, and
no target was moved in this commit.
environment setup 11.5% -> 0.6% (85 turns -> 1) met, decisively
hub + workplan 8.5% -> 0.0% (46 turns -> 0) met
text patching 10.4% -> 9.6% not met
orientation 5.1% -> 21.2% not met, worse
The confound is stated before any defence of the numbers: this is the
pass that built the tools, and the two categories that missed are exactly
the two whose tools were under construction. The clean test is the next
pass, and it is carried forward rather than waived.
Test 2 — did the work relocate? Not into prose. Output tokens per
response fell 896 -> 681. Output's rising share of cost is a shrinking
denominator, not more writing. Cost per response halved and the evidence
refuses to claim it: that is compaction (mean context 232,982 ->
117,822), and attributing it to tooling would repeat CB-WP-0002's
original error in a new direction.
Test 3 — did quality hold? Yes, recorded as explicit judgment. make all
green with two gates that did not exist before, and four findings
surfaced this pass, three caught by controls written this pass — one of
them a 5.2x attribution error that would have reached the hub as a
measured number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:27:09 +02:00
|
|
|
def collect(slug, pin_ref=None, since_ref=None):
|
|
|
|
|
"""Report over (since_ref, pin_ref]. Both ends optional.
|
|
|
|
|
|
|
|
|
|
`since` exists for CB-WP-0004 T05: comparing a pass against the pass
|
|
|
|
|
that measured it needs two disjoint windows over the same transcripts,
|
|
|
|
|
not two whole-corpus totals. Attribution and reconciliation are
|
|
|
|
|
unchanged — the window only selects which responses are counted.
|
|
|
|
|
"""
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
prices = load_prices()
|
|
|
|
|
pin = resolve_pin(pin_ref)
|
CB-WP-0004 T05: control loop — 6 points recovered, not 25-30
cb-cost gains --since, so the baseline (--pin 578dcbe, 662 responses,
$135.60) and this pass (--since 578dcbe, 83 responses, $7.82) are two
disjoint windows over the same transcripts. Every verdict is on share of
pass, since the windows differ 17x in size.
Test 1 — did mechanical turns disappear? Partly. Mechanical share fell
38.2% -> 32.4%. Six points against a predicted 25-30; reported unmet, and
no target was moved in this commit.
environment setup 11.5% -> 0.6% (85 turns -> 1) met, decisively
hub + workplan 8.5% -> 0.0% (46 turns -> 0) met
text patching 10.4% -> 9.6% not met
orientation 5.1% -> 21.2% not met, worse
The confound is stated before any defence of the numbers: this is the
pass that built the tools, and the two categories that missed are exactly
the two whose tools were under construction. The clean test is the next
pass, and it is carried forward rather than waived.
Test 2 — did the work relocate? Not into prose. Output tokens per
response fell 896 -> 681. Output's rising share of cost is a shrinking
denominator, not more writing. Cost per response halved and the evidence
refuses to claim it: that is compaction (mean context 232,982 ->
117,822), and attributing it to tooling would repeat CB-WP-0002's
original error in a new direction.
Test 3 — did quality hold? Yes, recorded as explicit judgment. make all
green with two gates that did not exist before, and four findings
surfaced this pass, three caught by controls written this pass — one of
them a 5.2x attribution error that would have reached the hub as a
measured number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:27:09 +02:00
|
|
|
since = resolve_pin(since_ref)
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
paths = transcript_paths(slug)
|
|
|
|
|
if not paths:
|
|
|
|
|
raise Abort(f"no transcripts found for {slug}")
|
|
|
|
|
|
|
|
|
|
responses = []
|
|
|
|
|
for p in paths:
|
|
|
|
|
responses.extend(read_responses(p, pin))
|
CB-WP-0004 T05: control loop — 6 points recovered, not 25-30
cb-cost gains --since, so the baseline (--pin 578dcbe, 662 responses,
$135.60) and this pass (--since 578dcbe, 83 responses, $7.82) are two
disjoint windows over the same transcripts. Every verdict is on share of
pass, since the windows differ 17x in size.
Test 1 — did mechanical turns disappear? Partly. Mechanical share fell
38.2% -> 32.4%. Six points against a predicted 25-30; reported unmet, and
no target was moved in this commit.
environment setup 11.5% -> 0.6% (85 turns -> 1) met, decisively
hub + workplan 8.5% -> 0.0% (46 turns -> 0) met
text patching 10.4% -> 9.6% not met
orientation 5.1% -> 21.2% not met, worse
The confound is stated before any defence of the numbers: this is the
pass that built the tools, and the two categories that missed are exactly
the two whose tools were under construction. The clean test is the next
pass, and it is carried forward rather than waived.
Test 2 — did the work relocate? Not into prose. Output tokens per
response fell 896 -> 681. Output's rising share of cost is a shrinking
denominator, not more writing. Cost per response halved and the evidence
refuses to claim it: that is compaction (mean context 232,982 ->
117,822), and attributing it to tooling would repeat CB-WP-0002's
original error in a new direction.
Test 3 — did quality hold? Yes, recorded as explicit judgment. make all
green with two gates that did not exist before, and four findings
surfaced this pass, three caught by controls written this pass — one of
them a 5.2x attribution error that would have reached the hub as a
measured number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:27:09 +02:00
|
|
|
if since:
|
|
|
|
|
responses = [r for r in responses if r["timestamp"] > since]
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
if not responses:
|
|
|
|
|
# Positive control: a run that measured nothing must not report $0.00
|
|
|
|
|
# as though it were an answer.
|
|
|
|
|
raise Abort(f"no responses in {len(paths)} transcript(s) — refusing to report")
|
|
|
|
|
|
T11: dated rates and staleness become data, ahead of the 2026-08-31 flip
The price sheet had two defects of one shape -- a schema that could not
hold the fact it needed, the same criticism the cost survey levelled at
the State Hub.
CA-16 time-boxed rates are DATA. Sonnet's intro price lived in a
`# intro ...` comment and was invisible to the collector that
reads the file. Now promo_input/promo_output/promo_until,
applied per response at its own timestamp.
CA-17 the 90-day staleness rule was prose in MetricsAndScenarios 1a
that every M-D2-CST verdict silently inherited. Now `recorded`
+ `max_age_days` in the sheet, and a stale sheet ABORTS.
Both are exercised by make cost-test: the promo rate must apply before
2026-08-31 and lapse after, and a 102-day-old sheet must trip.
Applying CA-16 moved AC-1 from $93.32 to $93.15 -- the $0.17 CB-EV-0002
predicted, now collected rather than noted. That is a legitimate
retarget under T07's distinction: the instrument disproved the target,
and its output is in this commit. The number has now been stated five
times ($248.46, $92.21, $92.87, $93.32, $93.15), each correction from a
different mechanism.
Evidence tables regenerated from the tool rather than hand-patched,
per CA-15 -- which is the rule that exists because hand-typed tables
were the only thing the adversarial review found wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:23:29 +02:00
|
|
|
stale = check_price_sheet_age(prices)
|
|
|
|
|
if stale:
|
|
|
|
|
raise Abort(stale)
|
|
|
|
|
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
for r in responses:
|
T11: dated rates and staleness become data, ahead of the 2026-08-31 flip
The price sheet had two defects of one shape -- a schema that could not
hold the fact it needed, the same criticism the cost survey levelled at
the State Hub.
CA-16 time-boxed rates are DATA. Sonnet's intro price lived in a
`# intro ...` comment and was invisible to the collector that
reads the file. Now promo_input/promo_output/promo_until,
applied per response at its own timestamp.
CA-17 the 90-day staleness rule was prose in MetricsAndScenarios 1a
that every M-D2-CST verdict silently inherited. Now `recorded`
+ `max_age_days` in the sheet, and a stale sheet ABORTS.
Both are exercised by make cost-test: the promo rate must apply before
2026-08-31 and lapse after, and a 102-day-old sheet must trip.
Applying CA-16 moved AC-1 from $93.32 to $93.15 -- the $0.17 CB-EV-0002
predicted, now collected rather than noted. That is a legitimate
retarget under T07's distinction: the instrument disproved the target,
and its output is in this commit. The number has now been stated five
times ($248.46, $92.21, $92.87, $93.32, $93.15), each correction from a
different mechanism.
Evidence tables regenerated from the tool rather than hand-patched,
per CA-15 -- which is the rule that exists because hand-typed tables
were the only thing the adversarial review found wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:23:29 +02:00
|
|
|
r["cost"] = price_of(prices, r["model"], r["toks"], r["timestamp"])
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
|
|
|
|
|
attribute(responses, commit_index(pin))
|
|
|
|
|
|
|
|
|
|
by_task = collections.defaultdict(float)
|
CB-WP-0004 T02: make task-done — close a task on measured numbers
Replaces the three hand-done steps of a task close (46 turns, $11.52 per
CB-RES-0003): the heredoc flipping status in the workplan file, the
hand-written hub call, and the hand-typed token counts.
The third is the reason this task exists. Every update_task_status this
repo produced carried estimated tokens_in/tokens_out — in a project whose
central finding is that estimated token counts are worthless. task-done
reads the measured figure from the transcripts, or refuses; there is no
path through it that emits an estimate.
cb-cost gains by_task_detail: cost, response count, model histogram and
token components per task. task-done imports cb-cost rather than parsing
its printed table, so the hub figure is not a copy that can drift from
its source.
The positive control found a real defect before the tool ran once.
Attribution keyed on a bare T\d\d from the commit subject, so CB-WP-0002
T01, CB-WP-0003 T01 and CB-WP-0004 T01 shared a bucket: the self-test
reported $12.10 for "T01" where the qualified figure is $2.33. That 5.2x
overstatement would have been pushed to the hub as a *measured* number —
the same fiction in a new form. task_label() now keys qualified subjects
on the full id and leaves unqualified ones bare rather than
retro-assigning them to a workplan. The pinned $93.15 benchmark is
unchanged, so historical attribution was not disturbed.
Fourth instance of trusted arithmetic: a number believed because a
program produced it rather than a hand.
Refusals, all exercised by --self-test: unknown id, typo'd id,
already-done task, missing state_hub_task_id, no measured spend, and a
status flip that produced no change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:17:51 +02:00
|
|
|
# T02: per-task token detail, so a task close can report *measured*
|
|
|
|
|
# tokens. Every update_task_status in this project before T02 carried
|
|
|
|
|
# hand-typed estimates — in a repo whose finding is that estimated
|
|
|
|
|
# token counts are worthless.
|
|
|
|
|
task_detail = collections.defaultdict(
|
|
|
|
|
lambda: {"cost": 0.0, "responses": 0, "models": collections.Counter(),
|
|
|
|
|
"toks": collections.Counter()})
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
by_component = collections.Counter()
|
|
|
|
|
by_component_cost = collections.defaultdict(float)
|
|
|
|
|
by_model = collections.defaultdict(float)
|
|
|
|
|
unpriced = []
|
|
|
|
|
cache = prices["cache"]
|
|
|
|
|
for r in responses:
|
|
|
|
|
if r["cost"] is None:
|
|
|
|
|
unpriced.append(r)
|
|
|
|
|
continue
|
|
|
|
|
by_task[r["task"]] += r["cost"]
|
CB-WP-0004 T02: make task-done — close a task on measured numbers
Replaces the three hand-done steps of a task close (46 turns, $11.52 per
CB-RES-0003): the heredoc flipping status in the workplan file, the
hand-written hub call, and the hand-typed token counts.
The third is the reason this task exists. Every update_task_status this
repo produced carried estimated tokens_in/tokens_out — in a project whose
central finding is that estimated token counts are worthless. task-done
reads the measured figure from the transcripts, or refuses; there is no
path through it that emits an estimate.
cb-cost gains by_task_detail: cost, response count, model histogram and
token components per task. task-done imports cb-cost rather than parsing
its printed table, so the hub figure is not a copy that can drift from
its source.
The positive control found a real defect before the tool ran once.
Attribution keyed on a bare T\d\d from the commit subject, so CB-WP-0002
T01, CB-WP-0003 T01 and CB-WP-0004 T01 shared a bucket: the self-test
reported $12.10 for "T01" where the qualified figure is $2.33. That 5.2x
overstatement would have been pushed to the hub as a *measured* number —
the same fiction in a new form. task_label() now keys qualified subjects
on the full id and leaves unqualified ones bare rather than
retro-assigning them to a workplan. The pinned $93.15 benchmark is
unchanged, so historical attribution was not disturbed.
Fourth instance of trusted arithmetic: a number believed because a
program produced it rather than a hand.
Refusals, all exercised by --self-test: unknown id, typo'd id,
already-done task, missing state_hub_task_id, no measured spend, and a
status flip that produced no change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:17:51 +02:00
|
|
|
d = task_detail[r["task"]]
|
|
|
|
|
d["cost"] += r["cost"]
|
|
|
|
|
d["responses"] += 1
|
|
|
|
|
d["models"][r["model"]] += 1
|
|
|
|
|
d["toks"].update(r["toks"])
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
by_model[r["model"]] += r["cost"]
|
T11: dated rates and staleness become data, ahead of the 2026-08-31 flip
The price sheet had two defects of one shape -- a schema that could not
hold the fact it needed, the same criticism the cost survey levelled at
the State Hub.
CA-16 time-boxed rates are DATA. Sonnet's intro price lived in a
`# intro ...` comment and was invisible to the collector that
reads the file. Now promo_input/promo_output/promo_until,
applied per response at its own timestamp.
CA-17 the 90-day staleness rule was prose in MetricsAndScenarios 1a
that every M-D2-CST verdict silently inherited. Now `recorded`
+ `max_age_days` in the sheet, and a stale sheet ABORTS.
Both are exercised by make cost-test: the promo rate must apply before
2026-08-31 and lapse after, and a 102-day-old sheet must trip.
Applying CA-16 moved AC-1 from $93.32 to $93.15 -- the $0.17 CB-EV-0002
predicted, now collected rather than noted. That is a legitimate
retarget under T07's distinction: the instrument disproved the target,
and its output is in this commit. The number has now been stated five
times ($248.46, $92.21, $92.87, $93.32, $93.15), each correction from a
different mechanism.
Evidence tables regenerated from the tool rather than hand-patched,
per CA-15 -- which is the rule that exists because hand-typed tables
were the only thing the adversarial review found wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:23:29 +02:00
|
|
|
rin, rout = rates_at(prices, r["model"], r["timestamp"])
|
|
|
|
|
unit = rin / 1e6
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
rates = {
|
|
|
|
|
"input": unit,
|
T11: dated rates and staleness become data, ahead of the 2026-08-31 flip
The price sheet had two defects of one shape -- a schema that could not
hold the fact it needed, the same criticism the cost survey levelled at
the State Hub.
CA-16 time-boxed rates are DATA. Sonnet's intro price lived in a
`# intro ...` comment and was invisible to the collector that
reads the file. Now promo_input/promo_output/promo_until,
applied per response at its own timestamp.
CA-17 the 90-day staleness rule was prose in MetricsAndScenarios 1a
that every M-D2-CST verdict silently inherited. Now `recorded`
+ `max_age_days` in the sheet, and a stale sheet ABORTS.
Both are exercised by make cost-test: the promo rate must apply before
2026-08-31 and lapse after, and a 102-day-old sheet must trip.
Applying CA-16 moved AC-1 from $93.32 to $93.15 -- the $0.17 CB-EV-0002
predicted, now collected rather than noted. That is a legitimate
retarget under T07's distinction: the instrument disproved the target,
and its output is in this commit. The number has now been stated five
times ($248.46, $92.21, $92.87, $93.32, $93.15), each correction from a
different mechanism.
Evidence tables regenerated from the tool rather than hand-patched,
per CA-15 -- which is the rule that exists because hand-typed tables
were the only thing the adversarial review found wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:23:29 +02:00
|
|
|
"output": rout / 1e6,
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
"cache_read": unit * cache["read"],
|
|
|
|
|
"write_5m": unit * cache["write_5m"],
|
|
|
|
|
"write_1h": unit * cache["write_1h"],
|
|
|
|
|
}
|
|
|
|
|
for k, n in r["toks"].items():
|
|
|
|
|
by_component[k] += n
|
|
|
|
|
by_component_cost[k] += n * rates[k]
|
|
|
|
|
|
|
|
|
|
total = sum(by_task.values())
|
|
|
|
|
# CA-14: reconciliation is asserted, not assumed.
|
|
|
|
|
residual = total - sum(by_component_cost.values())
|
|
|
|
|
if abs(residual) > 0.005:
|
|
|
|
|
raise Abort(
|
|
|
|
|
f"reconciliation failed: task total ${total:,.4f} vs component total "
|
|
|
|
|
f"${sum(by_component_cost.values()):,.4f} (residual ${residual:,.4f})"
|
|
|
|
|
)
|
|
|
|
|
|
CB-RES-0003 + CB-WP-0004: 38% of pass cost is mechanical turns
Review of where token-priced turns did work a deterministic tool could
do. Method: classify every turn in both transcripts by the tool calls it
made. The classifier is committed in tools/cb-cost.py and emitted by
`make cost-mix`, so the baseline is reproducible and the same command
can later falsify the predictions.
mech environment setup 84 turns $15.33
mech ad-hoc text patching 75 turns $13.86
git 37 turns $13.85
mech hub task status 25 turns $ 7.46
mech orientation / inspect 49 turns $ 6.87
hub other 32 turns $ 6.22
mech ad-hoc transcript 39 turns $ 4.56
mech workplan status edit 21 turns $ 4.06
MECHANICAL (dedup) 290 turns $51.26 = 38% of pass
Largest category is `cd` and `export PATH` -- pure friction, and
dep-weight.py already patched it at the leaf, which is evidence it was
noticed and fixed in the wrong place. Second is heredocs string-patching
markdown, which is also the mechanism behind duplicated-fact drift, the
error class InnerLoop v1.2 names and cannot gate.
Explicitly NOT automated: git (37 turns, $13.85) is mostly commit
message authorship -- the highest-output turns in the corpus and the
project's reasoning record. Automating it would save money and destroy
what makes corrections cheap.
CB-WP-0004 implements five candidates and predicts $33-41 recovery
(25-30%), below the 38% measured share on purpose: some inspection and
patching is genuinely exploratory.
The control loop is the deliverable, not a formality. T05 tests three
things and must report all: did mechanical turns disappear, did they
RELOCATE into prose, and did quality hold. If mechanical turns fall and
prose rises by as much, the saving is zero and that is the result to
publish.
Also a self-indictment worth recording: every hub update_task_status in
this project carried hand-typed token estimates, in a repo whose central
finding is that estimated token counts are worthless. T02 fixes it by
reading measured values from cb-cost.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:49:57 +02:00
|
|
|
# Tool mix: a turn's whole cost is charged to each category it touched,
|
|
|
|
|
# so columns may overlap and must not be summed as if disjoint.
|
|
|
|
|
mix_turns, mix_cost = collections.Counter(), collections.defaultdict(float)
|
|
|
|
|
for r in responses:
|
|
|
|
|
for c in r.get("categories") or []:
|
|
|
|
|
mix_turns[c] += 1
|
|
|
|
|
mix_cost[c] += r["cost"] or 0.0
|
|
|
|
|
mech = [r for r in responses
|
|
|
|
|
if set(r.get("categories") or []) & MECHANICAL]
|
|
|
|
|
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
sub = sum(r["cost"] or 0 for r in responses if r["subagent"])
|
|
|
|
|
return {
|
T04: specs/SessionShape.md — compaction is the lever, not session length
The task's original premise was wrong and is recorded rather than
deleted. It was written to prescribe one task per session; measurement
says the variable is context, not turn count.
Measured:
- compaction cut context 27x (542,991 -> 19,974) and cost/turn 3.1x;
the 202 turns after C1 cost less than half the 136 before it
- a turn costs $0.010 at 20k context and $0.270 at 540k
- break-even for a compaction is 2-11 turns, so: compact whenever
context exceeds ~300k and work remains
- a fresh session is NOT free -- cold start floors at ~51k and must
then re-read the artifacts a compact summary already holds (~66k).
Prefer compaction to continue work; prefer a fresh session when the
task changes, because then prior context is pure overhead.
cb-cost now emits SH-1..SH-3 so the targets come from the instrument
rather than from analysis, per InnerLoop v1.1. All three are UNMET
(mean context 232,982 vs 200,000; p90 492,042 vs 300,000; batching 7.8%
vs 20%) and are reported unmet rather than retargeted -- retargeting in
the commit that first measures is precisely what T07 exists to prevent.
Eighth error instance found while writing this: CB-WP-0001's claim that
"0 of 330 tool calls were batched" is wrong. 330 was the count of
single-call responses, not the total; 31 responses batched, covering 76
calls. It was carried into this workplan unverified. Trusted-arithmetic
class -- the one the T01 audit flagged as having no executable defence,
confirming that finding within hours of making it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:19:32 +02:00
|
|
|
"session_shape": session_shape(responses),
|
CB-RES-0003 + CB-WP-0004: 38% of pass cost is mechanical turns
Review of where token-priced turns did work a deterministic tool could
do. Method: classify every turn in both transcripts by the tool calls it
made. The classifier is committed in tools/cb-cost.py and emitted by
`make cost-mix`, so the baseline is reproducible and the same command
can later falsify the predictions.
mech environment setup 84 turns $15.33
mech ad-hoc text patching 75 turns $13.86
git 37 turns $13.85
mech hub task status 25 turns $ 7.46
mech orientation / inspect 49 turns $ 6.87
hub other 32 turns $ 6.22
mech ad-hoc transcript 39 turns $ 4.56
mech workplan status edit 21 turns $ 4.06
MECHANICAL (dedup) 290 turns $51.26 = 38% of pass
Largest category is `cd` and `export PATH` -- pure friction, and
dep-weight.py already patched it at the leaf, which is evidence it was
noticed and fixed in the wrong place. Second is heredocs string-patching
markdown, which is also the mechanism behind duplicated-fact drift, the
error class InnerLoop v1.2 names and cannot gate.
Explicitly NOT automated: git (37 turns, $13.85) is mostly commit
message authorship -- the highest-output turns in the corpus and the
project's reasoning record. Automating it would save money and destroy
what makes corrections cheap.
CB-WP-0004 implements five candidates and predicts $33-41 recovery
(25-30%), below the 38% measured share on purpose: some inspection and
patching is genuinely exploratory.
The control loop is the deliverable, not a formality. T05 tests three
things and must report all: did mechanical turns disappear, did they
RELOCATE into prose, and did quality hold. If mechanical turns fall and
prose rises by as much, the saving is zero and that is the result to
publish.
Also a self-indictment worth recording: every hub update_task_status in
this project carried hand-typed token estimates, in a repo whose central
finding is that estimated token counts are worthless. T02 fixes it by
reading measured values from cb-cost.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:49:57 +02:00
|
|
|
"tool_mix": {
|
|
|
|
|
"turns": dict(mix_turns),
|
|
|
|
|
"cost": dict(mix_cost),
|
|
|
|
|
"mechanical_turns": len(mech),
|
|
|
|
|
"mechanical_cost": sum(r["cost"] or 0 for r in mech),
|
|
|
|
|
},
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
"slug": slug,
|
|
|
|
|
"pin": pin,
|
CB-WP-0004 T05: control loop — 6 points recovered, not 25-30
cb-cost gains --since, so the baseline (--pin 578dcbe, 662 responses,
$135.60) and this pass (--since 578dcbe, 83 responses, $7.82) are two
disjoint windows over the same transcripts. Every verdict is on share of
pass, since the windows differ 17x in size.
Test 1 — did mechanical turns disappear? Partly. Mechanical share fell
38.2% -> 32.4%. Six points against a predicted 25-30; reported unmet, and
no target was moved in this commit.
environment setup 11.5% -> 0.6% (85 turns -> 1) met, decisively
hub + workplan 8.5% -> 0.0% (46 turns -> 0) met
text patching 10.4% -> 9.6% not met
orientation 5.1% -> 21.2% not met, worse
The confound is stated before any defence of the numbers: this is the
pass that built the tools, and the two categories that missed are exactly
the two whose tools were under construction. The clean test is the next
pass, and it is carried forward rather than waived.
Test 2 — did the work relocate? Not into prose. Output tokens per
response fell 896 -> 681. Output's rising share of cost is a shrinking
denominator, not more writing. Cost per response halved and the evidence
refuses to claim it: that is compaction (mean context 232,982 ->
117,822), and attributing it to tooling would repeat CB-WP-0002's
original error in a new direction.
Test 3 — did quality hold? Yes, recorded as explicit judgment. make all
green with two gates that did not exist before, and four findings
surfaced this pass, three caught by controls written this pass — one of
them a 5.2x attribution error that would have reached the hub as a
measured number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:27:09 +02:00
|
|
|
"since": since,
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
"responses": len(responses),
|
|
|
|
|
"transcripts": len(paths),
|
|
|
|
|
"total": total,
|
|
|
|
|
"subagent_total": sub,
|
|
|
|
|
"main_total": total - sub,
|
|
|
|
|
"by_task": dict(by_task),
|
CB-WP-0004 T02: make task-done — close a task on measured numbers
Replaces the three hand-done steps of a task close (46 turns, $11.52 per
CB-RES-0003): the heredoc flipping status in the workplan file, the
hand-written hub call, and the hand-typed token counts.
The third is the reason this task exists. Every update_task_status this
repo produced carried estimated tokens_in/tokens_out — in a project whose
central finding is that estimated token counts are worthless. task-done
reads the measured figure from the transcripts, or refuses; there is no
path through it that emits an estimate.
cb-cost gains by_task_detail: cost, response count, model histogram and
token components per task. task-done imports cb-cost rather than parsing
its printed table, so the hub figure is not a copy that can drift from
its source.
The positive control found a real defect before the tool ran once.
Attribution keyed on a bare T\d\d from the commit subject, so CB-WP-0002
T01, CB-WP-0003 T01 and CB-WP-0004 T01 shared a bucket: the self-test
reported $12.10 for "T01" where the qualified figure is $2.33. That 5.2x
overstatement would have been pushed to the hub as a *measured* number —
the same fiction in a new form. task_label() now keys qualified subjects
on the full id and leaves unqualified ones bare rather than
retro-assigning them to a workplan. The pinned $93.15 benchmark is
unchanged, so historical attribution was not disturbed.
Fourth instance of trusted arithmetic: a number believed because a
program produced it rather than a hand.
Refusals, all exercised by --self-test: unknown id, typo'd id,
already-done task, missing state_hub_task_id, no measured spend, and a
status flip that produced no change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:17:51 +02:00
|
|
|
"by_task_detail": {
|
|
|
|
|
k: {
|
|
|
|
|
"cost": v["cost"],
|
|
|
|
|
"responses": v["responses"],
|
|
|
|
|
# The model that ran the most responses for this task. The
|
|
|
|
|
# hub schema has one model field per event; a task split
|
|
|
|
|
# across models cannot be represented faithfully, so the
|
|
|
|
|
# full count travels in the note.
|
|
|
|
|
"model": v["models"].most_common(1)[0][0],
|
|
|
|
|
"models": dict(v["models"]),
|
|
|
|
|
"toks": dict(v["toks"]),
|
|
|
|
|
# CA-03/CA-04: the hub has no cache fields. tokens_in is
|
|
|
|
|
# the honest sum of everything billed on the input side.
|
|
|
|
|
"tokens_in": (v["toks"].get("input", 0)
|
|
|
|
|
+ v["toks"].get("cache_read", 0)
|
|
|
|
|
+ v["toks"].get("write_5m", 0)
|
|
|
|
|
+ v["toks"].get("write_1h", 0)),
|
|
|
|
|
"tokens_out": v["toks"].get("output", 0),
|
|
|
|
|
}
|
|
|
|
|
for k, v in task_detail.items()
|
|
|
|
|
},
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
"by_model": dict(by_model),
|
|
|
|
|
"tokens": dict(by_component),
|
|
|
|
|
"cost_by_component": dict(by_component_cost),
|
|
|
|
|
"unpriced": [
|
|
|
|
|
{"model": r["model"], "tokens": r["toks"]} for r in unpriced
|
|
|
|
|
],
|
|
|
|
|
"reconciled": True,
|
|
|
|
|
"residual": residual,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def render(rep, by_task=False, composition=False):
|
|
|
|
|
print(f"M-D2-CST cost report — {rep['slug']}")
|
|
|
|
|
print(f" pin {rep['pin'] or '(none — live file, not reproducible)'}")
|
CB-WP-0004 T05: control loop — 6 points recovered, not 25-30
cb-cost gains --since, so the baseline (--pin 578dcbe, 662 responses,
$135.60) and this pass (--since 578dcbe, 83 responses, $7.82) are two
disjoint windows over the same transcripts. Every verdict is on share of
pass, since the windows differ 17x in size.
Test 1 — did mechanical turns disappear? Partly. Mechanical share fell
38.2% -> 32.4%. Six points against a predicted 25-30; reported unmet, and
no target was moved in this commit.
environment setup 11.5% -> 0.6% (85 turns -> 1) met, decisively
hub + workplan 8.5% -> 0.0% (46 turns -> 0) met
text patching 10.4% -> 9.6% not met
orientation 5.1% -> 21.2% not met, worse
The confound is stated before any defence of the numbers: this is the
pass that built the tools, and the two categories that missed are exactly
the two whose tools were under construction. The clean test is the next
pass, and it is carried forward rather than waived.
Test 2 — did the work relocate? Not into prose. Output tokens per
response fell 896 -> 681. Output's rising share of cost is a shrinking
denominator, not more writing. Cost per response halved and the evidence
refuses to claim it: that is compaction (mean context 232,982 ->
117,822), and attributing it to tooling would repeat CB-WP-0002's
original error in a new direction.
Test 3 — did quality hold? Yes, recorded as explicit judgment. make all
green with two gates that did not exist before, and four findings
surfaced this pass, three caught by controls written this pass — one of
them a 5.2x attribution error that would have reached the hub as a
measured number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:27:09 +02:00
|
|
|
if rep.get("since"):
|
|
|
|
|
print(f" since {rep['since']} (window is exclusive of this instant)")
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
print(f" transcripts {rep['transcripts']} responses {rep['responses']}")
|
|
|
|
|
print(f" main ${rep['main_total']:>10,.2f}")
|
|
|
|
|
print(f" subagent tree ${rep['subagent_total']:>10,.2f}")
|
|
|
|
|
print(f" TOTAL ${rep['total']:>10,.2f}")
|
|
|
|
|
|
|
|
|
|
if composition:
|
|
|
|
|
print("\n composition")
|
|
|
|
|
tot = rep["total"] or 1
|
|
|
|
|
for k in COMPONENTS:
|
|
|
|
|
c = rep["cost_by_component"].get(k, 0.0)
|
|
|
|
|
print(
|
|
|
|
|
f" {k:<12}{rep['tokens'].get(k,0):>14,} tok "
|
|
|
|
|
f"${c:>9,.2f} {100*c/tot:>5.1f}%"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if by_task:
|
|
|
|
|
print("\n by task")
|
|
|
|
|
rows = sorted(rep["by_task"].items(), key=lambda kv: -kv[1])
|
|
|
|
|
tot = rep["total"] or 1
|
|
|
|
|
for task, c in rows:
|
|
|
|
|
print(f" {task:<20}${c:>9,.2f} {100*c/tot:>5.1f}%")
|
|
|
|
|
# CA-10: the limit is reported with the number, every time.
|
|
|
|
|
un = rep["by_task"].get(UNATTRIBUTED, 0.0)
|
|
|
|
|
print(
|
|
|
|
|
f"\n NOTE: {100*un/tot:.0f}% of cost is UNATTRIBUTED — commits whose "
|
|
|
|
|
f"subject carries no T## tag.\n"
|
|
|
|
|
f" A per-task table is a view over {100*(1-un/tot):.0f}% of spend."
|
|
|
|
|
)
|
|
|
|
|
|
CB-RES-0003 + CB-WP-0004: 38% of pass cost is mechanical turns
Review of where token-priced turns did work a deterministic tool could
do. Method: classify every turn in both transcripts by the tool calls it
made. The classifier is committed in tools/cb-cost.py and emitted by
`make cost-mix`, so the baseline is reproducible and the same command
can later falsify the predictions.
mech environment setup 84 turns $15.33
mech ad-hoc text patching 75 turns $13.86
git 37 turns $13.85
mech hub task status 25 turns $ 7.46
mech orientation / inspect 49 turns $ 6.87
hub other 32 turns $ 6.22
mech ad-hoc transcript 39 turns $ 4.56
mech workplan status edit 21 turns $ 4.06
MECHANICAL (dedup) 290 turns $51.26 = 38% of pass
Largest category is `cd` and `export PATH` -- pure friction, and
dep-weight.py already patched it at the leaf, which is evidence it was
noticed and fixed in the wrong place. Second is heredocs string-patching
markdown, which is also the mechanism behind duplicated-fact drift, the
error class InnerLoop v1.2 names and cannot gate.
Explicitly NOT automated: git (37 turns, $13.85) is mostly commit
message authorship -- the highest-output turns in the corpus and the
project's reasoning record. Automating it would save money and destroy
what makes corrections cheap.
CB-WP-0004 implements five candidates and predicts $33-41 recovery
(25-30%), below the 38% measured share on purpose: some inspection and
patching is genuinely exploratory.
The control loop is the deliverable, not a formality. T05 tests three
things and must report all: did mechanical turns disappear, did they
RELOCATE into prose, and did quality hold. If mechanical turns fall and
prose rises by as much, the saving is zero and that is the result to
publish.
Also a self-indictment worth recording: every hub update_task_status in
this project carried hand-typed token estimates, in a repo whose central
finding is that estimated token counts are worthless. T02 fixes it by
reading measured values from cb-cost.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:49:57 +02:00
|
|
|
mix = rep["tool_mix"]
|
|
|
|
|
if mix["turns"]:
|
|
|
|
|
print("\n tool mix — a turn's cost is charged to every category it")
|
|
|
|
|
print(" touched, so columns overlap and must not be summed")
|
|
|
|
|
for k in sorted(mix["turns"], key=lambda x: -mix["cost"][x]):
|
|
|
|
|
tag = "mech" if k in MECHANICAL else " "
|
|
|
|
|
print(f" {tag} {k:<28}{mix['turns'][k]:>5} turns "
|
|
|
|
|
f"${mix['cost'][k]:>8,.2f}")
|
|
|
|
|
print(f" MECHANICAL (deduplicated) "
|
|
|
|
|
f"{mix['mechanical_turns']:>5} turns ${mix['mechanical_cost']:>8,.2f}"
|
|
|
|
|
f" = {100*mix['mechanical_cost']/(rep['total'] or 1):.0f}% of pass")
|
|
|
|
|
|
T04: specs/SessionShape.md — compaction is the lever, not session length
The task's original premise was wrong and is recorded rather than
deleted. It was written to prescribe one task per session; measurement
says the variable is context, not turn count.
Measured:
- compaction cut context 27x (542,991 -> 19,974) and cost/turn 3.1x;
the 202 turns after C1 cost less than half the 136 before it
- a turn costs $0.010 at 20k context and $0.270 at 540k
- break-even for a compaction is 2-11 turns, so: compact whenever
context exceeds ~300k and work remains
- a fresh session is NOT free -- cold start floors at ~51k and must
then re-read the artifacts a compact summary already holds (~66k).
Prefer compaction to continue work; prefer a fresh session when the
task changes, because then prior context is pure overhead.
cb-cost now emits SH-1..SH-3 so the targets come from the instrument
rather than from analysis, per InnerLoop v1.1. All three are UNMET
(mean context 232,982 vs 200,000; p90 492,042 vs 300,000; batching 7.8%
vs 20%) and are reported unmet rather than retargeted -- retargeting in
the commit that first measures is precisely what T07 exists to prevent.
Eighth error instance found while writing this: CB-WP-0001's claim that
"0 of 330 tool calls were batched" is wrong. 330 was the count of
single-call responses, not the total; 31 responses batched, covering 76
calls. It was carried into this workplan unverified. Trusted-arithmetic
class -- the one the T01 audit flagged as having no executable defence,
confirming that finding within hours of making it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:19:32 +02:00
|
|
|
sh = rep["session_shape"]
|
|
|
|
|
print("\n session shape (specs/SessionShape.md)")
|
|
|
|
|
print(f" SH-1 mean context {sh['SH-1_mean_context']:>12,.0f} tok "
|
|
|
|
|
f"[{'ok ' if sh['SH-1_mean_context']<=200_000 else 'FAIL'} target 200,000]")
|
|
|
|
|
print(f" SH-2 p90 context {sh['SH-2_p90_context']:>12,.0f} tok "
|
|
|
|
|
f"[{'ok ' if sh['SH-2_p90_context']<=300_000 else 'FAIL'} target 300,000]")
|
|
|
|
|
print(f" SH-3 batching rate {100*sh['SH-3_batching_rate']:>11.1f}% "
|
|
|
|
|
f"[{'ok ' if sh['SH-3_batching_rate']>=0.20 else 'FAIL'} target 20.0%]")
|
|
|
|
|
print(f" {sh['tool_calls']} tool calls in {sh['responses_with_tools']} responses; "
|
|
|
|
|
f"{sh['calls_in_batched_turns']} in batched turns")
|
|
|
|
|
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
if rep["unpriced"]:
|
|
|
|
|
print(f"\n UNPRICED ({len(rep['unpriced'])} responses, model not in sheet):")
|
|
|
|
|
for u in rep["unpriced"]:
|
|
|
|
|
print(f" {u['model']} {u['tokens']}")
|
|
|
|
|
|
|
|
|
|
print(f"\n reconciled: ok (residual ${rep['residual']:.6f})")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -------------------------------------------------------------- self-test
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def self_test():
|
|
|
|
|
"""AC-5..AC-8. Each asserts a failure mode is actually detected."""
|
|
|
|
|
prices = load_prices()
|
|
|
|
|
checks = []
|
|
|
|
|
|
|
|
|
|
def check(name, ok, detail=""):
|
|
|
|
|
checks.append((name, ok, detail))
|
|
|
|
|
|
|
|
|
|
# AC-8: per-TTL cache pricing. 5m must not be priced at the 1h rate.
|
|
|
|
|
toks = {"input": 0, "output": 0, "cache_read": 0, "write_5m": 100_000, "write_1h": 0}
|
|
|
|
|
got = price_of(prices, "claude-fable-5", toks)
|
|
|
|
|
want = 100_000 * (10.0 / 1e6) * 1.25
|
|
|
|
|
wrong = 100_000 * (10.0 / 1e6) * 2.0
|
|
|
|
|
check("AC-8 5m cache priced at write_5m", abs(got - want) < 1e-9 and got != wrong,
|
|
|
|
|
f"${got:.4f} (1h would be ${wrong:.4f})")
|
|
|
|
|
|
|
|
|
|
# AC-5: dedup invariant is enforced.
|
|
|
|
|
import tempfile
|
|
|
|
|
with tempfile.NamedTemporaryFile("w", suffix=".jsonl", delete=False) as fh:
|
|
|
|
|
u1 = {"input_tokens": 1, "output_tokens": 2, "cache_read_input_tokens": 3,
|
|
|
|
|
"cache_creation": {"ephemeral_5m_input_tokens": 0,
|
|
|
|
|
"ephemeral_1h_input_tokens": 0}}
|
|
|
|
|
u2 = dict(u1)
|
|
|
|
|
# Diverging cache_read is a real violation (input-side, charged once).
|
|
|
|
|
u2["cache_read_input_tokens"] = 999
|
|
|
|
|
for u in (u1, u2):
|
|
|
|
|
fh.write(json.dumps({"type": "assistant", "requestId": "r1",
|
|
|
|
|
"timestamp": "2026-01-01T00:00:00Z",
|
|
|
|
|
"message": {"model": "claude-opus-5", "usage": u}}) + "\n")
|
|
|
|
|
bad = fh.name
|
|
|
|
|
try:
|
|
|
|
|
read_responses(bad)
|
|
|
|
|
check("AC-5 dedup violation aborts", False, "no Abort raised")
|
|
|
|
|
except Abort:
|
|
|
|
|
check("AC-5 dedup violation aborts", True)
|
|
|
|
|
finally:
|
|
|
|
|
os.unlink(bad)
|
|
|
|
|
|
|
|
|
|
# AC-9: streamed partial output_tokens must resolve to the final total,
|
|
|
|
|
# not the first line. Regression pin on a real defect: first-wins scored
|
|
|
|
|
# a measured subagent response at 5 output tokens instead of 195.
|
|
|
|
|
with tempfile.NamedTemporaryFile("w", suffix=".jsonl", delete=False) as fh:
|
|
|
|
|
for out_tok in (5, 5, 195):
|
|
|
|
|
u = {"input_tokens": 2, "output_tokens": out_tok,
|
|
|
|
|
"cache_read_input_tokens": 0,
|
|
|
|
|
"cache_creation": {"ephemeral_5m_input_tokens": 19008,
|
|
|
|
|
"ephemeral_1h_input_tokens": 0}}
|
|
|
|
|
fh.write(json.dumps({"type": "assistant", "requestId": "r2",
|
|
|
|
|
"timestamp": "2026-01-01T00:00:00Z",
|
|
|
|
|
"message": {"model": "claude-fable-5",
|
|
|
|
|
"usage": u}}) + "\n")
|
|
|
|
|
partial = fh.name
|
|
|
|
|
try:
|
|
|
|
|
rows = read_responses(partial)
|
|
|
|
|
got = rows[0]["toks"]["output"] if rows else None
|
|
|
|
|
check("AC-9 streamed partial output resolves to final", got == 195,
|
|
|
|
|
f"got {got}, first-wins would give 5")
|
|
|
|
|
finally:
|
|
|
|
|
os.unlink(partial)
|
|
|
|
|
|
CB-WP-0004 T02: make task-done — close a task on measured numbers
Replaces the three hand-done steps of a task close (46 turns, $11.52 per
CB-RES-0003): the heredoc flipping status in the workplan file, the
hand-written hub call, and the hand-typed token counts.
The third is the reason this task exists. Every update_task_status this
repo produced carried estimated tokens_in/tokens_out — in a project whose
central finding is that estimated token counts are worthless. task-done
reads the measured figure from the transcripts, or refuses; there is no
path through it that emits an estimate.
cb-cost gains by_task_detail: cost, response count, model histogram and
token components per task. task-done imports cb-cost rather than parsing
its printed table, so the hub figure is not a copy that can drift from
its source.
The positive control found a real defect before the tool ran once.
Attribution keyed on a bare T\d\d from the commit subject, so CB-WP-0002
T01, CB-WP-0003 T01 and CB-WP-0004 T01 shared a bucket: the self-test
reported $12.10 for "T01" where the qualified figure is $2.33. That 5.2x
overstatement would have been pushed to the hub as a *measured* number —
the same fiction in a new form. task_label() now keys qualified subjects
on the full id and leaves unqualified ones bare rather than
retro-assigning them to a workplan. The pinned $93.15 benchmark is
unchanged, so historical attribution was not disturbed.
Fourth instance of trusted arithmetic: a number believed because a
program produced it rather than a hand.
Refusals, all exercised by --self-test: unknown id, typo'd id,
already-done task, missing state_hub_task_id, no measured spend, and a
status flip that produced no change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:17:51 +02:00
|
|
|
# CA-08 label collision (found by T02's positive control): three
|
|
|
|
|
# workplans each had a T01, and a bare-label bucket summed all three
|
|
|
|
|
# into one figure — $12.10 for a task that cost $2.33. Qualified
|
|
|
|
|
# subjects must produce distinct buckets.
|
|
|
|
|
check("CA-08 qualified task ids do not collide across workplans",
|
|
|
|
|
task_label("CB-WP-0004 T01: fix env") == "CB-WP-0004-T01"
|
|
|
|
|
and task_label("CB-WP-0002 T01: survey") == "CB-WP-0002-T01"
|
|
|
|
|
and task_label("CB-WP-0004-T01: hyphenated") == "CB-WP-0004-T01")
|
|
|
|
|
check("CA-08 unqualified subjects stay bare, not retro-assigned",
|
|
|
|
|
task_label("T01: cost-accounting survey") == "T01"
|
|
|
|
|
and task_label("chore: no task here") is None)
|
|
|
|
|
|
T11: dated rates and staleness become data, ahead of the 2026-08-31 flip
The price sheet had two defects of one shape -- a schema that could not
hold the fact it needed, the same criticism the cost survey levelled at
the State Hub.
CA-16 time-boxed rates are DATA. Sonnet's intro price lived in a
`# intro ...` comment and was invisible to the collector that
reads the file. Now promo_input/promo_output/promo_until,
applied per response at its own timestamp.
CA-17 the 90-day staleness rule was prose in MetricsAndScenarios 1a
that every M-D2-CST verdict silently inherited. Now `recorded`
+ `max_age_days` in the sheet, and a stale sheet ABORTS.
Both are exercised by make cost-test: the promo rate must apply before
2026-08-31 and lapse after, and a 102-day-old sheet must trip.
Applying CA-16 moved AC-1 from $93.32 to $93.15 -- the $0.17 CB-EV-0002
predicted, now collected rather than noted. That is a legitimate
retarget under T07's distinction: the instrument disproved the target,
and its output is in this commit. The number has now been stated five
times ($248.46, $92.21, $92.87, $93.32, $93.15), each correction from a
different mechanism.
Evidence tables regenerated from the tool rather than hand-patched,
per CA-15 -- which is the rule that exists because hand-typed tables
were the only thing the adversarial review found wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:23:29 +02:00
|
|
|
# CA-16: a dated promo rate must apply before its expiry and lapse after.
|
|
|
|
|
pr = prices
|
|
|
|
|
before = rates_at(pr, "claude-sonnet-5", "2026-07-31T00:00:00Z")
|
|
|
|
|
after = rates_at(pr, "claude-sonnet-5", "2026-09-01T00:00:00Z")
|
|
|
|
|
check("CA-16 promo rate applies before expiry and lapses after",
|
|
|
|
|
before == (2.0, 10.0) and after == (3.0, 15.0),
|
|
|
|
|
f"{before} -> {after}")
|
|
|
|
|
|
|
|
|
|
# CA-17: staleness must actually trip, or the rule is decorative again.
|
|
|
|
|
import datetime as _dt
|
|
|
|
|
fresh = check_price_sheet_age(pr, _dt.date(2026, 8, 1))
|
|
|
|
|
stale = check_price_sheet_age(pr, _dt.date(2026, 11, 10))
|
|
|
|
|
check("CA-17 staleness detected past max_age_days",
|
|
|
|
|
fresh is None and stale is not None, "fresh ok, 102d trips")
|
|
|
|
|
|
T05: live cost budget replaces the dead token budget
The 8k/10k per-task token budget was never referenced or enforced and
T08 blew past it silently. Replaced with a budget that can actually
fire.
The design constraint is the interesting part: per-task cost needs the
commit that CLOSES the task, so a per-task budget is unavoidably
retrospective -- it can only report a breach after the money is spent,
which is the dead-policy failure again. What IS observable mid-task is
spend since the last commit, because the transcript is append-live. So
the budget binds on the open remainder.
CB-01 budget = USD since the last commit, via `make cost-budget`
CB-02 soft $10.00 (state progress, decide), hard $22.00 (stop)
Calibrated on the 32 non-empty commit intervals of CB-WP-0001: p50
$1.40, p90 $9.36, max $10.80. Soft sits just below the observed maximum
-- it would have fired exactly once on the calibration pass. Hard is ~2x
the observed max, a value never reached in 32 intervals, so reaching it
means the session is doing something the data has no example of.
Both thresholds are set ABOVE every observed value, so they bind on
future work rather than ratifying present work -- the distinction T07
is about.
Stated limit: it is a command, not a daemon. An agent that never runs it
gets no signal, which is the dead-policy failure one level up. Mitigated
only by being free to run and on the one command surface.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:21:30 +02:00
|
|
|
# CB-02: thresholds must be ordered, or the budget silently never fires.
|
|
|
|
|
ap_defaults = {"soft": 10.00, "hard": 22.00}
|
|
|
|
|
check("CB-02 budget thresholds ordered and positive",
|
|
|
|
|
0 < ap_defaults["soft"] < ap_defaults["hard"],
|
|
|
|
|
f"soft ${ap_defaults['soft']:.2f} < hard ${ap_defaults['hard']:.2f}")
|
|
|
|
|
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
# AC-6: zero responses must not report $0.00 as an answer.
|
|
|
|
|
with tempfile.NamedTemporaryFile("w", suffix=".jsonl", delete=False) as fh:
|
|
|
|
|
fh.write(json.dumps({"type": "user", "message": {}}) + "\n")
|
|
|
|
|
empty = fh.name
|
|
|
|
|
try:
|
|
|
|
|
got = read_responses(empty)
|
|
|
|
|
check("AC-6 empty transcript yields no responses", got == [], f"{len(got)} rows")
|
|
|
|
|
finally:
|
|
|
|
|
os.unlink(empty)
|
|
|
|
|
|
|
|
|
|
# AC-7: the subagent tree is discovered by the path globs.
|
|
|
|
|
slug = "-home-worsch-clay-borg"
|
|
|
|
|
paths = transcript_paths(slug)
|
|
|
|
|
subs = [p for p in paths if "/subagents/" in p]
|
|
|
|
|
check("AC-7 subagent tree enumerated", len(subs) > 0,
|
|
|
|
|
f"{len(subs)} subagent transcript(s) of {len(paths)} total")
|
|
|
|
|
|
|
|
|
|
print("cb-cost self-test (positive control)")
|
|
|
|
|
ok = True
|
|
|
|
|
for name, passed, detail in checks:
|
|
|
|
|
print(f" [{'ok ' if passed else 'FAIL'}] {name}" + (f" — {detail}" if detail else ""))
|
|
|
|
|
ok &= passed
|
|
|
|
|
return 0 if ok else 1
|
|
|
|
|
|
|
|
|
|
|
T05: live cost budget replaces the dead token budget
The 8k/10k per-task token budget was never referenced or enforced and
T08 blew past it silently. Replaced with a budget that can actually
fire.
The design constraint is the interesting part: per-task cost needs the
commit that CLOSES the task, so a per-task budget is unavoidably
retrospective -- it can only report a breach after the money is spent,
which is the dead-policy failure again. What IS observable mid-task is
spend since the last commit, because the transcript is append-live. So
the budget binds on the open remainder.
CB-01 budget = USD since the last commit, via `make cost-budget`
CB-02 soft $10.00 (state progress, decide), hard $22.00 (stop)
Calibrated on the 32 non-empty commit intervals of CB-WP-0001: p50
$1.40, p90 $9.36, max $10.80. Soft sits just below the observed maximum
-- it would have fired exactly once on the calibration pass. Hard is ~2x
the observed max, a value never reached in 32 intervals, so reaching it
means the session is doing something the data has no example of.
Both thresholds are set ABOVE every observed value, so they bind on
future work rather than ratifying present work -- the distinction T07
is about.
Stated limit: it is a command, not a daemon. An agent that never runs it
gets no signal, which is the dead-policy failure one level up. Mitigated
only by being free to run and on the one command surface.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:21:30 +02:00
|
|
|
def budget(slug, soft, hard):
|
|
|
|
|
"""Live cost budget (specs/CostAccounting.md §7).
|
|
|
|
|
|
|
|
|
|
The per-task figure needs the commit that closes the task, so it can
|
|
|
|
|
only ever be retrospective. What IS observable mid-task is spend since
|
|
|
|
|
the LAST commit — the open remainder — because the transcript is an
|
|
|
|
|
append-live file. That is the number a budget can actually fire on.
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
rep = collect(slug, None)
|
|
|
|
|
except Abort as e:
|
|
|
|
|
print(f"ABORT — {e}", file=sys.stderr)
|
|
|
|
|
return 1
|
|
|
|
|
open_spend = rep["by_task"].get(OPEN_REMAINDER, 0.0)
|
|
|
|
|
head = subprocess.run(
|
|
|
|
|
["git", "-C", REPO, "log", "-1", "--format=%h %s"],
|
|
|
|
|
capture_output=True, text=True, check=True).stdout.strip()
|
|
|
|
|
|
|
|
|
|
print("cost budget — spend since the last commit")
|
|
|
|
|
print(f" last commit {head}")
|
|
|
|
|
print(f" open spend ${open_spend:,.2f}")
|
|
|
|
|
print(f" soft / hard ${soft:,.2f} / ${hard:,.2f}")
|
|
|
|
|
if open_spend > hard:
|
|
|
|
|
print(f"\n HARD BREACH — ${open_spend:,.2f} > ${hard:,.2f}. Commit what "
|
|
|
|
|
f"works, or stop and decompose. Uncommitted work is also "
|
|
|
|
|
f"unattributable.", file=sys.stderr)
|
|
|
|
|
return 1
|
|
|
|
|
if open_spend > soft:
|
|
|
|
|
print(f"\n soft breach — ${open_spend:,.2f} > ${soft:,.2f}. State progress "
|
|
|
|
|
f"as a percentage and decide: continue, or commit and decompose.")
|
|
|
|
|
return 0
|
|
|
|
|
print("\n within budget")
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
def main():
|
|
|
|
|
ap = argparse.ArgumentParser()
|
|
|
|
|
ap.add_argument("--slug", default="-home-worsch-clay-borg")
|
|
|
|
|
ap.add_argument("--pin", help="commit-ish or ISO Z instant (CA-07)")
|
CB-WP-0004 T05: control loop — 6 points recovered, not 25-30
cb-cost gains --since, so the baseline (--pin 578dcbe, 662 responses,
$135.60) and this pass (--since 578dcbe, 83 responses, $7.82) are two
disjoint windows over the same transcripts. Every verdict is on share of
pass, since the windows differ 17x in size.
Test 1 — did mechanical turns disappear? Partly. Mechanical share fell
38.2% -> 32.4%. Six points against a predicted 25-30; reported unmet, and
no target was moved in this commit.
environment setup 11.5% -> 0.6% (85 turns -> 1) met, decisively
hub + workplan 8.5% -> 0.0% (46 turns -> 0) met
text patching 10.4% -> 9.6% not met
orientation 5.1% -> 21.2% not met, worse
The confound is stated before any defence of the numbers: this is the
pass that built the tools, and the two categories that missed are exactly
the two whose tools were under construction. The clean test is the next
pass, and it is carried forward rather than waived.
Test 2 — did the work relocate? Not into prose. Output tokens per
response fell 896 -> 681. Output's rising share of cost is a shrinking
denominator, not more writing. Cost per response halved and the evidence
refuses to claim it: that is compaction (mean context 232,982 ->
117,822), and attributing it to tooling would repeat CB-WP-0002's
original error in a new direction.
Test 3 — did quality hold? Yes, recorded as explicit judgment. make all
green with two gates that did not exist before, and four findings
surfaced this pass, three caught by controls written this pass — one of
them a 5.2x attribution error that would have reached the hub as a
measured number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:27:09 +02:00
|
|
|
ap.add_argument("--since", help="window start, exclusive: commit-ish or ISO Z")
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
ap.add_argument("--by-task", action="store_true")
|
|
|
|
|
ap.add_argument("--composition", action="store_true")
|
T04: specs/SessionShape.md — compaction is the lever, not session length
The task's original premise was wrong and is recorded rather than
deleted. It was written to prescribe one task per session; measurement
says the variable is context, not turn count.
Measured:
- compaction cut context 27x (542,991 -> 19,974) and cost/turn 3.1x;
the 202 turns after C1 cost less than half the 136 before it
- a turn costs $0.010 at 20k context and $0.270 at 540k
- break-even for a compaction is 2-11 turns, so: compact whenever
context exceeds ~300k and work remains
- a fresh session is NOT free -- cold start floors at ~51k and must
then re-read the artifacts a compact summary already holds (~66k).
Prefer compaction to continue work; prefer a fresh session when the
task changes, because then prior context is pure overhead.
cb-cost now emits SH-1..SH-3 so the targets come from the instrument
rather than from analysis, per InnerLoop v1.1. All three are UNMET
(mean context 232,982 vs 200,000; p90 492,042 vs 300,000; batching 7.8%
vs 20%) and are reported unmet rather than retargeted -- retargeting in
the commit that first measures is precisely what T07 exists to prevent.
Eighth error instance found while writing this: CB-WP-0001's claim that
"0 of 330 tool calls were batched" is wrong. 330 was the count of
single-call responses, not the total; 31 responses batched, covering 76
calls. It was carried into this workplan unverified. Trusted-arithmetic
class -- the one the T01 audit flagged as having no executable defence,
confirming that finding within hours of making it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:19:32 +02:00
|
|
|
ap.add_argument("--session-shape", action="store_true",
|
|
|
|
|
help="SH-1..SH-3 (always shown in the default report)")
|
T05: live cost budget replaces the dead token budget
The 8k/10k per-task token budget was never referenced or enforced and
T08 blew past it silently. Replaced with a budget that can actually
fire.
The design constraint is the interesting part: per-task cost needs the
commit that CLOSES the task, so a per-task budget is unavoidably
retrospective -- it can only report a breach after the money is spent,
which is the dead-policy failure again. What IS observable mid-task is
spend since the last commit, because the transcript is append-live. So
the budget binds on the open remainder.
CB-01 budget = USD since the last commit, via `make cost-budget`
CB-02 soft $10.00 (state progress, decide), hard $22.00 (stop)
Calibrated on the 32 non-empty commit intervals of CB-WP-0001: p50
$1.40, p90 $9.36, max $10.80. Soft sits just below the observed maximum
-- it would have fired exactly once on the calibration pass. Hard is ~2x
the observed max, a value never reached in 32 intervals, so reaching it
means the session is doing something the data has no example of.
Both thresholds are set ABOVE every observed value, so they bind on
future work rather than ratifying present work -- the distinction T07
is about.
Stated limit: it is a command, not a daemon. An agent that never runs it
gets no signal, which is the dead-policy failure one level up. Mitigated
only by being free to run and on the one command surface.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:21:30 +02:00
|
|
|
ap.add_argument("--budget", action="store_true",
|
|
|
|
|
help="CB-01/CB-02: spend since the last commit, live")
|
|
|
|
|
ap.add_argument("--soft", type=float, default=10.00)
|
|
|
|
|
ap.add_argument("--hard", type=float, default=22.00)
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
ap.add_argument("--self-test", action="store_true")
|
|
|
|
|
ap.add_argument("--json", action="store_true")
|
|
|
|
|
args = ap.parse_args()
|
|
|
|
|
|
|
|
|
|
if args.self_test:
|
|
|
|
|
return self_test()
|
|
|
|
|
|
T05: live cost budget replaces the dead token budget
The 8k/10k per-task token budget was never referenced or enforced and
T08 blew past it silently. Replaced with a budget that can actually
fire.
The design constraint is the interesting part: per-task cost needs the
commit that CLOSES the task, so a per-task budget is unavoidably
retrospective -- it can only report a breach after the money is spent,
which is the dead-policy failure again. What IS observable mid-task is
spend since the last commit, because the transcript is append-live. So
the budget binds on the open remainder.
CB-01 budget = USD since the last commit, via `make cost-budget`
CB-02 soft $10.00 (state progress, decide), hard $22.00 (stop)
Calibrated on the 32 non-empty commit intervals of CB-WP-0001: p50
$1.40, p90 $9.36, max $10.80. Soft sits just below the observed maximum
-- it would have fired exactly once on the calibration pass. Hard is ~2x
the observed max, a value never reached in 32 intervals, so reaching it
means the session is doing something the data has no example of.
Both thresholds are set ABOVE every observed value, so they bind on
future work rather than ratifying present work -- the distinction T07
is about.
Stated limit: it is a command, not a daemon. An agent that never runs it
gets no signal, which is the dead-policy failure one level up. Mitigated
only by being free to run and on the one command surface.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:21:30 +02:00
|
|
|
if args.budget:
|
|
|
|
|
return budget(args.slug, args.soft, args.hard)
|
|
|
|
|
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
try:
|
CB-WP-0004 T05: control loop — 6 points recovered, not 25-30
cb-cost gains --since, so the baseline (--pin 578dcbe, 662 responses,
$135.60) and this pass (--since 578dcbe, 83 responses, $7.82) are two
disjoint windows over the same transcripts. Every verdict is on share of
pass, since the windows differ 17x in size.
Test 1 — did mechanical turns disappear? Partly. Mechanical share fell
38.2% -> 32.4%. Six points against a predicted 25-30; reported unmet, and
no target was moved in this commit.
environment setup 11.5% -> 0.6% (85 turns -> 1) met, decisively
hub + workplan 8.5% -> 0.0% (46 turns -> 0) met
text patching 10.4% -> 9.6% not met
orientation 5.1% -> 21.2% not met, worse
The confound is stated before any defence of the numbers: this is the
pass that built the tools, and the two categories that missed are exactly
the two whose tools were under construction. The clean test is the next
pass, and it is carried forward rather than waived.
Test 2 — did the work relocate? Not into prose. Output tokens per
response fell 896 -> 681. Output's rising share of cost is a shrinking
denominator, not more writing. Cost per response halved and the evidence
refuses to claim it: that is compaction (mean context 232,982 ->
117,822), and attributing it to tooling would repeat CB-WP-0002's
original error in a new direction.
Test 3 — did quality hold? Yes, recorded as explicit judgment. make all
green with two gates that did not exist before, and four findings
surfaced this pass, three caught by controls written this pass — one of
them a 5.2x attribution error that would have reached the hub as a
measured number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:27:09 +02:00
|
|
|
rep = collect(args.slug, args.pin, args.since)
|
T04: tools/cb-cost.py — and its positive control fires on first contact
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>
2026-07-31 08:50:04 +02:00
|
|
|
except Abort as e:
|
|
|
|
|
print(f"ABORT — {e}", file=sys.stderr)
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
if args.json:
|
|
|
|
|
print(json.dumps(rep, indent=2))
|
|
|
|
|
else:
|
|
|
|
|
render(rep, by_task=args.by_task, composition=args.composition)
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
sys.exit(main())
|