acpmux: show how long a Claude chat's prompt cache stays warm in the context card

Merged
TE
2d ago
+475-42

Leo (2026-11-03): "i heard claude's cache time was like 5min... idk if we maybe also wanna be showing that/opt-in autocompact around that 5min mark"

This is part 1 of 2: the context card shows the prompt cache's state. Part 2 adds the opt-in "Compact before the cache expires" setting.

What Claude Code exposes (2.1.310)

  • What each call reports: every call's usage names the tier it wrote: cache_creation.ephemeral_5m_input_tokens / ephemeral_1h_input_tokens. This is the real TTL, and acpmux uses it.
  • How Claude Code picks the TTL, in its own order:
    1. FORCE_PROMPT_CACHING_5M
    2. CLAUDE_CODE_PROMPT_CACHE_TTL (5m | 1h)
    3. the promptCacheTtl setting
    4. ENABLE_PROMPT_CACHING_1H
    5. with none of these, "automatic": 1 hour on a Claude subscription within its limits, 5 minutes on an API key, Bedrock, Vertex or Foundry.
  • Subagents have their own CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL / subagentPromptCacheTtl (5 min unless ENABLE_PROMPT_CACHING_1H).

Change

  • Daemon (claude_stdio/prompt_cache.rs, new):
    • The expiry is the main loop's last request start (message_start) plus the TTL.
    • The TTL is the tier the call reported. A call that only read keeps the last tier seen.
    • The env and settings are not read: every turn writes its new tail, so the first call already names the tier. In 1,263 local transcripts, 210 of 79,919 main-loop calls (0.26%) named none.
    • The request time is the call's message_start, whose usage already names what it read and wrote, so a call stopped before its message_delta still moves the expiry.
    • The expiry goes out as _meta.acpmux.cacheExpiresAt (unix ms) on usage_update: on each main-loop call and on the turn's result.
    • No expiry is sent when the call used no cache, before any call named a tier, for a subagent's call, or after a compaction (its context is not cached yet).
    • The per-call usage_update is now built in prompt_cache.rs (Translator::call_usage), unchanged in shape, so inbound.rs gets shorter rather than longer.
    • Codex sends none.
  • Webview:
    • The context card adds one muted line under the bar: "Cache warm · 3:12 left", in the warning color in its last minute, then "Cache expired".
    • The UI only counts down to acpmux's timestamp. A finished compaction drops the line, even when its boundary named no size.
    • Timer: one 1 s interval, only while the card is open and the expiry is ahead. It stops at the expiry, when the expiry changes, and when the card unmounts.
  • Gallery: agent-pane.context-ring gets cache-warm, cache-expiring-soon and cache-expired. Each is Leo's screenshot's card (79% · 316.9K / 399.1K, Messages / MCP tools / Agent setup) plus the cache line, on a pinned clock.

Tests

  • Rust: claude_stdio/prompt_cache_tests.rs covers:

    • the tier from the writes
    • expiry = request + TTL
    • a read-only call keeping the 1 h tier
    • the result carrying the expiry and a compaction clearing it
    • no expiry without cache use, before a tier is named, or for a subagent
    • a call stopped before its message_delta still moving the expiry

    These are new behaviour, so they fail on base (the module does not exist there).

  • Webview: bun test src/agent-session/acpmux/ContextCard.test.tsx passes 20/20 locally. The new tests cover the countdown stopping at the expiry, no line without an expiry, and the m:ss format.

Observed / Not run

  • Ran: the ContextCard bun tests (20 pass), scripts/web-lint.sh (passed) and rustfmt.
  • Ran on the fleet at 6873f57: cargo test -p acpmux --lib claude_stdio::, 147 passed, 0 failed. The panic ratchet (rust_panic_ratchet.py) shows no new acpmux hits. The god-file check shows no file grown past its ceiling (inbound.rs is shorter than on base). The agent-pane turn-tokens test for the compaction drop is red without its fix and green with it.
  • Not run: the gallery capture. The advisory gallery run renders the three variants.

Changelog

  • Added: the agent pane's context card shows how long a Claude chat's prompt cache stays warm.

risk: normal (daemon telemetry plus one UI line; no behaviour change)

Agent session: agent session

🤖 Generated with Claude Code