90%, 그리고 그걸 팔아치운 한 문장
Spotify의 Claude Code 설정은 Spotify 프로덕트 매니저 Dimitri Mazmanov가 쓴 블로그 글이며, 코드는 GitHub에 공개되어 있습니다. 그는 팀이 쓰는 이 구성이 자신의 Claude Code 토큰 사용량을 90% 줄였다고 말합니다. 글의 첫 문장에 논지 전체가 담겨 있습니다. AI 코딩 agent가 하는 일의 대부분은 사고가 아니라 I/O라는 것입니다. 메서드 하나에 대한 질문에 답하려고 파일 다섯 개를 읽거나, 옆에 있는 스무 개를 그대로 베낀 스물한 번째 테스트 파일을 쓰는 일은 추론은 거의 없이 수천 토큰을 태웁니다.
트윗 하나가 이 글을 문장 한 줄로 150만 조회수까지 끌고 갔습니다. 글로 쓴 규칙은 제안이고, 차단은 제안이 아니다. Hacker News는 이 글을 첫 페이지에 올렸고, 271포인트에 댓글 173개가 달렸으며, 댓글의 절반은 같은 질문을 던졌습니다. 90%라는 건 무엇의 90%인가? Spotify가 직접 붙인 단서는 "bulk read"입니다. 이 글은 그 설정을 순수 Claude Code 안에서 다시 만든 뒤 측정하여, 그 단서가 정확히 무엇을 의미하는지 보여 줍니다.
Portal의 정체 (그리고 왜 돌릴 수 없는지)
Portal은 라우터가 아닙니다. Spotify가 오픈소스로 공개한 개발자 플랫폼 Backstage 위에 세운 Spotify 사내 개발자 포털입니다. 여기서 중요한 기능은 Modes라고 불립니다. Spotify의 정의에 따르면 mode는 임시 런타임에서 실행되는 선언형 agent이며, 대략 agent를 위한 AWS Lambda라고 보면 됩니다. 지시문을 쓰고, 모델을 고르고, temperature를 정하고, 도구를 붙입니다. Mazmanov는 대량 리더와 코드 라이터, 이렇게 둘을 만들었고, 둘 다 Gemini Flash에 temperature 0.2로 돌아가므로 의도적으로 저렴하고 무난하게 설계되었습니다.
라우팅은 Shunt라는 Claude Code plugin 안에 있습니다. GitHub에 공개되어 있고 명령 두 개로 설치됩니다. 하지만 두 번째 단계는 Portal 명령줄을 여러분의 Portal 인스턴스에 인증하는 과정인데, 여러분에게는 그런 인스턴스가 없습니다. plugin은 공개되어 있지만, plugin이 위임하는 대상은 공개되어 있지 않습니다.
따라서 유용한 선택은 plugin을 잊고 패턴만 가져오는 것입니다. 그의 표현대로 세 개의 층이 있습니다. hook, 스크립트, skill. 각 층마다 순수 Claude Code로 만든 대응물이 있으며, 이 글의 나머지는 그것을 만들고 측정합니다.
1층: 묻는 대신 차단하는 hook
설정의 버전 1은 프로젝트 지시 파일에 넣은 라우팅 규칙 블록이었습니다. Mazmanov의 표현으로는 "어느 정도는 작동했다"고 합니다. 규칙은 권고일 뿐 강제되지 않았고, Claude는 그것을 무시할 수 있었으며, 프로젝트마다 복사본이 따로 필요했습니다. 버전 2는 결정을 prompt 밖으로 꺼내 도구 계층으로 옮깁니다. 도구 호출 전에 실행되는 hook 두 개로 말입니다. 하나는 모든 파일 읽기를 감시하고, 다른 하나는 셸을 감시합니다.
읽기 hook은 33줄짜리 bash입니다. 환경 변수에서 임계값을 읽고(기본값은 350줄), 다음 세 가지는 통과시킵니다.
- offset이나 limit이 있는 읽기. Claude가 이미 무엇이 필요한지 알고 있기 때문입니다.
- 존재하지 않는 파일.
- 임계값 이하의 파일. 작은 것을 위임하는 비용이 그냥 읽는 비용보다 크기 때문입니다.
나머지는 모두 차단되며, Claude는 파일 대신 이런 메시지를 읽게 됩니다. 이 파일은 몇 줄이니 대량 리더 skill을 쓰고, 편집을 위해 정확한 내용이 필요하면 그 구간만 다시 읽으라는 내용입니다. 셸 hook은 큰 파일에 대한 cat, head, tail, less, more를 잡아냅니다. 파이프로 연결된 명령은 통과하는데, grep으로 파이프하는 것은 표적 읽기이기 때문입니다.
Mazmanov가 말한 계층화의 요점이 중요합니다. Claude가 skill 설명을 한 번도 읽지 않더라도 hook은 여전히 비싼 읽기를 차단합니다. skill은 방향 전환을 더 매끄럽게 만들고, 차단은 그것을 실제로 만듭니다. 뒤에서 중요해질 세부 사항이 하나 있습니다. 이 스크립트는 "block"이라는 최상위 decision으로 응답합니다. 그 단어를 기억해 두시기 바랍니다.
2층과 3층: 워커와 그 숫자들
워커는 prompt 두 개입니다. 리더: "당신은 정밀한 코드 분석가다. 구조화된 불릿만 출력하고, 인사도 산문도 쓰지 않으며, 모든 불릿은 정확한 이름, 타입 또는 줄 번호로 시작한다." 라이터: "기존 패턴, 명명, 스타일을 정확히 따른다. 코드만 출력하고, 펜스도 설명도 쓰지 않는다." 마지막 줄이 없으면 모델은 모든 것을 Markdown으로 감싸고, Claude는 그것을 다시 파싱해야 합니다.
스크립트 두 개가 이 둘을 감쌉니다. bulk-read는 질문과 파일 경로를 받아 보냅니다. code-write는 스펙과 참조 파일을 받아 결과를 곧바로 디스크에 씁니다. 그래서 Claude는 생성된 코드를 결코 보지 않습니다. 모든 위임은 일회성입니다. 후속 요청은 파일을 다시 보냅니다. 그럼에도 중요한 지점에서는 공짜인데, 코퍼스가 워커로 가고 Claude의 컨텍스트에는 절대 들어오지 않기 때문입니다.
3층은 언제 위임할지 Claude에게 알려주는 skill 파일입니다. 350줄이 넘는 파일, 세 개 이상의 파일에 걸친 질문, 큰 diff. 마지막 줄은 "편집 전에 줄 번호를 검증하라"입니다.
Spotify의 표는 Java monorepo 하나와 읽기 시나리오 세 개를 다룹니다. 단일 파일 사례는 약 34,000토큰에서 6,000토큰 미만으로 떨어지고, 세 행의 평균 절감률은 90%입니다.
| Spotify의 벤치마크 | 값 |
|---|---|
| 저장소 | 1 Java monorepo |
| 시나리오 | 3개, 모두 bulk read |
| 단일 파일 사례, 이전 | ~34,000 tokens |
| 단일 파일 사례, 이후 | < 6,000 tokens |
| 평균 절감률 | 90% |
| 토큰 추정 방식 | 토큰당 4자 |
| 라이터 강제 | 없음 (리더에만 hook 있음) |
Spotify가 직접 적어 둔 단서가 둘 있습니다. 토큰은 4글자당 1개로 추정한 값이고, 라이터에는 강제 장치가 전혀 없습니다. 즉 90%는 추정 입력 토큰 기준으로 bulk read 세 행을 평균한 값이며, 품질 점수도 달러 수치도 어디에도 없습니다. 이것이 검증해야 할 숫자입니다.
재구축 1부: model 필드를 가진 subagent
Claude Code에는 Explore subagent가 내장되어 있는데, 최근 릴리스부터는 메인 모델을 상속하며 상한은 Opus입니다. 그래서 "저렴한 리더"는 더 이상 저렴하지 않습니다. 문서는 해결책을 한 문장으로 제시합니다. Explore라는 이름의 프로젝트 subagent가 내장된 것을 덮어쓰고 자기만의 model 필드를 유지한다는 것입니다. markdown 파일 하나, front matter, 그리고 model 줄에 Haiku. 이것이 대량 리더입니다. 라이터는 두 번째 파일입니다. model은 Sonnet, 도구는 Read와 Write만, 본문은 Spotify의 지시문을 그대로 붙여 넣은 것입니다.
이것이 작동하는 이유는 모든 subagent가 새롭고 격리된 컨텍스트 윈도우에서 시작하기 때문입니다. subagent가 읽은 것은 그곳에 쌓이고, 메인 대화에는 들어오지 않습니다. 네트워크 왕복만 뺀 Spotify의 일회성 위임인 셈입니다.
그다음은 아무도 계획하지 않는 부분입니다. 이번 주 Reddit에서 Fable은 Opus agent를 띄우라는 지시를 받고 대신 Fable agent 다섯 개를 띄웠습니다. 30분 만에 주간 한도의 73%가 사라졌습니다. 가장 많은 지지를 받은 답변은 모델이 subagent를 보낼 때 실행되는 hook이었습니다. 모델을 명시적으로 선택하도록 강제하고, 작업을 해낼 수 있는 가장 저렴한 모델을 고르라고 알려줍니다. 이것이 세 번째 hook입니다. Agent 도구를 감시하고, model이 없는 호출은 "모델을 명시적으로 선택하라"는 한 문장과 함께 거부됩니다.
Spotify의 skill은 프로젝트 지시 파일의 세 줄이 됩니다. 350줄이 넘는 파일은 explorer에게, 보일러플레이트는 라이터에게, 모든 agent 호출은 model을 지정한다. 투박한 선택지도 있습니다. 모든 subagent에 하나의 모델을 강제하는 환경 변수 두 개입니다. 솔직한 한계는 리더가 더 저렴한 모델이라서, 리더가 돌려주는 것이 메인 모델이 아는 전부라는 점입니다. 측정 섹션에서 이를 다룹니다.
재구축 2부: 오늘날의 hook 형식으로 만든 deny
"block"이라는 단어를 기억하시기 바랍니다. Spotify의 스크립트는 최상위 decision을 반환하지만, 현재 Claude Code 문서는 다르게 말합니다. PreToolUse hook은 hook 전용 출력 객체 안에 결정을 반환하며, 그 필드의 이름은 permissionDecision입니다. 결과는 allow, deny, ask, defer 네 가지이고, 여기서 원하는 것은 deny입니다. hook이 reason에 적은 내용은 Claude에게 표시되며, 여러 hook이 응답하면 deny가 이깁니다.
그래서 읽기 hook을 다시 만들었습니다. 같은 임계값 350, 같은 세 가지 예외, 그리고 block 대신 Explore subagent와 사용할 모델을 지목하는 reason이 달린 deny. 문서에 분명히 적힌 함정이 하나 있습니다. 설정에 있는 hook은 subagent 안에서도 실행됩니다. 예외 처리가 없으면 Haiku 리더는 자기 읽기까지 거부당해 아무 일도 할 수 없습니다. 그래서 스크립트는 누가 호출하는지 확인하고 워커 둘은 통과시킵니다.
배선은 matcher 세 개(Read, Bash, Agent)가 각각 자기 스크립트를 가리키는 설정 파일 하나와, 환경 변수로 지정한 임계값입니다. 실제로 1,090줄짜리 파일을 읽으면 우리가 쓴 문장이 담긴 오류가 돌아옵니다. 이 읽기는 explorer에게 위임하고, 모델은 Haiku. 그다음 위임이 이어집니다. 메인 모델이 먼저 줄 수를 세고, model을 Haiku로 지정해 explorer를 호출하면, 줄 번호가 달린 불릿이 돌아옵니다. 세 턴, 44초.
Spotify의 말은 유효합니다. 계층화 덕분에 시스템은 우아하게 성능이 저하됩니다. 지시문이 라우팅을 맡고, hook은 안전망입니다. 다만 그 안전망에는 구멍이 있습니다. 파일 전체를 원하는 모델은 offset과 limit으로 잘라서 읽을 수 있고 이것은 통과하며, sed 범위로 셸을 통해 덤프할 수도 있는데 이 hook은 그것을 잡지 못합니다. 측정에서는 둘 다 셉니다.
측정
테스트 저장소는 Node 웹 프레임워크 Fastify입니다. 파일 294개, 그중 63개가 임계값을 넘습니다. 동일한 클론 두 개를 두고, 유일한 차이는 .claude 폴더와 규칙 파일뿐입니다. 메인 모델은 CLI 기본값인 Opus, 리더는 Haiku, 라이터는 Sonnet. 단일 prompt 세션, 후속 요청 없음, 시나리오마다 구성당 두 번 실행, 총 16회. 시나리오 네 개는 Spotify와 같습니다. 큰 파일 하나의 export, 파일 세 개와 그들이 서로를 호출하는 방식, 소스 파일과 그 테스트의 대조, 그리고 기존 파일을 바탕으로 디스크에 새로 쓴 테스트 파일.
| 시나리오 | 메인 컨텍스트, 미적용 | 메인 컨텍스트, 적용 | 변화 | 총비용, 미적용 | 총비용, 적용 | 변화 | 소요 시간, 미적용 | 소요 시간, 적용 | 변화 |
|---|---|---|---|---|---|---|---|---|---|
| 큰 파일 하나 | 88,693 | 51,552 | -41.9% | $0.139 | $0.087 | -37.8% | 22 s | 44 s | +100.8% |
| 파일 세 개 | 357,166 | 73,440 | -79.4% | $0.581 | $0.218 | -62.4% | 52 s | 129 s | +149.9% |
| 소스 대 테스트 | 303,808 | 114,136 | -62.4% | $0.451 | $0.374 | -17.1% | 93 s | 125 s | +33.6% |
| 새 테스트 파일 | 143,432 | 121,818 | -15.1% | $0.295 | $0.302 | +2.6% | 66 s | 87 s | +32.2% |
| 네 개 전체 | 223,274 | 90,236 | -59.6% | $0.366 | $0.245 | -33.1% | 58 s | 96 s | +65.3% |
먼저 볼 열은 메인 컨텍스트, 즉 비싼 모델이 실제로 본 토큰입니다. 파일 세 개 질문에서는 79% 줄고, 네 시나리오 전체에서는 59.6% 줄어듭니다. 요금은 그보다 덜 줄어 전체적으로 3분의 1인데, 리더의 토큰도 공짜가 아니기 때문이며, 작은 테스트 작성 작업에서는 요금이 2.6% 올랐습니다. 시간은 반대 방향으로 갑니다. 설정 없이 평균 58초, 설정하면 96초. 위임은 매번 더 느립니다.
두 구성이 가장 크게 갈리는 지점은 품질입니다. 설정이 없을 때 메인 모델은 줄 번호 없이 셸로 파일을 덤프하고 직접 세었으며, 그 결과 줄 번호가 전반적으로 틀렸습니다. 149번 줄에 있다고 보고한 함수는 실제로 156번 줄에 있었습니다. 설정이 있을 때는 네 번 중 한 번의 실행이 리더의 요약을 그대로 믿어 거짓 주장 세 개를 담았습니다. 그중 하나는 라우트 파일이 절대 호출하지 않는다고 리더가 말한 함수인데, 실제로는 553번 줄에서 호출합니다. 생성된 테스트 파일 네 개는 모두 통과하고, deny hook은 16회 실행 동안 한 번도 울리지 않았습니다. 규칙 파일이 있으면 메인 모델은 매번 줄 수를 확인하고 스스로 위임했습니다.
트레이스에서 한 가지 더. 규칙이 없을 때 메인 모델은 Read 도구를 한 번도 쓰지 않았습니다. 모든 것을 셸로 읽었고, 셸 범위 읽기는 같은 토큰을 쓰면서 hook을 통과합니다. 그러니 Spotify의 표는 90이라 말하고, 이 표는 컨텍스트에서 60, 요금에서 3분의 1이라 말합니다.
차단은 남기되, 요금이 90% 줄 거라 기대하진 마세요.
남길 만한 것은 셋입니다. Haiku로 도는 프로젝트 Explore subagent, 지시 파일의 세 줄짜리 규칙, 그리고 안전망으로서의 읽기 hook. 측정 결과는 메인 컨텍스트 60% 감소, 요금 3분의 1 감소, 실행 시간 3분의 2 증가입니다.
hook을 믿기 전에 두 가지를 고치시기 바랍니다. hook은 subagent 안에서도 실행되므로 워커는 예외 처리해야 합니다. 그리고 셸 구멍입니다. bash hook은 cat, head, tail을 잡지만 범위 읽기는 통과하며, 규칙이 없을 때 메인 모델은 정확히 그 방법을 썼습니다.
Spotify가 밝힌 한계는 그대로 유효합니다. 편집을 위임할 수 없고 추론도 위임할 수 없습니다. 워커는 Claude가 몇 초 만에 잡아낸 스레드 안전성 버그를 놓쳤고, 위임은 매번 왕복입니다. Hacker News의 회의론자들도 한 가지는 옳았습니다. 입력 토큰은 요금의 본체가 아닙니다. 출력 토큰이 더 비싸고, 이 설정은 출력 토큰에는 아무것도 해 주지 않습니다.
누가 절약하느냐는 결제 방식에 달렸습니다. API에서는 3분의 1이 줄어듭니다. Pro나 Max 플랜에서는 같은 설정이 달러가 아니라 5시간 및 주간 한도를 움직입니다. 임계값도 주의해야 합니다. 그 아래에서는 위임 비용이 절감액보다 크며, 45줄짜리 테스트 사례가 +2.6%로 그 증거입니다. 마지막으로, 여덟 번 중 두 번의 실행에서 리더의 요약에 오류가 섞였고, 메인 모델의 검증 턴이 그것을 잡아냈습니다. 그 턴을 건너뛰면 그 실수가 여러분의 편집에까지 이릅니다.
AIDive