Idle time between agent turns is billed
A prompt cache expires on elapsed time, so a pause between two turns — review, a slow tool, an overnight resume — reprices the next one at full input.
A provider's prompt cache expires on elapsed time, not on turn count. Two runs of the same agent over the same prompts can therefore carry different bills, because one of them paused long enough between turns for the cached prefix to lapse, and the next call re-sent that prefix as fresh input at the write rate. Nothing in your repository records the pause that caused it. The quantity worth holding is cache-write tokens per run, read against the wall-clock gap between the calls either side of them.
A cache miss can be a timer rather than a code change
Prompt prefix stability is the half of caching your code owns: the opening bytes have to be identical across calls or the provider has nothing to match. The other half is a clock. An entry that matches byte for byte still misses once it has aged out of the cache, and the lifetime is a property of the provider and the cache tier you selected, not of your prompt. Read the number off the provider's own pricing page before you reason about it — it differs by provider, and in some cases by tier within one provider.
The economics only work on reuse inside that window. On Anthropic's pricing, cache reads cost about a tenth of base input while writes cost about 1.25×, so a stable prefix pays for itself on first reuse. Run the same arithmetic on a write that is never read and it inverts: you paid a premium over base input for the privilege of storing a prefix that expired, and the next turn pays the premium again. A loop that always pauses past the window is strictly worse off than one with caching switched off, and the only visible symptom is a cache hit rate that nobody set an expectation for.
The pause is a schedule, not a decision
The gaps are made by things that are argued on other grounds entirely:
- A human approval gate between a plan and its application — a safety control, and one that parks the run on the far side of the cache window for as long as it takes someone to read a diff.
- A tool call that waits: a full test suite, a container build, a queued job, a rate-limit backoff that sleeps rather than failing.
- A resumed session, picked up hours later on the same transcript.
- A process restart between turns — a deploy, a crashed worker, a laptop closing.
The resumed case is worth stating on its own, because the thing that survives the pause is the wrong thing. A comment on a Hacker News thread on 2026-11-11 about cross-machine agent session storage describes the practice (source):
Unless of course you choose to provide a previous session to a new agent, but that should be expected.
The transcript is durable and you can move it between machines. The provider-side cache entry it was matching against is neither.
None of these is written down as a cost decision, and none of them changes a line of the prompt. They change when the next call arrives, which is enough.
Compression and caching pull in opposite directions
The highest-signal item in this week's research was a Hacker News post on 2026-11-12 titled "Claude Code's API billing depends on how often the cache expires". Its author describes building an open-source tool for compressing the identical tool outputs that agents resend, and reports two different results from it:
We built the open-source Kompressor for compressing identical tool outputs that agents constantly resend. In our Codex tests, we noticed a real cost reduction of 22.7%. But testing it with Claude Code, we found something completely different. In Claude Code, the cache plays a more crucial role than compression.
Take that as one team's measurement of two tools in their own tests, not as a general ratio. The mechanism underneath it does generalise, though, and it is the reason the two results differ: any transformation that rewrites text already sitting in the transcript changes the bytes a cache was matching on. Shrinking what the model reads and keeping the prefix stable are separate objectives, and optimising for one without measuring the other is how a saving arrives as a rise. Compacting a context costs twice makes the same argument from the summarisation side; compression is the lower-level version of it, and the expiry case is the version where you did not touch the transcript at all.
What it does to a comparison
The practical damage is to measurement. A prompt change evaluated across two days, where one day's runs were interactive and the other's ran back to back in CI, is a comparison of two schedules wearing the costume of a comparison of two prompts. A benchmark that executes a fixed task set with no pauses will under-report what the same agent costs in front of a person. And in a daily rollup, a run whose first turn after lunch re-paid the whole prefix is indistinguishable from a prompt regression, because the only field that separates them — elapsed time since the previous call on that prefix — is not on the row.
This is also one of the few cost defects where the fix is scheduling rather than code: batching the turns that can be batched, keeping an approval gate inside the window where that is safe, or accepting the write and sizing the budget for it. Deciding which requires knowing what the pauses currently cost, which is the part nobody measures. The detection method on broken prompt caching covers the byte-instability case; the timing case needs one extra column.
What to record
- Cache reads, cache writes and uncached input as three separate dimensions on every call. Collapsed into one input total, this entire effect is invisible — the token count barely moves while the price per token does.
- The wall-clock gap between consecutive calls in a run, stored next to the cache-write tokens on the call that follows it. That pairing is the whole diagnosis.
- Cache writes per run. On a long run over a stable prefix this should sit near one per distinct prefix. A count that tracks the turn count means the window is lapsing every turn.
- Whether the run was attended or unattended, as a dimension on the row. The schedule is the variable under test, so it has to be recorded like one.
- Cumulative input tokens per completed run, compared across schedules rather than across calendar weeks.
How this works in Capsera, and what it does not do
capsera.init() patches the provider clients — Anthropic messages, OpenAI chat
completions, OpenAI embeddings and Google Gemini, sync and async — so the usage
fields that carry cache reads and cache writes are captured wherever in your
process the call was made, with no per-call wiring.
@capsera.agent(name="planner") says whose turn it was, and because the agent
stack is a contextvars ContextVar rather than thread-local state, nested calls
inherit that identity without anything being threaded through. The dashboard's
prompt analysis reports cache hit rate and repeated prompts, which is where a
rate that falls with no deploy behind it shows up.
Two limits, both relevant here. Capsera runs in your process, so if the loop making these calls is a vendor CLI rather than your own code, nothing in-process sees it — that spend is on the harness's own ledger and this instrumentation cannot reach it. And because we store hashes and token counts and never prompt or response content, the dashboard can tell you the hit rate dropped and cannot tell you which bytes stopped matching. Budgets are checked before the provider call and are on by default, scoped per agent, per team or globally — a ceiling on an agent, not on a run, since there is no per-run cost ceiling.
If what you need is a cache you control rather than the provider's, with its own expiry policy, LiteLLM is MIT-licensed, self-hostable and ships a caching layer. If you need to diff the two prompts byte by byte to prove which one broke the prefix, Langfuse is open source, free, and stores exactly the content we deliberately do not.
Give every agent an identity, a budget, and hard limits.
One line of code. Anthropic, OpenAI, and Google Gemini.
See pricing