TL;DR
- El "90%" de Spotify es la media de escenarios de lectura masiva medidos en tokens de entrada estimados sobre un monorepo Java. El post no da ninguna cifra en dólares ni puntuación de calidad.
- Reconstruido en Claude Code estándar (un hook PreToolUse, dos subagentes baratos, una regla de enrutado de tres líneas) y medido sobre Fastify en cuatro escenarios y 16 ejecuciones, el patrón recortó el contexto del modelo principal un 59.6% y el coste total un 33.1%.
- Los hooks de denegación se activaron cero veces en las ejecuciones medidas. El ahorro vino de la regla de enrutado en CLAUDE.md; los hooks son la red de seguridad para el día en que el modelo la ignore.
- La delegación fue más lenta todas las veces, +65.3% de tiempo real de media. En el escenario pequeño de escribir tests costó un 2.6% más.
- Dos trampas: los hooks también se activan dentro de los subagentes, así que exime a tus workers, y las lecturas por rango con
sed -npasan directas por un hook que solo vigilacat,headytail. - El resumen del lector Haiku llevó errores factuales en dos de las ocho ejecuciones delegadas. Conserva el turno de verificación del modelo principal.
Qué dicen las mediciones
El plugin de Spotify, Shunt, desvía el trabajo masivo del modelo principal mediante dos "modos": un lector masivo y un escritor de código, ambos con Gemini 2.5 Flash en los ejemplos, y el campo model acepta cualquier modelo configurado en la instancia de Portal s1. El enrutado tiene tres capas. Un hook check-file-size salta en cada Read y bloquea los archivos que superan un umbral de líneas configurable, 350 por defecto, y remite al modelo a la skill de lectura masiva; un hook check-bash-read atrapa cat, head, tail, less y more sobre archivos grandes, mientras los comandos con pipe pasan s1. El código de los hooks y las dos skills están en el repositorio público s2, y la comprobación de tamaño se puede leer por separado s3. Los modos viven en Portal, la plataforma interna de Spotify, y por eso el plugin no se puede ejecutar fuera de la empresa tal como se distribuye s4.
La afirmación del benchmark es endeble. Spotify probó cuatro escenarios en un monorepo Java, "midiendo los tokens que Claude consumiría leyendo los archivos directamente frente a consumir el resumen del lector masivo", y reporta un ahorro medio de lectura masiva de alrededor del 90% s1. El propio post dice que el escenario de escritura de código es más difícil de medir en tokens, que los resúmenes de los workers no incluyen números de línea fiables y por tanto la edición no se puede delegar, que el worker no vio un bug sutil de thread-safety que el modelo principal sí atrapó, y que cada delegación añade de 10 a 30 segundos, con Portal limitando una invocación a 30 segundos s1. El hilo de Hacker News planteó las mismas dudas sobre qué mide el 90% s7.
La reconstrucción sustituye los modos de Portal por dos subagentes de Claude Code cuyos archivos de definición fijan el modelo: un lector Explore en Haiku y un escritor de código en Sonnet s6. La denegación es un hook PreToolUse que devuelve la decisión deny en el formato JSON de hook actual s5. El repositorio de prueba fue fastify/fastify en el commit ac28821d, 294 archivos .js/.ts, 78270 líneas, 63 archivos de más de 350 líneas. Ids de modelo según el JSON de sesión: conversación principal claude-opus-5[1m], lector claude-haiku-4-5-20251001, escritor claude-sonnet-5. Cada escenario se ejecutó dos veces por configuración, 16 ejecuciones medidas, sesiones claude -p de un solo turno con ajustes solo de proyecto para que ambos lados tuvieran un prompt de sistema idéntico s5.
Donde el patrón ganó: S2, una pregunta de grafo de llamadas sobre tres archivos, lib/route.js (691 líneas), lib/reply.js (1090) y lib/request.js (398), pasó de un contexto principal medio de 357165.5 tokens a 73440.0 (-79.4%) y de 0.5810500000000001 USD a 0.21823605000000001 USD (-62.4%). Donde no: S4, escribir un test para una fuente de 45 líneas a partir de una referencia de 19, costó 0.29465575 USD sin delegación y 0.3022213 USD con ella (+2.6%), porque Sonnet es un segundo contexto completo (13004 a 18729 tokens de cache-read) y el modelo principal aun así releyó el archivo generado y ejecutó el test s6.
Tres hallazgos importan más que los porcentajes. Primero, los hooks se activaron cero veces en las 16 ejecuciones medidas: con la regla de enrutado en CLAUDE.md, el modelo principal ejecutó wc -l y delegó por su cuenta. La única denegación observada vino de una ejecución de verificación sin CLAUDE.md, donde al modelo se le denegó Read, luego cat -n, y respondió solo con grep -n sin llamar nunca a la herramienta Agent s5. Segundo, los hooks se ejecutan dentro de los subagentes: en dos ejecuciones descartadas el propio lector Haiku fue denegado por la comprobación de tamaño y recurrió a lecturas por trozos con offset/limit. La solución es una salida case "$agent_type" in Explore|code-writer) exit 0 al inicio del hook, con el nombre del campo confirmado en el stdin registrado s5. Tercero, en la configuración base el modelo principal nunca usó la herramienta Read. Leyó cada archivo por Bash (cat -n, sed -n '1,200p', sed -n '200,560p'), así que un hook que solo vigila Read no atrapa nada, y un hook de Bash que solo coincide con cat, head y tail sin pipe sigue dejando pasar los rangos sed -n s3.
La calidad se comprobó contra la fuente con grep. La base produjo números de línea erróneos en una ejecución de S2 (volcó los archivos con sed -n sin números de línea y contó a mano). La configuración delegada produjo tres errores factuales en la otra ejecución de S2 y dos en una de S3, todos atribuibles a tomar el resumen de Haiku al pie de la letra: llamadores equivocados de buildRequest/buildReply, una constante no exportada listada como export, un iterador cubierto marcado como no cubierto. Donde el modelo principal gastó tokens de salida en reverificar con grep (S3, 4534 a 4738 tokens de salida), las respuestas se sostuvieron s6. En el escenario de escribir tests, todos los archivos generados pasaron: 12/12, 6/6, 7/7 y 10/10 tests. Un reporte de Reddit muestra el modo de fallo relacionado de un modelo principal que lanza workers con el modelo equivocado cuando nada lo fija s8, que es lo que previenen el hook require-model y el campo model: de los archivos de agente.
Mediciones
Medias de las 2 ejecuciones por celda. "Main context" es input + cache_creation + cache_read tokens facturados al modelo principal durante la sesión, la cifra comparable a los "tokens en el contexto principal" de Spotify. A = Claude Code estándar, B = hook + subagentes + regla en CLAUDE.md.
| escenario | contexto principal A | contexto principal B | cambio | salida principal A | salida principal B | cambio | coste total A | coste total B | cambio | duración A s | duración B s | cambio |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| S1 | 88693.0 | 51551.5 | -41.9% | 1424.5 | 1060.5 | -25.6% | 0.13910675 | 0.08653685 | -37.8% | 21.817500000000003 | 43.799499999999995 | +100.8% |
| S2 | 357165.5 | 73440.0 | -79.4% | 3835.0 | 2555.5 | -33.4% | 0.5810500000000001 | 0.21823605000000001 | -62.4% | 51.637 | 129.036 | +149.9% |
| S3 | 303807.5 | 114135.5 | -62.4% | 6192.0 | 4636.0 | -25.1% | 0.451037 | 0.3738534 | -17.1% | 93.321 | 124.64099999999999 | +33.6% |
| S4 | 143431.5 | 121818.0 | -15.1% | 5275.5 | 3340.0 | -36.7% | 0.29465575 | 0.3022213 | +2.6% | 65.7125 | 86.857 | +32.2% |
| all 4 | 223274.375 | 90236.25 | -59.6% | 4181.75 | 2898.0 | -30.7% | 0.366462375 | 0.2452119 | -33.1% | 58.122 | 96.08337499999999 | +65.3% |
Protocolo: dos clones superficiales idénticos byte a byte de fastify/fastify en ac28821d; repo-shunt añade .claude/ (ajustes, dos archivos de agente, tres hooks) y una regla de enrutado en CLAUDE.md, nada más. Cada sesión: claude -p "<prompt>" --output-format json --setting-sources project --strict-mcp-config con una config MCP vacía, sin --model, timeout de 600 s. Cuatro prompts, idénticos en ambos lados: S1 exports de lib/reply.js, S2 grafo de llamadas entre tres archivos de lib, S3 métodos de lib/hooks.js frente a la cobertura de test/hooks.test.js, S4 escribir test/head-route.test.js siguiendo test/noop-set.test.js. Las cifras se leen de modelUsage y total_cost_usd del JSON de sesión, sin redondear. Las respuestas se comprobaron contra la fuente con grep; los tests generados se ejecutaron con node --test.
Haz esto el lunes
- Ejecuta
wc -lsobre tu repo y cuenta los archivos de más de 350 líneas. Si la cuenta es casi cero, para aquí: el umbral existe porque delegar cuesta más de lo que ahorra en archivos pequeños. - Añade a tu CLAUDE.md una regla de enrutado de tres líneas: los archivos por encima del umbral van a un subagente lector, el código que sigue un patrón va a un subagente escritor, la depuración y la arquitectura se quedan con el modelo principal. En las mediciones esta regla hizo todo el trabajo.
- Crea
.claude/agents/Explore.mdconmodel: haikuy.claude/agents/code-writer.mdconmodel: sonneten el frontmatter, para que el modelo del worker quede fijado en el archivo y no se deje al orquestador. - Escribe el hook PreToolUse sobre Read como red de seguridad, devolviendo la decisión deny en el formato JSON de hook actual, y haz que sus primeras líneas hagan exit 0 cuando
agent_typesea uno de tus workers. - Amplía el hook de Bash más allá de
cat,headytail: cubre los rangossed -nycat -nsobre archivos grandes, deja pasar los comandos con pipe y grep. - Ejecuta una pregunta real con y sin la carpeta
.claude/, conclaude -p --output-format json, y comparatotal_cost_usdyduration_ms, no solo la columna de entrada. - Comprueba dos respuestas delegadas contra la fuente con grep antes de fiarte del resumen del lector; presupuesta el turno de verificación del modelo principal como parte del coste.
- Mide también una sesión de varios turnos: los resultados de un solo turno dejan el contexto principal en 50k a 119k tokens frente a 84k a 414k sin delegación, así que la segunda pregunta debería empezar más barata, pero eso no se midió.
Para ir más lejos
- Lee el formato de hook y el campo
agent_typeen la referencia oficial antes de copiar un hook de un blog; la forma del deny y los campos en stdin son lo que hace posible la exención de subagentes s5. - La documentación de subagentes cubre el campo de frontmatter
modely las restricciones de herramientas, que es como se mantiene a un lector de solo lectura y barato s6. - La sección "What doesn't work" de Spotify es la parte más útil del post: sin edición delegada (sin números de línea fiables en los resúmenes), sin razonamiento delegado (un bug de thread-safety no detectado), idas y vueltas de 10 a 30 segundos s1.
- El README de Shunt muestra la estructura de tres capas (hooks, scripts, skills) y los textos de las skills que le dicen al modelo cuándo delegar; la prosa de las skills es lo que vale la pena adaptar, no el hook s2.
- Los modos de Portal son una capa de configuración sobre un modelo más un prompt de sistema; la misma idea se traslada a un archivo de agente de Claude Code con un campo
models4. - El hilo de Hacker News es donde se plantearon primero las dudas de medición, y es una buena checklist de qué preguntar ante cualquier afirmación de ahorro de tokens s7.
- Un hilo de Reddit documenta a un orquestador lanzando cinco workers con su propio modelo caro; fija el modelo en el archivo de agente y, si quieres una garantía firme, deniega las llamadas a Agent sin campo
models8.
Fuentes
- Portal by Spotify cut my Claude Code token usage by 90%, Spotify Engineering. Por qué leerlo: la afirmación original, el diseño en tres capas y una sección honesta de limitaciones que desinfla el titular.
- Shunt plugin (spotify/portal-ai-plugins), GitHub. Por qué leerlo: los hooks, scripts y textos de skills reales, lo bastante cortos para leerlos enteros.
- check-file-size hook source, GitHub. Por qué leerlo: la comprobación de 350 líneas en unas pocas líneas de shell, la plantilla para tu propio deny.
- Portal Modes documentation, Spotify. Por qué leerlo: qué es un "modo", para ver por qué equivale a un archivo de subagente.
- Claude Code hooks reference, Anthropic. Por qué leerlo: el formato de deny actual y los campos de stdin, incluido el que identifica a un subagente.
- Claude Code subagents, Anthropic. Por qué leerlo: el campo de frontmatter
modely las listas de herramientas permitidas para un worker barato de solo lectura. - Hacker News discussion of the Spotify post, Hacker News. Por qué leerlo: las preguntas sobre qué mide el 90%, planteadas antes de que nadie volviera a medir.
- Fable spawned five Fable agents instead of Opus (r/ClaudeCode), Reddit. Por qué leerlo: el modo de fallo que evita un campo
modelfijado.
FAQ
¿Está mal el número del 90%?
Mide una sola cosa: tokens de entrada estimados en el contexto principal para escenarios de lectura masiva sobre archivos Java grandes. Con el mismo tipo de métrica, la reconstrucción vio de 41.9% a 79.4% en los escenarios de lectura. No dice nada del coste, el tiempo ni la calidad de las respuestas, y el post no afirma lo contrario.
¿Necesito Portal para conseguirlo?
No. El enrutado vive en una regla de CLAUDE.md, dos archivos de agente con un modelo fijado y un hook PreToolUse. Portal aporta los modelos de los workers en Spotify; una línea model: haiku hace el mismo trabajo en Claude Code estándar.
¿Cuándo cuesta más delegar?
Cuando los archivos son pequeños. El escenario de escribir un test de 45 líneas costó un 2.6% más con delegación porque el escritor es un segundo contexto completo y el modelo principal aun así releyó y probó el resultado. Cada ejecución delegada fue además más lenta, +65.3% de media.
¿Por qué nunca se activó el hook?
Porque la regla de enrutado en CLAUDE.md hizo que el modelo principal comprobara wc -l y delegara antes de intentar leer. El hook solo importa cuando el modelo ignora la regla, lo que ocurrió en la ejecución de verificación sin CLAUDE.md.
AIDive