Skip to content

feat: prompt cache keep-warm pings to prevent TTL expiry between turns #62475

Description

@100yenadmin

Problem

Anthropic prompt cache expires after ~5 min (cacheRetention: "short") or ~1 hour (cacheRetention: "long"). Between user messages, when no API call occurs, the cache goes cold. The next turn re-caches the entire system prompt prefix + conversation history at write pricing ($3.75/MTok) instead of read pricing ($0.375/MTok) — a 10× cost multiplier on every cache miss.

For sessions with moderate inter-message gaps (5–60 min), this is the dominant cost driver. A typical 84k system prompt + 80k conversation history = ~164k cached tokens. Each cache miss costs $0.62 vs a cache hit at $0.062.

Prior Art

This resurrects the core idea from #24711 (closed/stale) and addresses part of #57569 (cache pre-warming after reconnect).

Proposed Solution

A lightweight, invisible, non-turn mechanism that fires minimal API pings (max_tokens: 1) to keep the prompt cache warm between user interactions.

Key Design Decisions

  • Adaptive interval: Computed automatically as TTL × 0.8 (4 min for short, ~48 min for long). Not user-configurable — eliminates misconfiguration class.
  • Not a heartbeat: No agent turn, no tool calls, no compaction, no session history entry.
  • Compaction-aware: Dirty flag cancels pending pings when system prompt changes (prevents warming stale cache keys).
  • Async verification: Check cache_read_input_tokens in ping response to confirm cache hit.
  • Hard cost cap: Default $0.10/hr, auto-disables if exceeded.
  • Same payload construction path: Ping uses identical system blocks + tools as real turns via resolveAnthropicPayloadPolicy() + applyAnthropicCacheControlToSystem() to match the cache key exactly.

Config

// openclaw.json
{
  "agents": {
    "defaults": {
      "promptCacheKeepWarm": {
        "enabled": false,           // opt-in
        "maxCostPerHourUSD": 0.10,  // hard cap, auto-disables
        "durationMs": 3600000,      // stop after 1h of inactivity
        "providers": ["anthropic"]
      }
    }
  }
}

Lifecycle

  1. After assistant reply → capture PromptCacheSnapshot, start interval timer
  2. On timer fire → check dirty flag, check cost cap, build minimal payload, fire ping
  3. On user message → cancel timer (real turn takes over)
  4. On compaction/model-switch → set dirty flag (next tick cancels)
  5. On session close / SIGTERM → cancel all timers

Build-On Opportunity

PR #56575 already adds session timestamps (lastUserMessageAt, lastAssistantMessageAt, lastCacheTouchAt) and a gateway maintenance sweep timer. This feature could be implemented as an additional mode ("ping") alongside the existing "compact" and "reset" modes, reusing the timer infrastructure.

PR #62179 (exposes runtimeContext.promptCache to context engines) provides the cache-break detection needed for dirty flag awareness.

Cost Analysis

Config Ping cost Cache miss cost Savings per avoided miss
84k tokens, short TTL ~$0.032/ping $0.315/miss $0.28
164k tokens (long conv), long TTL ~$0.062/ping $0.615/miss $0.55

With cacheRetention: "long", keep-warm fires ~1 ping per gap period. Break-even at 1 avoided cache miss per session.

Provider Abstraction

  • Anthropic: Full message, max_tokens: 1 (no lighter endpoint exists)
  • OpenAI: No-op (automatic caching, nothing to warm)
  • Google: Future — cache TTL extension API call (different mechanism)

Upstream Dream Ask

An Anthropic POST /cache/refresh endpoint that extends TTL without requiring a full message payload would reduce ping cost ~99%.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.issue-rating: 🌊 off-meta tidepoolIssue quality rating does not apply to this item.staleMarked as stale due to inactivity

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions