Files
b1b283cd4c fix(buzz-acp): thread cache-read tokens into NIP-AM kind:44200 events (#3999)
## Problem

`buzz-agent` measures and sends `accumulatedCachedInputTokens` on the
wire (`usage.rs:93`). `buzz-acp` deserializes it correctly — but then
drops it: `TurnUsage` had no cache field, and `build_turn_metric_counts`
hardcoded `cache_read_tokens: None` and `cache_write_tokens: None` into
both `turn` and `cumulative` `TokenCounts`. Every kind:44200 event
published permanently lacked data the harness measured. The archive is
append-only — this is unrecoverable data loss per turn, every turn,
until fixed.

NIP-AM already specifies the fields (`cacheReadTokens` /
`cacheWriteTokens` inside `turn` and `cumulative`). This is a pure
threading fix.

## Changes

**`crates/buzz-acp/src/usage.rs`**

- `SessionState` gains `last_cached_input: u64` to track the committed
cache-read baseline.
- `TurnUsage` gains `turn_cache_read_tokens: Option<u64>` (field-local;
`None` when no baseline or counter decreased) and
`cumulative_cache_read_tokens: u64` (always present; zero when no cache
hits reported).
- `record()` computes the cache-read delta with field-local taint
semantics: a decrease in the cumulative counter nulls only
`turn_cache_read_tokens` — it does not flip `delta_reliable` or
invalidate `turn_input_tokens`/`turn_output_tokens`. Identical to the
`accumulatedTotalTokens` pattern already present.
- `take()` and the setup-notification branch both advance
`last_cached_input` in the committed baseline.

**`crates/buzz-acp/src/pool.rs`**

- `build_turn_metric_counts` wires `turn_cache_read_tokens` into
`turn.cache_read_tokens` (when `delta_reliable`) and
`Some(cumulative_cache_read_tokens)` into
`cumulative.cache_read_tokens`.
- `cache_write_tokens` remains `None` on both counts with an explanatory
comment: buzz-agent does not emit a write-side count on the wire today.
- Six existing `TurnUsage` struct literals in tests updated with the two
new fields.

## Tests

**`usage.rs` — new cache-read section (5 tests):**
-
`cache_read_first_turn_produces_none_turn_delta_and_passes_cumulative_through`
— no baseline → delta None, cumulative passes through
- `cache_read_second_turn_delta_computed_correctly` — delta = current −
previous
- `cache_read_decrease_nulls_turn_cache_but_leaves_delta_reliable` —
field-local taint: decrease nulls cache delta only, input/output stay
reliable
- `cache_read_zero_payload_after_baseline_produces_zero_delta` — zero on
both sides → `Some(0)`, not `None`
- `cache_read_threads_through_setup_notification_baseline` — setup
notification baseline correctly seeds the cache counter

**`pool.rs` — new acceptance test (1 test):**
- `test_build_turn_metric_counts_cache_read_tokens_thread_through` —
wire-parses a buzz-agent payload with nonzero
`accumulatedCachedInputTokens`, runs two turns through the tracker and
`build_turn_metric_counts`, and asserts nonzero `cacheReadTokens` in
cumulative + correct per-turn delta in `turn`; also asserts
`cache_write_tokens` is `None` throughout

## Quality gates at tip `c6405eb43f532572e3b7775e0dee826dc9cb3f82`

| Gate | Result |
|---|---|
| `cargo test -p buzz-acp` | **655/655**, 0 failed |
| `cargo clippy -p buzz-acp --all-targets -- -D warnings` | clean |
| `cargo fmt --check` | clean |

Note: the pre-push hook `mobile-test` gate fails on `origin/main` before
this branch (Flutter test in `channels_page_test.dart` /
`compose_bar_test.dart` — verified independently). My changes touch only
`crates/buzz-acp/src/`; the mobile failure is unrelated and
pre-existing.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1g8493u0xfsjrvflg4n08ezd7vec99mnwzlv0qgwpr9d7gvjwhuzqx59rhw <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
2026-07-31 16:41:56 -04:00
..
2026-07-27 14:18:24 -04:00