내가 출시하던 Compaction 플러그인이 출시 도중 나에게 경고를 보냈다
요약
Claude Code의 컨텍스트 압축 과정에서 발생하는 정보 손실을 방지하기 위한 'compact-ops' 플러그인 개발 경험을 다룹니다. 컨텍스트 윈도우가 가득 찰 때 운영 상태를 보존하고 사용자에게 경고를 제공하는 방식과 설계 원칙을 설명합니다.
핵심 포인트
- 컨텍스트 압축 시 발생하는 운영 상태(operational state) 손실 문제 해결
- 임계값 도달 시 사용자에게 계획과 결정을 요약하여 알림 제공
- 훅(hook) 실패 시에도 핵심 기능은 유지되는 페일 오픈(fail-open) 설계 적용
- 기존 compact-plus 프로젝트를 기반으로 한 개선 및 파생형 개발
새벽 3시, 나는 컨텍스트 압축 (context compaction)으로부터 세션을 보호하는 Claude Code 플러그인인 compact-ops의 출시를 마무리하고 있었다. 컨텍스트 사용량이 67%에 도달하자, 내 세션에 알림이 나타났다: 임계값 초과, 깨끗한 중단 지점에서 /compact를 실행하십시오. 여기 현재 계획과 최신 결정 사항이 있습니다. 나는 그 경고문을 직접 작성했다. 그리고 동시에 그 경고 덕분에 구조된 사람이기도 했다.
20분 후, 동일한 세션에서 정반대의 상황이 발생했다. 내가 /compact를 실행하자, 플러그인의 세 가지 훅 (hook)이 모두 실패했다. 출시 과정에서 플러그인의 디렉토리 레이아웃을 재구성했기 때문에, 실행 중이던 세션은 여전히 오래된 경로를 유지하고 있었다. 압축 (Compaction) 자체는 정상적으로 완료되었는데, 이는 모든 훅이 페일 오픈 (fail-open) 방식으로 설계되었기 때문이다. 즉, 훅이 깨지더라도 내장된 동작을 차단하는 대신 옆으로 물러나게 된다. 하나의 세션, 하나의 밤, 그리고 나는 기능이 작동하는 모습과 안전하게 실패하는 모습을 동시에 지켜볼 수 있었다. 이런 식의 도그푸딩 (dogfooding)은 돈 주고도 못 산다.
이 포스트에서는 /compact가 실제로 무엇을 버리는지, 이 플러그인의 모태가 된 플러그인에서 무엇을 빌려왔는지, 그리고 리포지토리 (repo)를 공개하기 전에 내가 강화한 여섯 가지 사항에 대해 다룬다.
/compact가 실제로 버리는 것
Claude Code 세션이 컨텍스트 윈도우 (context window)를 가득 채우면, 전체 대화는 하나의 내장된 요약본으로 압축되고 원래 메시지들은 폐기된다. 수동 /compact와 자동 압축 (auto-compact) 모두 이 방식으로 작동한다.
요약본은 코드에 대해서는 꽤 괜찮다. 하지만 버려지는 것은 운영 상태 (operational state)이다: "푸시가 이미 승인되었습니다", "그 방식을 시도했지만 실패했습니다", "그 숫자는 이 파일에서 나왔습니다"와 같은 정보들 말이다. 압축 이후의 에이전트 (agent)가 이를 잊어버리면, 이미 부여한 권한을 다시 요청하거나 이미 해결했던 수정을 다시 시도하게 된다. 그것이 바로 내가 보험을 들고 싶었던 실패 모드 (failure mode)였다.
다음은 플러그인이 있을 때와 없을 때의 타임라인이다:
| 순간 | 표준 Claude Code | compact-ops 사용 시 |
|---|---|---|
| 사용량이 60%를 초과할 때 (설정 가능) | 경고 없음; 예고 없이 자동 압축 (auto-compact) 실행 | 원샷 리마인더(one-shot reminder)와 함께 계획 / 단계 / 최신 결정 사항을 3줄로 요약하여 제시 |
| ... |
압축 알고리즘 자체는 건드리지 않았습니다. 모든 것은 외부에서 공식 훅 (hooks)을 통해 실행됩니다.
compact-plus를 기반으로
핵심 아이디어는 제 것이 아닙니다. u-ichi의 compact-plus (MIT)가 먼저 이 방식에 도달했습니다. 요약(summary)이 모든 것을 삼키기 전에 별도의 LLM을 호출하여 구조화된 상태 (structured state)를 작성하는 PreCompact 훅 (hook)을 사용하는 방식입니다. README를 읽는 즉시 설치하고 싶어지는 종류의 도구입니다.
제 설정은 해당 도구의 가정과 세 가지 불일치 사항이 있었기에, 포크 (fork) 대신 파생형 (derivative)을 구축했으며 정확히 세 가지를 변경했습니다:
- 사용량 경고는 플러그인만으로는 작동하지 않습니다. compact-plus는 저자의 별도 dotfiles 리포지토리에 있는 상태 표시줄 (statusline) 스크립트가 작성한 마커를 읽기 때문에, 플러그인만 설치해서는 경고가 절대 발생하지 않습니다. compact-ops는
UserPromptSubmit훅 (hook) 내부에서 트랜스크립트 (transcript)의 최신message.usage로부터 직접 사용량을 계산합니다. - 재부팅 후에도 상태가 유지됩니다. compact-plus는 상태를
$TMPDIR에 저장하는데, 이는 재시작 후 사라집니다. 매일 아침 어제의 작업을 재개한다면, 그것은 전혀 보험 역할을 할 수 없습니다. 이제 상태는~/.claude/compact-ops/아래에 프로젝트별로 정리되어 30일 동안 유지됩니다. - 복구 기능이
--resume시에도 작동합니다. 단순히 압축 과정에서만 작동하는 것이 아닙니다.SessionStart훅 (hook)을 통해 다음 날 세션을 다시 열 때(재부팅 포함) 동일한 상태를 주입합니다.
또한 저는 LLM 백엔드를 Claude 전용(Sonnet 주력, Haiku 폴백)으로 만들었습니다. compact-plus는 Codex로 폴백(fallback)되는데, 이는 Claude 구독 외에 ChatGPT Pro 구독이 별도로 있다는 것을 가정합니다.
10개의 고정된 헤딩을 가진 상태 파일
각 Compaction (압축) 전에는 별도의 LLM이 항상 동일한 10개의 헤딩을 가진 마크다운 파일을 작성합니다: Active Plan (활성 계획), Current Phase (현재 단계), TaskList Summary (작업 목록 요약), Session Decisions (세션 결정 사항), Constraints and Blockers (제약 사항 및 차단 요소), Worker Topology (워커 토폴로지), Skills Invoked (호출된 기술), Editing Files (편집 중인 파일), Failed Attempts (실패한 시도), Recovery Notes (복구 노트). 흥찰스러운 부분은 Session Decisions와 Failed Attempts입니다. 이는 내장된 요약 기능이 생략하기 쉬운 바로 그 운영상의 사실들입니다.
경고 측면에서도 동일한 파일을 재사용합니다. 사용량이 임계값을 넘으면, 주입된 리마인더는 단순히 "곧 압축하세요"라고 말하는 것이 아니라, 현재 상태의 상단 부분을 읊어줍니다. 이를 통해 에이전트는 압축 직전의 혼란스러운 전환 과정 중에도 전체적인 그림을 유지할 수 있습니다:
[COMPACT WARNING] 컨텍스트 사용량이 67%에 도달했습니다 (134,102 / 200,000 토큰).
상태 낭독:
- Active Plan: compact-ops v0.2.0 출시 (강화 + 마켓플레이스 수정)
...
그 낭독이 바로 새벽 3시 제 세션에 나타났던 내용입니다. 직접 작성한 훅(hook)에 의해 작성된 자신의 계획을 다시 읽는 것은 기묘한 종류의 코드 리뷰와 같습니다.
따라서 압축 후의 에이전트는 두 개의 닻, 즉 표준 요약과 구조화된 상태 파일과 함께 재시작합니다. 하지만 상태 파일이 절대적인 권위를 가진 것으로 취급되지는 않습니다. 주입된 복구 가이드는 무엇인가를 신뢰하기 전에 반드시 원래의 프로젝트 파일들을 다시 읽으라고 명시적으로 말합니다. 다른 LLM에 의해 맹목적으로 신뢰되는 LLM 요약은 그저 더 빠른 '전화기 게임 (game of telephone)'일 뿐입니다.
또한 이것은 단순한 마크다운 파일입니다. 핸드오프(handoff)가 어색하게 느껴질 때, 파일을 열어 이전 세션이 무엇을 전달하고 있다고 생각했는지 정확히 확인할 수 있습니다.
공개 전 강화한 6가지 사항
v0.1.0은 "내 컴퓨터에서는 작동함" 수준이었습니다. 리포지토리를 공개하기 전에 자체 검토와 더불어 독립적인 CLI 리뷰어를 통한 두 번째 검토를 수행했으며, 6가지 수정 사항을 포함한 v0.2.0을 출시했습니다:
- 권한 (Permissions). 상태 파일 (State files) 및 트랜스크립트 백업 (transcript backups)에는 도구 출력이 에코(echo)한 비밀 정보(secrets)를 포함하여 사용자의 가공되지 않은 대화 내용이 포함되어 있습니다. 이제 모든 훅 (hook)은
umask 077을 설정하며, 디렉토리는 700, 파일은 600 권한을 가집니다. - session_id 검증 (session_id validation). 훅 입력 JSON이 파일 경로로 직접 흘러 들어갔었습니다. 이제는 파일 시스템에 접근하기 전에 허용 목록 (allowlist: 영문/숫자 및
._-만 허용,..은 금지)을 통과해야 합니다. - LLM 출력 검증 (LLM output validation). 상태 생성기 (state generator)는 이전에는 첫 줄 검사 이후에는 신뢰되었습니다. 이제는 10개의 헤딩 (headings)이 모두 존재해야 하며, 그렇지 않으면 이전 상태를 유지하고 쓰기 작업을 건너뜁니다.
- 단일 패스 jq (Single-pass jq). 트랜스크립트 압축기 (transcript squasher)가 라인당
jq를 3~4번씩 실행했는데, 이는 긴 세션의 경우 훅의 전체 타임아웃 예산을 모두 소모할 수 있었습니다. 이제는 하나의 스트리밍 프로세스로 처리됩니다. - Gzip 백업 (Gzip backups). 트랜스크립트 JSONL은 크기가 대략 10분의 1로 압축됩니다.
- 디버그 로깅 (Debug logging). 진짜 교훈입니다. 페일 오픈 (fail-open) 설계는 운영 환경에서 문제를 일으키지 않지만, 동시에 아무런 소리 없이 죽어버리기도 합니다. 이 포스트 상단에 언급된 세 번의 훅 실패는 하네스 (harness)가 에러 라인을 출력했기 때문에 겨우 확인할 수 있었을 뿐, 플러그인 자체에는 왜 그런 일이 발생했는지 저에게 알려줄 방법이 없었습니다. 이제
COMPACT_OPS_DEBUG=1을 설정하면 삼켜진(swallowed) 모든 실패를 로깅합니다. 만약 페일 오픈 훅을 작성한다면, 로깅부터 작성하십시오. 저는 순서를 잘못 잡았고 운이 좋았을 뿐입니다.
의도적으로 구축하지 않은 것들
Claude Code 생태계에는 이미 컨텍스트 손실에 대한 더 무거운 해결책들이 있습니다: MCP 도구를 활용한 완전한 메모리 레이어 (memory layers), 과거 세션에 대한 BM25 검색 (retrieval), 수동으로 관리하는 3개 파일 개발 문서 (dev-docs) 시스템 등입니다. 좋은 도구들이지만, 트레이드오프 (trade-offs)가 다릅니다.
compact-ops는 의도적으로 좁은 범위를 유지합니다: MCP 서버 없음, 데이터베이스 없음, 새로 배워야 할 워크플로우 없음, 데몬 (daemon) 없음. 공식 훅 뒤에서 작동하는 쉘 스크립트이며, 상태 (state)는 cat으로 읽을 수 있는 일반 마크다운 (markdown)입니다. 만약 오작동하더라도 플러그인을 삭제하면 바로 순정 Claude Code 상태로 돌아갑니다. 이관(migrate)해야 할 데이터가 아무것도 없습니다. 저는 보험이 제 역할을 하지 않을 때 그 비용이 전혀 들지 않기를 원했습니다.
또 다른 비기능적 측면은, 이 플러그인이 내장된 요약(summary)을 절대 대체하지 않는다는 점입니다. 컴팩션(compaction) 자체를 가로채거나 재작성하는 도구들은 Claude Code가 내부 구조를 변경할 때마다 작동이 중단됩니다. 훅(Hooks)이 지원되는 인터페이스이므로, 그것이 전체적인 영향 범위(footprint)입니다.
시도해보기
git clone https://github.com/kenimo49/compact-ops.git
claude plugin marketplace add /path/to/compact-ops --scope user
claude plugin install compact-ops@compact-ops-local
요구 사항: Claude Code v2.x, jq, 그리고 상태 기록 백엔드(state-writing backend)로서의 claude CLI가 필요합니다. Linux 및 macOS를 지원합니다. 설치 후에는 평소처럼 /compact를 실행하면 됩니다. 각 컴팩션은 한 번의 추가적인 LLM 호출(기본값은 Sonnet이며, Haiku로 낮추거나 끌 수 있음) 비용이 발생합니다.
이 플러그인의 첫 번째 수혜자는 이 플러그인을 만든 바로 그 세션이었습니다. 같은 밤에, 자체적인 67% 경고 덕분에 데이터가 저장되었고, 이어서 자체적인 훅 실패(hook failure)를 통해 스트레스 테스트를 거쳤습니다. 에이전트 도구(Agent tooling)란 원래 이렇습니다. 설계의 이유는 무언가가 실제로 고장 난 곳에서만 기록됩니다.
/compact 이후에 당신의 에이전트는 무엇을 잊어버리나요? 컴팩션 이후의 세션이 당신의 오후 작업을 자신 있게 망쳐버린 경험이 있다면, 들려주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기