Claude Code 구축에서 얻은 교훈: 프롬프트 캐싱이 전부임
(x.com/trq212)- 장기 실행 에이전트 제품에서 프롬프트 캐싱은 이전 라운드트립의 연산을 재사용해 지연 시간과 비용을 획기적으로 줄이는 핵심 기술로, Claude Code의 전체 아키텍처가 이를 중심으로 설계됨
- 캐싱은 접두사 매칭 방식으로 작동하므로, 정적 콘텐츠를 앞에 배치하고 동적 콘텐츠를 뒤에 배치하는 순서 설계가 비용과 성능을 좌우함
- 세션 중간에 도구나 모델을 변경하면 캐시가 무효화되므로, 도구 제거 대신 경량 스텁과 상태 전환용 도구를 활용하는 우회 설계가 필수
- 컨텍스트 윈도우 초과 시 수행하는 컴팩션(요약 축소) 작업도 부모 대화의 캐시 접두사를 공유해야 비용 폭증을 방지할 수 있음
- 캐시 적중률을 업타임처럼 모니터링하고, 수 퍼센트의 캐시 미스도 심각한 비용·지연 영향을 미치므로 인시던트로 취급해야 함
- “Cache Rules Everything Around Me” 는 에이전트에도 그대로 적용됨
- 프롬프트 캐싱(prompt caching) 덕분에 Claude Code 같은 장시간 에이전트 제품을 가능하게 만드는 게 가능함
- 이전 라운드트립의 계산을 재사용해 latency와 cost를 크게 줄이는 효과
프롬프트 캐싱의 작동 원리와 시스템 프롬프트 배치
- 프롬프트 캐싱은 접두사(Prefix) 매칭 방식으로 작동하며, API가 요청 시작부터 각 cache_control 중단점까지의 모든 내용을 캐싱
- 요청 간 공유 접두사를 최대화하는 것이 핵심이며, 이를 위해 정적 콘텐츠를 먼저, 동적 콘텐츠를 나중에 배치해야 함
- Claude Code의 배치 순서:
- 정적 시스템 프롬프트 및 도구 (전역 캐싱)
- Claude.MD (프로젝트 내 캐싱)
- 세션 컨텍스트 (세션 내 캐싱)
- 대화 메시지
- 이 순서는 놀라울 정도로 깨지기 쉬우며,
정적 시스템 프롬프트에 상세 타임스탬프 삽입,
도구 순서의 비결정적 셔플링,
도구 매개변수 업데이트
등으로 인해 캐시 무효화가 될 수 있음
시스템 메시지를 활용한 업데이트 전략
- 시간 정보 변화나 사용자의 파일 변경처럼 프롬프트 내부 정보가 오래되어 버리는 상황 존재
- 프롬프트를 직접 업데이트하면 캐시 미스로 이어져 사용자 비용 증가 가능성이 있음
- 대신 다음 턴에서 메시지로 전달하는 방식을 활용
- Claude Code는 다음 user message 또는 tool result에 <system-reminder> 태그로 업데이트 정보 삽입
- 예를 들어 “이제 Wednesday” 같은 시간 업데이트 제공
- 이렇게 system messages 를 이용 하면 캐시를 보존 가능
세션 중간에 모델을 변경하면 안 되는 이유
- 프롬프트 캐시는 모델별로 고유하므로, 캐싱 비용 계산이 직관과 다를 수 있음
- 예: Opus로 100k 토큰 대화 중 간단한 질문을 위해 Haiku로 전환하면, Haiku용 캐시를 처음부터 다시 구축해야 하므로 오히려 Opus로 답하는 것보다 비용이 더 높아짐
- 모델 전환이 필요한 경우 서브에이전트 방식이 최선이며, Opus가 다른 모델에 "핸드오프" 메시지를 준비해 전달하는 방식
- Claude Code의 Explore 에이전트가 Haiku를 사용할 때 이 방식을 적용
세션 중간에 도구 추가·제거 금지
- 도구 집합 변경은 캐시 무효화의 가장 흔한 원인 중 하나
- 도구가 캐싱된 접두사의 일부이므로, 도구 하나를 추가하거나 제거하면 전체 대화의 캐시가 무효화됨
-
Plan Mode — 캐시 중심 설계
- 직관적 접근: 사용자가 plan mode에 진입하면 읽기 전용 도구만 남기는 것 → 캐시 파괴
- 실제 설계: 모든 도구를 항상 요청에 포함하고, EnterPlanMode과 ExitPlanMode를 도구로 구현
- Plan mode 진입 시 시스템 메시지로 지침 전달: 코드베이스 탐색만 수행, 파일 편집 금지, 완료 시 ExitPlanMode 호출
- 도구 정의는 절대 변경되지 않음
- 부가 이점: EnterPlanMode이 모델이 직접 호출 가능한 도구이므로, 어려운 문제 감지 시 자율적으로 plan mode 진입 가능하며 캐시 파괴 없음
-
Tool Search — 제거 대신 지연 로딩
- Claude Code는 수십 개의 MCP 도구가 로드될 수 있으며, 모두 포함하면 비용이 높고 제거하면 캐시가 파괴됨
- 해결책: defer_loading 방식으로, 도구 제거 대신 이름만 포함된 경량 스텁(defer_loading: true)을 전송
- 모델이 필요 시 ToolSearch 도구를 통해 전체 스키마를 로드
- 동일한 스텁이 항상 같은 순서로 존재하므로 캐싱된 접두사가 안정적으로 유지
- API의 tool search 기능을 통해 이를 간소화 가능
컨텍스트 포킹 — 컴팩션(Compaction)
- 컨텍스트 윈도우를 초과하면 대화를 요약해서 새 세션을 시작하는 컴팩션 수행
- 직관적 구현(다른 시스템 프롬프트와 도구 없이 별도 API 호출로 요약 생성)은 메인 대화의 캐시 접두사와 전혀 일치하지 않아 모든 입력 토큰에 대해 전액 비용 발생
-
Cache-Safe Forking 솔루션
- 컴팩션 실행 시 부모 대화와 동일한 시스템 프롬프트, 사용자 컨텍스트, 시스템 컨텍스트, 도구 정의를 사용
- 부모의 대화 메시지를 앞에 배치하고, 컴팩션 프롬프트를 끝에 새 사용자 메시지로 추가
- API 관점에서 이 요청은 부모의 마지막 요청과 거의 동일하게 보이므로 캐싱된 접두사 재사용 가능하며, 새 토큰은 컴팩션 프롬프트뿐
- 컨텍스트 윈도우 내에 컴팩트 메시지와 요약 출력 토큰을 위한 "컴팩션 버퍼" 확보가 필요
- 이러한 패턴을 기반으로 Anthropic이 API에 직접 컴팩션 기능을 내장하여 개발자가 직접 적용 가능
핵심 교훈 요약
- 프롬프트 캐싱은 접두사 매칭이며, 접두사 어디에서든 변경이 발생하면 그 이후의 모든 캐시가 무효화됨 → 전체 시스템을 이 제약 중심으로 설계해야 함
- 시스템 프롬프트 변경 대신 대화 중 시스템 메시지를 삽입하는 방식이 캐시 보존에 유리
- 대화 중간에 도구나 모델을 변경하지 말 것 → 상태 전환은 도구로 모델링하고, 도구 제거 대신 지연 로딩 활용
- 캐시 적중률을 업타임처럼 모니터링해야 하며, 수 퍼센트의 캐시 미스율도 비용과 지연에 극적인 영향
- 포크 작업(컴팩션, 요약, 스킬 실행)은 부모의 접두사를 공유해야 캐시 적중이 가능
- 에이전트를 구축한다면 첫날부터 프롬프트 캐싱 중심으로 설계해야 함
프롬프트 엔지니어링에서 컨텍스트 엔지니어링으로의 전환이 핵심인데, 실무적으로는 "관심사의 분리"가 답인 것 같습니다.
페르소나, 행동 규칙, 메모리를 각각 다른 파일로 관리하면 context rot을 줄이는 데 효과적이죠. 모놀리식 프롬프트보다 필요한 파일만 로드하는 게 attention budget에 훨씬 유리하니까요. 그래서 OpenClaw(또는 그와 유사한 프레임워크)들이 페르소나(SOUL.md), 행동 규칙(AGENTS.md), 메모리(MEMORY.md)를 각각 관리 하는 것 같습니다.
진짜 독자:
Claude Code 같은 “장시간 실행되는 에이전트”를 만드는 사람들
(특히 제품/플랫폼 엔지니어, LLM 인프라 엔지니어)
누가 보면 제일 도움되냐?
✅ 1) AI 에이전트 제품 만드는 팀
- IDE 에이전트 (Claude Code, Cursor, Copilot Workspace 류)
- 리서치 에이전트
- 장시간 작업하는 자동화 에이전트
✅ 2) LLM 비용/지연 최적화하는 엔지니어
- 이 글은 사실상: “프롬프트 캐싱 최적화가 곧 제품 성능/비용이다”
- 인프라 쪽 사람이 보면 바로 감 옵니다.
✅ 3) MCP 도구 잔뜩 붙이는 사람
- tool 추가/삭제가 캐시 깨는 문제
- plan mode 구현을 “도구로 모델링”하는 문제
반대로, 일반 유저는 거의 안 봄
“프롬프트 잘 쓰는 법” 같은 글이 아니라
“프롬프트를 제품 아키텍처 레벨에서 어떻게 다뤄야 하는가”
한 줄로 정리
LLM을 “채팅”이 아니라 “프로덕션 시스템”으로 만드는 사람을 위한 글입니다.