TL;DR
- Il "90%" di Spotify è la media di scenari di lettura massiva misurati in token di input stimati su un monorepo Java. Il post non dà alcuna cifra in dollari né un punteggio di qualità.
- Ricostruito in Claude Code puro (un hook PreToolUse, due subagent economici, una regola di routing di tre righe) e misurato su Fastify su quattro scenari e 16 esecuzioni, il pattern ha ridotto il contesto del modello principale del 59.6% e il costo totale del 33.1%.
- Gli hook di blocco non sono scattati nemmeno una volta nelle esecuzioni misurate. Il risparmio è venuto dalla regola di routing in CLAUDE.md; gli hook sono la rete di sicurezza per il giorno in cui il modello la ignora.
- La delega è stata più lenta ogni volta, +65.3% di tempo reale in media. Sullo scenario piccolo di scrittura di un test è costata il 2.6% in più.
- Due trappole: gli hook scattano anche dentro i subagent, quindi escludi i tuoi worker, e le letture a intervalli con
sed -npassano indisturbate da un hook che controlla solocat,headetail. - Il riassunto del reader Haiku conteneva errori fattuali in due esecuzioni delegate su otto. Tieni il turno di verifica del modello principale.
Cosa dicono le misure
Il plugin di Spotify, Shunt, sposta il lavoro massivo lontano dal modello principale tramite due "mode": un bulk-reader e un code-writer, entrambi su Gemini 2.5 Flash negli esempi, con il campo model che accetta qualsiasi modello configurato nell'istanza Portal s1. Il routing ha tre livelli. Un hook check-file-size scatta a ogni Read e blocca i file oltre una soglia di righe configurabile, di default 350, indirizzando il modello alla skill bulk-reader; un hook check-bash-read intercetta cat, head, tail, less e more sui file grandi, mentre i comandi con pipe passano s1. I sorgenti degli hook e le due skill sono nel repo pubblico s2, e il controllo sulla dimensione si può leggere a parte s3. Le mode vivono in Portal, la piattaforma interna di Spotify, ed è per questo che il plugin così com'è non può girare fuori dall'azienda s4.
Il benchmark è debole. Spotify ha testato quattro scenari su un monorepo Java, "measuring tokens Claude would consume reading files directly vs. consuming the bulk-reader's summary", e riporta un risparmio medio sulla lettura massiva di circa il 90% s1. Il post stesso dice che lo scenario di scrittura di codice è più difficile da misurare in token, che i riassunti dei worker non includono numeri di riga affidabili e quindi l'editing non si può delegare, che il worker ha mancato un bug sottile di thread safety che il modello principale ha preso, e che ogni delega aggiunge da 10 a 30 secondi, con Portal che limita una singola invocazione a 30 secondi s1. Il thread su Hacker News ha sollevato le stesse domande su cosa misuri il 90% s7.
La ricostruzione sostituisce le mode di Portal con due subagent di Claude Code i cui file di definizione fissano il modello: un reader Explore su Haiku e un code-writer su Sonnet s6. Il blocco è un hook PreToolUse che restituisce la decisione deny nel formato JSON attuale degli hook s5. Il repo testato era fastify/fastify al commit ac28821d, 294 file .js/.ts, 78270 righe, 63 file oltre le 350 righe. ID dei modelli come riportati dal JSON di sessione: conversazione principale claude-opus-5[1m], reader claude-haiku-4-5-20251001, writer claude-sonnet-5. Ogni scenario è stato eseguito due volte per configurazione, 16 esecuzioni misurate, sessioni claude -p a turno singolo con soli settings di progetto, così che entrambi i lati avessero lo stesso system prompt s5.
Dove il pattern ha vinto: S2, una domanda sul call graph di tre file, lib/route.js (691 righe), lib/reply.js (1090) e lib/request.js (398), è passata da un contesto principale medio di 357165.5 token a 73440.0 (-79.4%) e da 0.5810500000000001 USD a 0.21823605000000001 USD (-62.4%). Dove no: S4, scrivere un test per un sorgente di 45 righe a partire da un riferimento di 19 righe, è costato 0.29465575 USD senza delega e 0.3022213 USD con (+2.6%), perché Sonnet è un secondo contesto completo (da 13004 a 18729 token di cache-read) e il modello principale ha comunque riletto il file generato ed eseguito il test s6.
Tre risultati contano più delle percentuali. Primo, gli hook non sono scattati nemmeno una volta nelle 16 esecuzioni misurate: con la regola di routing in CLAUDE.md il modello principale ha eseguito wc -l e ha delegato da solo. L'unico blocco osservato è venuto da una verifica senza CLAUDE.md, in cui al modello è stato negato Read, poi cat -n, e ha risposto con il solo grep -n senza mai chiamare il tool Agent s5. Secondo, gli hook girano dentro i subagent: in due esecuzioni scartate il reader Haiku è stato bloccato a sua volta dal controllo sulla dimensione e ripiegato su letture a blocchi con offset/limit. La soluzione è un'uscita case "$agent_type" in Explore|code-writer) exit 0 all'inizio dell'hook, con il nome del campo confermato dallo stdin loggato s5. Terzo, nella configurazione di base il modello principale non ha mai usato il tool Read. Ha letto ogni file tramite Bash (cat -n, sed -n '1,200p', sed -n '200,560p'), quindi un hook che osserva solo Read non intercetta nulla, e un hook Bash che corrisponde solo a cat, head e tail senza pipe lascia comunque passare gli intervalli sed -n s3.
La qualità è stata verificata sul sorgente con grep. La baseline ha prodotto numeri di riga sbagliati in una esecuzione S2 (ha stampato i file con sed -n senza numeri di riga e ha contato a mano). La configurazione delegata ha prodotto tre errori fattuali nell'altra esecuzione S2 e due in una esecuzione S3, tutti ricondotti al riassunto di Haiku preso per buono: chiamanti sbagliati di buildRequest/buildReply, una costante non esportata elencata come export, un iteratore coperto segnato come non coperto. Dove il modello principale ha speso token di output per ricontrollare con grep (S3, da 4534 a 4738 token di output), le risposte hanno retto s6. Sullo scenario di scrittura dei test ogni file generato è passato: 12/12, 6/6, 7/7 e 10/10 test. Una segnalazione su Reddit mostra il caso affine di un modello principale che avvia worker sul modello sbagliato quando nulla lo fissa s8, ed è ciò da cui proteggono l'hook require-model e il campo model: nei file degli agent.
Misure
Medie delle 2 esecuzioni per cella. "Contesto principale" è input + cache_creation + cache_read token fatturati al modello principale nella sessione, la cifra confrontabile con i "tokens in the main context" di Spotify. A = Claude Code puro, B = hook + subagent + regola in CLAUDE.md.
| scenario | contesto principale A | contesto principale B | variazione | output principale A | output principale B | variazione | costo totale A | costo totale B | variazione | durata A s | durata B s | variazione |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 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% |
| tutti e 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% |
Protocollo: due cloni shallow byte-identici di fastify/fastify a ac28821d; repo-shunt aggiunge .claude/ (settings, due file agent, tre hook) e una regola di routing in CLAUDE.md, nient'altro. Ogni sessione: claude -p "<prompt>" --output-format json --setting-sources project --strict-mcp-config con una configurazione MCP vuota, senza --model, timeout di 600 s. Quattro prompt, identici su entrambi i lati: S1 export di lib/reply.js, S2 call graph su tre file di lib, S3 metodi di lib/hooks.js contro la copertura in test/hooks.test.js, S4 scrivere test/head-route.test.js seguendo test/noop-set.test.js. Le cifre sono lette da modelUsage e total_cost_usd del JSON di sessione, non arrotondate. Le risposte sono state verificate sul sorgente con grep; i test generati sono stati eseguiti con node --test.
Da fare lunedì
- Esegui
wc -lsul tuo repo e conta i file oltre le 350 righe. Se il numero è vicino a zero, fermati qui: la soglia esiste perché la delega costa più di quanto fa risparmiare sui file piccoli. - Aggiungi al tuo CLAUDE.md una regola di routing di tre righe: i file oltre la soglia vanno a un subagent reader, il codice che segue un pattern a un subagent writer, debugging e architettura restano al modello principale. Nelle misure questa regola ha fatto tutto il lavoro.
- Crea
.claude/agents/Explore.mdconmodel: haikue.claude/agents/code-writer.mdconmodel: sonnetnel frontmatter, così il modello del worker è fissato nel file e non lasciato all'orchestratore. - Scrivi l'hook PreToolUse su Read come rete di sicurezza, che restituisca la decisione deny nel formato JSON attuale degli hook, e fai terminare le sue prime righe con exit 0 quando
agent_typeè uno dei tuoi worker. - Estendi l'hook Bash oltre
cat,headetail: intercetta gli intervallised -necat -nsui file grandi, lascia passare i comandi con pipe e grep. - Esegui una domanda reale con e senza la cartella
.claude/, daclaude -p --output-format json, e confrontatotal_cost_usdeduration_ms, non solo la colonna dell'input. - Controlla con grep due risposte delegate sul sorgente prima di fidarti del riassunto del reader; conta il turno di verifica del modello principale come parte del costo.
- Misura anche una sessione a più turni: i risultati a turno singolo lasciano il contesto principale tra 50k e 119k token contro 84k-414k senza delega, quindi la seconda domanda dovrebbe partire più economica, ma non è stato misurato.
Per approfondire
- Leggi il formato degli hook e il campo
agent_typenel riferimento ufficiale prima di copiare un hook da un blog; la forma del deny e i campi su stdin rendono possibile l'esenzione dei subagent s5. - La documentazione dei subagent copre il campo frontmatter
modele le restrizioni sui tool, che è il modo per tenere un reader in sola lettura ed economico s6. - La sezione "What doesn't work" di Spotify è la parte più utile del post: niente editing delegato (niente numeri di riga affidabili nei riassunti), niente ragionamento delegato (un bug di thread safety mancato), round trip da 10 a 30 secondi s1.
- Il README di Shunt mostra la struttura a tre livelli (hook, script, skill) e i testi delle skill che dicono al modello quando delegare; la prosa delle skill è la parte da adattare, non l'hook s2.
- Le mode di Portal sono un livello di configurazione sopra un modello più un system prompt; la stessa idea si mappa su un file agent di Claude Code con un campo
models4. - Il thread su Hacker News è dove sono state sollevate per prime le domande sulla misura, ed è una buona checklist di cosa chiedere a qualsiasi dichiarazione di risparmio di token s7.
- Un thread su Reddit documenta un orchestratore che ha avviato cinque worker sul proprio costoso modello; fissa il modello nel file agent e, se vuoi una garanzia rigida, nega le chiamate Agent senza campo
models8.
Fonti
- Portal by Spotify cut my Claude Code token usage by 90%, Spotify Engineering. Perché leggerla: l'affermazione originale, il design a tre livelli e una sezione onesta sui limiti che ridimensiona il titolo.
- Shunt plugin (spotify/portal-ai-plugins), GitHub. Perché leggerla: gli hook, gli script e i testi delle skill reali, abbastanza brevi da leggerli per intero.
- check-file-size hook source, GitHub. Perché leggerla: il controllo da 350 righe in poche righe di shell, il modello per il tuo deny.
- Portal Modes documentation, Spotify. Perché leggerla: cos'è una "mode", per capire perché si mappa su un file subagent.
- Claude Code hooks reference, Anthropic. Perché leggerla: il formato deny attuale e i campi su stdin, compreso quello che identifica un subagent.
- Claude Code subagents, Anthropic. Perché leggerla: il campo frontmatter
modele le allowlist di tool per un worker economico in sola lettura. - Hacker News discussion of the Spotify post, Hacker News. Perché leggerla: le domande su cosa misura il 90%, poste prima che qualcuno rimisurasse.
- Fable spawned five Fable agents instead of Opus (r/ClaudeCode), Reddit. Perché leggerla: il caso di errore che un campo
modelfissato previene.
FAQ
Il numero del 90% è sbagliato?
Misura una cosa sola: token di input stimati nel contesto principale per scenari di lettura massiva su grandi file Java. Su una metrica dello stesso tipo la ricostruzione ha visto dal 41.9% al 79.4% sugli scenari di lettura. Non dice nulla su costo, tempo o qualità delle risposte, e il post non sostiene il contrario.
Mi serve Portal per ottenerlo?
No. Il routing sta in una regola di CLAUDE.md, due file agent con modello fissato e un hook PreToolUse. Portal fornisce i modelli dei worker in Spotify; una riga model: haiku fa lo stesso lavoro in Claude Code puro.
Quando la delega costa di più?
Quando i file sono piccoli. Lo scenario di scrittura di un test da 45 righe è costato il 2.6% in più con la delega perché il writer è un secondo contesto completo e il modello principale ha comunque riletto e testato il risultato. Anche ogni esecuzione delegata è stata più lenta, +65.3% in media.
Perché l'hook non è mai scattato?
Perché la regola di routing in CLAUDE.md ha spinto il modello principale a controllare wc -l e a delegare prima di provare a leggere. L'hook conta solo quando il modello ignora la regola, cosa che è successa nella verifica senza CLAUDE.md.
AIDive