TL;DR
- Les « 90% » de Spotify sont la moyenne de scénarios de lecture en masse, mesurés en tokens d'entrée estimés sur un monorepo Java. Le post ne donne ni chiffre en dollars ni score de qualité.
- Reconstruit dans Claude Code standard (un hook PreToolUse, deux sous-agents bon marché, une règle de routage de trois lignes) et mesuré sur Fastify, sur quatre scénarios et 16 exécutions, le pattern a réduit le contexte du modèle principal de 59.6% et le coût total de 33.1%.
- Les hooks de refus ne se sont déclenchés aucune fois pendant les exécutions mesurées. Les économies venaient de la règle de routage dans CLAUDE.md ; les hooks sont le filet de sécurité pour le jour où le modèle l'ignore.
- La délégation était plus lente à chaque fois, +65.3% de temps réel en moyenne. Sur le petit scénario d'écriture de test, elle a coûté 2.6% de plus.
- Deux pièges : les hooks se déclenchent aussi dans les sous-agents, donc exemptez vos workers, et les lectures par plage
sed -npassent tout droit à travers un hook qui ne surveille quecat,headettail. - Le résumé du lecteur Haiku contenait des erreurs factuelles dans deux des huit exécutions déléguées. Gardez le tour de vérification du modèle principal.
Ce que disent les mesures
Le plugin de Spotify, Shunt, détourne le travail en masse du modèle principal via deux « modes » : un lecteur en masse et un rédacteur de code, tous deux sous Gemini 2.5 Flash dans les exemples, le champ model acceptant n'importe quel modèle configuré dans l'instance Portal s1. Le routage comporte trois couches. Un hook check-file-size se déclenche à chaque Read et bloque les fichiers au-delà d'un seuil de lignes configurable, 350 par défaut, en renvoyant le modèle vers la skill de lecture en masse ; un hook check-bash-read attrape cat, head, tail, less et more sur les gros fichiers, tandis que les commandes avec pipe passent s1. Les sources des hooks et les deux skills sont dans le dépôt public s2, et la vérification de taille se lit seule s3. Les modes eux-mêmes vivent dans Portal, la plateforme interne de Spotify, ce qui explique que le plugin ne puisse pas tourner hors de l'entreprise tel que livré s4.
L'affirmation du benchmark est mince. Spotify a testé quatre scénarios sur un monorepo Java, en « mesurant les tokens que Claude consommerait en lisant les fichiers directement face à ceux du résumé du lecteur en masse », et rapporte des économies moyennes de lecture en masse d'environ 90% s1. Le post lui-même dit que le scénario d'écriture de code est plus difficile à mesurer en tokens, que les résumés des workers n'incluent pas de numéros de ligne fiables et que l'édition ne peut donc pas être déléguée, que le worker a raté un bug subtil de thread-safety que le modèle principal a repéré, et que chaque délégation ajoute 10 à 30 secondes, Portal plafonnant une invocation à 30 secondes s1. Le fil Hacker News a soulevé les mêmes questions sur ce que mesurent les 90% s7.
La reconstruction remplace les modes Portal par deux sous-agents Claude Code dont les fichiers de définition fixent le modèle : un lecteur Explore sur Haiku et un rédacteur de code sur Sonnet s6. Le refus est un hook PreToolUse qui renvoie la décision deny au format JSON de hook actuel s5. Le dépôt testé était fastify/fastify au commit ac28821d, 294 fichiers .js/.ts, 78270 lignes, 63 fichiers de plus de 350 lignes. Identifiants de modèle tels que rapportés par le JSON de session : conversation principale claude-opus-5[1m], lecteur claude-haiku-4-5-20251001, rédacteur claude-sonnet-5. Chaque scénario a tourné deux fois par configuration, soit 16 exécutions mesurées, en sessions claude -p à un seul tour avec des réglages limités au projet, pour que les deux côtés aient un prompt système identique s5.
Là où le pattern a gagné : S2, une question de graphe d'appels sur trois fichiers, lib/route.js (691 lignes), lib/reply.js (1090) et lib/request.js (398), est passé d'un contexte principal moyen de 357165.5 tokens à 73440.0 (-79.4%) et de 0.5810500000000001 USD à 0.21823605000000001 USD (-62.4%). Là où il n'a pas gagné : S4, l'écriture d'un test pour une source de 45 lignes à partir d'une référence de 19 lignes, a coûté 0.29465575 USD sans délégation et 0.3022213 USD avec (+2.6%), parce que Sonnet est un second contexte complet (13004 à 18729 tokens de cache-read) et que le modèle principal a quand même relu le fichier généré et lancé le test s6.
Trois constats comptent plus que les pourcentages. Premièrement, les hooks ne se sont déclenchés aucune fois sur les 16 exécutions mesurées : avec la règle de routage dans CLAUDE.md, le modèle principal a lancé wc -l et délégué de lui-même. Le seul refus observé venait d'une exécution de vérification sans CLAUDE.md, où le modèle s'est vu refuser Read, puis cat -n, et a répondu à partir de grep -n seul sans jamais appeler l'outil Agent s5. Deuxièmement, les hooks s'exécutent dans les sous-agents : dans deux exécutions écartées, le lecteur Haiku a lui-même été refusé par la vérification de taille et est retombé sur des lectures par morceaux avec offset/limit. Le correctif est une échappatoire case "$agent_type" in Explore|code-writer) exit 0 en tête du hook, avec le nom du champ confirmé dans le stdin journalisé s5. Troisièmement, dans la configuration de référence, le modèle principal n'a jamais utilisé l'outil Read. Il a lu chaque fichier via Bash (cat -n, sed -n '1,200p', sed -n '200,560p'), donc un hook qui ne surveille que Read n'attrape rien, et un hook Bash qui ne cible que cat, head et tail sans pipe laisse quand même passer les plages sed -n s3.
La qualité a été vérifiée contre la source avec grep. La référence a produit des numéros de ligne faux dans une exécution S2 (elle affichait les fichiers avec sed -n sans numéros de ligne et comptait à la main). La configuration déléguée a produit trois erreurs factuelles dans l'autre exécution S2 et deux dans une exécution S3, toutes dues au résumé Haiku pris au pied de la lettre : mauvais appelants pour buildRequest/buildReply, une constante non exportée listée comme export, un itérateur couvert marqué non couvert. Là où le modèle principal a dépensé des tokens de sortie à revérifier avec grep (S3, 4534 à 4738 tokens de sortie), les réponses tenaient s6. Sur le scénario d'écriture de test, chaque fichier généré a passé : 12/12, 6/6, 7/7 et 10/10 tests. Un rapport Reddit illustre le mode d'échec voisin d'un modèle principal qui lance des workers sur le mauvais modèle quand rien ne le fixe s8, ce que le hook require-model et le champ model: des fichiers d'agent préviennent.
Mesures
Moyennes des 2 exécutions par cellule. « Main context » est la somme input + cache_creation + cache_read facturée au modèle principal sur la session, le chiffre comparable aux « tokens dans le contexte principal » de Spotify. A = Claude Code standard, B = hook + sous-agents + règle CLAUDE.md.
| scénario | contexte principal A | contexte principal B | variation | sortie principale A | sortie principale B | variation | coût total A | coût total B | variation | durée A s | durée B s | variation |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 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% |
Protocole : deux clones superficiels identiques octet pour octet de fastify/fastify à ac28821d ; repo-shunt ajoute .claude/ (réglages, deux fichiers d'agent, trois hooks) et une règle de routage CLAUDE.md, rien d'autre. Chaque session : claude -p "<prompt>" --output-format json --setting-sources project --strict-mcp-config avec une config MCP vide, sans --model, timeout de 600 s. Quatre prompts, identiques des deux côtés : S1 exports de lib/reply.js, S2 graphe d'appels sur trois fichiers lib, S3 méthodes de lib/hooks.js face à la couverture de test/hooks.test.js, S4 écrire test/head-route.test.js en suivant test/noop-set.test.js. Les chiffres sont lus dans modelUsage et total_cost_usd du JSON de session, non arrondis. Les réponses ont été vérifiées contre la source avec grep ; les tests générés ont été lancés avec node --test.
À faire lundi
- Lancez
wc -lsur votre dépôt et comptez les fichiers de plus de 350 lignes. Si le compte est proche de zéro, arrêtez-vous ici : le seuil existe parce que la délégation coûte plus qu'elle ne rapporte sur les petits fichiers. - Ajoutez à votre CLAUDE.md une règle de routage de trois lignes : les fichiers au-dessus du seuil vont à un sous-agent lecteur, le code qui suit un pattern va à un sous-agent rédacteur, le débogage et l'architecture restent au modèle principal. Dans les mesures, c'est cette règle qui a fait tout le travail.
- Créez
.claude/agents/Explore.mdavecmodel: haikuet.claude/agents/code-writer.mdavecmodel: sonnetdans le frontmatter, pour que le modèle du worker soit fixé dans le fichier et non laissé à l'orchestrateur. - Écrivez le hook PreToolUse sur Read comme filet de sécurité, renvoyant la décision deny au format JSON de hook actuel, et faites que ses premières lignes fassent exit 0 quand
agent_typeest l'un de vos workers. - Étendez le hook Bash au-delà de
cat,headettail: ciblez les plagessed -netcat -nsur les gros fichiers, laissez passer les commandes avec pipe et grep. - Lancez une vraie question avec et sans le dossier
.claude/, viaclaude -p --output-format json, et compareztotal_cost_usdetduration_ms, pas seulement la colonne d'entrée. - Vérifiez deux réponses déléguées contre la source avec grep avant de faire confiance au résumé du lecteur ; budgétez le tour de vérification du modèle principal dans le coût.
- Mesurez aussi une session multi-tours : les résultats à un seul tour laissent le contexte principal entre 50k et 119k tokens contre 84k à 414k sans délégation, donc la deuxième question devrait démarrer moins cher, mais cela n'a pas été mesuré.
Aller plus loin
- Lisez le format de hook et le champ
agent_typedans la référence officielle avant de copier un hook depuis un article de blog ; la forme du deny et les champs sur stdin sont ce qui rend possible l'exemption des sous-agents s5. - La documentation des sous-agents couvre le champ de frontmatter
modelet les restrictions d'outils, ce qui permet de garder un lecteur en lecture seule et bon marché s6. - La section « What doesn't work » de Spotify est la partie la plus utile du post : pas d'édition déléguée (pas de numéros de ligne fiables dans les résumés), pas de raisonnement délégué (un bug de thread-safety raté), des allers-retours de 10 à 30 secondes s1.
- Le README de Shunt montre la structure en trois couches (hooks, scripts, skills) et les textes de skills qui disent au modèle quand déléguer ; la prose des skills est la partie à adapter, pas le hook s2.
- Les modes Portal sont une couche de configuration au-dessus d'un modèle et d'un prompt système ; la même idée se traduit en fichier d'agent Claude Code avec un champ
models4. - Le fil Hacker News est l'endroit où les questions de mesure ont été soulevées en premier, et c'est une bonne checklist de ce qu'il faut demander à toute affirmation d'économie de tokens s7.
- Un fil Reddit documente un orchestrateur lançant cinq workers sur son propre modèle coûteux ; fixez le modèle dans le fichier d'agent et, si vous voulez une garantie ferme, refusez les appels Agent sans champ
models8.
Sources
- Portal by Spotify cut my Claude Code token usage by 90%, Spotify Engineering. À lire pour : l'affirmation d'origine, la conception en trois couches et une section de limites honnête qui nuance le titre.
- Shunt plugin (spotify/portal-ai-plugins), GitHub. À lire pour : les vrais hooks, scripts et textes de skills, assez courts pour être lus en entier.
- check-file-size hook source, GitHub. À lire pour : la vérification des 350 lignes en quelques lignes de shell, le modèle pour votre propre deny.
- Portal Modes documentation, Spotify. À lire pour : ce qu'est un « mode », pour voir pourquoi il correspond à un fichier de sous-agent.
- Claude Code hooks reference, Anthropic. À lire pour : le format de deny actuel et les champs stdin, dont celui qui identifie un sous-agent.
- Claude Code subagents, Anthropic. À lire pour : le champ de frontmatter
modelet les listes d'outils autorisés pour un worker bon marché en lecture seule. - Hacker News discussion of the Spotify post, Hacker News. À lire pour : les questions sur ce que mesurent les 90%, posées avant que quiconque ne remesure.
- Fable spawned five Fable agents instead of Opus (r/ClaudeCode), Reddit. À lire pour : le mode d'échec qu'un champ
modelfixé évite.
FAQ
Le chiffre de 90% est-il faux ?
Il mesure une seule chose : les tokens d'entrée estimés dans le contexte principal pour des scénarios de lecture en masse sur de gros fichiers Java. Sur le même type de métrique, la reconstruction a obtenu de 41.9% à 79.4% sur les scénarios de lecture. Il ne dit rien du coût, du temps ou de la qualité des réponses, et le post ne prétend pas le contraire.
Ai-je besoin de Portal pour obtenir ça ?
Non. Le routage tient dans une règle CLAUDE.md, deux fichiers d'agent avec un modèle fixé et un hook PreToolUse. Portal fournit les modèles de workers chez Spotify ; une ligne model: haiku fait le même travail dans Claude Code standard.
Quand la délégation coûte-t-elle plus cher ?
Quand les fichiers sont petits. Le scénario d'écriture de test sur 45 lignes a coûté 2.6% de plus avec délégation, parce que le rédacteur est un second contexte complet et que le modèle principal a quand même relu et testé le résultat. Chaque exécution déléguée était aussi plus lente, +65.3% en moyenne.
Pourquoi le hook ne s'est-il jamais déclenché ?
Parce que la règle de routage dans CLAUDE.md a poussé le modèle principal à vérifier wc -l et à déléguer avant d'essayer de lire. Le hook ne compte que quand le modèle ignore la règle, ce qui est arrivé dans l'exécution de vérification sans CLAUDE.md.
AIDive