에이전트 메모리 엔진 (2/10) — SQLite 하나로 AI 에이전트 메모리 시스템 만들기

데몬 없이, 의존성 없이, 어디서든 동작하는 메모리 엔진의 구조와 원리


핵심 요약

이 글은 단일 파일 SQLite 기반으로 AI 에이전트 메모리 엔진(memcore)을 설계하는 방법을 다룬다. - 구조: 25개 모듈, ~6,464줄, 22개 DB 테이블 - 원칙: 데몬 없음, 단일 파일 이식성, 필수 외부 의존성 없음, 호스트 무관 - 핵심 기법: 4계층 하이브리드 검색(토픽·벡터·FTS5·LIKE), U-tag 변증법 기반 신뢰도 진화, 수집 레이어 편향 차단 규칙


이 글이 전달하는 것

AI 에이전트 메모리를 서버나 클라우드 벡터 DB 없이 구성하는 한 가지 방법을 정리한다. 구체적으로는 (1) 텍스트 파일 기반 메모리의 한계, (2) 벡터 DB 대안 비교와 선택 기준, (3) 검색 계층을 어떻게 섞을 것인가, (4) 신뢰도를 어떻게 진화시킬 것인가, (5) 편향된 기억이 쌓이는 것을 수집 단계에서 어떻게 차단할 것인가를 다룬다.


설계 원칙: 왜 단일 SQLite인가

텍스트 파일 기반 메모리는 세션이 길어질수록 컨텍스트 주입 비용이 증가하고, 검색이 LIKE 패턴 매칭에 묶인다. 관련성 낮은 기록이 섞여 들어오면서 응답 품질이 떨어진다.

벡터 DB(Pinecone, Weaviate, Chroma 등)는 일반적인 대안이지만, 외부 서버가 필요하거나 클라우드 의존성이 생긴다. 단독 에이전트가 단일 호스트에서 돌아가는 환경에서는 외부 서비스 장애가 에이전트 전체 장애로 번진다.

이 제약 조건에서 다음 네 가지 원칙이 성립한다:

  1. 데몬 없음 — 별도 프로세스 없이 라이브러리로만 동작
  2. 단일 파일 이식성.db 파일 하나로 전체 메모리 이동 가능
  3. 필수 외부 의존성 없음 — 핵심 기능은 표준 라이브러리로 처리
  4. 호스트 무관 — macOS, Linux, 환경 제약 없이 동작

이 네 조건을 모두 만족시키는 스토리지가 SQLite다. 확장(sqlite-vec, FTS5)이 파일 내부에 통합되며 추가 인프라 없이 벡터/전문 검색을 얹을 수 있다.


스키마: 22개 테이블, 코어/확장 분리

DB 스키마는 총 22개 테이블로 구성된다. 11개 코어 + 9개 확장 + 후속 추가 2개다.

코어 테이블은 메모리 엔티티, U-tag 상태, 임베딩 벡터, FTS5 인덱스, 토픽 링크를 담는다. 확장 테이블은 세션 기록, 리서치 트리거, 월간 요약 등 기능 단위로 분리돼 있다. 기능 단위 분리는 스키마 마이그레이션 시 코어를 건드리지 않고 확장만 손대도록 하기 위한 구조다.


4계층 하이브리드 검색

검색은 네 개 계층을 가중치로 혼합한다:

1. 토픽 매칭 (가중치 2.0)  →  가장 빠름, 토픽 분류된 기억
2. 벡터 유사도 (가중치 1.5) →  의미 기반, bge-m3-mlx 임베딩
3. FTS5 BM25 (기본)        →  키워드 기반 전문 검색
4. LIKE 폴백 (가중치 5.0)  →  위 3개 모두 결과 없을 때

가중치가 클수록 해당 소스 결과가 상위에 배치된다. 토픽 매칭은 사람이 분류한 결과이므로 정확도가 높아 우선순위가 높게 잡힌다. LIKE는 마지막 수단이지만, LIKE가 히트했다는 것은 곧 정확한 문자열 일치이므로 신뢰도가 높아 가중치 5.0이 부여된다.

네 계층은 서로 독립적으로 동작한다. FTS5가 없어도 나머지가 동작하고, 벡터가 없어도 토픽+FTS5+LIKE로 기본 검색이 된다. 점진적 강화 구조이기 때문에 특정 계층만 비활성화해도 시스템이 무너지지 않는다.


벡터 엔진 선택: sqlite-vec

후보 다섯 개를 이식성·속도·설치 부담·의존성 기준으로 비교했다.

라이브러리 점수 (35점 만점) 주요 특이사항
sqlite-vec 28 SQLite 확장, 이식성 최고
hnswlib 28 속도 빠름, 별도 파일 필요
faiss 22 기능 최다, 설치 무거움
Chroma 19 서버 필요
Pinecone 15 클라우드 의존

sqlite-vec과 hnswlib이 동점이었으나, 이식성에서 sqlite-vec이 앞섰다. SQLite 확장으로 로드되므로 .db 파일 안에 인덱스가 포함되고, 별도 인덱스 파일을 관리할 필요가 없다.

임베딩 모델은 bge-m3-mlx-fp16을 사용한다. oMLX로 로컬 실행, 1024차원, 한국어·영어 동시 지원, 추가 비용 없음. 다국어 텍스트가 섞이는 환경에서는 언어 전환마다 모델을 바꾸는 구조보다 이중언어 단일 모델이 검색 일관성을 유지하는 데 유리하다.


U-tag 변증법: 신뢰도를 어떻게 진화시키는가

"사용자가 커피를 좋아한다"는 기록이 있을 때, 이것이 1회 관찰인지 반복 검증된 사실인지를 구분해야 응답 품질이 유지된다. 단순 누적 카운터가 아니라 단계적 신뢰도 진화 모델이 필요하다.

U-tag(User Tag)는 다음 4단계 구조로 신뢰도를 관리한다:

observation  →  hypothesis  →  verified  →  user_md_review
(1x 관찰)       (2x 관찰)       (3x, 다른 날)   (5x 또는 3x+14일)

단계별 신뢰도 계산:

confidence = 0.1 * count  # 최대 0.1~0.3 수준

confidence = min(0.3 + 0.1 * count, 0.6)

confidence = min(0.5 + 0.1 * count, 0.95)

핵심 조건은 "다른 날"이다. 같은 세션·같은 맥락에서 3회 언급된 것은 1회 관찰과 정보량이 유사하다. 날짜가 다를 때에만 독립 증거로 간주하여 신뢰도 왜곡을 막는다.

의견(opinion) 타입 기억은 시간에 따라 신뢰도가 감쇠한다:

confidence -= 0.02 * days_since_last_update

사람의 의견은 변동한다. 감쇠 없이 고정 신뢰도를 유지하면 과거 의견이 현재 판단에 계속 개입한다.


편향 차단 규칙 — 수집 단계에서 막는다

메모리 엔진이 편향된 기록을 쌓으면 에이전트 판단 자체가 오염된다. 수집 레이어에 하드코딩된 규칙은 다음과 같다:

  • 감정적 발언 수집 금지 — "짜증난다", "오늘 기분 별로다" 등 일시적 감정 상태는 메모리에 저장하지 않는다
  • 농담 기반 해석 금지 — 농담·과장 발언을 사실로 등록하지 않는다
  • 추측 기반 해석 금지 — 행동에서 심리를 추론하지 않는다. "이걸 좋아하는 것 같다"는 관찰이지 "좋아한다"가 아니다
  • 민감 정보 추론 금지 — 관찰된 행동에서 건강·재정·관계 상태를 추론하지 않는다
  • 성격 라벨링 금지 — "내향적", "완벽주의" 같은 성격 라벨을 AI가 부여하지 않는다. 사용자가 직접 정의한 경우에만 등록

규칙을 프롬프트(CLAUDE.md)에만 두면 컨텍스트 상황에 따라 우회되거나 무시될 수 있다. 수집 레이어 코드에 하드코딩하면 기록 단계 자체에서 거부되므로 일관성이 유지된다.


마이그레이션 결과

기존 텍스트 파일 기반 메모리 40개를 memcore 스키마로 이관한 결과다.

항목 수량
원본 파일 40개
큐레이션된 메모리 236개
엔티티 4개
토픽 15개
토픽 링크 514개
마이그레이션 오류 0개

자유형식 텍스트에서 236개 메모리를 추출했다. 파싱 과정에서 중복과 노이즈를 걸러내는 것이 작업량의 대부분이다. 엔티티 4개·토픽 15개·토픽 링크 514개로 구조화되면서, 이후 토픽 매칭 검색(가중치 2.0 계층)의 히트율이 유효해진다.


현재 규모

항목 수치
모듈 수 25개
총 코드 줄 수 ~6,464줄
초기 버전 (18모듈) ~3,300줄
DB 테이블 22개
테스트 31개 통과, 0개 실패
테스트 파일 6개, ~450줄

초기 18모듈 3,300줄에서 25모듈 6,464줄로 성장했다. 신규 기능보다 안정화와 엣지케이스 처리에서 코드량이 더 늘었다.

테스트는 메모리 엔진에서 특히 중요하다. 신뢰도 계산이 0.01 차이로 틀려도 즉각 증상이 드러나지 않고, 시간이 지나며 판단 품질이 점진적으로 나빠진다. 테스트 커버리지가 확보되지 않으면 리팩터링 자체가 불가능해진다.


한계와 적용 범위

적용 가능한 영역 - 단일 호스트에서 돌아가는 단독 에이전트 메모리 - 외부 서비스 의존을 배제해야 하는 오프라인/에어갭 환경 - .db 파일 단위로 백업·이식이 필요한 워크로드 - 한국어·영어 혼용 텍스트에서 의미 기반 검색이 필요한 경우

한계 - 대규모 동시 쓰기에는 취약하다. SQLite는 파일 잠금 기반이며, 멀티 라이터 워크로드에는 부적합 - 벡터 인덱스가 전체 스캔 기반이므로, 수백만 건 이상으로 커지면 hnsw 계열 별도 인덱스가 유리 - U-tag 변증법은 "다른 날" 카운트를 신뢰 기반으로 삼기 때문에, 시간 조작이 있는 데이터 소스에서는 왜곡 가능 - 편향 차단 규칙은 수집 레이어에서 거르지만, 이미 기록된 레거시 데이터는 별도 정리 작업이 필요


열린 질문

  • U-tag의 감쇠 계수(0.02/day)는 경험적 설정이다. 도메인별로 감쇠 속도를 다르게 가져가야 할 근거가 쌓이면 테이블 기반 파라미터화가 필요하다.
  • 토픽 매칭 가중치(2.0)와 LIKE 폴백 가중치(5.0)가 같은 쿼리에서 충돌할 때 최종 랭킹 규칙은 현재 단순 합산이다. 쿼리 유형별 가중치 재조정이 필요한지는 더 많은 쿼리 로그가 쌓여야 판단 가능하다.
  • sqlite-vec 확장은 현재 경량이지만, 벡터 건수가 늘면서 ann(approximate nearest neighbor) 전환 시점이 언제인지는 추가 측정이 필요하다.

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

댓글

이 블로그의 인기 게시물

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

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

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