🪜 Jerarquía de 3 niveles
La invalidación sigue una jerarquía de 3 tiers: un cambio solo
invalida el su nivel y los de abajo. Como el orden es
tools → system → messages, tocar más el inicio tiene un mayor radio de daño.
Qué es
Los tres tiers son capas en cascada. El modelo es la raíz: cambiarlo invalida todo. Después vienen tools, system y messages, cada uno se invalida a sí mismo y a los que vienen después.
Por qué aprenderlo
Porque permite calcular el "radio de daño" de cualquier cambio antes de hacerlo: cambiar un mensaje cuesta poco; modificar tools o cambiar el modelo cuesta el prefijo entero.
Cuanto más al principio (tools) ocurre el cambio, mayor es el efecto en cascada. Un mensaje nuevo al final casi no cuesta.
Conceptos clave
| Cambio | Lo que lo invalida |
|---|---|
| cambiar de modelo | todo (tools + system + messages) |
cambiar tools | todo (tools + system + messages) |
cambiar system | system + messages |
| cambiar mensaje | solo messages |
🎚️ tool_choice / thinking on-off
No todo lo que cambia por solicitud rompe el caché. Cambiar tool_choice en cada solicitud, o activar/desactivar thinking,
mantiene el caché de tools+system. Son parámetros que
no entran en el prefijo almacenable en caché — no tienes que preocuparte por ellos.
Qué es
tool_choice e thinking son controles de comportamiento del request, no bytes del prefijo. Cambiarlos no modifica la coincidencia del prefijo.
Por qué aprenderlo
Porque evita la paranoia. Es fácil pensar que "cualquier cosa que cambie por request derriba el caché"; entonces dejas de usar recursos útiles por un miedo infundado.
✓ Puede variar libremente
- ✓
tool_choicediferente por request. - ✓Activar/desactivar
thinking.
✗ Sigue provocando caídas
- ✗Cambiar la lista
tools. - ✗Editar el system prompt.
Conceptos clave
tool_choice no entra en el prefijo.
thinking on/off conserva tools+system.
Lo que lo derriba es contenido del prefijo.
Sin paranoia: no todos los parámetros fallan.
🔭 20-block lookback
Cada breakpoint busca la entrada anterior revisando como máximo 20 bloques hacia
atrás. Un turno que agrega más de 20 bloques — loops con
muchos tool_use/tool_result —
puede hacer que el siguiente turno hacer que el caché falle silenciosamente.
Qué es
El lookback es la ventana de búsqueda del breakpoint: solo encuentra el prefijo almacenado en caché si está dentro de los 20 bloques anteriores. Si queda más atrás, no lo encuentra, aunque la caché exista.
Por qué aprenderlo
Porque es la causa "invisible" de fallos de caché en agentes que hacen muchas llamadas a herramientas por turno. El prefijo no cambió, pero el breakpoint no llega a la entrada anterior.
Recreación ilustrativa. Lo importante: en loops largos, inserta un breakpoint intermedio antes de superar los 20 bloques.
Consejo práctico
Si tu agente hace muchas llamadas a herramientas por turno y el caché «desaparece» sin que haya cambiado el prefijo, sospecha del lookback. Un breakpoint intermedio cada ~15 bloques mantiene la entrada dentro de la ventana.
Conceptos clave
Lookback = 20 bloques hacia atrás.
Turno con >20 bloques = riesgo de miss.
Común en loops de tool_use/tool_result.
Fix: un breakpoint cada ~15 bloques.
👻 Invalidadores silenciosos
Invalidadores silenciosos son bytes que cambian en el prefijo sin que te des cuenta.
Los clásicos: datetime.now()/Date.now()/uuid
en system; json.dumps sin sort_keys=True;
iterar set; herramientas montadas por el usuario; sección condicional en el system
(if flag: system += ...).
Qué es
Son fuentes de no determinismo que alteran el prefijo en cada solicitud, incluso con el caché caliente. A diferencia del write frío (por tiempo), aquí el problema es que el contenido cambia.
Por qué aprenderlo
Porque son la causa n.º 1 de que el caché "nunca funcione". El síntoma siempre es el mismo: cache_read_input_tokens siempre 0.
✗ Invalidantes silenciosos comunes
- ✗
datetime.now()/Date.now()/ uuid en system. - ✗
json.dumpssinsort_keys=True. - ✗Iterar
set(orden variable). - ✗Tools montadas por el usuario.
- ✗Sección condicional en el system (
if flag: system += ...).
✓ Correcciones
- ✓Inyecta fecha/hora en las
messages, no en el system. - ✓Serializa con
sort_keys=True. - ✓Ordena con
sorted(...)antes de montar. - ✓Congela el system; mueve lo dinámico al final.
Síntoma
cache_read_input_tokens siempre 0 al repetir el mismo prefijo. Es la señal de que hay un invalidator silencioso activo.
🌐 Timing concurrente (fan-out)
La entrada del caché solo queda legible DESPUÉS que la 1.ª respuesta empieza a transmitir. Entonces N solicitudes paralelos con el mismo prefijo pagan todos a precio completo — ninguno ha visto la caché del otro todavía.
Qué es
En el fan-out (lanzar varios agentes/solicitudes a la vez), el primer request todavía no terminó de guardar el prefijo cuando los demás se inician. Todos guardan desde cero.
Por qué aprenderlo
Porque lanzar todo junto parece más rápido, pero desperdicia el caché compartido. Un pequeño cambio en el orden de lanzamiento permite ahorrar mucho.
Fix: enviar 1, esperar el 1.er token (el caché queda legible) y solo entonces lanzar los N-1, que ahora leen desde el caché a bajo costo.
Conceptos clave
Entrada legible solo después del 1.º stream.
N en paralelo con el mismo prefijo = todos 1×.
Fix: 1 → esperar el 1.er token → N-1.
Aplica al fan-out / a los subagentes paralelos.
🧪 Verificar con usage
El objeto es el instrumento de medición usage en la respuesta:
cache_read_input_tokens e
cache_creation_input_tokens.
Cero en solicitudes repetidas = tiene un invalidator. Entonces haces
diff de los bytes del prompt entre dos requests iguales para encontrar el byte
que cambia.
Qué es
Auditar con usage es comparar los campos de caché entre dos solicitudes que deberían ser idénticas. Si la lectura no aumenta, algo está cambiando en el prefijo.
Por qué aprenderlo
Porque es la diferencia entre adivinar y demostrar. Con el diff de bytes localizas el invalidator silencioso exacto: la fecha, el uuid, la clave fuera de orden.
Ejemplo copy-run — auditar silent invalidators del pipeline
Objetivo: hacer que Claude Code revise el código que arma el prompt y enumere cada silent invalidator + cómo corregirlo.
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).
Cómo verificar: después de aplicar las correcciones, ejecuta el mismo pedido dos veces seguidas y compara el usage. O cache_read_input_tokens debe dejar de ser 0 en el 2.º request. Si todavía queda en 0, haz el diff de los bytes del prompt entre los dos requests para encontrar qué cambió.
Conceptos clave
usage = instrumento de medición.
Read 0 repetido = invalidator activo.
El diff de bytes localiza el cambio exacto.
Demostrar > adivinar: mide siempre.
Resumen del módulo
usage.Siguiente ruta:
Trilha 3 — Boas práticas avançadas (cache vs compactação, arquitetura pró-cache, prompts prontos)