Elasticsearch로 만든 Agent 영구 메모리 레이어 - R@10 0.89 달성기
We built a persistent agent memory layer on Elasticsearch with 0.89 recall
TL;DR Highlight
AI 에이전트가 세션이 끝나도 사용자 정보를 기억할 수 있도록 Elasticsearch 위에 구축한 멀티테넌트 장기 메모리 시스템 아키텍처 공개. 168개 질문 기준 R@10 0.89, 테넌트 간 데이터 누출 0건을 달성한 구체적인 구현 방법을 담았다.
Who Should Read
AI 에이전트를 개발 중인데 '이전 대화에서 사용자가 말한 걸 다음 세션에도 기억하게 하고 싶다'는 문제를 고민하는 백엔드 또는 AI 앱 개발자. 특히 멀티테넌트 환경에서 사용자별 메모리 격리가 필요한 SaaS 제품 개발자에게 유용하다.
Core Mechanics
- 컨텍스트 윈도우를 메모리 대신 쓰는 방식에는 한계가 있다. 비용과 레이턴시 문제뿐 아니라, 프롬프트 중간에 있는 정보를 모델이 무시하는 'lost in the middle' 현상이 발생해서 1M 토큰 컨텍스트도 진짜 메모리 시스템을 대체할 수 없다.
- 메모리를 단일 인덱스에 다 때려넣지 않고 인지과학의 COALA 프레임워크를 따라 세 가지 유형으로 분리해 각각 별도 Elasticsearch 인덱스에 저장한다. Episodic(사건 기록), Semantic(사용자에 대한 정제된 사실), Procedural(단계별 플레이북) 세 종류다.
- Episodic 메모리는 사용자 대화 턴을 타임스탬프와 함께 그대로 저장하는 단기 이벤트 로그다. 대부분은 단명하지만, 일부는 나중에 Semantic이나 Procedural 메모리의 근거 자료가 된다.
- Semantic 메모리는 '사라는 Lumio Hub v2를 갖고 있다', '사라의 iOS 버전은 17.4다'처럼 사용자에 대해 정제된 안정적인 사실을 세션을 넘어서 보존한다.
- Procedural 메모리는 'Zigbee 연결 끊김 트러블슈팅 방법'처럼 단계별 절차를 저장하며, 각 플레이북에 success_count와 failure_count를 추적한다. 사용자가 해결됐다/안 됐다고 피드백하면 통합(consolidation) LLM이 이 카운터를 참고해 플레이북을 개선하거나 교체한다.
- 메모리 검색은 벡터 검색과 키워드(BM25) 검색을 합친 하이브리드 방식으로 하고, RRF(Reciprocal Rank Fusion, 두 검색 결과를 역순위 합산으로 병합하는 기법)로 결과를 합친 뒤 크로스인코더 리랭커로 최종 순위를 재조정한다. 이 파이프라인으로 168개 질문 기준 R@10 0.89를 달성했다.
- 사용자가 기존 사실과 모순되는 말을 하면 이전 메모리를 삭제하지 않고 superseded 상태로 표시해 감춘다. 덕분에 최신 정보가 우선 노출되면서도 감사 추적(audit trail)이 유지된다.
- 멀티테넌트 격리는 Elasticsearch의 DLS(Document-Level Security, 문서 단위 접근 제어)로 구현해서 각 사용자는 자신의 메모리 문서만 볼 수 있다. 별도 인증 서비스 없이 Elasticsearch 하나로 해결하며, 168개 질문 테스트에서 크로스 테넌트 누출 0건을 기록했다.
Evidence
- Elasticsearch는 이 용도에 과하다는 비판이 있었다. SQLite, LanceDB 같은 훨씬 가벼운 도구로도 충분한데, 이 글은 'Elasticsearch를 AI에 맞게 포장하려는' 마케팅 성격이 강하다는 지적이다. 실제로 'SQLite로 에이전트 메모리를 구현했다'는 댓글도 있었다.
- 글의 문체가 LLM이 쓴 것 같다는 반응이 여러 개 달렸다. 원문이 학술 논문을 LLM으로 필터링한 것처럼 읽힌다는 비판과 함께, 누군가가 더 읽기 쉽게 정리한 요약본을 pastebin에 올리기도 했다.
- R@10 0.89가 실제로 좋은 수치인지, R@10이 무슨 의미인지 모르겠다는 질문이 있었다. R@10은 Recall at 10으로, 상위 10개 검색 결과 안에 정답이 포함된 비율을 뜻하며 0.89면 꽤 높은 편이다.
- 11%의 미스율이 실제 사용자 경험에서 어떻게 나타나는지를 묻는 댓글이 있었다. 사용자가 이미 시도해봤다고 말한 해결책을 에이전트가 다시 제안하는 상황이 발생하면 사용자가 실제로 느끼는 불만이 클 수 있다는 우려다.
- Elasticsearch 대신 Typesense를 사용하는 대안을 공유한 댓글도 있었다. Typesense는 벡터 검색과 BM25를 결합하면서 Elasticsearch보다 가볍고 Algolia와 유사한 경험을 제공해 마크다운 기반 지식 베이스에 적용 중이라는 경험을 공유했다.
How to Apply
- 멀티턴 에이전트를 개발 중이고 '이전 세션의 컨텍스트를 매번 프롬프트에 넣다 보니 비용이 너무 커진다'면, 이 아키텍처처럼 Episodic/Semantic/Procedural 세 인덱스로 분리해 필요한 정보만 검색해 주입하는 방식으로 전환하면 컨텍스트 길이와 비용을 줄일 수 있다.
- 여러 사용자가 같은 에이전트를 공유하는 SaaS 서비스를 만든다면, Elasticsearch의 DLS를 user_id 필드 기반으로 설정해 별도 인증 레이어 없이 문서 단위로 접근을 격리할 수 있다. GitHub에 전체 구현체가 공개되어 있으니 참고할 수 있다.
- 사용자 정보가 업데이트될 때 이전 정보를 삭제하면 감사 추적이 불가능해지는 문제가 있다면, supersession 패턴을 도입해 이전 문서를 is_superseded: true로 표시하고 검색 시 필터링하는 방식을 적용하면 최신 정보 우선 노출과 변경 이력 보존을 동시에 달성할 수 있다.
- 소규모 프로젝트라면 Elasticsearch 대신 SQLite + 벡터 확장(예: sqlite-vss)이나 LanceDB로 동일한 개념(에피소딕/시맨틱/절차 분리, 하이브리드 검색)을 구현하는 것이 운영 부담이 훨씬 적다. 이 글의 아키텍처 설계 원칙만 참고하고 스택은 규모에 맞게 선택하면 된다.
Terminology
관련 논문
TAHOE: 경험 기반 자동 Hint 최적화를 통한 Text-to-SQL 시스템
LLM이 SQL 생성 실패에서 배운 힌트를 재사용 가능한 Hint Bank로 쌓아, 모델 재학습 없이 Snowflake 방언 SQL 정확도를 대폭 끌어올리는 시스템.
FAISS 내부 동작 원리: 10억 개 벡터 유사도 검색
FAISS가 수십억 개 벡터를 빠르게 검색하는 핵심 알고리즘인 IVF(파티셔닝)와 Product Quantization(압축)을 시각적으로 설명한 글로, RAG나 벡터 검색 시스템을 구축하는 개발자에게 내부 동작 원리를 이해시켜 준다.
Airbyte Agents – 여러 데이터 소스를 아우르는 Agent용 Context Layer
Airbyte가 Slack, Salesforce, Linear 등 여러 SaaS 시스템의 데이터를 미리 인덱싱해서 Agent가 API를 일일이 뒤지지 않아도 되는 Context Store를 출시했다. 기존 MCP 방식보다 토큰을 최대 90%까지 줄이는 효과를 확인했다.
Polynomial Autoencoder가 Transformer Embedding에서 PCA를 능가하는 방법
PCA 인코더에 2차 다항식 디코더를 붙여서 닫힌 형태(closed-form)로 embedding 압축 품질을 크게 개선하는 기법으로, SGD 없이 numpy만으로 구현 가능하다.
비정형 Recall에서 Schema 기반 Memory로: 반복적 Schema-Aware Extraction을 통한 신뢰할 수 있는 AI Memory
RAG 스타일 텍스트 검색 대신 Schema로 정의된 구조화 레코드에 메모리를 저장하면, 정확한 사실 조회·상태 추적·집계 쿼리에서 압도적으로 높은 정확도를 얻을 수 있다.
Atomic – Local-first, AI 기반 개인 지식 그래프 앱
노트, 웹 클립, RSS 피드를 자동으로 임베딩·태깅·연결해주는 오픈소스 개인 지식 그래프 앱으로, 시맨틱 검색과 LLM 기반 위키 합성, MCP 통합까지 지원한다.