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.
AIDive