Skip to content

summarize's cache gate is wrong on OpenAI-shaped backends in both cache modes, in opposite directions #245

Description

@amiddavid

summarize's cache gate is derived from Ctx.CacheTTLMs, which apply.cacheTTL computes. That function is exact for the Anthropic family — the request itself declares the tier — and a guess for everything else. The guess is wrong for OpenAI-shaped backends in both cache modes, in opposite directions, with no setting in between.

Under cache_mode: auto

cacheAware is false, so CacheTTLMs and MaxCachedIdx are never set. CachePhase returns Unknown, and CacheAllows permits Unknown — which is correct and deliberate for a backend with no prompt cache, but OpenAI has an automatic prefix cache we are not modelling. So the gate permits compaction on every turn while silently discarding a real cache the provider maintains on its own.

(Note: CacheAllows now refuses Unknown when MaxCachedIdx >= 0, added in #234. It does not help here, because on this path MaxCachedIdx is never set either.)

Under cache_mode: on

apply.cacheTTL's default: arm returns extendedTTL — one hour — on the documented reasoning that an OpenAI-shaped cache "clears after minutes of inactivity and always within the hour". As an upper bound on lifetime that is the safe direction for a cold claim. As the input to a pre-expiry window it is the unsafe one: remaining = 3600s − idle stays far above the 60s window for essentially every turn, so CachePhasePreExpiry is unreachable and the gate never permits. The component is effectively disabled.

So the two modes are "compacts every turn, ignoring a real cache" and "never compacts at all", and there is no third setting.

A second hardcoded five minutes

dash/event.go:868 carries its own providerCacheTTLMs = 5 * 60 * 1000, independent of apply.cacheTTL. That is a third reader of a fact this repo has already had trouble keeping in one place — cachephase.go's own comment says it exists so there is "one fact, one reader" — and it is on the dashboard side, so a divergence shows up as the panel and the gate disagreeing about the same turn.

Explicitly fine

Bedrock and Vertex-fronted Claude are correct. Both are in cacheTTL's Anthropic-family switch, and dispatch is on the provider argument rather than on the model string, so an aws/ prefix never reaches the default: arm. A review verified this specifically, and it is worth recording because the model-id prefix makes it look as though it might not be.

What a fix probably needs

  • A per-provider TTL and a statement of whether that TTL is derived or assumed, so CachePhase can decline to classify rather than classify on a guess — the same measured/assumed split modelinfo.Exact already makes for context windows, and for the same reason.
  • A decision about OpenAI's automatic cache specifically: either model it (a real TTL, a real boundary) or state that summarize's cache gate is Anthropic-family-only and have the component say so rather than behaving differently in each mode.
  • dash/event.go's constant reading from wherever the first bullet puts the fact.

Scope

Pre-existing. apply.cacheTTL and its default: arm are older than #234; that PR is what made the value load-bearing for a firing decision rather than only for a cold/warm label, which is how the asymmetry became visible. Its own branch off main.

Found by the review on #234.

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