Skip to content

offload: summarize's kept-verbatim trim can force a re-summary, and that cache-write is not counted by expand_prefix_flips #209

Description

@amiddavid

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

  1. 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.
  2. 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.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions