Repository navigation
[Phase 2] Canary go/no-go report: compare deep-tier cost, cache-read cost and latency by model from token-usage artifacts #1951
Description
Story
As a release maintainer,
I want one command that turns pr-review token-usage artifacts into a pass/fail table for the epic's cost and latency metrics,
so that #1899's go/no-go is a mechanical read of evidence instead of hand-summing JSONL.
Why this blocks #1899
scripts/token_report.sh renders org-wide totals by workflow/tier/model. It does not compute per-invocation means, compare a candidate model with an incumbent, or apply #1899's bars (cost per invocation ≥ 20% lower, cache-read cost ≥ 50% lower, latency ≥ 20% better). There is also no guard against "PASS" on too few canary PRs.
Acceptance Criteria
-
New
scripts/canary_report.shwith a pure render function that reads a directory of token-usage JSONL files. Inputs:--candidateand--incumbent(both required, no model default; see AC7),--workflow(defaultpr-review) and--tier(defaultdeep). Output is a Markdown table per arm: invocation count, distinct PR count (fromcontext), mean and p50 USD per invocation, mean cache-read USD per invocation, mean and p50duration_ms, and the candidate-vs-incumbent delta for each metric. -
USD is priced with
price_for <model> <record-date>fromscripts/lib/model-pricing.sh, using the same source as token_report.sh. Records whose model has no price are counted and reported, never silently dropped. -
Each metric gets PASS / FAIL / INSUFFICIENT against its bar. INSUFFICIENT covers fewer than 5 distinct candidate PRs, fewer than 5 incumbent invocations, or no non-null
duration_mson either arm (latency). The overall verdict is PASS only if all three are PASS, and the exit codes are 0 = PASS, 1 = FAIL, 2 = INSUFFICIENT. Missing data must never read as PASS. -
main()downloadstoken-usage-*artifacts for this repo over a--since/--untilwindow. Reuse token_report.sh's collection helpers (source them or factor them intoscripts/lib/) rather than duplicating them. It can also read a local directory (--dir), so it can score themodel-ab-tokens-*artifacts from the model-ab runner story on identical inputs. -
A
workflow_dispatchentry (the existingtoken-report.ymlor a small new job) runs it and writes the table to$GITHUB_STEP_SUMMARY. -
tests/canary_report.batsexercises the pure function on fixture JSONL: pass, fail per metric, INSUFFICIENT (too few PRs, no durations) and unpriced-model accounting. Register it inlint.yml, and keepshellcheckclean. -
Generic across models and windows (revision, 2026-10-08). The script must compare ANY two models, not one canary. No model ID and no calendar date may appear as a default or hard-coded value in
scripts/canary_report.sh,.github/workflows/token-report.ymlor the test logic:--candidateand--incumbentare required. Omitting either exits with the operational-error code (3) and a usage message. Remove theclaude-opus-5-5/claude-opus-4-8defaults (script lines ~350-351 and ~695) and theirmodel-pin-okmarkers.- The baseline window has no fixed default. Take
--baseline-since/--baseline-untilwhen given; otherwise derive it as--baseline-days(default 14) ending at--since. Remove the2026-09-11/2026-09-25constants (script lines ~356-357 and ~698) and the dated examples in the header comments, replacing them with placeholders. - The thresholds are flags as well as env vars, with flag taking precedence:
--cost-bar,--cache-bar,--latency-bar,--min-prs,--min-invocations,--candidate-max-prs. Keep the current numbers as the defaults.
-
Workflow inputs.
token-report.ymlworkflow_dispatchexposescandidate_model,incumbent_model,canary_since,baseline_daysand the threshold inputs, and passes them through as arguments.run_canaryfails fast with a clear message when a required model input is empty. -
Header and docs describe the script as a model-vs-model comparison. Examples use
<candidate-model>/<incumbent-model>placeholders, and the baseline-expiry note states the 30-day retention without naming a specific model or date. -
Tests parametrized by model.
tests/canary_report.batstakes the model IDs from variables set once at the top (use obviously synthetic IDs such asmodel-newandmodel-old), not literals repeated through the file. Add cases for: missing--candidate/--incumbent(exit 3), a baseline window derived from--baseline-days, flags overriding env vars, and two non-Anthropic-looking model names scoring correctly. -
Latency when a baseline predates
duration_ms: the report keeps scoring latency INSUFFICIENT (never PASS), and its output says which arm lacks durations so the maintainer knows to use--model-ab-dir.
Dev Notes
- Per-invocation cost on real PRs depends on PR size. Also print a size-normalised figure (USD per 1M input-equivalent tokens) as context, but apply the bar to per-invocation cost as the epic states.
- Baseline expiry: token-usage artifacts are kept 30 days (
pr-review.ymlupload step). The last Opus 4.8 deep-tier records in this repo date from before the canary cut at 2026-09-25 14:07Z and expire by about 2026-10-25. Document in the script header how to snapshot a baseline directory (--dir) before then. - COST CAP: read-only over existing artifacts. It makes no model calls.
Likely target surface
scripts/canary_report.sh(new), possiblyscripts/lib/for shared artifact collectiontests/canary_report.bats(new).github/workflows/token-report.yml,.github/workflows/lint.yml
Split out of #1899 (hands-off release story) so its measurable prerequisites can be delivered by dev-lead. Epic #1895.
Activity
github-actions commentedon Sep 26, 2026
Thanks for filing this detailed story — the acceptance criteria and fixture-driven test plan make the scope very clear. One question before implementation begins: should
canary_report.shbe designed to run as a standalone CLI tool invoked manually by a release maintainer, or is the primary use case the GitHub Actionsworkflow_dispatchtrigger writing to$GITHUB_STEP_SUMMARY? Understanding the priority will help decide how much effort goes into human-readable terminal output versus CI-optimised formatting.don-petry commentedon Sep 26, 2026
Maintainer decision: sample selection (please build these in as the defaults)
pr-review/v1-nextwhose run emitted atier=deeprecord withmodel=claude-opus-5-5. Minimum 5, stop at 10 (the epic cost cap). Take the first 10 in time order after the canary window start, not a hand-picked subset. Deep-tier calls that fell back to another model (for exampleclaude-sonnet-5-0) are listed separately and don't count toward either arm.tier=deep,model=claude-opus-4-8records from pr-review runs in the 14 days before the canary cut (2026-09-11T14:07Z → 2026-09-25T14:07Z). Expose this as--baseline-since/--baseline-untilwith those defaults documented in the script header.--sinceflag. The maintainer sets it to the timepr-review/v1-nextis re-cut withduration_ms([Phase 2] Record per-call engine latency (duration_ms) in token-usage records so the deep-tier latency metric is measurable #1949). Latency is only scored on records from that window onward.model-ab-tokens-*artifact directory ([Phase 2] Add a workflow_dispatch runner for model-ab.sh plus a liveness/effort probe, so the Opus 5.5 non-regression A/B actually executes #1950), score both arms from that directory, where both ran on identical inputs. Report it as a separate section beside the real-PR section. Don't merge the two.Generated by Claude Code
don-petry commentedon Sep 26, 2026
Dev-Lead Implementation Plan
Issue: #1951 — [Phase 2] Canary go/no-go report: compare deep-tier cost, cache-read cost and latency by model from token-usage artifacts
Scope
Add
scripts/canary_report.sh— a read-only tool with a pure render function that turns a directory of token-usage JSONL into a per-arm (candidate vs incumbent) PASS/FAIL/INSUFFICIENT go/no-go table for the epic's cost, cache-read and latency bars, plus amain()that downloads this repo'stoken-usage-*artifacts over a window (reusing token_report.sh's collection helpers) or reads a local--dir.Implementation Checklist
scripts/canary_report.sh(new): sourcetoken_report.shto reuseprice_for,_fmt_usd,_gh_timeout,_extract_zip,_collect_one_artifact. Pure funcs:canary_annotate(price each record viaprice_for <model> <record-date>, count unpriced) andrender_canary_report(filter by workflow/tier, split arms by model, apply candidate cap + since/baseline windows, compute invocation/distinct-PR counts, mean+p50 USD, mean cache-read USD, mean+p50 duration_ms, deltas, size-normalised USD/1M input-equiv, PASS/FAIL/INSUFFICIENT per bar, overall verdict, exit 0/1/2).main(): flag parsing + windowed single-repo collection + optional--model-ab-dircontrolled section +$GITHUB_STEP_SUMMARY.--candidate claude-opus-5-5,--incumbent claude-opus-4-8,--workflow pr-review,--tier deep,--baseline-since 2026-09-11T14:07Z,--baseline-until 2026-09-25T14:07Z,--since(canary window start, latency scored from here), candidate cap first 10 distinct PRs, min 5 candidate PRs / 5 incumbent invocations. Header documents baseline expiry (~2026-10-25) and how to snapshot a--dirbaseline..github/workflows/token-report.yml: add aworkflow_dispatch-gatedcanaryjob (guarded so it never runs on the weekly schedule) that runs the script to$GITHUB_STEP_SUMMARY..github/workflows/lint.yml: registertests/canary_report.batsin thebatsjob list.Tests to Write
tests/canary_report.bats: pure-function tests on fixture JSONL — overall PASS; FAIL per metric (cost, cache-read, latency); INSUFFICIENT (fewer than 5 candidate PRs; no non-null duration_ms); unpriced-model accounting (counted + reported, never silently dropped); exit codes 0/1/2.Test Command
scripts/run-bats.sh tests/canary_report.bats(andbash .dev-lead/scripts/dev-lead-lint.shfor shellcheck)don-petry commentedon Sep 26, 2026
Dev-Lead: Implementation Complete — PR #1953
The implementation for issue #1951 is durable: commits are pushed and a pull request is open. This record is backed by verifiable artifacts you can confirm in one click.
ba8e7a8cf84f40f899384dc8d5e1e17533e00e11main: 4 file(s), +725/-0 linesReview happens on the PR. Acceptance criteria and test results are evidenced by the PR's diff and CI checks — not asserted here. If a later stage fails, this claim is retracted in place.
fix: add model-pin-ok markers to canary_report.sh hardcoded defaults
fix(reviews): make canary report model/date/threshold generic and har…
feat: implement issue #1951 — [Phase 2] Canary go/no-go report: compa…