fix(eval): agent cost was the wrong model's price, and the budget was the wrong unit - #545
fix(eval): agent cost was the wrong model's price, and the budget was the wrong unit#545sunholo-voight-kampff wants to merge 2 commits into
Conversation
… the wrong unit Two defects found auditing OpenAI's 2026-07-30 GPT-5.6 price cut, plus the end-of-run tally that would have surfaced either one sooner. 1. PER-HARNESS PRICING. Executors that compute cost from token counts used their own hardcoded table, so the codex harness billed EVERY model it ran at gpt-5-codex's $1.25/$10 per 1M — gpt5-6-luna and gpt5-4-mini banked at identical rates despite really costing $0.20/$1.20 and $0.75/$4.50. Task.Pricing now carries the per-model rates and ResolveCostModel prefers them; a present-but-zero rate is honoured so free local models are not billed at cloud prices. The cost-KILL path was already per-model correct, which meant banked cost and kill threshold came from two different price tables: luna/graph_bfs banked $0.34259 at codex rates while the $0.30 budget that spared it saw $0.26980 at luna's. 2. COST PROVENANCE. cost_usd said nothing about whether anyone was charged. codex (auth_mode chatgpt) and claude (OAuth) run on subscriptions, so their dollars are list-price equivalents, not spend — yet they summed with metered OpenRouter/Vertex spend under one label, directly beneath a v1.0 KPI whose numerator is attributable METERED dollars. Each executor now classifies its own auth lane; banked as cost_provenance, stored on chain stages (schema v17), split out by ClassifyStageCost into a new `subscription` status that CostRollup keeps OUT of TotalKnownCost. Absent labels read as unknown, never metered — nothing is backfilled. 3. WORK GATES. A dollar ceiling buys tokens in inverse proportion to price, so nominally-uniform $0.30/$0.50 caps spanned 24x in real work: claude-sonnet-4-6, the longitudinal ANCHOR, got 0.14M tokens against deepseek-v4-flash's 3.40M — it had no budgets block at all and fell through to a formula that buys almost nothing at $3/$15. A suite asking "does the agent loop rescue weak models" gave the weakest the most rope. budgets.max_tokens_per_bench sets one shared 3.0M ceiling; subscription lanes gate on tokens alone and now sit identical. Task.MaxTokensPerBench had been plumbed since M-EVAL-OS-LONGITUDINAL but ONLY opencode honoured it; codex and claude now enforce it too. Metered opencode lanes keep their dollar ceiling as a real spend control with tokens as a second bound — still cost-limited, but for a stated reason. eval-suite now prints a cost tally at the end, split by provenance, so the metered figure is never conflated with subscription arithmetic again. Verified: build, go vet, make check-boundaries, and tests across executor, eval_harness, observatory, eval_analysis, coordinator, server and storage. Tally rendered against the real v0.30.0 baseline (2,090 rows, all correctly classified unknown — they predate the label). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The provenance and token-gate work pushed both executors past the 800-line check-file-sizes gate (codex 817, claude 843) — a CI failure, not a warning. Extracted into a cost.go per package: authLane() + CostModel() for both, plus getModel() for claude. These belong together — what a run costs and whether anyone was charged for it are the same question asked twice. codex 817 -> 761, claude 843 -> 778. No behaviour change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
Mission-loop triage (iteration 150). This PR is an orphan: it has zero mentions in the V1 mission charter and zero in the mission log (control: Its purpose is still live — I checked rather than assuming. The standing rule here is that an
Control: So the two defects it fixes are still present: agent cost banked from the executor's hardcoded table rather than per-model rates, and State: Not actioned this iteration — a triage sweep never outranks the queue head by itself, and this is a rebase-and-revalidate job, not a merge. Filed as a queue row in the charter so it stops being invisible to the loop. 🤖 Generated with Claude Code |
… scope decision after 2 blocked quorum rounds
Pick: the queue head, m-net-effect-proxy-boundary (D5 Option B). NEW-DOC + quorum.
Designer codex gpt-5.6-sol (rotation advanced from claude:claude-fable-5).
Outcome: 662-line design doc with 19 verification rows and a silent-revert check on
every acceptance criterion. Quorum BLOCKED twice; item parked needs-human-review on
one decision (D-6). Planner/executor/evaluator not fired.
R1 caught a genuine design defect (gemini-3-1-pro): target-IP resolution specified in
TWO places — the existing preflight resolveAndValidateIP and the new RoundTripper — a
TOCTOU DNS-rebinding race plus a broken-proxied-request risk on hosts without external
DNS. Routed without a controller-invented resolution; the designer made the direct
RoundTripper the sole resolve-validate-dial site and skips local target DNS entirely on
proxy routes.
R1's other objection was an unverified premise, so the controller measured it rather
than forwarding it: production has 0 custom RoundTrippers, 0 DefaultTransport uses,
0 Transport.Clone, no shared HTTP factory (control: 29 inline http.Client{} sites).
That pass also produced the fact the doc lacked — Go's DefaultTransport sets
Proxy: ProxyFromEnvironment (transport.go:46-48, go1.26.5) — so bare clients are
already inside the egress boundary and only hand-built nil-Proxy transports can
escape. That turns "we found seven sites" into "seven is all there can be".
R2's surviving objection asks that the completeness gate become a go/packages AST
analyzer. Not carve-out-eligible (expands scope, needs judgment), so Standing rule 2
applies: park, do not force through. The decision was made cheap to answer by testing
the reviewer's own hypothesis — all five constructions the analyzer would catch are
ZERO at HEAD, each with a firing control. Two of those five controls failed on first
run and were re-run before any number was used.
Also: corrected the stale sweep-batch row (#588 was already closed 2026-08-05; and M2
gated the live-network SUBTEST at net_test.go:364, not the whole TestNetHttpPost
function). Filed orphaned PR #545 as a queue row — found by iteration 149's new
died-mid-flight check on its first independent use; 125 commits behind, zero charter
and zero log mentions, and its purpose is NOT superseded (measured at HEAD).
Retro — one skill edit (>=2-instance bar, purely additive: 38 insertions, 0 deletions):
Gate 2 gains rule 3f — a reviewer's objection is a claim too, and on an
"unverified premise" objection the controller's job is to MEASURE it, because the
measurement can refute the objection outright or shrink the work. Instance 1:
iteration 126. Instance 2: this iteration.
Metered: $0.1785 of the $5 ceiling (quorum x2 only; codex is OAuth-subscription).
Co-Authored-By: codex <gpt-5.6-sol>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>


Found while confirming OpenAI's 2026-07-30 GPT-5.6 price cut. Three defects, one new tally.
1. Agent cost billed the wrong model
Executors that compute cost from tokens used their own hardcoded table, so the codex harness billed every model it ran at gpt-5-codex's
$1.25/$10per 1M.gpt5-6-lunaandgpt5-4-minibanked at identical rates despite really costing$0.20/$1.20and$0.75/$4.50.Task.Pricingnow carries per-model rates;ResolveCostModelprefers them and falls back to the executor's table. A present-but-zero rate is honoured, so free local models aren't billed at cloud prices.The cost-kill path was already per-model correct — which meant banked cost and kill threshold came from two different price tables:
cost_usd2.
cost_usdnever said whether anyone was chargedcodex(auth_mode: chatgpt) andclaude(OAuth) run on subscriptions, so their dollars are list-price equivalents — yet they summed with metered OpenRouter/Vertex spend under one label, directly beneath a v1.0 KPI whose numerator is attributable metered dollars.Each executor now classifies its own auth lane. Banked as
cost_provenance, stored on chain stages (schema v17), and split byClassifyStageCostinto asubscriptionstatus thatCostRollupkeeps out ofTotalKnownCost. Absent labels read as unknown, never metered — nothing is backfilled by guess.3. The budget was denominated in the wrong unit
A dollar ceiling buys tokens in inverse proportion to price, so nominally-uniform
$0.30/$0.50caps spanned 24× in real work:claude-sonnet-4-6— the longitudinal anchor everything else is scored against — was the most starved model in the suite. It had nobudgets:block at all and fell through to a formula that buys almost nothing at$3/$15. A suite asking "does the agent loop rescue weak models" was giving the weakest the most rope.budgets.max_tokens_per_benchsets one shared 3.0M ceiling. Subscription lanes gate on tokens alone and now sit identical; their dollar caps were raised only so the token gate binds — not a spend increase, those lanes aren't billed. Meteredopencodelanes keep their dollar ceiling as a real spend control with tokens as a second bound.Task.MaxTokensPerBenchhad been plumbed toexecutor.Tasksince M-EVAL-OS-LONGITUDINAL, but onlyopencodeever honoured it.codexandclaudenow enforce it too.4. End-of-run cost tally
eval-suitenow prints per-model cost then totals split by provenance.METERED (actually billed)is the only line that answers "what did this cost us".Verification
Build,
go vet,make check-boundaries, and tests across executor, eval_harness, observatory, eval_analysis, coordinator, server and storage. Tally rendered against the real v0.30.0 baseline — 2,090 rows, all correctly classifiedunknown(they predate the label).Reviewer notes
$10.80,$3.60) are free on this rig's OAuth but become real per-benchmark exposure underAILANG_AUTH_MODE=apikeyin cloud dispatch. Flagged inline at both sites.cost_usd; correcting them would mean fabricating numbers, so the annotation route was taken (same call asCAVEATS.md).🤖 Generated with Claude Code