Il 6 maggio 2026, Anthropic ha rilasciato cache diagnostics in beta pubblica al Code with Claude [1]. Aggiungi un header beta alle tue richieste Messages e l'API ti dice, su ogni risposta, esattamente perché il tuo prompt cache è saltato: il modello è cambiato, il system prompt è andato in drift, i tool sono stati riordinati, o la message history è stata riscritta invece che estesa.
Prima di questo, l'unico segnale era usage.cache_read_input_tokens che scendeva a zero. Sapevi che il cache era saltato, non sapevi perché. Allora facevi bisect. Era il timestamp che inietti nel system block per telemetria? Uno schema Pydantic riordinato? Un turno assistant ri-serializzato fra due richieste? Ogni ipotesi costava un'ora di traffico di replay per confermare o escludere.
Ho fatto girare la nuova beta per 7 giorni su un carico di lavoro che S8 aveva già segnalato come spreco silenzioso del 17-26% del budget API. Risultato: tre regressioni che stavo pagando da marzo, una risolta in 11 minuti, le altre due in meno di un pomeriggio ciascuna. I token letti dal cache sono passati da una media giornaliera del 38% del prefisso cacheable al 92%. Il costo su quel carico è sceso di circa il 30%.
Questa guida spiega cosa restituisce davvero diagnostics, traduce i sei tipi di cache_miss_reason in fix applicabili da un team di una persona, e propone una matrice decisionale per capire quando la beta vale l'header HTTP in più.
Anthropic cache diagnostics featured
Il problema prima del 6 maggio
S8 ha documentato il sintomo originale a marzo 2026: bollette API in crescita del 17-26% senza modifiche al codice, dopo che Anthropic ha silenziosamente tagliato il TTL del prompt cache da 1 ora a 5 minuti. A fine aprile ho fatto la stessa audit sui miei carichi e ho tappato la falla evidente (flag TTL a 1 ora, heartbeat negli agent loop lunghi). Due carichi però continuavano a perdere.
Il segnale diagnostico era un contatore singolo: usage.cache_read_input_tokens. Quando andava a zero su un turno dove il prefisso doveva essere cacheato, sapevi che il cache era saltato. Niente secondo contatore. Nessun campo "reason". Nessun diff. Il bisect era affare tuo.
Il mio bisect più veloce metteva in media 4 ore. Logavo ogni campo di ogni richiesta che passava per l'endpoint sospetto per un giorno, poi facevo diff fra i turni con cache drop e i turni precedenti. Alcune divergenze erano ovvie in cinque minuti (uno UUID stringificato che mi ero dimenticato). Altre richiedevano di seguire l'ordine di serializzazione dello schema dei tool su due versioni di Python prima di scoprire che una delle due non ordinava le chiavi.
Cache diagnostics consegna la risposta insieme alla risposta del modello. Il bisect passa da ore a un round trip di un header HTTP.
L'API in 60 secondi
L'header beta è cache-diagnosis-2026-04-07. Mettilo su ogni richiesta. Al turno 1, opt-in passando "diagnostics": {"previous_message_id": null}. Al turno N+1, passa l'id della risposta del turno N.
if r2.diagnostics and r2.diagnostics.cache_miss_reason:
reason = r2.diagnostics.cache_miss_reason
print(f"miss: {reason.type}, ~{reason.cache_missed_input_tokens} token")
Il campo `diagnostics` sulla risposta ha quattro stati possibili [2]:
- **Campo assente**: hai dimenticato l'header beta.
- **`null`**: turno 1 (niente da confrontare), oppure confronto eseguito e prefissi identici.
- **`{"cache_miss_reason": null}`**: confronto ancora in corso quando la risposta è stata serializzata. Inconcludente, controlla al turno successivo.
- **`{"cache_miss_reason": {...}}`**: divergenza rilevata, con `type` e una stima in `cache_missed_input_tokens`.
In streaming, il campo `diagnostics` arriva sull'evento `message_start`. Anthropic pubblica esempi funzionanti in nove linguaggi compresi TypeScript, Go, Java, C# e Ruby. Cache diagnostics è disponibile solo sulla Claude API: non su Bedrock, non su Vertex.
I fingerprint che Anthropic memorizza sono solo hash e stime di conteggio token, scopati a organizzazione e workspace. Compatibili con ZDR. Sicuri da abilitare in ambienti regolati [2].
---
## I sei tipi di `cache_miss_reason`, tradotti in fix
`cache_miss_reason` è una discriminated union sul campo `type`. Anthropic riporta solo la divergenza più precoce, quindi risolverla può smascherare una seconda. Iterare.
### 1. `model_changed`
Causa: il campo `model` differisce dal turno precedente. Di solito un router, un A/B test o un fallback ha scelto un modello diverso. Il prompt cache è per-modello, quindi ogni switch lo invalida.
Fix: tieni il modello costante dentro una conversazione cacheata. Se hai davvero bisogno di un fallback (rate limit, outage), accetta che il path di fallback non cacheerà e gestiscilo esplicitamente.
### 2. `system_changed`
Causa: il tuo parametro `system` non è byte-stabile. I sospetti tipici:
- `f"Ora corrente: {datetime.utcnow()}"` infilato nel system block per "telemetria".
- UUID per richiesta interpolati per correlare i log.
- Una stringa di versione che viene incrementata a ogni deploy e letta a build time.
Fix: tratta il system prompt come un artefatto compilato. Sposta i dati dinamici nel primo messaggio `user` dopo il tuo cache breakpoint. Il prefisso del cache vuole una stringa congelata, non un template.
### 3. `tools_changed`
Causa: l'array `tools` differisce. O i tool sono stati aggiunti, rimossi o riordinati, oppure il JSON dell'`input_schema` è stato serializzato in modo non deterministico.
Fix: blocca la lista dei tool in un ordine fisso. Se costruisci gli schema da Pydantic, FastAPI o zod, forza l'ordine delle chiavi: `json.dumps(schema, sort_keys=True)` in Python, helper equivalenti altrove. Una delle mie regressioni era uno schema Pydantic il cui ordine dipendeva dall'ordine di import fra due servizi. Gli schema erano semanticamente identici e il cache li trattava come prompt diversi.
### 4. `messages_changed`
Causa: una entry precedente in `messages` è stata alterata, riordinata o rimossa invece di essere appended. Due pattern comuni:
- Ricostruisci il contenuto `assistant` da un database a ogni turno invece di riecheggiare i blocchi originali che Claude ha restituito.
- I blocchi `tool_result` vengono ri-serializzati diversamente al resend (whitespace, ordine chiavi).
Fix: tratta la history come append-only. Riecheggia il contenuto `assistant` verbatim, memorizzalo come Claude lo ha restituito, non rigenerarlo. Stesso discorso per i blocchi `tool_result`.
### 5. `previous_message_not_found`
Causa: nessun fingerprint stored per il `previous_message_id` fornito. Questo NON è prova che la tua richiesta sia cambiata.
Tre sotto-casi:
- La richiesta precedente non aveva l'header beta.
- È stata emessa da workspace o organizzazione diversi.
- È passato troppo tempo (i fingerprint scadono dopo una finestra breve).
Le tecniche che stai leggendo funzionano. Testa subito i tuoi prompt con Prompt Score e vedi il punteggio in tempo reale.
Fix: manda l'header beta su ogni turno. Tieni i turni consecutivi vicini in wall-clock. Se batchi di notte, il cache è già perso e diagnostics non aiuta.
6. unavailable
Causa: modello, system e tool combaciano, ma un altro parametro che influenza il prompt è diverso. I candidati: tool_choice, thinking, context_management, output_config, output_format, o il set di header anthropic-beta attivi. Oppure la conversazione è così lunga che la divergenza è oltre l'orizzonte di confronto di Anthropic.
Fix: tieni costanti i parametri che influenzano il prompt per la vita della conversazione cacheata. Se unavailable persiste e hai già fatto audit dei parametri elencati, ricadi sui controlli manuali documentati da Anthropic nella guida di troubleshooting del prompt caching.
Tutti e quattro i tipi *_changed portano anche cache_missed_input_tokens, una stima di quanto prefisso cacheable è stato perso dopo il punto di divergenza. Anthropic avverte che è un indicatore di magnitudo derivato dalle lunghezze in byte, non un numero di billing, ma permette di prioritizzare quale fix sblocca più spesa.
Sei tipi di cache_miss_reason e i loro fix
La mia audit di 7 giorni
Ho fatto girare la beta contro due carichi ancora segnalati dopo la patch S8: un analizzatore di documenti multi-turno e un agent loop che impiega 8-15 minuti fra un input umano e il successivo.
Giorno 1-2. Rollout della strumentazione. Ho messo l'header beta dietro un feature flag che copriva l'8% del traffico. Zero rischio: diagnostics non blocca né fa fallire mai la richiesta. Il caso peggiore è il campo null o unavailable.
Giorno 3. Prima ragione emersa. system_changed sul 71% dei turni in cache-miss. Causa root: un campo "telemetry_request_id": <uuid> che avevo aggiunto al system block a marzo apposta per correlare i log delle richieste. L'ho spostato nel primo messaggio user dopo il cache breakpoint. 11 minuti incluso il deploy. I cache read su quel carico sono tornati al baseline pre-marzo entro un'ora, man mano che i vecchi fingerprint scadevano e se ne formavano di nuovi.
Giorno 4-5.tools_changed sull'analizzatore di documenti. Due servizi facevano POST allo stesso endpoint Anthropic con schema costruiti dagli stessi modelli Pydantic, ma l'ordine di inserimento del dict in Python differiva fra loro per via di un refactor che aveva pinnato un servizio a una pydantic più vecchia. Gli schema si serializzavano in JSON con chiavi in ordini diversi. Ho forzato sort_keys=True su entrambi. Circa il 14% dei restanti cache miss su quel carico è sparito.
Giorno 6.messages_changed sull'agent loop. Stavo ricostruendo i blocchi di contenuto assistant da una colonna Postgres invece di riecheggiare quanto Claude aveva restituito. Il testo era identico, ma stavo ricostruendo blocchi strutturati da una riga di database perdendo l'ordine originale dei blocchi. Sono passato ad append-only e il 9% finale di miss si è chiarito.
Giorno 7. Diff vs baseline del giorno 1 sui due carichi:
Metrica
Prima
Dopo
Cache hit rate medio per turno
38%
92%
cache_creation_input_tokens al giorno
4,1M
0,7M
Stima spesa mensile su quel carico
$610
$420
Circa il 30% di taglio sulla bolletta dei carichi strumentati. Nessuno dei tre fix era un ridisegno. Tutti e tre erano bug latenti che il mio vecchio loop diagnostico non sapeva indicare.
Matrice decisionale: quando attivarla
È una beta. Nomi dei campi e semantica possono ancora cambiare. Costa un header in più su ogni richiesta e un po' di codice per portare avanti previous_message_id nel loop conversazionale. Non ogni carico vale l'install.
Usa cache diagnostics se...
Salta se...
Lo spend cache-related supera il 10% della bolletta API mensile
Non usi affatto il prompt caching
Le conversazioni o gli agent loop sono di 3+ turni
Fai solo completion one-shot
Usi tool, system prompt strutturati o prompt da template
I tuoi prompt sono semplice testo user-only
Spedisci direttamente sulla Claude API
Vai via Bedrock o Vertex (non supportato)
Hai almeno un carico con cali inspiegati di cache_read_input_tokens
I cache read sono già stabili al 90%+
Se tre o più voci della colonna sinistra si applicano a te, l'install è di un pomeriggio e il diagnostic resta attivo in permanenza. Se due o meno, probabilmente non vale il code path fino a quando la feature non esce di beta.
Matrice decisionale per cache diagnostics
Cosa cambia per i team piccoli
Tre take dal farlo girare per una settimana.
Il diagnostico consegna la risposta invece del sintomo. Bisect-are una regressione del prompt cache richiedeva ore di traffico di replay. Adesso è un header e un previous_message_id. Il costo del bisect su una regressione mi è sceso da 4 ore a meno di 10 minuti. Se hai mai pagato il 20% in più per niente perché un UUID era finito nel blocco sbagliato, questo è l'install che chiude la classe di bug.
I prompt di produzione vanno tenuti byte-stabili by design, non per caso. Il system prompt e lo schema dei tool non sono testo libero: sono artefatti compilati che il cache tratta come chiavi. Trattali così. Ordina le chiavi degli schema. Pinna le versioni Pydantic fra servizi che condividono endpoint. Sposta ogni "freshness" nel primo messaggio user.
Il costo dell'install è un header HTTP. Cache diagnostics è ZDR-eligible: solo hash e conteggi di token, nessun testo di prompt memorizzato. Sicuro in ambienti regolati. Nessuna logica di retry da mantenere. Se il confronto è unavailable o pending, la tua richiesta si completa lo stesso normalmente.
Se sei un team di una persona o cinque e la tua bolletta Anthropic è una voce di spesa significativa, questo è un install di un'ora che mi ha restituito il 30% sul carico. Due delle tre regressioni non le avrei mai trovate senza.
Fonti
[1] Anthropic Release Notes, Claude Platform, maggio 2026. Annuncio della beta pubblica di cache diagnostics al Code with Claude, 6 maggio 2026.
Vuoi sapere quali dei tuoi prompt stanno sprecando token prima che l'API te lo dica? Valutali con Keep My Prompts e vedi quali dei 6 criteri di qualità i prompt del tuo team già passano o falliscono.