What exactly did the model see — and why did the cache miss?
Traza already stores every request an agent sent. The X-Ray reads one back as the context window it was; the autopsy compares every call with the one before it in the same conversation. Deterministic arithmetic over stored spans: no model is called, nothing leaves the process.
The context window as chunks, each with where it came from.
A step’s recorded request is split into one chunk per content part — system prompt, user turns, the model’s own
earlier words, tool calls, tool results, tool definitions — each with an estimated token count and the step of the
run that introduced it: in the demo call, this tool result
came from the Read call at step 1; this is what
the model itself said at step 0.
It marks what is dead weight — tool results that have sat in the context for many steps without the model drawing
on them, content that is there twice — and what was lost: what the conversation’s previous call had that this one
does not, such as the instruction a compaction summarized away.
#6tool resultThe file /tmp/demo-shop/src/cart.rs has been updated.from step 3 · tool:Edit, 9 steps ago
#7tool call · BashBash({"command":"cargo test","description":"Run the tests"})from step 4 · llm:claude-sonnet-4-5-20250929, 8 steps ago
#8tool resulterror[E0609]: no field `price` on type `&Item`from step 5 · tool:Bash, 7 steps ago
#9tool call · AgentAgent({"description":"Fix the Item type","prompt":"Add a price field to Item in src/item.rs","subagent_type":"general-pu…from step 6 · llm:claude-sonnet-4-5-20250929, 6 steps ago
#10tool resultAdded `price: u64` to Item.from step 7 · tool:Agent, 5 steps ago
Composition: tool call 66% · tool result 21% · assistant 8% · user 4%.
Cache: stable, the prefix is unchanged since step 6 (9 chunks, ~158 tokens) and within the 5 min 0 s TTL.
Demo corpus
Real output of traza-server 0.28.0 over the engine’s synthetic Claude Code and Codex transcripts (4 sessions), imported with traza-server import, recorded 2026-10-18 by
scripts/demo-numbers.mjs. Synthetic runs with known outcomes, not customer data.
Cache Autopsy
One verdict per model call, and the bytes that broke the prefix.
A provider’s prompt cache serves the longest previously seen prefix, byte for byte, while the entry lives. So each
call is paired with the call before it in the same conversation, the two contexts are compared chunk by chunk, and
the first chunk that changed is named — with the changed stretch of its text, usually one volatile token: a
timestamp, a request id.
The tokens the cache could have served are estimated and priced at the input rate less the cache-read rate, when
your pricing table names both. Breaks are grouped by shape, costliest first, each with fixed advice from a lookup
table: move the clock after the cached prefix, append retrieved documents after the history, sort tool output keys.
9 model calls of one session, autopsied with a 60-second cache lifetime (ttl_seconds=60)
step 0—new_contextthe first model call of the run: there was no earlier prompt to cache
step 2← 0stablethe prefix is unchanged since step 0 (1 chunks, ~8 tokens) and within the 1 min 0 s TTL
step 4← 2stablethe prefix is unchanged since step 2 (5 chunks, ~45 tokens) and within the 1 min 0 s TTL
step 6← 4stablethe prefix is unchanged since step 4 (7 chunks, ~131 tokens) and within the 1 min 0 s TTL
step 9—new_contextno earlier call among the previous 32 belongs to this call's conversation (gen_ai.conversation.id): the first call of a new conversation (a sub-agent, say), or one whose previous call lies further back
step 11← 9stablethe prefix is unchanged since step 9 (1 chunks, ~10 tokens) and within the 1 min 0 s TTL
step 12← 6ttl_expired~158 tok · $0.00043the prefix is unchanged since step 6 (9 chunks, ~158 tokens), but 1 min 15 s passed since that call began — past the 1 min 0 s TTL
step 13← 12ttl_expired~197 tok · $0.00053the prefix is unchanged since step 12 (11 chunks, ~197 tokens), but 3 min 30 s passed since that call began — past the 1 min 0 s TTL
step 14← 13stablethe prefix is unchanged since step 13 (13 chunks, ~215 tokens) and within the 1 min 0 s TTL
ttl_seconds=60
2 breaks · ~355 tokens re-processed · $0.00096
default TTL (300 s)
0 breaks · 7 stable · 2 new contexts
Advice, from the engine’s lookup table: Calls are further apart than the cache lifetime. Use a longer-lived cache if the provider offers one, or pass ttl_seconds= if it is already in use.
Dollars at the rates in scripts/demo-pricing.json. The provider’s own cache report contradicts the recorded prompts on 1 call; the autopsy counts that rather than explaining it away.
Cache Autopsy causes
Cause
When
stable
The previous call’s whole context is an unchanged prefix of this one, within the cache lifetime — or a new turn in the last place.
ttl_expired
The prefix is intact, but the cache lifetime (300 s unless you pass ttl_seconds) had passed since the previous call began.
volatile_content
A chunk inside the prefix changed in place, was swapped, or new content was inserted ahead of what the cache held.
reordered
The same content in different bytes or places: key order, spacing, a moved chunk.
new_context
No previous call to pair with, a chunk removed from inside the prefix, or most of the context rewritten (compaction).
unknown
A shorter prompt than the previous one, or a request that cannot be read.
Demo corpus
Real output of traza-server 0.28.0 over the engine’s synthetic Claude Code and Codex transcripts (4 sessions), imported with traza-server import, recorded 2026-10-18 by
scripts/demo-numbers.mjs. Synthetic runs with known outcomes, not customer data.
On Traza Cloud
What cache breaks cost in your team’s costliest sessions.
The X-Ray and the autopsy run on your project’s cell like any read. Traza Cloud’s Coding agents page autopsies the
ten most expensive sessions in its window, across the organization’s projects, and sums them into one figure: how
often the cache held, the dollars lost to cache breaks, and the costliest break shapes with a session to open for
each. It is a sample, and says how many sessions it covers. Your coding agent can ask too: the
xray_step MCP tool returns a compact X-Ray with the cache verdict and its diff.
Estimates and heuristics are labelled. Per-chunk tokens are estimates; staleness and “near-identical” are rules; which call a conversation continues is a heuristic unless the call names its conversation.
The provider’s cache rules are not modelled — minimum cacheable length, breakpoint placement, refreshes on a hit. The provider’s own cache report is shown beside every verdict, and disagreements are counted.
Byte-exactness is the recording’s. Instrumentation records messages, not the HTTP body the SDK sent; a difference it normalized away is invisible.
A transcript has no system prompt or tool definitions. For imported Claude Code and Codex sessions, the context is the conversation only — which is why the demo call’s 11 chunks estimate far fewer tokens than the provider counted.
Bounded. One request reads its run’s first 20,000 spans and splits a context into at most 2,000 chunks; the whole request runs within one query deadline and is refused, never answered partially, when it runs out.