Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
# hand-edited (AGENTS.md) — reformatting it would fight the next release.
CHANGELOG.md
**/CHANGELOG.md

# Generated by `mise run token-bench` from the committed fixtures and diffed
# byte-for-byte by `mise run token-bench-check` (CLOUD-119) — reformatting it would
# fail the gate on the next run, exactly as CHANGELOG.md would fight release-plz.
bench/tokens/RESULTS.md
86 changes: 86 additions & 0 deletions bench/tokens/RESULTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Token economics, measured

Generated by `mise run token-bench` from the committed fixtures. Do not hand-edit:
`mise run token-bench-check` regenerates this file and diffs it byte-for-byte, so an
edited number fails the gate rather than becoming the published one.

Reproduce it yourself — no credential, no network, committed inputs only:

```
git clone https://github.com/button-inc/batten && cd batten
git submodule update --init && mise install
mise run token-bench
```

## What is counted

Bytes an arm puts in front of an agent — stdout **and** stderr, across every
step the task costs. Bytes are exact. Tokens are an estimate through one
declared divisor, and dollars are that estimate at one quoted published rate.

| constant | value | source | retrieved |
| --- | --- | --- | --- |
| bytes per token | 4 — 1 token is approximately 4 characters of English text | <https://platform.claude.com/docs/en/about-claude/pricing> | 2026-08-11 |
| Claude Sonnet 5, fresh input | $2.00 / MTok | <https://platform.claude.com/docs/en/about-claude/pricing> | 2026-08-11 |
| Claude Sonnet 5, cache read | $0.20 / MTok | <https://platform.claude.com/docs/en/about-claude/pricing> | 2026-08-11 |
| Claude Sonnet 5, output | $10.00 / MTok | <https://platform.claude.com/docs/en/about-claude/pricing> | 2026-08-11 |

The divisor affects absolute token and dollar columns only; every ratio is divisor-independent.

## Per capability

### scan-pointer — pointer-only check output

**Question.** Which files and lines carry the banned pattern?

**Baseline** (1 step(s), `grep -rnF '.unwrap()' src`). The full matched output: one whole source line per hit, every hit. This is what a tree scan puts into an agent's context today, and it carries the matched bytes themselves — the payload rather than a pointer to it, which is what makes the same shape unusable over content nobody wants echoed.

**Batten** (1 step(s), `batten check`). `path:line rule-id` per finding and nothing else (non-negotiable rule 4). Same question, same file set, same number of findings — only the answer's shape differs.

**Method.** measured; 3 runs per arm, byte-identical across all of them; run
count for the task is the step count above.

| arm | steps | bytes | est. tokens | USD / 1k tasks (fresh) | USD / 1k tasks (cache read) | exit |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| baseline | 1 | 2705 | 677 | 1.3540 | 0.1354 | 0 |
| batten | 1 | 805 | 202 | 0.4040 | 0.0404 | 2 |
| **ratio** | | **3.36×** | **3.35×** | | | |

### build-warning — exec output predicate

**Question.** Does this build's own output say it is not actually done, and where?

**Baseline** (2 step(s), `tail -n 200 build.log` then `cat build.log`). The named pain, modelled as the two commands it actually costs: the agent pipes to `tail`, the warning is above the window, so it re-runs and takes the whole dump. Two steps, both billed. The fixture places the warning outside a 200-line tail on purpose — that is the case worth measuring, and a fixture that put it inside would be measuring the lucky guess.

**Batten** (1 step(s), `batten exec -- cat build.log`). One step, and the reduction is exactly the discarded `tail` window — nothing more. `exec` TEES (CLOUD-162): the child's bytes reach the caller unchanged by design, so the agent still ingests the whole log, plus Batten's pointer to the line. What it stops paying for is the guess that missed. Whether the log itself becomes cheaper is CLOUD-121's question, not this one's, and the number here is reported at whatever it actually is rather than at what the story would like.

**Method.** measured; 3 runs per arm, byte-identical across all of them; run
count for the task is the step count above.

| arm | steps | bytes | est. tokens | USD / 1k tasks (fresh) | USD / 1k tasks (cache read) | exit |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| baseline | 2 | 25731 | 6433 | 12.8660 | 1.2866 | 0 |
| batten | 1 | 17531 | 4383 | 8.7660 | 0.8766 | 1 |
| **ratio** | | **1.47×** | **1.47×** | | | |

### log-handle — capture-once handles

**Question.** What is in this log around the line that matters, without re-running the command?

**not measured** — the capability is not shipped — `show <handle>` and its select verbs are CLOUD-121, the next issue in this bundle

No figure is published for this capability, and none is projected from a
neighbouring one. A projection is an assertion wearing a measurement's clothes.

## Aggregate

**Not published.** An aggregate across capabilities is a weighted mean over a
workload mix nobody here has measured, so it would be exactly the unmethodical
figure this benchmark exists to beat. Measured capabilities: 2. Reporting
"not measured" with a reason above: 1.

## Stated gaps

- **cross-session prefix-cache hit rate: not measured.** requires a live provider credential; hit rate and time-to-first-token are properties of the world, not of this commit, so they cannot be a gate (see .claude/rules/toolchain.md on gate-vs-schedule)
Measured instead: byte-stability of each arm across runs — the precondition a warm prefix cache depends on, and the half that IS a property of this commit

10 changes: 10 additions & 0 deletions bench/tokens/fixtures/build-warning/batten.toml.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version = 1

# The predicate this workload measures: a command that exits 0 while its own
# output says it is not actually done. No severity knob — a finding that exits 0
# is invisible to the only surface an agent reads (see crates/batten/src/outputs.rs).
[[exec_pattern]]
id = "lying-zero"
pattern = "warning[duplicate]"
stream = "both"
reason = "configure the build to fail on a duplicate dependency instead of warning"
Loading
Loading