The gap
expand_prefix_flips (#208) counts one event: a replay declined because the agent had expanded that content (components/offload.reapplyFrozen, its only increment site). #208 also added a second event of the same class, and it is uncounted.
trimSpanForKeptVerbatim lowers summarize's end so the span never contains expanded content. A checkpoint whose boundary reached past the new end then fails tryReuse's boundary > end guard, so summarize re-summarizes a shorter span from scratch — different summary text at a fixed prefix position, which is a suffix cache-write at ~11.5× a read.
That is the correct trade: losing the agent's expanded content is worse than one cache-write, and re-compacting it would be the bounce loop cg:keep: exists to prevent. But it means a deployment can pay expansion-induced prefix churn while expand_prefix_flips reads zero.
Why it was not counted in #208
A second increment site changes what the number means. Today it answers "how often did a replay decline because of an expand", which is per turn per message and already needs a caveat documented in three places. Adding "and also, how often did a checkpoint get invalidated by the trim" makes it a mixed total that no single reading covers — the ambiguous-counter shape #200 states as a rule.
So #208 narrowed the wording instead: metrics.Snapshot, docs/reference/routes.md and the counter's own doc all now say it counts one event and that a zero does not mean expansion cost nothing. This issue is the decision that wording defers.
The options
- A second counter.
expand_resummaries or similar, incremented where the trim shortens a span that had a checkpoint reaching past the new end. Keeps each number readable; costs a second series for a rarer event.
- One counter with a label.
cg_expand_prefix_flips_total{cause="replay_declined|checkpoint_invalidated"}. One series, decomposable — but the /stats JSON field would have to become an object or gain a sibling, and that is a contract change.
- Leave it uncounted and documented, the current state. Defensible while the event is believed rare — but nobody has measured how rare, and the trim is new.
What would settle it
Whether the trim actually invalidates checkpoints in practice, which needs resummarize_tokens > 0 (checkpointing on) plus an agent that expands content inside the summarizable middle. kept_verbatim_after_expand on summarize's gate row is the leading indicator: non-zero there with resummarize_tokens set is the population at risk.
How it was found
Review of #208 (round 2), by the reviewer tracing what lowering end does to tryReuse's boundary > end guard. Related: #201 (the flip), #200 (the counter-splitting rule), #204.
The gap
expand_prefix_flips(#208) counts one event: a replay declined because the agent had expanded that content (components/offload.reapplyFrozen, its only increment site). #208 also added a second event of the same class, and it is uncounted.trimSpanForKeptVerbatimlowers summarize'sendso the span never contains expanded content. A checkpoint whoseboundaryreached past the newendthen failstryReuse'sboundary > endguard, so summarize re-summarizes a shorter span from scratch — different summary text at a fixed prefix position, which is a suffix cache-write at ~11.5× a read.That is the correct trade: losing the agent's expanded content is worse than one cache-write, and re-compacting it would be the bounce loop
cg:keep:exists to prevent. But it means a deployment can pay expansion-induced prefix churn whileexpand_prefix_flipsreads zero.Why it was not counted in #208
A second increment site changes what the number means. Today it answers "how often did a replay decline because of an expand", which is per turn per message and already needs a caveat documented in three places. Adding "and also, how often did a checkpoint get invalidated by the trim" makes it a mixed total that no single reading covers — the ambiguous-counter shape #200 states as a rule.
So #208 narrowed the wording instead:
metrics.Snapshot,docs/reference/routes.mdand the counter's own doc all now say it counts one event and that a zero does not mean expansion cost nothing. This issue is the decision that wording defers.The options
expand_resummariesor similar, incremented where the trim shortens a span that had a checkpoint reaching past the new end. Keeps each number readable; costs a second series for a rarer event.cg_expand_prefix_flips_total{cause="replay_declined|checkpoint_invalidated"}. One series, decomposable — but the/statsJSON field would have to become an object or gain a sibling, and that is a contract change.What would settle it
Whether the trim actually invalidates checkpoints in practice, which needs
resummarize_tokens > 0(checkpointing on) plus an agent that expands content inside the summarizable middle.kept_verbatim_after_expandon summarize's gate row is the leading indicator: non-zero there withresummarize_tokensset is the population at risk.How it was found
Review of #208 (round 2), by the reviewer tracing what lowering
enddoes totryReuse'sboundary > endguard. Related: #201 (the flip), #200 (the counter-splitting rule), #204.