🧊 System prompt congelado
O system prompt (o texto de instruções antes da conversa) fica logo na frente do prefixo. Se você interpola data/hora, modo ou nome do usuário ali, cada requisição tem bytes diferentes no começo — e invalida tudo. A regra é: system congelado; contexto dinâmico vai depois, nas messages.
Legenda: o dinâmico não some — ele muda de lugar. Sai da frente (system) e vai pro fim (messages), onde não quebra o prefixo.
System sem variáveis
Vai pro fim das messages
System é quase posição 0
Basta pra invalidar
🔤 Tools determinísticas
A lista de tools é renderizada na posição 0 — antes de tudo. Se você a monta de um set/dict sem ordenar, a ordem varia entre requests e o cache nunca bate. Serialize sempre na mesma ordem: ordene por nome.
✗ Ordem variável
tools = [t for t in registry] # registry é um set/dict # ordem muda entre requests → miss
✓ Ordem estável
tools = sorted(registry, key=lambda t: t["name"]) # mesma ordem sempre → prefixo idêntico # cache_read para de ser 0
Objetivo
Fazer o Claude Code apontar o que, no seu system prompt e na montagem das tools, está impedindo o cache — e reescrever pra maximizar o hit.
Prompt (cole no Claude Code)
Revise meu system prompt e a montagem de `tools` em <caminho/do/arquivo>. Aponte o que está impedindo o prompt caching: 1) interpolação dinâmica no system (data/hora, nome do usuário, modo, uuid, qualquer coisa que muda entre requests); 2) ordem não-determinística das tools (montadas de set/dict sem sort). Depois reescreva: (a) congele o system e mova o dinâmico pro fim das messages; (b) serialize as tools em ordem estável (ordene por nome). Mostre o diff e explique por que cada mudança preserva o prefixo.
Como verificar
Rode duas requisições iguais depois da mudança e confira o usage: cache_read_input_tokens deve deixar de ser 0 a partir da 2ª. Se ainda vier 0, sobrou um invalidator no prefixo (veja o tópico 5).
Tools vêm antes de tudo
Ordene por nome
Ordem varia = miss
Mesmos bytes = hit
🚫 Não trocar modelo no meio
Cache é por modelo. Trocar de modelo no meio invalida tudo — o prefixo cacheado de um modelo não serve pro outro. Precisa de um modelo mais barato pra uma subtarefa? Use um subagente, mantendo o loop principal num modelo só.
Legenda: o loop roxo fica num modelo só; o subagente ciano usa outro modelo sem tocar no cache do pai.
Por que subagente resolve
O subagente tem o próprio prefixo e o próprio cache. Ele roda a subtarefa no modelo barato e devolve o resultado como texto — o loop principal continua no mesmo modelo, com o cache intacto.
Cache não cruza modelos
Invalida tudo
Modelo barato à parte
Mantém o cache quente
🌿 Fork reusa o prefixo do pai
Uma operação lateral — um resumo, um subagente — que remonta system/tools/model diferentes perde o cache do pai. Para reaproveitar o prefixo quente, o fork precisa copiar system/tools/model verbatim e só anexar o específico no fim.
Legenda: o fork ciano herda o prefixo do pai (só grava o sufixo); o fork vermelho remonta e paga tudo de novo.
✓ Fork que reusa
- ✓Copia
systembyte a byte - ✓Mantém a mesma lista de
toolse o mesmomodel - ✓Anexa a instrução específica só no fim
✗ Fork que perde
- ✗Escreve um system novo "enxuto" pro resumo
- ✗Passa outro conjunto de tools ou outro modelo
- ✗Resultado: prefixo diferente, cache do zero
Copie system/tools/model
Anexe o específico
Reusa o cache do pai
= pagar do zero
🔎 Checklist de silent invalidators
Um silent invalidator não dá erro — só faz cache_read_input_tokens ficar sempre 0. Antes de culpar o modelo, varra tudo que alimenta o prefixo e cace estes suspeitos.
Legenda: qualquer fonte de variação no que vira tools/system muda o prefixo — e derruba o cache em silêncio.
Os 6 suspeitos de sempre
- ☐
datetime.now()/Date.now()no system - ☐
uuidou id aleatório no prefixo - ☐
json.dumpssemsort_keys=True - ☐Iterar um
set(ordem não garantida) - ☐
toolsmontadas por usuário/modo - ☐Seção condicional no system (
if flag: system += …)
Sintoma → causa
cache_read_input_tokens sempre 0 em requests repetidos = tem um invalidator. Faça o diff dos bytes do prefixo entre dois requests iguais e o byte que muda é o culpado (o tópico 4 do módulo 3.3 tem o prompt pronto pra isso).
Não dá erro
O sintoma único
Varra o prefixo
Acha o byte culpado
🔥 Pré-aquecimento e TTL 1h
Uma requisição com max_tokens: 0 ao subir o app grava o cache do prefixo e retorna na hora — tira a latência do 1º request real. Só vale se três coisas forem verdade; e, pra tráfego com pausas longas, existe o TTL de 1h (write ~2×).
Legenda: o pré-aquecimento paga o write antes do tráfego, pra o primeiro usuário não sentir a latência da gravação.
✓ Pré-aquecer vale quando
- ✓A 1ª latência é visível pro usuário
- ✓O prefixo é grande (vale a gravação)
- ✓Há um momento antes do tráfego (boot/deploy)
TTL de 1h
- •Write mais caro (~2× em vez de ~1,25×)
- •Vale pra tráfego com gaps > 5 min
- •Segura o cache quente entre pausas longas
Grava sem gerar resposta
Boot/deploy
Gaps > 5 min
Write de 1h
Resumo do módulo
max_tokens: 0 no boot; 1h pra gaps longos.Próximo módulo
3.3 — Prompts prontos (copy-run): o kit de 6 prompts pra auditar e corrigir cache no Claude Code.