@tencentdb-agent-memory/memory-tencentdb)는 AI 에이전트가 대화를 스스로 기억하고 다음 턴에 알아서 떠올리게 만드는 로컬 장기 메모리 플러그인이다. 대화를 평평한 벡터 더미에 쏟아붓는 흔한 방식을 거부하고, 원본 대화(L0) → 원자적 사실(L1) → 장면 블록(L2) → 사용자 페르소나(L3)의 4계층 의미 피라미드로 증류한다. 동시에 길어진 작업 로그를 Mermaid 기호 그래프로 압축(symbolic memory)해 토큰을 최대 61% 절감한다. OpenClaw·Hermes 게이트웨이의 플러그인으로 동작하며, 기본은 외부 서비스 없이 SQLite + sqlite-vec 로컬 백엔드로 돈다.이 레포가 무엇을 하는 물건인가.
우리는 에이전트에게 똑같은 업무 절차(SOP)·프로젝트 배경·도구 관례·출력 형식을 끝없이 반복 설명한다. 이 플러그인은 그 대화를 ① 원본 그대로 보관(L0)하고, ② 핵심 사실만 원자 단위로 추출(L1)하며, ③ 비슷한 사실을 장면으로 묶고(L2), ④ 최종적으로 "이 사용자는 이런 사람"이라는 페르소나로 증류(L3)한다. 다음 대화가 시작되기 직전, 관련 기억과 페르소나를 자동으로 컨텍스트에 주입한다.
핵심은 "다 욱여넣기"가 아니다. 평소엔 가장 압축된 페르소나/장면만 보고, 세부가 필요할 때만 아래 계층(원자 사실·원본 대화)으로 파고든다(progressive disclosure). "윗 계층은 구조를, 아래 계층은 증거를 보존한다"가 설계 철학이다.
TencentDB Agent Memory는 두 개의 기둥으로 이루어진다. 하나는 위에서 말한 메모리 계층화(memory layering) 즉 장기 개인화 기억이고, 다른 하나는 기호 메모리(symbolic memory) 즉 작업 중 폭증하는 도구 로그를 외부 파일로 덜어내고(offload) 컨텍스트엔 Mermaid 다이어그램 한 장만 남기는 단기 기억 압축이다. 둘 다 "평평하게 쌓기"를 거부하고 계층으로 다룬다는 점에서 한 철학을 공유한다.
before-agent-start·after-tool-call 같은 훅(hook)에 끼어들어 대화를 가로채 기억을 쓰고 회상을 주입한다. 즉 에이전트 코드를 고치지 않고도 "기억 능력"을 덧붙인다.트렌딩 이유 · 평평한 벡터 메모리 대비 장점.
2025~2026년 에이전트 메모리(mem0, Letta/MemGPT, Zep 등)는 뜨거운 주제였지만, 대부분 "대화를 조각내 벡터 DB에 던지고 유사도로 꺼내는" 평평한 구조였다. 그 결과 회상은 맥락 없이 흩어진 조각들 사이의 깜깜이 검색으로 전락하기 쉽다. TencentDB Agent Memory가 주목받는 이유는 이 한계를 정면으로 공격하면서, 실측 벤치마크로 효과를 증명했기 때문이다.
| 기억 능력 | 벤치마크 | 기본 성공률 | 플러그인 적용 | 토큰 변화 |
|---|---|---|---|---|
| 단기(기호) | WideSearch | 33% | 50% | −61.38% |
| 단기(기호) | SWE-bench | 58.4% | 64.2% | −33.09% |
| 단기(기호) | AA-LCR | 44.0% | 47.5% | −30.98% |
| 장기(계층) | PersonaMem | 48% | 76% | — |
저자들은 이 수치가 단발 턴이 아니라 긴 호흡의 연속 세션에서 측정됐다고 강조한다(예: SWE-bench는 세션당 50개 작업을 연속 수행해 실제 컨텍스트 누적 압력을 흉내). 즉 "오래 쓸수록 진가가 나는" 메모리다.
대화를 잘게 잘라 벡터로만 저장하면, 회상 시 거시적 안내(이게 무슨 상황의 조각인지)가 사라진다. 반대로 LLM 요약으로 뭉뚱그리면 정보가 비가역적으로 손실되어, 나중에 "원문이 정확히 뭐였지?"를 추적할 수 없다. 한쪽은 무차별 누적, 다른 쪽은 돌이킬 수 없는 손실 압축이라는 양극단의 함정이다.
해법은 두 가지다. 첫째 이종 저장(heterogeneous storage) — 아래 계층(사실·로그)은 DB에 넣어 전문 검색을 보장하고, 윗 계층(페르소나·장면)은 사람이 읽는 Markdown으로 저장해 정보 밀도와 화이트박스 검수를 얻는다. 둘째 무손실 복구 — "윗 계층 기호(페르소나/캔버스) → 중간 색인(장면/jsonl) → 바닥 원문(L0 대화/refs)"으로 내려가는 결정론적 드릴다운 경로를 항상 유지한다. 압축해도 원문 증거로 되돌아갈 수 있다.
런타임(백엔드)·저장/검색·LLM·인프라 각각.
전체가 TypeScript로 작성된 Node.js 플러그인이다(type: "module" ESM, Node ≥ 22.16). 빌드는 tsdown, 테스트는 vitest. OpenClaw 게이트웨이의 플러그인 SDK(pluginApi ≥ 2026.3.13)에 맞춰 index.ts가 훅·도구를 등록한다.
| 요소 | 역할 |
|---|---|
| TypeScript / Node 22+ | 전체 언어·런타임. node:sqlite 내장 모듈(Node 22의 DatabaseSync) 사용. |
ai + @ai-sdk/openai | Vercel AI SDK. L1 추출·페르소나 생성 등 LLM 호출 통일 인터페이스. |
node-llama-cpp | peerDependency(선택). 로컬 LLM으로 추출·요약을 돌려 데이터를 외부로 안 보냄. |
zod | 설정·LLM 출력 스키마 검증. |
js-tiktoken | 토큰 카운팅(오프로딩 임계치 판단). |
undici | HTTP 클라이언트(원격 임베딩·TCVDB 호출). |
핵심은 src/core/store/types.ts의 백엔드 무관 인터페이스(IMemoryStore)다. 상위 모듈(훅·도구·파이프라인)은 인터페이스만 의존하고, 구현체는 팩토리(factory.ts)가 설정에 따라 골라 끼운다.
| 요소 | 역할 |
|---|---|
SQLite + sqlite-vec | 기본 로컬 백엔드. 벡터 유사도(vec0) + FTS5 전문 검색을 한 파일(vectors.db)에. |
| FTS5 + BM25 | 키워드 검색. @node-rs/jieba로 중국어 분절, 없으면 유니코드 정규식 폴백. |
| Tencent Cloud VectorDB | 선택 백엔드(tcvdb). 서버측 임베딩 + hybridSearch. 같은 인터페이스로 교체. |
| 임베딩 서비스 | OpenAI 호환 원격(text-embedding-3-small 등) 또는 비활성(none) 시 키워드만. |
| RRF 하이브리드 | 키워드 결과 + 벡터 결과를 Reciprocal Rank Fusion으로 병합. |
| Markdown 파일 | L2 장면 블록·L3 페르소나·Mermaid 캔버스는 .md로 저장(화이트박스). |
| 요소 | 역할 |
|---|---|
| 패키징 | npm 배포(@tencentdb-agent-memory/memory-tencentdb). tsdown 번들 + 런타임 의존성 스테이징. |
| OpenClaw 통합 | openclaw plugins install … 한 줄. openclaw.json에 enabled:true만으로 제로컨피그 구동. |
| Hermes 통합 | hermes-plugin/ — Nous Research의 Hermes 게이트웨이용 어댑터. |
| CLI 유틸 | read-local-memory·export-tencent-vdb·migrate-sqlite-to-tcvdb 세 bin 스크립트. |
| 데이터 디렉토리 | ~/.openclaw/memory-tdai/ — conversations/ · records/ · scene_blocks/ · vectors.db. |
| 관측(선택) | opik 트레이서로 LLM 호출 추적(optionalDependency). |
1/(k+rank)를 더해 합산한다. 점수 스케일이 달라도 공정하게 섞을 수 있어 하이브리드 검색의 단골 도구다.대화 캡처 → 4계층 증류 → 자동 회상, 그리고 기호 메모리 오프로딩.
이 플러그인은 사실상 두 시스템이 한 패키지에 들어있다. ① 장기 메모리(src/core/)는 세션을 넘는 개인화 기억(L0→L3)을, ② 단기 오프로딩(src/offload/)은 한 작업 안에서 폭증하는 도구 로그를 Mermaid로 압축한다. 둘은 같은 훅 지점(게이트웨이)에 등록되지만 목적이 다르다.
사용자가 새 메시지를 보내면 before-agent-start 단계에서 auto-recall이 발동한다. L1 기억을 검색해 프롬프트 앞에 붙이고(per-turn 동적), L3 페르소나·L2 장면 내비게이션을 시스템 프롬프트 끝에 붙인다(캐시 가능). 에이전트가 답하고 나면 after-tool-call/턴 종료 시 auto-capture가 대화를 L0에 기록하고, 파이프라인 매니저에게 "이제 L1/L2/L3를 돌릴 때인가?"를 묻는다.
장기 메모리의 심장은 아래로 갈수록 증거(원문)에 가깝고, 위로 갈수록 구조(추상)에 가까운 피라미드다.
| 계층 | 이름 | 저장 형태 | 역할 |
|---|---|---|---|
| L0 | Conversation (원본 대화) | JSONL + 벡터 | 날것의 대화. 모든 추적의 바닥 증거. |
| L1 | Atom (원자적 사실) | DB(FTS5+vec0) | "사용자는 X를 선호" 같은 단일 사실. 중복 제거(dedup) 후 저장. |
| L2 | Scenario (장면 블록) | Markdown | 관련 사실을 한 상황으로 묶은 블록. LLM이 .md 파일을 읽고/쓰며 정리. |
| L3 | Persona (사용자 페르소나) | Markdown | "이 사용자는 이런 사람" 최상위 요약. 일상 회상의 1순위 주입 대상. |
node_id/record_id로 L1·L0까지 내려가 원문을 꺼낸다. 토큰을 아끼면서도 정밀도를 잃지 않는 비결.// 상위 모듈은 이 인터페이스에만 의존 (SQLite/TCVDB 구현 몰라도 됨)
interface IMemoryStore {
// 능력 기반(capability): 벡터·FTS·하이브리드 지원 여부를 플래그로 노출
// 결함 허용(fault-tolerant): 실패 시 throw 대신 빈 결과/false 반환
searchL1Vector(...): Promise<L1SearchResult[]>;
searchL1Fts(...): Promise<L1FtsResult[]>;
...
}
// factory.ts 가 config.storeBackend 로 구현체 선택
switch (config.storeBackend) {
case "tcvdb": return new TcvdbMemoryStore(...); // 텐센트 클라우드 벡터DB
case "sqlite":
default: return new VectorStore(dbPath, dims, logger); // 로컬 기본
}
임베딩 키가 없거나 provider:"none"이면 벡터 능력을 끄고 키워드 경로로 자동 강등(graceful degradation)한다. "되면 좋고, 안 돼도 죽지 않는다"는 결함 허용 설계.
긴 작업에서 토큰을 가장 많이 먹는 건 장황한 중간 로그(검색 결과·코드·에러 트레이스)다. src/offload/는 이를 외부 파일(refs/*.md)로 덜어내고, 컨텍스트엔 node_id가 박힌 Mermaid 그래프 한 장만 남긴다. 에이전트는 이 기호 그래프 위에서 추론하다가, 세부 확인이 필요하면 node_id로 grep해 원문을 즉시 복구한다.
L2 Mermaid 파이프라인은 L1에서 직접 호출되지 않고 독립 트리거로 돈다 — ① 오프로드 jsonl에 node_id=null 항목이 임계치 이상 쌓이거나, ② 마지막 L2 실행 후 일정 시간이 지나면 발동한다(l2-mermaid.ts).
장기 메모리를 "비서가 쓰는 업무 노트"로 생각하자. 회의 녹취(L0)를 그대로 두되, 거기서 "결정 사항" 한 줄씩 뽑아 색인 카드(L1)를 만들고, 같은 프로젝트 카드들을 한 폴더(L2)로 묶고, 맨 앞장엔 "이 고객은 이런 분"이라는 요약지(L3)를 붙인다. 평소엔 요약지만 보고, 분쟁이 생기면 색인 카드를 거쳐 녹취 원문까지 정확히 되짚는다. 기호 메모리는 여기에 더해, 두꺼운 회의록을 한 장짜리 흐름도로 바꿔 책상에 올려두는 것 — 흐름도의 항목 번호(node_id)로 서랍 속 원본을 바로 찾는다.
src/core/(장기 메모리)와 src/offload/(기호 메모리)가 양대 축.
① store/types.ts로 "저장소 계약(IMemoryStore)" 파악 → ② store/factory.ts로 백엔드 선택·강등 로직 → ③ hooks/auto-recall.ts로 "회상이 어떻게 컨텍스트에 들어가나"(이게 사용자 체감의 전부) → ④ hooks/auto-capture.ts + scene/scene-extractor.ts로 4계층이 어떻게 자라나 → ⑤ offload/pipelines/l2-mermaid.ts로 기호 압축. 통찰의 80%가 ①~③에 있다.
에이전트 메모리 시스템 설계에서 무엇을 배우나.
가장 큰 교훈은 메모리는 형성도 회상도 계층적이어야 한다는 것이다. 모든 걸 한 벡터 DB에 던지면 회상이 깜깜이 검색이 된다. L0→L1→L2→L3처럼 추상도를 올리되, 각 층이 아래 층으로 내려가는 추적 경로를 유지하면 "요약의 편의 + 원문의 정확성"을 동시에 얻는다. RAG·노트 앱·로그 시스템 어디에도 옮길 수 있는 원칙이다.
IMemoryStore 인터페이스는 "상위 모듈은 인터페이스에만 의존"의 교과서다. 덕분에 로컬 SQLite ↔ 텐센트 클라우드 벡터DB를 코드 변경 없이 갈아끼운다. 게다가 임베딩 키가 없으면 throw 하지 않고 키워드 검색으로 자동 강등한다 — "기능이 없으면 죽는다"가 아니라 "할 수 있는 만큼 한다"는 견고함.
save/search 두 메서드만 있는 IStore 인터페이스를 정의하고, ① 메모리 배열 구현 ② JSON 파일 구현 두 백엔드를 만들어 팩토리로 선택하게 하라. 호출부 코드가 안 바뀌는지 확인.순수 벡터 검색은 정확한 키워드(고유명사·코드)에 약하고, 순수 키워드는 의미 유사에 약하다. 이 레포는 FTS5 BM25 + 벡터 유사도를 RRF로 병합해 둘의 장점을 취한다. tools/memory-search.ts의 strategy(hybrid/embedding/fts) 자동 강등 로직이 좋은 참고서다.
score = Σ 1/(60+rank) RRF로 재정렬해 보라. 어느 쪽이 끌어올려지는지 관찰.오프로딩 모듈은 "컨텍스트는 유한 자원"이라는 관점의 모범이다. 장황한 로그를 외부로 덜고 Mermaid 한 장 + node_id 인덱스만 남기면, 토큰을 60% 아끼면서도 grep으로 원문에 즉시 닿는다. 긴 호흡 에이전트(코딩·리서치)에서 컨텍스트 관리가 곧 성능임을 보여준다.
refs/001.md에 저장하고 (2) "001-N1: 검색 성공, 001-N2: 파싱 실패" 식 요약 줄만 컨텍스트에 남기는 미니 오프로더를 써 보라."로컬 우선" 설계 — 기본은 외부 서비스 없이도 돈다.
| 항목 | 요구사항 |
|---|---|
| 호스트 | OpenClaw ≥ 2026.3.13 (또는 Hermes 게이트웨이). 플러그인으로 설치. |
| 런타임 | Node.js ≥ 22.16.0 — 내장 node:sqlite(DatabaseSync) 사용에 필수. |
| 저장 백엔드(기본) | 로컬 SQLite + sqlite-vec. 별도 DB 서버·API 키 불필요(제로컨피그). |
| 임베딩(선택) | 벡터 회상을 원하면 OpenAI 호환 임베딩 4종(apiKey/baseUrl/model/dimensions) 필요. 없으면 키워드 모드로 강등. |
| 로컬 LLM(선택) | node-llama-cpp peerDep — 추출·요약을 로컬에서 돌려 데이터 외부 유출 방지. |
| 원격 백엔드(선택) | Tencent Cloud VectorDB(tcvdb) — url·apiKey·database 설정 시 서버측 임베딩+hybridSearch. |
| 단기 오프로딩(선택) | ≥ 0.3.4에서 offload.enabled + contextEngine 슬롯 등록 + 패치 스크립트 1회 적용. |
| 데이터 위치 | ~/.openclaw/memory-tdai/ (conversations · records · scene_blocks · vectors.db). |
이 플러그인은 결함 허용 설계라 잘못 설정해도 에러로 멈추지 않고 조용히 기능을 끈 채 동작한다. ① embedding.provider를 원격으로 두고 apiKey/baseUrl/model/dimensions 중 하나라도 빠지면 → 벡터 비활성, 키워드만. ② recall.scoreThreshold가 너무 높으면 → 기록은 있는데 회상이 안 됨. ③ l0l1RetentionDays를 1~2로 두면(공격적 정리) 히스토리가 과도하게 삭제될 수 있어 allowAggressiveCleanup 명시 필요. ④ 설정 바꾼 뒤 게이트웨이 재시작을 안 하면 반영 안 됨. 로그에 [memory-tdai] 접두사가 보이는지로 생존을 확인하라.
난이도별 5단계 — 설치 체험부터 미니 메모리 엔진 구현까지.
OpenClaw에 openclaw plugins install @tencentdb-agent-memory/memory-tencentdb → openclaw.json에 enabled:true → gateway restart. 대화에서 본인 취향·제약(예: "나는 항상 Python으로, 들여쓰기는 2칸")을 2~3턴 말한 뒤, 새 세션에서 그게 자동으로 떠오르는지 확인한다. ~/.openclaw/memory-tdai/ 디렉토리가 생겼는지도 본다.
위 설치 후 conversations/*.jsonl(L0), records/(L1 DB), scene_blocks/*.md(L2), persona.md(L3)를 차례로 열어 같은 정보가 어떻게 추상화되는지 비교한다. read-local-memory CLI도 써 보고, "내가 말한 한 사실이 어느 계층에 어떻게 남았나"를 추적해 본다.
src/core/hooks/auto-recall.ts를 읽고, ① 어떤 검색 전략(hybrid)으로 L1을 찾는지, ② prependContext(프롬프트 앞)와 appendSystemContext(시스템 끝)를 왜 나누는지(캐시), ③ 회상 라인이 길면 어떻게 잘라내는지를 한 문단으로 정리한다. tools/memory-search.ts의 strategy 강등 로직도 같이 본다.
임의의 대화 배열을 입력받아 ① L0로 JSONL 기록 → ② LLM 1회 호출로 "원자 사실" 리스트 추출(L1) → ③ 또 1회 호출로 "사용자 페르소나" Markdown 생성(L3)하는 80~120줄짜리 스크립트를 만든다. 중복 제거(dedup)와 "각 L1이 어느 L0에서 왔는지(드릴다운 ID)"를 반드시 넣는다.
store/types.ts의 IMemoryStore를 본떠 본인만의 IStore를 정의하고, sqlite-vec 구현과 in-memory(또는 Redis) 구현 두 개를 만들어 팩토리로 선택하게 한다. 임베딩 키가 없을 때 키워드 전용으로 graceful degradation하는 분기까지 구현해, 호출부가 전혀 안 바뀌는지 검증한다.
6주 코스 — 임베딩 검색 기초부터 계층형 에이전트 메모리 설계까지.
| 주차 | 주제 | 실습 · 참고 |
|---|---|---|
| 1주차 | 임베딩 & 벡터 검색 기초 — 코사인 유사도·sqlite-vec | 임베딩 API로 문장 벡터화 → 유사도 검색 미니앱 |
| 2주차 | 전문 검색 & 하이브리드 — FTS5/BM25 + RRF 융합 | store/sqlite.ts·tools/memory-search.ts 정독 |
| 3주차 | 인터페이스 설계 & 결함 허용 — 백엔드 추상화·강등 | store/types.ts·factory.ts · 어댑터 2종 구현(과제 5) |
| 4주차 | 계층형 메모리 — L0→L3 증류·점진적 공개·드릴다운 | README Core Technology · scene-extractor.ts · 미니 파이프라인(과제 4) |
| 5주차 | 훅 기반 통합 — 게이트웨이 훅으로 회상/캡처 끼우기 | index.ts·auto-recall/auto-capture · 플러그인 훅 모델 |
| 6주차 | 컨텍스트 엔지니어링 — 오프로딩·기호 메모리·토큰 예산 | offload/ · Mermaid 캔버스 · tiktoken 토큰 추적 |
본문·소스에 나온 용어 빠른 참조.
| 용어 | 의미 |
|---|---|
| L0 / L1 / L2 / L3 | 의미 피라미드: 원본 대화 → 원자 사실 → 장면 블록 → 사용자 페르소나. |
| memory layering | 메모리를 평평하게 쌓지 않고 추상도별로 계층화하는 이 레포의 핵심 패러다임. |
| symbolic memory | 장황한 로그를 Mermaid 기호 그래프로 압축해 컨텍스트 토큰을 절감하는 단기 기억. |
| offload (오프로딩) | 도구 로그 전문을 외부 파일(refs/*.md)로 덜어내고 요약만 컨텍스트에 남기기. |
| node_id | Mermaid 캔버스의 항목 식별자. grep으로 원문(refs)을 즉시 복구하는 키. |
| auto-recall / auto-capture | 턴 시작 시 기억 주입(회상) / 턴 종료 시 대화 기록(캡처) 훅. |
| progressive disclosure | 요약부터 보고 필요할 때만 상세로 내려가는 점진적 공개 원칙. |
| drill-down (드릴다운) | 윗 계층 기호 → 중간 색인 → 바닥 원문으로 추적하는 무손실 경로. |
| IMemoryStore | 백엔드 무관 저장소 인터페이스. 상위 모듈이 의존하는 유일한 계약. |
| sqlite-vec | SQLite 확장으로 벡터(vec0) 유사도 검색을 한 파일에서 처리. |
| FTS5 / BM25 | SQLite 전문 검색 / 그 랭킹 함수. 키워드 회상 경로. |
| RRF | 역순위 융합. 키워드·벡터 결과를 순위 기반으로 병합하는 하이브리드 기법. |
| graceful degradation | 임베딩 등 능력이 없으면 throw 대신 키워드 모드로 자동 강등하는 결함 허용. |
| persona.md | L3 최상위 요약. 일상 회상의 1순위로 컨텍스트에 주입되는 사용자 프로필. |
| scene block | L2 장면 블록. 관련 L1 사실을 한 상황으로 묶은 Markdown. |
| TCVDB | Tencent Cloud VectorDB. 같은 인터페이스로 교체 가능한 원격 백엔드. |
| OpenClaw / Hermes | 이 플러그인이 얹히는 에이전트 게이트웨이 호스트들. |