kew report¶
Generate an outcome-linked spend report in markdown: what the fleet did, what it cost, what shipped. Headline metric: cost per merged PR.

Usage: kew report [OPTIONS]
Options:
--days INTEGER Reporting window in days [default: 7]
-o, --output PATH Write markdown to a file (default: stdout)
--notify POST the report to the configured webhook
--models Render the per-model outcome scorecard instead of the spend report
The report contains up to eight sections: header (repo, period, generated-at), totals (spend, runs by status, tokens, spend vs configured budget caps), outcomes (PRs opened, PRs merged, cost per merged PR, median cost per run), the top 5 most expensive issues, per-model and per-runner breakdowns, a cache economics section (when cache token data exists), held/blocked runs, and a footer stating the cost-per-merged-PR formula.
Planned vs actual cost. Every run records both a planned bound (its tokens priced at the pricing snapshot/override bound — the conservative max-across-provider-endpoints figure dispatch plans against) and the recorded actual (estimated_cost_usd: the runner's native provider-billed total when it reports one, else the same token bound). Total spend is always the actual. When any run in the window recorded a bound, the totals section adds a Planned bound line naming both halves and their ratio — the third honest cost pair alongside list-vs-subscription. Runs from before this pair shipped — and runs on a model with no pricing entry, which have no rate to price against — carry no bound and are excluded from the comparison, never counted as $0 planned.
When governed-review runs have positive estimated spend, the totals section adds a Governed-review spend line — the adversarial_review + review_remediation run-kind spend as its own bucket (it is a subset of total spend, not additive). Reviewer runs are a separate budget pool ([review].max_review_usd) and are bucketed separately in the cost-anomaly breaker medians too (#445), so a heavyweight review run is never judged against cheap first-dispatch baselines.
Cost per merged PR = total spend in the window ÷ distinct PRs from window runs that are merged at report time. Spend on failed and killed runs is deliberately included — that is the honest unit economics. Zero merged PRs renders n/a.
Cache economics¶
When any run in the reporting window recorded cache-creation or cache-read tokens, the report adds a Cache economics section showing a per-(runner, model, provider) breakdown and the aggregate savings vs an all-cold baseline.
| Column | Definition |
|---|---|
| Runner / Model / Provider | The three-part identity of the route that produced the cache data |
| Read tokens | Prompt tokens served from the provider's cache (at the read-discounted rate) |
| Write tokens | Prompt tokens written to cache (at the write-premium rate) |
| Uncached | Input tokens that were neither cached nor written — plain prefill |
| Hit ratio | read_tokens / (read + write + uncached) — the fraction of input work served from cache |
| Runs (cold) | Total runs in the group, with the count of runs that had zero cache activity (neither read nor write) in parentheses |
The summary line reports total savings:
The "all-cold baseline" is a counterfactual: what the input tokens would have cost if the provider had no prompt caching at all (full prefill at the base input rate, no write premium or read discount). The savings line is the difference between actual and that baseline.
A one-line reminder also appears after the interactive usage section (when present):
Capturing a pre-auto baseline. Before enabling cache_aware or order_aware, run kew report with the feature in advise-only mode (default). The cache section shows the savings the feature would achieve, letting you compare before-and-after numbers without the confound of the feature acting on itself. The ledger also records every routing_decision and cache_reorder audit event with applied: false, so you can rebuild the counterfactual from the audit trail.
Examples:
# See cache savings for the last 7 days (advise-only mode by default)
kew report
# Monthly cache economics
kew report --days 30
# Inspect governor decisions (advise or applied)
kew audit tail | grep routing_decision
PR outcomes (merge state, review cycles, diff stats) are persisted to the local SQLite store. kew report attempts to sync the latest state from GitHub before reading, so when GitHub is reachable the report reflects current outcomes without an extra step. If GitHub is unreachable or a PR can't be resolved, the report still renders (exit code 0) using the previously-persisted outcomes (which may be stale). Run kew sync explicitly to refresh outcomes without generating a report.
Examples:
# Last 7 days to stdout
kew report
# Monthly rollup
kew report --days 30
# Write to a file (e.g. from cron, then mail it to your boss)
kew report -o spend-report.md
Model scorecard (--models)¶
kew report --models renders a per-model outcome scorecard — merge rate, mean review submissions, and cost per merged PR, broken down by model and complexity — measured from your own repo's actual outcomes, not a benchmark. The normal spend report is unchanged; --models renders only the scorecard.
Metrics
| Column | Definition |
|---|---|
| Runs | All runs using that model (throughput; spend basis — includes failed and abandoned runs) |
| Merge rate | merged / opened PRs authored by the model (see Attribution). Always shown with raw counts, e.g. 71% (12/17). Renders — when no PRs were opened. |
| Reviews | Mean review_cycles per authored PR — the count of PR review submissions, including bot reviewers (e.g. CodeRabbit), and 0 for a PR that merged with no reviews. Renders — when no data is available. |
| $/merged PR | Total spend for that model's runs ÷ distinct merged PRs it authored. Spend includes all runs (failed, abandoned, and runs it spent reviewing other models' PRs), so a model with many no-PR runs shows a higher value. Renders — when no PRs merged. |
| Planned vs actual | Planning accuracy: recorded actual ÷ planned bound, over the runs carrying both halves of the pair. 1.00x means the token × pricing-bound estimate predicted spend exactly; below 1.00x means the bound over-records (an OpenRouter run has measured ~0.10x). Renders — when no run in the bucket recorded a bound — runs from before the pair shipped are excluded rather than counted as $0 planned. |
Rows are grouped by the configured model field (the routing lever, always present). Each model row is followed by · simple / medium / complex / unknown complexity sub-rows; sub-row runs and PR counts sum to the model total.
Attribution. When multiple runs share one PR — cross-model retries, and every governed review — the PR outcome (pr_state, review_cycles) is attributed to the authoring run: the earliest-started_at run whose run_kind is not a review kind (review, adversarial_review, review_remediation). Reviews start after the dispatch they review and carry the same PR URL, so crediting the latest run would hand every reviewed PR's merge to the reviewing model (#1131). When a PR has review runs only — an out-of-band PR that kew reviewed but did not write — the latest run is used as a fallback so the PR is still counted somewhere.
Each run's estimated_cost_usd counts toward the model that ran it, regardless of outcome or run kind. So a failed opus retry raises opus's $/merged PR and earns it no merge, and spend a model incurs reviewing someone else's PR sits on its own row against the PRs it authored. review_cycles follows the PR, not the reviewer: it counts GitHub review submissions on the authored PR (bots included), which is a property of that PR's code, not of whichever model kew ran as reviewer.
Honest scope. opened and merged count PRs that had at least one successful, synced kew run — a PR whose kew runs all failed (and so no PR URL was ever recorded by kew) is not counted. The merge rate answers "of the PRs this model successfully produced, how many merged?" not "every PR the model ever touched."
Small-sample and honest-null handling. Rows with 1 or 2 opened PRs are marked * in the merge-rate cell; rows with no opened PRs show —. Zero-denominator cells always render — (em dash), never 0%. If a model has no opened PRs yet (fresh repo or only failed runs), it still lists with its runs and spend while merge rate, Reviews, and $/merged render —. If there are no runs at all in the window, the output renders a single line: No model activity in the last N days.
--days and -o apply as they do for the spend report. --models --notify is rejected with an error (webhook delivery is for the spend report, not the scorecard).
Illustrative output:
# Model scorecard — owner/repo
- **Period:** 2026-05-27 → 2026-06-26 (30 days)
- **Generated:** 2026-06-26 14:00 UTC
| Model | Runs | Merge rate | Reviews | $/merged PR |
| --- | --- | --- | --- | --- |
| claude-opus-4-8 | 24 | 71% (12/17) | 1.4 | $3.10 |
| · simple | 6 | 83% (5/6) | 1.0 | $1.20 |
| · medium | 10 | 67% (6/9) | 1.5 | $2.90 |
| · complex | 8 | 50%* (1/2) | 2.0 | $5.40 |
| claude-sonnet-4-6 | 31 | 58% (11/19) | 2.1 | $0.92 |
| · simple | 18 | 73% (8/11) | 1.8 | $0.40 |
| · medium | 13 | 38% (3/8) | 2.7 | $1.60 |
\* low sample (<3 PRs) · Reviews = PR review submissions (incl. bots) · $/merged PR includes failed and abandoned runs
Examples:
# Scorecard for the last 30 days
kew report --models --days 30
# Write to a file
kew report --models -o scorecard.md
# Rejected — --notify is for the spend report, not the scorecard
kew report --models --notify
Evidence pack (report export)¶
kew report export writes a self-contained evidence pack — one HTML file (inline CSS/JS, no CDN references, prints cleanly) or its machine-readable JSON twin. It is the client-facing deliverable: a single artifact proving what ran, what it cost, and what merged over a window.
Usage: kew report export [OPTIONS]
Options:
--out PATH Output file path for the evidence pack [required]
-f, --format [html|json] Export format [default: html]
--since TEXT Window start (ISO 8601, e.g. 2026-06-01). Default: 30 days ago.
v1 sections:
- Economics summary — total spend, PRs opened/merged, cost per merged PR, and spend-vs-cap for the rolling 1-day and 7-day windows.
- Outcome ledger — issue → run(s) → PR → merge state, grouped per epic (runs not attributed to an epic fall into an
(unassigned)bucket). - Run-kind breakdown — runs, success rate, and cost split by dispatch / review / loop / planning.
- Model scorecard — per-model runs, merge rate, mean review submissions, spend, and cost per merged PR, with a per-complexity drill-down. Reuses the same scorecard data as
kew report --models(no re-derivation), rendered into the pack rather than recomputed. - Audit attestation — the tamper-evident audit chain verified in-process (the same
kew audit verify --anchorsmachinery, no subprocess): event count, the verified id range, the chain-head hash, and — when anaudit-anchors.jsonlfile is present at the repo root — how many anchors matched plus any gaps (mismatched / absent / ahead). Verify-on-demand only: the pack embeds the head hash so a reader can re-runkew audit verifylater; nothing is published externally.
The HTML embeds a machine-readable JSON data island (<script type="application/json" id="kew-evidence-data">) so a single HTML file is also a data carrier; --format json writes the same payload as a standalone file. Report data is read through the service layer (kew.service.report), not by reaching into the run store directly.
Examples: