Long-Term Memory Reserach
에이전트의 장기 기억력을 위해서 여러가지 오픈소스들이 다양한 방법들로 문제를 풀어나가고잇다.
오늘은 그 생태계의 오픈소스들을 하나씩 알아보면서 어떤 특징이 있는지 보려고한다.
분류축은 세 가지로 갈린다
- 원본 검색 (세션 텍스트를 청크로 저장해 BM25, 의미 유사도 검색 - 원발화는 보존되지만 고정된 top-k 묶임)
- LLM 가공 메모리 (수집 단계에서 LLM이 추출해서 재구조화)
- 파일 기반 에이전트 (지속되는 마크다운 저장소에 툴 호출 접근)
Mem0 #
LLM이 대화의 사실만 뽑아 저장함
두 단계의 구조를 가지고 있다. 먼저 LLM이 대화에서 핵심 사실을 뽑고 그 뒤에 Tool Calling으로
ADD / UPDATE / DELETE / NOOP중 하나를 실행해 메모리 일관성을 유지한다.
추출기와 업데이트 모듈은 function calling이 되는 소형 모델을 쓴다.
이렇게 구현한 이유는 대화의 원문은 대부분 노이즈기 때문이다.
"안녕하세요" "ㄱㅅ해요" 이런걸 벡터 디비엣 넣어봐야 검색만 오염되는거고 그래서 쓰기 시점에 걸러서 원자적 사실만 남기는 선택이다.
Trade Off #
사실 이 설계는 핵심 강점과 약점이 너무나도 잘 들어나게 된다.
장점은 압축된 표현이고 단점은 정보 손실이다.
왜냐면 LLM이 쓰기 시점에 무엇이 읽기 시점에 필요할지 결정하는건데, 인간의 100퍼센트 의도대로
정확하게 관리할 수 있을까? 이건 인간한테 맡겨도 안될것이다. 필연적으로 내 의도는 내가 제일 잘 알기에
MLA에 손실 압축 문제같은거랑 비슷하다 지금은 안 중요해보이지만 버린게 나중에 결정적일수있고 복구가 안되기도 하는 문제가 제일 단점이다
그래프 변형(Mem0ᵍ) #
엔티티 임베딩을 계산해서 유사한 기존 노드를 찾고 추가 LLM 호출로 충돌 감지와 갱신을 수행한다.
쿼리시에는 그래프 이웃 순회와 트리플 의미로 매칭을 병행한다.
다만 그래프층은 토큰을 사용할때 대략 2배로 늘리는 대신, 관계 구조가 유용한 시간, 개방형 질문에서 이득을 준다.
비싼대신 좋다고
Letta (MemGPT) #
무엇을 기억할 지 모델이 직접 정하는 방식.
Mem0처럼 파이프라인이 미리 걸러내면 걸러내는 시점에 맥락을 모르낟, 정작 맥락을 아는 건 대화중인 모델인데 그 모델에겐 결정권이 없다.
해법 #
그래서 LLM이 그때 축약해서 저장하기보단
메모리 관리를 모델의 툴 호출로 만들었고 저장 구조는 3계층이다
- Core Memory: 컨텍스트 안에 항상 존재하며 접근방식은 모델이 읽고 쓴다.
- Recall Memory: 컨텍스트 바깥에 존재하며 직접 검색해서 꺼내쓴다.
- Archival Memory: 외부 저장소에 저장되어 있으며 툴 호출로 넣고 꺼낸다.
Core Memory는 컨텍스트 윈도우에 들어가는 작은 블록이고
Recall Memory는 컨텍스트 밖에 저장된 검색 가능한 대화 기록
Archival Memory는 툴 호출로 질의하는 장기 저장소다.
편집 단위는 memory block 이라는 라벨 붙은 문자열이고 모델이 core_memory_append 덧 붙이기와 core_memory_replace 갈아끼우기 두 함수로 수정한다
- sleep time compute: 초기 설계에는 대화중에 메모리 정리를 해서 응답이 느렸는데 그래서 sleep time 에이전트가 비동기로 메모리 관리를 맡도록 바꿔 응답 시간과 메모리 품질을 개선했다.
별도의 sleep-time 에이전트가 비동기로 돌면서 주 에이전트의 코어 메모리를 편집한다.
구체적인 경험에서 패턴을 추상화하고 저장된 사실 간 모순을 해소하며 미래 추론을 빠르게 할 연관을 미리 계산해둔다.
강점 #
- 맥락을 아는 주체가 결정하니 판단이 더 정확할 수 있고
- 무엇을 왜 저장했는지 툴콜링 로그가 남는다
- Letta Code는 git 기반 메모리 디렉터리를 지원해 에이전트가 배운 내용의 버전 이력이 남는다.
단점 #
도입 범위가 크다. 이게 제약인데
Letta는 기존 스택에 메모리 레이어가 아니라 스택 그자체를 얹는거라
메모리 솔루션으로 평가하면 도입은 곧 에이전트 플랫폼 전체를 도입하는 것이라서
이미 다른 에이전트 프레임워크를 쓰고있다면 메커니즘만 ./goal돌려서 써보자 ㅋㅋ
결국 결론은 메모리 블럭으로, 계층으로 의미를 더 덧붙여 툴콜링으로 히스토리를 관리하고 맥락을 정리하는
롱 텀 메모리 관리 기법인 것이다.
Mem0보다 개선된건 계층의 분리, 정확성, 과거 히스토리 캘 수 있고 툴콜링으로 인해 정의된 규약 포맷으로 관리
Zep / Graphiti #
유효 기간을 붙여 관리하자
앞의 둘은 저장된 사실이 계속 참이라고 가정한다.
하지만 사용자 정보는 바뀐다 "나 파이썬 써" -> "3개월뒤 이프로젝트는 타입스크립트야" -> "6개월뒤 나 언어 안써(?)"
이런 정보들을 벡터디비에 넣으면 벡터 저장소는 둘 다 갖고 있다가 검색하면 둘 다 내놓는 방식으로 동작하게 되어버린다.
즉 어느게 현재인지 알 수 없다.
해결법 #
사실 해결법은 심플하다 모든 사실에 시간 정보를 붙인다. 타임스탬프 두 종류, 총 네개의 변수가 있다.
시스템에서 사실이 생성 만료된 시점 한쌍과 그 사실이 현실에서 참이었던 구간이 한 쌍이다.
valid_at / invalid_at ← 현실에서 참이었던 기간
created_at / expired_at ← 시스템이 알고 있던 기간
앨런 튜링 1912년 6월 23일 출생, 같은 절대 시각 그리고 2주전에 시작했어 같은 상대시각을 모두 다룰 수 있다.
핵심 동작 #
삭제 대신 무효화를 하는 것인데
새 정보가 기존 사실과 모순되면 엣지를 삭제하는 대신에 invalid 타임스탬프를 써넣고 그래프는 언제 무엇을 믿었는가에 답하면서도 낡은 사실을 현재 것으로 내놓지 않게 될 수 있다.
원본 보존 에피소드(원본 메시지)와 거기서 뽑은 의미 엣지는 양방향 인덱스로 연결되어 추출된 사실에서 출처로 되짚어갈 수 있고. 원본에서 관련 사실을 바로 가져올 수도 있다.
강점 #
- 시점별 질의가 가능 : "3월에 이 사람 주소 정보"
- 감사, 컴플라이언스 대응 가능 (이력이 남아서)
- 잘못된 갱신을 되돌릴 수 있음
- 모든 사실이 원본으로 추적 가능함
단점 #
무겁다 에피소드를 추가할때마다 입력 텍스트가 LLM을 거쳐서 엔티티와 엣지로 분해되고 시간 유효구간과 함께 Neo4j에 저장된다
- 그래프 디비 운영 부담
- 쓰기마다 LLM 호출 비용 더 듬
- 임베딩과 리랭커까지 별도 모델 필요함
Honcho #
무슨 말을 했냐 보단 어떤 사람인가를 저장
앞의 셋은 전부 사실(fact)를 저장한다 그런데 개인화에 필요한건 사실 목록이 아니라 성향과 선호다.
"이 사람은 뭘 선호하지?" 같은 사실이 아닌 취향기반한 것을 답하려면 대화를 다시 다 읽어 판단해야한다.
결정론적으로 저장하는 것이 안디ㅏ.
문제를 푼 방식 #
메시지가 들어오면 표상(representation)을 만든다
메시지가 도착하면 소형 파인튜닝 모델이 잠재 정보 - 선호,신념,사실,모순을 추출해 회자의 구조화된 표상에 쓴다.
Peer 구조, 사람과 AI를 같은 타입으로 취급 #
인간과 AI 에이전트 모두 Peer로 통합하고 내부 벡터 저장소는 (observer, observed) peer 쌍으로 키를 잡는다.
같은 메커니즘에 자기 표상 (observer == observed)과 상호 모델링을 동시에 지원한다.
즉 저장 키가 누가, 누구에 대해다. 내가 나를 보는것과 내가 상대를 보는것이 같은 구조로 처리한다.
사용자 peer는 기본적으로 observeOther가 false이다. 사용자가 말한 것으로만 모델링이 되고 에이전트가 답한 내용으로는 모델링 되지 않는다.
에이전트의 추측이 사용자 프로필로 역류하는 것을 막는다.
아키텍처 #
API 서버와 워커를 분리하고 API는 큐에 넣고 즉시 반환한뒤 deriver 워커가 솝된다.
HTTP를 LLM 작업 앞으로 블로킹하지 않는다.
메모리 형성은 에이전트 룰 루프가 아니라 배치당 구조화 출력 LLM 호출 한 번으로 한다.
비용이 예측 가능하고 지연이 낮다, 툴을 쓰는 진짜 에이전트는 Dialectic 하나 뿐이다.
Dialectic API는 자연어 엔드포인트로 에이전트가 Honcho에 백채널 대화를 해서 사용자에 대한 실행 가능한 통찰을 실시간으로 다 받는다. DB쿼리가 아니라 에이전트에게 물어보는 것
장점 #
- 고수준 질문에 빠르게 대답할 수 있다
- 쓰기 지연을 내주는 대신 "앨리스는 뭘 선호해?" 같은 질의에서 전체 대화 이력을 다시 안읽어도 답할 수 있다
- 쓰기 비용 예측 가능 (호출횟수고정)
- 다자 대화에서 관점별 모델링이 가능하다.
단점 #
- 쓰기 지연 (메시지 백그라운드 작업 촉발)
- 검색 자체가 목적은 아님 정확히 원문 이용이 필요하면 부적합하다.
- 라이선스가 AGPL-3.0으로 상용 제품 임베딩 전 검토가 필요하다.
Cognee #
메모리를 데이터 파이프라인으로
위 시스템들은 대화를 대상으로 하는데 그런데 회사가 가진 문서 수천 건을 메모리로 만들려면 형식이 재각각이고 처리 과정을 검증할 방법도 필요하다
ECL 파이프라인이 존재하는데 (Extract, Cognify, Load) 추출 - 인지 - 적재 방식이다.
공개 API는 add(), cognify(), search(), memify()이고, 에이전트용 인터페이스로 remember/recall/forget/improve가 있다.
Cognify 단계가 핵심인데 단순 청킹, 임베딩 대신 엔티티와 타입 있는 관계의 네트워크를 만든다.
각 단계는 엔지니어가 검사하거나 재정의할 수 있는 재사용 테스크로 블랙박스가 아니라는게 이 프로젝트의 포인트이다.
장점 #
- 다양한 형식의 원본 처리
- 파이프라인 각 단계를 검증 및 교체가 가능
- 문서 대량 처리 강점이 있음 Bayer는 과학 논문 1만편을 리서치 메모리로 처리하고 와이오밍대는 흩어진 정책문서에서 페이징 단위 출처가 붙은 근거 그래프를 만들었다고 함
단점 #
- 대화 메모리로는 과함
- 파이프라인 설정 학습 비용
File Based (Hermes, Opneclaw) #
걍 마크다운
위 시스템들의 공통 약점은 왜 이걸 기억했지? 에 대답하기 어렵고 잘못 저장된걸 사람이 직접 고치기 어렵다
그리고 전부 인프라가 추가로 필요하다
그래서 hermes, openclaw에서는 연구 문헌에서도 별도 범주로 분류된다.
파일 기반 에이전트는 세션 넘어 관리되는 마크다운 저장소에 LLM이 툴콜링으로 접근하는 방식으로 구현되어있다.
특별한 기술 없고 그냥 파일 읽고 쓰는 에이전트 끝
장점 #
- 사람이 열어서 읽고 직접 수정 가능하고
- git으로 이력 관리가 되며
- 인프라가 불필요하다
- 디버깅이 실제로 된다
단점 #
- 검색이 단순 (멍청해짐 파일 커지면, 위 메커니즘들이 필요하긴 함)
- 자동 중복 제거, 모순 해소가 없다
- 규모가 커지면 성능이 저하된다.
LettCode도 MemFS라는 git 기반 메모리 디렉터리를 도입했다 정교한 쪽에서 단순한 쪽으로 다시 옮겨온 사례다.
| 시스템 | 저장 대상 | 결정 주체 | 모순 처리 | 도입 형태 |
|---|---|---|---|---|
| Mem0 | 추출된 사실 | 파이프라인 | UPDATE로 덮어씀 | 라이브러리 |
| Letta | 편집 가능한 블록 | 모델 자신 | 모델이 자기 편집 | 플랫폼 전체 |
| Zep/Graphiti | 시간 붙은 엣지 | 파이프라인 | 무효화(이력 보존) | 서비스 + 그래프DB |
| Honcho | 인물 표상 | 파이프라인(쓰기) / 에이전트(읽기) | 표상 갱신 | 서비스 |
| Cognee | 지식그래프 | 파이프라인 | 그래프 재구성 | 라이브러리 |
| 파일 | 마크다운 텍스트 | 모델 + 사람 | 사람이 편집 | 없음 |
oh-my-hermes에서는 이러한 점을 letta code의 메모리 라벨링과 버저닝 관리
메모리 블록 적용으로 컨텍스트 안과 밖 그리고 주기적으로 에이전트 메모리를 관리하고 드리밍하며 (async llm data pipeline)
직접 유저와 프루닝 작업을 거치는 (가지치기) 스킬셋들도 제공한 강화된 헤르메스 버전 메모리 시스템을 도입해 관리중이다.