기억하는 에이전트는 어떻게 만드는가 — 추출·저장·회수·supersede 아키텍처 비교

2026-09-19 · 약 80분
에이전트 메모리LLM 에이전트LettaMem0ZepRAG지식 그래프
컨텍스트 창이 커져도 메모리가 필요한 이유, 추출에서 망각까지의 공통 파이프라인, MemGPT/Letta·Mem0·Zep/Graphiti·LangMem·A-MEM 의 설계 비교, 벡터·그래프·파일 저장 계층의 선택, 모순을 다루는 supersede 의미론과 메모리 포이즈닝, 언제 얼마나 어디에 주입할지, LoCoMo·LongMemEval 과 벤더 수치 논쟁, 단계별 도입 가이드를 8개 섹션으로 정리했다.

컨텍스트 창이 커져도 메모리가 필요한 이유

1M 토큰 창이 표준 단가로 풀린 2026년에도 이력을 통째로 넣는 방식은 비용·지연·정확도에서 한계가 있고, 세션 간 지속·사실 갱신·삭제는 창 크기로 풀리지 않는다. 메모리를 RAG 와 가르는 것은 에이전트가 런타임에 직접 쓰는 경로이며, CoALA 의 네 기억 분류와 ChatGPT·Claude·Gemini 의 수렴 방향이 그 설계 기준을 준다.

핵심 요점

  • Anthropic 은 Claude 4.6 이후 모델에 1M 창을 표준 단가로 제공하지만 과금은 선형이고 캐시 수명은 5분 또는 1시간이라, 다음 날 세션에 이력을 되돌리는 시나리오에서는 매번 전액을 다시 낸다.
  • Lost in the Middle, NoLiMa(13개 중 11개 모델이 32K 에서 기준선의 50% 아래), Chroma Context Rot(18개 모델, 약 300토큰 집중 프롬프트가 약 113K 전체 프롬프트를 앞섬)은 관련 사실만 골라 주는 쪽이 더 정확하다는 같은 결론을 가리킨다.
  • 반대로 Mem0 논문 자체 표에서 약 26K 토큰 대화는 full-context(72.90%)가 Mem0(66.88%)보다 정확해, 이력이 짧고 지연 예산이 넉넉하면 메모리 없이 가는 편이 낫다.
  • 메모리와 RAG 의 차이는 쓰기 경로이며, 이 때문에 추출·쓰기 시점·충돌 시 supersede·검증과 삭제라는 네 가지 결정이 새로 생긴다.
  • CoALA 는 기억을 작업·에피소드·의미·절차로 나누고 절차 기억 쓰기가 가장 위험하다고 경고하므로, 에피소드는 추가 전용 원본으로 두고 절차 기억의 자동 쓰기는 리뷰 게이트 뒤에 둔다.
  • Mem0·Zep·Letta 의 LoCoMo 수치와 OpenAI·Anthropic 의 메모리 개선 수치는 모두 이해당사자가 측정한 것이고 상호 반박이 이어졌으므로 자기 워크로드로 다시 재야 한다.

1M 토큰 창이 표준이 된 뒤에도 남는 문제

2026년 9월 현재 1M 토큰 창은 특별한 옵션이 아니다. Anthropic 가격 문서는 Claude 4.6 이후 모델이 1M 창 전체를 표준 단가로 제공하며 "900k 토큰 요청도 9k 토큰 요청과 같은 토큰당 단가"라고 적는다(가격 문서). 그렇다면 대화 이력과 도구 실행 로그를 전부 창에 넣으면 되지 않는가. 공개된 근거는 세 방향에서 아니라고 답한다.

비용과 지연: 단가가 같아도 매 턴 전액을 다시 낸다

  • 선형 과금. 같은 문서 기준 Claude Opus 5 입력은 $5/MTok 이다. 누적 이력 900K 토큰을 매 호출에 실으면 입력만 호출당 $4.5, 캐시 적중(0.1배)이어도 $0.45 다. 게다가 캐시 수명은 5분 또는 1시간이다. "어제 세션의 사실을 오늘 되돌려준다"는 메모리의 핵심 시나리오에서는 캐시가 이미 만료돼 쓰기 단가(1.25배)를 다시 낸다.
  • 구간 할증. Gemini API 가격표는 Gemini 3.1 Pro Preview 입력을 200K 토큰 이하 $2.00, 초과 $4.00 로, 출력을 $12.00 / $18.00 로 나눈다(가격표).
  • 첫 토큰 지연. Anthropic 이 프롬프트 캐싱 소개 글에 실은 표에서 100K 토큰 프롬프트의 지연은 캐시 없이 11.5초, 적중 시 2.4초다. Mem0 논문(저자 측정, GPT-4o-mini, LoCoMo)에서도 대화 전체를 넣는 full-context 기준선의 p95 지연은 17.117초, 메모리 경로는 1.440초였다.

정확도: lost-in-the-middle 에서 context rot 까지

근거 측정 주체 확인된 내용
Lost in the Middle (Liu 외, TACL) 학계 관련 정보가 입력의 처음·끝에 있을 때 가장 잘 쓰고, 중간에 있으면 크게 떨어진다. 긴 컨텍스트 전용 모델도 같다
NoLiMa (ICML 2025) 학계·기업 연구 질문과 정답 문장의 어휘 중복을 없애자, 128K 이상을 내세운 13개 모델 중 11개가 32K 에서 짧은 입력 기준선의 50% 아래로 하락. GPT-4o 는 99.3% → 69.7%
Context Rot (Chroma, 2025-07-14) 벡터 DB 벤더 18개 모델 모두 입력이 길수록 불안정. LongMemEval 에서 관련 부분만 담은 약 300토큰 프롬프트가 약 113K 토큰 전체 프롬프트보다 모든 모델군에서 유의하게 높았다. 방해 문장 하나로도 하락
LongMemEval (ICLR 2025) 학계 500문항·5능력(정보 추출, 다중 세션 추론, 시간 추론, 지식 갱신, 기권). 상용 어시스턴트와 롱컨텍스트 LLM 이 지속 대화에서 30% 정확도 하락

Chroma 는 검색 인프라를 파는 회사라 이해관계가 있지만 결론은 학계 결과와 같은 방향이다. 특히 300토큰 대 113K 토큰 비교는 메모리의 논거 그 자체다. 같은 질문이라도 관련 사실만 골라 주면 더 잘 답한다.

모델은 나아지고 있다. Anthropic 은 Opus 4.6 발표(2026-02-05)에서 MRCR v2 8-needle 1M 변형 점수를 Opus 4.6 76%, Sonnet 4.5 18.5% 로 공개했다(자사 측정). 뒤집어 읽으면 최상위 모델도 1M 에서 바늘 8개를 온전히 찾지 못하며, 같은 글이 context rot 을 "흔한 불만"이라고 부른다. 2026년 6월 arXiv 논문(2606.29718)은 장기 검색 에이전트에서 컨텍스트가 길수록 탐색을 일찍 포기하는 "조기 종료"가 늘어난다고 보고했다.

반대 방향 근거도 있다. Mem0 논문의 표에서 full-context 의 LLM-as-a-Judge 점수는 72.90% 로, Mem0(66.88%)와 그래프 변형(68.44%)보다 높다. LoCoMo 대화는 평균 약 26K 토큰이라 창에 그대로 들어가기 때문이다. 이력이 창의 작은 일부이고 지연 예산이 넉넉하면 통째로 넣는 쪽이 가장 정확하다.

창이 아무리 커도 못 하는 일

  • 세션·사용자 경계 넘기. 컨텍스트는 요청이 끝나면 사라진다. 다음 세션에 무엇을 실을지는 누군가 결정해야 한다.
  • 갱신. "서울에 산다"와 "부산으로 이사했다"가 이력에 함께 있으면 모델은 호출마다 어느 쪽이 현재인지 다시 판정한다. LongMemEval 이 지식 갱신을 독립 능력으로 재는 이유다.
  • 삭제와 감사. 이력 전체를 주입하는 구조로는 "그건 잊어줘"를 이행할 수 없고, 무엇을 기억하는지 보여줄 목록도 없다.
  • 압축 손실. 창이 차면 요약(compaction)으로 넘긴다. Anthropic 메모리 도구 문서는 둘을 함께 쓰라고 권하면서 메모리의 몫을 "요약에서 살아남아야 하는 정보의 보존"으로 적는다.

메모리는 RAG 가 아니다: 쓰기 경로가 있다

검색해서 프롬프트에 붙이는 읽기 절반은 같다. 차이는 쓰기다. RAG 의 코퍼스는 사람이 쓴 문서를 오프라인 파이프라인이 색인한 것이고, 메모리의 코퍼스는 에이전트가 런타임에 자기 대화와 도구 실행에서 직접 만들어낸다.

축 RAG 에이전트 메모리
원천 사람이 작성한 문서 대화·도구 실행 궤적
쓰는 주체·시점 배치 인덱서, 오프라인 에이전트 또는 백그라운드 추출기, 런타임
갱신 단위 문서 재색인 사실 단위 추가·수정·무효화
격리 컬렉션·테넌트 사용자·에이전트·세션·프로젝트
고유한 실패 검색 누락 잘못 쓴 기억의 영구 오염, 낡은 사실, 주입 공격의 지속화
평가 검색 재현율 지식 갱신·시간 추론·기권

쓰기 경로가 생기면 RAG 에 없던 결정 네 가지가 따라온다. 무엇을 쓸지(추출), 언제 쓸지(응답 경로 안인가 백그라운드인가), 기존 기억과 충돌하면 어떻게 할지(supersede), 누가 검증하고 지울지다.

쓰는 시점은 두 갈래다. 하나는 에이전트가 도구 호출로 직접 쓰는 방식이다. Claude API 의 메모리 도구는 선언이 한 줄이고, 모델은 /memories 아래 파일을 view·create·str_replace·insert·delete·rename 여섯 명령으로 다룬다.

{"type": "memory_20250818", "name": "memory"}

이 도구는 클라이언트 측에서 실행된다. 저장소는 애플리케이션이 소유하고, /memories/../../secrets.env 같은 경로 순회 방어도 구현자 책임이다. 다른 하나는 응답 경로 밖에서 대화 이력을 종합하는 백그라운드 방식이며, 아래에서 보듯 ChatGPT 가 이쪽으로 갔다.

CoALA 의 네 기억으로 설계를 나눈다

CoALA(Sumers·Yao·Narasimhan·Griffiths, TMLR 게재본 2024-03)는 언어 에이전트를 기억, 행동 공간, 의사결정 루프로 분해하고, 학습을 "장기 기억에 정보를 쓰는 것"으로 정의한다.

기억 CoALA 정의 프로덕션에서의 실체 쓰기 위험
작업 현재 결정 주기의 활성 정보를 심볼 변수로 유지 컨텍스트 창, 스크래치패드, 상태 객체 낮음(휘발)
에피소드 이전 결정 주기의 경험 대화 로그, 도구 호출 궤적, 세션 요약 낮음(추가 전용)
의미 세계와 자신에 대한 지식 추출된 사실, 엔티티·관계 그래프, 사용자 프로필 중간(오추출·낡음)
절차 LLM 가중치(암묵)와 에이전트 코드(명시) 시스템 프롬프트, 스킬, 도구 정의, 규칙 파일 높음

CoALA 는 절차 기억 쓰기가 에피소드·의미 기억 쓰기보다 "훨씬 위험하다"고 경고한다. 버그를 심거나 설계자의 의도를 뒤엎을 수 있어서다. 여기서 실무 규칙 세 개가 나온다.

  1. 에피소드는 추가 전용 원본으로 보관한다. 추출기를 바꾸면 다시 돌릴 수 있어야 한다.
  2. 의미 기억은 에피소드에서 파생하고, 사실마다 출처 포인터를 남긴다.
  3. 절차 기억의 자동 쓰기는 사람 리뷰나 테스트 게이트 뒤에 둔다.

시중 메모리 제품의 대부분은 "에피소드 → 의미" 변환기다. 제품을 비교할 때는 네 칸 중 어디를 채우는지부터 묻는다.

소비자 제품은 같은 구조로 수렴했다 (2026-09 기준)

  • ChatGPT. 2024년 명시적 saved memories 로 시작해, 2025-04-10 과거 대화 전체를 참조하는 기능을 Plus·Pro 에 추가했다(TechCrunch 보도, EU·영국 등 제외). 2026-06-04 에는 백그라운드에서 대화 이력을 종합하는 "Dreaming" 구조를 미국 Plus·Pro 부터 배포했다. 편집 가능한 메모리 요약을 노출하고, "7월에 싱가포르에 갈 예정"을 여행 뒤 "2026년 7월에 다녀왔다"로 고쳐 쓴다. OpenAI 원문은 접근 차단으로 직접 열지 못했고, ETIH 보도(2026-06-08)에 따르면 OpenAI 자체 평가에서 사실 회상 41.5% → 82.8%, 선호 준수 31.4% → 71.3%, 시간 민감 항목 9.4% → 52.2% → 75.1%(2024→2025→2026)다. 제3자 재현은 확인하지 못했다.
  • Claude. 2025-09-11 Team·Enterprise, 2025-10-23 Pro·Max 로 확대됐고, 2026-03-02 무료 플랜 개방과 타사 메모리 가져오기 도구가 보도됐다(MacRumors·9to5Mac). 프로젝트마다 메모리가 분리되고, 사용자가 메모리 요약을 보고 고치며, incognito 대화는 기억에 남지 않는다. API 쪽 메모리 도구에 대해 Anthropic 은 컨텍스트 관리 발표(2025-09-29)에서 메모리 도구와 컨텍스트 편집을 함께 쓰면 기준선 대비 39%(편집 단독 29%) 개선, 100턴 웹 검색 평가에서 토큰 84% 절감이라고 밝혔다. 모두 내부 평가다.
  • Gemini. 2025-08-13 발표로 과거 대화 기반 개인화가 기본 켜짐으로 들어갔고, Temporary Chat 은 개인화·학습에 쓰이지 않되 최대 72시간 보관된다. 현재 도움말은 설정 위치를 Personal Intelligence 의 Memory 토글로 안내하며 18세 이상 개인 계정이 대상이다.

세 제품의 공통점이 곧 자체 구현의 최소 요구사항이다. ① 명시 저장에서 백그라운드 추출로 옮겨 간다. ② 기억을 사람이 읽고 고칠 수 있는 형태로 노출한다. ③ 프로젝트·작업 단위로 스코프를 나눈다. ④ 기억하지 않는 모드를 둔다.

벤더 수치를 읽는 법

이 분야의 벤치마크는 대부분 이해당사자가 쟀다. Mem0 논문(2025-04)은 OpenAI 메모리 대비 LLM-as-a-Judge 26% 상대 향상, full-context 대비 p95 지연 91% 감소와 90% 이상의 토큰 절감을 주장한다. Zep 은 반박 글(2025-05-06, 이후 수정)에서 Mem0 가 Zep 을 잘못 구성해 쟀다며(두 화자 모두 user 역할, 타임스탬프 전용 필드 미사용, 순차 검색) Zep 75.14% 대 Mem0 약 68% 를 제시했다. 다만 Zep 이 처음 내건 수치는 84% 였고, Mem0 CTO 가 GitHub 이슈(2025-05-08)에서 5번(adversarial) 범주 계산 오류를 들어 58.44% 로 재계산한 바 있다. Letta 는 2025-08-12 글에서 파일 도구만 쥔 에이전트가 GPT-4o-mini 로 74.0% 를 냈다고 보고했다. LoCoMo 자체도 대화가 16K~26K 토큰으로 짧고 지식 갱신 문항이 없다는 비판을 받는다. 전말은 평가 섹션에서 다루고 여기서는 결론만 적는다. 자기 워크로드로 다시 잰다.

도입 판단 체크리스트

  • 사실이 세션을 넘어 재사용되는가. 아니면 컨텍스트 편집과 압축으로 충분하다.
  • 사실이 바뀌는가(주소, 설정값, 진행 상태). 바뀐다면 supersede 가 필수다.
  • 누적 이력이 창의 몇 % 인가. LoCoMo 급(약 26K)이면 full-context 가 정확도에서 앞선다.
  • p95 지연 예산이 몇 초인가. 위 Anthropic 표에서 100K 토큰 프롬프트는 캐시 없이 11.5초였다.
  • 삭제·열람 요구(개인정보, 감사)가 있는가.
  • 사용자·프로젝트 사이의 격리가 필요한가.

이후 섹션 지도

이후 섹션은 다섯 갈래다.

  • 파이프라인: 추출, 정규화·중복 제거, 저장·회수·주입, 갱신·망각의 단계와 쓰기 시점(hot path, 백그라운드, 세션 종료).
  • 저장소: 벡터·그래프·파일 중 무엇을 어떤 질의 때문에 고르는지, 하이브리드 검색, 파일 기반 메모리가 강한 이유.
  • 갱신(supersede): bi-temporal 모델, 출처와 신뢰도, 망각과 삭제 요구, 메모리 포이즈닝.
  • 시스템 비교: Letta·Mem0·Zep 을 "누가, 언제, 무엇을 덮어쓰는가"라는 축으로 놓고 LangMem·A-MEM 까지 본다.
  • 평가: 벤더 수치 논쟁의 전말, full-context 기준선을 먼저 돌려야 하는 이유, 자체 평가 셋과 운영 지표.

공통 파이프라인 — 추출에서 망각까지

에이전트 메모리는 구현이 달라도 추출→정규화·중복 제거→저장→회수→주입→갱신·망각의 여섯 단계를 거치며, 품질 사고의 대부분은 회수가 아니라 추출과 갱신 단계에서 생긴다. 쓰기 시점(hot path·백그라운드·세션 종료 통합)은 지연과 '방금 말한 것을 바로 읽을 수 있는가' 사이의 선택이고, 단계별 지표를 따로 재야 원인을 가릴 수 있다.

핵심 요점

  • Mem0 논문은 요약 S와 최근 10개 메시지를 문맥으로 사실을 추출한 뒤 유사 기억 10개와 비교해 ADD/UPDATE/DELETE/NOOP 중 하나를 LLM이 고르게 하고, Zep은 직전 4개 메시지 문맥과 reflexion식 재검토, 임베딩+전문 검색 기반 개체 해소를 쓴다.
  • 추출 프롬프트는 허용 유형·음성 예시·빈 결과·근거 인용을 강제해야 하며, 과잉 추출·환각 기억·맥락 손실이 대표 실패이고 HaluMem은 환각이 추출·갱신 단계에서 생겨 질의응답으로 전파된다고 보고한다.
  • hot path 쓰기는 즉시 읽히지만 턴 지연을 늘리고, 백그라운드 쓰기는 지연이 없고 재현율이 높은 대신 반영 전 빈 구간이 생기므로 디바운스(LangMem은 운영값으로 30~60분 예시)·멱등 키·스코프당 단일 작성자가 필요하다.
  • 같은 세션 안의 즉시 일관성이 필요하면 세션 범위 메모만 동기로 쓰고 장기 기억은 세션 종료 후 비동기로 통합하는 2단 구조가 실용적 절충이다.
  • 모순된 사실은 삭제보다 무효화가 안전하다: Zep은 옛 엣지의 t_invalid를 새 엣지의 t_valid로 설정해 이력을 보존하고, 현실 유효 구간과 시스템 기록 시각을 분리해 둔다.
  • LOCOMO 수치(Mem0 66.88%·full-context 72.90%, Zep 재측정 75.14%, Letta 파일시스템 74.0%)는 모두 각 벤더의 자가 측정이고 서로 반박이 오갔으므로, 제품 선택보다 자기 트래픽의 단계별 지표 측정이 우선이다.

여섯 단계를 한 장으로

Letta·Mem0·Zep는 저장 구조가 서로 다르지만, 대화와 도구 실행 결과가 "다음 세션의 컨텍스트"로 돌아오기까지 거치는 단계는 같다. 아래 표는 2026년 9월 19일 기준 공개 논문·공식 문서에서 확인되는 구현을 단계별로 정리한 것이다.

단계 답해야 할 질문 공개된 구현 예 관측 지점
1. 추출 이 턴에 기억할 가치가 있는 게 있는가 Mem0: 요약 S + 최근 m=10개 메시지 + 현재 턴 쌍으로 프롬프트 구성. Zep(Graphiti): 직전 n=4개 메시지를 문맥으로 개체 추출 + reflexion식 재검토 턴당 추출 건수, 빈 추출 비율, 근거 인용 누락률
2. 정규화·중복 제거 이미 아는 사실인가, 같은 개체인가 Mem0: 유사 기억 s=10개를 꺼내 LLM이 ADD/UPDATE/DELETE/NOOP 선택. Zep: 1024차원 임베딩 + 전문 검색 → LLM 개체 해소 연산 분포(ADD:UPDATE:DELETE:NOOP), 병합 오류 표본
3. 저장 어떤 단위·범위로 둘 것인가 LangMem의 collection(문서 누적) vs profile(단일 문서 갱신), Zep의 4개 타임스탬프 스코프별 건수·증가율, 쓰기 지연
4. 회수 지금 질문에 무엇이 관련 있는가 Zep: 코사인 + BM25 + 그래프 BFS, RRF·MMR·cross-encoder 재정렬 회수 건수, 적중률, 무효 기억 적중률
5. 주입 어디에, 얼마나, 어떤 우선순위로 Letta 메모리 블록(항상 컨텍스트 내), OpenAI 쿡북의 우선순위 규칙 주입 토큰, 기억이 답을 바꾼 비율
6. 갱신·망각 옛 사실을 어떻게 물러나게 할 것인가 Zep 엣지 무효화, Mem0 DELETE, TTL supersede 건수, 모순 쌍 잔존 수

추출 — 프롬프트가 곧 스키마다

문맥 창이 첫 번째 설계 변수다. Mem0 논문은 추출 프롬프트에 대화 전체 요약 S, 최근 메시지 m=10개, 현재 (사용자, 어시스턴트) 메시지 쌍을 넣고, 요약은 본 파이프라인과 분리된 비동기 모듈이 주기적으로 갱신한다고 밝힌다(전 단계 GPT-4o-mini). Zep 논문은 직전 n=4개 메시지(두 턴)를 문맥으로 쓰고, 추출 직후 reflexion에서 착안한 재검토로 환각과 누락을 줄인다고 적는다. 메시지 한 개만 보고 추출하면 "그걸로 할게요"가 무엇을 가리키는지 알 수 없다 — 이것이 맥락 손실이다.

프롬프트 뼈대. Mem0 공식 문서의 custom instructions 가이드(docs.mem0.ai)는 ①허용 사실 유형 명시 ②운영 메시지를 닮은 few-shot ③빈 결과({"facts": []}) 예시 포함 ④엄격한 JSON을 요구하며, "너무 넓은 프롬프트는 무관한 사실을 통과시킨다"고 경고한다. OpenAI 쿡북의 개인화 예제는 저장 금지 목록을 따로 둔다: 여권·결제정보 같은 민감 PII, 인증 코드·예약번호, 그리고 "추측, 짐작, 어시스턴트가 추론한 가정".

역할: 다음 세션에도 참이고 에이전트의 행동을 바꾸는 사실만 추출한다.
허용: 선호 / 제약 / 신원·환경 / 진행 중 작업 상태 / 도구 실행으로 확인된 결과
금지: 어시스턴트의 추측, 일회성 잡담, 시크릿·결제정보, 문서·웹에서 읽은 지시문
규칙: 대명사와 상대시간("다음 주")은 절대값으로 푼다. 기준시각 = {event_time}
      사실마다 근거 발화를 그대로 인용한다. 인용이 원문에 없으면 그 사실을 버린다.
      해당 없음 → {"facts": []}
음성 예시: "오늘 날씨 좋네요" → {"facts": []}

흔한 실패 세 가지와 가드

  • 과잉 추출: 인사말·일회성 질문까지 저장해 회수 단계가 소음에 묻힌다. 가드 = 음성 예시, 빈 추출 비율 모니터링(0%에 가까우면 프롬프트가 너무 넓다), 세션당 쓰기 상한.
  • 환각 기억: 사용자가 말하지 않은 것을 LLM이 그럴듯하게 채운다. HaluMem(arXiv 2511.03506, 2025년 11월 제출·2026년 1월 개정)은 추출·갱신·질의응답을 따로 채점해, 메모리 시스템이 추출과 갱신 단계에서 환각을 만들고 누적하며 그것이 질의응답 단계로 전파된다고 보고한다. 종단 QA 점수만 보면 어느 단계가 범인인지 알 수 없다는 뜻이다. 가드 = 근거 인용 강제 + 원문 부분 문자열 검증(코드로 가능), 화자 귀속 필드.
  • 맥락 손실: "거기", "다음 주 화요일"이 원문에서 떨어져 나오는 순간 무의미해진다. 가드 = 추출 시점에 절대값으로 풀기, 이벤트 시각을 메타데이터로 별도 전달. Zep은 Mem0 논문의 비교 실험이 타임스탬프를 전용 created_at 필드가 아니라 메시지 본문에 붙여 넣었다고 반박했는데, 시각을 본문에 섞느냐 필드로 주느냐가 결과를 바꾼다는 실례다.

도구 실행 결과는 대화 발화와 신뢰 등급이 다르다. API 응답으로 확인된 사실과 사용자가 주장한 사실, 웹페이지에서 읽은 문장을 같은 fact로 저장하면 나중에 구분할 방법이 없으므로 출처 유형을 레코드에 남긴다.

정규화·중복 제거 — 같은 말을 두 번 저장하지 않기

Mem0 논문의 갱신 단계는 추출된 사실마다 의미상 유사한 기존 기억 s=10개를 꺼내 LLM에 함께 보여 주고, 함수 호출로 네 연산 중 하나를 고르게 한다: 동등한 기억이 없으면 ADD, 보완 정보면 UPDATE, 모순되면 DELETE, 바꿀 게 없으면 NOOP. 그래프 변형(Mem0^g)은 모순된 관계를 지우지 않고 무효로 표시한다. Zep은 개체를 임베딩 + 전문 검색으로 후보를 좁힌 뒤 LLM이 동일 개체인지 판정하고, 엣지 중복 검사는 같은 개체 쌍 사이의 엣지로만 범위를 제한해 비용을 줄인다.

이 방식이 유일한 답은 아니다. Mem0는 2026년 4월 알고리즘을 "single-pass ADD-only extraction"으로 설명한다(Mem0 블로그, 2026-09-18 갱신본). 쓰기 경로에서 LLM 판정을 덜어내고 회수 쪽(의미·키워드·개체 세 신호 융합)으로 무게를 옮긴 것인데, 같은 글은 오래된 기억이 "확신에 찬 오답"이 되는 staleness를 미해결 문제로 꼽는다. 쓰기에서 모순을 정리하지 않으면 그 부담은 회수·주입 단계로 넘어간다.

저장·회수·주입 — 레코드에 반드시 남길 것

{
  "scope": {"user_id": "u_123", "agent_id": "support"},
  "fact": "결제 통화를 KRW로 변경함",
  "kind": "preference",
  "source": {"session_id": "s_88", "turn": 14, "speaker": "user",
             "type": "utterance", "quote": "이제 원화로 결제할게요"},
  "valid_at": "2026-09-01", "invalid_at": null,
  "created_at": "2026-09-01T03:12:09Z", "superseded_by": null,
  "extractor": "extract-prompt@v7"
}
  • 시간 축은 둘이다. Zep은 사실이 현실에서 참이었던 구간(t_valid, t_invalid)과 시스템이 그것을 기록·만료한 시각(t′created, t′expired)을 따로 둔다. "언제 알았나"와 "언제부터 참인가"를 섞으면 소급 정정을 표현할 수 없다.
  • extractor 버전을 남겨야 프롬프트를 바꾼 뒤 품질이 떨어졌을 때 해당 버전이 쓴 기억만 골라 재처리할 수 있다.
  • 회수는 벡터 단독보다 혼합이 표준이다. LongMemEval 논문(ICLR 2025)은 색인 키에 추출 사실을 덧붙이는 fact-augmented key expansion과, 질의의 시간 표현으로 검색 범위를 좁히는 time-aware query expansion을 제안했다.
  • 주입에는 우선순위 규칙이 필요하다. OpenAI 쿡북은 "최신 사용자 입력 → 세션 메모 → 전역 기억" 순서를 시스템 프롬프트에 명시하고, 컨텍스트가 잘린 뒤에는 세션 메모를 다시 주입한다. 기억이 현재 발화를 이기게 두면 개인화가 아니라 고집이 된다.

쓰기 시점 — hot path, 백그라운드, 세션 종료

hot path(동기) 백그라운드(비동기) 세션 종료 시 통합
방식 에이전트가 턴 도중 메모리 도구를 호출 별도 프로세스·에이전트가 대화를 되짚어 추출 세션 메모를 모아 한 번에 병합
지연 턴 응답에 LLM 호출이 추가됨 사용자 경로 영향 없음 영향 없음
일관성 쓰자마자 읽힌다 반영 전까지 빈 구간 존재 다음 세션이 통합보다 먼저 시작될 수 있음
추출 품질 과제 수행과 주의가 경쟁 전체 맥락을 보고 추출 전체 맥락 + 중복·충돌 일괄 해소
예 MemGPT식 자기 편집, Anthropic 메모리 도구 LangMem 백그라운드, Letta sleep-time, Mem0 비동기 add OpenAI 쿡북 consolidation

LangMem 문서는 hot path가 "체감되는 지연"을 더하고 에이전트가 사용자 요구를 충족하는 데 "장애물 하나를 더 얹는다"고, 백그라운드는 반영이 늦는 대신 추출 재현율이 높다고 정리한다. 백그라운드에는 디바운스가 따라붙는다. LangMem의 ReflectionExecutor는 after_seconds만큼 기다렸다가 처리하고, 그 사이 새 메시지가 오면 대기 작업을 취소하고 새 메시지를 포함해 다시 예약한다. 문서가 드는 이유는 연속 메시지에서의 중복 작업, 대화 중간의 불완전한 맥락, 불필요한 토큰 소비이며, 운영 환경 지연값으로 30~60분을 예시한다. 서버리스에서는 호출 사이에 로컬 스레드가 사라지므로 원격 실행기를 쓰라고 덧붙인다.

Letta의 sleep-time 에이전트(2025년 4월 발표)는 한 걸음 더 나가 역할을 분리한다. 주 에이전트에는 코어 메모리 편집 도구를 주지 않고, 메모리 블록을 공유하는 별도 에이전트가 백그라운드에서 블록을 고쳐 쓴다. 주 에이전트는 편집 완료를 기다리지 않고 언제든 읽는다. 공식 문서상 트리거는 일정 스텝 수 경과 또는 컨텍스트 압축 시점이고, 빈도를 높일수록 토큰을 더 쓴다. Mem0 블로그에 따르면 v1.0.0부터 async_mode=True가 기본값이다.

결정 기준

  1. 같은 세션 안에서 방금 말한 것을 바로 써야 하는가(주소 변경 직후 주문). 그렇다면 장기 저장은 비동기로 두더라도 세션 범위 메모는 동기로 쓴다. 쿡북의 "실행 중 증류 → 종료 후 통합" 2단 구조가 이 절충이다.
  2. 턴 지연 예산이 빡빡한가(음성·실시간). 그렇다면 hot path 쓰기는 배제한다.
  3. 비동기로 간다면 세 가지를 직접 챙겨야 한다: 스코프당 단일 작성자(같은 사용자에 대한 두 작업이 동시에 ADD하지 않게), 멱등 키(session_id + 턴 범위), 그리고 "세션 종료"의 정의(무활동 타임아웃 + 내구성 있는 큐 — 프로세스가 죽으면 통합도 사라진다).

갱신·망각 — 지우지 말고 물러나게

Zep은 시간상 겹치는 모순을 발견하면 옛 엣지를 지우지 않고, 옛 엣지의 t_invalid를 새 엣지의 t_valid로 설정한다. 이력이 남으므로 "3월에는 어디 살았지?"에 답할 수 있고 오판을 되돌릴 수 있다. 물리 삭제(DELETE)는 단순하지만, LLM이 모순이 아닌 것을 모순으로 오판했을 때 복구할 근거가 없다. 망각 쪽은 단순한 규칙이 공개 자료의 주류다. OpenAI 쿡북은 "망각은 버그가 아니라 필수"라며 세션 한정 메모 폐기와 TTL을 권하고, Anthropic 메모리 도구 문서는 오래 접근되지 않은 메모리 파일의 주기적 삭제와 파일 크기 상한을 보안 고려사항으로 둔다. 개인정보 삭제 요청은 무효화로는 부족하고 물리 삭제 경로가 따로 있어야 한다.

벤더 수치를 읽는 법

파이프라인 단계별 비용·지연을 말할 때 자주 인용되는 수치는 모두 자가 측정이다.

  • Mem0 논문(2025년 4월, Mem0 팀 측정, LOCOMO·LLM 판정): Mem0 66.88%, Mem0^g 68.44%, Zep 65.99%, 전체 대화를 그대로 넣은 full-context 72.90%. p95 지연은 Mem0 1.440초 대 full-context 17.117초. 주목할 점은 자기 표에서도 full-context가 정확도 1위라는 것이다. 이 벤치마크에서 메모리 파이프라인이 산 것은 정확도가 아니라 지연과 토큰이다.
  • Zep의 반박(2025-05-06, Zep 팀 측정): Mem0가 Zep을 잘못 구성했다고 주장했다(두 화자 모두에 user 역할 부여, 타임스탬프를 본문에 덧붙임, 검색을 순차 실행해 지연 과대 계상). 재측정값은 75.14% ± 0.17인데, 이 글 자체도 최초 계산 오류를 정정한 이력이 있다. 같은 글은 LOCOMO 대화가 평균 1.6만~2.6만 토큰으로 짧고 범주 5에 정답이 없다는 점도 지적한다.
  • Letta(2025-08-12, Letta 팀 측정): 대화 기록을 파일로 두고 검색 도구만 준 에이전트가 GPT-4o mini로 LOCOMO 74.0%를 기록했다고 발표했고(Letta 블로그), Mem0의 MemGPT 측정은 재현 방법을 확인할 수 없었다고 적었다.

세 수치 모두 제3자 재현이 아니므로 제품 선택 근거로 쓰기보다, 자기 트래픽에서 아래 지표를 직접 재는 편이 낫다.

단계별 관측 체크리스트

  • [ ] 추출: 골든 대화 세트로 정밀도·재현율 측정, 프롬프트 버전별 비교. 빈 추출 비율과 100턴당 쓰기 수(memory_write_rate, 쿡북 제안 지표).
  • [ ] 갱신: ADD/UPDATE/DELETE/NOOP 분포 추이. ADD만 늘면 중복 제거가, DELETE가 급증하면 모순 오판이 의심된다. 주 1회 표본 수동 검수.
  • [ ] 쓰기 지연: 발화 시각 → 검색 가능 시각의 p50/p95, 큐 깊이, 재시도·중복 처리 건수.
  • [ ] 회수·주입: 턴당 회수 건수와 주입 토큰, 무효(invalid_at 설정) 기억이 주입된 횟수, 올바른 선호가 적용되기까지의 턴 수(time_to_personalization).
  • [ ] 추적성: 응답 하나에서 주입된 기억 ID → 근거 발화 → 추출 프롬프트 버전까지 역추적 가능한가. 이게 안 되면 환각 기억을 발견해도 원인 단계를 가릴 수 없다.

시스템 비교 — MemGPT/Letta · Mem0 · Zep/Graphiti · LangMem · A-MEM

MemGPT/Letta·Mem0·Zep/Graphiti·LangMem·A-MEM 은 같은 문제를 풀지만 '누가, 언제 쓰고, 낡은 사실을 어떻게 처리하는가'에서 갈린다. 2026년 Mem0 의 ADD-only 전환과 Letta 의 git 기반 MemFS 까지 반영해 저장 형태·쓰기 방식·갱신 의미론·오픈소스 여부·운영 복잡도를 비교하고, 벤더 벤치마크를 측정 주체와 반박까지 함께 읽는 법을 정리한다.

핵심 요점

  • 다섯 시스템을 가르는 축은 쓰기 주체(에이전트 자신 대 별도 파이프라인), 쓰기 시점(hot path 대 백그라운드), 갱신 의미론(덮어쓰기·삭제·무효화·누적) 세 가지다.
  • Mem0 는 2026-04-16 새 알고리즘에서 ADD/UPDATE/DELETE/NOOP 결정을 버리고 LLM 1회 호출의 ADD-only 추출로 바꿨으며, 외부 그래프 스토어 지원과 relations 필드도 없앴기 때문에 supersede 판단이 쓰기 시점에서 검색 랭킹으로 옮겨갔다.
  • Zep/Graphiti 는 사실 엣지에 사건 시간(t_valid·t_invalid)과 기록 시간(t'_created·t'_expired)을 함께 두고, 모순이 생기면 삭제 대신 옛 엣지의 t_invalid 를 닫아 '그때는 무엇이 사실이었나'를 질의할 수 있게 한다.
  • Letta 는 2026-02-12 Context Repositories(MemFS)로 메모리를 git 저장소의 마크다운 파일로 옮겼고, 갱신은 제자리 덮어쓰기이며 이력은 데이터 모델이 아닌 git 커밋에 남는다.
  • LoCoMo 에서 Zep 점수는 65.99(Mem0 측정)·84(Zep 최초 발표)·75.14(Zep 정정)·58.44(Mem0 재계산) 네 가지가 존재하고, Mem0 논문 자체에서도 full-context 가 72.90 으로 모든 메모리 시스템을 앞섰다.
  • Mem0 의 LoCoMo 92.5·LongMemEval 94.4 는 자체 측정이며 오픈소스 SDK 에 없는 독점 최적화가 들어간 매니지드 플랫폼 기준이므로, 선택은 공개 점수가 아니라 값이 바뀌는 사실을 섞은 자기 도메인 평가로 해야 한다.

비교의 축: 누가, 언제, 무엇을 덮어쓰는가

다섯 시스템은 모두 "대화와 도구 실행에서 사실을 뽑아 다음 세션에 되돌려준다"는 같은 문제를 푼다. 갈리는 지점은 세 가지다.

  • 쓰기 주체 — 에이전트가 도구 호출로 직접 쓰는가, 별도 파이프라인이 뒤에서 뽑는가.
  • 쓰기 시점 — 응답 경로(hot path) 안인가, 백그라운드인가.
  • 갱신 의미론 — 낡은 사실을 덮어쓰는가, 지우는가, 무효 표시만 하는가, 그냥 쌓는가.

이 글의 서술은 2026-09-19 기준이다. 2026년에 Mem0 와 Letta 가 둘 다 기본 설계를 바꿨기 때문에, 2025년 자료로 만든 비교표는 절반이 틀린다.

MemGPT/Letta — 에이전트가 자기 컨텍스트를 편집한다

MemGPT 논문(Packer 외, 2023-10-12 제출)은 컨텍스트 윈도를 RAM, 외부 저장소를 디스크로 보는 가상 컨텍스트 관리를 제안했다. LLM 이 함수 호출로 메인 컨텍스트와 외부 저장소 사이를 직접 페이징한다.

Letta 는 이를 메모리 블록으로 제품화했다. 블록은 label·description·value·limit(문자 수 상한) 네 속성을 갖고, 검색 없이 항상 프롬프트 앞에 붙는다. 공식 문서는 에이전트가 어디에 무엇을 쓸지 판단하는 주된 근거가 description 이라고 강조한다. 블록은 읽기 전용으로 잠글 수 있고 여러 에이전트가 공유할 수 있다.

2026-02-12 Letta 는 Context Repositories(MemFS) 를 발표했다. 메모리를 DB 행이 아니라 에이전트 소유 git 저장소의 마크다운 파일로 두고, 전용 메모리 도구 대신 bash·파일 도구로 편집한다.

  • system/ 아래 파일은 매 턴 시스템 프롬프트에 고정된다. 나머지는 파일 트리만 보이다가 필요할 때 연다.
  • 모든 수정은 커밋으로 남는다. 백그라운드 "sleep-time(dreaming)" 서브에이전트는 worktree 를 따로 받아 최근 대화를 정리하고 병합한다.
  • MemFS 문서에 따르면 기본 상태에는 시맨틱 검색이 없다(별도 mod). letta-ai/letta 저장소는 V1 API 서버 보관소가 됐고 개발은 letta-code 로 옮겨갔다(둘 다 Apache-2.0).

갱신 의미론은 제자리 덮어쓰기다. 이력은 데이터 모델이 아니라 git 로그에 있다. "언제 고쳐 썼나"는 복원되지만 "언제부터 사실이었나"는 질의할 수 없다. 실패 모드도 분명하다. 기억 품질이 모델의 도구 사용 능력에 종속되어, 약한 모델은 블록을 방치하거나 과하게 고친다.

Mem0 — "추출 후 연산 결정"에서 ADD-only 로

Mem0 논문(arXiv 2504.19413, ECAI 2025)의 원래 파이프라인은 2단계다.

  1. 추출: 대화 요약 S + 최근 메시지 m=10 개 + 새 메시지 쌍을 LLM 에 넣어 후보 사실을 뽑는다.
  2. 갱신: 후보마다 벡터 유사도 상위 s=10 개 기존 기억을 가져오고, LLM 이 tool call 로 ADD / UPDATE / DELETE / NOOP 중 하나를 고른다. 별도 분류기는 없다.

그래프 변형 Mem0g 는 엔티티 추출기와 관계 생성기로 트리플을 만들어 Neo4j 에 넣고, 충돌 관계는 물리 삭제하지 않고 invalid 로 표시한다. 실험 모델은 GPT-4o-mini 였다.

2026-04-16 Mem0 는 이 설계를 뒤집었다. 새 알고리즘은 추출을 LLM 한 번 호출로 합치고 ADD 만 한다. 마이그레이션 문서의 표현은 "nothing is overwritten or deleted"다. 뉴욕에서 샌프란시스코로 이사하면 두 사실이 시간 맥락과 함께 모두 남는다. 함께 바뀐 것들:

  • 에이전트가 말한 확인·추천도 사용자 발화와 동급으로 저장한다.
  • 외부 그래프 스토어 지원을 없애고 엔티티 링킹을 내장했다. 검색 결과의 relations 필드는 더 이상 채워지지 않는다. enable_graph, async_mode, output_format 파라미터도 제거됐다.
  • 검색은 시맨틱·BM25·엔티티 매칭 세 점수를 병렬 계산해 융합한다.
  • 용량 관리는 만료일 기능이나 명시적 delete API 로 한다.

실무 함의는 supersede 가 쓰기 시점에서 읽기 시점으로 이동했다는 것이다. 예전에는 저장소에 최신값 하나만 남았지만, 이제는 옛값과 새값이 둘 다 검색되고 어느 쪽이 현재인지 가르는 일이 랭킹과 응답 LLM 몫이다. "현재 주소는 하나"처럼 단일값이 필요한 필드는 애플리케이션이 따로 관리해야 한다.

Zep/Graphiti — 사실마다 두 개의 시간축

Zep 논문(arXiv 2501.13956, 2025-01-20)의 엔진 Graphiti 는 그래프를 3층으로 쌓는다. 에피소드(원문 메시지), 시맨틱 엔티티(엔티티와 사실 엣지), 커뮤니티(증분 편입이 쉬운 라벨 전파로 묶은 엔티티 군집과 요약)다.

핵심은 bi-temporal 모델이다. 사실 엣지는 사건 시간축 T 의 t_valid·t_invalid 와 시스템 기록 시간축 T′ 의 t′_created·t′_expired 를 함께 갖는다. 새 엣지가 들어오면 LLM 이 의미상 가까운 기존 엣지와 모순 여부를 비교하고, 모순이면 옛 엣지를 지우지 않고 t_invalid 를 새 엣지의 t_valid 로 닫는다.

이사 사실을 다루는 방식(개념 예시)
Letta      human 블록의 "서울 거주"를 "부산 거주"로 수정 (이력은 git 커밋)
Mem0(구)   UPDATE(id=17, "부산 거주")
Mem0(신)   ADD "2026-08 부산으로 이사" — 기존 "서울 거주"도 유지
Graphiti   (user)-[거주: 서울] t_invalid=2026-08 / 새 엣지 t_valid=2026-08

엔티티 해소는 임베딩 검색 + 전문 검색 + LLM 판정이다. 검색은 코사인·BM25·BFS 결과를 RRF, MMR, 에피소드 언급 빈도, 노드 거리, 크로스 인코더로 재정렬하며 검색 단계에는 LLM 호출이 없다.

운영 관점의 사실들:

  • Graphiti는 Apache-2.0 이고 백엔드는 Neo4j 5.26+, FalkorDB 1.1.2+, Amazon Neptune 이다(Kuzu 는 deprecated 표기).
  • 수집 동시성은 SEMAPHORE_LIMIT(기본 10)로 제한한다. 에피소드 하나에 추출·해소·무효화 LLM 호출이 여러 번 붙어 rate limit 에 쉽게 걸리기 때문이다.
  • README 는 structured output 을 지원하는 LLM 을 권하고, 작은 모델에서는 스키마 불일치로 수집이 실패할 수 있다고 경고한다.
  • 자체 호스팅용 Zep Community Edition 은 2025-04-02 공지로 지원이 끝났다. 오픈소스로 남은 것은 Graphiti 뿐이고 Zep 본체는 매니지드 서비스다.

LangMem — 서비스가 아니라 라이브러리

LangMem(MIT)은 서버가 없다. LangGraph 의 BaseStore(개발용 InMemoryStore, 운영용 AsyncPostgresStore) 위에서 도는 함수 모음이다.

  • 기억 유형: semantic(사실), episodic(성공한 상호작용 사례), procedural(시스템 프롬프트 자체를 피드백으로 고쳐 쓰는 프롬프트 최적화).
  • semantic 저장 형태 두 가지: 문서가 계속 늘어나는 collection 과, 스키마가 고정된 단일 문서를 덮어쓰는 profile. 단일값 필드는 profile, 열린 사실은 collection 이 맞다.
  • 쓰기 경로 두 가지: 에이전트가 create_manage_memory_tool 로 대화 중 직접 쓰는 hot path 와, 대화가 끝난 뒤 LLM 이 회고하며 뽑는 background. 문서는 전자가 체감 지연을 늘리고 후자가 recall 이 높다고 설명한다.
  • 갱신: 메모리 매니저가 기존 기억을 보고 삽입·수정·삭제를 정하며 enable_inserts·enable_deletes 로 허용 범위를 조인다.

대신 조정 프롬프트, 네임스페이스 설계, 백그라운드 실행기 운영이 전부 자기 몫이다.

A-MEM — Zettelkasten 식 노트와 링크

A-MEM(arXiv 2502.12110, NeurIPS 2025)은 기억 하나를 노트로 만든다. 노트는 원문, 타임스탬프, LLM 이 만든 키워드·태그·맥락 설명, 임베딩(all-minilm-l6-v2), 링크 집합을 갖는다.

  1. 노트 구성: 새 기억에 속성을 붙인다.
  2. 링크 생성: 코사인 상위 k 개 이웃을 뽑고 LLM 이 연결 여부를 판정한다.
  3. 기억 진화: 새 노트가 이웃 노트의 맥락 설명·키워드·태그를 고쳐 쓰게 한다.

저자 측정으로 기억 연산당 약 1,200 토큰(기준선 16,900 토큰 대비 85~93% 절감)이고 멀티홉 질문에서 기준선의 2배 이상을 보고했다. 다만 3단계는 버전 없는 덮어쓰기다. 새 노트 하나가 과거 노트 여러 개의 해석을 바꾸는데 되돌릴 기록이 없다. 공개 코드는 연구 구현이며 운영 도구는 없다.

비교표

시스템 저장 형태 쓰기 방식 갱신 의미론 오픈소스 운영 복잡도
Letta (MemFS) 항상 주입되는 블록 + git 마크다운 파일 에이전트가 직접 편집, sleep-time 서브에이전트 제자리 덮어쓰기, 이력은 git Apache-2.0 중 — 에이전트 런타임 전체를 채택해야 함
Mem0 (2026-04 이후) 벡터 기억 + 내장 엔티티 링크 파이프라인, LLM 1회, ADD-only 누적. 최신성은 랭킹이 판정, 삭제는 API Apache-2.0 SDK + 매니지드 하 — 단일값 필드는 직접 관리
Zep/Graphiti 3층 시간축 지식 그래프 파이프라인, 에피소드당 LLM 다회 엣지 무효화(bi-temporal), 삭제 없음 Graphiti 만 Apache-2.0 상 — 그래프 DB, 수집 비용·지연
LangMem LangGraph Store 의 collection/profile hot path 도구 또는 background 매니저가 삽입·수정·삭제 MIT 중 — 서버는 없지만 전부 직접 구성
A-MEM 속성·링크가 달린 노트 + 벡터 인덱스 파이프라인, 노트마다 링크·진화 이웃 노트 재작성(버전 없음) 연구 코드 공개 하(도입) / 상(운영 도구 부재)

벤치마크 수치는 "누가 쟀는가"와 함께 읽는다

이 분야의 수치는 거의 전부 벤더 자체 측정이다.

  • Mem0 논문(Mem0 측정): LoCoMo LLM-as-a-Judge 점수 Mem0 66.88, Mem0g 68.44, Zep 65.99, LangMem 58.10, A-Mem 48.38. 그런데 약 26k 토큰 대화를 통째로 넣은 full-context 가 72.90 으로 1위였다. Mem0 의 주장은 정확도가 아니라 p95 지연 1.440초 대 17.117초다.
  • Zep 의 반박(2025-05-06): Mem0 가 두 화자에게 모두 user 역할을 주고, 타임스탬프를 created_at 대신 본문에 붙이고, 검색을 순차 실행했다고 지적하며 자사 점수를 84% 로 발표했다. 이틀 뒤 Mem0 CTO 가 GitHub 이슈에서 제외 대상인 5번(adversarial) 범주의 정답이 분자에만 들어갔다며 58.44 ± 0.20 을 제시했고, Zep 은 글을 75.14 ± 0.17 로 정정했다. 한 시스템, 한 벤치마크에 점수가 네 개다.
  • Zep 논문(Zep 측정): DMR 94.8 대 MemGPT 93.4. LongMemEval(gpt-4o)에서 full-context 60.2% → 71.2%, 지연 28.9초 → 2.58초, 컨텍스트 115k → 1.6k 토큰. 같은 논문이 single-session-assistant 유형은 17.7% 나빠졌다고 적었다.
  • Letta(2025-08-12, Letta 측정): 대화 기록을 파일로 두고 grep·search_files·open 만 준 gpt-4o-mini 에이전트가 LoCoMo 74.0% 로 Mem0g 보고치 68.5% 를 넘었다. Letta 는 Mem0 가 MemGPT 수치 산출 방법 문의에 답하지 않았다고 적었다.
  • Mem0 새 알고리즘(Mem0 측정): LoCoMo 92.5, LongMemEval 94.4, BEAM 1M 64.1, 10M 48.6, 질의당 약 7k 토큰. 평가 프레임워크는 공개됐지만 GitHub README 는 이 점수가 오픈소스 SDK 에 없는 독점 최적화가 들어간 매니지드 플랫폼 기준이라고 명시한다.
  • 벤치마크 자체의 결함: Dell Zhang 의 2026-05-20 분석이 인용한 Penfield Labs 감사는 LoCoMo 1,540 문항 중 99개(6.4%)에서 채점을 오염시키는 정답지 오류를 찾았고, 표준 judge 가 의도적으로 틀린 답의 62.81% 를 통과시켰다고 보고했다.

결론은 단순하다. 공개 점수로 고르지 말고, 자기 도메인 대화 50~100건에 "값이 바뀐 사실"과 "대답하면 안 되는 질문"을 섞어 같은 응답 모델·같은 judge 로 직접 돌려라.

선택 기준

  • "그때는 무엇이 사실이었나"를 답해야 한다(계약, 요금제, 담당자 이력): Graphiti. 수집 LLM 비용과 그래프 DB 운영을 감당할 수 있는지 먼저 확인한다.
  • 개인화 사실을 빨리 붙여야 하고 운영 인력이 없다: Mem0. ADD-only 전환 이후에는 단일값 필드를 별도 profile 로 두는 설계가 필수다.
  • 장기 실행 에이전트 하나가 스스로 학습해야 한다(코딩 에이전트, 개인 비서): Letta. 메모리만이 아니라 런타임을 고르는 결정이다.
  • 이미 LangGraph 위에 있다: LangMem 의 profile + background 조합으로 시작하고, 병목이 보일 때 전용 시스템으로 옮긴다.
  • A-MEM 은 부품으로 본다: 링크 생성과 기억 진화 아이디어는 기존 벡터 스토어에 이식할 수 있다. 진화 단계에는 반드시 변경 로그를 붙인다.

저장 계층 — 벡터, 그래프, 그리고 그냥 파일

메모리 저장소는 벡터냐 그래프냐가 아니라 다음 세션에 어떤 질의(의미 유사·정확 일치·다중 홉·시간·상시 필요)가 오는지로 고른다. 하이브리드 검색과 RRF는 갈래 하나가 죽어도 결과를 내기 때문에 임베딩 지연 같은 어긋남이 조용히 지나가며, 작은 규모에서는 에이전트가 직접 읽고 고치는 git 기반 마크다운 파일이 의외로 강하다.

핵심 요점

  • 벡터는 바꿔 말한 질문에, BM25는 식별자·고유명사에, 그래프는 다중 홉과 유효 구간 질의에, 키-값 블록은 검색 없이 상시 주입할 소수 사실에 강하며 서로의 약점을 대신하지 못한다.
  • RRF는 점수 대신 순위만 더하는 융합으로(1/(k+rank), 원 논문과 Elasticsearch 기본값 모두 k=60) 단위가 다른 갈래를 튜닝 없이 합치지만, 0건인 갈래를 오류로 드러내지 않으므로 갈래별 히트 수를 반드시 로그로 남겨야 한다.
  • 색인과 임베딩·그래프 추출은 비용이 달라 쓰기 경로가 갈라지고, 그 사이의 메모리는 일부 갈래에만 보인다 — embedding_status 컬럼, 가장 오래된 pending 나이 메트릭, 최근 기록의 직접 주입, outbox 패턴으로 대응한다.
  • pgvector는 근사 색인에서 필터가 색인 스캔 뒤에 적용되어 기본 ef_search 40에서 10% 선택도면 평균 4행만 남으므로, 사용자별 격리가 있는 메모리에서는 0.8.0의 반복 스캔 설정을 확인해야 한다.
  • Letta가 자체 측정한 파일 기반 에이전트의 LoCoMo 74.0%, Mem0 논문의 전체 컨텍스트 기준선 72.90%, Zep의 84%→75.14% 정정은 모두 벤더가 자기 제품을 잰 수치라서 저장소 선택의 근거로는 약하다.
  • 파일 메모리의 실질적 강점은 갱신이 곧 편집(str_replace)이라는 점, 인덱스 파일과 토픽 파일의 점진적 공개, git의 diff·blame·revert가 감사와 복구를 맡는다는 점, 파생 색인이 없어 어긋날 것이 없다는 점이다.

저장소는 "어떤 질의에 답할 것인가"로 고른다

메모리 저장소를 고를 때 흔한 실수는 "벡터 DB냐 그래프 DB냐"부터 묻는 것이다. 순서가 반대다. 에이전트가 다음 세션에 던지는 질의는 대략 다섯 종류로 갈리고, 저장 형태마다 답할 수 있는 종류가 다르다.

  • 의미 유사: "지난번에 배포 문제로 뭐라고 했더라" — 표현이 달라도 찾아야 한다.
  • 정확 일치: 오류 코드, 티켓 번호, 함수명, 사람 이름 — 한 글자만 달라도 다른 것이다.
  • 관계·다중 홉: "이 고객 담당자가 속한 팀이 쓰는 배포 도구".
  • 시간: "지금 유효한 값"과 "3월에는 무엇이었나".
  • 상시 필요: 사용자 이름, 말투 선호, 금지 사항 — 검색할 것이 아니라 항상 컨텍스트에 있어야 한다.
저장 형태 잘하는 질의 못하는 질의 공개 사례
벡터 색인 의미 유사, 바꿔 말한 질문 식별자·희귀 토큰의 정확 일치, "현재 값"(옛 사실과 새 사실이 똑같이 가깝다), "전부 나열해" 같은 열거·집계 Mem0 기본 구성, Letta archival memory
지식 그래프 엔티티 중심 조회, 다중 홉, 유효 구간 질의 스키마에 안 담기는 뉘앙스, 쓰기 직후 조회(추출이 느리다), 추출 오류의 영구화 Zep/Graphiti
키-값·블록 상시 필요한 소수의 사실 규모 — 컨텍스트 예산을 늘 점유하고 검색이 없다 Letta memory block
마크다운 파일 에이전트의 직접 탐색·수정, 사람의 감사, 이력 추적 어휘가 어긋나는 질의, 다중 사용자 격리, 동시 쓰기 Anthropic 메모리 도구, Claude Code auto memory, Letta MemFS

몇 가지는 공식 문서가 직접 경고한다. pgvector README는 근사 색인에서 필터가 색인 스캔 "뒤에" 적용된다고 적는다. 조건이 전체 행의 10%에만 맞고 hnsw.ef_search가 기본값 40이면 평균 4행만 남는다. user_id로 거르는 멀티테넌트 메모리에서 "결과가 이상하게 적다"의 원인이 대개 이것이고, 0.8.0부터 들어간 반복 스캔(SET hnsw.iterative_scan = strict_order 또는 relaxed_order)이 대응책이다.

그래프의 강점은 시간이다. Zep 논문(Rasmussen 외, arXiv 2501.13956, 2025-01)은 간선마다 타임스탬프 넷을 둔다. 사실이 실제로 유효했던 구간(t_valid, t_invalid)과 시스템이 그것을 기록·만료한 시점(t'_created, t'_expired)이다. 모순되는 새 사실이 들어오면 옛 간선을 지우지 않고, 옛 간선의 t_invalid를 새 간선의 t_valid로 설정한다. 벡터만으로는 흉내 내기 어려운 부분이다.

키-값 쪽의 대표는 Letta의 memory block이다. 문서 정의상 label·description·value·limit(문자 수 상한) 네 필드이고, "always visible - no retrieval needed", 즉 프롬프트 앞에 항상 붙는다. 검색 실패가 원천적으로 없는 대신 자리를 상시 차지하므로 limit이 곧 설계 변수다.

하이브리드 검색과 RRF

프로덕션 메모리 시스템은 결국 여러 갈래를 함께 돌린다. Zep 논문은 검색 함수를 셋으로 명시한다. 코사인 유사도, Okapi BM25 전문 검색, 그래프 너비 우선 탐색(BFS)이다. 앞의 둘은 Neo4j에 내장된 Lucene 구현을 쓴다. 재순위 단계는 RRF, MMR, 에피소드 언급 빈도, 노드 거리, 크로스 인코더 중에서 고른다. Mem0도 2026년 9월에 확인한 공식 문서 기준으로 같은 방향에 있다. 질의에서 엔티티를 뽑아 연결된 메모리에 가산점을 주고, 이를 의미(벡터) 점수·키워드(BM25) 점수와 합쳐 단일 score로 돌려준다. 외부 그래프 DB(Neo4j·Memgraph·Kuzu 등)를 붙이던 방식은 같은 문서에서 "이전 버전의 방식"으로 서술되며, 연결은 선언된 관계가 아니라 동시 출현에서 추론된 것이라고 한계도 밝힌다.

갈래를 합칠 때의 문제는 점수 단위가 서로 다르다는 것이다. BM25는 상한이 없고, 코사인은 -1~1이며, 그래프 거리는 홉 수다. RRF(Reciprocal Rank Fusion) 는 점수를 버리고 순위만 쓴다.

RRF(d) = Σ_r 1 / (k + rank_r(d))

Cormack·Clarke·Büttcher의 SIGIR 2009 논문은 파일럿 실험에서 k=60이 최적에 가까웠지만 "선택이 결정적이지는 않았다"고 적었고(k=0일 때 MAP 0.2072, k=60일 때 0.2145), Elasticsearch도 rank_constant 기본값을 60으로 둔다. 구현은 열 줄이면 된다.

from collections import defaultdict

def rrf(branches: dict[str, list[str]], k: int = 60, window: int = 50):
    scores, seen_by = defaultdict(float), defaultdict(list)
    for name, ids in branches.items():            # 각 갈래는 순위순 id 목록
        for rank, mem_id in enumerate(ids[:window], start=1):
            scores[mem_id] += 1.0 / (k + rank)
            seen_by[mem_id].append(name)
    stats = {name: len(ids) for name, ids in branches.items()}  # 갈래별 건수
    ranked = sorted(scores, key=scores.get, reverse=True)
    return [(m, scores[m], seen_by[m]) for m in ranked], stats

bm25=[m7,m2,m9], vector=[], graph=[m2,m4]를 넣으면 m2가 0.0325로 1위가 되고, 한 갈래에만 나온 m7은 0.0164에 머문다. 여기서 봐야 할 것은 벡터 갈래가 0건인데도 그럴듯한 결과가 나왔다는 점이다. RRF는 죽은 갈래를 오류로 만들지 않는다. 그래서 stats를 반환하고, 질의마다 로그로 남겨야 한다.

실무에서 조정할 것은 셋이다. ① 갈래별 후보 창(window)이 너무 작으면 융합할 재료가 없다. ② 식별자 질의가 많으면 BM25 갈래에 가중치를 주되, 가중치는 회수 로그로 검증한다. ③ 한국어는 형태소 분석기 없이 BM25를 돌리면 조사 때문에 어휘 갈래가 0건이 되기 쉽다 — 위와 같은 "조용한 실패"다.

색인은 됐는데 임베딩이 밀린 상태

쓰기 경로는 비용이 다른 단계로 쪼개진다. 원문 저장과 어휘 색인은 싸고 동기적이다. 임베딩은 모델 호출이라 배치·비동기로 밀린다. 그래프 추출은 LLM을 여러 번 부르므로 더 늦다. 그 사이의 메모리는 일부 갈래에만 보인다.

이것은 구현 실수가 아니라 구조의 성질이다. LangChain RecordManager API 레퍼런스는 한계를 이렇게 적는다. 레코드 매니저가 벡터 스토어와 별개로 구현되어 있어 전체가 분산 시스템이 되고, "레코드 매니저 쓰기는 성공했는데 대응하는 벡터 스토어 쓰기는 실패"할 수 있다고. Mem0 논문에는 경쟁 제품에 대한 관찰도 있다. Zep에 메모리를 추가한 직후의 검색은 자주 실패했고, 몇 시간 뒤 같은 검색은 상당히 나아졌다는 것이다. 경쟁사가 쓴 평가라는 점은 감안해야 하지만, 비동기 그래프 구축이 쓰기 직후 읽기에 약하다는 일반적 성질과 맞는다.

메모리에서 이 문제가 특히 아픈 이유는, 가장 자주 되묻는 것이 방금 말한 것이기 때문이다. 점검 목록은 다음과 같다.

  1. 레코드마다 embedding_status(pending/ready/failed)와 embedding_model을 둔다. 모델을 바꾸면 기존 벡터와 비교할 수 없으므로 이중 색인 후 전환한다.
  2. 대기 건수와 가장 오래된 pending의 나이를 메트릭으로 낸다. "대기 0"은 "모두 최신"이 아니라 "대기열이 비었다"일 뿐이다.
  3. 질의마다 갈래별 히트 수를 남기고, 한 갈래가 연속 0건이면 경보한다.
  4. 최근 N분의 기록은 검색을 거치지 않고 세션 버퍼에서 직접 주입한다(read-your-writes).
  5. 원문과 색인 작업 요청을 같은 트랜잭션에 쓰는 outbox 패턴을 쓰고, 워커는 멱등 upsert와 재시도를 갖춘다.
  6. 역방향 어긋남도 본다. 원장에서 무효화(supersede)된 사실이 벡터 색인에 남아 회수될 수 있다. 회수 결과를 원장과 조인해 유효성을 다시 거른다.

원장과 벡터를 한 Postgres에 두면(pgvector) 1·5·6이 트랜잭션 하나로 해결된다. 작은 규모에서 전용 벡터 DB보다 이쪽을 먼저 권하는 가장 큰 이유다.

파일 기반 메모리가 의외로 강한 이유

Letta는 2025년 8월 12일 블로그에서, LoCoMo 대화 기록을 파일로 붙이고 grep·search_files·open·close 네 도구만 준 gpt-4o-mini 에이전트가 74.0% 를 기록했다고 밝혔다. Mem0 논문이 보고한 자사 그래프 변형의 점수(논문 표 기준 68.44%)보다 높다. 읽을 때 주의할 점이 셋 있다. 측정자는 Letta 자신이다. Letta의 파일은 자동으로 임베딩되므로 search_files는 의미 검색이며 "순수 grep"이 아니다. 그리고 Letta는 이 결과를 "파일이 최고"가 아니라 "검색 방식보다 에이전트가 도구를 제대로 쓰는지가 중요하다"는 근거로 제시했다.

LoCoMo 수치 전반이 저장소 선택의 근거로 약하다는 점도 같이 알아야 한다. Mem0 논문(2025-04)은 Mem0 66.88%, Zep 65.99%를 보고했는데, 같은 표에서 대화 전체를 컨텍스트에 넣은 기준선이 72.90%로 둘 다 이긴다. Zep은 2025년 5월 반박 글에서 올바르게 구성한 Zep은 84%라고 주장했고, 이틀 뒤 Mem0 CTO가 GitHub 이슈(getzep/zep-papers #5)에서 제외한 범주 5의 정답이 분자에만 들어갔다고 지적하며 58.44%를 제시했다. Zep은 글을 고쳐 현재 75.14% ± 0.17로 적고 있다. 양쪽 모두 자기 제품을 자기가 잰 수치다. Zep은 같은 글에서 LoCoMo 대화가 1.6만~2.6만 토큰이라 컨텍스트 창에 그냥 들어간다는 한계도 짚었다.

벤치마크를 걷어내도 남는 구조적 이유는 다음과 같다.

  • 갱신이 곧 편집이다. Anthropic 메모리 도구(memory_20250818)의 명령은 view·create·str_replace·insert·delete·rename 여섯 개다. 낡은 사실을 고치는 일이 별도 파이프라인이 아니라 str_replace 한 번이다. 도구는 클라이언트 측에서 실행되므로 저장 위치는 운영자가 정한다.
  • 인덱스 파일과 토픽 파일로 점진적 공개가 된다. Claude Code의 auto memory는 MEMORY.md의 첫 200줄 또는 25KB만 세션 시작 때 읽고, 토픽 파일은 필요할 때 파일 도구로 연다. Letta MemFS는 system/ 디렉터리 아래만 매 턴 시스템 프롬프트에 싣는다.
  • git이 감사 로그다. Letta의 Context Repositories(2026-02-12)는 메모리 변경을 커밋 메시지와 함께 자동 버전 관리하고, 서브에이전트마다 worktree를 줘서 동시에 쓴 뒤 병합한다. diff는 무엇이 바뀌었는지, blame은 그 사실이 언제 들어왔는지, revert는 오염된 메모리의 복구를 맡는다.
  • 파생 색인이 없으면 어긋날 것도 없다. 원본이 곧 검색 대상이므로 앞 절의 문제가 생기지 않는다.

한계도 분명하다. 파일이 늘면 어휘가 어긋나는 질의를 grep이 놓친다. 다중 사용자에서는 경로 검증이 보안 경계가 된다 — Anthropic 문서는 /memories/../../secrets.env 같은 경로를 모든 명령에서 막으라고 경고하고, 파일 크기 상한과 오래된 파일의 만료도 구현자 책임으로 둔다.

선택 기준

건수보다 신호로 판단하는 편이 안전하다.

상황 권장 구성 넘어갈 신호
단일 사용자·코딩 에이전트·운영 노트 마크다운 + 인덱스 파일 + git, 검색은 grep 인덱스 파일이 로드 상한에 걸린다, grep 결과가 컨텍스트에 안 들어간다
다수 사용자, 사용자별 격리 필요 Postgres 한 대: 원장 테이블 + 전문 검색 + pgvector, RRF 융합 필터 결합 recall 저하, 임베딩 대기열 지연이 SLO를 넘는다
관계·시간 질의가 제품의 핵심 전용 벡터 엔진 + 시간 그래프(Zep/Graphiti 방식), outbox·갈래 모니터링 필수 —
모든 단계 공통 상시 필요한 소수 사실은 키-값 블록으로 고정 주입 블록이 limit에 닿으면 검색 계층으로 내린다

도입 전에 회수 로그로 확인할 질문은 넷이다. 식별자·고유명사 질의의 비중은 얼마인가(높으면 BM25가 필수다). "지금 유효한 값"을 묻는가(그렇다면 저장소 종류보다 valid_at·invalid_at 컬럼이 먼저다). 다중 홉 질의가 실제로 있는가(없으면 그래프는 보류한다). 쓰기 직후 읽기가 필요한가(필요하면 최근 버퍼부터 만든다).

갱신·모순·망각 — supersede 의미론

새 사실이 옛 사실과 충돌할 때 덮어쓰지 말고 종료 시각을 찍어 무효화하고 이력을 남겨야 하는 이유를, bi-temporal 모델·출처 등급·망각·삭제 요구권·메모리 포이즈닝 방어까지 하나의 설계로 묶어 설명한다. 사건 시각과 기록 시각을 분리한 이력은 감사 수단이자 오염된 기억을 되돌리는 유일한 복구 수단이다.

핵심 요점

  • Mem0 기본형은 모순을 DELETE 로, Zep/Graphiti 는 옛 엣지의 t_invalid 를 새 엣지의 t_valid 로 닫아 처리하며, Letta 메모리 블록은 전체 교체에 last-write-wins 라 이력이 필요하면 git 기반 MemFS 같은 별도 계층이 있어야 한다.
  • 사건 시각(valid_at/invalid_at)과 기록 시각(created_at/expired_at)을 분리해야 '당시 지식으로는 옳았던 행동'과 버그를 구분할 수 있고, 특정 시점 이후 기록만 골라 되돌릴 수 있다.
  • LoCoMo 에는 지식 갱신 문항이 없고 같은 Zep 에 대해 58.44%(Mem0 측정)·65.99%(Mem0 논문)·75.14%(Zep 측정)가 공존하므로, supersede 품질은 벤더 수치가 아니라 자체 '값이 바뀌는 시나리오' 회귀셋으로 판정해야 한다.
  • 회수 기준 시간 감쇠는 자주 불리는 오염 기억을 오히려 살려 두므로 무효화된 사실과 낮은 신뢰 등급에는 점수 상한을 두고, Mem0 expiration_date 처럼 숨기기만 하는 만료를 삭제로 착각하면 안 된다.
  • GDPR 17조 삭제는 invalidate 와 다른 물리 삭제 경로여야 하며, Zep 문서가 경고하듯 에피소드를 지워도 공유 노드 요약과 무효 표시는 남으므로 요약문·임베딩·캐시·백업까지 닿는 런북이 필요하다.
  • 메모리 포이즈닝은 AgentPoison·MINJA 같은 연구를 넘어 Microsoft 가 60일간 31개 기업의 시도 50건을 관측한 상용 기법이 됐고, 방어의 핵심은 채널별 신뢰 등급으로 supersede 권한을 제한하고 에피소드 단위로 되돌릴 수 있게 하는 것이다.

덮어쓰기는 가장 싼 선택이고, 가장 늦게 청구되는 비용이다

"서울에 산다"를 기억하던 에이전트가 "지난달 부산으로 이사했다"를 들었을 때 할 수 있는 일은 세 가지다. 옛 사실을 덮어쓰거나, 삭제하고 새로 넣거나, 옛 사실에 종료 시각을 찍어 **무효화(invalidate)**하고 이력을 남기거나. 세 제품의 기본 동작은 서로 다르다.

시스템 충돌 시 기본 동작 이력
Mem0(기본) 후보 사실마다 유사 기억 상위 s=10개를 꺼내 LLM 이 ADD/UPDATE/DELETE/NOOP 중 하나를 tool call 로 선택. 모순은 DELETE 본체에는 남지 않음
Mem0ᵍ(그래프) 업데이트 리졸버가 관계를 "물리적으로 지우지 않고 invalid 로 표시" 보존
Zep/Graphiti LLM 이 의미상 가까운 기존 엣지와 비교해 모순이면 옛 엣지의 t_invalid 를 새 엣지의 t_valid 로 설정 보존, 시점 질의 가능
Letta 메모리 블록 블록 쓰기는 전체 교체, 동시 편집은 "last write wins" 블록 자체엔 없음. Letta Code 의 MemFS 는 git 기반이라 커밋 이력이 남음

(Mem0 논문 arXiv 2504.19413, Zep 논문 arXiv 2501.13956, Letta 공식 문서를 2026-09-19 에 확인했다.)

의사결정 기준은 단순하다. "그때는 뭐였지?"를 물을 일이 있거나, 잘못 들어간 기억을 되돌려야 할 가능성이 있으면 무효화+이력이다. 선호·주소·직책·설정값처럼 변하는 상태는 전부 여기에 든다. 덮어쓰기가 허용되는 건 오타 교정처럼 옛 값이 처음부터 틀렸던 경우뿐이고, 그때도 "틀렸던 값"과 "바뀐 값"은 다른 연산으로 기록해야 나중에 구분된다.

bi-temporal — 타임스탬프 네 개가 최소 단위다

Zep 논문은 두 타임라인을 분리한다. T 는 사건이 실제로 일어난 순서, T′ 는 시스템이 그 데이터를 받아들인 순서다. 엣지마다 네 값을 갖는다.

-- 사실 1건 = 행 1개. UPDATE 하지 않고 닫은 뒤 새 행을 연다.
fact_id, subject, predicate, object,
valid_at,    -- T : 현실에서 참이 되기 시작한 시각
invalid_at,  -- T : 현실에서 참이 아니게 된 시각 (NULL = 현재 유효)
created_at,  -- T′: 시스템이 기록한 시각
expired_at,  -- T′: 시스템이 무효 처리한 시각
source_episode_id, channel, trust_tier, extractor_version

-- "8월 1일 시점에 우리가 알고 있던 주소"
WHERE created_at <= '2026-08-01' AND (expired_at IS NULL OR expired_at > '2026-08-01')

두 축을 나누는 이유는 실무적이다. 9월에 "7월에 이사했다"를 들으면 valid_at 은 7월, created_at 은 9월이다. 8월에 에이전트가 서울 기준으로 배송 안내를 했다면 그건 당시 지식으로는 옳은 행동이고, 이 구분이 없으면 사후 감사에서 버그와 늦게 도착한 정보를 가를 수 없다. "2주 전" 같은 상대 표현은 Graphiti 가 에피소드 기준 시각(t_ref)으로 절대 시각화한다 — 메시지의 실제 발화 시각을 전용 필드로 넘기지 않으면 이 계산이 통째로 틀어진다.

주의할 점은 Graphiti 가 무효화 판정에서 **"일관되게 새 정보를 우선한다"**고 논문에 명시한 것이다. 정상 사용자에겐 합리적인 기본값이지만, 뒤에서 보듯 공격자에게도 같은 기본값이다.

supersede 품질은 누가 어떻게 쟀나

갱신 능력을 직접 재는 공개 벤치마크는 LongMemEval(Wu 외, ICLR 2025, 500문항)의 knowledge-update 범주다. Zep 은 자사 논문에서 전체 정확도를 full-context 대비 gpt-4o 60.2%→71.2%, gpt-4o-mini 55.4%→63.8% 로 보고했는데, 측정자는 Zep 자신이고 같은 표에서 knowledge-update 는 gpt-4o-mini 기준 3.36% 하락, single-session-assistant 는 두 모델 모두 하락(−9.06%, −17.7%)했다. 시간 그래프가 갱신 질문을 자동으로 풀어 주지는 않는다는 뜻이다.

LoCoMo 수치는 더 조심해야 한다. Mem0 논문(2025-04, Mem0 측정)은 LLM-as-a-Judge 기준 Mem0 66.88%, Mem0ᵍ 68.44%, Zep 65.99%, full-context 72.90% 를 실었다. Zep 은 2025-05-06 게시한 블로그에서 Mem0 가 Zep 을 잘못 구성했다며(두 화자 모두 user 역할 배정, 타임스탬프를 created_at 대신 본문에 붙임, 검색을 순차 실행) 자체 재측정값을 냈고, 현재 게시본의 수치는 75.14% ± 0.17 이다. Mem0 CTO 는 2025-05-08 Zep 저장소 이슈 #5 에서 Zep 이 처음 내건 84% 가 5번(adversarial) 범주 정답을 분자에만 넣은 계산 오류라며 자체 재측정값 58.44% ± 0.20 을 제시했고, 이 이슈는 2026-09-19 확인 시점에도 열려 있었다. 같은 시스템에 58%·66%·75%·84% 가 붙은 셈이고, 어느 것도 제3자 검증이 아니다. 게다가 Zep 의 지적대로 LoCoMo 는 대화가 1.6만~2.6만 토큰이라 컨텍스트에 통째로 들어가고 지식 갱신 문항이 아예 없다. 결론: supersede 품질은 벤더 표로 고를 수 없고, 자기 도메인의 "값이 바뀌는 시나리오" 회귀셋을 직접 만들어야 한다.

출처와 신뢰도 — 누가 말했는지 모르는 사실은 갱신 권한도 없다

추출 단계에서 사실마다 최소한 다음을 붙인다: 원본 에피소드 ID, 채널(사용자 직접 발화 / 도구 실행 결과 / 외부 문서·웹 본문 / 에이전트 추론), 신뢰 등급, 추출 모델·프롬프트 버전. Graphiti 가 데이터를 에피소드 단위로 받아 provenance 를 유지하는 것도 같은 이유다. 이 메타데이터가 있어야 아래 규칙을 쓸 수 있다.

  • supersede 는 기존 사실 이상 등급의 채널만 할 수 있다. 웹 페이지 본문에서 뽑힌 "사용자는 X 를 선호한다"가 사용자가 직접 말한 선호를 무효화하면 안 된다.
  • 에이전트 추론에서 나온 사실은 별도 등급으로 두고, 추론의 근거 에피소드가 무효화되면 함께 재검토 대상에 올린다.
  • 추출기 버전을 남기면 프롬프트 결함이 발견됐을 때 해당 버전 산출물만 골라 재추출할 수 있다.

망각 — 감쇠, 만료, 삭제는 서로 다른 연산이다

회수 점수 감쇠의 원형은 Generative Agents(Park 외, 2023)다. recency 는 마지막 회수 이후 경과한 게임 내 시간에 대한 지수 감쇠(계수 0.995), importance 는 LLM 이 1(양치질)~10(이별·합격)으로 매긴 점수, relevance 는 임베딩 코사인 유사도이고, 셋을 min-max 로 [0,1] 정규화해 가중치 1 로 더한다. 실무 이식 시 두 가지를 바꾼다. 감쇠 기준이 "마지막 회수"라 자주 불려 나오는 기억은 영원히 살아남는데, 오염된 기억이 바로 그런 기억이다 — 무효화된 사실과 낮은 신뢰 등급은 감쇠와 무관하게 점수 상한을 둔다. 그리고 "알레르기"처럼 드물게 불리지만 잊으면 안 되는 사실은 importance 하한으로 감쇠에서 제외한다.

만료는 또 다르다. Mem0 의 expiration_date 는 문서가 명시하듯 숨길 뿐 지우지 않는다 — search()·get_all() 에서 빠질 뿐 ID 로 조회하면 나오고 show_expired=True 로 되살아난다. 체험판 종료일 같은 시한부 사실에는 맞지만 보존기한 준수 수단은 아니다.

삭제 요구권 — 이력 보존과 정면으로 부딪힌다

GDPR 17조 1항은 정보주체가 "부당한 지체 없이" 삭제를 받을 권리를 규정한다. 무효화+이력 설계에서 invalid_at 을 찍는 건 삭제가 아니다. erasure 는 이력 행까지 물리 삭제하는 별도 경로여야 한다. 제품별로 확인된 동작은 다음과 같다.

  • Mem0: delete_all(user_id=...), batch_delete()(요청당 최대 1000건). 문서가 GDPR/CCPA 삭제 용도를 직접 언급한다.
  • Zep: 에피소드 삭제 시 엣지·노드는 다른 에피소드와 연결이 없을 때만 지워진다. 문서의 경고 두 줄이 핵심이다 — 공유 노드의 이름·요약은 재생성되지 않고, 삭제된 에피소드가 무효화했던 사실은 무효 상태로 남는다.

즉 파생물에 잔여가 남는다. 삭제 런북에 넣을 항목: ① 원본 에피소드 ② 추출 사실과 이력 행 ③ 임베딩 ④ 엔티티·커뮤니티 요약문 재생성 ⑤ 블록형 코어 메모리 안의 문장 ⑥ 검색 캐시·백업·트레이스 로그 ⑦ 평가·학습용으로 복사된 데이터셋. 사용자 단위 파티션 키를 처음부터 모든 계층에 관통시키지 않으면 ④~⑦은 사실상 찾을 수 없다.

메모리 포이즈닝 — 프롬프트 인젝션에 영속성이 붙는다

일반 인젝션은 세션이 끝나면 사라진다. 메모리에 쓰이면 모든 미래 세션에 주입된다. 공개된 사례와 연구를 측정 주체와 함께 정리한다.

공개 시점 내용 수치·측정자
2024-07 AgentPoison — 메모리/RAG 지식베이스에 백도어 트리거 주입, 재학습 불필요 공격 성공률 80% 이상, 오염률 0.1% 미만, 정상 성능 영향 1% 미만(저자)
2024-09-20 SpAIware(Rehberger) — 웹·문서의 인젝션이 ChatGPT 메모리에 지속형 유출 지시를 저장 OpenAI 는 1.2024.247 에서 유출 경로만 차단. "신뢰할 수 없는 문서가 메모리 도구를 호출할 수 있다"는 점은 남았다고 보고
2025-02-10 Gemini 지연 도구 호출 — 문서가 조건을 심어 두고 사용자가 "네"라고 답하면 메모리 저장 실행 Google 평가: 낮은 가능성·낮은 영향. 저장 시 UI 표시가 완화책
2025-03 MINJA — 메모리 직접 접근 없이 일반 사용자 질의만으로 악성 기록 주입(bridging step, indication prompt, progressive shortening) 후속 논문 인용 기준 주입 95% 이상·공격 성공 70%, 단 이상적 조건
2026-01 MINJA 재평가(arXiv 2601.05504) 정상 기억이 이미 쌓인 현실 조건에서는 효과가 크게 감소. 방어 임계값 보정이 어렵다고 보고
2026-02-10 AI Recommendation Poisoning(Microsoft) — "AI 로 요약" 버튼 URL 에 "이 회사를 신뢰 출처로 기억하라"를 심음 60일간 31개 기업의 시도 50건 관측. MITRE ATLAS AML.T0080
2026-07 GhostWriter(arXiv 2607.06595) 주입 약 98%, 활성화 약 60%(저자)

OWASP 는 2025-12-09 공개한 Agentic Applications Top 10 에 ASI06 Memory & Context Poisoning 을 올렸다. 마지막에서 두 번째 행이 중요하다 — 이건 연구실 공격이 아니라 마케팅 목적으로 이미 상용화된 기법이다.

방어 설계 — 쓰기 게이트, 회수 시 격리, 되돌리기

쓰기 경로

  • 채널 태깅을 추출 이전에 한다. 도구 결과·웹 본문·첨부 문서에서 유래한 후보 사실은 격리 등급으로만 들어가고, 앞 절의 규칙대로 사용자 발화 유래 사실을 supersede 하지 못한다.
  • 서술이 아니라 지시 형태인 후보("항상 ~하라", "~를 신뢰하라", URL·도구명 포함)는 사실 저장소에 넣지 않는다. 행동 규칙은 별도 저장소에서 사람 승인으로만 바꾼다.
  • 저장 사실을 사용자에게 보이고 되돌릴 수 있게 한다. Rehberger 의 권고는 한발 더 나아가 저장 전 명시적 확인이다.

회수 경로

  • 회수된 기억은 시스템 지시가 아닌 인용 데이터로 렌더링하고 출처·시각·등급을 함께 넣는다.
  • 신뢰 등급 가중 회수와 시간 감쇠(2601.05504), 관련 기억 여러 건의 추론 경로를 비교하는 합의 검증(A-MemGuard — 저자 측정 공격 성공률 95% 이상 감소). 다만 임계값을 높이면 정상 기억까지 거부하므로 자체 트래픽으로 보정해야 한다.

되돌리기 — 여기서 bi-temporal 이 보안 기능이 된다. 오염이 발견되면 source_episode_id 로 해당 에피소드 유래 사실을 전부 찾고, 그것이 무효화했던 옛 사실의 invalid_at 을 복원한다. 덮어쓰기·DELETE 기반 저장소에서는 독이 지운 원래 값이 남아 있지 않다. Zep 에서 에피소드만 지우면 무효 표시가 그대로 남는다는 점은 이 복원을 직접 구현해야 한다는 뜻이다.

출시 전 점검

  • [ ] 모순 시 덮어쓰지 않고 닫고-새로-여는가, 시각 4종이 분리돼 있는가
  • [ ] 모든 사실에 에피소드·채널·등급·추출기 버전이 붙는가
  • [ ] 낮은 등급이 높은 등급을 supersede 하는 경로가 막혀 있는가
  • [ ] "에피소드 N 유래 변경 전부 되돌리기"가 한 명령으로 되는가
  • [ ] 사용자 삭제가 요약문·임베딩·캐시·백업까지 닿는가
  • [ ] 값이 바뀌는 시나리오와 주입 시나리오가 회귀셋에 들어 있는가

회수와 주입 — 언제, 얼마나, 어디에

회수 단계의 목표는 많이 찾아 넣는 것이 아니라 질문 바로 앞에 작고 정확한 기억만 놓는 것이다. 항상 검색·도구 검색·하이브리드의 선택 기준, 질의 재작성과 재순위, 캐시를 깨지 않는 주입 위치, 최신성 가중, 기억이 답을 망치는 네 가지 경우와 사용자가 기억을 보고 고치는 UX를 2026년 9월 기준 공개 문서·논문으로 정리한다.

핵심 요점

  • Chroma의 Context Rot(2025-07, 18개 모델)은 LongMemEval 질문을 약 300토큰의 관련 컨텍스트로 풀 때가 약 113k토큰 전체 이력으로 풀 때보다 모든 모델군에서 뚜렷이 높았다고 보고해, 회수의 목표가 재현율이 아니라 작은 컨텍스트임을 보여준다.
  • 회수 시점은 항상 검색(Zep: 최근 메시지 4개를 질의로 자동 검색, 문서상 P95 200ms 미만), 도구 검색(Letta archival, Anthropic 메모리 도구), 하이브리드(상주 블록 + 도구)로 나뉘며, 놓치면 사고가 나는 사실은 검색에 맡기지 말고 상주 블록에 둔다.
  • LongMemEval은 시간 인식 질의 확장으로 시간 추론 질문의 회수 재현율이 6.8~11.3%, 사실 증강 키 확장으로 recall@k가 9.4% 올랐다고 보고했고, 독립 연구 MemDelta(2026-06)는 임베딩 모델만 바꿔도 정확도가 6.2%p 움직인다고 밝혀 아키텍처 비교 전에 임베딩·재순위기를 고정해야 함을 시사한다.
  • Zep 공식 문서는 회수한 기억을 system·developer 메시지 같은 특권 채널이 아니라 대화 이력 뒤·최신 사용자 메시지 앞에 두라고 권하며, 이 배치는 프롬프트 캐시 프리픽스를 보존하고 비신뢰 텍스트를 지시와 분리한다.
  • 메모리는 무관 기억 주입, 낡은 기억 고착, 과잉 개인화, 인젝션으로 오염된 기억의 네 경로로 답을 망치며, 점수 컷오프와 빈 주입 허용, 회수 시점 supersede 필터와 유효기간 동봉, 스코프 분리와 작업 유형 게이팅으로 완화한다.
  • Mem0·Zep·Letta의 벤치마크 수치는 모두 자체 측정이고 Mem0와 Zep은 LoCoMo 점수를 두고 서로 반박한 상태이므로, 설계 근거로는 비교 당사자가 아닌 측정을 우선해야 한다.

회수의 목표는 재현율이 아니라 "작고 정확한 컨텍스트"다

기억이 아무리 잘 저장돼 있어도 답을 바꾸는 것은 이번 턴 프롬프트에 실제로 들어간 몇 줄이고, 많이 넣는다고 나아지지도 않는다. Chroma의 Context Rot 보고서(2025-07-14, 18개 모델)는 LongMemEval 질문을 관련 부분만 남긴 약 300토큰 프롬프트와 약 113k토큰 전체 이력으로 각각 풀게 했는데, 모든 모델군에서 짧은 쪽이 뚜렷이 높았다. 별도 실험에서는 방해 문장 하나만 섞여도 성능이 떨어졌다. 설계 질문은 셋이다 — 언제 찾고, 얼마나 넣고, 어디에 놓을 것인가. (수치는 모두 2026-09-19 확인 기준.)

언제 회수하나: 세 가지 패턴

패턴 공개 구현 예 약점
항상 검색 후 주입 Zep thread.get_user_context — 스레드의 최근 메시지 4개를 질의로 자동 검색, 문서상 P95 200ms 미만 필요 없는 턴에도 비용·잡음
모델이 도구로 검색 Letta archival_memory_search, Anthropic 메모리 도구(memory_20250818, 클라이언트가 /memories 파일 연산을 실행) 모델이 "찾아볼 게 있다"는 걸 몰라 호출을 건너뜀
하이브리드 Letta 메모리 블록(라벨·설명·값·문자 상한, 프롬프트에 상주) + archival 검색 상주 블록 비대화

도구형의 약점에 대한 Anthropic의 처방: 메모리 도구를 켜면 API가 시스템 프롬프트에 "무엇보다 먼저 메모리 디렉터리를 보라"는 지시를 자동 삽입한다. 순수한 "필요 시"가 아니라 세션 시작 시 강제 1회 + 이후 자율이다. Anthropic의 컨텍스트 엔지니어링 글(2025-09-29)도 Claude Code를 CLAUDE.md는 선적재하고 나머지는 grep·glob으로 읽는 하이브리드로 설명한다.

도구형이 단발 검색보다 강할 수 있다는 근거는 Letta의 2025-08-12 글이다. 대화 이력을 파일로 넣고 grep·search_files·open·close만 준 gpt-4o-mini 에이전트가 LoCoMo 74.0%를 기록해, Mem0 논문이 보고한 자사 최고 구성(그래프) 68.5%를 넘었다. 단 측정자는 Letta 자신이고, Letta는 Mem0가 MemGPT 비교 수치를 어떻게 냈는지 문의했으나 답을 받지 못했다고 적었다.

선택 기준:

  • 첫 토큰 지연이 빡빡하고 턴이 짧다(음성·실시간) → 항상 검색 + 엄격한 컷오프.
  • 도구 사용이 안정적인 모델의 다단계 작업 → 하이브리드. 부족하면 다시 검색할 수 있다는 것이 핵심 이점.
  • 놓치면 사고가 나는 사실(알레르기, 금지 작업, 권한 범위) → 검색에 맡기지 말고 상주 블록.

질의 재작성과 재순위

마지막 발화를 그대로 임베딩하면 "그거 저번처럼 해줘"에서 무너진다.

  1. 질의 구성 — 최근 몇 턴을 합쳐 지시어를 푼 독립 질의로 재작성한다(Rewrite-Retrieve-Read, HyDE 계열). Zep은 최근 메시지 4개를 쓰는데, 그래프 검색 질의는 400자에서 잘린다. 긴 대화를 통째로 넘기면 뒤가 조용히 버려진다.
  2. 시간 표현 해석 — "지난주", "이사 전에"를 날짜 범위 필터로 바꾼다. LongMemEval(Wu 외, ICLR 2025)은 이 시간 인식 질의 확장으로 시간 추론 질문의 회수 재현율이 6.8~11.3% 올랐고, 색인 키에 추출 사실을 덧붙이는 것만으로 recall@k 9.4%, 정답률 5.4% 향상을 보고했다.
  3. 다중 신호 후보 + 재순위 — 벡터 + BM25(+ 엔티티·그래프 이웃). Zep의 기본 재순위기는 두 순위를 합치는 RRF이고, 다양성용 MMR(mmr_lambda), 중심 노드 기준 node distance, 언급 빈도 기준 episode mentions, 가장 정확하지만 느린 cross-encoder를 고를 수 있다. Mem0 플랫폼 문서는 rerank 옵션의 추가 지연을 150~200ms로 적는다.
  4. 컷오프 — top-k 고정이 아니라 재순위 점수 임계값으로 자른다. 0건을 반환할 수 있어야 한다.

튜닝 순서도 중요하다. 독립 연구 MemDelta(2026-06-29)는 임베딩 모델만 바꿔도 정확도가 6.2%p 움직였고(n=500, p=0.004), Mem0가 MiniLM 기반 RAG는 11%p 앞서지만 클라우드 임베딩 RAG에는 1.2%p 뒤졌다고 보고했다. 메모리 아키텍처를 비교하기 전에 임베딩과 재순위기부터 고정하라는 뜻이다.

토큰 예산과 주입 위치

예산은 "남는 만큼"이 아니라 상한으로 정한다. 공개된 기준점: Zep 자동 검색의 컨텍스트 블록 기본 상한은 2,500자(최대 50,000자). Mem0는 2026년 4월 알고리즘이 질의당 평균 약 6,900토큰을 쓴다고 밝혔는데(전체 컨텍스트 약 26,000토큰 대비), Mem0 자체 측정이고 같은 회사의 2026년 9월 벤치마크 가이드도 벤더 점수 대부분이 "독립 검증되지 않은 자체 보고"라고 적는다.

위치 넣을 것 주의
시스템 프롬프트 거의 안 변하는 프로필·제약(상주 블록) 바뀔 때마다 프롬프트 캐시가 깨진다. Anthropic 캐시 프리픽스는 tools→system→messages 순, 캐시 읽기는 기본 입력가의 0.1배, 5분 TTL 쓰기는 1.25배
대화 이력 뒤, 최신 사용자 메시지 앞 이번 턴 회수 기억 Zep 공식 권장. 앞부분이 불변이라 캐시 프리픽스가 보존된다
도구 결과 모델이 요청한 검색 결과 "지시"가 아닌 "데이터" 채널

Zep 문서는 컨텍스트 블록을 system·developer 메시지 같은 특권 지시 채널에 넣지 말라고 명시한다. 기억은 사용자와 외부 문서에서 온 비신뢰 텍스트이기 때문이다. 위치는 정확도도 바꾼다. Lost in the Middle(Liu 외, TACL 2023)은 관련 정보가 입력의 처음이나 끝에 있을 때 성능이 가장 높고 중간에서 크게 떨어짐을 보였다. 질문 바로 앞이 기억의 자리다.

[tools][system: 고정 지침 + 상주 프로필 블록]  ← 캐시 프리픽스(불변)
[history: 최근 대화]
[memory: 이번 턴 회수분, 사실당 1~2문장 + 유효기간]  ← 매 턴 교체
[user: 최신 메시지]

형식도 성능이다. LongMemEval에서 회수가 완벽해도 읽기 방식에 따라 최대 10점이 갈렸고, JSON 구조화 + Chain-of-Note(관련 메모를 먼저 뽑고 답하기) 조합이 가장 좋았다.

최신성 가중

출발점은 Generative Agents(Park 외, 2023)의 score = recency + importance + relevance다. 세 항을 min-max로 정규화하고 가중치는 모두 1, recency는 마지막 회수 이후 게임 시간당 0.995 지수 감쇠, importance는 LLM이 매긴 1~10점이다. 프로덕션으로 옮길 때 고칠 점:

  • 감쇠 기준 시각을 구분한다. "마지막 회수 시각" 기준은 자주 불린 기억이 계속 불리는 고착을 만든다. 사건 발생·기록·마지막 확인 시각을 따로 저장하고 기본은 발생 시각으로 둔다.
  • 유형별 반감기. 일화("어제 배포 실패")는 빠르게, 정체성·제약("땅콩 알레르기")은 감쇠 없이.
  • 감쇠보다 무효화가 먼저. supersede된 사실은 점수를 깎지 말고 필터로 제외한다. Zep 컨텍스트 블록은 사실마다 (2024-11-14 02:13:19+00:00 - present) 같은 유효 구간을 붙여 모델이 시점을 직접 판단하게 한다.

메모리가 답을 망치는 경우와 완화

① 무관 기억 주입. Shi 외(ICML 2023)는 GSM-IC로 무관한 문장이 끼면 정답률이 급락함을 보였고, "무관한 정보는 무시하라"는 지시와 self-consistency를 완화책으로 제시했다. 실무 완화: 점수 컷오프와 빈 주입, 메모리 블록 머리에 "현재 요청과 무관하면 쓰지 말 것", 평가셋에 기억이 필요 없는 질문을 넣어 메모리 on/off 정답률 비교.

② 낡은 기억 고착. "서울 거주"와 "부산으로 이사"가 함께 회수되면 모델은 더 자주 등장한 쪽으로 기운다. LongMemEval 저자들은 같은 GPT-4o인데 이력을 통째로 읽힌 조건 대비 ChatGPT는 37%, Coze는 64% 성능이 하락했다고 보고했고, MemoryAgentBench(2025-07 공개, 2026-06 개정)는 선택적 망각을 포함한 네 역량을 모두 갖춘 방법이 아직 없다고 결론냈다. 완화: 회수 시점 supersede 필터, 날짜 동봉, "충돌하면 현재 대화 우선" 규칙, 충돌 감지 시 되묻기.

③ 과잉 개인화. Simon Willison은 2025-05-21 글에서, 개에게 펠리컨 의상을 입힌 그림을 요청했더니 요청한 적 없는 "Half Moon Bay" 표지판이 그려졌다고 보고했다. 과거 대화로 만든 사용자 요약이 시스템 프롬프트에 통째로 주입된 탓이었다. Anthropic도 메모리 출시 공지(2025-09-11)에서 기억이 해로운 패턴을 강화하거나 과잉 영합으로 이어지는지 테스트하고 조정했다고 밝혔다. 완화: 프로젝트·워크스페이스 단위 스코프 분리, 작업 유형 게이팅(번역·코드 리뷰·이미지 생성엔 개인 기억 미주입), 민감 범주는 명시 요청 때만.

④ 오염된 기억. Johann Rehberger의 SpAIware(2024-09-20)는 웹페이지의 프롬프트 인젝션이 ChatGPT 메모리에 지시를 심어 이후 대화를 계속 유출시킨 사례다. 유출 경로는 macOS 앱 1.2024.247에서 막혔지만 인젝션으로 기억이 쓰이는 문제는 남았다고 그는 적었다. 회수된 기억을 지시 채널에 넣지 말아야 하고, 아래의 가시성이 곧 보안 통제인 이유다.

기억을 보여주고 고치게 하는 UX

Claude는 기억 요약을 사용자가 열람하고 대화로 수정할 수 있고, 프로젝트마다 기억이 분리되며, 기억에 남지 않는 시크릿 채팅을 제공한다(Pro·Max 확대 2025-10-23). 반대로 Willison의 불만은 요약이 보이지 않고 어느 대화가 영향을 줄지 고를 수 없다는 점이었다. 자체 제품용 체크리스트:

  • [ ] 답변마다 사용된 기억을 펼쳐볼 수 있다(원 세션·날짜 포함). 회수된 기억 id를 서버 로그에도 남겨 오답 디버깅에 쓴다.
  • [ ] 기억이 쓰일 때 알림이 뜨고 그 자리에서 되돌릴 수 있다.
  • [ ] 수정은 덮어쓰기가 아니라 supersede로 들어가고, 삭제는 파생 요약·임베딩·캐시까지 전파된다.
  • [ ] 기억 없이 대화하는 모드와 범위(개인·프로젝트·조직) 전환이 있다.
  • [ ] "왜 이렇게 답했어?"에 근거 기억을 제시하고, "그거 틀렸어" 한마디가 곧 갱신 파이프라인의 입력이 된다.

이 절에 나온 벤더 수치의 측정 주체와 반박

  • Mem0 논문(2025-04) — LoCoMo LLM-judge에서 OpenAI 메모리 대비 26% 상대 향상, 전체 컨텍스트 대비 p95 지연 91% 감소·토큰 90% 이상 절감. Mem0 자체 측정. Zep이 2025-05-06 글로 반박했다(Mem0가 Zep을 잘못 구성했고, 재측정하면 Zep 75.14%±0.17이며, 전체 컨텍스트 기준선 약 73%가 Mem0 최고치 약 68%보다 높다는 주장). Mem0 CTO는 2025-05-08 GitHub 이슈에서 Zep의 실제 점수는 58.44%±0.20이라고 재반박했고, 확인 시점에 Zep의 공개 답변은 보이지 않았다.
  • Zep 논문(2025-01) — DMR 94.8%(MemGPT 93.4%), LongMemEval 정확도 최대 18.5% 향상·지연 90% 감소. Zep 자체 측정.

양쪽 다 자기 제품이 이기는 표다. 설계 근거로는 LongMemEval·Context Rot·MemDelta처럼 비교 당사자가 아닌 쪽의 측정을 우선하라.

평가 — LoCoMo, LongMemEval, 그리고 벤더 수치 논쟁

LoCoMo·LongMemEval·DMR이 각각 어떤 기억 능력을 재는지, 같은 시스템의 LoCoMo 점수가 측정 주체에 따라 58.44%에서 84%까지 갈린 벤더 논쟁에서 무엇을 배울지 정리한다. 전체 컨텍스트 베이스라인과의 교차점을 찾는 법, 자체 평가 셋 설계, 운영에서 계속 봐야 할 지표까지 다룬다.

핵심 요점

  • DMR은 대화당 메시지 60개라 전체 대화 베이스라인(94.4%)이 메모리 시스템(94.8%)과 사실상 같아 변별력이 없고, LongMemEval의 다섯 능력(정보 추출·다중 세션·시간 추론·지식 갱신·기권)이 파이프라인 단계별 결함을 가장 잘 드러낸다.
  • Zep의 LoCoMo 점수는 Mem0 측정 65.99%, Zep 초기 주장 84%, Mem0 재계산 58.44%, Zep 정정 75.14%로 네 번 바뀌었고, 원인은 경쟁 제품 오설정과 범주 5를 둘러싼 분자·분모 불일치였다.
  • Mem0 자체 논문에서도 전체 컨텍스트가 J 72.90으로 Mem0 그래프 변형 68.44를 앞섰으며, 메모리 계층의 이득은 p95 지연 17.1초 대 1.44초와 토큰 절감 쪽에 있다.
  • 2026년 2월 제3자 감사는 LoCoMo 1,540문항 중 99개(6.4%)의 정답 키 오류로 상한이 93.57%이고 판정 LLM이 모호한 의도적 오답의 62.81%를 통과시킨다고 보고했으므로, 90점대 자체 보고 수치는 이 상한과 함께 읽어야 한다.
  • 자체 평가 셋은 증거 위치 라벨, 구값을 stale로 따로 세는 지식 갱신 문항, 5~10%의 기권 문항, 도구 실행 결과 회상 유형을 갖추고 오라클·메모리·전체 컨텍스트 세 조건으로 돌린다.
  • 운영에서는 회수 적중률, 주입 토큰 p95, 오염률(stale 주입과 스코프 누출 분리), 검색·쓰기 지연과 쓰기 후 가시성 지연, 거짓 회상률을 계속 추적한다.

벤치마크가 실제로 재는 것

메모리 벤치마크는 모두 "긴 대화를 넣고 나중에 묻는다"는 형식이지만 난이도와 재는 능력이 다르다. 2026년 9월 19일 기준, 벤더 발표에 가장 자주 등장하는 세 가지는 다음과 같다.

벤치마크 출처 규모 재는 능력 알려진 한계
DMR MemGPT 논문(2023-10) 대화당 메시지 60개 단일 턴 사실 회수 전체가 컨텍스트에 들어가 변별력이 없다
LoCoMo Maharana 외(2024-02) 논문 기준 평균 300턴·최대 35세션, 공개본은 대화 10개 single-hop, multi-hop, temporal, open-domain, adversarial 짧고, 정답 키 오류가 있고, 5번 범주는 사실상 버려진다
LongMemEval Wu 외(ICLR 2025) 500문항, S는 문제당 약 115k 토큰, M은 500세션·약 1.5M 토큰 정보 추출, 다중 세션 추론, 시간 추론, 지식 갱신, 기권 채팅 어시스턴트 한정, 도구 실행 기억은 없다

DMR은 이제 근거로 쓰기 어렵다. Zep 논문(2025-01)은 Zep 94.8% 대 MemGPT 93.4%(gpt-4-turbo)를 보고하면서도, 같은 표에 대화 전체를 그대로 넣은 베이스라인 94.4%를 실었다. gpt-4o-mini에서는 전체 대화 98.0%, Zep 98.2%다. 저자들 스스로 "메시지 60개라 컨텍스트에 쉽게 들어가고, 단일 턴 사실 회수 질문뿐"이라고 한계를 적었다.

LoCoMo의 원 지표는 F1 부분 일치였지만, 벤더들은 LLM-as-a-Judge 점수(J)로 바꿔 보고한다. 공개 저장소의 README에 따르면 현재 배포본은 초기 50개 대화 중 가장 긴 10개만 남긴 서브셋이고, Mem0 논문은 대화당 평균을 약 26k 토큰으로 적는다. 최신 모델의 컨텍스트 창에는 여유 있게 들어가는 크기다.

LongMemEval은 능력 분해가 가장 쓸 만하다. 7개 문항 유형(single-session-user/assistant/preference, multi-session, knowledge-update, temporal-reasoning, 그리고 ID에 _abs가 붙은 기권 문항 30개)이 5개 능력으로 묶이고, 증거 세션을 무관한 세션 더미에 끼워 넣는 구조라 히스토리 길이를 자유롭게 늘릴 수 있다. 채점은 GPT-4o 판정이며 논문은 인간 전문가와 97% 이상 일치한다고 보고한다. 같은 논문에서 증거만 주고 읽게 한 GPT-4o는 0.9184였는데, 같은 내용을 대화로 누적시킨 ChatGPT는 0.5773, Coze는 0.3299였다. 2025년 9월에 정답 간섭을 제거한 정제판이 나왔으므로, 점수를 비교할 때는 어느 판인지 확인해야 한다.

다섯 능력은 파이프라인 단계와 거의 1:1로 대응한다. 단일 세션 회상은 추출·색인, 다중 세션 추론은 회수 폭과 합성, 시간 추론은 타임스탬프 메타데이터, 지식 갱신은 supersede, 기권은 회수 임계치와 "모른다"고 답하는 읽기 프롬프트를 시험한다. 총점 하나보다 유형별 점수가 어디를 고쳐야 하는지 알려 준다.

후속 벤치마크도 봐 둘 만하다. MemoryAgentBench(2025-07, 최신 개정 2026-06)는 정확한 회수, 테스트 시점 학습, 장거리 이해, 선택적 망각의 네 역량을 나누고 "네 가지를 모두 해내는 방법은 없다"고 결론지었다. BEAM(2025-10, 개정 2026-02)은 대화 100개·검증 문항 2,000개에 대화당 최대 10M 토큰까지 늘려, 전체 컨텍스트 방식이 물리적으로 성립하지 않는 구간을 만든다.

벤더 수치 논쟁: 같은 시스템에 점수가 네 개

시점 누가 측정했나 주장 반박·조건
2025-01 Zep(자체) LongMemEval에서 정확도 최대 18.5% 향상, 지연 약 90% 감소. gpt-4o 기준 전체 컨텍스트 60.2% → Zep 71.2%, 컨텍스트 115k → 1.6k 토큰 같은 논문에서 single-session-assistant 유형은 gpt-4o 기준 17.7% 하락
2025-04 Mem0(자체, 경쟁 제품도 직접 구동) LoCoMo J: Mem0 66.88, 그래프 변형 68.44, Zep 65.99, LangMem 58.10, OpenAI 메모리 52.90. gpt-4o-mini, 10회 반복, 5번 범주 제외 같은 표의 전체 컨텍스트가 72.90으로 가장 높다
2025-05-06 Zep(블로그) Mem0가 Zep을 잘못 구성했다(두 화자 모두 user 역할, 타임스탬프를 전용 필드 대신 본문에 부착, 검색을 순차 실행). 올바른 구성이면 84% 이틀 뒤 Mem0 CTO가 GitHub 이슈로 반박
2025-05-08 Mem0(이슈) Zep이 제외한 5번 범주를 분자에는 넣고 분모에서는 뺐다. 바로잡으면 58.44% ±0.20. 프롬프트 변경과 단일 실행도 지적 Zep은 계산 오류를 인정하고 글을 75.14% ±0.17로 고쳤다
2025-08-12 Letta(자체) 대화를 파일로 두고 grep·search_files·open 도구만 준 에이전트가 gpt-4o-mini로 74.0%. Mem0 그래프 변형 보고치 68.5%보다 높다 Mem0가 보고한 MemGPT 수치의 산출 방법 문의에 답을 받지 못했다고 명시
2026-02 제3자 감사(GitHub locomo-audit) 1~4번 범주 1,540문항 중 99개(6.4%)의 정답 키가 틀려 이론상 상한 93.57%. 판정 LLM은 "모호하지만 주제에 맞는" 의도적 오답의 62.81%를 정답 처리 5번 범주 446문항(22.5%)은 대부분 평가에서 빠지고, 범주 표본이 96~841개로 편차가 크다
2026-04-16 Mem0(자체) 새 알고리즘으로 LoCoMo 92.5, LongMemEval 94.4, 회수당 약 6.9k 토큰 연구 페이지 본문에서 판정 모델과 반복 횟수는 확인하지 못했다. 위 감사 상한과 1.1점 차이다

Zep이라는 한 시스템의 LoCoMo 점수가 65.99(Mem0 측정), 84(Zep 초기), 58.44(Mem0 재계산), 75.14(Zep 정정)로 네 번 바뀌었다. 여기서 얻을 교훈은 구체적이다.

  • 경쟁 제품을 직접 돌린 수치는 절반만 믿는다. 역할 매핑과 타임스탬프 필드 같은 통합 세부가 10점 가까이 움직인다. 비교가 필요하면 상대 벤더의 공식 예제 구성으로 돌린다.
  • 분모를 먼저 고정한다. 범주 포함 여부와 문항 수를 표 머리에 적는다. 84%는 분자와 분모의 범주가 달라서 나온 숫자였다.
  • 반복 실행과 분산. 위 감사에 따르면 다회 실행 방법론을 문서화한 곳은 Mem0뿐이었고, 범주별 인접 비교의 56%는 통계적으로 구분되지 않았다. open-domain은 15점 이상 벌어져야 구분된다.
  • 판정기를 검증한다. 판정 프롬프트가 다르면 점수는 비교 불가다. 관대한 판정기는 장황하고 두루뭉술한 답을 내는 시스템에 유리하다.
  • 90점대 LoCoMo 점수는 상한 93.57%와 함께 읽는다. 같은 감사는 범주별 상한을 넘는 보고치도 찾아냈다. 남은 차이는 기억력보다 정답 키 잡음일 가능성이 있다.
  • 점수만 있고 토큰·지연이 없는 표는 반쪽이다. 메모리 계층을 쓰는 이유가 거기 있기 때문이다.

Letta의 지적도 새겨 둘 만하다. LoCoMo류는 회수 벤치마크이지 에이전트가 언제 무엇을 기억하고 찾을지 스스로 결정하는 능력의 벤치마크가 아니다. 범용 파일 도구를 쥔 에이전트가 전용 메모리 제품 보고치를 넘었다는 결과는, 회수 메커니즘만큼 에이전트의 도구 사용 능력이 점수를 좌우한다는 뜻이다.

전체 컨텍스트 베이스라인을 먼저 돌려라

Mem0 논문의 표를 그대로 읽으면, 히스토리 전체를 프롬프트에 넣는 방식이 J 72.90으로 Mem0(66.88)와 그래프 변형(68.44)을 앞선다. 대신 p95 전체 지연이 17.117초 대 1.440초이고, 토큰은 대화당 약 26k 대 약 7k다. 메모리 계층은 정확도가 아니라 비용과 지연으로 정당화된 셈이다. "전체 컨텍스트 대비 지연 91% 감소"라는 헤드라인은 이 맥락에서 읽어야 한다.

히스토리가 115k 토큰인 LongMemEval S에서는 관계가 뒤집힌다. Zep 논문에서 전체 컨텍스트는 gpt-4o 60.2%, gpt-4o-mini 55.4%였고 Zep은 각각 71.2%, 63.8%였다. LongMemEval 논문도 증거 세션만 준 오라클 조건 대비 전체 히스토리 조건에서 GPT-4o가 30.3%, Llama 3.1 70B가 55.1% 떨어진다고 보고한다.

의사결정 기준은 단순하다.

  1. 자기 데이터로 오라클 / 메모리 시스템 / 전체 컨텍스트 세 조건을 같은 문항에 돌린다.
  2. 히스토리 길이를 구간별로 늘려 가며 정확도 곡선을 그리고, 전체 컨텍스트가 메모리 시스템 아래로 내려가는 교차점을 찾는다.
  3. 사용자의 p90 히스토리 길이가 교차점보다 짧다면 프롬프트 캐싱을 곁들인 전체 컨텍스트가 더 정확하고 더 단순하다. 메모리 계층은 세션을 넘는 누적, 비용 상한, 지연 예산이 요구될 때 넣는다.
  4. 오라클과 메모리 시스템의 차이는 회수 손실, 오라클과 100%의 차이는 읽기 손실이다. 어느 쪽이 큰지에 따라 고칠 곳이 달라진다.

자체 평가 셋 구축법

공개 벤치마크는 일상 대화다. 도구 실행 결과, 설정값, 사용자의 업무 제약을 기억해야 하는 프로덕션 에이전트에는 자체 셋이 필요하다. LongMemEval의 구성법을 그대로 빌리면 된다.

- id: ku-017
  type: knowledge_update   # single | multi | temporal | knowledge_update | abstain | tool_result
  haystack_sessions: 40    # 무관한 세션을 섞어 길이를 조절
  evidence:
    - {session: s03, turn: 12, fact: "배포 리전 = ap-northeast-2", at: 2026-03-02}
    - {session: s11, turn: 4,  fact: "배포 리전 = us-west-2", at: 2026-05-20, supersedes: "s03#12"}
  question: "지금 배포 대상 리전이 어디지?"
  asked_at: 2026-06-01
  gold: "us-west-2"
  stale_answers: ["ap-northeast-2"]   # 오답이 아니라 stale로 따로 집계
  • 유형을 여섯 개로 나눈다. LongMemEval의 다섯 능력에 도구 실행 결과 회상(지난 세션의 명령 출력, 실패 원인, 승인 이력)을 더한다.
  • 증거 위치를 라벨링한다. 세션·턴 ID가 있어야 회수 단계(recall@k)와 읽기 단계(QA 정확도)를 따로 잴 수 있다.
  • 지식 갱신 문항은 (구값, 신값, 갱신 시각)으로 만든다. 구값을 답하면 stale로 따로 세어 supersede 결함을 일반 오답과 구분한다.
  • 기권 문항을 5~10% 넣는다. 일어난 적 없는 사건을 전제로 묻고, "기록에 없다"가 정답이다. 회수 임계치를 낮출수록 이 점수가 떨어지는지 본다.
  • 판정기를 먼저 시험한다. 모델과 프롬프트 버전을 고정하고, 50~100건을 사람이 채점해 일치율을 재고, 의도적으로 모호한 오답을 넣어 통과율을 확인한다. 통과율이 높으면 판정 프롬프트에 "구체 값이 일치해야 정답"을 명시한다.
  • 유형당 100문항 이상을 목표로 한다. 96문항 범주에서 15점 차이도 구분되지 않았다는 감사 결과가 근거다. 점수는 Wilson 구간과 함께 보고한다.
  • 회귀 게이트로 쓴다. 추출 프롬프트, 임베딩 모델, supersede 규칙, top-k를 바꾸는 PR마다 돌리고 유형별 점수·주입 토큰·p95 지연을 함께 비교한다.

운영 지표

오프라인 점수는 배포 시점의 스냅샷이다. 운영에서는 아래를 계속 본다. 기준값은 출발점 예시이며 자기 트래픽에 맞춰 조정한다.

지표 정의 계측 방법 경보 기준 예시
회수 적중률 주입한 메모리 중 응답이 실제로 사용한 비율. 오프라인에서는 증거 recall@k 응답에 사용한 메모리 ID를 구조화 출력으로 남기거나 주 단위 표본을 LLM 판정 2주 이동 평균이 기준선 대비 10%p 하락
주입 토큰 턴당 메모리 블록 토큰의 p50·p95와 프롬프트 내 비중 프롬프트 조립 단계에서 계수 p95가 예산 초과. 참고로 벤치마크 보고치는 Zep 약 1.6k, Mem0 약 7k
오염률 주입된 메모리 중 틀렸거나, 이미 대체됐거나, 다른 사용자·스코프의 것인 비율 주 200건 표본 감사. stale 주입과 스코프 누출을 분리 집계 스코프 누출은 1건도 장애로 취급, stale 주입은 추세 관리
지연 검색 p50·p95, 추출·쓰기 지연, 쓰기 후 회수 가능해지기까지의 시간 단계별 스팬 추적 검색 p95가 응답 지연 예산의 일정 비율 초과
기권·거짓 회상 기록이 없을 때 "모른다"고 답한 비율과, 없는 사실을 기억처럼 말한 비율 기권 문항을 합성 트래픽으로 주기 주입 거짓 회상률 상승
저장소 건전성 사용자당 메모리 수 증가율, 중복률, supersede 체인 길이 일 배치 집계 중복률 상승은 추출 프롬프트 회귀의 신호

Mem0 논문의 p95 검색 0.200초·전체 1.440초, Zep 논문의 LongMemEval 응답 2.58초(gpt-4o)가 참고선이 되지만, 모두 벤더가 자기 환경에서 잰 값이다. 특히 쓰기 후 가시성 지연은 어떤 공개 벤치마크도 재지 않는다. 추출이 비동기라면 "방금 말한 것을 다음 턴에 모르는" 구간이 생기므로, 최근 N턴의 원문 버퍼로 그 틈을 메우고 있는지 별도 테스트로 확인해야 한다.

구현 가이드 — 단계별 도입과 안티패턴

에이전트 메모리는 세션 요약 파일 → 사실 추출+키-값 → 하이브리드 검색 → 시간축 그래프 순으로, 아래 칸이 실패한 증거가 있을 때만 올린다. 비용은 임베딩이 아니라 추출 LLM 호출 수와 회수 주입 토큰이 정하고, 벤더 벤치마크는 측정 주체마다 같은 시스템이 58~75%로 갈리므로 자기 트래픽 골든셋과 full-context 기준선으로 판정해야 한다.

핵심 요점

  • Mem0 자체 논문에서도 full-context(72.90%)가 Mem0(66.88%)보다 정확했고 메모리 계층이 이긴 축은 p95 지연(1.44초 대 17.12초)과 토큰(약 7k 대 26k)이므로, 기록이 컨텍스트에 들어가는 동안은 세션 요약 파일이 정확도 기준선이다.
  • 다음 단계로 올라갈 신호는 구체적이어야 한다: 파일이 한 번에 안 읽히거나 모순이 공존하면 ②, 키를 모르는 질의가 회수 실패의 다수면 ③, 시간 추론·지식 갱신 질의가 실패의 주류면 ④다.
  • Mem0 v3가 추출을 단일 호출 ADD-only로 바꾸면서 '지금 무엇이 참인가'의 판정이 읽기 쪽으로 넘어왔으므로, supersede 열과 유효 구간을 직접 쥐거나 시간축 그래프를 써야 한다.
  • 멀티테넌트 격리는 스코프를 서버가 인증 컨텍스트에서 주입하고 벡터 검색을 pre-filter로 돌리며, 삭제가 source_ref를 따라 임베딩·엣지·요약까지 연쇄되도록 설계한다.
  • 월 10만 세션 기준 gpt-4o-mini 추출은 약 $78인데 임베딩은 $0.30 수준이라, 비용 모델의 변수는 추출 호출 수와 턴당 주입 토큰이고 매니지드는 과금 단위(요청 수·바이트)가 설계를 끌고 간다.
  • LoCoMo에서 Zep 점수는 Mem0 측정 65.99%, Zep 재측정 75.14%, Mem0 재반박 58.44%로 갈렸고 MINJA는 질의만으로 기억을 오염시킬 수 있음을 보였으므로, 도입 전 골든셋 평가와 append+supersede·출처 등급 기반 쓰기 통제가 필수다.

성숙도 사다리 — 위 칸은 아래 칸이 실패한 증거가 있을 때만 오른다

메모리 계층은 "기능"이 아니라 LLM이 쓰기 경로에 끼어 있는 데이터베이스다. 칸을 하나 올릴 때마다 쓰기 비용(LLM 호출)과 틀릴 수 있는 지점이 같이 늘어난다. 아래 수치와 가격은 모두 2026년 9월 19일에 원문을 열어 확인한 것이다.

단계 구성 쓰기 1회 비용 다음 칸으로 올라갈 신호
① 세션 요약 파일 사용자·프로젝트별 마크다운, 세션 시작 때 통째로 읽음 요약 LLM 1회 파일이 한 번에 안 읽힘, 모순된 두 줄이 공존, 동시 쓰기 충돌
② 사실 추출 + 키-값 (scope, subject, predicate) → value 행, supersede 열 추출 LLM 1~2회 키를 모르는 질의("지난번 그 배포 이슈")가 회수 실패의 다수
③ 하이브리드 검색 ②에 BM25 + 임베딩 + RRF 융합 + 임베딩 1회 "언제부터 바뀌었지" 같은 시간 질의, 엔티티 다중 홉, 감사 요구
④ 시간축 그래프 엔티티·엣지에 유효 구간, 무효화 에피소드당 LLM 여러 회 —

① 세션 요약 파일

가장 과소평가된 칸이다. Anthropic의 memory tool(memory_20250818)은 /memories 아래 파일을 view/create/str_replace/insert/delete/rename 여섯 명령으로 다루는 클라이언트 측 도구라, 저장소는 여러분 인프라에 그대로 남는다. Letta는 2025-08-12 블로그에서 파일시스템 도구만 쥔 GPT-4o mini 에이전트가 LoCoMo 74.0%를 냈다고 발표했다(Letta 자체 측정, 비교 대상은 Mem0 논문의 그래프 변형 68.5%). Mem0 자신의 논문(2025-04-28, Mem0 측정, GPT-4o-mini·10회 반복)에서도 대화 전체를 넣는 full-context가 72.90%로 Mem0 66.88%, 그래프 변형 68.44%보다 높았다. 메모리 계층이 이긴 축은 정확도가 아니라 p95 지연(1.44초 대 17.12초)과 토큰(약 7k 대 약 26k)이다. 기록이 컨텍스트에 들어가는 동안은 ①이 정확도 기준선이고, 위 칸은 이 기준선을 상대로 평가해야 한다.

올라갈 신호는 구체적이다. memory tool 문서상 view는 16,000자를 넘는 파일을 잘라 보여 준다. 그 크기를 넘기 시작했거나, "선호 배포 리전: 서울"과 "도쿄"가 한 파일에 같이 살아 있으면 ②로 간다.

② 사실 추출 + 키-값, 그리고 supersede

LangChain 문서는 장기 기억을 namespace(폴더)와 key(파일명)로 조직하고, 저장 형태를 profile(JSON 한 장을 계속 갱신)과 collection(문서를 계속 추가)으로 나눈다. profile은 커질수록 갱신 오류가 늘고, collection은 회수율이 높은 대신 갱신·검색이 복잡해진다는 것이 문서의 요지다. 실무 절충은 "추가만 하되 옛 행을 가리키는 열을 둔다"이다.

CREATE TABLE memory_fact (
  id uuid PRIMARY KEY,
  org_id text NOT NULL, user_id text, agent_id text,     -- scope
  subject text, predicate text, value jsonb,
  source_kind text CHECK (source_kind IN
    ('user_stated','tool_observed','agent_inferred')),   -- 신뢰 등급
  source_ref text NOT NULL,                              -- 원문 턴·도구 호출 id
  valid_from timestamptz NOT NULL, valid_to timestamptz, -- 사실이 참이던 구간
  recorded_at timestamptz DEFAULT now(),                 -- 시스템이 안 시각
  superseded_by uuid REFERENCES memory_fact(id)
);
-- 현재 사실 = superseded_by IS NULL AND valid_to IS NULL

주의할 최신 변화가 있다. Mem0는 v3에서 추출을 "Single-pass ADD-only (one LLM call, no UPDATE/DELETE)"로 바꿨고(마이그레이션 문서), 플랫폼 문서도 새 기억이 기존 기억을 덮어쓰거나 지우지 않는다고 적는다. 쓰기는 싸고 빨라졌지만 "지금 무엇이 참인가"의 판정이 읽기 쪽으로 넘어왔다. 위 스키마처럼 supersede를 직접 쥐거나 ④로 가야 하는 이유다.

쓰기 시점도 정해야 한다. LangChain 문서의 구분대로 핫패스 쓰기는 즉시 반영되지만 지연을 먹고, 백그라운드 쓰기는 지연이 없는 대신 트리거 주기를 설계해야 한다. 기본값은 세션 종료 또는 N턴 유휴 시 백그라운드 추출이다.

③ 하이브리드 검색

스코프로 먼저 거르고(pre-filter) 그 안에서 BM25와 벡터를 각각 돌려 RRF로 합친다. Elasticsearch 문서 기준 RRF는 1/(k + rank) 합산이고 rank_constant 기본값은 60이며, 서로 무관한 점수 체계를 튜닝 없이 합칠 수 있다는 것이 장점이다. 함정은 임베딩이 비동기일 때 방금 쓴 기억이 벡터 갈래에 안 보이는 구간이다. 어휘 갈래가 그 구간을 덮는지 테스트로 고정하라.

④ 시간축 그래프

Graphiti(Apache-2.0)는 사실에 유효 구간을 두고 "old facts are invalidated — not deleted" 방식으로 갱신하며, 의미 임베딩·BM25·그래프 순회를 결합해 회수한다. Neo4j·FalkorDB·Neptune이 필요하고, 구조화 출력을 지원하는 LLM을 요구하며, 429를 피하려고 SEMAPHORE_LIMIT 기본값을 10으로 둘 만큼 수집이 LLM 호출에 무겁다. Zep 논문(2025-01, Zep 측정)은 DMR 94.8% 대 MemGPT 93.4%, LongMemEval에서 최대 18.5% 정확도 향상과 지연 90% 감소를 주장한다. 시간 추론·지식 갱신 질의가 평가셋 실패의 주류가 아닐 때 ④는 과투자다.

멀티테넌트 격리와 권한

스코프 축은 제품마다 이름만 다르다. Mem0는 user_id·agent_id·app_id·run_id, LangGraph는 namespace 튜플, Letta는 블록 단위 공유와 read_only 플래그다. 직접 만들든 사 오든 규칙은 같다.

  • 스코프는 서버가 인증 컨텍스트에서 주입한다. LLM이 도구 인자로 넘긴 user_id를 믿으면 프롬프트 인젝션 한 번으로 남의 기억이 읽힌다.
  • 벡터 검색은 pre-filter. top-k를 뽑은 뒤 거르면 타 테넌트 결과가 자리를 차지해 회수율이 테넌트 수에 따라 조용히 떨어진다.
  • 조직 공유 기억은 기본 읽기 전용. 개인 → 조직 승격은 별도 쓰기 경로와 승인자를 둔다.
  • 삭제는 파생물까지 연쇄. 원문 턴을 지우면 거기서 나온 사실 행·임베딩·그래프 엣지·요약도 source_ref로 찾아 지운다. 출처 열이 없으면 삭제 요청에 답할 수 없다.

비용 모델

월 비용 ≈ 세션 수 × 추출 호출 수 × (입력 토큰 × 단가_in + 출력 토큰 × 단가_out)
        + 임베딩 토큰 × 단가_emb + 저장
        + 턴 수 × 주입 토큰 × 메인 모델 단가_in      ← 흔히 빠뜨리는 항

OpenAI 가격표 기준 gpt-4o-mini는 입력 $0.15·출력 $0.60/1M 토큰(Batch는 절반), text-embedding-3-small은 $0.02/1M이다. 월 10만 세션, 세션당 추출 입력 4,000·출력 300 토큰이면 추출이 $60 + $18 = $78(Batch $39), 사실 5개 × 30토큰 임베딩은 $0.30이다. 임베딩과 저장은 오차 수준이고 추출 호출 수와 회수 주입 토큰이 비용을 정한다. 2회 호출(추출 + 갱신 판정) 구조는 곧바로 두 배이고, 그래프 수집은 그보다 많다.

매니지드는 과금 단위가 다르다. Mem0는 요청 수(Starter $19: add 5만·검색 5천, Pro $249: add 50만·검색 5만)로, 검색 한도가 add의 1/10이라 매 턴 검색하는 설계는 검색 쿼터가 먼저 찬다. Zep은 바이트(350바이트당 1크레딧, Flex $125에 5만 크레딧, 초과분 1만 크레딧당 $25)라 도구 출력을 통째로 넣으면 바로 비싸진다. Letta API 플랜은 월 $20에 활성 에이전트당 $0.10과 LLM 사용량이 붙는다.

Build vs 오픈소스 vs 매니지드

직접 구축 (Postgres + FTS + pgvector) 오픈소스 셀프호스트 (Mem0 OSS·Graphiti·Letta) 매니지드 (Mem0·Zep·Letta Cloud)
맞는 단계 ①~③ ②~④ ②~④
고를 때 데이터 거주 규제, 기존 RLS·감사 체계 재사용, supersede 규칙을 직접 쥐어야 할 때 추출 프롬프트·그래프 로직을 재발명하기 싫고 운영 인력이 있을 때 팀이 작고 2주 안에 검증해야 할 때
숨은 비용 추출 프롬프트 회귀 테스트, 중복 병합 버전 업이 의미론을 바꾼다(Mem0 v3의 ADD-only 전환) → 버전 핀 + 회귀 평가 원문 대화가 외부로 나감, 과금 단위에 설계가 끌려감
출구 자유 Zep Community Edition은 2025-04-02 지원 종료 공지, 셀프호스트는 Graphiti 위에 서비스 계층을 직접 얹어야 함 원문 + source_ref를 자기 쪽에도 보관해 재추출 가능하게

안티패턴

모든 걸 기억한다. 추출 게이트 없이 턴마다 저장하면 회수 품질이 기억 수에 반비례한다. 저장 조건을 "다음 세션에도 참일 것, 재사용될 것, 출처가 있을 것" 셋으로 고정하고, 도구 실행은 출력 전문이 아니라 결과에서 확인된 사실만 남긴다. memory tool 문서도 민감정보 검증, 파일 크기 상한, 장기 미접근 파일 만료를 구현자 책임으로 못 박는다.

검증 없는 자기 수정. 에이전트가 자기 기억을 직접 덮어쓰면 한 번의 오추론이 영구 사실이 된다. MINJA 논문(2025-03 제출, 2026-02 v5)은 메모리 저장소에 접근하지 않고 질의만으로 악성 레코드를 기억에 심는 공격을 보였다. 방어는 구조로 한다: 쓰기는 append + supersede(물리 삭제 금지, 롤백 가능), agent_inferred 등급은 사용자 확인 전까지 회수 가중치를 낮추고, 웹·도구 출력에서 온 문장은 절대 지시문(절차 기억)으로 승격하지 않는다.

평가 없는 도입. 같은 LoCoMo에서 Zep의 점수는 Mem0 논문에선 65.99%, Zep의 반박 블로그(2025-05-06)에선 설정 오류 3건을 고친 75.14 ± 0.17%(초기 발표치는 계산 오류를 인정하고 정정), Mem0 CTO의 재반박 이슈(2025-05-08)에선 58.44%다. Mem0는 v3 문서에서 LoCoMo 71.4 → 91.6, LongMemEval 67.8 → 93.4를 내세우지만 자체 발표이며 독립 재현은 확인하지 못했다. 벤더 수치는 후보를 추리는 데만 쓰고, 판정은 자기 트래픽에서 뽑은 50~100문항으로 한다. 문항은 LongMemEval의 다섯 능력(정보 추출·다중 세션 추론·시간 추론·지식 갱신·기권)에 고르게 배분하고, 기준선으로 ①과 full-context를 반드시 같이 돌린다.

도입 체크리스트

  • [ ] 골든셋 50문항 이상, 다섯 능력별 점수와 full-context 기준선 기록
  • [ ] 모든 기억 행에 source_ref·source_kind·recorded_at
  • [ ] 갱신은 supersede, 물리 삭제는 사용자 삭제 요청 경로에서만(파생물 연쇄 포함)
  • [ ] 스코프는 서버 주입, 벡터 검색은 pre-filter, 교차 테넌트 회수 테스트 존재
  • [ ] 추출은 백그라운드, 실패 시 재시도 큐와 멱등 키(세션 id + 턴 범위)
  • [ ] 방금 쓴 기억이 임베딩 전에도 회수되는지 테스트
  • [ ] 세션당 추출 호출 수·토큰, 턴당 주입 토큰을 대시보드로 계측
  • [ ] 기억 수 상한과 만료 정책, 민감정보 스크러버
  • [ ] 라이브러리·추출 프롬프트 버전 핀, 업그레이드 전 골든셋 회귀
  • [ ] 사용자가 자기 기억을 보고 고치고 지울 수 있는 화면 또는 API
이 글은 AI 리서치 파이프라인으로 작성되고 사람이 검수했습니다. 섹션마다 1차 출처를 표기합니다.