AIDive

Video-Paket

Spotifys 90% Token-Ersparnis bei Claude Code nachgebaut und gemessen: Hook, Subagents

11 Min. Lesezeit

TL;DR

  • Spotifys "90 %" ist der Mittelwert von Bulk-Read-Szenarien, gemessen in geschätzten Input-Tokens auf einem Java-Monorepo. Der Post nennt weder einen Dollarbetrag noch einen Qualitätswert.
  • Nachgebaut in reinem Claude Code (ein PreToolUse-Hook, zwei günstige Subagents, eine Routing-Regel in drei Zeilen) und auf Fastify über vier Szenarien und 16 Läufe gemessen, senkte das Muster den Kontext des Hauptmodells um 59.6% und die Gesamtkosten um 33.1%.
  • Die Deny-Hooks haben in den gemessenen Läufen keinmal ausgelöst. Die Ersparnis kam von der Routing-Regel in der CLAUDE.md; die Hooks sind das Sicherheitsnetz für den Tag, an dem das Modell sie ignoriert.
  • Die Delegation war jedes Mal langsamer, im Schnitt +65.3% Wandzeit. Beim kleinen Szenario "Test schreiben" kostete sie 2.6% mehr.
  • Zwei Fallen: Hooks feuern auch innerhalb von Subagents, also nimm deine Worker aus; und sed -n-Bereichslesungen gehen glatt durch einen Hook, der nur cat, head und tail beobachtet.
  • Die Zusammenfassung des Haiku-Readers enthielt in zwei von acht delegierten Läufen Sachfehler. Behalte den Verifikationsschritt des Hauptmodells.

Was die Messungen zeigen

Spotifys Plugin Shunt leitet Bulk-Arbeit über zwei "Modes" vom Hauptmodell weg: einen Bulk-Reader und einen Code-Writer, in den Beispielen beide mit Gemini 2.5 Flash, wobei das Modellfeld jedes in der Portal-Instanz konfigurierte Modell akzeptiert s1. Das Routing hat drei Schichten. Ein check-file-size-Hook feuert bei jedem Read und blockiert Dateien über einer konfigurierbaren Zeilenschwelle (Standard 350), woraufhin das Modell auf den Bulk-Reader-Skill verwiesen wird; ein check-bash-read-Hook fängt cat, head, tail, less und more auf großen Dateien ab, während Pipe-Befehle durchgehen s1. Die Hook-Quellen und die beiden Skills liegen im öffentlichen Repo s2, die Größenprüfung ist separat lesbar s3. Die Modes selbst leben in Portal, Spotifys interner Plattform, weshalb sich das Plugin in der ausgelieferten Form außerhalb der Firma nicht betreiben lässt s4.

Die Benchmark-Behauptung ist dünn. Spotify hat vier Szenarien auf einem Java-Monorepo getestet, "measuring tokens Claude would consume reading files directly vs. consuming the bulk-reader's summary", und meldet mittlere Bulk-Read-Einsparungen von rund 90% s1. Der Post selbst sagt, das Code-Write-Szenario sei in Tokens schwerer zu messen, die Zusammenfassungen der Worker enthielten keine verlässlichen Zeilennummern, weshalb sich das Editieren nicht delegieren lasse, der Worker habe einen subtilen Thread-Safety-Bug übersehen, den das Hauptmodell fand, und jede Delegation koste 10 bis 30 Sekunden, wobei Portal einen einzelnen Aufruf auf 30 Sekunden deckelt s1. Der Hacker-News-Thread stellte dieselben Fragen dazu, was die 90% eigentlich messen s7.

Der Nachbau ersetzt die Portal-Modes durch zwei Claude-Code-Subagents, deren Definitionsdateien das Modell festlegen: ein Explore-Reader auf Haiku und ein Code-Writer auf Sonnet s6. Das Deny ist ein PreToolUse-Hook, der die Deny-Entscheidung im aktuellen Hook-JSON-Format zurückgibt s5. Getestet wurde fastify/fastify bei Commit ac28821d, 294 .js/.ts-Dateien, 78270 Zeilen, 63 Dateien über 350 Zeilen. Modell-IDs laut Session-JSON: Hauptkonversation claude-opus-5[1m], Reader claude-haiku-4-5-20251001, Writer claude-sonnet-5. Jedes Szenario lief pro Konfiguration zweimal, 16 gemessene Läufe, Single-Turn-claude -p-Sessions mit reinen Projekt-Settings, damit beide Seiten denselben System-Prompt hatten s5.

Wo das Muster gewann: S2, eine Call-Graph-Frage über drei Dateien (lib/route.js mit 691 Zeilen, lib/reply.js mit 1090 und lib/request.js mit 398), ging von einem mittleren Hauptkontext von 357165.5 Tokens auf 73440.0 (-79.4%) und von 0.5810500000000001 USD auf 0.21823605000000001 USD (-62.4%). Wo nicht: S4, das Schreiben eines Tests für eine 45-Zeilen-Quelle anhand einer 19-Zeilen-Referenz, kostete 0.29465575 USD ohne Delegation und 0.3022213 USD mit (+2.6%), weil Sonnet ein zweiter voller Kontext ist (13004 bis 18729 Cache-Read-Tokens) und das Hauptmodell die erzeugte Datei trotzdem erneut las und den Test ausführte s6.

Drei Befunde zählen mehr als die Prozentwerte. Erstens: Die Hooks feuerten in den 16 gemessenen Läufen keinmal. Mit der Routing-Regel in der CLAUDE.md führte das Hauptmodell wc -l aus und delegierte von selbst. Das einzige beobachtete Deny stammte aus einem Verifikationslauf ohne CLAUDE.md, in dem dem Modell Read verweigert wurde, dann cat -n, und es allein mit grep -n antwortete, ohne je das Agent-Tool aufzurufen s5. Zweitens: Hooks laufen auch in Subagents. In zwei verworfenen Läufen wurde der Haiku-Reader selbst von der Größenprüfung abgewiesen und wich auf stückweise offset/limit-Lesungen aus. Die Lösung ist eine Ausnahme case "$agent_type" in Explore|code-writer) exit 0 ganz oben im Hook, wobei der Feldname aus dem geloggten stdin bestätigt wurde s5. Drittens: In der Baseline-Konfiguration benutzte das Hauptmodell das Read-Tool überhaupt nie. Es las jede Datei über Bash (cat -n, sed -n '1,200p', sed -n '200,560p'), ein Hook, der nur Read beobachtet, fängt also nichts, und ein Bash-Hook, der nur cat, head und tail ohne Pipe matcht, lässt sed -n-Bereiche weiterhin durch s3.

Die Qualität wurde per grep gegen die Quelle geprüft. Die Baseline lieferte in einem S2-Lauf falsche Zeilennummern (sie gab Dateien mit sed -n ohne Zeilennummern aus und zählte von Hand). Die delegierte Konfiguration lieferte im anderen S2-Lauf drei und in einem S3-Lauf zwei Sachfehler, alle darauf zurückzuführen, dass die Haiku-Zusammenfassung für bare Münze genommen wurde: falsche Aufrufer von buildRequest/buildReply, eine nicht exportierte Konstante als Export aufgeführt, ein abgedeckter Iterator als nicht abgedeckt markiert. Wo das Hauptmodell Output-Tokens für die erneute Prüfung per grep ausgab (S3, 4534 bis 4738 Output-Tokens), hielten die Antworten s6. Im Test-Szenario bestand jede erzeugte Datei: 12/12, 6/6, 7/7 und 10/10 Tests. Ein Reddit-Bericht zeigt den verwandten Fehlerfall eines Hauptmodells, das Worker auf dem falschen Modell startet, wenn nichts es festlegt s8; genau davor schützen der require-model-Hook und das model:-Feld in den Agent-Dateien.

Messungen

Mittelwerte der 2 Läufe pro Zelle. "Hauptkontext" sind Input + cache_creation + cache_read Tokens, die dem Hauptmodell über die Session berechnet wurden, die mit Spotifys "tokens in the main context" vergleichbare Größe. A = reines Claude Code, B = Hook + Subagents + CLAUDE.md-Regel.

Szenario Hauptkontext A Hauptkontext B Änderung Haupt-Output A Haupt-Output B Änderung Gesamtkosten A Gesamtkosten B Änderung Dauer A s Dauer B s Änderung
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%
alle 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%

Protokoll: zwei byte-identische Shallow-Clones von fastify/fastify bei ac28821d; repo-shunt fügt .claude/ (Settings, zwei Agent-Dateien, drei Hooks) und eine CLAUDE.md-Routing-Regel hinzu, sonst nichts. Jede Session: claude -p "<prompt>" --output-format json --setting-sources project --strict-mcp-config mit leerer MCP-Konfiguration, ohne --model, 600 s Timeout. Vier Prompts, auf beiden Seiten identisch: S1 Exports von lib/reply.js, S2 Call-Graph über drei lib-Dateien, S3 Methoden von lib/hooks.js gegenüber der Abdeckung in test/hooks.test.js, S4 test/head-route.test.js nach dem Vorbild von test/noop-set.test.js schreiben. Die Werte stammen ungerundet aus modelUsage und total_cost_usd der Session-JSON. Die Antworten wurden per grep gegen die Quelle geprüft; die erzeugten Tests liefen mit node --test.

Das machst du am Montag

  • Lass wc -l über dein Repo laufen und zähle die Dateien über 350 Zeilen. Liegt die Zahl nahe null, hör hier auf: Die Schwelle existiert, weil Delegation bei kleinen Dateien mehr kostet, als sie spart.
  • Füge deiner CLAUDE.md eine Routing-Regel in drei Zeilen hinzu: Dateien über der Schwelle gehen an einen Reader-Subagent, musterfolgender Code an einen Writer-Subagent, Debugging und Architektur bleiben beim Hauptmodell. In den Messungen hat diese Regel die ganze Arbeit erledigt.
  • Lege .claude/agents/Explore.md mit model: haiku und .claude/agents/code-writer.md mit model: sonnet im Frontmatter an, damit das Worker-Modell in der Datei festgelegt ist und nicht dem Orchestrator überlassen bleibt.
  • Schreibe den PreToolUse-Hook auf Read als Sicherheitsnetz, der die Deny-Entscheidung im aktuellen Hook-JSON-Format zurückgibt, und lass seine ersten Zeilen mit exit 0 enden, wenn agent_type einer deiner Worker ist.
  • Erweitere den Bash-Hook über cat, head und tail hinaus: Matche sed -n-Bereiche und cat -n auf großen Dateien, lass Pipe- und grep-Befehle durch.
  • Stelle eine echte Frage mit und ohne den .claude/-Ordner über claude -p --output-format json und vergleiche total_cost_usd und duration_ms, nicht allein die Input-Spalte.
  • Prüfe zwei delegierte Antworten per grep gegen die Quelle, bevor du der Zusammenfassung des Readers traust; plane den Verifikationsschritt des Hauptmodells als Teil der Kosten ein.
  • Miss auch eine Multi-Turn-Session: Die Single-Turn-Ergebnisse lassen den Hauptkontext bei 50k bis 119k Tokens gegenüber 84k bis 414k ohne Delegation, die zweite Frage sollte also günstiger starten, das wurde aber nicht gemessen.

Weiterlesen

  • Lies das Hook-Format und das Feld agent_type in der offiziellen Referenz, bevor du einen Hook aus einem Blogpost kopierst; die Deny-Form und die Felder auf stdin machen die Subagent-Ausnahme erst möglich s5.
  • Die Subagent-Dokumentation behandelt das Frontmatter-Feld model und Tool-Beschränkungen, womit sich ein Reader schreibgeschützt und günstig halten lässt s6.
  • Spotifys eigener Abschnitt "What doesn't work" ist der nützlichste Teil des Posts: kein delegiertes Editieren (keine verlässlichen Zeilennummern in Zusammenfassungen), kein delegiertes Denken (ein übersehener Thread-Safety-Bug), 10 bis 30 Sekunden pro Roundtrip s1.
  • Die Shunt-README zeigt die dreischichtige Struktur (Hooks, Skripte, Skills) und die Skill-Texte, die dem Modell sagen, wann es delegieren soll; die Skill-Prosa ist der Teil, den man anpassen sollte, nicht der Hook s2.
  • Portal-Modes sind eine Konfigurationsschicht über einem Modell plus System-Prompt; dieselbe Idee lässt sich auf eine Claude-Code-Agent-Datei mit model-Feld abbilden s4.
  • Im Hacker-News-Thread wurden die Messfragen zuerst gestellt, und er taugt als Checkliste dafür, was man von jeder Token-Spar-Behauptung verlangen sollte s7.
  • Ein Reddit-Thread dokumentiert einen Orchestrator, der fünf Worker auf seinem eigenen teuren Modell startete; leg das Modell in der Agent-Datei fest und, wenn du eine harte Garantie willst, verweigere Agent-Aufrufe ohne model-Feld s8.

Quellen

FAQ

Ist die 90%-Zahl falsch?

Sie misst eine einzige Sache: geschätzte Input-Tokens im Hauptkontext bei Bulk-Read-Szenarien auf großen Java-Dateien. Auf einer ähnlichen Metrik sah der Nachbau 41.9% bis 79.4% bei Lese-Szenarien. Über Kosten, Zeit oder Antwortqualität sagt sie nichts, und der Post behauptet auch nichts anderes.

Brauche ich Portal dafür?

Nein. Das Routing steckt in einer CLAUDE.md-Regel, zwei Agent-Dateien mit festgelegtem Modell und einem PreToolUse-Hook. Portal liefert bei Spotify die Worker-Modelle; eine Zeile model: haiku erledigt dasselbe in reinem Claude Code.

Wann kostet Delegation mehr?

Bei kleinen Dateien. Das Test-Szenario mit 45 Zeilen kostete mit Delegation 2.6% mehr, weil der Writer ein zweiter voller Kontext ist und das Hauptmodell das Ergebnis trotzdem erneut las und testete. Jeder delegierte Lauf war außerdem langsamer, im Schnitt +65.3%.

Warum hat der Hook nie ausgelöst?

Weil die Routing-Regel in der CLAUDE.md das Hauptmodell dazu brachte, wc -l zu prüfen und zu delegieren, bevor es zu lesen versuchte. Der Hook zählt nur, wenn das Modell die Regel ignoriert, was im Verifikationslauf ohne CLAUDE.md geschah.