Novanta per cento, e la frase che l'ha venduto
Il setup Claude Code di Spotify è un post sul blog di Dimitri Mazmanov, product manager a Spotify, con il codice su GitHub: secondo lui la configurazione usata dal suo team ha tagliato del 90% il suo consumo di token in Claude Code. La sua prima riga contiene l'intera argomentazione: la maggior parte di ciò che fa un agent di coding AI non è ragionamento, è I/O. Leggere cinque file per rispondere a una domanda su un solo metodo, o scrivere il ventunesimo file di test che copia i venti accanto, brucia migliaia di token con quasi zero ragionamento.
Un tweet ha portato il post a un milione e mezzo di visualizzazioni con una sola frase: le regole scritte sono un suggerimento, un blocco no. Hacker News lo ha messo in prima pagina, 271 punti e 173 commenti, e metà dei commenti poneva la stessa domanda: 90% di cosa? Il qualificatore di Spotify stessa è "bulk read". Questo articolo ricostruisce il setup dentro Claude Code puro, poi lo misura, così sai esattamente cosa ti dà quel qualificatore.
Cos'è davvero Portal (e perché non puoi usarlo)
Portal non è un router. È il portale interno per sviluppatori di Spotify, costruito su Backstage, la piattaforma per sviluppatori che Spotify ha rilasciato open source. La funzionalità rilevante al suo interno si chiama Modes: per definizione di Spotify, un mode è un agent dichiarativo che gira su un runtime effimero, più o meno AWS Lambda per gli agent. Scrivi le istruzioni, scegli un modello, imposti una temperatura, colleghi gli strumenti. Mazmanov ne ha costruiti due, un bulk reader e un code writer, entrambi su Gemini Flash a temperatura 0.2, quindi entrambi economici e noiosi di proposito.
Il routing vive in un plugin di Claude Code chiamato Shunt. È pubblico su GitHub e si installa con due comandi. Il secondo passo, però, autentica la riga di comando di Portal contro la tua istanza Portal, e tu non ne hai una. Il plugin è pubblico; la cosa a cui delega non lo è.
La mossa utile è quindi dimenticare il plugin e tenere il pattern. Ha tre livelli, con le sue parole: hook, script, skill. Ognuno di essi ha un equivalente in Claude Code puro, ed è quello che il resto di questo articolo costruisce e misura.
Livello uno: l'hook che blocca invece di chiedere
La versione 1 del setup era un blocco di regole di routing nel file di istruzioni del progetto. Con le parole di Mazmanov, "funzionava più o meno": le regole erano consultive, non imposte, Claude poteva ignorarle, e ogni progetto aveva bisogno della propria copia. La versione 2 sposta la decisione fuori dal prompt e dentro il livello degli strumenti con due hook, entrambi eseguiti prima di una chiamata a uno strumento. Uno sorveglia ogni lettura di file, l'altro sorveglia la shell.
L'hook di lettura è 33 righe di bash. Legge una soglia dall'ambiente, 350 righe di default, poi lascia passare tre cose:
- Una lettura con un offset o un limit, perché Claude sa già cosa gli serve.
- Un file che non esiste.
- Un file pari o inferiore alla soglia, perché delegare qualcosa di piccolo costa più che leggerlo.
Tutto il resto viene bloccato, con un messaggio che Claude legge al posto del file: questo file ha tot righe, usa la skill bulk reader, e se ti serve il contenuto esatto per una modifica, rileggi solo quella sezione. L'hook della shell intercetta cat, head, tail, less e more su un file grande. Un comando con pipe passa, perché una pipe verso grep è una lettura mirata.
Il punto di Mazmanov sulla stratificazione è quello importante: anche se Claude non legge mai la descrizione della skill, l'hook blocca comunque la lettura costosa. La skill rende il reindirizzamento più fluido; il blocco lo rende reale. Un dettaglio conta più avanti: lo script risponde con una decisione di primo livello chiamata "block". Tieni a mente quella parola.
Livelli due e tre: i worker e i loro numeri
I worker sono due prompt. Il reader: "sei un analista di codice preciso, produci solo elenchi puntati strutturati, niente saluti, niente prosa, inizia ogni punto con il nome esatto, il tipo o il numero di riga". Il writer: "rispetta esattamente i pattern, la nomenclatura e lo stile esistenti; produci solo il codice, niente fence, niente spiegazioni". Senza quell'ultima riga il modello avvolge tutto in Markdown che Claude deve poi analizzare.
Due script li incapsulano. Bulk-read prende una domanda e dei percorsi di file e li spedisce. Code-write prende una specifica e un file di riferimento e scrive il risultato direttamente su disco, così Claude non vede mai il codice generato. Ogni delega è one-shot: un follow-up rimanda i file. Questo è gratis dove conta, perché il corpus va al worker e non entra mai nel contesto di Claude.
Il livello tre è un file skill che dice a Claude quando delegare: file oltre le 350 righe, domande che coinvolgono tre o più file, diff grandi. La sua ultima riga è "verifica i numeri di riga prima di modificare".
La tabella di Spotify copre un solo monorepo Java e tre scenari di lettura. Il caso a file singolo scende da circa 34.000 token a meno di 6.000, e il risparmio medio sulle tre righe è del 90%.
| Benchmark di Spotify | Valore |
|---|---|
| Repository | 1 monorepo Java |
| Scenari | 3, tutti bulk read |
| Caso a file singolo, prima | ~34.000 token |
| Caso a file singolo, dopo | < 6.000 token |
| Risparmio medio | 90% |
| Stima dei token | 4 caratteri per token |
| Enforcement del writer | nessuno (solo il reader ha un hook) |
Due avvertenze sono stampate da Spotify stessa: i token sono stimati a quattro caratteri ciascuno, e il writer non ha alcun enforcement. Quindi il 90% è la media di tre righe di bulk read in token di input stimati, senza alcun punteggio di qualità e senza alcuna cifra in dollari da nessuna parte. È quello il numero da testare.
Ricostruzione, parte uno: un subagent con un campo model
Claude Code include un subagent Explore integrato, e da una release recente eredita il tuo modello principale, con tetto Opus, quindi il "reader economico" non è più economico. La documentazione dà la soluzione in una frase: un subagent di progetto chiamato Explore sovrascrive quello integrato e mantiene il proprio campo model. Un file markdown, un front matter, e la riga model dice Haiku. Quello è il bulk reader. Il writer è un secondo file: model Sonnet, strumenti solo Read e Write, e il corpo sono le istruzioni di Spotify incollate.
Funziona perché ogni subagent parte con una finestra di contesto nuova e isolata. Ciò che legge finisce lì, non nella conversazione principale. È la delega one-shot di Spotify senza il round trip di rete.
Poi arriva la parte che nessuno pianifica. Su Reddit questa settimana, a Fable è stato detto di lanciare agent Opus e ha lanciato invece cinque agent Fable: il 73% di un limite settimanale sparito in trenta minuti. La risposta più votata era un hook che gira quando il modello invia un subagent, lo obbliga a scegliere il modello esplicitamente e gli dice di prendere il più economico in grado di fare il compito. Quello è l'hook numero tre: sorveglia lo strumento Agent, e una chiamata senza modello viene rifiutata con una sola frase, "scegli il modello esplicitamente".
La skill di Spotify diventa tre righe nel file di istruzioni del progetto: i file oltre le 350 righe vanno all'explorer, il boilerplate va al writer, ogni chiamata agent imposta un modello. Esiste anche l'opzione brutale: due variabili d'ambiente che impongono un solo modello a ogni subagent. Il limite onesto è che il reader è un modello più economico, quindi ciò che restituisce è tutto ciò che il modello principale sa. La sezione sulla misurazione copre questo punto.
Ricostruzione, parte due: il deny, nel formato hook attuale
Ricorda la parola "block". Lo script di Spotify restituisce una decisione di primo livello, ma la documentazione attuale di Claude Code dice qualcosa di diverso: un hook PreToolUse restituisce la sua decisione dentro un oggetto di output specifico dell'hook, e il campo si chiama permissionDecision. Ha quattro esiti, allow, deny, ask e defer, e quello che serve qui è deny. Qualunque cosa l'hook scriva come motivazione viene mostrata a Claude, e se più hook rispondono, vince deny.
L'hook di lettura ricostruito mantiene la stessa soglia di 350 e le stesse tre eccezioni, e al posto di "block" restituisce un deny con una motivazione che nomina il subagent Explore e il modello da usare. Una trappola che la documentazione dichiara chiaramente: gli hook delle tue impostazioni girano anche dentro i subagent. Senza una scappatoia, al reader Haiku vengono negate le sue stesse letture e non può mai fare il suo lavoro, quindi lo script controlla chi sta chiamando e lascia passare i due worker.
Il cablaggio è un solo file di impostazioni con tre matcher, Read, Bash e Agent, ciascuno che punta al proprio script, e la soglia impostata come variabile d'ambiente. In pratica una lettura su un file di 1.090 righe torna come errore con la frase scritta: delega questa lettura all'explorer, modello Haiku. La delega poi segue: il modello principale conta prima le righe, chiama l'explorer con il modello impostato su Haiku, e i punti tornano, ciascuno con un numero di riga. Tre turni, 44 secondi.
La frase di Spotify regge: la stratificazione fa sì che il sistema degradi con grazia. L'istruzione fa il routing, l'hook è la rete. La rete però ha un buco. Un modello che vuole l'intero file può spezzarlo con offset e limit, che passa, oppure scaricarlo tramite la shell con un range di sed, che questo hook non intercetta. La misurazione conta entrambi.
La misurazione
Il repository di test è Fastify, il framework web per Node: 294 file, 63 dei quali oltre la soglia. Due cloni identici, l'unica differenza è la cartella .claude e il file delle regole. Modello principale Opus, il default della CLI; reader Haiku; writer Sonnet. Sessioni a prompt singolo, senza follow-up, ogni scenario eseguito due volte per configurazione, sedici run in totale. I quattro scenari sono gli stessi di Spotify: gli export di un file grande, tre file e come si chiamano a vicenda, un file sorgente contro il suo test, e un nuovo file di test scritto su disco a partire da uno esistente.
| Scenario | Contesto principale, senza | Contesto principale, con | Variazione | Costo totale, senza | Costo totale, con | Variazione | Durata, senza | Durata, con | Variazione |
|---|---|---|---|---|---|---|---|---|---|
| Un file grande | 88.693 | 51.552 | -41,9% | $0,139 | $0,087 | -37,8% | 22 s | 44 s | +100,8% |
| Tre file | 357.166 | 73.440 | -79,4% | $0,581 | $0,218 | -62,4% | 52 s | 129 s | +149,9% |
| Sorgente vs test | 303.808 | 114.136 | -62,4% | $0,451 | $0,374 | -17,1% | 93 s | 125 s | +33,6% |
| Nuovo file di test | 143.432 | 121.818 | -15,1% | $0,295 | $0,302 | +2,6% | 66 s | 87 s | +32,2% |
| Tutti e quattro | 223.274 | 90.236 | -59,6% | $0,366 | $0,245 | -33,1% | 58 s | 96 s | +65,3% |
Il contesto principale, i token che il modello costoso ha effettivamente visto, è la prima colonna che conta. Sulla domanda a tre file cala del 79%, e su tutti e quattro gli scenari del 59,6%. Il conto cala meno, un terzo in totale, perché i token del reader non sono gratis, e sul piccolo compito di scrittura del test il conto è salito del 2,6%. Il tempo va nella direzione opposta: 58 secondi in media senza il setup, 96 con. La delega è più lenta ogni volta.
La qualità è dove le due configurazioni differiscono di più. Senza il setup, il modello principale ha scaricato i file tramite la shell senza numeri di riga e li ha contati a mano, producendo numeri di riga sbagliati ovunque: una funzione segnalata alla riga 149 stava in realtà alla riga 156. Con il setup, una run su quattro ha preso il riassunto del reader per buono e ha riportato tre affermazioni false, una delle quali una funzione che secondo il reader il file delle route non chiama mai, mentre lo fa, alla riga 553. Tutti e quattro i file di test generati passano, e gli hook deny sono scattati zero volte in sedici run: con il file delle regole presente, il modello principale ha controllato il numero di righe e ha delegato da solo ogni volta.
Un'altra cosa dalle tracce: senza la regola, il modello principale non ha mai usato lo strumento Read. Ha letto tutto tramite la shell, e una lettura a range da shell costa gli stessi token e passa l'hook. Quindi la tabella di Spotify dice 90; questa dice 60 sul contesto e un terzo sul conto.
Tieni il blocco. Non aspettarti un conto tagliato del novanta.
Tre cose vale la pena tenere: un subagent Explore di progetto su Haiku, una regola di tre righe nel file di istruzioni, e l'hook di lettura come rete di sicurezza. Il risultato misurato è 60% di contesto principale in meno, un terzo in meno sul conto, e due terzi di tempo reale in più.
Prima di fidarti dell'hook, sistema due cose. Gli hook girano dentro i subagent, quindi esenta i tuoi worker. E il buco della shell: l'hook bash intercetta cat, head e tail, ma una lettura a range passa, e il modello principale ha usato esattamente quella quando non aveva la regola.
I limiti dichiarati da Spotify restano. Non puoi delegare le modifiche e non puoi delegare il ragionamento; il worker si è perso un bug di thread safety che Claude ha colto in pochi secondi, e ogni delega è un round trip. Anche gli scettici di Hacker News avevano ragione su una cosa: i token di input non sono il conto. I token di output costano di più, e questo setup non fa niente per loro.
Chi risparmia dipende da come paghi. Sull'API, un terzo in meno. Su un piano Pro o Max, lo stesso setup sposta le tue finestre di cinque ore e settimanali, non i dollari. Attenzione anche alla soglia: sotto di essa, delegare costa più di quanto fa risparmiare, e il caso di test da 45 righe ne è la prova a più 2,6%. Infine, in due run su otto il riassunto del reader conteneva errori, e il turno di verifica del modello principale li ha colti. Salta quel turno e quegli errori arrivano alle tue modifiche.
AIDive