Compaction 이중 경로와 replaceGeneration
컨텍스트가 거의 차면 먼저 정리하고, 진짜로 터지면 정리한 뒤 재시도합니다. 재시도 전에 세대 번호를 대조하고, 정리가 먹히지 않으면 재시도하지 않습니다.
replaceGeneration 세대 번호인지.
컨텍스트 윈도우를 여행 가방으로 생각하세요. 메시지마다 옷 한 벌, 점선은 80% 경고선. 왼쪽 아래 정리 횟수가 replaceGeneration(세대 번호)이고 줄지 않습니다. 시나리오 셋이 압축의 세 운명을 보여 주고, 매 단계에 자막이 있습니다.
packages/compaction/compaction-basic/src/index.ts 147–223행.어떤 문제를 푸는가
능동 임계값 경로만 있다고 가정해 보세요. 요청마다 재서 80%를 넘으면 정리. 충분해 보이지만 부족합니다. 토큰 수는 추정치라 provider 실측과 항상 어긋납니다. 거대한 도구 결과가 갑자기 들어오면 측정은 임계 전인데 요청은 이미 한도를 넘겨 provider가 거부합니다. 복구 로직이 없으면 turn이 오류로 끝나고, 사용자는 알 수 없는 실패만 봅니다.
한 번 잘못 세면 바로 벽에 부딪히고 두 번째 방어선이 없습니다. 이게 단일 트리거의 문제입니다.
아이디어는 무엇인가
DSH는 이를 독립 트리거 둘로 나눕니다. 거의 차면 능동 정리, 벽에 부딪히면 수동 구출. 서로 다른 이벤트·조건·실패 의미를 씁니다.
pressure 경로는 각 Step(모델 요청 한 번) 시작 전에 걸립니다. 총 토큰을 재서 용량의 0.8을 넘으면 정리하고, 최근 대화 원문의 16% 꼬리를 남깁니다. 실패 의미는 느슨해요. 정리 중 오류면 로그만 남기고 계속 가고, 미리 정리에 실패해도 세상이 무너지진 않습니다.
context-overflow 경로는 요청 오류 뒤에 걸리고, 어댑터가 정규화한 CONTEXT_WINDOW_EXCEEDED만 인정합니다. 다른 오류는 통과. 임계값은 보지 않고 예비 예산을 0으로 만든 뒤 진짜 축소를 강제합니다. 실패 의미는 엄격해요. 재시도할지, 원본 오류를 보고할지 반드시 결정합니다. 재시도 상한 기본 1회, 성공한 모델 응답마다 카운터를 리셋해 정상 세션이 막히지 않습니다.
출처:pressure 리스너는 packages/compaction/compaction-basic/src/index.ts 147–165행, overflow 리스너는 179–223행; 기본값 0.8·0.16은 같은 패키지 config.ts 20·23행, 재시도 상한 기본 1은 93행 — provider+model 조합별로 덮어쓸 수 있습니다.
왜 오래가는가
예방과 최후 수단을 나누는 것은 신뢰성 공학의 통칙입니다. 백업과 복구는 두 시스템, 레이트 리밋과 서킷 브레이커는 두 게이트 — 같은 이치예요. 예방은 싸고 자주 돌며 실패해도 괜찮고, 최후 수단은 드물지만 믿을 만해야 하며 실패에 설명이 필요합니다. 한 로직에 넣으면 서로 타협합니다. 언어를 바꿔 harness를 다시 써도, 모델에 컨텍스트 한도가 있고 토큰이 추정이면 이 트리거 둘은 그대로 필요합니다.
어떤 문제를 푸는가
벽에 부딪힌 뒤 한 번 정리하고 요청을 다시 보냅니다. 문제는: 정리가 진짜 먹혔는지 어떻게 아나? Compaction은 열린 seam이라 서드파티가 커스텀 백엔드를 붙일 수 있습니다. 매번 성공을 보고하면서 모델이 보는 내용은 안 바꾸는 백엔드를 상상해 보세요. 반환값만 보고 재시도하면 요청은 그대로 한도 초과 → 오류 → 압축 → 재시도, 한 바퀴마다 API 비용만 태우고 새벽까지 돕니다.
아이디어는 무엇인가
DSH의 답은 세대 번호입니다. 배경: surface는 세션 로그에서 모델이 보는 이벤트의 실시간 프로젝션, 즉 모델 눈의 대화입니다. replaceGeneration은 그 대화가 몇 번 교체됐는지 세는 읽기 전용 카운터입니다. 코드베이스 전체에서 올리는 곳은 한곳뿐 — 옛 메시지가 요약으로 진짜 교체되고 디스크에 쓰인 순간. 줄이거나 리셋하는 API는 없습니다. 세대가 앞으로 가면 수학적으로 최소 한 번의 실제·영속 교체가 일어난 것과 같습니다.
오버플로 복구는 세 단계입니다. 정리 전에 현재값을 스냅샷, 정리, 새 값과 스냅샷을 비교. 엄격히 커져야 재시도, 아니면 통과시켜 원본 오류를 그대로 보고. 플러그인이 뭐라고 하냐보다 장부 숫자가 변했느냐가 중요합니다.
반대 방향 디테일도 있어요. 정리 중 예외가 나도, 앞선 무료 가지치기가 이미 디스크에 쓰이고 세대가 전진했다면 그 진전만으로도 재시도를 허용합니다. 증거로 통과, 증거로 거부 — 같은 기준입니다.
출처:스냅샷·비교는 packages/compaction/compaction-basic/src/index.ts 191행과 218–222행, 예외 후 이미 영속된 진전으로 재시도는 195–208행; replaceGeneration 정의는 packages/core/session/src/surface.ts 136–142행, 전체에서 유일한 증가 지점은 361–371행. 프로젝트 Agent Note(.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.zh.md)는 반환값만 보는 방식을 명시적으로 기각했습니다 — 커스텀 백엔드가 성공을 보고하면서 모델 가시 상태를 안 바꿀 수 있기 때문입니다.
왜 오래가는가
단조 증가 버전으로 상태가 변했음을 증명하는 패턴은 DB 낙관적 잠금이 수십 년 써 온 것이고, Git 커밋 체인·분산 시스템의 epoch도 변형입니다. 이득은 신뢰 문제를 산수로 바꾸는 것 — 실행자는 거짓말할 수 있어도 장부는 못 합니다. 신뢰할 수 없는 확장점이 있는 한, 재시도 전에 고칠 수 없는 카운터를 확인하는 것이 가장 싼 방어입니다.
능동 임계값 경로는 DSH pressure와 동형입니다. 기본 85% 트리거, 기본 꺼진 two-pass 사전 요약 — 자세한 내용은 사이트 Compaction: 85% 임계값과 선택적 two-pass. 요청 오류 후 수동 복구+세대 대조는 확인한 Grok Build 자료에 등가 메커니즘이 없습니다. 공개 증거 기준이며, 미지 항목은 남겨 둡니다.
능동 쪽이 가장 두껍습니다. API 호출마다 트리밍·마이크로 압축·접기·전체 요약 네 공정을 거칩니다. 비용 방어는 카운트 서킷 브레이커: 자동 압축이 연속 3회 실패하면 멈춤. 이 3은 실제 사고에서 왔고, 소스 주석에 1279개 세션이 50회 이상 연속 실패해 전 세계 하루 약 25만 API 호출을 낭비했다는 기록이 있습니다. 출처: claude-code-sourcemap-main/study/chapters/03-context-management.md 78–81, 121–124행.
비교 초점은 하나: 압축 실패가 루프로 돈을 태우느냐. Claude Code는 실패를 세어 3에서 서킷 브레이커 — 사고 데이터로 맞춘 카운터 손절입니다. DSH는 횟수를 안 세고, 재시도마다 세대 전진 증거를 요구해 무효 재시도를 한 번도 안 냅니다 — 구조적 증명입니다. 전자의 3은 사고가 먹여 주고, 후자의 0은 유도됩니다. 이중 경로 직교까지 더하면, 세 곳 비교에서 DSH만의 설계입니다.
거짓 성공 백엔드를 손으로 밀어 보기
재시도 상한을 1로 두고, 매번 정리 성공을 보고하면서 모델 가시 내용은 절대 바꾸지 않는 커스텀 압축 백엔드를 설치합니다. 이제 첫 오버플로 오류가 납니다.
문제 1: DSH가 두 번째 압축을 시도할까요? 힌트: 대조 실패 후 원본 오류가 바로 보고되고 turn이 끝나, 재시도 카운터가 늘 기회가 없습니다.
문제 2: 판정 기준을 백엔드 반환값만으로 바꾸면, 같은 장면에서 한 바퀴 5초일 때 첫 1분에 실패가 정해진 요청이 몇 번? Claude Code의 3회 서킷 브레이커는 몇 번째 요청 뒤에 손절할까요?
replaceGeneration의 단조 전진이고, 플러그인 반환값은 안 칩니다. 증거를 요구하세요. 구두 보고를 믿지 마세요.