Cache disclosure surfaces
Every response header a hit sets, every client control a caller can send, and every field the decision ledger records about the cache.
On this page
Three surfaces carry what the cache did: the response headers a client sees, the control headers a
client may send, and the decision.response_cache block on the ledger row. This page is the full
list of each.
Headers are covered generally in response headers and ledger fields in decision ledger fields; this page is the cache-specific detail.
Response headers
x-tt-cache is set on every cache-eligible request. The rest appear on a served hit.
| Header | On | Values |
|---|---|---|
x-tt-cache |
every eligible request | hit · miss · bypass · error |
x-tt-cache-kind |
hit | exact · normalized · semantic |
x-tt-cache-age |
hit | age of the stored entry, in seconds |
x-tt-cache-source |
hit | the request id whose response this is |
x-tt-cache-entry |
hit | the entry id, and the handle a per-entry purge takes |
x-tt-cache-similarity |
semantic hit | the match score |
x-tt-cache-threshold |
semantic hit | the bar that score cleared |
x-tt-cache-verified |
semantic hit | true · false · none when no verifier ran |
There is no setting that suppresses them. They are written from the same value the ledger block is written from, before any response byte goes out.
HTTP/1.1 200 OK
Content-Type: application/json
X-Tt-Cache: hit
X-Tt-Cache-Age: 102
X-Tt-Cache-Entry: 3a2e3889d0a87a520dce119a216f57b64631b2514a094c71d65f078a67452f23
X-Tt-Cache-Kind: exact
X-Tt-Cache-Source: tt_3fg5aqubovpekklypzj5fxq3de
X-Tt-Mode: route
X-Tt-Request-Id: tt_n67vihukwdsdhxma57ugkvzm74
x-tt-decision is absent on a hit: no upstream was contacted for that response.
What is replayed alongside them
The stored entry carries the response headers the client saw, minus the ones that must be
recomputed or must not be reused: framing and length, content encoding, the authentication headers,
and the whole x-tt-* namespace. Those are stamped fresh at replay, so a replayed response carries
its own request id rather than the original’s.
Client control headers
A caller sends x-tt-cache on the request to change what the cache does for it. Each control’s
true effect is recorded on the row.
| Sent | Effect | Row records |
|---|---|---|
off |
no read, no write | outcome: bypass, bypass_reason: client_off |
refresh |
no read, write allowed | outcome: miss, stored: true |
no-store |
read allowed, no write | a normal hit or miss, stored: false |
| anything else | treated as no control | as though the header were absent |
These are TokenTriage control headers and are stripped before the request is forwarded; the provider never sees them.
Two behaviours are worth knowing:
refresh replaces the entry in place. The entry id is derived from the scope and fingerprint, so
it does not change. What changes is the stored payload and its provenance — after a refresh,
x-tt-cache-source on later hits names the refreshing request.
A no-store hit is still a hit. The control skips the write, not the disclosure. The response
carries the full hit header set.
An unrecognised value fails open to no control, so a client typo cannot silently disable caching for a caller.
The ledger block
decision.response_cache is present on every row where the cache engaged: a lookup ran, a store
happened, or a bypass was decided. Its absence means the cache did not touch the request.
"response_cache": {
"outcome": "hit",
"kind": "exact",
"rule": "chat-cache",
"strategy": "exact",
"entry_sha7": "3a2e388",
"source_req_id": "tt_3fg5aqubovpekklypzj5fxq3de",
"age_ms": 102885,
"scope_sha7": "799cda7",
"stored_provider": "openai",
"stored_model": "gpt-4o-mini",
"source_usage": { "input_tokens": 1200, "output_tokens": 350, "total_tokens": 1550 },
"saved": { "usd": 0.00039, "estimated": true, "unpriced": false },
"saved_basis": "source_usage repriced @ litellm@2026-07-14#179affb, fixed usage",
"stored": false,
"lookup_us": 518,
"serving_tier": "l2"
}
| Field | On | Meaning |
|---|---|---|
outcome |
always | hit · would_hit · miss · bypass · error |
rule |
when a rule was selected | the rule id that governed |
strategy |
hit, would_hit | the matcher that matched |
kind |
hit, would_hit | the disclosure kind of the match |
entry_sha7 |
hit, would_hit | first seven characters of the entry id |
scope_sha7 |
hit, would_hit | first seven characters of the scope key |
source_req_id |
hit, would_hit | the request whose response is stored |
age_ms |
hit, would_hit | age of the entry at match time |
stored_provider, stored_model |
hit, would_hit | the identity that generated the payload |
source_usage |
hit, would_hit | the source row’s usage, the savings input |
saved, saved_basis |
hit, would_hit | the repriced counterfactual and how it was computed |
serving_tier |
hit, would_hit | l1 · l2 · l3 |
similarity, threshold |
semantic | the score and the effective bar |
verified |
semantic, when a verifier ran | the gray-band verdict |
recall_exact, recall_config |
semantic | false plus a config string when an approximate index recalled |
verified_active, verified_calibrated, verified_samples, verified_bound |
semantic under a calibration policy | the calibration state behind the effective threshold |
embed_cost |
metered embedder | the cache’s own priced spend for this lookup |
stored |
always | whether this row’s response was written to the store |
bypass_reason |
bypass, and an over-budget miss | the closed reason vocabulary |
lookup_us, embed_us |
when measured | lookup and embedding time |
Fields whose absence is meaningful
verified is absent when no verifier ran, which is the normal case for a confident-zone semantic
hit and for every exact or normalized hit. It is not the same as verified: false, which means a
verifier ran and did not pass.
embed_cost is absent for any lookup that did not embed and for any in-process embedder, which
records no spend rather than a zero.
source_usage is absent when the stored entry carried no usage.
recall_exact is present only when a similarity decision was made.
The top-level fields a hit row changes
Two, and both matter for anything that aggregates rows.
"usage": { "input_tokens": 0, "output_tokens": 0, "total_tokens": 0, "source": "replay" },
"cost": { "usd": 0, "pricing_snapshot": "litellm@2026-07-14#179affb", "estimated": false }
usage.source: "replay" with zero buckets is what keeps replayed tokens from being counted as fresh
spend. The source row’s tokens are disclosed inside the cache block instead, where a token
aggregation will not pick them up.
cost.usd: 0 on a hit row is a confident zero, which is correct here: no upstream call was made, so
no money was spent. It is not the UNPRICED case, where a price could
not be resolved.
The row still carries the routing decision (decision.tier, decision.upstream) because routing
ran before the cache lookup. On a hit those name the route the request would have taken.