
기억 ― 휘발되는 단기 문맥과 세션을 초월하는 영속 메모리【프롬프트로 풀어보는 AI 에이전트 #5】
요약
AI 에이전트의 기억 구조를 단기 문맥(휘발성)과 영속 메모리(비휘발성)의 두 계층으로 구분하여 설명합니다. 세션을 초월하여 유지되어야 하는 '안정적인 사실'을 영속 메모리에 저장하는 설계 원칙과 OSS 사례를 다룹니다.
핵심 포인트
- LLM은 스테이트리스하므로 히스토리가 에이전트 상태의 전부임
- 단기 문맥은 세션 종료 시 휘발되며 컨텍스트 길이에 제한이 있음
- 영속 메모리는 세션을 넘어 유지되는 안정적인 사실의 저장소임
- 영속 메모리에는 사건이 아닌 변하지 않는 사실만을 저장해야 함
- Hermes는 파일 기반, agent-zero는 벡터 DB 기반으로 구현함
연재 「프롬프트로 풀어보는 AI 에이전트」 제5회 (제4장).
실재하는 3가지 AI 에이전트 OSS를 실제 코드와 실제 프롬프트를 원전에서 인용하며 분석하여,
최종적으로 「스스로 AI 에이전트를 만들 수 있는」 상태를 목표로 하는 연재입니다.
이 장의 전제 (제1장~제3장 복습)
제1장에서, 실행형 에이전트의 본체는 Reason–Act–Observe를 반복하는 루프임을 확인했습니다. 그때, 다음과 같이 적어 한 가지 숙제를 남겨두었습니다.
히스토리(history)가 상태의 전부다. LLM은 스테이트리스(stateless)이므로, 과거의 발언·관측은 매번 통째로 전달한다. 에이전트의 「기억」은, 제4장에서 다룰 영속 메모리(persistent memory)를 제외하면, 이 히스토리에 다름 아니다.
―― 제1장 「ReAct 루프」
본 장은 이 숙제를 회수하는 단계입니다. 전제는 세 가지만 있습니다. ①LLM은 스테이트리스이며, 매 턴 대화 히스토리를 통째로 전달할 것 (제1장). ②system 역할의 메시지가 히스토리의 선두에 상주하며, 그것이 **시스템 프롬프트(system prompt)**가 될 것 (제2장). ③도구(tool)는 LLM에게 정의·제시하여 호출하게 하는 외부 함수일 것 (제3장). 기억의 저장·취득도 결국 하나의 「memory 툴」로서 구현됩니다.
본 장의 목표는, 단기 문맥(휘발성)과 영속 메모리(비휘발성)의 2개 계층을 설계하여, 독자가 영속 메모리를 갖춘 에이전트의 최소 버전을 스스로 그릴 수 있게 되는 것입니다.
기억에는 두 개의 계층이 있다 ―― 휘발되는 문맥과, 영속하는 사실
제1장의 최소 루프를 떠올려 보세요. history라는 리스트에 system / user / assistant / tool (관측)을 계속 쌓아 나가며, 매 턴 그것을 통째로 LLM에게 전달했습니다. 이것이 에이전트의 **단기 기억 (단기 문맥)**입니다. 똑똑해 보이는 응답은 거의 이 히스토리 덕분입니다.
하지만 이 히스토리에는 두 가지 한계가 있습니다.
- 세션이 끝나면 사라진다 (휘발성). 다음에 기동한 에이전트는, 지난번에 사용자가 「나는 TypeScript를 사용한다」라고 말한 것을 기억하지 못합니다.
- 무한히 늘릴 수 없다. LLM의 컨텍스트 길이(context length)는 유한하며, 긴 대화는 오래된 부분을 압축하거나 잘라낼 수밖에 없습니다. 잘려 나간 부분은 역시 사라집니다.
그래서 실행형 에이전트는 히스토리와 별개로 **영속 메모리 (비휘발성 기억 계층)**를 가집니다. 본 장에서 「영속 메모리」라고 부르는 것은, 세션을 넘어 남으며, 기동 시 시스템 프롬프트에 실리는 안정적인 사실의 저장소를 말합니다. 단기 문맥이 「지금 이야기하고 있는 것」이라면, 영속 메모리는 「계속 기억해 두어야 할 것」입니다.
여기서 본 장의 주제와 직결되는, 두 OSS에 공통된 설계 철학을 먼저 제시합니다.
영속 메모리에 저장하는 것은 「태스크의 히스토리」가 아니라 「안정적인 사실」이다.
「PR #123을 제출했다」 「Phase 2가 끝났다」와 같은 흘러가는 사건은 메모리에 넣지 않습니다. 「사용자는 간결한 답변을 선호한다」 「이 프로젝트는 pytest를 사용한다」와 같이 시간이 지나도 변하지 않는 사실만을 넣습니다. 왜 그렇게 선을 긋는가 ―― 그것은 영속 메모리가 「기동할 때마다 시스템 프롬프트로 계속 주입되는」 비용이 높은 장소이기 때문입니다. 이 구분선을 두 OSS가 각각 다른 방식으로 구현하고 있음을, 이제부터 원전에서 확인하겠습니다.
- Hermes: 영속 메모리를
MEMORY.md/USER.md라는 두 개의 텍스트 파일로 보유하며, 기동 시 **동결 스냅샷(frozen snapshot)**으로서 시스템 프롬프트에 주입한다. 「선언적인 사실만을 작성하라」는 가이드가 붙는다. - agent-zero: 영속 메모리를 **벡터 DB (vector DB)**로 보유하며, 루프 중에 **자동으로 쿼리를 생성하여 유사 검색(recall)**을 수행하고, 관련 있는 기억만을 해당 턴의 문맥에 삽입한다.
선언적 파일(Hermes)과 벡터 자동 recall(agent-zero). 같은 「안정적인 사실을 저장한다」는 철학을, 정적 주입과 동적 검색이라는 대조적인 태도로 구현하고 있다 ―― 이것이 본 장의 축입니다. 먼저 Hermes를 통해 「파일에 써서 동결하여 싣는」 기본형을 보고, 다음으로 agent-zero를 통해 「저장해 두었다가 필요한 때에만 떠올리는」 발전형을 보겠습니다.
실물로 확인하기 ① ― Hermes: 2개 파일의 영속 메모리와 동결 스냅샷
Hermes (NousResearch/hermes-agent @ 6928692)의 영속 메모리는, tools/memory_tool.py의 MemoryStore
클래스가 핵심입니다. 모듈 서두의 docstring이 설계의 전체상을 그대로 설명하고 있습니다.
"""
Memory Tool Module - Persistent Curated Memory
Provides bounded, file-backed memory that persists across sessions. Two stores:
...
―― tools/memory_tool.py @ 6928692, L2-14 (발췌)
여기에 본 장의 논점이 거의 전부 들어 있습니다. 차례대로 분해해 보겠습니다.
영속 메모리의 실체는 두 개의 파일 ―― MEMORY.md 와 USER.md
Hermes는 영속 메모리를 **역할이 다른 두 개의 스토어 (store)**로 나눕니다. MEMORY.md (에이전트 자신의 메모 = 환경의 사실 · 프로젝트의 관습 · 도구의 습관)와 USER.md (사용자에 대해 알고 있는 것 = 취향 · 커뮤니케이션 스타일 · 습관)입니다. 제2장에서 보았던 SOUL.md (인격)가 "에이전트가 누구인가"였다면, 이것은 "에이전트가 무엇을 기억하고 있는가"를 보유합니다.
각각 § (섹션 기호)로 구분된 여러 엔트리(entry)로 구성되며, 글자 수 (토큰이 아닌) 상한이 정해져 있습니다.
def __init__(self, memory_char_limit: int = 2200, user_char_limit: int = 1375):
self.memory_entries: List[str] = []
self.user_entries: List[str] = []
...
―― tools/memory_tool.py @ 6928692, L124-128
MEMORY.md는 2,200자, USER.md는 1,375자가 기본 상한입니다. 왜 글자 수일까요? docstring이 그 이유를 밝히고 있습니다 ―― Character limits (not tokens) because char counts are model-independent. (글자 수라면 모델에 의존하지 않기 때문. 동일 파일 L17). 토큰 수는 모델의 토크나이저 (tokenizer)에 따라 달라지지만, 글자 수는 어떤 LLM에서도 동일합니다. 영속 메모리는 여러 모델에서 재사용한다는 전제가 있으므로, 상한 기준 또한 모델에 의존하지 않는 글자 수를 채택한다는 판단입니다.
상한을 초과하는 추가는 묵묵히 잘라내지 않고, 에러로 거부하여 기존 엔트리의 정리를 유도합니다.
if new_total > limit:
current = self._char_count(target)
return {
...
―― tools/memory_tool.py @ 6928692, L328-339 (발췌)
상한이 있기 때문에 오히려 "무엇을 남기고 무엇을 버릴 것인가"의 선택이 강제되며, 메모리가 비대해지지 않습니다. "안정적인 사실만을 저장한다"라는 철학이 글자 수 상한이라는 물리적 제약으로 뒷받침되고 있는 것입니다.
동결 스냅샷 (Frozen Snapshot) ―― 쓰기는 즉시, 주입은 그대로
MemoryStore의 가장 큰 특징은 docstring에 있었던 **동결 스냅샷 (frozen snapshot)**입니다. 클래스는 두 가지 상태를 병행하여 가집니다.
class MemoryStore:
"""
Bounded curated memory with file persistence. One instance per AIAgent.
...
―― tools/memory_tool.py @ 6928692, L113-122
두 가지 상태란 ―― (A) 시스템 프롬프트에 싣는 동결 스냅샷 (세션 시작 시 단 한 번 생성되며, 세션 중에는 절대 다시 쓰지 않음)과, (B) 도구 호출에 따라 변하는 살아있는 상태 (디스크에 즉시 영속화됨)입니다. 세션 시작 시, 디스크에서 읽어 스냅샷을 찍습니다.
def load_from_disk(self):
...
self.memory_entries = self._read_file(mem_dir / "MEMORY.md")
...
―― tools/memory_tool.py @ 6928692, L132-170 (발췌)
그리고, 시스템 프롬프트(system prompt)에 실을 때 반환하는 것은 **(B) 살아있는 상태가 아니라 (A) 동결된 스냅샷 (frozen snapshot)**입니다.
def format_for_system_prompt(self, target: str) -> Optional[str]:
"""
Return the frozen snapshot for system prompt injection.
...
―― tools/memory_tool.py @ 6928692, L443-454
왜 이런 이중 구조로 만드는가. 제2장의 prefix-cache를 보호하기 위해서입니다. 제2장에서 Hermes는 세션 내내 시스템 프롬프트의 바이트열(byte sequence)을 동일하게 유지하여 캐시(cache)가 작동하도록 했습니다(날짜를 '분' 단위가 아닌 '일' 단위로 설정했던 그 철저함을 기억해 보세요). 만약 memory 툴로 메모리를 수정할 때마다 시스템 프롬프트의 내용도 바뀐다면, 매 턴마다 선두 바이트열이 변화하여 캐시가 무효화되고 맙니다.
그래서 Hermes는 결단을 내립니다 ―― 턴 도중에 메모리에 기록하더라도, 그것은 즉시 디스크에 저장되지만 (영속성(durability)은 보장되지만), 현재 진행 중인 세션의 시스템 프롬프트에는 반영하지 않는다. 반영되는 것은 다음 세션 시작 시, 즉 다음에 load_from_disk()로 스냅샷을 다시 찍었을 때입니다. docstring의 Mid-session writes update files on disk immediately (durable) but do NOT change the system prompt (동 L12-13)가 바로 이 결단 그 자체입니다.
"지금 저장한 사실이 현재 세션에는 적용되지 않는다"는 점은 언뜻 불편해 보일 수 있지만, 영속 메모리(persistent memory)는 본래 "다음 회차를 위해" 남겨두는 것입니다. 그 용도라면, 즉시 반영보다 prefix-cache의 안정성을 우선시하는 이 설계는 합리적입니다.
이 메모리는 "휘발되는 문맥"의 어디에 실리는가 ―― volatile 층
제2장에서 Hermes의 시스템 프롬프트는 stable / context / volatile의 3개 층으로 구성되며, 변화 빈도가 낮은 순서대로 나열된다는 것을 확인했습니다. 영속 메모리의 스냅샷은 그 가장 마지막 = volatile (휘발) 층에 실립니다.
# ── Volatile tier (changes per session/turn — never cached) ───
volatile_parts: List[str] = []
if agent._memory_store:
...
―― agent/system_prompt.py @ 6928692, L301-313
format_for_system_prompt("memory")와 format_for_system_prompt("user")의 반환값(동결된 스냅샷)을 volatile 층에 쌓습니다. 영속 메모리는 시스템 프롬프트라는 "가장 휘발되지 않는 장소" 안의 "가장 휘발되기 쉬운 층"에 놓인다 ―― 이 언뜻 복잡해 보이는 배치가 "세션을 넘나들며 남지만, 세션 단위로는 업데이트될 수 있다"는 영속 메모리의 성질을 정확하게 나타냅니다.
무엇을 기억하고 무엇을 기억하지 않을 것인가 ―― memory 툴의 스키마와 MEMORY_GUIDANCE
여기까지가 "어떻게 저장하고 어떻게 실을 것인가"에 대한 메커니즘입니다. 그렇다면 "언제 무엇을 기억할 것인가"는 어떻게 가르치는가? 제3장에서 보았듯이, Hermes는 툴을 **function calling의 스키마 (schema)**로 정의합니다. memory 툴의 스키마 description이 바로 저장 방침 그 자체입니다.
MEMORY_SCHEMA = {
"name": "memory",
"description": (
...
―― tools/memory_tool.py @ 6928692, L652-676 (발췌)
주목해야 할 점은 두 가지입니다. 첫째, 언제 저장할지를 "사용자가 정정했을 때", "취향을 공유했을 때", "환경에 대해 발견했을 때"와 같이 구체적으로 나열하며, do this proactively, don't wait to be asked (요청을 기다리지 말고 선제적으로 수행하라)라고 명시하고 있습니다.
「요청을 기다리지 말고 능동적으로 수행하라」고 지시하고 있다는 점입니다. 기억은 수동적인 것이 아니라, 에이전트가 자발적으로 가져오는 행위라고 정의하고 있습니다. 두 번째는, Do NOT save task progress, session outcomes, ...
「태스크의 진행 상황, 세션의 결과, 작업 로그는 저장하지 마라」라고, 본 장의 서두에 나온 철학 ―― 「이력이 아닌 안정적인 사실을 쌓는다」 ―― 가 스키마(Schema) 내에 명문화되어 있다는 점입니다.
도구 조작(ACTIONS)은 add / replace / remove 세 가지이며, replace와 remove는 「짧고 고유한 부분 문자열(substring)」로 엔트리를 식별합니다 (ID도, 전체 문장도 아닌 방식). 스키마의 parameters를 보면, 제3장에서 배웠던 JSON Schema의 형태 그 자체입니다.
"parameters": {
"type": "object",
"properties": {
...
―― tools/memory_tool.py @ 6928692, L677-700 (발췌)
그리고 제2장에서 예고했던 MEMORY_GUIDANCE (memory 도구를 가질 때만 시스템 프롬프트에 주입되는 행동 규범)는, 작성 규칙까지 깊게 파고듭니다. 여기에는 본 장에서 가장 중요한 한 줄이 포함되어 있습니다.
MEMORY_GUIDANCE = (
"You have persistent memory across sessions. Save durable facts using the memory "
...
...
―― agent/prompt_builder.py @ 6928692, L136-157 (발췌)
Write memories as declarative facts, not instructions to yourself.
(기억은 자신에게 내리는 명령이 아니라, 선언적인 사실로서 작성하라). 게다가 구체적인 예시도 있습니다 ―― 'User prefers concise responses' ✓ (「사용자는 간결한 답변을 선호함」 = ○) / 'Always respond concisely' ✗ (「항상 간결하게 답변하라」 = ×). 왜 명령형이 금지되는 걸까요? 그 이유도 적혀 있습니다 ―― Imperative phrasing gets re-read as a directive in later sessions (명령형 문구는 이후 세션에서 지시 사항으로 다시 읽히게 되어), 과거의 작업을 반복하게 하거나 사용자의 현재 요청을 덮어쓸 수 있기 때문입니다.
이는 깊은 통찰입니다. 영속 메모리(Persistent Memory)는 매 세션의 시스템 프롬프트에 포함되므로, 곧 과거의 내가 미래의 나에게 내리는 지시가 될 수 있습니다. 따라서 '사실'로 작성하면 참조만 하고 끝나지만, '명령'으로 작성하면 미래의 에이전트를 구속하게 됩니다. And if a fact will be stale in a week, it does not belong in memory. (일주일 뒤면 쓸모없어질 사실은 메모리에 넣지 마라) 역시 같은 철학을 다른 방식으로 표현한 것입니다.
태스크 이력은 어디로 ―― session_search와의 역할 분담
MEMORY_GUIDANCE와 MEMORY_SCHEMA 모두 반복해서 use session_search to recall those from past transcripts (과거의 트랜스크립트는 session_search를 사용하여 회상하라)라고 말하고 있었습니다. 여기에 Hermes 기억 설계의 또 다른 기둥이 있습니다. 「안정적인 사실」은 memory에, 「과거의 대화 그 자체」는 session_search에라는 역할 분담입니다.
"""
Session Search Tool - Long-Term Conversation Recall
Single-shape tool with three calling modes (inferred from args, no explicit
...
―― tools/session_search_tool.py @ 6928692, L2-23 (발췌)
session_search는 과거의 모든 세션을 저장한 SQLite DB를 FTS5(전체 텍스트 검색, Full-Text Search)로 조회하는 도구입니다.
No LLM calls anywhere
(No LLM calls anywhere) (LLM 호출은 전혀 없음). 과거의 대화 로그를 필요할 때 검색하여 읽어오기 위한, 말하자면 장기적인 "회의록" 접근 방식입니다. 정리하자면, Hermes의 기억은 세 가지 계층으로 구성됩니다.
| 계층 | 실체 | 성질 | 보유 내용 |
|---|---|---|---|
| 단기 문맥 | messages 이력 (제1장) | 휘발성 · 세션 내 | 현재 대화 중인 내용 |
| 영속 메모리 | MEMORY.md / USER.md | 비휘발성 · 항상 시스템 프롬프트에 포함 | 안정적인 사실 · 선호도 |
| 장기 트랜스크립트 | session DB (FTS5) | 비휘발성 · 검색으로 추출 | 과거의 대화 그 자체 |
메모리(상시 주입)는 용량이 작고 비용이 높기 때문에 "안정적인 사실"로 한정하고, 양이 많은 "과거 로그"는 검색을 통해 온디맨드(On-demand)로 가져옵니다. ―― 이러한 분업이 메모리를 작게 유지하기 위한 핵심입니다.
외부 프로바이더 연동 ―― 메모리 계층을 교체 가능하게 만들기
마지막으로 확장점 하나를 소개합니다. Hermes는 내장된 2개의 파일 메모리 외에, 단 하나의 **외부 메모리 프로바이더 (External Memory Provider)**를 삽입할 수 있습니다. agent/memory_manager.py의 MemoryManager가 그 통합 지점입니다.
"""MemoryManager — orchestrates memory providers for the agent.
...
Only ONE external plugin provider is allowed at a time — attempting to
...
―― agent/memory_manager.py @ 6928692, L1-8 (발췌)
MemoryManager는 내장 프로바이더와 최대 한 개의 외부 프로바이더를 묶어, 시스템 프롬프트에 블록을 제공하는 것(build_system_prompt), 턴(Turn) 전의 선독(prefetch_all), 턴 후의 동기화(sync_all)와 같은 라이프사이클을 각 프로바이더에 위임합니다. 외부 프로바이더를 하나로 제한하는 이유는 tool schema bloat and conflicting memory backends (도구 스키마의 비대화 및 백엔드 충돌)를 방지하기 위해서입니다. 여기서 중요한 점은, 메모리 계층이 "교체 가능한 인터페이스"로 분리되어 있다는 것입니다. 이는 다음에 살펴볼 agent-zero와 같은 벡터 검색형 기억을 Hermes에 외부 프로바이더로서 연결할 수도 있다는 설계적 함의를 담고 있습니다.
실물로 확인하기 ② ― agent-zero: 벡터 DB와 자동 Recall
agent-zero (agent0ai/agent-zero @ f9d8167)의 영속 메모리는 Hermes와는 정반대의 구조를 취합니다. Hermes가 "텍스트 파일에 기록하고, 그 전부를 항상 시스템 프롬프트에 올리는" 방식이라면, agent-zero는 "벡터 DB(Vector DB)에 저장해 두었다가, 필요할 때만 의미적으로 유사한 것을 검색하여 올리는" 방식입니다. 기억 기능은 본체가 아닌 plugins/_memory/라는 플러그인으로 분리되어 있습니다.
4가지 메모리 조작 도구
agent-zero는 기억을 제2·3장에서 보았던 방식대로 프롬프트로 정의된 도구(Tool) 군으로서 제공합니다. plugins/_memory/prompts/agent.system.tool.memory.md가 4가지 도구를 나열하고 있습니다.
## memory tools
use when durable recall or storage is useful
- `memory_load`: args `query`, optional `threshold`, `limit`, `filter`
...
―― prompts/agent.system.tool.memory.md @ f9d8167, L1-6
memory_save (저장) · memory_load (검색 및 취득) · memory_delete (ID 지정 삭제) · memory_forget (쿼리 일치 삭제)의 4가지입니다. Hermes의 add/replace/remove가 "파일 엔트리 편집"이었던 것에 반해, 이것은 "벡터 DB로의 입출력"입니다. 인자(Argument)를 보면 이를 알 수 있습니다 ―― query (검색 문자열), threshold (유사도 임계값 0~1), filter...
(메타데이터 방식). 이것들은 전문 검색(Full-text search)이 아니라 **벡터 유사도 검색 (Vector similarity search)**의 용어들입니다.
저장 도구의 실체는 짧으며, 텍스트를 벡터 DB에 insert_text 할 뿐입니다.
class MemorySave(Tool):
async def execute(self, text="", area="", **kwargs):
if not area:
...
―― plugins/_memory/tools/memory_save.py
@ f9d8167
, L6-19
취득 도구 또한 대칭적이며, query로 **유사도 임계값 검색 (Similarity threshold search)**을 수행하여 히트된 텍스트를 반환합니다.
class MemoryLoad(Tool):
async def execute(self, query="", threshold=DEFAULT_THRESHOLD, limit=DEFAULT_LIMIT, filter="", **kwargs):
db = await Memory.get(self.agent)
...
―― plugins/_memory/tools/memory_load.py
@ f9d8167
, L8-12 (발췌. DEFAULT_THRESHOLD = 0.7, 동일 파일 L4)
기억은 세 가지 **에리어 (Area)**로 분류되어 저장됩니다 ―― MAIN (일반) · FRAGMENTS (대화의 파편) · SOLUTIONS (해결책)입니다.
class Memory:
class Area(Enum):
MAIN = "main"
...
―― plugins/_memory/helpers/memory.py
@ f9d8167
, L54-59
자동 Recall ―― 유틸리티 LLM이 쿼리를 생성하고, 벡터 검색을 수행한다
이 부분이 agent-zero의 진수입니다. Hermes는 메모리를 "기동 시에 전부" 로드했습니다. 반면 agent-zero는 루프(Loop) 안에서 능동적으로, 지금 관련 있는 기억만을 검색하여 문맥(Context)에 삽입합니다. 이를 자동 Recall (자동 회상)이라고 부릅니다. 이를 담당하는 것은 확장 _50_recall_memories.py이며, 파일 경로(message_loop_prompts_after)가 나타내듯 메시지 루프 내에서 프롬프트 구성의 후반 단계에서 실행됩니다.
메커니즘은 2단계로 구성됩니다. 제1단계: 유틸리티 LLM (보조 역할을 하는 작은 LLM)에게 현재 대화로부터 "검색 쿼리 (Search query)"를 생성하게 합니다.
# call util llm to summarize conversation
user_instruction = (
loop_data.user_message.output_text() if loop_data.user_message else "None"
...
―― plugins/_memory/extensions/python/message_loop_prompts_after/_50_recall_memories.py
@ f9d8167
, L84-101 (발췌. 끝부분의 # callback=log_callback, 주석 행은 생략)
사용자의 발언과 직전의 이력을 전달하여, 보조 LLM이 "이 대화와 관련된 기억을 끌어오기 위한 검색어"를 만들게 합니다. 그 쿼리 생성을 지시하는 프롬프트는 명확합니다.
# AI's job
1. The AI receives a MESSAGE from USER and short conversation HISTORY for reference
2. AI analyzes the MESSAGE and HISTORY for CONTEXT
...
―― plugins/_memory/prompts/memory.memories_query.sys.md
@ f9d8167
, L1-4, L10-11 (발췌. 중앙의 Format / Rules / Ignored 섹션은 생략)
제2단계: 해당 쿼리로 벡터 DB를 유사도 검색하고, 결과를 해당 턴의 문맥에 삽입합니다.
# search for general memories and fragments
memories = await db.search_similarity_threshold(
query=query,
...
―― plugins/_memory/extensions/python/message_loop_prompts_after/_50_recall_memories.py
@ f9d8167
, L132-225 (발췌. L137 끝부분의 # exclude solutions 인라인 주석 등 원문 내 주석류는 생략)
검색 결과(memories_txt)를 agent.system.memories.md라는 템플릿에 주입하여 extras["memories"]에 저장합니다. 이 extras (extras_persistent)가 제1장에서 살펴보았던 prepare_prompt (monologue 내)에서 이력(history)의 끝에 합류하여 LLM으로 전달됩니다 (agent.py @ f9d8167, L558-573에서 extras_persistent를 extras 메시지로 이력에 연결).
즉, agent-zero의 영속 메모리(persistent memory)는 시스템 프롬프트(system prompt)에 상주하는 것이 아니라, 루프의 각 반복마다 '지금 필요한 만큼만' 동적으로 문맥(context)에 주입됩니다. Hermes의 '모든 것을 동결하여 항상 포함시키는 방식'과는 정반대되는, 검색 주도(search-driven) 방식입니다.
"오래된 기억에 과도하게 의존하지 마라" ―― 주입 템플릿의 가드레일 (guardrail)
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기