엔지니어링 분야에서 흔히 "캐시가 모든 것을 지배한다"는 말이 있습니다. 이는 에이전트의 경우에도 마찬가지로 적용됩니다.
Claude Code와 같은 장기 실행 에이전트 제품은 프롬프트 캐싱 덕분에 가능합니다. 이를 통해 이전 왕복의 계산 결과를 재사용하여 지연 시간과 비용을 크게 줄일 수 있습니다.
프롬프트 캐싱이란 무엇이며, 어떻게 작동하고, 기술적으로 어떻게 구현할까요? @RLanceMartin의 프롬프트 캐싱 및 새로운 자동 캐싱 출시에 관한 글에서 자세히 알아보세요.
Claude Code에서는 전체 하네스를 프롬프트 캐싱을 기반으로 구축했습니다. 높은 프롬프트 캐시 적중률은 비용을 절감하고 구독 요금제에 더 관대한 비율 제한을 설정하는 데 도움이 되므로, 프롬프트 캐시 적중률에 대한 알림을 실행하고 너무 낮을 경우 SEV를 선언합니다.
다음은 대규모로 프롬프트 캐싱을 최적화하면서 얻은 (종종 직관적이지 않은) 교훈들입니다.
캐싱을 위한 프롬프트 구성

프롬프트 캐싱은 접두사 일치 방식으로 작동합니다. API는 요청의 시작부터 각 cache_control 중단점까지 모든 것을 캐시합니다. 즉, 항목을 배치하는 순서가 매우 중요하며, 가능한 한 많은 요청이 접두사를 공유하도록 해야 합니다.
가장 좋은 방법은 정적 콘텐츠를 먼저, 동적 콘텐츠를 마지막에 배치하는 것입니다. Claude Code의 경우 다음과 같습니다.
- 정적 시스템 프롬프트 및 도구 (전역 캐시)
- Claude.MD (프로젝트 내에서 캐시)
- 세션 컨텍스트 (세션 내에서 캐시)
- 대화 메시지
이렇게 하면 더 많은 세션이 캐시 적중을 공유하도록 최대화할 수 있습니다.
하지만 이는 놀라울 정도로 깨지기 쉽습니다! 이 순서를 위반한 사례는 다음과 같습니다. 정적 시스템 프롬프트에 상세한 타임스탬프를 넣거나, 도구 순서 정의를 비결정적으로 섞거나, 도구의 매개변수(예: AgentTool이 호출할 수 있는 에이전트)를 업데이트하는 경우 등이 있습니다.
업데이트에는 메시지 사용
프롬프트에 넣은 정보가 시간이 지나면서 구식이 될 수 있습니다(예: 시간 정보나 사용자가 파일을 변경한 경우). 프롬프트를 업데이트하고 싶은 유혹이 있을 수 있지만, 그러면 캐시 미스가 발생하여 사용자에게 상당히 비용이 많이 들 수 있습니다.
대신 다음 턴에 메시지를 통해 이 정보를 전달할 수 있는지 고려하세요. Claude Code에서는 다음 사용자 메시지나 도구 결과에 업데이트된 정보(예: 현재 수요일임)가 포함된 <system-reminder> 태그를 추가하여 캐시를 보존합니다.
세션 중간에 모델 변경 금지
프롬프트 캐시는 모델마다 고유하므로 프롬프트 캐싱의 계산이 매우 직관적이지 않을 수 있습니다.
Opus와의 대화에서 10만 토큰을 사용한 후 비교적 쉬운 질문을 하고 싶다면, Haiku의 프롬프트 캐시를 다시 구축해야 하므로 Opus가 답변하도록 하는 것보다 Haiku로 전환하는 것이 오히려 더 비쌉니다.
모델을 전환해야 하는 경우 가장 좋은 방법은 하위 에이전트를 사용하는 것입니다. Opus가 필요한 작업에 대해 다른 모델에 전달할 "핸드오프" 메시지를 준비하는 방식입니다. 우리는 Claude Code의 Explore 에이전트에서 Haiku를 사용하여 이 작업을 자주 수행합니다.
세션 중간에 도구 추가 또는 제거 금지
대화 중간에 도구 세트를 변경하는 것은 프롬프트 캐싱을 깨는 가장 흔한 방법 중 하나입니다. 직관적으로는 모델이 지금 당장 필요하다고 생각되는 도구만 제공해야 한다고 생각할 수 있습니다. 하지만 도구는 캐시된 접두사의 일부이므로 도구를 추가하거나 제거하면 전체 대화의 캐시가 무효화됩니다.
계획 모드 — 캐시 중심 설계
계획 모드는 캐싱 제약 조건에 맞춰 기능을 설계한 좋은 예입니다. 직관적인 접근 방식은 사용자가 계획 모드에 진입하면 도구 세트를 읽기 전용 도구만 포함하도록 교체하는 것입니다. 하지만 이렇게 하면 캐시가 깨집니다.
대신, 요청에 모든 도구를 항상 유지하고 EnterPlanMode와 ExitPlanMode 자체를 도구로 사용합니다. 사용자가 계획 모드를 켜면 에이전트는 계획 모드임을 알리는 시스템 메시지와 함께 지침(코드베이스 탐색, 파일 편집 금지, 계획 완료 시 ExitPlanMode 호출)을 받습니다. 도구 정의는 절대 변경되지 않습니다.
여기에는 추가적인 이점도 있습니다: EnterPlanMode는 모델이 스스로 호출할 수 있는 도구이므로, 캐시 중단 없이 어려운 문제를 감지했을 때 자율적으로 계획 모드에 진입할 수 있습니다.
도구 검색 — 제거 대신 지연
동일한 원칙이 도구 검색 기능에도 적용됩니다. Claude Code는 수십 개의 MCP 도구를 로드할 수 있으며, 이를 모든 요청에 포함시키는 것은 비용이 많이 듭니다. 하지만 대화 중간에 제거하면 캐시가 깨집니다.
해결책은 defer_loading입니다. 도구를 제거하는 대신, 도구 이름만 포함된 가벼운 스텁(defer_loading: true 포함)을 보내 모델이 필요할 때 ToolSearch 도구를 통해 "발견"하도록 합니다. 전체 도구 스키마는 모델이 선택한 경우에만 로드됩니다. 이렇게 하면 동일한 스텁이 항상 동일한 순서로 존재하여 캐시된 접두사가 안정적으로 유지됩니다.
다행히도 API를 통해 도구 검색 도구를 사용하여 이를 간소화할 수 있습니다.
컨텍스트 분기 — 압축

압축은 컨텍스트 창이 부족할 때 발생합니다. 지금까지의 대화를 요약하고 해당 요약으로 새 세션을 계속합니다.
놀랍게도 압축에는 프롬프트 캐싱과 관련하여 직관적이지 않은 많은 엣지 케이스가 있습니다.
특히 압축할 때는 요약을 생성하기 위해 전체 대화를 모델에 보내야 합니다. 이것이 별도의 API 호출(다른 시스템 프롬프트와 도구 없음)인 경우(간단한 구현), 기본 대화의 캐시된 접두사와 전혀 일치하지 않습니다. 모든 입력 토큰에 대해 전체 가격을 지불하게 되어 사용자 비용이 급격히 증가합니다.
해결책 — 캐시 안전 분기
압축을 실행할 때는 상위 대화와 정확히 동일한 시스템 프롬프트, 사용자 컨텍스트, 시스템 컨텍스트 및 도구 정의를 사용합니다. 상위 대화의 메시지를 앞에 추가한 다음, 압축 프롬프트를 새 사용자 메시지로 마지막에 추가합니다.
API 관점에서 이 요청은 상위의 마지막 요청과 거의 동일합니다. 동일한 접두사, 동일한 도구, 동일한 기록을 가지므로 캐시된 접두사가 재사용됩니다. 새로운 토큰은 압축 프롬프트 자체뿐입니다.
다만 이를 위해서는 압축 메시지와 요약 출력 토큰을 포함할 수 있도록 컨텍스트 창에 충분한 공간을 확보하기 위해 "압축 버퍼"를 저장해야 합니다.
압축은 까다롭지만 다행히도 이러한 교훈을 직접 배울 필요는 없습니다. Claude Code의 경험을 바탕으로 API에 압축을 직접 구축했으므로, 여러분의 애플리케이션에서도 이러한 패턴을 적용할 수 있습니다.
얻은 교훈
- 프롬프트 캐싱은 접두사 일치입니다. 접두사 내의 어떤 변경도 그 이후의 모든 것을 무효화합니다. 이 제약 조건에 맞춰 전체 시스템을 설계하세요. 순서를 올바르게 정하면 대부분의 캐싱이 자동으로 작동합니다.
- 시스템 프롬프트 변경 대신 메시지를 사용하세요. 계획 모드 진입, 날짜 변경 등을 위해 시스템 프롬프트를 편집하고 싶을 수 있지만, 대화 중에 이를 메시지로 삽입하는 것이 더 좋습니다.
- 대화 중간에 도구나 모델을 변경하지 마세요. 도구 세트를 변경하는 대신 도구를 사용하여 상태 전환(예: 계획 모드)을 모델링하세요. 도구를 제거하는 대신 로딩을 지연시키세요.
- 가동 시간을 모니터링하듯 캐시 적중률을 모니터링하세요. 캐시 중단 시 알림을 보내고 장애로 처리합니다. 몇 퍼센트의 캐시 미스율이 비용과 지연 시간에 큰 영향을 미칠 수 있습니다.
- 분기 작업은 상위의 접두사를 공유해야 합니다. 사이드 계산(압축, 요약, 스킬 실행)을 실행해야 하는 경우 동일한 캐시 안전 매개변수를 사용하여 상위 접두사에서 캐시 적중을 얻으세요.
Claude Code는 처음부터 프롬프트 캐싱을 기반으로 구축되었습니다. 에이전트를 구축 중이라면 여러분도 동일한 방식을 따라야 합니다.





