AIDive

Spotify ścięło tokeny Claude Code o 90%? Sprawdziłem: 60%

AIDive · Opublikowano

Agenty do kodowaniaModele AI

Dziewięćdziesiąt procent i zdanie, które to sprzedało

Setup Spotify dla Claude Code to wpis na blogu Dimitriego Mazmanova, product managera w Spotify, z kodem na GitHubie: pisze on, że konfiguracja używana przez jego zespół ścięła jego zużycie tokenów Claude Code o 90%. Pierwsze zdanie niesie całą argumentację: większość tego, co robi agent AI do kodowania, to nie myślenie, tylko I/O. Czytanie pięciu plików, żeby odpowiedzieć na pytanie o jedną metodę, albo pisanie dwudziestego pierwszego pliku testowego, który kopiuje dwadzieścia leżących obok, pochłania tysiące tokenów przy niemal zerowym rozumowaniu.

Jeden tweet zaniósł wpis do półtora miliona wyświetleń na jednym zdaniu: spisane reguły to sugestia, blokada nie. Hacker News wyniósł go na stronę główną, 271 punktów i 173 komentarze, a połowa komentarzy zadawała to samo pytanie: 90% czego? Własne zastrzeżenie Spotify brzmi „bulk read”. Ten artykuł odtwarza setup w czystym Claude Code, a potem go mierzy, żebyś dokładnie wiedział, co to zastrzeżenie kupuje.

Czym naprawdę jest Portal (i dlaczego go nie uruchomisz)

Portal nie jest routerem. To wewnętrzny portal deweloperski Spotify, zbudowany na Backstage, platformie deweloperskiej, którą Spotify udostępniło jako open source. Istotna funkcja w jego wnętrzu nazywa się Modes: według definicji Spotify mode to deklaratywny agent uruchamiany na efemerycznym runtime, z grubsza AWS Lambda dla agentów. Piszesz instrukcje, wybierasz model, ustawiasz temperaturę, podpinasz narzędzia. Mazmanov zbudował dwa: bulk readera i code writera, oba na Gemini Flash przy temperaturze 0.2, więc oba są celowo tanie i nudne.

Routing mieszka w pluginie Claude Code o nazwie Shunt. Jest publiczny na GitHubie i instaluje się dwoma poleceniami. Krok drugi jednak uwierzytelnia wiersz poleceń Portalu wobec twojej instancji Portalu, a ty takiej nie masz. Plugin jest publiczny; to, do czego deleguje, nie jest.

Sensowny ruch to więc zapomnieć o pluginie i zachować wzorzec. Ma trzy warstwy, jego własnymi słowami: hooki, skrypty, skille. Każda z nich ma odpowiednik w czystym Claude Code i właśnie to buduje i mierzy reszta tego artykułu.

Warstwa pierwsza: hook, który blokuje zamiast pytać

Wersja 1 setupu była blokiem reguł routingu w pliku instrukcji projektu. Słowami Mazmanova „jako tako działała”: reguły były doradcze, nie egzekwowane, Claude mógł je zignorować, a każdy projekt potrzebował własnej kopii. Wersja 2 przenosi decyzję z prompta do warstwy narzędzi za pomocą dwóch hooków, oba odpalają się przed wywołaniem narzędzia. Jeden pilnuje każdego odczytu pliku, drugi pilnuje shella.

Hook odczytu to 33 linie basha. Czyta próg ze środowiska, domyślnie 350 linii, a potem przepuszcza trzy rzeczy:

  • Odczyt z offsetem lub limitem, bo Claude już wie, czego potrzebuje.
  • Plik, który nie istnieje.
  • Plik na progu lub poniżej, bo delegowanie czegoś małego kosztuje więcej niż przeczytanie tego.

Wszystko inne jest blokowane, z komunikatem, który Claude czyta zamiast pliku: ten plik ma tyle linii, użyj skilla bulk readera, a jeśli potrzebujesz dokładnej treści do edycji, przeczytaj ponownie tylko ten fragment. Hook shella łapie cat, head, tail, less i more na dużym pliku. Polecenie z pipe przechodzi, bo przekierowanie do grepa to odczyt celowany.

Uwaga Mazmanova o warstwowości jest tu najważniejsza: nawet jeśli Claude nigdy nie przeczyta opisu skilla, hook i tak zablokuje kosztowny odczyt. Skill sprawia, że przekierowanie jest płynniejsze; blokada sprawia, że jest realne. Jeden szczegół będzie ważny później: skrypt odpowiada decyzją najwyższego poziomu o nazwie „block”. Zapamiętaj to słowo.

Warstwy druga i trzecia: workery i ich liczby

Workery to dwa prompty. Reader: „jesteś precyzyjnym analitykiem kodu, zwracaj wyłącznie ustrukturyzowane punkty, bez powitań, bez prozy, każdy punkt zaczynaj od dokładnej nazwy, typu lub numeru linii”. Writer: „dopasuj się dokładnie do istniejących wzorców, nazewnictwa i stylu; zwracaj tylko kod, bez ogrodzeń, bez wyjaśnień”. Bez tej ostatniej linii model opakowuje wszystko w Markdown, który Claude musi potem parsować.

Opakowują je dwa skrypty. Bulk-read bierze pytanie i ścieżki plików i wysyła je dalej. Code-write bierze specyfikację i plik referencyjny i zapisuje wynik prosto na dysk, więc Claude nigdy nie widzi wygenerowanego kodu. Każda delegacja jest jednorazowa: kolejne pytanie wysyła pliki ponownie. To jest darmowe tam, gdzie ma znaczenie, bo korpus trafia do workera i nigdy nie wchodzi do kontekstu Claude'a.

Warstwa trzecia to plik skilla, który mówi Claude'owi, kiedy delegować: pliki powyżej 350 linii, pytania obejmujące trzy lub więcej plików, duże diffy. Jego ostatnia linia brzmi „zweryfikuj numery linii, zanim edytujesz”.

Tabela Spotify obejmuje jedno monorepo w Javie i trzy scenariusze odczytu. Przypadek z jednym plikiem spada z około 34 000 tokenów do poniżej 6 000, a średnia oszczędność w trzech wierszach wynosi 90%.

Benchmark Spotify Wartość
Repozytoria 1 Java monorepo
Scenariusze 3, wszystkie bulk read
Przypadek z jednym plikiem, przed ~34 000 tokenów
Przypadek z jednym plikiem, po < 6 000 tokenów
Średnia oszczędność 90%
Szacowanie tokenów 4 znaki na token
Egzekwowanie dla writera brak (hook ma tylko reader)

Dwa zastrzeżenia drukuje samo Spotify: tokeny są szacowane po cztery znaki każdy, a writer nie ma żadnego egzekwowania. Te 90% to więc średnia z trzech wierszy bulk read w szacowanych tokenach wejściowych, bez oceny jakości i bez żadnej kwoty w dolarach. To jest liczba do sprawdzenia.

Odtworzenie, część pierwsza: subagent z polem model

Claude Code ma wbudowanego subagenta Explore, a od niedawnego wydania dziedziczy on twój główny model, z górnym limitem na Opusie, więc „tani reader” nie jest już tani. Dokumentacja podaje rozwiązanie w jednym zdaniu: projektowy subagent o nazwie Explore nadpisuje wbudowanego i zachowuje własne pole model. Jeden plik markdown, front matter, a w linii model stoi Haiku. To jest bulk reader. Writer to drugi plik: model Sonnet, narzędzia tylko Read i Write, a treść to wklejone instrukcje samego Spotify.

Działa to dlatego, że każdy subagent startuje ze świeżym, odizolowanym oknem kontekstu. To, co czyta, ląduje tam, a nie w głównej rozmowie. To jednorazowa delegacja Spotify minus sieciowa podróż w obie strony.

Potem przychodzi część, której nikt nie planuje. W tym tygodniu na Reddicie Fable dostał polecenie odpalenia agentów Opus i zamiast tego odpalił pięciu agentów Fable: 73% tygodniowego limitu zniknęło w trzydzieści minut. Najwyżej ocenioną odpowiedzią był hook uruchamiany, gdy model wysyła subagenta, który zmusza go do jawnego wyboru modelu i każe wybrać najtańszy zdolny wykonać zadanie. To jest hook numer trzy: pilnuje narzędzia Agent, a wywołanie bez modelu jest odrzucane jednym zdaniem: „wybierz model jawnie”.

Skill Spotify staje się trzema liniami w pliku instrukcji projektu: pliki powyżej 350 linii idą do explorera, boilerplate idzie do writera, każde wywołanie agenta ustawia model. Istnieje też opcja tępa: dwie zmienne środowiskowe, które wymuszają jeden model na każdym subagencie. Uczciwe ograniczenie jest takie, że reader to tańszy model, więc to, co zwraca, jest wszystkim, co główny model wie. Sekcja o pomiarze to obejmuje.

Odtworzenie, część druga: deny w dzisiejszym formacie hooków

Pamiętasz słowo „block”. Skrypt Spotify zwraca decyzję najwyższego poziomu, ale aktualna dokumentacja Claude Code mówi coś innego: hook PreToolUse zwraca decyzję wewnątrz obiektu hookSpecificOutput, a pole nazywa się permissionDecision. Ma cztery wyniki, allow, deny, ask i defer, a tutaj potrzebny jest deny. Cokolwiek hook wpisze jako powód, jest pokazywane Claude'owi, a jeśli odpowiada kilka hooków, deny wygrywa.

Odtworzony hook odczytu zachowuje ten sam próg 350 i te same trzy wyjątki, a zamiast „block” zwraca deny z powodem, który wskazuje subagenta Explore i model do użycia. Jedną pułapkę dokumentacja podaje wprost: hooki z twoich ustawień działają również wewnątrz subagentów. Bez wyjątku reader na Haiku dostaje deny na własne odczyty i nigdy nie może wykonać swojej pracy, więc skrypt sprawdza, kto wywołuje, i przepuszcza oba workery.

Okablowanie to jeden plik ustawień z trzema matcherami, Read, Bash i Agent, każdy wskazujący na swój skrypt, oraz próg ustawiony jako zmienna środowiskowa. W praktyce odczyt pliku o 1 090 liniach wraca jako błąd z napisanym przez nas zdaniem: deleguj ten odczyt do explorera, model Haiku. Potem następuje delegacja: główny model najpierw liczy linie, wywołuje explorera z modelem ustawionym na Haiku, a punkty wracają, każdy z numerem linii. Trzy tury, 44 sekundy.

Teza Spotify się broni: warstwowość oznacza, że system degraduje się łagodnie. Instrukcja robi routing, hook jest siatką. Siatka ma jednak dziurę. Model, który chce cały plik, może pociąć go na kawałki offsetem i limitem, co przechodzi, albo zrzucić go przez shell zakresem seda, czego ten hook nie łapie. Pomiar liczy jedno i drugie.

Pomiar

Repozytorium testowe to Fastify, framework webowy dla Node: 294 pliki, 63 z nich powyżej progu. Dwa identyczne klony, jedyna różnica to folder .claude i plik reguł. Model główny Opus, domyślny w CLI; reader Haiku; writer Sonnet. Sesje z jednym promptem, bez pytań uzupełniających, każdy scenariusz uruchomiony dwa razy na konfigurację, szesnaście przebiegów łącznie. Cztery scenariusze są takie same jak u Spotify: eksporty jednego dużego pliku, trzy pliki i to, jak się nawzajem wywołują, plik źródłowy zestawiony ze swoim testem oraz nowy plik testowy zapisany na dysk na podstawie istniejącego.

Scenariusz Kontekst główny, bez Kontekst główny, z Zmiana Koszt łączny, bez Koszt łączny, z Zmiana Czas, bez Czas, z Zmiana
Jeden duży plik 88,693 51,552 -41.9% $0.139 $0.087 -37.8% 22 s 44 s +100.8%
Trzy pliki 357,166 73,440 -79.4% $0.581 $0.218 -62.4% 52 s 129 s +149.9%
Źródło vs test 303,808 114,136 -62.4% $0.451 $0.374 -17.1% 93 s 125 s +33.6%
Nowy plik testowy 143,432 121,818 -15.1% $0.295 $0.302 +2.6% 66 s 87 s +32.2%
Wszystkie cztery 223,274 90,236 -59.6% $0.366 $0.245 -33.1% 58 s 96 s +65.3%

Kontekst główny, czyli tokeny, które drogi model faktycznie zobaczył, to pierwsza kolumna, która się liczy. Przy pytaniu o trzy pliki spada o 79%, a we wszystkich czterech scenariuszach o 59.6%. Rachunek spada mniej, łącznie o jedną trzecią, bo własne tokeny readera nie są darmowe, a przy małym zadaniu pisania testu rachunek wzrósł o 2.6%. Czas idzie w drugą stronę: średnio 58 sekund bez setupu, 96 z nim. Delegacja jest wolniejsza za każdym razem.

Jakość to miejsce, w którym obie konfiguracje różnią się najbardziej. Bez setupu główny model zrzucał pliki przez shell bez numerów linii i liczył ręcznie, produkując błędne numery linii na całej długości: funkcja zgłoszona w linii 149 faktycznie siedziała w linii 156. Z setupem jeden przebieg na cztery wziął podsumowanie readera za dobrą monetę i niósł trzy fałszywe twierdzenia, w tym jedno o funkcji, której według readera plik route nigdy nie wywołuje, a wywołuje, w linii 553. Wszystkie cztery wygenerowane pliki testowe przechodzą, a hooki deny odpaliły się zero razy w szesnastu przebiegach: przy obecnym pliku reguł główny model sprawdzał liczbę linii i za każdym razem delegował sam.

Jeszcze jedna rzecz ze śladów: bez reguły główny model ani razu nie użył narzędzia Read. Czytał wszystko przez shell, a odczyt zakresu w shellu kosztuje tyle samo tokenów i przechodzi przez hook. Tabela Spotify mówi więc 90; ta mówi 60 na kontekście i jedną trzecią na rachunku.

Zostaw blokadę. Nie licz, że rachunek spadnie o dziewięćdziesiąt.

Trzy rzeczy warto zachować: projektowego subagenta Explore na Haiku, trzylinijkową regułę w pliku instrukcji i hook odczytu jako siatkę asekuracyjną. Zmierzony wynik to 60% mniej głównego kontekstu, jedna trzecia mniej na rachunku i dwie trzecie więcej czasu zegarowego.

Zanim zaufasz hookowi, napraw dwie rzeczy. Hooki działają wewnątrz subagentów, więc wyłącz z nich swoje workery. I dziura w shellu: hook basha łapie cat, head i tail, ale odczyt zakresu przechodzi, a główny model używał dokładnie tego, kiedy nie miał reguły.

Własne ograniczenia Spotify pozostają w mocy. Nie da się delegować edycji i nie da się delegować rozumowania; worker przeoczył błąd bezpieczeństwa wątków, który Claude wyłapał w sekundy, a każda delegacja to podróż w obie strony. Sceptycy z Hacker News mieli rację jeszcze w jednym: tokeny wejściowe to nie rachunek. Tokeny wyjściowe kosztują więcej, a ten setup nic z nimi nie robi.

Kto oszczędza, zależy od tego, jak płacisz. Na API jedna trzecia mniej. Na planie Pro lub Max ten sam setup przesuwa twoje okna pięciogodzinne i tygodniowe, nie dolary. Uważaj też na próg: poniżej niego delegacja kosztuje więcej, niż oszczędza, a 45-liniowy przypadek testowy jest na to dowodem z plus 2.6%. Na koniec, w dwóch przebiegach na osiem podsumowanie readera zawierało błędy, a tura weryfikacji głównego modelu je wyłapała. Pomiń tę turę, a te błędy trafią do twoich edycji.

Źródła

Najczęstsze pytania

Czy Spotify naprawdę ścięło zużycie tokenów Claude Code o 90%?
90% Spotify to średnia z trzech scenariuszy bulk read w monorepo w Javie, liczona w szacowanych tokenach wejściowych po cztery znaki na token, bez oceny jakości i bez kwoty w dolarach. Ten sam pomysł odtworzony w czystym Claude Code na Fastify zmniejszył kontekst głównego modelu o 59.6%, a koszt łączny o 33%.
Czym jest Portal Spotify i czy mogę go używać z Claude Code?
Portal to wewnętrzny portal deweloperski Spotify zbudowany na Backstage; jego funkcja Modes uruchamia deklaratywnych agentów na efemerycznym runtime. Plugin Shunt, który kieruje Claude Code do tych agentów, jest publiczny na GitHubie, ale uwierzytelnia się wobec instancji Portalu, której nie masz, więc możesz tylko skopiować wzorzec, a nie uruchomić plugin.
Jak sprawić, żeby Claude Code delegował odczyty dużych plików do tańszego modelu?
Utwórz projektowego subagenta o nazwie Explore z polem model ustawionym na Haiku, co nadpisuje wbudowanego agenta Explore, który teraz dziedziczy twój główny model. Dodaj trzylinijkową regułę do pliku instrukcji projektu (pliki powyżej 350 linii idą do explorera, boilerplate idzie do writera, każde wywołanie agenta ustawia model) oraz hook PreToolUse na Read, który odrzuca duże odczyty z powodem wskazującym tego subagenta.
Jak hook PreToolUse blokuje dziś wywołanie narzędzia w Claude Code?
Hook zwraca decyzję wewnątrz obiektu hookSpecificOutput, w polu o nazwie permissionDecision, z czterema możliwymi wartościami: allow, deny, ask i defer. Ciąg z powodem jest pokazywany Claude'owi, a gdy odpowiada kilka hooków, deny wygrywa. Skrypt Spotify używa starszej decyzji najwyższego poziomu o nazwie block.
Czy delegowanie odczytów do subagenta na Haiku sprawia, że Claude Code jest tańszy?
Na API zdjęło to jedną trzecią z rachunku w czterech scenariuszach, ale własne tokeny readera nie są darmowe: przy zadaniu pisania 45-liniowego testu koszt łączny wzrósł o 2.6%. Na planie Pro lub Max oszczędność pokazuje się jako limit w oknach pięciogodzinnym i tygodniowym, nie jako dolary, a tokeny wyjściowe pozostają nietknięte.
Dlaczego hooki Claude Code odpalają się wewnątrz subagentów?
Hooki zdefiniowane w twoich ustawieniach działają dla każdego agenta, w tym dla subagentów, które wysyłasz. Hook blokujący odczyty odmawia więc readerowi na Haiku jego własnych odczytów, chyba że skrypt sprawdza, kto wywołuje, i przepuszcza agenty workerów.

Powiązane filmy