Skip to content

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.

kew report demo

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:

Cache saved **$4.20** vs all-cold baseline ($42.00) — **10.0%** 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):

*Cache saved you $4.20 (10.0% of input cost)*

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 --anchors machinery, no subprocess): event count, the verified id range, the chain-head hash, and — when an audit-anchors.jsonl file 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-run kew audit verify later; 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:

# Self-contained HTML evidence pack (last 30 days)
kew report export --out evidence-pack.html

# Machine-readable JSON twin
kew report export --out evidence-pack.json --format json

# Explicit window start
kew report export --out pack.html --since 2026-06-01