EP.2에서 hooks로 Claude가 읽는 것을 줄였다. 테스트 결과는 실패만 출력하고, 빌드 로그는 에러만 발라냈다.

얼마나 줄어드는지 측정하기로 했다.

측정부터

Claude Code는 모든 대화를 JSONL 파일로 남긴다. ~/.claude/projects/ 아래에 프로젝트별로 쌓이는데, 각 항목에 토큰 사용량이 들어 있다. 이걸 읽어서 비용을 계산하는 스크립트(analyze-tokens.js)를 만들었다. 날짜별 세션 파일과 교차 비교해서 턴 수, cache_read/write/output을 각각 집계하고 비용으로 환산한다.

만드는 과정에서 버그를 하나 잡았다. 세션 기록 hook이 e.usage를 참조하고 있었는데, JSONL 구조는 e.message.usage였다. 세션 파일에 토큰이 기록 안 되고 있었던 거다. 분석기는 JSONL을 직접 읽도록 우회했다.

아래는 3월 27일 하루의 비용이다.

총 비용의 58%는 읽기

항목비용비중
cache_read$19.2258%
cache_write~$9.6229%
output~$4.3313%
합계$33.17

cache_read가 58%다. Claude가 생성한 비용의 절반 이상이 “읽기"라는 뜻이다. EP.1에서 “Claude가 소모하는 토큰의 대부분은 추론이 아니라 읽기"라고 썼는데, 숫자로 보니까 확실하다.

뭘 읽고 있는 거야

Claude가 매 turn 읽는 것들을 추적했다.

항목로드 시점규모
프로젝트 CLAUDE.md매 turn155~214줄
루트 CLAUDE.md매 turn10줄
MEMORY.md매 turn23줄 이내
대화 히스토리매 turn누적

의외의 발견이 있었다. SKILL.md(469줄)와 에이전트 문서(1,233줄)는 매 turn 로드되지 않는다. 스킬이 호출될 때만, 에이전트가 실행될 때만 올라간다. 이미 lazy loading이다.

매 turn 고정으로 올라가는 건 CLAUDE.md와 MEMORY.md뿐이다.

CLAUDE.md를 뜯어서 rules/로

CLAUDE.md가 155~214줄이라고 해서 전부 매 turn 필요한 건 아니다. 산출물 경로 테이블은 산출물을 쓸 때만 필요하고, 게임 도메인 규칙은 게임 관련 작업을 할 때만 필요하다.

Claude Code에는 .claude/rules/ 디렉토리가 있다. 여기에 파일을 만들고 paths: frontmatter를 붙이면, 해당 경로에 접근할 때만 로드된다.

---
paths: tasks/**
---

산출물 경로 테이블, 공통 헤더 블록, 역할별 규칙...

tasks/ 폴더를 건드릴 때만 이 규칙이 올라온다. 일반 대화에서는 올라오지 않는다.

한 프로젝트에서 먼저 시범 적용했다. 산출물 관련 섹션 45줄을 artifact-rules.md로 분리하고, CLAUDE.md에서 해당 부분을 지웠다. 동작했다. 나머지에도 일괄 적용했다.

프로젝트CLAUDE.md (전→후)절감률분리된 파일
A (쇼핑몰)155줄 → 96줄38%artifact-rules.md
B (교육 플랫폼)207줄 → 140줄32%artifact-rules.md, edu-rules.md
C (게임 플랫폼)214줄 → 114줄47%artifact-rules.md, game-domain.md

C가 47%나 줄었다. 게임 플랫폼이라 도메인 규칙(46줄)이 매 turn 올라가고 있었는데, 이제 게임 관련 파일을 건드릴 때만 올라간다.

그런데 말입니다

수치가 나쁘지 않다. 32~47% 경량화. 보기엔 좋다.

하지만 솔직히 말하면, 이건 “CLAUDE.md 155214줄 중에서 3247%“다. turn당 수백 토큰 수준의 절감이다. cache_read $19.22의 대부분은 CLAUDE.md 200줄이 아니라 대화 히스토리 누적이 원인이다. 한 세션에서 20턴을 돌리면, 20턴째에는 앞의 19턴 전체가 cache_read로 올라간다.

하루 비용의 절반 이상을 차지한 프로젝트가 있었다. 다른 프로젝트보다 3배 이상 비쌌다. 이유는 단순하다. 한 세션에서 17턴을 돌렸다. 후반부 턴의 cache_read가 급증한 거다.

CLAUDE.md를 아무리 다이어트시켜도, 대화 히스토리가 쌓이는 구조를 건드리지 않으면 cache_read의 대부분은 그대로다. EP.1에서 에이전트 수를 줄이고, EP.2에서 hooks로 출력을 줄이고, EP.3에서 고정 문서를 줄였다. 전부 의미 있는 작업이지만, 진짜 큰 바가지는 여전히 새고 있다.

끊을 것인가, 이어갈 것인가 그것이 문제로다

20턴을 넘기면 새 세션을 시작하는 게 cache_read 관점에서는 효율적이다. 하지만 새 세션을 시작하면 cache_write가 다시 발생한다. CLAUDE.md, MEMORY.md, 시스템 프롬프트를 처음부터 다시 쓴다.

cache_read를 줄이려고 세션을 끊으면 cache_write가 늘어난다. cache_write를 줄이려고 세션을 이어가면 cache_read가 늘어난다. 어느 쪽이든 비용이 든다.

정답은 아직 모른다. 20턴 정도가 손익분기점이라는 감은 있지만, 작업 유형에 따라 다르다. 대화가 많은 설계 작업은 빨리 끊는 게 낫고, 연속 코딩은 이어가는 게 낫다.

이게 EP.3의 진짜 발견이다. 줄일 수 있는 건 줄였다. 남은 건 “어떻게 쓸 것인가"의 영역이다.

이 시리즈의 다른 글


참고