Memory 四层架构:Session Context → Daily Logs → MEMORY.md → Vector Index
第26章 Memory 四层架构:Session Context → Daily Logs → MEMORY.md → Vector Index
"The model only 'remembers' what gets saved to disk — there is no hidden state." —— OpenClaw 设计文档
26.1 为什么需要 Memory 分层?
大语言模型本质上是无状态的。每次推理调用结束,中间的激活值、注意力权重全部消失。在没有外部持久化机制的情况下,Agent 在对话结束后就会"失忆"——它不知道上周你告诉过它什么,不记得你的名字拼写偏好,也无法回顾三天前做过的决策。
OpenClaw 的 Memory 系统通过四层架构解决这个根本矛盾:将"需要记住的信息"外化为文件系统上的持久化数据,再在合适的时机重新注入到 Context Window 中。
这四层从"最短暂"到"最持久"依次为:
Session Context
↓ 压缩后可能晋升
Daily Logs
↓ 周期性整合
MEMORY.md(Long-term Memory)
↓ 并行建立
Vector Index(SQLite + Embeddings)
每一层都有明确的存储位置、写入时机和读取策略。理解这四层,是掌握 OpenClaw 状态管理的核心。
26.2 第一层:Session Context(会话上下文)
26.2.1 存储位置与文件格式
~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl
每一行是一条 JSON 消息记录,格式遵循对话历史标准:
{"role":"user","content":"帮我分析这份销售报告","timestamp":"2026-04-26T09:00:00Z"}
{"role":"assistant","content":"好的,请上传文件...","timestamp":"2026-04-26T09:00:02Z"}
{"role":"tool","name":"read_file","result":"...","timestamp":"2026-04-26T09:00:05Z"}
26.2.2 加载时机
Session Context 是最"实时"的一层,在整个当前对话期间始终存在于 Context Window 中。当 Agent 启动一个新的 Session 时,对应的 .jsonl 文件被完整读入(如果文件存在),随后每轮新消息追加写入同一文件。
关键约束:Context Window 是有限的(例如 200K tokens)。当 Session 历史过长,必须触发 Compaction 或 Pruning 机制(见第 27 章)。
26.2.3 适合存储的信息类型
| 信息类型 | 示例 | 生命周期 |
|---|---|---|
| 当前任务的中间状态 | "刚刚处理了第 3 页,继续第 4 页" | 仅限本次 Session |
| 工具调用结果 | 读取到的文件内容、API 返回 | 仅限本次 Session |
| 用户在本轮说的指令 | "今天用正式语气" | 仅限本次 Session |
| 临时变量/草稿 | 代码片段、计算中间值 | 仅限本次 Session |
Session Context 是高密度、短生命周期的信息层。信息丰富但易逝;Session 结束后,如果没有显式持久化,这些内容将永远消失。
26.3 第二层:Daily Logs(每日日志)
26.3.1 存储位置与文件格式
~/.openclaw/workspace/<workspaceId>/memory/YYYY-MM-DD.md
例如:
~/.openclaw/workspace/myproject/memory/2026-04-26.md
~/.openclaw/workspace/myproject/memory/2026-04-25.md
文件格式为普通 Markdown,由 Agent 自行决定内容结构,通常呈现为带时间戳的追加记录:
## 2026-04-26
### 09:15 — 销售报告分析
用户上传了 Q1-2026-sales.xlsx,发现华东区同比增长 23%,用户希望下周做 PPT 汇报。
### 14:30 — 代码审查
审查了 auth-service 的 PR #142,发现一个潜在的 SQL 注入点,已告知用户。
26.3.2 加载时机
每次 Session 启动时,OpenClaw 自动加载今日和昨日两份日志文件(如果存在)。这确保 Agent 在新 Session 开始时,能够感知最近 48 小时内发生的重要事件,而不需要用户重新解释上下文。
# config 中的加载策略(默认)
dailyLogDays: 2 # 加载今日 + 昨日
26.3.3 写入时机
Daily Logs 的写入发生在两种情况:
- Compaction Pre-flush:当检测到 Context Window 即将满时,Agent 在压缩前将重要信息写入当日日志(详见第 27 章)。
- Session 结束钩子:Session 正常关闭时,Agent 可选择性地将关键收获总结写入日志。
26.3.4 适合存储的信息类型
| 信息类型 | 示例 |
|---|---|
| 今日完成的任务记录 | "完成了数据库迁移脚本" |
| 发现的重要问题 | "发现线上环境的内存泄漏" |
| 用户的偏好变化 | "用户今天更倾向于简洁输出" |
| 跨 Session 的连续任务进度 | "还差第 4、5 章未完成" |
Daily Logs 是中等密度、周期性生命周期的信息层,是 Session Context 与 MEMORY.md 之间的缓冲区。
26.4 第三层:Long-term Memory(MEMORY.md)
26.4.1 存储位置
~/.openclaw/workspace/<workspaceId>/MEMORY.md
这是一个单一的、精选的 Markdown 文件,存储跨越时间的核心知识。
26.4.2 内容示例
# Memory Index
- [用户基本信息](user_profile.md) — 偏好简洁、直接的回答风格,不喜欢过多的礼节性语言
- [项目架构决策](arch_decisions.md) — 使用微服务架构,服务间通过 gRPC 通信
- [代码规范](coding_standards.md) — 使用 TypeScript strict mode,禁止 any 类型
- [重要联系人](contacts.md) — 技术负责人:张三([email protected])
MEMORY.md 通常是一个索引文件,指向更详细的子文件;也可以是一个内联的紧凑摘要。
26.4.3 加载时机
MEMORY.md 仅在主私有 Session(Private Primary Session)启动时自动加载。这个设计是刻意的:
- 主私有 Session:拥有完整的长期记忆访问权限
- 子 Session(Sub-session):默认不加载,仅获得任务所需的最小上下文
- 群组 Session(Group Session):不加载个人 MEMORY.md,只加载工作区共享的上下文
# Session 类型与 MEMORY.md 加载规则
session:
primary_private:
load_memory: true
sub_session:
load_memory: false
inherit_context: minimal
group_session:
load_memory: false
load_shared_context: true
26.4.4 写入时机
MEMORY.md 的更新是慎重的、有选择性的,发生在:
- Dreaming 进程:后台整合进程定期将 Daily Logs 中的重要信息晋升至 MEMORY.md
- 显式 Agent 决策:Agent 判断某条信息具有长期价值,主动写入
- 用户指令:用户直接要求"记住这件事"
26.4.5 适合存储的信息类型
| 信息类型 | 示例 | 更新频率 |
|---|---|---|
| 用户身份信息 | 名字、职位、团队 | 极少 |
| 长期偏好 | 语言风格、输出格式偏好 | 偶尔 |
| 项目核心决策 | 技术选型、架构决策 | 偶尔 |
| 重要约束 | 安全规则、禁止事项 | 极少 |
| 领域知识摘要 | 公司业务流程的关键节点 | 随项目演进 |
26.5 第四层:Vector Index(向量索引)
26.5.1 存储位置
~/.openclaw/memory/<agentId>.sqlite
SQLite 数据库中包含两个关键组件:
-- BM25 全文检索虚表
CREATE VIRTUAL TABLE memory_fts USING fts5(content, ...);
-- 向量存储表(sqlite-vec 扩展)
CREATE TABLE memory_embeddings (
id INTEGER PRIMARY KEY,
content TEXT,
embedding FLOAT[768], -- 维度取决于 Embedding 模型
source_file TEXT,
created_at INTEGER
);
26.5.2 加载时机
向量索引不会"加载"到 Context Window——它是一个按需查询的外部检索层。当 Agent 需要回忆某些信息时,发起语义搜索查询,检索结果以文本片段的形式注入上下文。
用户提问 → Agent 判断需要检索历史记忆 → 向量查询 → 返回 Top-K 片段 → 注入 Context → 生成回答
26.5.3 内容来源
向量索引索引的是其他三层的内容:Daily Logs、MEMORY.md,以及历史 Session 中被标记为值得检索的片段。这是一个派生层——其所有内容都来自上层文件,可以随时通过重新索引来重建。
# 重建向量索引(不会丢失任何原始信息)
openclaw memory rebuild-index --workspace myproject
26.5.4 适合存储的信息类型
| 检索场景 | 示例查询 |
|---|---|
| 历史决策回顾 | "我们之前为什么选择 PostgreSQL?" |
| 类似问题参考 | "上次处理这类 bug 的方法是什么?" |
| 文档语义搜索 | "找出所有关于认证流程的讨论" |
| 长期知识积累 | "有没有关于性能优化的历史记录?" |
26.6 文件即数据库:设计哲学深解
26.6.1 为什么选择 Markdown 而非数据库?
OpenClaw 做出了一个鲜明的技术决策:用 Markdown 文件而非关系型数据库或专有格式作为核心存储。这个决策背后有以下理由:
1. 人类可读性(Human Readability)
# 任何人都可以用普通工具查看 Agent 的记忆
cat ~/.openclaw/workspace/myproject/MEMORY.md
grep "数据库" ~/.openclaw/workspace/myproject/memory/2026-04-26.md
相比之下,SQLite 二进制文件、向量数据库的专有格式需要专门工具才能读取。
2. 可版本控制性(Version Control Friendly)
# Memory 可以纳入 Git 版本控制
cd ~/.openclaw/workspace/myproject
git init
git add memory/ MEMORY.md
git commit -m "Agent memory snapshot: 2026-04-26"
每次 MEMORY.md 的变更都可以追踪、回滚、对比 diff。
3. 可人工编辑性(Human Editable)
用户可以直接打开 Markdown 文件,手动修改、删除错误的记忆、添加外部知识。这种透明性是闭源记忆系统无法提供的。
4. 无隐藏状态(No Hidden State)
"The model only 'remembers' what gets saved to disk — there is no hidden state."
这句设计宣言意味着:Agent 的所有"记忆"都在文件系统上可见。不存在什么神秘的内部状态——你看到的就是它所知道的全部。
26.6.2 SQLite 作为加速层的角色
SQLite(向量索引层)是整个架构中唯一的"非人类可读"存储,但它的角色是加速层而非主存储:
Markdown 文件(权威数据源)
↓ 异步索引
SQLite(检索加速层)
↓ 可随时重建
SQLite 中的向量和 BM25 索引完全是从 Markdown 文件派生出来的。如果 SQLite 损坏或删除:
openclaw memory rebuild-index --workspace myproject
# 重新读取所有 Markdown 文件,重建索引
# 不会丢失任何信息
这个设计确保了 Markdown 是"单一事实来源"(Single Source of Truth),SQLite 只是让搜索更快。
26.7 Memory 与 Context Window 的关系
Context Window 是 LLM 每次推理时实际"看到"的信息总量,Memory 系统的核心任务是决策哪些信息值得占用宝贵的 Context 空间。
Context Window(200K tokens 示例)
┌──────────────────────────────────────────────┐
│ System Prompt(AGENTS.md / SOUL.md) ~8K │
│ MEMORY.md(如果是主 Session) ~4K │
│ Daily Logs(今日 + 昨日) ~8K │
│ Retrieved Memory Chunks(向量检索结果) ~4K │
│ Current Session History 余量 │
│ Available for new messages ~176K │
└──────────────────────────────────────────────┘
当 Session 历史增长,可用空间减少,Compaction 机制介入(见第 27 章)。
26.8 不同 Session 类型的 Memory 加载差异
| Session 类型 | Session Context | Daily Logs | MEMORY.md | Vector Index |
|---|---|---|---|---|
| 主私有 Session | 完整加载 | 今 + 昨 | 完整加载 | 按需检索 |
| 子 Session | 当前任务上下文 | 不加载 | 不加载 | 受限检索 |
| 群组 Session | 群组共享历史 | 不加载 | 不加载 | 共享工作区检索 |
| 只读沙箱 Session | 当前上下文 | 只读 | 只读 | 只读检索 |
子 Session 的设计遵循最小上下文原则:它只获得完成当前子任务所需的最少信息,避免将无关的历史信息污染任务上下文,同时也减少 Token 消耗。
26.9 实践指南:什么信息该放哪一层
决策树
这条信息需要记住多久?
├── 只需要今天/这次对话 → Session Context(自动管理,无需操作)
├── 需要几天内可以回忆 → Daily Logs(Compaction 时自动写入)
├── 永久保留,每次 Session 都需要 → MEMORY.md(显式写入或 Dreaming 晋升)
└── 需要语义搜索找回 → Vector Index(自动索引,无需操作)
层级选择原则
| 原则 | 说明 |
|---|---|
| 越持久越精简 | MEMORY.md 应只存核心知识,避免堆砌 |
| 不要手动管理 Session Context | 它由系统自动维护 |
| Daily Logs 允许冗余 | 流水账式记录没问题,Dreaming 会整合 |
| Vector Index 是自动的 | 不需要手动决定"是否索引",系统自动完成 |
| 定期审查 MEMORY.md | 过时的信息会占用 Context,应定期清理 |
常见错误
❌ 错误:把所有对话摘要都写入 MEMORY.md
→ 结果:MEMORY.md 膨胀到 50K tokens,每次都消耗大量 Context
✓ 正确:只将"影响 Agent 长期行为的决策"写入 MEMORY.md
→ 结果:MEMORY.md 保持在 2-4K tokens,高度精炼
❌ 错误:依赖 Session Context 存储重要决策(不做任何持久化)
→ 结果:Session 结束后,关键决策丢失
✓ 正确:在 Session 结束时,触发 Memory Flush,将关键点写入 Daily Logs
26.10 本章小结
OpenClaw 的四层 Memory 架构,是对"LLM 无状态"这一根本限制的系统性解决方案:
- Session Context — 当前对话的工作内存,自动管理
- Daily Logs — 近期记忆的缓冲区,追加写入,自动加载
- MEMORY.md — 长期精选记忆,谨慎维护,主 Session 加载
- Vector Index — 语义检索加速层,派生可重建
"文件即数据库"的设计哲学贯穿全局:Markdown 是权威数据源,SQLite 只是加速检索的派生层。这种设计让 Agent 的记忆系统对用户完全透明、完全可控,没有任何隐藏状态。
下一章,我们将深入 Compaction 机制——当 Context Window 接近极限时,系统如何在不丢失关键信息的前提下完成"记忆压缩"。
下一章:第27章 — Compaction 算法:触发公式、Pre-flush 机制与长会话信息保全