AIDive

Pack de vídeo

O corte de 90% de tokens da Spotify no Claude Code, medido: hook, subagentes, protocolo

11 min de leitura

TL;DR

  • Os "90%" da Spotify são a média de cenários de leitura em massa medidos em tokens de entrada estimados num monorepo Java. O post não traz valor em dólar nem nota de qualidade.
  • Reconstruído no Claude Code padrão (um hook PreToolUse, dois subagentes baratos, uma regra de roteamento de três linhas) e medido no Fastify em quatro cenários e 16 execuções, o padrão cortou o contexto do modelo principal em 59.6% e o custo total em 33.1%.
  • Os hooks de bloqueio dispararam zero vezes nas execuções medidas. A economia veio da regra de roteamento no CLAUDE.md; os hooks são a rede de segurança para o dia em que o modelo a ignorar.
  • A delegação foi mais lenta todas as vezes, +65.3% de tempo real em média. No cenário pequeno de escrever teste, custou 2.6% a mais.
  • Duas armadilhas: hooks também disparam dentro dos subagentes, então isente seus workers, e leituras por intervalo com sed -n passam direto por um hook que só observa cat, head e tail.
  • O resumo do leitor Haiku trouxe erros factuais em duas das oito execuções delegadas. Mantenha o turno de verificação do modelo principal.

O que as medições dizem

O plugin da Spotify, o Shunt, desvia o trabalho em massa do modelo principal por dois "modos": um leitor em massa e um escritor de código, ambos rodando Gemini 2.5 Flash nos exemplos, com o campo model aceitando qualquer modelo configurado na instância do Portal s1. O roteamento tem três camadas. Um hook check-file-size dispara em todo Read e bloqueia arquivos acima de um limite de linhas configurável, 350 por padrão, apontando o modelo para a skill de leitura em massa; um hook check-bash-read pega cat, head, tail, less e more em arquivos grandes, enquanto comandos com pipe passam s1. O código dos hooks e as duas skills estão no repositório público s2, e a verificação de tamanho pode ser lida sozinha s3. Os modos em si vivem no Portal, a plataforma interna da Spotify, e por isso o plugin não roda fora da empresa do jeito que é distribuído s4.

A alegação do benchmark é fraca. A Spotify testou quatro cenários num monorepo Java, "medindo os tokens que o Claude consumiria lendo os arquivos diretamente contra consumir o resumo do leitor em massa", e reporta economia média de leitura em massa de cerca de 90% s1. O próprio post diz que o cenário de escrita de código é mais difícil de medir em tokens, que os resumos dos workers não trazem números de linha confiáveis e por isso a edição não pode ser delegada, que o worker deixou passar um bug sutil de thread-safety que o modelo principal pegou, e que cada delegação soma de 10 a 30 segundos, com o Portal limitando uma invocação a 30 segundos s1. O thread do Hacker News levantou as mesmas dúvidas sobre o que os 90% medem s7.

A reconstrução troca os modos do Portal por dois subagentes do Claude Code cujos arquivos de definição fixam o modelo: um leitor Explore no Haiku e um escritor de código no Sonnet s6. O bloqueio é um hook PreToolUse que devolve a decisão deny no formato JSON de hook atual s5. O repositório testado foi o fastify/fastify no commit ac28821d, 294 arquivos .js/.ts, 78270 linhas, 63 arquivos com mais de 350 linhas. IDs de modelo conforme o JSON da sessão: conversa principal claude-opus-5[1m], leitor claude-haiku-4-5-20251001, escritor claude-sonnet-5. Cada cenário rodou duas vezes por configuração, 16 execuções medidas, sessões claude -p de um único turno com configurações só do projeto para que os dois lados tivessem prompt de sistema idêntico s5.

Onde o padrão ganhou: S2, uma pergunta de grafo de chamadas sobre três arquivos, lib/route.js (691 linhas), lib/reply.js (1090) e lib/request.js (398), foi de um contexto principal médio de 357165.5 tokens para 73440.0 (-79.4%) e de 0.5810500000000001 USD para 0.21823605000000001 USD (-62.4%). Onde não ganhou: S4, escrever um teste para uma fonte de 45 linhas a partir de uma referência de 19, custou 0.29465575 USD sem delegação e 0.3022213 USD com ela (+2.6%), porque o Sonnet é um segundo contexto completo (13004 a 18729 tokens de cache-read) e o modelo principal ainda releu o arquivo gerado e rodou o teste s6.

Três achados pesam mais que as porcentagens. Primeiro, os hooks dispararam zero vezes nas 16 execuções medidas: com a regra de roteamento no CLAUDE.md, o modelo principal rodou wc -l e delegou por conta própria. O único bloqueio observado veio de uma execução de verificação sem CLAUDE.md, em que o modelo teve o Read negado, depois o cat -n, e respondeu só com grep -n sem nunca chamar a ferramenta Agent s5. Segundo, hooks rodam dentro dos subagentes: em duas execuções descartadas o próprio leitor Haiku foi bloqueado pela verificação de tamanho e recorreu a leituras em pedaços com offset/limit. A correção é uma saída case "$agent_type" in Explore|code-writer) exit 0 no topo do hook, com o nome do campo confirmado no stdin registrado em log s5. Terceiro, na configuração de base o modelo principal nunca usou a ferramenta Read. Ele leu cada arquivo pelo Bash (cat -n, sed -n '1,200p', sed -n '200,560p'), então um hook que só observa o Read não pega nada, e um hook de Bash que só casa cat, head e tail sem pipe ainda deixa os intervalos sed -n passarem s3.

A qualidade foi conferida contra a fonte com grep. A base produziu números de linha errados em uma execução do S2 (despejou os arquivos com sed -n sem números de linha e contou à mão). A configuração delegada produziu três erros factuais na outra execução do S2 e dois numa do S3, todos rastreados a tomar o resumo do Haiku ao pé da letra: chamadores errados de buildRequest/buildReply, uma constante não exportada listada como export, um iterador coberto marcado como não coberto. Onde o modelo principal gastou tokens de saída reverificando com grep (S3, 4534 a 4738 tokens de saída), as respostas se sustentaram s6. No cenário de escrita de teste, todo arquivo gerado passou: 12/12, 6/6, 7/7 e 10/10 testes. Um relato no Reddit mostra o modo de falha parente de um modelo principal que dispara workers no modelo errado quando nada o fixa s8, que é o que o hook require-model e o campo model: nos arquivos de agente evitam.

Medições

Médias das 2 execuções por célula. "Main context" é input + cache_creation + cache_read tokens cobrados do modelo principal na sessão, o número comparável aos "tokens no contexto principal" da Spotify. A = Claude Code padrão, B = hook + subagentes + regra no CLAUDE.md.

cenário contexto principal A contexto principal B variação saída principal A saída principal B variação custo total A custo total B variação duração A s duração B s variação
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: dois clones rasos idênticos byte a byte do fastify/fastify em ac28821d; repo-shunt adiciona .claude/ (configurações, dois arquivos de agente, três hooks) e uma regra de roteamento no CLAUDE.md, nada mais. Cada sessão: claude -p "<prompt>" --output-format json --setting-sources project --strict-mcp-config com config MCP vazia, sem --model, timeout de 600 s. Quatro prompts, idênticos nos dois lados: S1 exports de lib/reply.js, S2 grafo de chamadas entre três arquivos de lib, S3 métodos de lib/hooks.js contra a cobertura de test/hooks.test.js, S4 escrever test/head-route.test.js seguindo test/noop-set.test.js. Os números são lidos de modelUsage e total_cost_usd no JSON da sessão, sem arredondar. As respostas foram conferidas contra a fonte com grep; os testes gerados foram rodados com node --test.

Faça isto na segunda

  • Rode wc -l no seu repositório e conte os arquivos com mais de 350 linhas. Se a contagem for perto de zero, pare por aqui: o limite existe porque delegar custa mais do que economiza em arquivos pequenos.
  • Adicione ao seu CLAUDE.md uma regra de roteamento de três linhas: arquivos acima do limite vão para um subagente leitor, código que segue um padrão vai para um subagente escritor, depuração e arquitetura ficam com o modelo principal. Nas medições, essa regra fez todo o trabalho.
  • Crie .claude/agents/Explore.md com model: haiku e .claude/agents/code-writer.md com model: sonnet no frontmatter, para que o modelo do worker fique fixado no arquivo e não entregue ao orquestrador.
  • Escreva o hook PreToolUse no Read como rede de segurança, devolvendo a decisão deny no formato JSON de hook atual, e faça as primeiras linhas darem exit 0 quando agent_type for um dos seus workers.
  • Estenda o hook de Bash além de cat, head e tail: case intervalos sed -n e cat -n em arquivos grandes, deixe passar comandos com pipe e grep.
  • Rode uma pergunta real com e sem a pasta .claude/, com claude -p --output-format json, e compare total_cost_usd e duration_ms, não só a coluna de entrada.
  • Confira duas respostas delegadas contra a fonte com grep antes de confiar no resumo do leitor; inclua o turno de verificação do modelo principal no orçamento de custo.
  • Meça também uma sessão de vários turnos: os resultados de turno único deixam o contexto principal em 50k a 119k tokens contra 84k a 414k sem delegação, então a segunda pergunta deveria começar mais barata, mas isso não foi medido.

Para ir além

  • Leia o formato de hook e o campo agent_type na referência oficial antes de copiar um hook de um post de blog; o formato do deny e os campos no stdin são o que torna possível a isenção dos subagentes s5.
  • A documentação de subagentes cobre o campo de frontmatter model e as restrições de ferramentas, que é como manter um leitor somente leitura e barato s6.
  • A seção "What doesn't work" da própria Spotify é a parte mais útil do post: sem edição delegada (sem números de linha confiáveis nos resumos), sem raciocínio delegado (um bug de thread-safety deixado passar), idas e vindas de 10 a 30 segundos s1.
  • O README do Shunt mostra a estrutura de três camadas (hooks, scripts, skills) e os textos das skills que dizem ao modelo quando delegar; a prosa das skills é a parte que vale adaptar, não o hook s2.
  • Os modos do Portal são uma camada de configuração sobre um modelo mais um prompt de sistema; a mesma ideia se traduz num arquivo de agente do Claude Code com um campo model s4.
  • O thread do Hacker News é onde as dúvidas de medição apareceram primeiro, e serve de checklist do que perguntar a qualquer alegação de economia de tokens s7.
  • Um thread no Reddit documenta um orquestrador disparando cinco workers no próprio modelo caro; fixe o modelo no arquivo de agente e, se quiser garantia firme, bloqueie chamadas a Agent sem campo model s8.

Fontes

FAQ

O número de 90% está errado?

Ele mede uma coisa só: tokens de entrada estimados no contexto principal em cenários de leitura em massa de arquivos Java grandes. Numa métrica do mesmo tipo, a reconstrução viu de 41.9% a 79.4% nos cenários de leitura. Ele não diz nada sobre custo, tempo ou qualidade das respostas, e o post não alega o contrário.

Preciso do Portal para conseguir isso?

Não. O roteamento vive numa regra do CLAUDE.md, dois arquivos de agente com modelo fixado e um hook PreToolUse. O Portal fornece os modelos dos workers na Spotify; uma linha model: haiku faz o mesmo trabalho no Claude Code padrão.

Quando delegar custa mais?

Quando os arquivos são pequenos. O cenário de escrever teste para 45 linhas custou 2.6% a mais com delegação, porque o escritor é um segundo contexto completo e o modelo principal ainda releu e testou o resultado. Toda execução delegada também foi mais lenta, +65.3% em média.

Por que o hook nunca disparou?

Porque a regra de roteamento no CLAUDE.md fez o modelo principal checar wc -l e delegar antes de tentar ler. O hook só importa quando o modelo ignora a regra, o que aconteceu na execução de verificação sem CLAUDE.md.