s09:记忆系统(Memory System)

核心能力概览


记忆系统解决的核心问题:让 Agent "记住"用户偏好、项目事实,并在后续对话中智能地唤醒这些知识。它包含三个层面的能力:

  1. 持久化存储与索引:记忆如何落盘、如何被检索
  2. 自动提取与巩固:记忆如何从对话中自动产生、如何自我维护
  3. 动态加载与上下文注入:什么记忆会被"唤醒"并注入当前会话

1. 记忆的持久化存储与索引


  • 概念:记忆不再是仅存在于上下文窗口中的临时信息,而是以 Markdown 文件的形式持久化存储在 .memory/ 目录中。
  • 元数据:每个记忆文件都带有 YAML Frontmatter(由 --- 包裹的部分),记录 nametypedescription,让记忆本身变得结构化、可检索。
  • 索引MEMORY.md 文件是所有记忆的目录索引。它不存储完整内容,只存一个列表,让 Agent 每次启动都能快速知道"我拥有哪些记忆",并按需加载。
---
name: user-language-preference
type: user_preference
description: 用户偏好用中文交流和回复
---
用户希望所有回复使用简体中文。

2. 记忆的自动提取与巩固


  • 提取(Extraction)extract_memories 函数在每轮对话结束后自动运行,通过 LLM 分析对话内容,自动识别用户偏好、重要事实等,并将其结构化地写入新的记忆文件。用户不需要显式说"记住…",但系统也鼓励这么说。
  • 巩固(Consolidation)consolidate_memories 是一个**“记忆的垃圾回收与整理”**机制。当记忆文件数量超过阈值,它会调用 LLM 来合并重复项、删除过时信息,保持记忆库的精简和高效。

3. 记忆的动态加载与上下文注入


  • 加载(Loading)select_relevant_memories 是注入上下文前的关键一步。它不会把整个记忆库都塞给 LLM,而是根据当前对话,通过 LLM 自行选择最相关的一小部分记忆。
  • 注入(Injection):在调用 LLM 之前,agent_loop 会将选中的相关记忆内容注入到本轮用户消息中,并构建一个包含了记忆索引的 system 提示词,从而**"唤醒"Agent 对该用户和项目的认识**。

记忆基础设施:存储、读写与索引


记忆是文件系统上独立的 .md 文件——用最简单、最通用的方式(文件)实现了持久化,利用 YAML frontmatter 为每个记忆实体添加结构化元数据(类型、描述),为后续的语义选择和整理打下基础。

索引模式MEMORY.md 扮演了数据库索引的角色。LLM 每次只需要读取这个轻量级的索引,就能知道有哪些记忆,然后按需去读取具体的文件。这解决了"记忆库太大,无法塞入上下文"的核心问题。

# 工作区和目录定义
WORKDIR = Path.cwd()
MEMORY_DIR = WORKDIR / ".memory"
MEMORY_DIR.mkdir(exist_ok=True)  # 确保目录存在
MEMORY_INDEX = MEMORY_DIR / "MEMORY.md"

def _parse_frontmatter(text: str) -> tuple[dict, str]:
    # ... 解析 YAML frontmatter,将元数据和正文分离 ...
    pass

def write_memory_file(name: str, mem_type: str, description: str, body: str):
    """写入单个记忆文件"""
    # ... 生成文件名,写入带 frontmatter 的文件 ...
    _rebuild_index() # 关键:每次写入后,立即重建总索引
    return filepath

# 保证了索引和实际文件的一致性。
def _rebuild_index():
    """扫描所有 .md 文件,用它们的元数据构建 MEMORY.md"""
    # ... 遍历 MEMORY_DIR 下的记忆文件,提取名称和描述,生成索引列表 ...
    pass

记忆的智能选择与加载


决定了什么记忆会被"唤醒"并注入到当前会话的上下文中,是记忆系统的核心智能所在。

  • 两级检索:这是一个**"先摘要,后全文"的模式。- select_relevant_memories 只处理轻量级的摘要信息**(名称和描述),快速筛选出候选记忆;然后,load_memories 才去读取这些候选者的完整正文。这极大地节约了 LLM 的上下文窗口。
  • AI 选择,而非规则用 LLM 来选择记忆。函数构造了一个 prompt,将"最近对话"和"记忆目录"一起交给模型,让它自己判断哪些记忆相关。这使得记忆的关联更加语义化、智能化,远胜于固定的关键词匹配。关键词匹配仅作为 LLM 调用失败时的优雅降级方案。
  • 结构化注入<relevant_memories> ... </relevant_memories> 这种 XML 标签包裹,是一种清晰的提示工程技巧,能帮助 LLM 更好地理解"这是一段被注入的外部记忆,而非当前用户说的话"。
# 只处理轻量级的摘要信息(名称和描述),快速筛选出候选记忆
def select_relevant_memories(messages: list, max_items: int = 5) -> list[str]:
    """使用 LLM 从记忆目录中选择与当前对话最相关的记忆"""
    # 1. 收集最近的对话作为上下文
    # 2. 将记忆的【索引目录】格式化为 prompt
    # 3. 调用 LLM,让它返回相关记忆的索引列表 (JSON 数组)
    # 4. 失败时回退到简单的关键词匹配
    pass

# 读取候选者的完整正文,节约了 LLM 的上下文窗口
def load_memories(messages: list) -> str:
    """加载相关记忆的完整内容,并打包成 XML 块"""
    selected_files = select_relevant_memories(messages)
    # ... 读取这些文件的完整内容,包装在  标签中 ...
    return "\n\n".join(parts)

记忆的生成与演化


定义了新记忆是如何产生的,以及旧的记忆库是如何自我维护的。

  • 自动进化:让记忆库成为一个活的知识库,可以随对话不断增长和完善。它不需要人工干预,Agent 能通过反思对话来自动学习。
  • 熵增控制:是防止记忆库无限膨胀和混乱的关键。它周期性地对记忆进行"压缩"和"整理",确保记忆库的规模可控、内容内聚、无冗余。这模仿了人脑的记忆巩固过程(从海马体到皮层)。
  • 双重 LLM 使用:用模型来管理知识——无论是提取新记忆还是整理旧记忆,其核心逻辑都是由一个独立的、目标明确的 LLM 调用来完成的。
def extract_memories(messages: list):
    """核心:从对话历史中提取新的记忆"""
    # 1. 收集最近 N 轮对话内容
    # 2. 获取现有记忆的摘要,用于去重
    # 3. 构造 prompt,要求 LLM 提取 'user preference', 'project fact' 等
    # 4. 解析 LLM 返回的 JSON,调用 write_memory_file 写入新记忆
    pass

def consolidate_memories():
    """当记忆文件超过阈值时,用 LLM 合并和整理记忆库"""
    # 1. 将所有记忆文件的内容和元数据格式化为 prompt
    # 2. 要求 LLM 合并重复、删除过时信息,生成一个精简后的记忆列表
    # 3. 清空旧的记忆文件,用 LLM 返回的新列表重新写入
    pass

记忆在 Agent 主循环中的集成


1. 生命周期集成

  • (a)会话开始 / 每轮对话前:通过 build_systemload_memories 加载和注入长期记忆。
  • (b)会话结束 / 每轮对话后:通过 extract_memoriesconsolidate_memories 进行学习和整理。

2. 上下文编织(Context Weaving)

代码将记忆内容直接拼接在本轮用户消息的前面。这是一种将长期记忆"编织"进短期会话上下文的标准模式。模型在看到用户问题之前,先看到了关于用户和项目的"背景信息",从而能做出更个性化、更符合上下文的回应。

def build_system() -> str:
    # 动态构建 system prompt,包含 MEMORY.md 索引
    index = read_memory_index()
    # ...
    return f"You are a coding agent..." + memories_section + "..."

def agent_loop(messages: list):
    # ...
    # 1. 在每轮用户输入后,立即调用 load_memories 加载相关记忆
    memories_content = load_memories(messages)
    # 2. 动态构建包含记忆索引的 system prompt
    system = build_system()
    # ... 在 while 循环中 ...
        # 3. 将加载到的记忆内容,注入到本轮 request 的用户消息中
        # 消息列表的浅拷贝,避免直接修改 messages
        request_messages = messages.copy()
        request_messages[memory_turn] = {
            # 展开该消息的所有原有字段,只覆盖 content 字段
            **messages[memory_turn],
            # 记忆内容被拼接在该消息原文的最前面,中间用两个换行分隔
            "content": memories_content + "\n\n" + messages[memory_turn]["content"],
        }
        response = client.messages.create(
            model=MODEL, system=system, messages=request_messages, ...
        )
        # ... 执行工具 ...
    # 4. 循环结束(模型不再调用工具后),提取和巩固记忆
    extract_memories(pre_compress)
    consolidate_memories()
    return

本模块知识点


  • 持久化存储:记忆以 Markdown 存 .memory/,YAML Frontmatter 结构化元数据,MEMORY.md 作索引。
  • 自动提取与巩固:extract_memories 每轮结束提取,consolidate_memories 超阈值时合并去重(熵增控制)。
  • 两级检索:先摘要筛选候选,再读全文;用 LLM 选择而非关键词(失败回退关键词)。
  • 上下文编织:记忆内容拼接在本轮用户消息前面注入。