AIDive

Spotify cortou 90% dos tokens do Claude Code. Refiz e medi

Por AIDive · Publicado em

Agentes de códigoModelos de IA

Noventa por cento, e a frase que vendeu isso

O setup Spotify Claude Code é um post de blog de Dimitri Mazmanov, gerente de produto no Spotify, com o código no GitHub: ele diz que a configuração usada pelo time dele cortou em 90% o uso de tokens do Claude Code. A primeira linha do post carrega o argumento inteiro: a maior parte do que um agente de código com IA faz não é pensar, é I/O. Ler cinco arquivos para responder a uma pergunta sobre um único método, ou escrever o vigésimo primeiro arquivo de teste que copia os vinte ao lado, queima milhares de tokens com quase nenhum raciocínio.

Um tweet levou o post a um milhão e meio de views com uma única frase: regra escrita é sugestão, bloqueio não é. O Hacker News colocou o post na primeira página, 271 pontos e 173 comentários, e metade dos comentários fazia a mesma pergunta: 90% de quê? A ressalva do próprio Spotify é "bulk read", leitura em massa. Este artigo reconstrói o setup dentro do Claude Code puro e depois mede, para você saber exatamente o que essa ressalva compra.

O que o Portal é de verdade (e por que você não consegue rodar)

O Portal não é um roteador. É o portal de desenvolvedor interno do Spotify, construído sobre o Backstage, a plataforma de desenvolvedor que o Spotify abriu como código livre. O recurso relevante dentro dele se chama Modes: pela definição do Spotify, um mode é um agente declarativo que roda em um runtime efêmero, algo como o AWS Lambda para agentes. Você escreve as instruções, escolhe um modelo, define uma temperatura, anexa ferramentas. Mazmanov construiu dois deles, um leitor em massa e um escritor de código, ambos no Gemini Flash com temperatura 0.2, então os dois são baratos e sem graça de propósito.

O roteamento vive em um plugin do Claude Code chamado Shunt. Ele é público no GitHub e se instala com dois comandos. O passo dois, porém, autentica a linha de comando do Portal contra a sua instância do Portal, e você não tem uma. O plugin é público; aquilo a que ele delega não é.

Então a jogada útil é esquecer o plugin e ficar com o padrão. Ele tem três camadas, nas palavras do próprio autor: hooks, scripts, skills. Cada uma delas tem um equivalente em Claude Code puro, e é isso que o resto deste artigo constrói e mede.

Camada um: o hook que bloqueia em vez de pedir

A versão 1 do setup era um bloco de regras de roteamento no arquivo de instruções do projeto. Nas palavras de Mazmanov, "meio que funcionava": as regras eram consultivas, não impostas, o Claude podia ignorá-las, e cada projeto precisava da própria cópia. A versão 2 tira a decisão do prompt e a coloca na camada de ferramentas, com dois hooks, ambos disparando antes de uma chamada de ferramenta. Um observa cada leitura de arquivo, o outro observa o shell.

O hook de leitura tem 33 linhas de bash. Ele lê um limite do ambiente, 350 linhas por padrão, e então deixa passar três coisas:

  • Uma leitura com offset ou limit, porque o Claude já sabe o que precisa.
  • Um arquivo que não existe.
  • Um arquivo com o limite ou abaixo dele, porque delegar algo pequeno custa mais do que ler.

Todo o resto é bloqueado, com uma mensagem que o Claude lê no lugar do arquivo: este arquivo tem tantas linhas, use a skill de leitura em massa, e se você precisar do conteúdo exato para uma edição, releia só aquele trecho. O hook do shell pega cat, head, tail, less e more em um arquivo grande. Um comando com pipe passa, porque encadear em um grep é uma leitura direcionada.

O ponto de Mazmanov sobre as camadas é o mais importante: mesmo que o Claude nunca leia a descrição da skill, o hook ainda bloqueia a leitura cara. A skill deixa o redirecionamento mais suave; o bloqueio o torna real. Um detalhe vai importar mais adiante: o script responde com uma decisão de nível superior chamada "block". Guarde essa palavra.

Camadas dois e três: os workers e os números deles

Os workers são dois prompts. O leitor: "você é um analista de código preciso, produza apenas bullets estruturados, sem saudações, sem prosa, comece cada bullet com o nome, tipo ou número de linha exato". O escritor: "siga exatamente os padrões, a nomenclatura e o estilo existentes; produza apenas o código, sem cercas de código, sem explicações". Sem essa última linha, o modelo embrulha tudo em Markdown que o Claude depois precisa interpretar.

Dois scripts os envolvem. O bulk-read recebe uma pergunta e caminhos de arquivos e os envia. O code-write recebe uma especificação e um arquivo de referência e grava o resultado direto em disco, então o Claude nunca vê o código gerado. Cada delegação é one-shot: um follow-up envia os arquivos de novo. Isso é de graça onde importa, porque o corpus vai para o worker e nunca entra no contexto do Claude.

A camada três é um arquivo de skill que diz ao Claude quando delegar: arquivos com mais de 350 linhas, perguntas que atravessam três ou mais arquivos, diffs grandes. A última linha dele é "verifique os números de linha antes de editar".

A tabela do Spotify cobre um monorepo Java e três cenários de leitura. O caso de arquivo único cai de cerca de 34.000 tokens para menos de 6.000, e a economia média nas três linhas é de 90%.

Benchmark do Spotify Valor
Repositórios 1 monorepo Java
Cenários 3, todos leituras em massa
Caso de arquivo único, antes ~34.000 tokens
Caso de arquivo único, depois < 6.000 tokens
Economia média 90%
Estimativa de tokens 4 caracteres por token
Imposição no escritor nenhuma (só o leitor tem hook)

Duas ressalvas são impressas pelo próprio Spotify: os tokens são estimados a quatro caracteres cada, e o escritor não tem imposição nenhuma. Então os 90% são a média de três linhas de leitura em massa em tokens de entrada estimados, sem nota de qualidade e sem valor em dólares em lugar nenhum. Esse é o número a testar.

Reconstrução, parte um: um subagente com campo de modelo

O Claude Code vem com um subagente Explore embutido, e desde um lançamento recente ele herda o seu modelo principal, com teto no Opus, então o "leitor barato" não é mais barato. A documentação dá a solução em uma frase: um subagente de projeto chamado Explore sobrescreve o embutido e mantém o próprio campo de modelo. Um arquivo markdown, um front matter, e a linha do modelo diz Haiku. Esse é o leitor em massa. O escritor é um segundo arquivo: modelo Sonnet, ferramentas Read e Write apenas, e o corpo são as próprias instruções do Spotify coladas.

Funciona porque cada subagente começa com uma janela de contexto nova e isolada. O que ele lê cai ali, não na conversa principal. É a delegação one-shot do Spotify sem a ida e volta pela rede.

Depois vem a parte que ninguém planeja. No Reddit, nesta semana, o Fable recebeu a instrução de abrir agentes Opus e abriu cinco agentes Fable no lugar: 73% de um limite semanal foram embora em trinta minutos. A resposta mais votada foi um hook que roda quando o modelo despacha um subagente, o obriga a escolher o modelo explicitamente e manda escolher o mais barato que dê conta da tarefa. Esse é o hook número três: ele observa a ferramenta Agent, e uma chamada sem modelo é recusada com uma frase, "escolha o modelo explicitamente".

A skill do Spotify vira três linhas no arquivo de instruções do projeto: arquivos com mais de 350 linhas vão para o explorador, boilerplate vai para o escritor, toda chamada de agente define um modelo. A opção bruta também existe: duas variáveis de ambiente que forçam um único modelo em todo subagente. O limite honesto é que o leitor é um modelo mais barato, então o que ele devolve é tudo o que o modelo principal sabe. A seção de medição cobre isso.

Reconstrução, parte dois: o deny, no formato atual de hook

Lembre da palavra "block". O script do Spotify devolve uma decisão de nível superior, mas a documentação atual do Claude Code diz algo diferente: um hook PreToolUse devolve a decisão dentro de um objeto de saída específico do hook, e o campo se chama permissionDecision. Ele tem quatro resultados, allow, deny, ask e defer, e o que queremos aqui é deny. O que o hook escrever como motivo é mostrado ao Claude, e se vários hooks respondem, deny vence.

O hook de leitura reconstruído mantém o mesmo limite de 350 e as mesmas três exceções, e em vez de "block" devolve um deny com um motivo que nomeia o subagente Explore e o modelo a usar. Uma armadilha que a documentação declara sem rodeios: os hooks das suas configurações também rodam dentro dos subagentes. Sem uma exceção, o leitor Haiku recebe deny nas próprias leituras e nunca consegue fazer o trabalho dele, então o script verifica quem está chamando e deixa os dois workers passarem.

A fiação é um único arquivo de configurações com três matchers, Read, Bash e Agent, cada um apontando para o seu script, e o limite definido como variável de ambiente. Na prática, uma leitura de um arquivo de 1.090 linhas volta como erro com a frase que escrevemos: delegue esta leitura ao explorador, modelo Haiku. A delegação vem em seguida: o modelo principal conta as linhas primeiro, chama o explorador com o modelo definido como Haiku, e os bullets voltam, cada um com um número de linha. Três turnos, 44 segundos.

A linha do Spotify se sustenta: as camadas fazem o sistema degradar com elegância. A instrução faz o roteamento, o hook é a rede. A rede, porém, tem um buraco. Um modelo que quer o arquivo inteiro pode fatiá-lo com offset e limit, o que passa, ou despejá-lo pelo shell com um range do sed, que este hook não pega. A medição conta os dois.

A medição

O repositório de teste é o Fastify, o framework web para Node: 294 arquivos, 63 deles acima do limite. Dois clones idênticos, com a única diferença sendo a pasta .claude e o arquivo de regra. Modelo principal Opus, o padrão da CLI; leitor Haiku; escritor Sonnet. Sessões de prompt único, sem follow-ups, cada cenário rodado duas vezes por configuração, dezesseis execuções no total. Os quatro cenários são os mesmos do Spotify: os exports de um arquivo grande, três arquivos e como eles se chamam entre si, um arquivo-fonte contra o seu teste, e um novo arquivo de teste gravado em disco a partir de um existente.

Cenário Contexto principal, sem Contexto principal, com Variação Custo total, sem Custo total, com Variação Duração, sem Duração, com Variação
Um arquivo grande 88,693 51,552 -41.9% $0.139 $0.087 -37.8% 22 s 44 s +100.8%
Três arquivos 357,166 73,440 -79.4% $0.581 $0.218 -62.4% 52 s 129 s +149.9%
Fonte vs teste 303,808 114,136 -62.4% $0.451 $0.374 -17.1% 93 s 125 s +33.6%
Novo arquivo de teste 143,432 121,818 -15.1% $0.295 $0.302 +2.6% 66 s 87 s +32.2%
Os quatro 223,274 90,236 -59.6% $0.366 $0.245 -33.1% 58 s 96 s +65.3%

O contexto principal, os tokens que o modelo caro de fato viu, é a primeira coluna que importa. Na pergunta de três arquivos ele cai 79%, e nos quatro cenários juntos, 59,6%. A conta cai menos, um terço no total, porque os tokens do próprio leitor não são de graça, e na pequena tarefa de escrever um teste a conta subiu 2,6%. O tempo vai na direção contrária: 58 segundos em média sem o setup, 96 com. Delegar é mais lento toda vez.

A qualidade é onde as duas configurações mais diferem. Sem o setup, o modelo principal despejou os arquivos pelo shell sem números de linha e contou na mão, produzindo números de linha errados do começo ao fim: uma função reportada na linha 149 estava na verdade na linha 156. Com o setup, uma execução em quatro aceitou o resumo do leitor sem questionar e carregou três afirmações falsas, uma delas uma função que o leitor disse que o arquivo de rotas nunca chama, quando chama, na linha 553. Os quatro arquivos de teste gerados passam, e os hooks de deny dispararam zero vezes em dezesseis execuções: com o arquivo de regra presente, o modelo principal verificou a contagem de linhas e delegou sozinho toda vez.

Mais uma coisa dos traces: sem a regra, o modelo principal nunca usou a ferramenta Read. Ele leu tudo pelo shell, e uma leitura por range no shell custa os mesmos tokens e passa pelo hook. Então a tabela do Spotify diz 90; esta diz 60 no contexto e um terço na conta.

Fique com o bloqueio. Não espere a conta cair noventa.

Três coisas valem a pena guardar: um subagente Explore de projeto no Haiku, uma regra de três linhas no arquivo de instruções e o hook de leitura como rede de segurança. O resultado medido é 60% menos contexto principal, um terço a menos na conta e dois terços a mais de tempo total.

Antes de confiar no hook, conserte duas coisas. Hooks rodam dentro dos subagentes, então isente os seus workers. E o buraco no shell: o hook do bash pega cat, head e tail, mas uma leitura por range passa, e o modelo principal usou exatamente isso quando não tinha regra.

Os próprios limites do Spotify continuam de pé. Você não consegue delegar edição nem delegar raciocínio; o worker deixou passar um bug de thread safety que o Claude pegou em segundos, e cada delegação é uma ida e volta. Os céticos do Hacker News também tinham razão em uma coisa: tokens de entrada não são a conta. Tokens de saída custam mais, e este setup não faz nada por eles.

Quem economiza depende de como você paga. Na API, um terço a menos. Num plano Pro ou Max, o mesmo setup move as suas janelas de cinco horas e semanal, não dólares. Cuidado também com o limite: abaixo dele, delegar custa mais do que economiza, e o caso de teste de 45 linhas é a prova, com mais 2,6%. Por fim, em duas execuções de oito o resumo do leitor trouxe erros, e o turno de verificação do modelo principal os pegou. Pule esse turno e esses erros chegam às suas edições.

Fontes

Perguntas frequentes

O Spotify realmente cortou 90% do uso de tokens do Claude Code?
Os 90% do Spotify são a média de três cenários de leitura em massa em um monorepo Java, contados em tokens de entrada estimados a quatro caracteres por token, sem nota de qualidade e sem valor em dólares. Reconstruída com Claude Code puro no Fastify, a mesma ideia cortou o contexto do modelo principal em 59,6% e o custo total em 33%.
O que é o Portal do Spotify e dá para usar com o Claude Code?
O Portal é o portal de desenvolvedor interno do Spotify, construído sobre o Backstage; o recurso Modes dele roda agentes declarativos em um runtime efêmero. O plugin Shunt, que roteia o Claude Code para esses agentes, é público no GitHub, mas ele se autentica contra uma instância do Portal que você não tem, então só dá para copiar o padrão, não rodar o plugin.
Como faço o Claude Code delegar leituras de arquivos grandes a um modelo mais barato?
Crie um subagente de projeto chamado Explore com o campo de modelo definido como Haiku, o que sobrescreve o agente Explore embutido, que agora herda o seu modelo principal. Adicione uma regra de três linhas ao arquivo de instruções do projeto (arquivos com mais de 350 linhas vão para o explorador, boilerplate vai para o escritor, toda chamada de agente define um modelo) e um hook PreToolUse em Read que nega leituras grandes com um motivo que nomeia esse subagente.
Como um hook PreToolUse bloqueia uma chamada de ferramenta no Claude Code hoje?
O hook devolve a decisão dentro do objeto hookSpecificOutput, em um campo chamado permissionDecision, com quatro valores possíveis: allow, deny, ask e defer. A string de motivo é mostrada ao Claude, e quando vários hooks respondem, deny vence. O script do Spotify usa uma decisão de nível superior mais antiga, chamada block.
Delegar leituras a um subagente Haiku deixa o Claude Code mais barato?
Na API, tirou um terço da conta em quatro cenários, mas os tokens do próprio leitor não são de graça: em uma tarefa de escrever um teste de 45 linhas o custo total subiu 2,6%. Num plano Pro ou Max a economia aparece como cota nas janelas de cinco horas e semanal, não como dólares, e os tokens de saída ficam intactos.
Por que os hooks do Claude Code disparam dentro dos subagentes?
Os hooks definidos nas suas configurações rodam para todo agente, incluindo os subagentes que você despacha. Um hook que bloqueia leituras, portanto, nega ao leitor Haiku as próprias leituras, a menos que o script verifique quem está chamando e deixe os agentes workers passarem.

Vídeos relacionados