AIDive

영상 팩

Spotify의 Claude Code 토큰 90% 절감, 재구현과 측정: 훅, 서브에이전트, 프로토콜

읽는 데 11분

TL;DR

  • Spotify가 말하는 "90%"는 Java 모노레포에서 대량 읽기 시나리오를 추정 입력 토큰으로 측정한 값의 평균입니다. 글에는 비용(달러) 수치도, 품질 점수도 없습니다.
  • 순정 Claude Code(PreToolUse 훅 하나, 저렴한 서브에이전트 둘, 세 줄짜리 라우팅 규칙)로 다시 만들어 Fastify에서 4개 시나리오, 16회 실행으로 측정한 결과, 이 패턴은 메인 모델의 컨텍스트를 59.6%, 전체 비용을 33.1% 줄였습니다.
  • 측정한 실행에서 deny 훅은 한 번도 발동하지 않았습니다. 절감은 CLAUDE.md의 라우팅 규칙에서 나왔고, 훅은 모델이 그 규칙을 무시하는 날을 위한 안전망입니다.
  • 위임은 매번 더 느렸고, 평균 실행 시간이 +65.3% 늘었습니다. 규모가 작은 테스트 작성 시나리오에서는 비용이 2.6% 더 들었습니다.
  • 함정이 둘 있습니다. 훅은 서브에이전트 안에서도 발동하므로 워커는 예외로 빼야 하고, sed -n 범위 읽기는 cat, head, tail만 보는 훅을 그대로 통과합니다.
  • Haiku 리더의 요약에는 위임된 8회 실행 중 2회에서 사실 오류가 있었습니다. 메인 모델의 검증 턴은 유지하세요.

측정 결과가 말해 주는 것

Spotify의 플러그인 Shunt는 두 가지 "모드", 즉 bulk-reader와 code-writer를 통해 대량 작업을 메인 모델에서 떼어 냅니다. 예시에서는 둘 다 Gemini 2.5 Flash로 돌고, model 필드에는 Portal 인스턴스에 설정된 어떤 모델이든 지정할 수 있습니다 s1. 라우팅은 세 겹입니다. check-file-size 훅은 모든 Read에서 발동해 설정 가능한 줄 수 임계값(기본 350)을 넘는 파일을 막고, 대신 bulk-reader 스킬을 쓰라고 안내합니다. check-bash-read 훅은 큰 파일에 대한 cat, head, tail, less, more를 잡아내며, 파이프로 이어진 명령은 통과시킵니다 s1. 훅 소스와 두 스킬은 공개 저장소에 있고 s2, 크기 검사는 따로 읽을 수도 있습니다 s3. 모드 자체는 Spotify의 사내 플랫폼 Portal 안에 있어서, 배포된 그대로는 사내 밖에서 이 플러그인을 돌릴 수 없습니다 s4.

벤치마크 주장은 근거가 얇습니다. Spotify는 Java 모노레포에서 4개 시나리오를 테스트했고, "Claude가 파일을 직접 읽을 때 소비할 토큰과 bulk-reader의 요약을 소비할 때의 토큰"을 측정해 대량 읽기 절감률 평균이 약 90%라고 보고합니다 s1. 같은 글이 직접 인정하는 내용도 있습니다. 코드 작성 시나리오는 토큰으로 측정하기 더 어렵고, 워커의 요약에는 믿을 만한 줄 번호가 없어서 편집은 위임할 수 없으며, 워커는 메인 모델이 잡아낸 미묘한 스레드 안전성 버그를 놓쳤고, 위임할 때마다 10~30초가 더해지며 Portal은 한 번의 호출을 30초로 제한합니다 s1. Hacker News 스레드에서도 90%가 무엇을 측정한 값인지에 대해 같은 의문이 제기되었습니다 s7.

재구현에서는 Portal 모드를 정의 파일에서 모델을 고정하는 Claude Code 서브에이전트 둘로 바꿨습니다. Haiku로 돌리는 Explore 리더와 Sonnet으로 돌리는 code-writer입니다 s6. deny는 현재 훅 JSON 형식의 deny 결정을 반환하는 PreToolUse 훅입니다 s5. 테스트 대상 저장소는 커밋 ac28821d의 fastify/fastify로, .js/.ts 파일 294개, 78270줄, 350줄 초과 파일 63개입니다. 세션 JSON이 보고한 모델 id는 메인 대화가 claude-opus-5[1m], 리더가 claude-haiku-4-5-20251001, 라이터가 claude-sonnet-5입니다. 시나리오마다 설정당 2회씩, 총 16회를 측정했고, 단일 턴 claude -p 세션에 프로젝트 설정만 적용해 양쪽의 시스템 프롬프트를 동일하게 맞췄습니다 s5.

이 패턴이 이긴 곳: S2는 lib/route.js(691줄), lib/reply.js(1090줄), lib/request.js(398줄) 세 파일에 걸친 호출 그래프 질문으로, 메인 컨텍스트 평균이 357165.5 토큰에서 73440.0으로(-79.4%), 비용이 0.5810500000000001 USD에서 0.21823605000000001 USD로(-62.4%) 내려갔습니다. 이기지 못한 곳: S4는 19줄짜리 레퍼런스를 보고 45줄짜리 소스의 테스트를 쓰는 시나리오로, 위임 없이는 0.29465575 USD, 위임하면 0.3022213 USD(+2.6%)였습니다. Sonnet이 두 번째 풀 컨텍스트이고(캐시 읽기 토큰 13004에서 18729) 메인 모델이 생성된 파일을 다시 읽고 테스트까지 돌렸기 때문입니다 s6.

퍼센트보다 중요한 발견이 셋 있습니다. 첫째, 16회 측정 실행 내내 훅은 한 번도 발동하지 않았습니다. CLAUDE.md에 라우팅 규칙이 있으면 메인 모델이 알아서 wc -l을 돌리고 위임했습니다. 유일하게 관찰된 deny는 CLAUDE.md 없이 돌린 검증 실행에서 나왔는데, 모델은 Read를 거부당하고 cat -n도 거부당한 뒤 Agent 도구를 한 번도 부르지 않고 grep -n만으로 답했습니다 s5. 둘째, 훅은 서브에이전트 안에서도 돕니다. 폐기한 실행 두 건에서 Haiku 리더 자신이 크기 검사에 막혀 offset/limit로 쪼개 읽는 방식으로 후퇴했습니다. 해법은 훅 맨 위에 case "$agent_type" in Explore|code-writer) exit 0 예외를 두는 것이고, 필드 이름은 기록해 둔 stdin으로 확인합니다 s5. 셋째, 베이스라인 설정에서 메인 모델은 Read 도구를 아예 쓰지 않았습니다. 모든 파일을 Bash(cat -n, sed -n '1,200p', sed -n '200,560p')로 읽었기 때문에 Read만 감시하는 훅은 아무것도 잡지 못하고, 파이프 없는 cat, head, tail만 매칭하는 Bash 훅도 sed -n 범위 읽기는 그대로 통과시킵니다 s3.

품질은 소스와 grep으로 대조해 확인했습니다. 베이스라인은 S2 실행 한 건에서 줄 번호가 틀렸습니다(sed -n으로 줄 번호 없이 파일을 덤프하고 손으로 셌습니다). 위임 설정은 나머지 S2 실행에서 사실 오류 세 건, S3 실행에서 두 건을 냈고, 모두 Haiku 요약을 액면 그대로 받아들인 데서 비롯됐습니다. buildRequest/buildReply의 호출자가 틀렸고, export되지 않은 상수가 export로 나열됐으며, 커버된 이터레이터가 커버되지 않은 것으로 표시됐습니다. 메인 모델이 grep으로 재검증하느라 출력 토큰을 쓴 경우(S3, 출력 토큰 4534에서 4738)에는 답이 정확했습니다 s6. 테스트 작성 시나리오에서는 생성된 파일이 전부 통과했습니다. 12/12, 6/6, 7/7, 10/10 테스트입니다. Reddit의 한 보고는 모델이 고정되지 않았을 때 메인 모델이 엉뚱한 모델로 워커를 띄우는 관련 실패 사례를 보여 주는데 s8, require-model 훅과 에이전트 파일의 model: 필드가 바로 이를 막아 줍니다.

측정값

실행 2회의 셀별 평균입니다. "Main context"는 세션 동안 메인 모델에 청구된 input + cache_creation + cache_read 토큰으로, Spotify의 "메인 컨텍스트의 토큰"과 비교할 수 있는 수치입니다. A = 순정 Claude Code, B = 훅 + 서브에이전트 + CLAUDE.md 규칙.

시나리오 main context A main context B 변화 main output A main output B 변화 total cost A total cost B 변화 duration A s duration B s 변화
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%
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%

프로토콜: fastify/fastify의 ac28821d 얕은 복제본 두 개(바이트 단위로 동일). repo-shunt에는 .claude/(설정, 에이전트 파일 둘, 훅 셋)와 CLAUDE.md 라우팅 규칙만 추가했고 그 외에는 아무것도 없습니다. 모든 세션: claude -p "<prompt>" --output-format json --setting-sources project --strict-mcp-config에 빈 MCP 설정, --model 없음, 타임아웃 600초. 프롬프트 4개는 양쪽에서 동일합니다. S1은 lib/reply.js의 export, S2는 lib 파일 세 개에 걸친 호출 그래프, S3는 lib/hooks.js의 메서드와 test/hooks.test.js의 커버리지 비교, S4는 test/noop-set.test.js를 따라 test/head-route.test.js 작성입니다. 수치는 세션 JSON의 modelUsage와 total_cost_usd에서 반올림 없이 읽었습니다. 답은 소스와 grep으로 대조했고, 생성된 테스트는 node --test로 실행했습니다.

월요일에 할 일

  • 저장소 전체에 wc -l을 돌려 350줄을 넘는 파일이 몇 개인지 세 보세요. 거의 0이면 여기서 멈추세요. 작은 파일에서는 위임 비용이 절감분보다 크기 때문에 임계값이 있는 것입니다.
  • CLAUDE.md에 세 줄짜리 라우팅 규칙을 추가하세요. 임계값을 넘는 파일은 리더 서브에이전트로, 패턴을 따르는 코드는 라이터 서브에이전트로 보내고, 디버깅과 아키텍처는 메인 모델이 맡습니다. 측정에서는 이 규칙이 모든 일을 했습니다.
  • frontmatter에 model: haiku를 넣은 .claude/agents/Explore.md와 model: sonnet을 넣은 .claude/agents/code-writer.md를 만드세요. 워커 모델이 오케스트레이터 재량에 맡겨지지 않고 파일에 고정됩니다.
  • 안전망으로 Read용 PreToolUse 훅을 작성해 현재 훅 JSON 형식의 deny 결정을 반환하게 하고, 첫 줄들에서 agent_type이 워커 중 하나이면 exit 0 하게 만드세요.
  • Bash 훅을 cat, head, tail 너머로 확장하세요. 큰 파일에 대한 sed -n 범위와 cat -n을 매칭하고, 파이프 명령과 grep 명령은 통과시키세요.
  • 실제 질문 하나를 .claude/ 폴더가 있을 때와 없을 때 claude -p --output-format json으로 돌려, 입력 열만 보지 말고 total_cost_usd와 duration_ms를 비교하세요.
  • 리더의 요약을 믿기 전에 위임된 답 두 개를 소스와 grep으로 대조하세요. 메인 모델의 검증 턴도 비용의 일부로 예산에 넣으세요.
  • 멀티턴 세션도 측정하세요. 단일 턴 결과에서 메인 컨텍스트는 위임 시 50k119k 토큰, 위임 없이는 84k414k 토큰이므로 두 번째 질문은 더 싸게 시작할 것으로 보이지만, 이는 측정하지 않았습니다.

더 알아보기

  • 블로그 글의 훅을 복사하기 전에 공식 레퍼런스에서 훅 형식과 agent_type 필드를 읽어 보세요. deny 형태와 stdin 필드가 서브에이전트 예외를 가능하게 합니다 s5.
  • 서브에이전트 문서는 model frontmatter 필드와 도구 제한을 다룹니다. 리더를 읽기 전용이면서 저렴하게 유지하는 방법입니다 s6.
  • Spotify 글에서 가장 쓸모 있는 부분은 "What doesn't work" 섹션입니다. 편집 위임 불가(요약에 믿을 만한 줄 번호가 없음), 추론 위임 불가(놓친 스레드 안전성 버그), 왕복 10~30초입니다 s1.
  • Shunt README는 3계층 구조(훅, 스크립트, 스킬)와 모델에게 언제 위임할지 알려 주는 스킬 텍스트를 보여 줍니다. 가져다 쓸 가치가 있는 것은 훅이 아니라 스킬 문구입니다 s2.
  • Portal 모드는 모델과 시스템 프롬프트 위에 얹은 설정 계층이고, 같은 발상은 model 필드가 있는 Claude Code 에이전트 파일에 그대로 대응됩니다 s4.
  • Hacker News 스레드는 측정에 대한 의문이 처음 제기된 곳이며, 토큰 절감 주장을 마주할 때 무엇을 물어야 하는지 보여 주는 좋은 체크리스트입니다 s7.
  • 한 Reddit 스레드는 오케스트레이터가 값비싼 자기 모델로 워커 다섯 개를 띄운 사례를 기록합니다. 에이전트 파일에 모델을 고정하고, 확실한 보장이 필요하면 model 필드가 없는 Agent 호출을 deny하세요 s8.

출처

FAQ

90%라는 수치는 틀렸나요?

한 가지만 측정합니다. 큰 Java 파일에 대한 대량 읽기 시나리오에서 메인 컨텍스트의 추정 입력 토큰입니다. 같은 종류의 지표에서 재구현은 읽기 시나리오에서 41.9%~79.4%를 얻었습니다. 비용, 시간, 답변 품질에 대해서는 아무 말도 하지 않으며, 글도 그렇게 주장하지 않습니다.

이걸 하려면 Portal이 필요한가요?

아니요. 라우팅은 CLAUDE.md 규칙, 모델이 고정된 에이전트 파일 둘, PreToolUse 훅에 있습니다. Spotify에서는 Portal이 워커 모델을 공급하고, 순정 Claude Code에서는 model: haiku 한 줄이 같은 역할을 합니다.

위임이 더 비싸지는 때는 언제인가요?

파일이 작을 때입니다. 45줄짜리 테스트 작성 시나리오는 라이터가 두 번째 풀 컨텍스트이고 메인 모델이 결과를 다시 읽고 테스트까지 했기 때문에 위임 시 비용이 2.6% 더 들었습니다. 위임한 실행은 전부 더 느리기도 했고, 평균 +65.3%였습니다.

훅이 한 번도 발동하지 않은 이유는 무엇인가요?

CLAUDE.md의 라우팅 규칙 덕분에 메인 모델이 읽기를 시도하기 전에 wc -l로 확인하고 위임했기 때문입니다. 훅이 의미를 갖는 것은 모델이 규칙을 무시할 때뿐이고, 그런 일은 CLAUDE.md 없이 돌린 검증 실행에서 일어났습니다.