Claude Code가 세션 사이의 모든 것을 잊어버리는 것을 방지하기 위해 MCP 서버를 구축했습니다
요약
Claude Code가 세션 간의 맥락을 유지하지 못하는 문제를 해결하기 위해 6개의 메모리 계층을 갖춘 MCP 서버인 linksee-memory를 구축했습니다. 이 도구는 SQLite를 기반으로 하며, 사용자의 결정 이유와 '다시는 하지 말아야 할 실수(caveat)'를 영구적으로 저장하여 반복적인 오류를 방지합니다.
핵심 포인트
- Claude Code의 세션 간 망각 문제 해결을 위한 MCP 서버 구축
- 6개의 구조화된 메모리 계층과 WHY-first 설계 원칙 적용
- caveat 계층을 통해 잘못된 패턴의 반복 제안을 0으로 감소
- 로컬 SQLite 기반으로 Claude Code, Cursor 등과 호환 가능
요약 (TL;DR)
- 문제 (Problem): Claude Code는 매 세션마다 초기 상태로 시작합니다. 이미 거절한 라이브러리를 다시 제안하거나, 몇 주 전에 내린 결정에 대해 _"X를 사용할까요?"_라고 묻고, 변경되지 않은 파일을 처음부터 다시 읽습니다 (매번 전체 토큰 비용을 지불함).
- 해결책 (Solution): 6개의 구조화된 메모리 계층(memory layers)과 청크 단위(chunk-level) 파일 차이(diff) 캐싱을 갖춘 MCP 서버인
linksee-memory를 구축했습니다. 로컬 SQLite를 사용하며, MIT 라이선스이고, 클라우드를 사용하지 않습니다. 동일한 데이터베이스를 통해 Claude Code / Cursor / Codex / Gemini CLI 전반에서 작동합니다. - 내 워크플로우를 실제로 변화시킨 기능:
caveat계층 — _"다시는 이렇게 하지 마세요"_와 같은 항목을 위해 '망각 방지'가 적용된 엔트리입니다. 잘못된 패턴에 대한 반복적인 제안을 **0(zero)**으로 줄였습니다. - 설치 (Install):
npm install -g linksee-memory
문제: 내 에이전트가 계속해서 똑같은 실수를 반복했습니다
지난달 세 번의 월요일 동안, 저는 Claude Code에게 동일한 배포 실패 사례를 설명했습니다. 근본 원인은 매번 동일했습니다. 우리의 프로덕션 Cloudflare Workers가 분산된 인스턴스 간에 메모리 캐시 불일치(memory-cache incoherence) 문제를 겪고 있었습니다. 매 세션마다 저는 동일한 조사 과정을 거쳤습니다 — 동일한 파일, 동일한 로그, 동일한 결론에 도달하기 위한 동일한 30분의 추적 과정 말입니다.
Claude Code는 이전 세션을 기억하지 못합니다. 그래서 매주 월요일 아침, 저는 에이전트와 함께 처음부터 똑같은 버그를 다시 발견하고 있었습니다.
기존의 해결책들을 시도해 보았지만, 어느 것도 적합하지 않았습니다:
| 도구 (Tool) | 장점 | 나에게 부족했던 점 |
|---|---|---|
CLAUDE.md | 설정 불필요, 공식 지원 | 평면적인 구조, 모델이 일부를 무시함 |
| ... |
모두 서로 다른 것을 최적화하는 좋은 제품들입니다. 하지만 제가 가장 원했던 기능 — 똑같은 실수를 절대 반복하지 않겠다는 보장 — 은 그 어떤 것에도 없었습니다.
그래서 저는 linksee-memory를 만들었습니다.
설계: caveat을 핵심으로 하는 6개의 계층
linksee-memory는 메모리를 6개의 명시적인 계층으로 구성합니다:
+-- goal 우리는 이것을 왜 하고 있는가?
+-- context 현재 상황 및 제약 조건
+-- emotion 사용자의 기분, 관계의 어조
...
핵심 설계 원칙은 **WHY-first (이유 우선)**입니다. 다른 도구들은 사실 (facts) ("우리는 PostgreSQL을 사용한다")을 저장합니다. 반면 linksee-memory는 WHY ("워크로드가 엄격한 일관성 요구가 있는 OLTP이므로 PostgreSQL을 선택했다")와 WHAT ("연결 풀: 20, 타임아웃: 30초")을 분리합니다.
caveat 레이어는 특별합니다. 이곳의 엔트리들은 자동 망각 (auto-forgetting)으로부터 영구적으로 보호됩니다. 오래된 기억이 쇠퇴하고 통합(consolidate)되더라도, caveat는 영원히 유지됩니다. 이것이 제가 "다시는 이 실수를 반복하지 마라"라는 명령을 프롬프트 규율이 아닌, 구조적 속성으로서 강제하는 방식입니다.
8개가 아닌 3개의 도구
초기 버전에는 8개의 별도 도구(remember, update_memory, forget, recall, recall_file, list_entities, consolidate, read_smart)가 있었습니다. 이는 제가 SKILL.md를 통해 도구 선택 방법을 가르칠 수 있었던 Claude Code에서는 잘 작동했습니다.
하지만 Cursor는 recall과 recall_file을 구분하지 못했습니다. Codex는 remember와 update_memory를 혼동했습니다. 도구가 너무 많으면 = LLM의 도구 선택 (tool selection) 능력이 무너집니다.
v0.7에서는 모든 것을 3개의 도구로 통합했습니다:
| 도구 | 기능 |
|---|---|
remember | 메모리 생성, 업데이트 또는 삭제. 파라미터로부터 모드 자동 감지. |
| ... |
// 생성
remember({ entity_name: "MyProject", layer: "caveat", content: "..." })
...
의도(intent)당 하나의 도구 이름만 사용합니다. 이제 모든 LLM 클라이언트가 이를 올바르게 처리합니다.
나를 놀라게 한 부분: read_smart()
구조화된 메모리는 도구의 절반에 불과합니다. 나머지 절반은 파일 차이(file-diff) 캐싱입니다.
당신의 에이전트가 매 세션 시작 시 무엇을 하는지 생각해 보세요:
package.json을 다시 읽음- 메인 엔트리 파일을 다시 읽음
- 설정을 다시 읽음
- 테스트 코드를 다시 읽음
각 파일은 약 500~2000줄 정도입니다. 매 세션마다, 이 모든 파일을 다시 읽기 위해 전체 토큰 비용을 지불해야 합니다. 하지만 대부분의 세션에서, 이 파일들의 대부분은 변경되지 않았습니다.
read_smart()는 청크(chunk) 수준의 캐싱을 통해 이 문제를 해결합니다:
// 첫 번째 읽기: 전체 파일, 청크 단위로 나누어 캐싱
const r1 = read_smart({ path: "src/http-server.ts" });
// -> 전체 내용, 약 3400 토큰
...
청크 경계는 언어 인지적(language-aware)입니다:
- Code (TS / JS / Python / 등): AST 기반, 함수 또는 클래스당 하나의 청크
- Markdown:
h2/h3섹션당 하나의 청크 - JSON / YAML: 최상위 키(top-level key)당 하나의 청크
캐시 키는 sha256(chunk_content)입니다. 실제로 파일 재읽기 시 약 86%의 토큰 감소를 확인했습니다.
구체적인 예시: Claude의 반복을 막아준 주의사항 (caveat)
caveat이 작동하는 실제 사례를 소개합니다.
몇 주 전, 일회성 데이터 마이그레이션 작업을 위해 Claude는 _"cron job을 설정합시다"_라고 제안했습니다. 일회성 작업에는 cron을 사용해서는 안 됩니다 (인증 로테이션 오버헤드, 모니터링 비용, 일회성 의미론과 맞지 않는 재시도 로직 등).
저는 다음과 같은 주의사항(caveat) 항목을 하나 저장했습니다:
일회성 작업에 대해 cron을 제안하지 마세요. 대안: GitHub Actions의
workflow_dispatch또는 완료 알림 기능이 있는 수동 스크립트.
해당 주의사항을 추가하기 전 4번의 세션 동안, Claude는 일회성 작업에 대해 cron을 4번 제안했습니다.
주의사항을 추가한 이후 3주 동안은? 0번입니다.
그리고 제가 사용하는 모든 LLM 클라이언트가 동일한 SQLite 파일을 공유하기 때문에, 이 주의사항은 Claude Code뿐만 아니라 Cursor, Codex, Gemini CLI에서도 동일하게 작동합니다.
설치 (2분 소요)
Claude Code
npm install -g linksee-memory
claude mcp add -s user linksee -- npx -y linksee-memory
Cursor
Settings -> Features -> "Model Context Protocol" -> Edit:
"linksee": {
"command": "npx",
"args": ["-y", "linksee-memory"]
...
OpenAI Codex
codex mcp add linksee-memory -- npx -y linksee-memory
Gemini CLI
~/.gemini/settings.json:
{
"mcpServers": {
"linksee": {
...
모든 것은 로컬에서 실행됩니다. SQLite 파일은 ~/.linksee-memory/memory.db에 위치합니다. 클라우드, API 키, 텔레메트리(telemetry)는 없습니다. MIT 라이선스입니다.
메모리가 단일 SQLite 파일에 저장되기 때문에, 사용자의 머신에 있는 모든 MCP 클라이언트 간에 공유됩니다. 저의 Claude Code 세션과 Cursor 세션은 동일한 메모리를 공유합니다.
진심으로 피드백을 기다립니다
저는 이것을 3개월 이상 매일 사용해 왔으며, 저의 워크플로우에 맞춰 직접 설계했다는 점에서 저의 결과에는 편향이 있을 수 있습니다. 저는 다음 사항들이 궁금합니다:
caveat레이어가 실제로 여러분의 에이전트가 실수를 반복하는 것을 방지하나요, 아니면 제가 하나의 데이터셋(저 자신)에 대해서만 패턴 매칭(pattern-matching)을 하고 있는 것인가요?- 여러분의 코드베이스(codebase)에서 청크 캐시(chunk cache)는 어떻게 동작하나요? 모노레포(Monorepos), 생성된 코드(generated code), 노트북(notebooks) 등 버그 리포트를 환영합니다.
- 6개의 레이어가 너무 많은가요? 아니면 너무 적은가요? 이 중 어느 것도 다루지 않는, 여러분이 원하는 메모리 유형이 있나요?
GitHub에서 이슈를 생성하거나, X에서 @ELLECraftsinga1으로 메시지를 보내주세요. 모든 메시지에 답변해 드립니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기