컨텍스트 관리 훅 (Context Management Hooks)
관련 소스 파일
목적 및 범위 (Purpose and Scope)
컨텍스트 관리 훅(Context Management Hooks)은 LLM 대화 컨텍스트 내의 토큰 소비를 모니터링하고 관리하여 컨텍스트 윈도우(Context Window) 오버플로우를 방지하고, 토큰 제한 오류로부터 유연한 복구를 가능하게 합니다. 이 시스템은 다음과 같이 조정된 세 가지 메커니즘을 구현합니다:
- 컨텍스트 윈도우 모니터 (Context Window Monitor): 토큰 사용량을 추적하고 에이전트에게 선제적인 경고를 제공합니다.
- 선제적 압축 (Preemptive Compaction): 컨텍스트 용량의 80%에 도달하면 트리거되어 요약(Summarization)을 유도합니다.
- Anthropic 자동 압축 (Anthropic Auto-Compact): 트런케이션(Truncation, 자르기), 되돌리기(Reversion), 요약을 통해 토큰 제한 오류로부터 자동으로 복구합니다.
이 훅들은 Context Window Anxiety Management 패턴을 구현하여, 실제 제한에 도달했을 때 원활한 복구를 가능하게 함과 동시에 조기 컨텍스트 패닉을 방지합니다.
문서 주입을 통한 컨텍스트 강화에 대한 정보는 Context Injection Hooks를 참조하십시오. 도구 출력 트런케이션에 대해서는 Tool Enhancement Hooks를 참조하십시오.
src/hooks/anthropic-auto-compact/index.ts L1-L154
훅 등록 및 초기화 (Hook Registry and Initialization)
세 가지 컨텍스트 관리 훅은 disabled_hooks 설정 배열에 따라 조건부로 초기화됩니다:
| 훅 이름 | 생성 함수 | 설정 키 | 기본 상태 | 라이프사이클 이벤트 |
|---|---|---|---|---|
| Context Window Monitor | createContextWindowMonitorHook |
context-window-monitor |
활성화됨 | message.updated |
| Preemptive Compaction | (Monitor와 통합됨) | (동일) | 활성화됨 | message.updated |
| Anthropic Auto-Compact | createAnthropicAutoCompactHook |
anthropic-auto-compact |
활성화됨 | session.error, message.updated, session.idle |
Anthropic Auto-Compact 훅은 공격적인 트런케이션 동작을 위한 experimental 설정 객체를 허용합니다:
const anthropicAutoCompact = isHookEnabled("anthropic-auto-compact")
? createAnthropicAutoCompactHook(ctx, {
experimental: config.experimental
})
: null;
출처: src/hooks/anthropic-auto-compact/index.ts L24-L149
src/hooks/anthropic-auto-compact/types.ts L1-L54
컨텍스트 관리 파이프라인 (Context Management Pipeline)
이벤트 기반 모니터링 및 복구 흐름
flowchart TD
AggressiveTrunc["공격적 트런케이션"]
SessionIdle["session.idle 이벤트"]
CheckPending["pendingCompact<br>플래그 설정됨?"]
ExecuteCompact["executeCompact 실행"]
LargestTrunc["가장 큰 결과 트런케이션"]
FixEmpty["빈 메시지 수정"]
Revert["메시지 되돌리기"]
Summarize["전체 요약"]
RetryPrompt["자동으로 다시 프롬프트"]
ClearState["복구 상태 초기화"]
SessionError["session.error 이벤트<br>또는 오류가 포함된 message.updated"]
ErrorParser["parseAnthropicTokenLimitError"]
TokenLimitCheck["토큰 제한<br>초과?"]
SetPending["pendingCompact 플래그 설정<br>오류 데이터 저장"]
EmptyContentCheck["비어 있지 않은<br>콘텐츠 오류?"]
MessageUpdate["message.updated 이벤트"]
ContextMonitor["컨텍스트 윈도우 모니터"]
TokenCalc["토큰 사용량 계산"]
CheckThreshold["토큰 사용량 > 70%?"]
Warning["여유 공간 경고 표시"]
CheckPreemptive["토큰 사용량 > 80%?"]
PreemptivePrompt["압축 프롬프트 주입"]
subgraph subGraph3 ["복구 실행 (Recovery Execution)"]
SessionIdle
CheckPending
ExecuteCompact
RetryPrompt
ClearState
SessionIdle -.->|"아니오"| CheckPending
CheckPending -.->|"예"| ExecuteCompact
CheckPending -.-> SessionIdle
ExecuteCompact -.-> AggressiveTrunc
Summarize -.-> RetryPrompt
RetryPrompt -.->|"예"| ClearState
subgraph subGraph2 ["복구 전략 (Recovery Strategies)"]
AggressiveTrunc
LargestTrunc
FixEmpty
Revert
Summarize
AggressiveTrunc -.-> LargestTrunc
LargestTrunc -.-> FixEmpty
FixEmpty -.-> Revert
Revert -.-> Summarize
end
end
subgraph subGraph1 ["오류 감지 (Error Detection)"]
SessionError
ErrorParser
TokenLimitCheck
SetPending
EmptyContentCheck
SessionError -.->|"예"| ErrorParser
ErrorParser -.-> TokenLimitCheck
TokenLimitCheck -.-> SetPending
ErrorParser -.-> EmptyContentCheck
EmptyContentCheck -.-> SetPending
end
subgraph subGraph0 ["지속적 모니터링 (Continuous Monitoring)"]
MessageUpdate
ContextMonitor
TokenCalc
CheckThreshold
Warning
CheckPreemptive
PreemptivePrompt
MessageUpdate -.->|"아니오"| ContextMonitor
ContextMonitor -.->|"아니오"| TokenCalc
TokenCalc -.->|"예"| CheckThreshold
CheckThreshold -.->|"예"| Warning
Warning -.-> CheckPreemptive
CheckThreshold -.-> MessageUpdate
CheckPreemptive -.-> PreemptivePrompt
CheckPreemptive -.-> MessageUpdate
end
주요 특징:
- 선제적 (Proactive): 컨텍스트 윈도우 모니터가 문제가 발생하기 전에 방지합니다.
- 반응적 (Reactive): Auto-Compact가 실제 발생한 오류로부터 자동으로 복구합니다.
- 계단식 (Cascading): 여러 폴백(Fallback) 전략을 통해 복구 성공을 보장합니다.
출처: src/hooks/anthropic-auto-compact/index.ts L28-L143
src/hooks/anthropic-auto-compact/executor.ts L249-L397
컨텍스트 윈도우 모니터 (Context Window Monitor)
개요
컨텍스트 윈도우 모니터는 실시간으로 토큰 소비를 추적하고, 용량 제한에 도달할 때 에이전트에게 선제적인 경고를 제공합니다. 이는 에이전트가 공간 제약을 인식하여 조기에 요약하거나 작업을 중단하는 “컨텍스트 윈도우 불안감(context window anxiety)”을 방지합니다.
토큰 추적 메커니즘
모니터는 message.updated 이벤트를 관찰하고 메시지 콘텐츠를 기반으로 대략적인 토큰 사용량을 계산합니다. 사용량이 특정 임계값을 넘으면 대화에 경고나 프롬프트를 주입합니다.
flowchart TD
MessageEvent["message.updated 이벤트<br>role: assistant"]
ExtractContent["메시지 콘텐츠 추출<br>parts, tool calls, text"]
EstimateTokens["토큰 수 추정<br>토큰당 약 4자"]
Check70["사용량 > 최대 토큰의 70%?"]
Check80["사용량 > 최대 토큰의 80%?"]
Toast70["토스트 표시:<br>'컨텍스트 여유가 충분합니다'"]
Toast80["토스트 표시:<br>'요약을 고려해 보세요'"]
InjectPrompt["시스템 메시지 주입:<br>'컨텍스트 사용량이 높습니다. 필요시 요약하세요'"]
WarningMap["warningIssued:<br>Map sessionID -> threshold"]
UpdateMap["경고 발행됨 표시<br>중복 경고 방지"]
MessageEvent -.->|"아니오"| ExtractContent
ExtractContent -.-> EstimateTokens
EstimateTokens -.->|"아니오"| Check70
Check70 -.->|"예"| Toast70
Toast70 -.-> Check80
Check70 -.-> MessageEvent
Check80 -.->|"예"| Toast80
InjectPrompt -.-> UpdateMap
Check80 -.-> MessageEvent
subgraph subGraph2 ["상태 추적"]
WarningMap
UpdateMap
end
subgraph subGraph1 ["경고 액션"]
Toast70
Toast80
InjectPrompt
Toast80 -.-> InjectPrompt
end
subgraph subGraph0 ["임계값 감지"]
Check70
Check80
end
경고 임계값 (Warning Thresholds)
| 임계값 | 액션 | 목적 |
|---|---|---|
| 70% | 안심 토스트 표시 | 조기 불안감 방지 |
| 80% | 경고 표시 + 프롬프트 주입 | 선제적 요약 유도 |
| 100% | 오류 → Auto-Compact 트리거 | 자동 복구 |
세션 상태 관리
모니터는 경고 스팸을 방지하기 위해 세션별 상태를 유지합니다:
interface ContextMonitorState {
warningIssued: Map<string, number> // sessionID -> 경고가 발생한 최고 임계값
lastTokenCount: Map<string, number> // sessionID -> 마지막으로 계산된 토큰 수
}
경고는 세션당 임계값별로 한 번만 발행됩니다.
선제적 압축 (Preemptive Compaction)
개요
선제적 압축은 컨텍스트 윈도우 모니터와 통합되어 있으며 토큰 임계값 80%에서 활성화됩니다. 반응적 복구와 달리, 이 메커니즘은 오류가 발생하기 전에 에이전트가 컨텍스트를 요약하거나 압축하도록 선제적으로 권장합니다.
트리거 로직
flowchart TD
TokenUsage["토큰 사용량 계산됨"]
Check80["사용량 >= 최대 컨텍스트의 80%?"]
CheckWarned["이 세션에 대해<br>이미 경고했는가?"]
InjectPrompt["시스템 프롬프트 주입:<br>'높은 컨텍스트 사용량 감지...'"]
MarkWarned["세션을 경고됨으로 표시<br>반복 프롬프트 방지"]
NoAction["액션 없음"]
TokenUsage -.->|"예"| Check80
Check80 -.->|"아니오"| CheckWarned
Check80 -.->|"예"| NoAction
CheckWarned -.->|"아니오"| InjectPrompt
CheckWarned -.-> NoAction
InjectPrompt -.-> MarkWarned
주입 전략
80% 임계값을 넘으면 모니터는 시스템 메시지를 주입합니다:
High context usage detected (>80%). Consider summarizing recent tool outputs
or delegating subtasks to background agents to free up context space.
(높은 컨텍스트 사용량이 감지되었습니다(>80%). 컨텍스트 공간을 확보하기 위해 최근 도구 출력을 요약하거나 하위 작업을 백그라운드 에이전트에게 위임하는 것을 고려해 보십시오.)
이 프롬프트는:
- 대화에서 시스템 레벨 메시지로 나타납니다.
- 구체적인 액션(요약, 위임)을 제안합니다.
- 압축을 강제하지 않고 에이전트의 재량에 맡깁니다.
- 세션당 80% 임계값에서 단 한 번만 트리거됩니다.
동작 특성
| 측면 | 동작 |
|---|---|
| 트리거 임계값 | 최대 컨텍스트 토큰의 80% |
| 액션 유형 | 시스템 메시지 주입 |
| 빈도 | 임계값당 세션별 1회 |
| 에이전트 영향 | 제안 사항이며 강제성 없음 |
| 조정 | 완전한 커버리지를 위해 Auto-Compact와 함께 작동 |
Anthropic 자동 압축 (Anthropic Auto-Compact)
개요
Anthropic Auto-Compact는 Anthropic(Claude) 모델의 토큰 제한 오류를 자동으로 처리하는 반응적 복구 시스템입니다. 세션이 컨텍스트 제한에 도달하면, 이 훅은 사용자 개입 없이 다중 전략 복구 파이프라인을 실행합니다.
오류 감지 및 파싱
flowchart TD
SessionError["session.error 이벤트"]
MessageError["오류 필드가 포함된<br>message.updated"]
ExtractMessage["중첩된 구조에서<br>오류 메시지 추출"]
CheckKeywords["키워드 확인:<br>'prompt is too long'<br>'context_length_exceeded'<br>'non-empty content'"]
ExtractTokens["토큰 수 추출:<br>Regex: /(\d+) tokens > (\d+) maximum/"]
ExtractIndex["메시지 인덱스 추출:<br>Regex: /messages.(\d+)/"]
ParsedError["ParsedTokenLimitError<br>{currentTokens, maxTokens,<br>errorType, messageIndex}"]
SetPending["pendingCompact 플래그 설정<br>Map에 오류 데이터 저장"]
SessionError -.-> ExtractMessage
MessageError -.-> ExtractMessage
ExtractIndex -.-> ParsedError
ParsedError -.-> SetPending
subgraph subGraph1 ["오류 파싱 (parseAnthropicTokenLimitError)"]
ExtractMessage
CheckKeywords
ExtractTokens
ExtractIndex
ExtractMessage -.-> CheckKeywords
CheckKeywords -.-> ExtractTokens
ExtractTokens -.-> ExtractIndex
end
subgraph subGraph0 ["오류 소스"]
SessionError
MessageError
end
지원되는 오류 유형
| 오류 유형 | 트리거 패턴 | 복구 전략 |
|---|---|---|
token_limit_exceeded |
“X tokens > Y maximum” | 트런케이션 → 되돌리기 → 요약 |
non-empty content |
“messages[N]: non-empty content” | 빈 메시지 수정 → 재시도 |
bedrock_input_too_long |
Bedrock 전용 오류 | 공격적 트런케이션 |
context_length_exceeded |
일반적인 OpenAI 스타일 오류 | 표준 파이프라인 |
출처: src/hooks/anthropic-auto-compact/parser.ts L1-L183
src/hooks/anthropic-auto-compact/types.ts L1-L10
복구 파이프라인 아키텍처
flowchart TD
ErrorDetected["오류 파싱 및 저장됨"]
SessionIdle["session.idle 이벤트"]
CheckPending["pendingCompact<br>플래그 설정됨?"]
ExecuteCompact["executeCompact 함수"]
CheckExperimental["experimental.aggressive_truncation<br>활성화됨?"]
CalcTarget["대상 토큰 계산<br>targetTokens = maxTokens * 0.5"]
FindAll["findToolResultsBySize<br>크기순으로 모든 도구 출력 가져오기"]
TruncateLoop["대상에 도달할 때까지<br>출력을 반복적으로 트런케이션"]
CheckSufficient["충분한 바이트가<br>제거되었는가?"]
FindLargest["findLargestToolResult<br>수정되지 않은 가장 큰 출력 가져오기"]
TruncateSingle["truncateToolResult<br>TRUNCATION_MESSAGE로 교체"]
CheckEmptyError["오류 유형:<br>non-empty content?"]
FindEmpty["findEmptyMessages<br>또는 findEmptyMessageByIndex"]
ReplaceEmpty["replaceEmptyTextParts<br>또는 injectTextPart"]
InjectInterrupted["주입: '[user interrupted]'"]
GetLastPair["getLastMessagePair<br>userMessageID + assistantMessageID"]
CheckRevertLimit["revertAttempt <<br>maxRevertAttempts?"]
RevertMessage["client.session.revert<br>마지막 어시스턴트 응답 제거"]
GetProvider["마지막 어시스턴트 메시지에서<br>providerID 및 modelID 가져오기"]
Summarize["client.session.summarize<br>전체 히스토리 압축"]
RetryPrompt["client.session.prompt_async<br>자동 재시도"]
ToastSuccess["토스트: '세션이 복구되었습니다'"]
ToastFailure["토스트: '수동 개입이 필요합니다'"]
ClearState["pendingCompact 플래그 초기화"]
ErrorDetected -.->|"아니오"| SessionIdle
SessionIdle -.->|"예"| CheckPending
CheckPending -.-> SessionIdle
CheckPending -.->|"예"| ExecuteCompact
ExecuteCompact -.-> CheckExperimental
CheckSufficient -.->|"예"| RetryPrompt
CheckSufficient -.->|"없음"| FindLargest
CheckExperimental -.-> FindLargest
FindLargest -.->|"아니오"| CheckEmptyError
TruncateSingle -.-> RetryPrompt
CheckEmptyError -.->|"예"| GetLastPair
InjectInterrupted -.-> RetryPrompt
CheckRevertLimit -.-> GetProvider
RevertMessage -.-> RetryPrompt
Summarize -.-> RetryPrompt
RetryPrompt -.-> ToastSuccess
RetryPrompt -.-> ToastFailure
ToastSuccess -.-> ClearState
ToastFailure -.-> ClearState
subgraph subGraph4 ["전략 5: 요약"]
GetProvider
Summarize
GetProvider -.-> Summarize
end
subgraph subGraph3 ["전략 4: 메시지 되돌리기"]
GetLastPair
CheckRevertLimit
RevertMessage
GetLastPair -.-> CheckRevertLimit
CheckRevertLimit -.-> RevertMessage
end
subgraph subGraph2 ["전략 3: 빈 메시지 수정"]
CheckEmptyError
FindEmpty
ReplaceEmpty
InjectInterrupted
CheckEmptyError -.-> FindEmpty
FindEmpty -.->|"아니오"| ReplaceEmpty
ReplaceEmpty -.->|"실패"| InjectInterrupted
end
subgraph subGraph1 ["전략 2: 단일 트런케이션"]
FindLargest
TruncateSingle
FindLargest -.->|"예"| TruncateSingle
end
subgraph subGraph0 ["전략 1: 공격적 트런케이션"]
CheckExperimental
CalcTarget
FindAll
TruncateLoop
CheckSufficient
CheckExperimental -.->|"아니오"| CalcTarget
CalcTarget -.-> FindAll
FindAll -.->|"아니오"| TruncateLoop
TruncateLoop -.->|"찾음"| CheckSufficient
end
파이프라인 특성:
- 계단식 폴백 (Cascading Fallbacks): 각 전략은 점진적으로 더 큰 영향을 미칩니다.
- 상태 추적: 무한 루프를 방지하기 위해 시도 횟수를 카운트합니다.
- 자동 재시도: 성공적인 복구 후 프롬프트를 자동으로 다시 실행합니다.
출처: src/hooks/anthropic-auto-compact/executor.ts L249-L397
src/hooks/anthropic-auto-compact/index.ts L107-L143
전략 1: 공격적 트런케이션 (실험적)
공격적 트런케이션은 목표 토큰 감소량을 달성하기 위해 여러 도구 출력을 동시에 제거합니다. 이는 대규모 출력이 많이 존재할 때 가장 효율적인 전략입니다.
설정
{
"experimental": {
"aggressive_truncation": true
}
}
알고리즘
// 목표: 최대 토큰의 50% (TRUNCATE_CONFIG.targetTokenRatio를 통해 설정 가능)
targetTokens = maxTokens * 0.5
tokensToReduce = currentTokens - targetTokens
bytesToReduce = tokensToReduce * 4 // 토큰당 4자로 가정
// 출력 크기순으로 정렬된 모든 도구 결과 찾기 (큰 것부터)
results = findToolResultsBySize(sessionID)
// 목표에 도달할 때까지 반복적으로 트런케이션
for (result of results) {
if (totalBytesRemoved >= bytesToReduce) break
truncateToolResult(result.partPath)
totalBytesRemoved += result.outputSize
}
트런케이션 설정 상수
| 상수 | 값 | 설명 |
|---|---|---|
maxTruncateAttempts |
20 | 복구당 트런케이션할 최대 출력 수 |
targetTokenRatio |
0.5 | 트런케이션 후 목표 토큰 비율 (50%) |
charsPerToken |
4 | 토큰당 추정 문자 수 |
minOutputSizeToTruncate |
500 | 작은 출력은 무시 |
출처: src/hooks/anthropic-auto-compact/executor.ts L264-L340
src/hooks/anthropic-auto-compact/types.ts L48-L53
src/hooks/anthropic-auto-compact/storage.ts L195-L257
전략 2: 단일 출력 트런케이션
공격적 트런케이션이 비활성화되어 있거나 불충분한 경우, 시스템은 가장 큰 단일 도구 출력을 트런케이션합니다.
저장 구조
도구 출력은 OpenCode 저장소 디렉토리에 저장됩니다:
~/.local/share/opencode/storage/
├── message/
│ └── {sessionID}/
│ └── {messageID}.json
└── part/
└── {messageID}/
└── {partID}.json ← 도구 결과가 여기에 저장됨
각 파트(part) 파일은 다음을 포함합니다:
interface StoredToolPart {
id: string
sessionID: string
messageID: string
type: "tool"
callID: string
tool: string
state: {
status: "completed"
input: Record<string, unknown>
output: string ← 원본 출력
time: { start: number, end?: number, compacted?: number }
}
truncated?: boolean ← 트런케이션 후 true로 설정
originalSize?: number ← 참조를 위해 저장된 원본 크기
}
트런케이션 프로세스
flowchart TD
FindLargest["findLargestToolResult<br>모든 message/part 파일 스캔<br>output.length 순으로 정렬"]
ReadPart["PART_STORAGE에서<br>파트 JSON 파일 읽기"]
ModifyOutput["output = TRUNCATION_MESSAGE<br>truncated = true<br>originalSize = output.length"]
WritePart["writeFileSync<br>수정된 파트 저장"]
NextTool["다음 도구 결과를<br>읽을 수 있음"]
FindLargest -.-> ReadPart
ReadPart -.-> ModifyOutput
ModifyOutput -.-> WritePart
WritePart -.-> NextTool
트런케이션 메시지
[TOOL RESULT TRUNCATED - Context limit exceeded. Original output was too large
and has been truncated to recover the session. Please re-run this tool if you
need the full output.]
출처: src/hooks/anthropic-auto-compact/storage.ts L85-L154
src/hooks/anthropic-auto-compact/executor.ts L342-L367
전략 3: 빈 메시지 복구
“non-empty content” 오류는 대화 히스토리의 메시지에 콘텐츠가 없을 때 발생하며, 이는 API 요구 사항 위반입니다. 이 전략은 플레이스홀더 텍스트를 주입하여 빈 메시지를 수정합니다.
감지
// 오류 메시지 예시:
// "messages[12]: non-empty content"
const messageIndex = extractMessageIndex(errorMessage) // 결과: 12
const emptyMessageId = findEmptyMessageByIndex(sessionID, messageIndex)
수정 방법
| 방법 | 함수 | 동작 |
|---|---|---|
| 교체 (Replace) | replaceEmptyTextParts |
기존의 빈 텍스트 파트를 교체 |
| 주입 (Inject) | injectTextPart |
파트가 없는 경우 새로운 텍스트 파트 추가 |
두 방법 모두 플레이스홀더 "[user interrupted]"를 주입합니다.
복구 흐름
flowchart TD
EmptyError["오류: 'non-empty content'"]
ExtractIndex["오류 텍스트에서<br>메시지 인덱스 추출"]
FindMessage["findEmptyMessageByIndex<br>또는 findEmptyMessages"]
CheckParts["메시지에<br>빈 파트가 있는가?"]
Replace["replaceEmptyTextParts<br>'[user interrupted]'"]
Inject["injectTextPart<br>'[user interrupted]'"]
Toast["토스트: 'N개의 빈 메시지 수정됨'"]
Retry["자동 재시도"]
EmptyError -.-> ExtractIndex
ExtractIndex -.-> FindMessage
FindMessage -.-> CheckParts
CheckParts -.->|"예"| Replace
CheckParts -.->|"아니오"| Inject
Replace -.-> Toast
Inject -.-> Toast
Toast -.-> Retry
출처: src/hooks/anthropic-auto-compact/executor.ts L173-L247
src/hooks/anthropic-auto-compact/parser.ts L45-L51
전략 4: 메시지 되돌리기 (Message Reversion)
트런케이션 전략이 실패하면, 시스템은 컨텍스트 크기를 줄이기 위해 마지막 어시스턴트 메시지를 되돌립니다(삭제합니다).
되돌리기 로직
// 마지막 사용자 + 어시스턴트 메시지 쌍 가져오기
const { userMessageID, assistantMessageID } = await getLastMessagePair(sessionID, client, directory)
// 어시스턴트 응답 되돌리기
await client.session.revert({
path: { id: sessionID },
body: { messageID: userMessageID }, // 이 메시지 시점으로 되돌림
query: { directory }
})
되돌리기 제한
| 설정 | 값 | 목적 |
|---|---|---|
maxRevertAttempts |
3 | 과도한 메시지 삭제 방지 |
minMessagesRequired |
2 | 대화에 최소한의 콘텐츠 유지 보장 |
상태 추적
interface FallbackState {
revertAttempt: number
lastRevertedMessageID?: string // 되돌린 메시지 추적
}
전체 대화가 삭제되는 것을 방지하기 위해 세션당 3회 시도 후 되돌리기를 중단합니다.
출처: src/hooks/anthropic-auto-compact/executor.ts L75-L120
src/hooks/anthropic-auto-compact/types.ts L16-L19
src/hooks/anthropic-auto-compact/types.ts L43-L46
전략 5: 요약 (Summarization)
요약은 최종 폴백 전략입니다. OpenCode의 내장 요약 API를 사용하여 전체 대화 히스토리를 짧은 형태로 압축합니다.
요약 요청
await client.session.summarize({
path: { id: sessionID },
body: {
providerID: "anthropic", // 마지막 어시스턴트 메시지에서 가져옴
modelID: "claude-opus-4-5"
},
query: { directory }
})
동작
- 모델 선택: 대화와 동일한 모델을 사용합니다 (컨텍스트 품질 유지).
- 자동 재시도: 요약 후 훅이 자동으로 다시 프롬프트를 실행합니다.
- 상태 보존: 요약된 상태에서 세션이 원활하게 계속됩니다.
- 최후의 수단: 다른 모든 전략이 소진된 후에만 실행됩니다.
출처: src/hooks/anthropic-auto-compact/executor.ts L369-L395
재시도 및 백오프 메커니즘 (Retry and Backoff)
성공적인 복구 후, 시스템은 오류를 일으켰던 원래 프롬프트를 자동으로 재시도합니다.
재시도 상태
interface RetryState {
attempt: number // 현재 재시도 횟수
lastAttemptTime: number // 마지막 재시도 타임스탬프
}
재시도 설정
| 상수 | 값 | 목적 |
|---|---|---|
maxAttempts |
2 | 실패 전 최대 재시도 횟수 |
initialDelayMs |
2000 | 첫 재시도 전 초기 지연 시간 |
backoffFactor |
2 | 지수 백오프 배수 |
maxDelayMs |
30000 | 최대 지연 시간 상한 |
백오프 계산
const delay = Math.min(
initialDelayMs * Math.pow(backoffFactor, attempt),
maxDelayMs
)
// 시도 0: 2000ms
// 시도 1: 4000ms
// 시도 2: 8000ms (maxDelayMs에서 캡핑됨)
자동 재시도 실행
flowchart TD
RecoverySuccess["복구 전략 성공"]
CheckAttempts["retryState.attempt <<br>maxAttempts?"]
CalcDelay["지수 백오프 계산<br>delay = initial * backoff^attempt"]
Wait["setTimeout(delay)"]
GetLastPrompt["세션 히스토리에서<br>마지막 사용자 프롬프트 추출"]
Reprompt["client.session.prompt_async<br>동일한 프롬프트로 재실행"]
IncrementAttempt["retryState.attempt++<br>retryState.lastAttemptTime = 현재"]
ToastSuccess["토스트: '세션이 성공적으로 복구되었습니다'"]
ToastFailure["토스트: '최대 재시도 횟수 도달'"]
ClearState["clearSessionState"]
RecoverySuccess -.-> CheckAttempts
CheckAttempts -.->|"예"| CalcDelay
CheckAttempts -.->|"아니오"| ToastFailure
CalcDelay -.-> Wait
Wait -.-> GetLastPrompt
GetLastPrompt -.-> Reprompt
Reprompt -.-> IncrementAttempt
IncrementAttempt -.-> ToastSuccess
ToastSuccess -.-> ClearState
ToastFailure -.-> ClearState
출처: src/hooks/anthropic-auto-compact/executor.ts L249-L397
src/hooks/anthropic-auto-compact/types.ts L11-L14
src/hooks/anthropic-auto-compact/types.ts L36-L41
세션 상태 관리 (Session State Management)
자동 압축 상태 구조
Anthropic Auto-Compact 훅은 복구 전략을 조정하고 무한 루프를 방지하기 위해 세션별로 포괄적인 상태를 유지합니다:
interface AutoCompactState {
pendingCompact: Set<string> // 복구 대기 중인 세션
errorDataBySession: Map<string, ParsedTokenLimitError> // 파싱된 오류 상세 정보
retryStateBySession: Map<string, RetryState> // 재시도 횟수 추적
fallbackStateBySession: Map<string, FallbackState> // 되돌리기 추적
truncateStateBySession: Map<string, TruncateState> // 트런케이션 추적
emptyContentAttemptBySession: Map<string, number> // 빈 메시지 수정 추적
compactionInProgress: Set<string> // 동시 복구 방지
}
상태 조정 다이어그램
flowchart TD
DeleteEvent["session.deleted 이벤트"]
CleanupMaps["모든 Map에서 sessionID 제거<br>모든 Set에서 제거"]
ErrorEvent["session.error 또는<br>오류가 포함된 message.updated"]
ParseError["parseAnthropicTokenLimitError"]
StoreError["errorDataBySession.set"]
SetPending["pendingCompact.add"]
SetInProgress["compactionInProgress.add"]
IdleEvent["session.idle 이벤트"]
CheckPending["pendingCompact.has?"]
CheckInProgress["compactionInProgress.has?"]
ExecuteRecovery["executeCompact"]
CheckRetry["retryStateBySession 확인"]
CheckFallback["fallbackStateBySession 확인"]
CheckTruncate["truncateStateBySession 확인"]
CheckEmpty["emptyContentAttemptBySession 확인"]
UpdateStates["시도 카운터 증가<br>마지막 시도 시간 추적"]
RecoveryComplete["복구 성공 또는 실패"]
ClearSession["clearSessionState<br>모든 Map/Set 항목 제거"]
UpdateStates -.-> RecoveryComplete
subgraph subGraph3 ["정리 단계"]
RecoveryComplete
ClearSession
RecoveryComplete -.-> ClearSession
end
subgraph subGraph2 ["복구 실행 단계"]
IdleEvent
CheckPending
CheckInProgress
ExecuteRecovery
UpdateStates
IdleEvent -.->|"아니오"| CheckPending
CheckPending -.->|"예"| CheckInProgress
CheckPending -.->|"예"| IdleEvent
CheckInProgress -.->|"아니오"| ExecuteRecovery
CheckInProgress -.-> IdleEvent
ExecuteRecovery -.-> CheckRetry
CheckEmpty -.-> UpdateStates
subgraph subGraph1 ["전략별 상태"]
CheckRetry
CheckFallback
CheckTruncate
CheckEmpty
CheckRetry -.-> CheckFallback
CheckFallback -.-> CheckTruncate
CheckTruncate -.-> CheckEmpty
end
end
subgraph subGraph4 ["라이프사이클 관리"]
DeleteEvent
CleanupMaps
DeleteEvent -.-> CleanupMaps
end
subgraph subGraph0 ["오류 감지 단계"]
ErrorEvent
ParseError
StoreError
SetPending
SetInProgress
ErrorEvent -.-> ParseError
ParseError -.-> StoreError
StoreError -.-> SetPending
SetPending -.-> SetInProgress
end
복구 시도별 상태 라이프사이클
| 상태 Map | 목적 | 삭제 시점 | 최대값 |
|---|---|---|---|
pendingCompact |
복구가 필요한 세션 플래그 | 복구 성공 후 | 해당 없음 (Set) |
errorDataBySession |
파싱된 오류 상세 정보 저장 | 복구 성공 후 | 해당 없음 |
retryStateBySession |
재시도 횟수 추적 | 최대 재시도 후 | maxAttempts: 2 |
fallbackStateBySession |
되돌리기 횟수 추적 | 최대 되돌리기 후 | maxRevertAttempts: 3 |
truncateStateBySession |
트런케이션 횟수 추적 | 최대 트런케이션 후 | maxTruncateAttempts: 20 |
emptyContentAttemptBySession |
빈 메시지 수정 추적 | 수정 성공 후 | 무제한 |
compactionInProgress |
동시 실행 방지 | 실행 완료 후 | 해당 없음 (Set) |
정리 및 메모리 관리
훅은 메모리 누수를 방지하기 위해 session.deleted 이벤트를 수신합니다:
if (event.type === "session.deleted") {
const sessionID = props?.info?.id
if (sessionID) {
autoCompactState.pendingCompact.delete(sessionID)
autoCompactState.errorDataBySession.delete(sessionID)
autoCompactState.retryStateBySession.delete(sessionID)
autoCompactState.fallbackStateBySession.delete(sessionID)
autoCompactState.truncateStateBySession.delete(sessionID)
autoCompactState.emptyContentAttemptBySession.delete(sessionID)
autoCompactState.compactionInProgress.delete(sessionID)
}
}
출처: src/hooks/anthropic-auto-compact/types.ts L26-L34
src/hooks/anthropic-auto-compact/index.ts L31-L42
src/hooks/anthropic-auto-compact/executor.ts L156-L164
훅 설정 (Hook Configuration)
훅 비활성화
개별 컨텍스트 관리 훅은 설정을 통해 비활성화할 수 있습니다:
{
"disabled_hooks": [
"context-window-monitor",
"anthropic-auto-compact"
]
}
설정 파일은 다음 위치에서 로드됩니다:
.opencode/oh-my-opencode.json(프로젝트 레벨, 최우선 순위)~/.config/opencode/oh-my-opencode.json(사용자 레벨)~/.config/opencode/oh-my-opencode.json(Windows:%APPDATA%\opencode\oh-my-opencode.json)
실험적 설정 (Experimental Configuration)
Anthropic Auto-Compact 훅은 experimental 설정 객체를 통해 실험적 기능을 지원합니다:
{
"experimental": {
"aggressive_truncation": true,
"auto_resume": true
}
}
| 옵션 | 기본값 | 설명 |
|---|---|---|
aggressive_truncation |
false |
목표 토큰 50% 도달을 위한 다중 출력 트런케이션 활성화 |
auto_resume |
false |
생각(thinking) 오류로부터 성공적으로 복구한 후 자동 재시도 |
플랫폼별 설정 경로
플러그인은 플랫폼에 따라 사용자 설정 디렉토리를 결정합니다:
// getUserConfigDir() 구현
if (process.platform === "win32") {
// 크로스 플랫폼 ~/.config 우선순위
const crossPlatform = "~/.config/opencode/oh-my-opencode.json"
if (exists(crossPlatform)) return crossPlatform
// Windows 네이티브 %APPDATA%로 폴백
return "%APPDATA%/opencode/oh-my-opencode.json"
}
// Linux/macOS: XDG_CONFIG_HOME 또는 ~/.config
return "$XDG_CONFIG_HOME/opencode/oh-my-opencode.json"
출처: src/shared/config-path.ts L1-L48
src/hooks/anthropic-auto-compact/index.ts L8-L10
도구 출력 트런케이션과의 통합
컨텍스트 관리 훅은 포괄적인 컨텍스트 윈도우 관리를 제공하기 위해 도구 출력 트런케이터(tool output truncator)와 협력합니다:
조정 전략
| 컴포넌트 | 역할 | 타이밍 |
|---|---|---|
| 컨텍스트 윈도우 모니터 | 사용량 70% 이상 시 선제적 경고 | 대화 중 (message.updated) |
| 선제적 압축 | 사용량 80% 시 제안 | 대화 중 (message.updated) |
| 도구 출력 트런케이터 | 도구별 동적 트런케이션 | 도구 실행 후 (tool.execute.after) |
| Anthropic 자동 압축 | 긴급 복구 | API 오류 발생 후 (session.error, session.idle) |
계층적 방어 아키텍처
flowchart TD
Monitor["컨텍스트 윈도우 모니터<br>임계값: 70%"]
Preemptive["선제적 압축<br>임계값: 80%"]
ToolTrunc["도구 출력 트런케이터<br>도구당: 50k 토큰 캡"]
ErrorDetect["오류 감지<br>토큰 제한 오류 파싱"]
AggressiveTrunc["공격적 트런케이션<br>다중 출력 제거"]
SingleTrunc["단일 트런케이션<br>가장 큰 출력"]
EmptyFix["빈 메시지 수정"]
Revert["메시지 되돌리기"]
Summarize["전체 요약"]
NormalOp["정상 작동"]
Warning["경고 표시됨"]
ErrorThrown["API 오류 발생"]
Recovered["세션 복구됨"]
NormalOp -.->|"80%"| Monitor
Monitor -.->|"70%"| Warning
Warning -.-> Preemptive
ToolTrunc -.->|"성공"| ErrorThrown
ToolTrunc -.-> NormalOp
ErrorThrown -.-> ErrorDetect
ErrorDetect -.-> AggressiveTrunc
Summarize -.-> Recovered
subgraph subGraph2 ["복구 계층 (자동)"]
AggressiveTrunc
SingleTrunc
EmptyFix
Revert
Summarize
AggressiveTrunc -.-> SingleTrunc
SingleTrunc -.-> EmptyFix
EmptyFix -.-> Revert
Revert -.-> Summarize
end
subgraph subGraph1 ["감지 계층 (반응적)"]
ErrorDetect
end
subgraph subGraph0 ["방지 계층 (선제적)"]
Monitor
Preemptive
ToolTrunc
Preemptive -.->|"여전히 초과"| ToolTrunc
end
이 다층적 접근 방식은 다음을 보장합니다:
- 방지 (Prevention): 제한에 도달하기 전에 에이전트에게 경고합니다.
- 완화 (Mitigation): 도구 출력을 선제적으로 트런케이션합니다.
- 복구 (Recovery): 사용자 개입 없이 오류를 자동으로 처리합니다.
도구별 트런케이션 상세 정보는 Tool Enhancement Hooks를 참조하십시오.
src/hooks/anthropic-auto-compact/executor.ts L264-L397
저장소 시스템 아키텍처 (Storage System Architecture)
OpenCode 저장소 디렉토리 구조
Anthropic Auto-Compact는 OpenCode의 영구 저장소 계층에서 작동합니다:
~/.local/share/opencode/storage/ (또는 macOS에서 xdg-basedir를 통한 ~/.local/share)
├── message/
│ ├── {sessionID}/
│ │ └── {messageID}.json ← 메시지 메타데이터
│ └── {workspaceID}/
│ └── {sessionID}/
│ └── {messageID}.json
└── part/
└── {messageID}/
├── {partID}.json ← 도구 결과 콘텐츠
├── {partID}.json
└── {partID}.json
파트(Part) 파일 스키마
각 도구 결과는 part/ 디렉토리에 JSON 파일로 저장됩니다:
interface StoredToolPart {
id: string // 파트 UUID
sessionID: string // 부모 세션
messageID: string // 부모 메시지
type: "tool" // 파트 유형
callID: string // 도구 호출 ID
tool: string // 도구 이름 (예: "grep", "read")
state: {
status: "pending" | "running" | "completed" | "error"
input: Record<string, unknown> // 도구 인자
output?: string // 도구 결과 (대용량 가능)
error?: string // 실패 시 오류 메시지
time?: {
start: number // 타임스탬프: 도구 시작
end?: number // 타임스탬프: 도구 종료
compacted?: number // 타임스탬프: 트런케이션 시점
}
}
truncated?: boolean // 트런케이션 후 true로 설정
originalSize?: number // 메트릭을 위해 저장됨
}
저장소 조작 작업
| 작업 | 함수 | 목적 | 파일 시스템 영향 |
|---|---|---|---|
| 가장 큰 결과 찾기 | findLargestToolResult |
가장 큰 출력 식별 | 읽기 전용 스캔 |
| 크기순 찾기 | findToolResultsBySize |
정렬된 리스트 가져오기 | 읽기 전용 스캔 |
| 트런케이션 | truncateToolResult |
출력을 메시지로 교체 | 쓰기 (파트 파일 수정) |
| 트런케이션 수 카운트 | countTruncatedResults |
복구 통계 추적 | 읽기 전용 스캔 |
| 총 크기 가져오기 | getTotalToolOutputSize |
총 출력 바이트 계산 | 읽기 전용 스캔 |
크로스 플랫폼 경로 확인
저장소 모듈은 플랫폼별 XDG 디렉토리 확인을 처리합니다:
// 기본값: xdg-basedir 사용
let OPENCODE_STORAGE = join(xdgData ?? "", "opencode", "storage")
// macOS 수정: xdg-basedir → ~/Library/Application Support
// 하지만 OpenCode CLI는 ~/.local/share를 사용함
if (process.platform === "darwin" && !existsSync(OPENCODE_STORAGE)) {
const localShare = join(homedir(), ".local", "share", "opencode", "storage")
if (existsSync(localShare)) {
OPENCODE_STORAGE = localShare
}
}
이를 통해 macOS에서 OpenCode의 실제 저장소 위치와의 호환성을 보장합니다.
출처: src/hooks/anthropic-auto-compact/storage.ts L1-L258
src/shared/config-path.ts L1-L48
기술적 구현 세부 사항 (Technical Implementation Details)
훅 생성 패턴
Anthropic Auto-Compact 훅은 표준 팩토리 패턴을 따릅니다:
export function createAnthropicAutoCompactHook(
ctx: PluginInput,
options?: AnthropicAutoCompactOptions
) {
const autoCompactState = createAutoCompactState()
const experimental = options?.experimental
const eventHandler = async ({ event }) => {
// session.error, message.updated, session.idle 처리
}
return {
event: eventHandler
}
}
주요 특징:
- 상태 캡슐화:
autoCompactState는 클로저(closure)에 의해 캡처됩니다. - 옵션 지원: 생성 시점에
experimental설정이 전달됩니다. - 이벤트 기반: 여러 이벤트 유형에 대해 단일
event핸들러를 반환합니다.
이벤트 핸들러 시그니처
훅은 여러 이벤트 유형에 대해 통합된 이벤트 핸들러를 구현합니다:
interface EventHandler {
event: (context: {
event: {
type: "session.error" | "message.updated" | "session.idle" | "session.deleted",
properties?: unknown
}
}) => Promise<void>
}
이벤트 유형별 디스패치:
session.error: 오류를 파싱하고 보류(pending) 플래그를 설정합니다.message.updated: 어시스턴트 메시지에서 인라인 오류를 추출합니다.session.idle: 보류 플래그가 설정된 경우 복구를 실행합니다.session.deleted: 세션 상태를 정리합니다.
비동기 복구 실행
복구 전략은 OpenCode의 클라이언트 API와 함께 async/await를 사용합니다:
// 도구 트런케이션: 동기식 파일 I/O
const result = truncateToolResult(partPath) // fs.readFileSync + fs.writeFileSync
// 메시지 되돌리기: 비동기 API 호출
await client.session.revert({
path: { id: sessionID },
body: { messageID },
query: { directory }
})
// 요약: 비동기 API 호출
await client.session.summarize({
path: { id: sessionID },
body: { providerID, modelID },
query: { directory }
})
// 프롬프트 재시도: 비동기 API 호출
await client.session.prompt_async({
path: { sessionID },
body: { parts: [{ type: "text", text: lastPrompt }] },
query: { directory }
})
오류 처리: 모든 API 호출은 중요하지 않은 작업(예: 토스트 알림)에 대해 .catch(() => {})를 사용하여 훅의 실패가 세션을 중단시키지 않도록 합니다.
출처: src/hooks/anthropic-auto-compact/index.ts L24-L149
src/hooks/anthropic-auto-compact/executor.ts L249-L397
다른 훅 카테고리와의 비교
| 훅 카테고리 | 실행 단계 | 출력 변조 | 세션 상태 | 목적 |
|---|---|---|---|---|
| 컨텍스트 강화 | tool.execute.after |
예 (추가) | 디렉토리/규칙별 | 배경 지식 추가 |
| 도구 관리 | tool.execute.after |
예 (트런케이션) | 도구 호출별 | 출력 크기 제어 |
| 품질 보증 | event 리스너 |
간접적 | 세션별 | 완전성 강제 |
| 세션 복구 | event 리스너 |
간접적 | 오류별 | 오류 수정 |
컨텍스트 강화 훅은 컨텍스트 축적에 대한 계층적 접근 방식과 세션 범위의 중복 제거 전략 면에서 독특합니다.