Fix cross-references that pointed to non-existent directory paths: - key-concepts.md: 15 links → correct level-2-systems/level-3-internals paths - request-lifecycle.md: 7 links → correct level-2-systems paths - bridge-ide.md, context-compression.md, oauth-auth.md: level-2-architecture → level-1-overview - mcp-lsp-integration.md: sibling refs → correct ../level-2-systems/ paths - command-system.md: index.md → README.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
14 KiB
컨텍스트 압축 & 토큰 관리
레벨: 내부 구현 (Level 3) 대상 독자: Claude Code 핵심 기여자, 컨텍스트 관리 전략 연구자 관련 소스:
src/services/compact/,src/services/tokenEstimation.ts,src/memdir/,src/context/
개요
Claude Code의 컨텍스트 관리 시스템은 단일 메커니즘이 아니라 다중 계층으로 구성된 통합 전략이다. 대화가 길어질수록 토큰 사용량이 컨텍스트 윈도우 한계에 근접하며, 이를 처리하기 위해 다음 네 가지 서브시스템이 협력한다:
- 토큰 추정 엔진 (
tokenEstimation.ts): API 호출 없이 빠른 토큰 수 계산 - 자동 압축(AutoCompact) (
autoCompact.ts): 임계값 초과 시 대화를 자동 요약 - 대화 압축(Compact) (
compact.ts): 실제 요약 생성 및 메시지 교체 - 메모리 디렉토리(Memdir) (
memdir/): 장기 기억을 파일 시스템에 영속화
세 가지 상호 배타적인 컨텍스트 관리 모드가 존재하며, GrowthBook 플래그를 통해 선택된다: 표준 AutoCompact, 리액티브 압축(REACTIVE_COMPACT), 컨텍스트 콜랩스(CONTEXT_COLLAPSE).
아키텍처 다이어그램
graph TB
subgraph "토큰 모니터링"
TE["tokenEstimation.ts\n빠른 토큰 추정"]
TW["calculateTokenWarningState()\n임계값 상태 계산"]
CWH["compactWarningHook.ts\nUI 경고 표시"]
end
subgraph "압축 의사결정"
SAC["shouldAutoCompact()\n압축 필요성 판단"]
ACIN["autoCompactIfNeeded()\n압축 실행 조정"]
CB["Circuit Breaker\n연속 실패 3회 시 중단"]
end
subgraph "압축 실행"
SMC["trySessionMemoryCompaction()\nSession Memory 우선 시도"]
CC["compactConversation()\n전체 대화 요약"]
FA["runForkedAgent()\n독립 에이전트로 요약 생성"]
PCC["runPostCompactCleanup()\n압축 후 정리"]
end
subgraph "메모리 시스템"
MD["memdir/\n장기 기억 저장소"]
FRM["findRelevantMemories.ts\n관련 기억 검색"]
MS["memoryScan.ts\n기억 스캔"]
end
subgraph "컨텍스트 윈도우"
CW["getEffectiveContextWindowSize()\n실효 윈도우 계산"]
ACT["getAutoCompactThreshold()\n압축 임계값"]
end
TE --> TW
TW --> SAC
SAC --> ACIN
ACIN --> CB
CB --> SMC
SMC -->|"실패 시"| CC
CC --> FA
FA -->|"요약 완료"| PCC
CW --> ACT
ACT --> SAC
MD --> FRM
FRM --> CC
style ACIN fill:#2d6a9f,color:#fff
style CC fill:#5a4f9a,color:#fff
style SMC fill:#1e7a4f,color:#fff
style CB fill:#7a2020,color:#fff
토큰 임계값 계층
graph LR
CW["컨텍스트 윈도우\n(모델별 상이)"]
RES["예약 토큰\n(최대 20,000)"]
ECW["실효 윈도우\n= CW - RES"]
ACT["AutoCompact 임계값\n= ECW - 13,000"]
WT["경고 임계값\n= ECW - 20,000"]
ET["오류 임계값\n= ECW - 20,000"]
BL["차단 한계\n= ECW - 3,000"]
CW --> RES --> ECW
ECW --> ACT
ECW --> WT
ECW --> ET
ECW --> BL
style ACT fill:#d4a017,color:#000
style BL fill:#c0392b,color:#fff
style WT fill:#e67e22,color:#fff
핵심 구현 분석
1. 토큰 추정 엔진 (tokenEstimation.ts)
실제 API 토큰 카운팅은 네트워크 왕복 비용이 발생한다. tokenEstimation.ts는 로컬에서 빠르게 추정값을 계산하여 실시간 의사결정에 사용한다.
다중 API 공급자(Anthropic 직접, Bedrock, Vertex)를 지원하며, 각 공급자에 특화된 토큰 카운팅 로직이 구현되어 있다. Bedrock의 경우 @aws-sdk/client-bedrock-runtime을 동적으로 임포트하여 초기 번들 크기(약 279KB)를 절약한다.
thinking 블록 처리: 어시스턴트 메시지에 thinking 또는 redacted_thinking 블록이 포함된 경우 hasThinkingBlocks()가 이를 감지하여 토큰 카운팅 시 최소 예산(TOKEN_COUNT_THINKING_BUDGET = 1024)을 적용한다.
tool search 필드 정제: stripToolSearchFieldsFromMessages()는 tool search 베타에서만 유효한 caller 필드를 tool_use 블록에서 제거한다. API 오류를 방지하기 위해 토큰 카운팅 전에 메시지를 정제하는 방어적 설계다.
2. 실효 컨텍스트 윈도우 계산 (autoCompact.ts)
// 출력 예약 토큰 상한: 20,000 (p99.99 압축 요약 크기)
const MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000
export function getEffectiveContextWindowSize(model: string): number {
const reservedTokensForSummary = Math.min(
getMaxOutputTokensForModel(model),
MAX_OUTPUT_TOKENS_FOR_SUMMARY,
)
let contextWindow = getContextWindowForModel(model, getSdkBetas())
// 환경변수 오버라이드 (테스트용)
const autoCompactWindow = process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW
if (autoCompactWindow) {
const parsed = parseInt(autoCompactWindow, 10)
if (!isNaN(parsed) && parsed > 0) {
contextWindow = Math.min(contextWindow, parsed)
}
}
return contextWindow - reservedTokensForSummary
}
MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20,000은 압축 요약 출력의 p99.99 관찰값(17,387 토큰)에서 도출된 경험적 수치다.
3. 임계값 상태 계산
다섯 가지 임계값 상태가 계산된다:
export const AUTOCOMPACT_BUFFER_TOKENS = 13_000 // AutoCompact 발동 버퍼
export const WARNING_THRESHOLD_BUFFER_TOKENS = 20_000 // 경고 표시 버퍼
export const ERROR_THRESHOLD_BUFFER_TOKENS = 20_000 // 오류 표시 버퍼
export const MANUAL_COMPACT_BUFFER_TOKENS = 3_000 // 차단 한계 버퍼
// calculateTokenWarningState() 반환 타입
{
percentLeft: number // 잔여 비율 (0-100)
isAboveWarningThreshold: boolean
isAboveErrorThreshold: boolean
isAboveAutoCompactThreshold: boolean
isAtBlockingLimit: boolean // 입력 차단 여부
}
isAtBlockingLimit이 true이면 새 사용자 입력이 거부된다. 이는 API prompt_too_long 오류가 발생하기 전에 시스템이 선제적으로 개입하는 최후 방어선이다.
4. 자동 압축 의사결정 (shouldAutoCompact())
압축 실행 전 여러 가드 조건이 확인된다:
// 재귀 방지: session_memory, compact 소스는 압축 불가
if (querySource === 'session_memory' || querySource === 'compact') return false
// marble_origami (컨텍스트 에이전트) 방지
if (feature('CONTEXT_COLLAPSE') && querySource === 'marble_origami') return false
// 리액티브 전용 모드: 선제적 AutoCompact 억제
if (feature('REACTIVE_COMPACT') && getFeatureValue_CACHED_MAY_BE_STALE('tengu_cobalt_raccoon', false)) return false
// 컨텍스트 콜랩스 모드: AutoCompact 억제 (콜랩스가 90%/95% 지점을 처리)
if (feature('CONTEXT_COLLAPSE') && isContextCollapseEnabled()) return false
snipTokensFreed 파라미터는 snip 작업으로 이미 회수된 토큰을 차감하여 이중 계산을 방지한다.
5. 서킷 브레이커 패턴
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3
// 연속 실패 3회 후 해당 세션의 AutoCompact를 완전히 비활성화
if (tracking?.consecutiveFailures >= MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES) {
return { wasCompacted: false }
}
이 서킷 브레이커는 2026-03-10 BQ 분석 데이터에 기반한다: 단일 세션에서 50회 이상 연속 실패가 1,279개 세션에서 관찰되었으며 (최대 3,272회), 이로 인해 하루 약 250,000건의 불필요한 API 호출이 발생했다.
6. 압축 실행 순서 (autoCompactIfNeeded())
flowchart TD
A[shouldAutoCompact 확인] -->|true| B[trySessionMemoryCompaction 시도]
B -->|성공| C[setLastSummarizedMessageId 초기화]
C --> D[runPostCompactCleanup]
D --> E[notifyCompaction 캐시 베이스라인 초기화]
E --> F[markPostCompaction]
B -->|실패/없음| G[compactConversation 실행]
G --> H[runForkedAgent로 요약 생성]
H --> I[setLastSummarizedMessageId 초기화]
I --> J[runPostCompactCleanup]
G -->|오류| K[consecutiveFailures 증가]
K --> L{3회 이상?}
L -->|yes| M[서킷 브레이커 트립]
L -->|no| N[실패 반환]
Session Memory 압축이 우선 시도되는 이유: 세션 메모리는 메시지를 직접 정리하므로 비용이 낮다. 실패 시에만 전체 대화 요약(compactConversation)으로 폴백한다.
7. 대화 압축 구현 (compact.ts)
compactConversation()은 현재 대화를 독립 포크 에이전트(runForkedAgent)에게 전달하여 요약을 생성한다. 요약 생성에 사용되는 최대 출력 토큰은 COMPACT_MAX_OUTPUT_TOKENS로 제한된다.
주요 처리 단계:
executePreCompactHooks(): 압축 전 훅 실행analyzeContext(): 현재 컨텍스트 분석 (통계 수집)runForkedAgent(): 독립 에이전트로 요약 프롬프트 실행createCompactBoundaryMessage(): 압축 경계 메시지 생성normalizeMessagesForAPI(): API 전송용 메시지 정규화reAppendSessionMetadata(): 세션 메타데이터 재추가executePostCompactHooks(): 압축 후 훅 실행
RecompactionInfo 구조체는 연쇄 압축 추적에 사용된다:
export type RecompactionInfo = {
isRecompactionInChain: boolean // 이전 압축 후 재압축 여부
turnsSincePreviousCompact: number // 이전 압축 이후 턴 수
previousCompactTurnId?: string // 이전 압축 턴 ID
autoCompactThreshold: number // 적용된 임계값
querySource?: QuerySource // 압축 유발 소스
}
8. 메모리 디렉토리 시스템 (memdir/)
memdir은 세션 간에 유지되는 장기 기억 저장소다. 파일 시스템 기반으로 구현되어 있으며, 기억은 유형별로 분류된다.
// memoryTypes.ts
export const MEMORY_TYPE_VALUES = [
'episodic', // 특정 사건 기억
'semantic', // 지식/사실 기억
'procedural', // 절차/방법 기억
]
findRelevantMemories.ts는 현재 대화 컨텍스트와 관련된 기억을 검색하며, memoryScan.ts는 기억 디렉토리를 스캔하여 유효한 기억 항목을 필터링한다. memoryAge.ts는 기억의 노화(aging) 정책을 구현한다.
팀 메모리(teamMemPaths.ts, teamMemPrompts.ts)는 다중 에이전트 시나리오에서 에이전트 간 공유 기억을 관리한다.
9. 컨텍스트 윈도우 모달 컨텍스트 (src/context/)
src/context/ 디렉토리는 UI 레이어의 React 컨텍스트를 포함한다. mailbox.tsx는 에이전트 간 메시지 큐를 관리하며, stats.tsx는 토큰 통계를 React 상태로 노출한다.
// stats.tsx - 토큰 통계 컨텍스트
// modalContext.tsx - 모달 상태 관리
// QueuedMessageContext.tsx - 큐잉된 메시지 상태
// fpsMetrics.tsx - 렌더링 성능 메트릭
// voice.tsx - 음성 입력 컨텍스트
설계 결정
세 가지 압축 모드의 공존
AutoCompact, 리액티브 압축, 컨텍스트 콜랩스는 서로 배타적이며 GrowthBook 플래그로 제어된다. 이 설계는 프로덕션 환경에서 A/B 테스트를 가능하게 하면서 기능 플래그 없이 빌드에서는 불필요한 코드를 tree-shaking으로 제거할 수 있게 한다(feature('CONTEXT_COLLAPSE') 패턴).
컨텍스트 콜랩스 모드에서 AutoCompact가 억제되는 이유: 콜랩스의 90% 커밋 / 95% 차단 흐름이 실효 컨텍스트의 약 93%에 해당하는 AutoCompact 임계값(13,000 토큰 버퍼)과 겹치기 때문이다. 두 시스템이 동시에 활성화되면 경쟁 조건이 발생한다.
프롬프트 캐시 연속성 보장
압축 후 notifyCompaction()을 호출하여 프롬프트 캐시 베이스라인을 초기화한다. 이를 누락하면 압축 직후 캐시 읽기 드롭이 systemPromptChanged=true로 잘못 집계되어 tengu_prompt_cache_break 이벤트가 허위 양성으로 기록된다. 2026-03-01 BQ 분석에서 해당 이벤트의 20%가 허위 양성이었음이 확인되어 수정된 사항이다.
Session Memory 압축 우선순위
trySessionMemoryCompaction()이 compactConversation()보다 먼저 시도되는 이유는 비용 효율성이다. 세션 메모리 압축은 중요한 메시지를 선택적으로 정리하므로 전체 요약 생성 API 호출을 회피할 수 있다. setLastSummarizedMessageId(undefined) 초기화는 두 경로 모두에서 수행되어, 이전 메시지 UUID가 새 메시지 배열에 존재하지 않는 상황에서 발생하는 참조 오류를 방지한다.
환경변수 오버라이드
다수의 임계값이 환경변수로 오버라이드 가능하다:
CLAUDE_CODE_AUTO_COMPACT_WINDOW: 컨텍스트 윈도우 상한 설정CLAUDE_AUTOCOMPACT_PCT_OVERRIDE: 퍼센트 기반 임계값 설정 (0-100)CLAUDE_CODE_BLOCKING_LIMIT_OVERRIDE: 차단 한계 오버라이드DISABLE_COMPACT: 압축 전체 비활성화DISABLE_AUTO_COMPACT: 자동 압축만 비활성화 (수동/compact는 유지)
관련 파일 참조
| 파일 | 역할 |
|---|---|
src/services/compact/autoCompact.ts |
AutoCompact 임계값, 의사결정, 서킷 브레이커 |
src/services/compact/compact.ts |
대화 요약 생성 핵심 로직 |
src/services/compact/sessionMemoryCompact.ts |
Session Memory 기반 압축 |
src/services/compact/compactWarningHook.ts |
UI 경고 훅 |
src/services/compact/microCompact.ts |
마이크로 압축 |
src/services/compact/prompt.ts |
압축 프롬프트 템플릿 |
src/services/compact/postCompactCleanup.ts |
압축 후 정리 |
src/services/tokenEstimation.ts |
토큰 추정 엔진 (Anthropic/Bedrock/Vertex) |
src/memdir/memdir.ts |
장기 기억 저장소 진입점 |
src/memdir/findRelevantMemories.ts |
컨텍스트 기반 기억 검색 |
src/memdir/memoryScan.ts |
기억 디렉토리 스캔 |
src/memdir/memoryTypes.ts |
기억 유형 정의 |
src/context/stats.tsx |
토큰 통계 React 컨텍스트 |
src/context/mailbox.tsx |
에이전트 메시지 큐 |