에이전트 메모리 엔진 (3/10) — memcore 완성기: 18 모듈 3,300줄

20 테이블, CLI 8 커맨드, 295 curated rows, 그리고 graceful degradation


핵심 요약

  • memcore는 파일 기반 메모리 파이프라인을 SQLite 단일 파일로 재구축한 라이브러리다. 18 모듈, 약 3,300줄.
  • CLI 8개로 운영의 전체 주기를 cron 한 줄씩 걸어 자동화할 수 있다: migrate / lint / warn / decay / wiki-lint / stats / backfill-vectors / ontology-sync.
  • 핵심 설계 원칙은 graceful degradation — 의존성이 빠져도 기능이 축소될 뿐, 전체가 멈추지 않는다.

이 글에서 얻어갈 것

  • SQLite 단일 파일 기반 에이전트 메모리 엔진의 모듈 분할 방식과 테이블 설계
  • 벡터 검색이 없는 환경에서도 FTS5로 폴백하는 하이브리드 prefetch 전략
  • 메모리 관리 주기(감쇠·검증·정리)를 CLI로 분리하고 cron에 위임하는 패턴
  • MemoryProvider 인터페이스로 상위 시스템과 메모리 구현을 분리하는 방법

범위 정의 — 초기 구축본과의 차이

memcore의 초기 버전은 9 테이블, 51 tests, 파일 기반(bank/) 구조의 마이그레이션까지 포함했다. 이 글이 다루는 "완성된 형태"는 테이블 20개, CLI 커맨드 집합, 벡터 검색 모듈, 온톨로지 동기화가 추가된 상태다. 즉, 저장 계층뿐 아니라 운영 주기와 폴백 전략까지 엔진 내부에 내장된 버전이 대상이다.

모듈 구조 (18개)

# 모듈 역할
1 core SQLite 연결, WAL, 스키마 초기화
2 ingest retain-extract / retain-merge 통합
3 prefetch FTS5 하이브리드 검색 (토픽 → FTS5 → LIKE)
4 dialectic U-tag 3-phase (관찰→가설→검증)
5 decay opinions confidence 감쇠 (0.02/일, <0.30 제거)
6 lint Retain 태그 포맷 검증
7 entities entities stale 감지 (30일)
8 topics 토픽 레지스트리 + M2M 관계
9 bank_migrate bank/ → SQLite 변환 (READ-ONLY, --incremental)
10 decisions LLM 의사결정 큐 (TOPIC_CLASSIFY, CONFLICT_RESOLVE, CHANGELOG_SUMMARIZE)
11 archive memory/ → archived/ 이동
12 housekeeping recall TTL, session cleanup, stale orphan
13 vectors sqlite-vec + bge-m3 임베딩 (선택적)
14 wiki Karpathy LLM Wiki 패턴 (토픽 페이지 CRUD)
15 wiki_lint 7종 위키 검사 (모순/stale/orphan/gap/size/citation/frontmatter)
16 ontology CLAUDE.md → DB 단방향 캐시 (에이전트/관계/개인 계층)
17 promotion cascade promotion (local→lessons→global)
18 stats 통계 / 헬스체크

모듈은 저장(core) → 쓰기(ingest/promotion) → 읽기(prefetch) → 정리(decay/housekeeping/archive) → 검증(lint/wiki_lint) → 부가(vectors/wiki/ontology) → 관측(stats) 순서로 책임이 나뉜다. 각 모듈은 다른 모듈의 내부 구현을 알지 못하고, core가 노출하는 연결 객체와 공용 스키마만을 공유한다.

20 테이블

그룹 테이블 용도
메타 meta 스키마 버전, 설정
지식 curated, curated_fts 핵심 지식 + FTS5 검색
토픽 topics, topic_curated 토픽 레지스트리 + M2M
엔티티 entities 프로젝트별 상태
에피소드 episodes 일일 로그
정체성 identity MEMORY.md 캐시
U-tag u_patterns, u_observations, u_hypotheses, u_verifications 변증법 3단계
결정 decisions LLM 의사결정 큐
위키 wiki_pages, wiki_sources, wiki_log Karpathy wiki
벡터 vec_curated sqlite-vec 임베딩 (선택적)
온톨로지 ont_agents, ont_relations, ont_layers CLAUDE.md 캐시
승격 promotions local→lessons→global 이력

테이블은 크게 세 층으로 나눠 볼 수 있다. 사실 기록층(curated, episodes, entities, identity), 추론 과정층(u_patterns 계열, decisions, promotions), 보조 인덱스층(curated_fts, vec_curated, topic_curated). 보조 인덱스층이 비어도 사실 기록층은 자립적으로 조회 가능하도록 스키마가 설계돼 있다.

CLI 8 커맨드

memcore migrate          # bank/ → SQLite 변환
memcore lint             # Retain 태그 + 데이터 무결성
memcore warn             # 메모리 경고 리포트
memcore decay            # opinions confidence 감쇠 실행
memcore stats            # 통계 / 헬스체크
memcore wiki-lint        # 위키 7종 검사
memcore backfill-vectors # sqlite-vec 임베딩 일괄 생성
memcore ontology-sync    # CLAUDE.md → DB 동기화

CLI는 런타임에서 분리 가능한 유지보수 작업을 외부로 밀어내는 장치다. 감쇠·린트·통계처럼 주기적으로 수행되어야 하지만 대화 루프 안에서 돌 필요는 없는 작업을 cron/launchd에 위임하면, 런타임 쪽은 prefetch·ingest만 책임지면 된다. 오케스트레이션 프레임워크 없이도 시스템이 자기 관리 주기를 가질 수 있다.

데이터 현황

항목 수치
curated rows 295
topics 15
entities 4
topic_curated links 514
분포 knowledge 150 / pattern 28 / daily 17 / world 14 / identity 13 / experience 10 / opinion 4

topic_curated 링크 수가 curated rows의 약 1.74배(514/295)라는 점은 한 항목이 평균적으로 1~2개 토픽에 연결됨을 뜻한다. 단일 태깅이 아닌 다중 토픽 연결이 기본형이라는 신호다. 분포를 보면 knowledge 계열이 절반 이상을 차지하며, opinion 계열이 적은 것은 감쇠(decay)가 실제로 작동해 저신뢰 항목이 제거되고 있다는 간접 증거로 읽을 수 있다.

핵심 설계 원칙 — graceful degradation

memcore는 의존성이 빠진 상태에서도 코드 경로가 유효하도록 설계된다.

  • sqlite-vec 없음: FTS5 텍스트 검색으로 폴백. 의미 검색이 키워드 검색으로 떨어지지만 경로 자체는 유지된다.
  • bge-m3 없음: 벡터 빌드 단계를 건너뛴다. prefetch는 FTS5 단독으로 동작.
  • CLAUDE.md 없음: 온톨로지 캐시가 비어 있을 뿐, 다른 모듈에 영향 없음.
  • 위키 페이지 없음: wiki_lint가 "0 pages checked"를 반환하고 정상 종료.

이 방식의 실용적 이점은 설치 프로파일 편차를 견딘다는 점이다. 풀 스펙은 Apple Silicon + oMLX + sqlite-vec이지만, 최소 스펙은 Python + SQLite만으로 성립한다. 동일 코드베이스를 서로 다른 환경에 배포할 때 분기 설치 스크립트를 줄이고, 런타임 내 feature flag로 갈음한다.

상위 시스템과의 접점 — MemoryProvider

memcore는 상위 런타임과 MemoryProvider 추상 인터페이스로만 결합된다. 구현체는 MemcoreProvider(MemoryProvider).

  • prefetchmemcore.prefetch.search
  • on_session_endmemcore.ingest
  • on_memory_writememcore.promotion (Tier 게이트 통과)
  • system_prompt_block → curated 상위 N개를 컨텍스트에 주입

상위 런타임은 memcore의 존재를 알지 못한다. 알아야 하는 것은 인터페이스 메서드의 시그니처뿐이다. 이 경계 덕분에 저장 계층을 교체하거나 업그레이드할 때 상위 코드는 변경되지 않는다. 같은 원리가 벡터 백엔드 교체(sqlite-vec → Qdrant/FAISS)나 메모리 엔진 실험(A/B 두 구현 병행)에도 적용된다.

한계와 적용 범위

  • 단일 파일 SQLite: 동시 쓰기가 많지 않은 에이전트 시나리오에 적합. 멀티 라이터 워크로드에는 부적합.
  • 임베딩 비용: backfill-vectors는 일괄 처리에 최적화돼 있다. 스트리밍/실시간 임베딩에는 별도 경로가 필요.
  • 온톨로지 단방향 캐시: DB에서 편집해 CLAUDE.md로 되돌리는 역방향 동기화는 범위 밖. 진본은 Markdown.
  • CLI 기반 운영: cron/launchd가 없는 환경(예: 순수 서버리스)에서는 외부 스케줄러가 필요.

적용 범위는 "로컬에서 돌고, 단일 작성자이며, 의존성 편차가 큰 에이전트 환경에서 안정적으로 관측 가능한 메모리 저장소가 필요한 경우"로 요약된다.

열린 질문

  • 여러 에이전트가 동시에 동일 memcore 파일을 참조할 때의 일관성 모델은 어떻게 정의할 것인가?
  • 감쇠·승격 정책의 상수(0.02/일, <0.30 제거 등)가 코퍼스 규모에 따라 적응적으로 바뀌어야 하는가?
  • MemoryProvider 계약을 유지한 채 non-SQLite 백엔드(예: 키-값 + 외부 FTS)로 바꾸면 어떤 기능이 축소되는가?

3,300줄은 "1년간 축적된 메모리 판단 로직을 단일 파일 스토리지 위에 재배치한 결과"에 해당하는 규모다. 파일 기반 → SQLite 전환의 본질은 포맷 변경이 아니라, 운영 주기를 CLI 한 줄로 환원해 외부 스케줄러에 위임할 수 있게 된 것에 있다.

시리즈 전체 안내: 시리즈 목차

댓글

이 블로그의 인기 게시물

"LLM 핵심 학습 (1/6) — 기본: 토큰화·임베딩·어텐션·위치 인코딩"

"LLM 핵심 학습 (2/6) — 파인튜닝: LoRA·QLoRA·증류·Adapter"

"ML 기초 학습 (1/9) — 머신러닝과 sklearn: 학습의 좌표계"