🐾 claw-stack
· Orange & Qiushi Wu memory architecture AI agents

AI 에이전트를 위한 지속형 메모리 시스템 구축

Claw-Stack 메모리 시스템을 구축한 방법: 세션 시작 인덱스로서의 MEMORY.md, 주제별 Markdown 파일, SQLite FTS5 + QMD 벡터 검색, 그리고 MEMORY.md 비대화에 대한 고통스러운 교훈

AI 에이전트에 메모리를 부여하기 위한 정석적인 조언은 다음과 같습니다: 벡터 데이터베이스를 사용하세요. 임베딩을 저장하고, 유사성 검색을 수행하고, 관련 청크를 검색하세요. 이는 쿼리 패턴이 “이 질문과 유사한 문서를 찾아라”인 검색-증강 생성 시스템에는 좋은 조언입니다. 하지만 지난 화요일에 무엇을 했는지6주 전 프로젝트 X에 대해 어떤 결정을 내렸는지 기억해야 하는 에이전트에는 반드시 올바른 답은 아닙니다.

Claw-Stack 메모리 시스템을 어떻게 구축했는지, 왜 이렇게 보이는지, 그리고 과정에서 무엇을 배웠는지 설명하겠습니다.

상태 비저장 에이전트의 문제점

모든 Claude 세션은 새로 시작됩니다. 모델은 시작 시 명시적으로 그 컨텍스트를 주입하지 않는 한 이전 세션에 대한 메모리가 없습니다. 한 번만 대화하는 연구 어시스턴트의 경우 이는 문제가 되지 않습니다. 하지만 매일 실행되고, 프로젝트에 대한 지식을 축적하고, 몇 주에 걸쳐 일관된 행동을 유지해야 하는 자율 에이전트의 경우, 이는 근본적인 문제입니다.

순진한 해결책은 모든 것을 시스템 프롬프트에 넣는 것입니다. 이는 몇 백 KB의 컨텍스트를 축적할 때까지 작동하며, 그 지점에서 두 가지 일이 발생합니다: 컨텍스트 한계에 도달하기 시작하고, 매우 긴 컨텍스트의 초반 부분을 사용하는 모델의 능력이 저하됩니다. 에이전트는 3개월 전에 지시한 것을 무시하기 시작합니다. 현재 상호작용에서 너무 멀리 있기 때문입니다.

우리는 두 가지 속성을 가진 메모리 시스템이 필요했습니다: 선택적이어야 하고(현재 세션과 관련된 것만 주입), 인간이 읽을 수 있어야 했습니다(에이전트가 무엇을 믿는지 감시하고, 편집하고, 수정할 수 있어야 했습니다).

세 계층 아키텍처

메모리 시스템은 세 가지 계층으로 구성됩니다:

계층 1: MEMORY.md — 모든 세션 시작 시 로드되는 컴팩트한 인덱스입니다. 최근 활동, 활성 프로젝트, 주요 연락처 및 인프라 메모에 대한 섹션이 있는 구조화된 Markdown 파일입니다. 의도적으로 짧게 유지됩니다 — 시스템이 바이트 크기 상한을 적용합니다 — 큰 작업 설명이 있는 세션에서 컨텍스트 예산을 소비하지 않도록 합니다.

계층 2: 주제별 파일memory/의 특정 주제에 대해 깊이 있게 다루는 더 긴 Markdown 파일입니다. projects/claw-stack.md, contacts/key-people.md, infrastructure/servers.md. 이들은 자동으로 로드되지 않습니다. 에이전트는 주제에 대해 깊이 있게 알아야 할 때 특정 파일을 가져오는 read_memory 도구를 가지고 있습니다.

계층 3: SQLite + QMD 벡터 검색 — FTS5 전문 검색과 QMD(SQLite를 기반으로 구축된 벡터 임베딩 도구) 인덱스가 있는 SQLite 데이터베이스입니다. 에이전트가 MEMORY.md 및 주제별 파일에서 답변할 수 없는 쿼리를 받으면, 모든 메모리 컨텐츠에 걸쳐 벡터 검색을 실행하여 관련 조각을 찾습니다.

벡터 데이터베이스를 사용하지 않은 이유

짧은 답: 우리의 규모와 액세스 패턴의 경우, 독립형 벡터 데이터베이스의 운영 오버헤드가 그만한 가치가 없습니다.

우리가 전용 벡터 데이터베이스 대신 SQLite + FTS5를 선택한 주요 이유:

  1. 불투명성. 전용 벡터 데이터베이스를 사용하면, 검색 도구 없이 검색이 올바른지 쉽게 검사할 수 없습니다. Markdown 파일은 모든 텍스트 에디터에서 열 수 있습니다. 우리의 SQLite 데이터베이스는 모든 SQLite 도구로 열리며, 스키마는 우리가 직접 작성한 테이블입니다.

  2. 운영 단순성. 전체 메모리 저장소는 단일 .db 파일과 Markdown 파일 디렉토리입니다. 관리할 별도의 프로세스, 형식 마이그레이션, 데이터베이스 바이너리와 데이터 간의 버전 호환성 문제가 없습니다.

  3. 우리 규모에 충분함. 우리는 모든 파일에 약 50,000개의 단어로 된 메모리 컨텐츠를 가지고 있습니다. SQLite FTS5는 밀리초 단위로 그것에 걸쳐 전문 검색을 수행할 수 있습니다. 벡터 유사성이 키워드 검색보다 의미 있게 나은 경우는 실제이지만, 운영 오버헤드를 정당화할 만큼 충분히 드뭅니다.

QMD(벡터 검색 계층)는 SQLite 위에 위치합니다. 임베딩은 작은 양자화된 모델을 사용하여 로컬로 계산되고 텍스트와 함께 SQLite 테이블에 저장됩니다. 재인덱싱은 몇 초가 걸립니다. 전체 메모리 저장소는 단일 .db 파일과 Markdown 파일 디렉토리입니다.

정렬 파이프라인

메모리는 자동으로 관리되지 않습니다. 모든 세션 후에 정렬 파이프라인이 실행됩니다:

raw session files
  → scan memory/*.md (MD5 hash check, skip unchanged)
  → extract facts per category (project updates, decisions, contacts)
  → deduplicate against existing memory
  → write updated per-topic files
  → rebuild SQLite FTS5 index
  → update MEMORY.md index

추출 단계는 LLM을 사용합니다(주로 Gemini, Claude Haiku를 대체로): 세션 기록을 읽고 특정 형식으로 구조화된 메모를 생성합니다. 중복 제거 단계는 규칙 기반입니다: 새 사실이 기존 항목의 부분 문자열이면 건너뛰고, 기존 항목과 모순되면 인간 검토를 위해 표시합니다.

파이프라인은 모든 세션 직후가 아니라 cron 일정(활동 중 몇 시간마다)으로 실행됩니다. 이는 처리 비용을 배치하고 후속 세션에 의해 즉시 덮어씌워질 메모리 파일을 쓰는 것을 피합니다.

MEMORY.md 비대화 문제

가장 고통스러운 교훈은 MEMORY.md 성장에 대한 것이었습니다.

우리는 MEMORY.md 길이에 제한이 없이 시작했습니다. 정렬 도구가 계속 추가했습니다. 6주 후 MEMORY.md는 700줄이 넘었습니다. 이는 예측 가능한 효과를 가졌습니다: 세션 시작이 실제 작업 컨텐츠가 로드되기 전에 대부분의 컨텍스트 예산을 소비했으며, 모델은 수백 줄의 브리핑을 종합하면서 동시에 유용한 작업을 수행하는 것을 명백히 어려워했습니다.

수정은 정렬 도구의 행동을 변경하고 크기 상한을 적용하는 것이었습니다. 새 사실을 MEMORY.md에 직접 추가하는 대신, 정렬 도구는 주제별 파일에 쓰고 MEMORY.md를 포인터로 업데이트합니다 — MEMORY.md에 전체 상태를 포함하는 대신 “현재 상태는 projects/claw-stack.md를 참조하세요”라고 말하는 한 줄입니다. 시스템은 이제 MEMORY.md의 바이트 크기 한계를 적용하여 폭주 성장을 방지합니다.

이는 MEMORY.md가 무엇인지 다시 생각하도록 요구했습니다. 에이전트가 알고 있는 모든 것의 요약이 아닙니다. 이는 세션 브리핑입니다 — 세션 시작 시 에이전트를 정향하는 데 필요한 최소 컨텍스트입니다. 그 이상의 것은 필요에 따라 가져옵니다.

리팩터링 후, 세션 시작이 눈에 띄게 빨라졌으며, 모델은 가진 컨텍스트를 더 잘 활용합니다. MEMORY.md를 진정으로 컴팩트하게 유지하는 것은 지속적인 규율입니다 — 우리는 고정 줄 수가 바이트 크기 한계보다 덜 유용하다는 것을 발견했으며, 그것도 정렬 도구가 인라인 컨텐츠보다는 포인터를 사용하는 것에 대해 적극적이어야 합니다.

인간이 읽을 수 있는 상태로서의 메모리

시스템 뒤의 설계 철학은 에이전트 메모리가 인간이 읽을 수 있고 인간이 편집할 수 있어야 한다는 것입니다. 이는 우리가 의도적으로 부과한 제약입니다.

에이전트가 잘못된 신념을 발전시킬 때 — 가끔 발생합니다 — 우리는 Markdown 파일에서 잘못된 항목을 찾아 편집할 수 있으며, 수정은 다음 세션에 적용됩니다. 벡터 데이터베이스를 사용하면 잘못된 신념을 수정하려면 어떤 임베딩을 업데이트할지 알아야 하고, 삭제하고, 새 것을 작성해야 하며, 잠재적으로 캐시된 검색을 무효화해야 합니다. Markdown 파일을 사용하면 파일을 열고 텍스트를 변경합니다.

이는 또한 감시를 간단하게 만듭니다. 자율 에이전트에게 당신을 대신하여 결정을 내리도록 신뢰하기 전에, 그 신념을 읽고 올바른지 검증할 수 있어야 합니다. 전체 메모리 시스템은 Markdown 파일의 디렉토리입니다. 모든 텍스트 에디터가 작동합니다.

트레이드오프는 형식이 고정되어 있다는 것입니다. 우리의 메모리 파일은 정렬 도구가 구문 분석하고 업데이트하는 방법을 알고 있는 특정 스키마를 따릅니다. 새로운 메모리 범주를 추가하려면, 파일 스키마와 정렬 도구를 모두 업데이트해야 합니다. 한 명의 운영자가 있는 연구 프로젝트의 경우, 이는 수용 가능합니다. 많은 에이전트와 많은 유형의 메모리를 가진 프로덕션 시스템의 경우, 더 유연한 것을 원할 것입니다.

다르게 하게 될 것들

처음부터 시작한다면:

처음부터 더 작은 MEMORY.md를 사용하세요. 초기 크기 상한으로 피할 수 있었던 비대화를 정리하는 데 몇 주를 낭비했습니다. 바이트 크기 한계와 포인터 기반 항목이 일상 사용 어시스턴트의 고정 줄 수보다 더 나은 목표입니다.

에피소드 메모리와 의미 메모리를 더 일찍 분리하세요. “화요일 세션에서 무슨 일이 있었는가”(에피소드)와 “Claw-Stack 아키텍처는 무엇인가”(의미)는 다른 검색 전략의 이점을 받는 메모리의 다른 유형입니다. 우리는 처음에 섞었고 나중에 분리하는 데 시간을 보냈습니다.

감시 도구를 먼저 구축하세요. 에이전트 메모리 시스템을 유지하기의 가장 어려운 부분은 인덱싱이나 검색이 아닙니다 — 메모리가 언제 잘못되었는지 아는 것입니다. 우리는 감시 보기(주어진 주제에 대해 에이전트가 무엇을 믿는지 보여주는 스크립트)를 너무 늦게 구축했습니다. 우리가 작성한 첫 번째 도구여야 했습니다.

메모리 시스템은 Claw-Stack의 일부로서 우리가 가장 만족하는 부분입니다. 안정적으로 작동하는 지루한 인프라이며, 이는 정확히 메모리가 무엇이어야 하는 것입니다.