Pular para o conteúdo
MÓDULO 2.3

🔎 Invalidação a fundo

A anatomia completa da invalidação: a hierarquia de 3 tiers, o que não derruba o cache, o 20-block lookback, os silent invalidators, o timing do fan-out e como auditar com usage.

6
Tópicos
~35
Minutos
Técnico
Nível
Prática
Tipo
0% 0 de 6
1

🪜 Hierarquia de 3 tiers

A invalidação segue uma hierarquia de 3 tiers: uma mudança só invalida o tier dela e os de baixo. Como a ordem é tools → system → messages, mexer mais no começo tem raio de dano maior.

O que é

Os três tiers são camadas em cascata. O modelo é a raiz: trocá-lo invalida tudo. Depois vêm tools, system e messages, cada um invalidando a si e ao que vier depois.

Por que aprender

Porque permite calcular o "raio de dano" de qualquer mudança antes de fazê-la: alterar uma mensagem é barato; mexer em tools ou trocar o modelo custa o prefixo inteiro.

tier 1 · tools (posição 0) tier 2 · system tier 3 · messages muda tools → invalida os 3 muda system → system + messages muda mensagem → só messages trocar o modelo = raiz de tudo → reset total

Quanto mais no começo (tools) a mudança acontece, maior a cascata. Uma mensagem nova no fim quase não custa.

Conceitos-chave

MudançaO que invalida
trocar modelotudo (tools + system + messages)
mudar toolstudo (tools + system + messages)
mudar systemsystem + messages
mudar mensagemsó messages
2

🎚️ tool_choice / thinking on-off

Nem tudo que muda por request quebra o cache. Trocar tool_choice a cada request, ou ligar/desligar thinking, mantém o cache de tools+system. São parâmetros que não entram no prefixo cacheável — não precisa se preocupar com eles.

O que é

tool_choice e thinking são controles de comportamento do request, não bytes do prefixo. Alterá-los não muda o casamento de prefixo.

Por que aprender

Porque evita paranoia. É fácil achar que "qualquer coisa que eu mude por request derruba o cache" — e aí você deixa de usar recursos úteis por medo infundado.

✓ Pode variar à vontade

  • tool_choice diferente por request.
  • Ligar/desligar thinking.

✗ Continua derrubando

  • Mudar a lista tools.
  • Editar o system prompt.

Conceitos-chave

tool_choice não entra no prefixo.

thinking on/off preserva tools+system.

O que derruba é conteúdo do prefixo.

Sem paranoia: nem todo param quebra.

3

🔭 20-block lookback

Cada breakpoint procura a entrada anterior olhando no máximo 20 blocos pra trás. Um turno que adiciona mais de 20 blocos — loops com muitos tool_use/tool_result — pode fazer o próximo turno errar o cache silenciosamente.

O que é

O lookback é a janela de busca do breakpoint: ele só encontra o prefixo cacheado se ele estiver dentro dos 20 blocos anteriores. Passou disso, ele não acha — mesmo com o cache existindo.

Por que aprender

Porque é a causa "invisível" de miss em agentes que fazem muitas chamadas de ferramenta por turno. Nada mudou no prefixo, mas o breakpoint não alcança a entrada anterior.

entrada de cache anterior (fora da janela) ✗ não alcançável janela de lookback = últimos 20 blocos breakpoint loop com > 20 tool_use/tool_result empurra a entrada anterior pra fora → miss silencioso Fix: breakpoint intermediário a cada ~15 blocos

Recriação ilustrativa. O que importa: em loops longos, insira um breakpoint intermediário antes de estourar os 20 blocos.

Dica prática

Se seu agente faz muitas chamadas de ferramenta por turno e o cache "some" sem que o prefixo tenha mudado, suspeite do lookback. Um breakpoint intermediário a cada ~15 blocos mantém a entrada dentro da janela.

Conceitos-chave

Lookback = 20 blocos pra trás.

Turno com >20 blocos = risco de miss.

Comum em loops de tool_use/tool_result.

Fix: breakpoint a cada ~15 blocos.

4

👻 Silent invalidators

Silent invalidators são bytes que mudam no prefixo sem você perceber. Os clássicos: datetime.now()/Date.now()/uuid no system; json.dumps sem sort_keys=True; iterar set; tools montadas por usuário; seção condicional no system (if flag: system += ...).

O que é

São fontes de não-determinismo que alteram o prefixo a cada request, mesmo com o cache quente. Diferente do write frio (por tempo), aqui o problema é o conteúdo mudando.

Por que aprender

Porque são a causa nº 1 de cache que "nunca funciona". O sintoma é sempre o mesmo: cache_read_input_tokens sempre 0.

✗ Silent invalidators comuns

  • datetime.now() / Date.now() / uuid no system.
  • json.dumps sem sort_keys=True.
  • Iterar set (ordem variável).
  • Tools montadas por usuário.
  • Seção condicional no system (if flag: system += ...).

✓ Correções

  • Injete data/hora nas messages, não no system.
  • Serialize com sort_keys=True.
  • Ordene com sorted(...) antes de montar.
  • Congele o system; mova o dinâmico pro fim.

Sintoma

cache_read_input_tokens sempre 0 repetindo o mesmo prefixo. É o carimbo de que tem um silent invalidator ativo.

5

🌐 Timing concorrente (fan-out)

A entrada de cache só fica legível DEPOIS que a 1ª resposta começa a streamar. Então N requests paralelos com o mesmo prefixo pagam todos preço cheio — nenhum viu o cache do outro ainda.

O que é

No fan-out (disparar vários agentes/requests de uma vez), a gravação do prefixo pelo primeiro request ainda não terminou quando os outros partem. Todos gravam do zero.

Por que aprender

Porque disparar tudo junto parece mais rápido, mas joga fora o cache compartilhado. Uma pequena mudança na ordem do disparo economiza muito.

✗ N ao mesmo tempo → todos cheios plano req · 1× req · 1× req · 1× ✓ 1 primeiro → depois os N-1 req 1 grava (espere o 1º token) lê · 0,1× lê · 0,1× lê · 0,1×

Fix: mandar 1, esperar o 1º token (o cache fica legível), e só então disparar os N-1 — que agora leem barato.

Conceitos-chave

Entrada legível só após o 1º stream.

N paralelos com mesmo prefixo = todos 1×.

Fix: 1 → esperar 1º token → N-1.

Vale para fan-out / subagentes paralelos.

6

🧪 Verificar com usage

O instrumento de medição é o objeto usage na resposta: cache_read_input_tokens e cache_creation_input_tokens. Zero em requests repetidos = tem invalidator. Aí você faz diff dos bytes do prompt entre dois requests iguais para achar o byte que muda.

O que é

Auditar com usage é comparar os campos de cache entre dois requests que deveriam ser idênticos. Se o read não sobe, algo no prefixo está mudando.

Por que aprender

Porque é a diferença entre chutar e provar. Com o diff de bytes você localiza o silent invalidator exato — a data, o uuid, a chave fora de ordem.

Exemplo copy-run — auditar silent invalidators do pipeline

prompt · cole no Claude Code

Objetivo: fazer o Claude Code varrer o código que monta o prompt e listar cada silent invalidator + como corrigir.

Audite o pipeline em <caminho da pasta/arquivo que monta
o prompt>. Procure especificamente, no que alimenta o
PREFIXO (tools + system), por:

  - datetime.now() / Date.now() / uuid interpolados no system
  - json.dumps(...) SEM sort_keys=True
  - iteração de set (ordem não-determinística)
  - montagem de `tools` a partir de set/dict sem sorted(...)
  - seções condicionais no system (if flag: system += ...)

Para cada ocorrência, aponte:
  1. arquivo e linha;
  2. por que quebra o cache (muda os bytes do prefixo);
  3. a correção (mover o dinâmico para as messages,
     sort_keys=True, sorted(...), congelar o system).

Como verificar: depois de aplicar as correções, rode o mesmo pedido duas vezes seguidas e compare o usage. O cache_read_input_tokens deve deixar de ser 0 no 2º request. Se ainda ficar 0, faça o diff dos bytes do prompt entre os dois requests para achar o que mudou.

Conceitos-chave

usage = instrumento de medição.

Read 0 repetido = invalidator ativo.

Diff de bytes localiza a mudança exata.

Provar > chutar: meça sempre.

Resumo do módulo

Hierarquia de 3 tiers — mudança invalida o tier dela e os de baixo.
tool_choice / thinking não derrubam — não entram no prefixo.
20-block lookback — loops longos furam o cache; fix: breakpoint a cada ~15 blocos.
Silent invalidators — data, uuid, dumps sem sort, set, condicional no system.
Fan-out e usage — dispare 1 antes dos N-1; audite com usage.

Próxima trilha:

Trilha 3 — Boas práticas avançadas (cache vs compactação, arquitetura pró-cache, prompts prontos)