9월 21일 Hacker News에 “Lossless-memory: 절대 요약하지 않는 개인 AI 메모리”라는 Show HN이 올라왔다. 67점을 받은 작은 글타래였지만, 비슷한 이름의 프로젝트가 올해 여럿 나왔다. OpenClaw용 플러그인 lossless-claw는 스타가 약 4,900개이고, 코딩 에이전트용이라는 open-mem/omp도 “무손실”을 내세운다.
배경에는 컨텍스트 압축에 대한 불만이 있다. 대화가 길어지면 Claude Code나 OpenClaw는 오래된 대화를 요약으로 바꾼다. 그 뒤 모델이 자기가 한 편집을 기억하지 못하거나 이미 한 일을 다시 한다는 이슈가 꾸준히 올라온다. 사람들이 원하는 건 “요약하지 말고 다 기억하라”다.
이 글은 lossless-memory와 lossless-claw의 코드와 문서를 읽고, 원문을 다 남기는 메모리가 실제로 무엇을 해결하고 무엇을 새로 정해야 하는지 정리한다. lossless-memory에는 합성 데이터로 실험을 하나 돌렸다. 도구 호출 결과를 윈도우에서 빼는 방법은 #71에서 다뤘다. 이번에는 윈도우 밖에 무엇을 남기고 어떻게 다시 꺼내느냐를 본다.
압축 뒤에 사라지는 것
Claude Code 저장소에는 압축 관련 이슈가 여럿 있다. 5월에 올라온 #63601은 3일 동안 도구를 441번 호출한 세션에서, 압축 뒤 모델이 자기가 한 편집을 사용자가 한 일로 두 번 착각했다고 보고했다. 보고자는 이렇게 적었다. “세션 jsonl 파일에는 실제로 일어난 일이 남아 있다. 모델이 보는 컨텍스트 창에는 없다.” 7월의 #75759는 같은 세션에서 한 행동을 모델이 부인한다고 했고, 9월의 #91351은 압축 뒤 작업 상태와 최근 작업 내용이 사라진다고 했다. 자동 압축을 끄는 방법을 묻는 이슈도 여럿 있다.
Anthropic API의 서버 측 압축도 같은 구조다. 오래된 턴을 Claude가 쓴 요약으로 바꾸고, 임계값 방식에서는 요약 블록 이전 내용을 API가 버린다. 원문을 보관하는 것은 애플리케이션의 책임이다.
그런데 Claude Code는 원문을 이미 보관하고 있다. 공식 문서에 따르면 ~/.claude/projects/<프로젝트>/<세션>.jsonl에 모든 메시지, 도구 호출, 도구 결과가 저장되고, 큰 도구 출력은 tool-results/ 폴더에 따로 저장된다. 이 파일들은 cleanupPeriodDays 설정값(기본 30일)이 지나면 삭제된다.
그러니 Claude Code 사용자에게 “요약하지 않는 메모리”가 해결하는 문제는 두 가지로 좁혀진다. 30일 뒤에 원문이 지워지는 것, 그리고 압축 뒤에 모델이 그 원문을 다시 볼 방법이 없는 것이다. 첫째는 보관 기간 설정의 문제이고, 둘째는 무엇을 어떻게 꺼내 컨텍스트에 넣느냐의 문제다.
lossless-memory: 저장은 다 하고, 넣을 때는 자른다
lossless-memory는 개인 개발자가 “한 사람, 한 AI, 한 대의 머신”을 위한 동반자 AI용으로 만든 기억 계층이다. 9월 4일 만들어졌고 스타는 108개, Python 코드는 약 3,900줄이다. 저자는 Show HN 글타래의 답글이 곧바로 숨김 처리돼서 질문에 대한 답을 저장소의 FAQ 문서로 옮겼다. 개발은 개인 사정으로 당분간 멈춘다고 밝혔다.
저장. 대화를 날짜별 JSONL 파일에 한 줄씩 쓴다. 검색용으로 SQLite 전문 검색 인덱스(텍스트를 두 글자씩 쪼개 저장한다)와 로컬 임베딩 모델(multilingual-e5-small) 벡터 인덱스를 만든다. LLM으로 요약하는 코드는 어디에도 없다. README의 “아무것도 요약하지 않는다”는 사실이다.
검색. 질의에서 “어제”, “9월 23일” 같은 시간 표현을 먼저 찾는다. 시간 표현이 있으면 그 범위 안에서만 키워드를 찾는다. 시간 표현이 없으면 키워드 검색과 벡터 검색 결과를 합치고(RRF), 최근 기록에 가중치를 준다(반감기 30일). 결과는 관련도로 고르고 시간순으로 출력한다. 검색 방식이 바뀐 경우(키워드가 없어서 범위 전체를 돌려줬다거나 벡터 검색으로 넘어갔다거나)에는 출력 머리글에 그 사실을 적는다. 이 프로젝트에서 가장 잘 만든 부분이다.
컨텍스트에 넣을 때의 손실. 저장은 무손실이지만 모델에게 보여 주는 쪽은 그렇지 않다.
- 검색 결과는 한 줄당 300자에서 자른다(
--full옵션을 주지 않으면). - 매 턴 주입한다는 “지금 무슨 이야기 중인지” 색인은 사용자 발화만 대상으로, 한 줄에 앞 25자만 쓰고, 한 시간이 지난 부분은 30분마다 한 줄로 접고, 최대 40줄이다.
- Claude Code 대화를 가져올 때는 도구 결과와 thinking 블록을 버리고, 도구 호출은 “도구 이름: 첫 입력 200자”만 남긴다.
생성형 요약은 없지만 추출형 압축은 있다. 특히 세 번째 항목 때문에 코딩 에이전트 세션에서 가장 많은 부분을 차지하는 도구 결과는 이 “무손실 기록”에 들어가지 않는다. 대화 텍스트는 다 남기지만 에이전트 작업 기록은 다 남기지 않는다.
출력 상한이 없는 경우. 검색 코드를 보면 시간 범위 검색에서는 결과 개수를 제한하지 않는다. 범위 안에서 키워드가 2건 미만으로 걸리면 범위 전체를 돌려주는 대체 동작도 있다. 이걸 확인하려고 하루치 합성 로그 2,000줄(줄당 약 60단어)을 넣고 검색해 봤다.
| 질의 | 반환 줄 수 | 출력 크기 |
|---|---|---|
2026-09-23 (날짜만) |
2,000 | 654,187자 |
2026-09-23 zebra (없는 단어) |
2,000 | 654,260자 |
budget (날짜 없음) |
8 | 2,976자 |
README는 날짜를 넣은 질의를 가장 강한 질의라고 소개한다. 그런데 기록이 많은 날에는 이 질의 하나가 수십만 자를 돌려준다. 없는 단어를 붙여도 하루 전체가 나온다. 합성 데이터는 키워드가 비현실적으로 빽빽하다. 하지만 키워드가 없을 때 범위 전체를 돌려주는 동작은 데이터와 상관없다. 저자가 공개한 실제 운영 규모(약 90일간 벡터 인덱스 12만 4천 줄)로 나누면 하루 평균 약 1,400줄이다. 저자의 비공개 훅이 출력을 따로 자를 수도 있지만, 저장소 코드만 쓴다면 호출하는 쪽에서 상한을 걸어야 한다.
인덱스 관리. 저자의 운영 기록에는 벡터 인덱스가 44만 줄에서 86만 줄까지 불어난 사건이 있다. 증분 인덱서가 파일 재작성을 감지하지 못해 라이브러리 사전과 라이선스 파일 약 75만 줄이 섞여 들어갔고, 벡터 검색 결과로 외국어 사전이 나오면서 알게 됐다고 한다. 원문 로그는 작고 단순하다. 문제는 그 위에 쌓는 인덱스에서 생겼다.
lossless-claw: 요약은 하되 원문으로 돌아갈 길을 남긴다
lossless-claw는 OpenClaw용 컨텍스트 관리 플러그인으로, 2월에 공개된 LCM(Lossless Context Management) 논문을 구현했다. TypeScript 약 5만 6천 줄에 테스트 파일이 129개인 큰 프로젝트이고 지금도 활발하게 개발 중이다.
이름에 “lossless”가 붙었지만 이 플러그인은 요약을 한다. 모든 메시지를 SQLite에 보존하고, 오래된 대화 조각을 LLM으로 요약해 요약 노드를 만들고, 요약이 쌓이면 다시 상위 요약으로 묶는다. 매 턴 모델이 보는 컨텍스트는 이 요약들과 최근 원문 64개다. 공식 문서도 “요약은 설계상 손실이 있다”고 적는다.
대신 잃은 것을 되찾는 장치가 있다. 요약 프롬프트는 반드시 “Expand for details about:“로 끝나고 뒤에 빠진 내용을 나열하게 돼 있다. 모델은 요약을 읽으면서 무엇이 생략됐는지 알 수 있고, lcm_grep(원문 검색), lcm_describe, lcm_expand_query(하위 에이전트가 요약을 펼쳐 답한다) 도구로 원문을 다시 찾을 수 있다.
캐시와의 충돌도 문서에 드러나 있다. 예산이 빠듯할 때 현재 질문과 관련 있는 항목을 골라 남기는 promptAwareEviction 옵션은 기본값이 꺼져 있다. 문서는 이 옵션이 “보존되는 앞부분이 바뀌어 프롬프트 캐시 적중률을 낮출 수 있다”고 경고한다. 캐시가 살아 있는 동안 압축을 미루는 옵션은 폐기됐다. 가장 최근 커밋에도 캐시 안정성 수정이 있다. 무엇을 컨텍스트에 남길지 똑똑하게 고를수록 매 턴 앞부분이 바뀌어 캐시가 깨진다. 이 긴장은 아직 풀리지 않았다.
비밀값 처리도 참고할 만하다. 현재 질문에 나온 대문자 식별자를 과거 기록에서 찾아 힌트로 붙이는 기능이 있는데, API 키나 토큰 형식의 문자열은 제외한다. 원문을 다 저장하면 비밀값도 다 저장되고, 검색으로 다시 꺼내질 수 있다는 점을 의식한 설계다. lossless-memory에는 이런 필터가 없다.
LCM 논문은 긴 컨텍스트 과제(OOLONG)에서 Claude Code보다 평균 점수가 높았다고 보고했다(74.8 대 70.3). 다만 논문 스스로 이득의 상당 부분을 항목을 컨텍스트 밖에서 병렬로 처리하는 작업 분할 기능에 돌린다. 원문을 보존한 효과만 따로 잰 수치는 아니다. 논문은 2월 자료다.
새로운 것과 원래 있던 것
원문을 지우지 않는 메모리는 새 아이디어가 아니다. Letta(옛 MemGPT)는 모든 상태를 데이터베이스에 보존해서 컨텍스트에서 밀려나도 잃지 않고, 필요하면 대화 검색 도구로 꺼낸다. Hacker News에서 비교 대상으로 언급된 obra/episodic-memory도 Claude Code 등의 대화 기록을 원문 그대로 보관하고, 검색용으로 요약과 임베딩을 만든다.
그러니 이 프로젝트들의 차이는 원문을 보존하느냐가 아니다. 원문에서 무엇을 기준으로 골라 컨텍스트에 넣느냐가 다르다.
| 프로젝트 | 원문 보존 | 컨텍스트에 넣는 것 | 고르는 기준 |
|---|---|---|---|
| lossless-memory | 대화 텍스트 전부(도구 결과 제외) | 매 턴 25자 색인, 필요할 때 검색 결과 | 시간 우선, 다음 키워드와 의미 |
| lossless-claw | 모든 메시지 | 요약 트리와 최근 원문 64개, 필요할 때 원문 검색 | 시간 순서 요약, 필요할 때 펼침 |
| episodic-memory | 대화 기록 사본 | 검색 결과 | 의미 검색과 요약 |
| Claude Code 기본 | 모든 메시지와 도구 결과(30일) | 압축 요약과 최근 대화 | 모델이 쓴 요약 |
#50에서 본 TencentDB Agent Memory는 원문 계층 위에 요약 계층을 두고 결과 수, 문자 수, 시간에 상한을 걸었다. lossless-memory는 요약 계층 없이 원문만 두고, 검색 경로 하나에는 상한이 없다. 원문을 다 남기면 병목은 저장에서 꺼내는 쪽으로 옮겨 간다. #46에서 본 “검색이 아니라 컨텍스트가 병목”이라는 이야기와 같은 방향이다.
원문을 남기는 메모리를 설계할 때 정할 것
아래는 이 글에서 정리한 결정 항목이다. 두 프로젝트의 코드와 문서에서 문제가 된 지점을 기준으로 골랐다.
1. 얼마나 오래 보관하나. Claude Code 사용자라면 가장 간단한 방법은 cleanupPeriodDays를 늘리거나, 지워지기 전에 다른 곳으로 복사하는 것이다. 별도 메모리 도구를 붙이기 전에 이것부터 정한다.
2. 무엇을 보관하나. 도구 결과는 양이 가장 많고, 다시 실행하면 얻을 수 있는 경우가 많다. lossless-memory는 버리고 Claude Code는 30일 동안 보관한다. 비밀값이 섞일 수 있으니 저장 전이나 검색 결과에서 걸러야 한다.
3. 어떻게 컨텍스트에 넣나. 방법은 세 가지다. 매 턴 자동으로 넣기, 모델이 도구로 찾아 넣기, 사람이 명시적으로 불러 넣기. 자동 주입은 확실하지만 매 턴 토큰을 쓰고 캐시를 깰 수 있다. 도구 방식은 모델이 검색을 하지 않으면 소용이 없다. Hacker News에는 “모델들이 게을러져서 실제로 찾아보지 않는다”는 경험담도 있었다. lossless-claw의 “Expand for details about:” 목록은 모델에게 무엇이 빠졌는지 보여 줘서 도구를 부르게 만드는 방법이다.
4. 출력 상한을 누가 거나. 검색 도구가 결과 크기를 제한하지 않으면 한 번의 호출로 컨텍스트가 찬다. 위 실험처럼 65만 자가 나올 수 있다. MCP 서버나 훅처럼 검색을 호출하는 쪽에서 문자 수나 토큰 수 상한을 강제한다.
5. 매 턴 바뀌는 블록을 어디에 두나. 자동 주입하는 기억 블록을 시스템 프롬프트 쪽에 두면 매 턴 앞부분이 바뀌어 캐시가 깨진다. Hacker News에서 나온 제안은 바뀌는 블록을 마지막 사용자 메시지 안, 새 질문 바로 앞에 두는 것이었고, lossless-memory 저자도 FAQ에서 이를 받아들였다(아직 구현하지는 않았다). #48에서 본 캐시 앞부분 안정성 문제와 같다.
6. 인덱스를 어떻게 점검하나. 원문 줄 수와 인덱스 줄 수를 주기적으로 대조한다. lossless-memory의 인덱스 오염 사건은 검색 결과가 이상해진 뒤에야 발견됐다.
확인하는 방법
원문 메모리를 붙이기 전에 자기 환경에서 확인해 볼 수 있는 것들이다. 아래 명령은 Claude Code 기준이다.
- 원문이 얼마나 쌓여 있는지 본다.
du -sh ~/.claude/projects로 용량을 보고,grep -rl "This session is being continued" ~/.claude/projects | wc -l로 압축이 일어난 세션 수를 센다. 이 글을 쓰는 시점에 내 환경에서는 94MB, 압축된 세션이 5개였다. - 압축으로 잃은 것을 잰다. 압축이 일어난 세션에서 압축 전에 정한 결정, 수정한 파일 경로, 오류 메시지를 10개쯤 뽑는다. 압축 뒤 모델에게 물어 맞히는 비율을 세고, 원문 검색 결과를 넣어 줬을 때와 비교한다.
- 검색 품질을 잰다. 자기 기록으로 “언제 무엇을 정했나” 질문을 20개 만들고, 키워드만, 벡터만, 둘을 합친 경우, 시간 우선인 경우의 정답 포함률을 비교한다. 질문에 쓴 단어가 원문과 다른 경우를 따로 센다.
- 최악의 출력 크기를 잰다. 날짜만 넣은 질의처럼 넓은 질의를 던져서 결과가 얼마나 큰지 본다. 상한이 없으면 호출하는 쪽에 건다.
- 캐시 영향을 잰다. 기억 블록을 시스템 프롬프트 뒤에 둘 때와 마지막 사용자 메시지 안에 둘 때, 10턴 동안 캐시 읽기 토큰 비율을 비교한다.
정리
“요약하지 않는 메모리”를 내세운 두 프로젝트 모두 원문은 전부 저장하지만 컨텍스트에 넣을 때는 자르거나 요약한다. lossless-memory는 300자 절단과 25자 색인으로, lossless-claw는 LLM 요약 트리로 줄인다. 차이는 손실을 내는 방식과, 잃은 것을 다시 찾을 경로를 얼마나 잘 만들었느냐에 있다. lossless-memory는 검색 방식이 바뀐 것을 출력에 정직하게 표시하고, lossless-claw는 요약마다 빠진 내용의 목록을 붙인다. 반면 lossless-memory는 시간 범위 검색에 상한이 없어서 합성 데이터 실험에서 한 번에 65만 자를 돌려줬고, lossless-claw는 똑똑하게 고르는 옵션이 캐시와 충돌해 기본값으로 꺼 뒀다.
Claude Code 사용자라면 원문은 이미 디스크에 있다. 압축 불만을 해결하려면 먼저 보관 기간을 정하고, 그다음 원문을 어떤 기준으로 꺼내 넣을지, 그 결과의 크기를 누가 제한할지, 바뀌는 블록을 어디에 둘지를 정해야 한다. 새 메모리 도구를 붙이는 일은 이 결정을 도구에 맡기는 것이므로, 그 도구가 이 결정들을 어떻게 내리고 있는지부터 확인하는 게 좋다.
참고: Show HN: Lossless-memory (2026-09-21), aru-labs/lossless-memory, Martian-Engineering/lossless-claw, LCM: Lossless Context Management (Voltropy, 2026-02), obra/episodic-memory, open-mem/omp, Claude Code .claude 디렉터리 문서, Claude Code 이슈 #63601, #75759, #91351, Letta 메모리 문서. lossless-memory 분석은 2026-09-24 기준 저장소 코드이고, 실험은 이 코드에 합성 로그 2,000줄(하루치)을 넣어 돌린 결과다. lossless-claw는 공식 문서와 일부 코드를 확인했다. LCM 논문 수치는 논문 저자의 보고다. 설계 항목과 확인 방법은 이 글의 제안이며, 확인 방법 1의 용량 수치 외에는 직접 측정하지 않았다.