X-Ray & Cache Autopsy

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.

Context X-Ray

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.

curl -s https://api.trazadb.com/v1/xray/$TRACE_ID/$SPAN_ID -H "Authorization: Bearer $TRAZA_API_KEY"

Step 12 of session 11111111… · claude-sonnet-4-5-20250929

11 chunks, ~197 estimated tokens of conversation; the provider reported 1,512 context tokens, 1,500 of them read from its cache.

  1. #0 user Add a total to the cart summary from step 0 · llm:claude-sonnet-4-5-20250929, 12 steps ago
  2. #1 assistant Look at the cart module first. from step 0 · llm:claude-sonnet-4-5-20250929, 12 steps ago
  3. #2 assistant I'll look at the cart module. from step 0 · llm:claude-sonnet-4-5-20250929, 12 steps ago
  4. #3 tool call · Read Read({"file_path":"/tmp/demo-shop/src/cart.rs"}) from step 0 · llm:claude-sonnet-4-5-20250929, 12 steps ago
  5. #4 tool result pub struct Cart { items: Vec<Item> } from step 1 · tool:Read, 11 steps ago
  6. #5 tool call · Edit Edit({"file_path":"/tmp/demo-shop/src/cart.rs","new_string":"pub struct Cart { items: Vec<Item> }\n\nimpl Cart {\n pu… from step 2 · llm:claude-sonnet-4-5-20250929, 10 steps ago
  7. #6 tool result The file /tmp/demo-shop/src/cart.rs has been updated. from step 3 · tool:Edit, 9 steps ago
  8. #7 tool call · Bash Bash({"command":"cargo test","description":"Run the tests"}) from step 4 · llm:claude-sonnet-4-5-20250929, 8 steps ago
  9. #8 tool result error[E0609]: no field `price` on type `&Item` from step 5 · tool:Bash, 7 steps ago
  10. #9 tool call · Agent Agent({"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
  11. #10 tool result Added `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.

curl -s "https://api.trazadb.com/v1/cache/$RUN?ttl_seconds=60" -H "Authorization: Bearer $TRAZA_API_KEY"

9 model calls of one session, autopsied with a 60-second cache lifetime (ttl_seconds=60)

  1. step 0 — new_context the first model call of the run: there was no earlier prompt to cache
  2. step 2 ← 0 stable the prefix is unchanged since step 0 (1 chunks, ~8 tokens) and within the 1 min 0 s TTL
  3. step 4 ← 2 stable the prefix is unchanged since step 2 (5 chunks, ~45 tokens) and within the 1 min 0 s TTL
  4. step 6 ← 4 stable the prefix is unchanged since step 4 (7 chunks, ~131 tokens) and within the 1 min 0 s TTL
  5. step 9 — new_context no 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
  6. step 11 ← 9 stable the prefix is unchanged since step 9 (1 chunks, ~10 tokens) and within the 1 min 0 s TTL
  7. step 12 ← 6 ttl_expired ~158 tok · $0.00043 the 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
  8. step 13 ← 12 ttl_expired ~197 tok · $0.00053 the 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
  9. step 14 ← 13 stable the 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
CauseWhen
stableThe 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_expiredThe prefix is intact, but the cache lifetime (300 s unless you pass ttl_seconds) had passed since the previous call began.
volatile_contentA chunk inside the prefix changed in place, was swapped, or new content was inserted ahead of what the cache held.
reorderedThe same content in different bytes or places: key order, spacing, a moved chunk.
new_contextNo previous call to pair with, a chunk removed from inside the prefix, or most of the context rewritten (compaction).
unknownA 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.

Coding agents

Stated plainly

What the X-Ray does not do.

  • 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.

The full X-Ray guide, limits included