20.1 记忆模型设计

生成模型:Claude Opus 4.6 (anthropic/claude-opus-4-6) Token 消耗:输入 ~300k tokens,输出 ~7k tokens(本节)


LLM 本身没有持久记忆——每次对话都是从零开始。用户昨天告诉 Agent 的偏好、上周讨论的项目决策、上个月记录的待办事项,在新对话中全部消失。OpenClaw 的记忆系统解决了这个问题:让 Agent 可以跨会话记住和检索重要信息。

本节分析记忆系统的设计哲学、文件布局和核心管理器。


20.1.1 "纯 Markdown 即记忆"的设计哲学

为什么不用数据库?

许多 AI Agent 框架将记忆存储在向量数据库(如 Pinecone、Weaviate)或关系型数据库中。OpenClaw 选择了一条不同的路径:记忆的"源"是纯 Markdown 文件

~/.openclaw/
├── MEMORY.md              ← 长期记忆(手动维护或 Agent 写入)
├── memory/
│   ├── 2026-01-15.md      ← 日志式记忆(按日期自动生成)
│   ├── 2026-01-16.md
│   └── project-notes.md   ← 自定义记忆文件

这个设计有几个深层考量:

特性
Markdown 方案
数据库方案

可读性

用户可以直接阅读和编辑

需要专门的查询工具

版本控制

可以纳入 Git 管理

需要导出/迁移工具

Agent 写入

Agent 直接使用文件工具写入

需要专门的 API

可移植性

复制文件即可迁移

需要数据库驱动/协议

调试

cat MEMORY.md

需要数据库客户端

换句话说,OpenClaw 把 Markdown 文件当作真值源(Source of Truth),向量索引只是为了加速搜索而构建的派生索引。索引丢失可以重建,但 Markdown 文件包含了所有原始信息。

路径判定

isMemoryPath 函数定义了哪些文件属于"记忆":

规则很简单:根目录下的 MEMORY.md(或 memory.md),以及 memory/ 目录下的所有 .md 文件。


20.1.2 记忆文件布局

长期记忆:MEMORY.md

MEMORY.md 是用户的长期记忆文件,通常包含持久的偏好、规则和知识。它可以被用户手动编辑,也可以被 Agent 在对话中写入。典型内容:

日志记忆:memory/YYYY-MM-DD.md

memory/ 目录下的日期命名文件是会话记忆的快照。当用户使用 /new 命令开始新会话时,session-memory 钩子会自动将当前会话的关键对话内容保存到这里:

额外记忆路径

除了标准路径,用户可以通过配置添加额外的记忆目录:

这允许用户将团队共享的知识库、项目文档等纳入记忆搜索范围。

文件发现

listMemoryFiles 函数扫描所有记忆文件:

注意安全措施:walkDir跳过符号链接entry.isSymbolicLink()),防止通过符号链接引入不可控的文件。


20.1.3 记忆管理器(src/memory/manager.ts

MemoryIndexManager

MemoryIndexManager 是记忆系统的核心引擎。它负责:文件索引、向量嵌入、BM25 全文搜索、混合排序、缓存管理。

单例缓存

MemoryIndexManager 通过静态方法 get 获取实例,使用全局缓存避免重复创建:

衍生解释:单例模式与缓存键

单例模式(Singleton Pattern)确保一个类只有一个实例。这里使用 Map 作为缓存,Key 由 agentId + 配置序列化组成。这意味着同一个 Agent 使用相同配置时共享同一个管理器实例,但不同 Agent 或不同配置会创建独立实例。这比传统的单例更灵活——它是参数化单例

配置解析

记忆搜索的配置由 resolveMemorySearchConfig 统一解析,支持全局默认值和每个 Agent 的覆盖:

默认值经过精心调校:

参数
默认值
含义

chunking.tokens

400

每个分块约 400 token(~1600 字符)

chunking.overlap

80

相邻分块重叠 80 token

query.maxResults

6

最多返回 6 条结果

query.minScore

0.35

低于 0.35 分的结果被丢弃

hybrid.vectorWeight

0.7

向量相似度占 70% 权重

hybrid.textWeight

0.3

关键词匹配占 30% 权重

文件分块

Markdown 文件被分割成固定大小的分块(Chunk),每个分块独立嵌入向量:

衍生解释:分块与重叠(Chunking & Overlap)

为什么要分块?因为嵌入模型有输入长度限制(如 8192 token),而且更短的文本通常能产生更精确的语义向量。分块的挑战在于语义断裂——一段连贯的论述可能被截断在两个分块的边界。重叠(Overlap) 通过让相邻分块共享一部分文本来缓解这个问题。例如 80 token 的重叠意味着分块 A 的最后 80 个 token 也会出现在分块 B 的开头。

文件监视与脏标记

管理器使用 chokidar 监视记忆文件的变更,并设置 dirty 标记:

当 Agent 调用 memory_search 时,如果 dirtytrue,会先触发增量同步再执行搜索——实现懒索引(Lazy Indexing)


本节小结

  1. "Markdown 即记忆" 是核心设计哲学——纯文本文件作为真值源,向量索引只是派生数据。

  2. 记忆有两层结构——MEMORY.md 存储长期知识,memory/YYYY-MM-DD.md 存储会话快照。

  3. MemoryIndexManager 是核心引擎——通过参数化单例缓存、文件监视、懒索引等机制管理整个记忆生命周期。

  4. 分块算法以行为单位——按字符数估算 token 数,支持重叠以避免语义断裂。

  5. 文件监视实现实时感知——chokidar 监听文件变更,设置脏标记,搜索时按需重建索引。

  6. 配置支持多层覆盖——全局默认值 → Agent 级别覆盖,包括嵌入提供者、分块策略、搜索参数等。

Last updated