乐于分享
好东西不私藏

OpenClaw架构-Session 管理与记忆系统深度拆解

OpenClaw架构-Session 管理与记忆系统深度拆解

文档版本:v2.3 | 更新日期:2026-03-12 | 分析对象:openclaw/openclaw

前置阅读:02-agent-runtime.md(Agent Loop、Context 组装)。本文聚焦 Session 生命周期与 Memory 持久化,Context 组装的 Token 预算与修剪策略详见 02 §6。

功能实现状态

模块
功能
状态
章节
Session
创建 / 查找 / 生命周期管理
✅ 已实现
§2.1-2.4
Session
Sliding Window 修剪
✅ 已实现
§2.5
Session
Summary Compression
📋 规划中
§2.5
Session
Importance Scoring 修剪
📋 规划中
§2.5
Memory
6 类记忆 Schema + L0/L1/L2 分层
✅ 已实现
§3.1-3.2
检索
BM25 + Vector 混合检索 (文件后端)
✅ 已实现
§4.1-4.3
检索
sqlite-vec 后端
⚙️ 可选 (已集成)
§4.3
检索
Reranking (Cross-Encoder)
⚙️ 可选 (默认关闭)
§4.1
检索
多语言分词 (中/日/韩/英/阿)
✅ 已实现
§4.5
FSRS
FSRS-6 时效性加权
✅ 已实现
§5.1-5.2
写入
自动提取 + 去重 + 限流
✅ 已实现
§6.1
写入
错误降级 (LLM/Embedding/持久化)
✅ 已实现
§6.1
反思
LLM 反思 + 洞察层
✅ 已实现
§7
跨 Agent
共享 User Memory
✅ 已实现
§8
迁移
导入 / 导出
✅ 已实现
§9
迁移
跨版本格式自动升级
📋 规划中
§9
安全
本地存储 + 文件权限
✅ 已实现
§12
安全
AES-256-GCM 静态加密
📋 规划中
§12

1. 定位

Session 管理"短期记忆"(单次对话上下文),Memory 管理"长期记忆"(跨会话持久知识)。两者配合让 Agent 既能连贯对话,又能"记住"用户的偏好和历史。

每轮结束 → 提取写入

会话开始 → 检索注入

长期记忆 (Memory)
偏好 · 档案 · 事件 · 经验 · 洞察
生命周期: 永久 · memory/
短期记忆 (Session)
消息历史 · 工具调用 · 临时变量
生命周期: 会话内 · sessions/


2. Session 管理

2.1 Session 数据结构

  ~/.openclaw/agents/<agentId>/sessions/<sessionId>/  ├── meta.json             Session 元数据  ├── messages.jsonl        消息历史(追加写入, JSONL 格式)  └── context.json          上下文快照(修剪后的缓存)  meta.json:  {    "sessionId": "s-abc123",    "agentId": "main",    "channel": "telegram",    "chatId": "123456789",    "userId": "123456789",    "createdAt": "2026-02-04T10:00:00Z",    "lastActiveAt": "2026-02-26T14:30:00Z",    "messageCount": 47,    "totalInputTokens": 65200,    "totalOutputTokens": 20000,    "totalCost": 0.085,    "status": "active",    "model": "moonshot/kimi-k2.5",    "summaryCheckpoint": 20       // Summary Compression 的最后压缩位置  }

2.2 消息存储格式

  messages.jsonl (每行一条消息, 追加写入):  {"idx":0,"role":"user","content":"你好","ts":"2026-02-04T10:00:00Z","tokens":5}  {"idx":1,"role":"assistant","content":"你好!有什么可以帮你的?","ts":"...","tokens":15}  {"idx":2,"role":"user","content":"帮我清一下邮件","ts":"...","tokens":8}  {"idx":3,"role":"assistant","content":"","toolCalls":[{"id":"tc-1","name":"gmail.list","args":{}}],"ts":"...","tokens":20}  {"idx":4,"role":"tool","toolCallId":"tc-1","content":"{...邮件列表...}","ts":"...","tokens":350}  {"idx":5,"role":"assistant","content":"你有5封新邮件...","ts":"...","tokens":80}  为什么用 JSONL 而不是 JSON 数组?  • 追加写入: 不需要读取全部 → 修改 → 重写  • 崩溃安全: 写到一半最多丢最后一条  • 流式友好: 可以按行读取,不需要全部加载  并发安全:  • 单 Agent 串行处理消息 (Agent Loop 内无并发写入同一 Session)  • 跨 Agent 写不同 Session → 无冲突  • 极端场景: 同一 chatId 在 idle_timeout 前后快速连发    → SessionManager 内部用 per-key mutex 保护 getOrCreate    → 保证同一时刻只有一个 Agent Loop 持有该 Session

2.3 Session 生命周期

idle_timeout (30min)

新消息到来

7天无活动

创建
活跃 (active)
休眠 (dormant)
归档 (archived)

各状态说明:

  • • 创建:新用户首次消息、显式 session.create、或 /session new 命令
  • • active:持续有消息交互,每条消息更新 lastActiveAt,追加到 messages.jsonl
  • • dormant:序列化到磁盘,释放内存(5MB → 1KB),保留 meta.json
  • • archived:消息历史压缩归档,meta.json 标记 status: archived,关键事实已持久化到 Memory

2.4 Session 查找策略

命中

未命中

命中

未找到

用户消息到达
构建 keychannel:chatId
内存 Map 查找
使用该 Session
磁盘扫描meta.json 匹配
加载到内存
创建新 Session

查找逻辑:

  • • key 构建channel:chatId,例 telegram:123456789
  • • 磁盘查找:扫描 sessions/*/meta.json,匹配 channel + chatId + status ≠ archived
  • • 新建sessionId = s- + uuidv7(),创建目录 + 写入 meta.json

优化:启动时加载所有 meta.json 到内存 Map,不加载 messages(按需加载)。

Group Chat:群聊场景下 chatId 为群组 ID,同一群内所有用户共享一个 Session。消息中的 sender.userId 区分发言人,记忆提取时按 userId 分别写入各自的 User Memory。详见 02-agent-runtime.md §5 中的 DM/Group 策略差异。

2.5 会话历史修剪与 Summary Compression

meta.json 中的 summaryCheckpoint 字段标记 Summary Compression 的最后压缩位置。修剪策略由 Agent 配置 context.pruneStrategy 决定,详细算法见 02-agent-runtime.md §6.3,此处仅说明 Session 侧的存储行为:

  ┌────────────────────────────────────────────────────────────┐  │  Sliding Window (默认, 已实现):                              │  │  • Session 不做任何预处理                                    │  │  • Context 组装时从 messages.jsonl 尾部按 Token 预算截取     │  │  • messages.jsonl 保留全量(归档前不删除)                    │  │                                                             │  │  Summary Compression (规划中):                               │  │  • 消息数 > threshold (默认 30) 时触发                       │  │  • 对索引 0 到 summaryCheckpoint 的消息生成摘要              │  │  • 摘要写入 context.json,更新 summaryCheckpoint             │  │  • Context 组装: [摘要] + [checkpoint 之后的原始消息]        │  │  • 原始 messages.jsonl 不删除(审计可追溯)                  │  │                                                             │  │  Importance Scoring (规划中):                                │  │  • 每条消息附带重要性评分 → 优先保留高分消息                 │  │  • 适合长周期任务对话                                        │  └────────────────────────────────────────────────────────────┘

3. Memory 系统(长期记忆)

3.1 记忆 Schema

OpenClaw 的记忆分为 User Memory 和 Agent Memory:

Agent Memory
lesson 经验
pattern 模式
case 案例
User Memory
profile 档案
preference 偏好
event 事件

合并策略总结:

类型
mergeStrategy
说明
profile
overwrite
新值直接覆盖旧值
preference
latest-wins
保留最新的偏好表达
event
no-merge
每条独立, 永不合并
lesson
append
追加新经验
pattern
latest-wins
持续精炼, 保留最新版
case
no-merge
每条独立, 永不合并

关键规则:档案/偏好 → 总是合并(新覆盖旧);事件/案例 → 永远不合并(每条独立)。搞反了 = 灾难(偏好丢失 or 事件重复)。

3.2 三级内容模型 (L0/L1/L2)

命中后

显式请求

L0 ~100 tokens摘要 · 索引/去重/Embedding
L1 ~500 tokens概览 · 按需加载
L2 不限完整原文 · 按 id 加载

各层职责:

  • • L0:每次检索都加载到 Context。例:用户偏好 TypeScript,不喜欢 Java
  • • L1:模型需要更多细节时展开。例:用户多次表达对 TS 的偏好
  • • L2:包含原始对话片段和时间戳,仅 memory.get(id) 时按需加载

为什么分层? 10,000 条记忆 × L2 全文 = 几十万 Token 检索成本。分层后:索引只用 L0 → 命中后按需展开 → 节省 90%+。Context 中通常注入 Top-K 条 L0 摘要(~100 tokens × 10 = 1K),特别相关时自动展开为 L1。


4. 混合检索引擎

支持 BM25 + 向量 + Reranking 三路混合搜索(架构自 v2026.2.2 引入)。

存储后端演进:v2026.2.2 初版使用自建 JSON 倒排索引 + hnswlib-node HNSW 索引(零外部依赖);v2026.2.22 起引入 sqlite-vec 作为可选后端(package.json 已列为依赖),可统一管理向量和元数据。两种后端通过 memory.backend 配置切换,默认仍为文件索引方案。

4.1 检索流程

用户查询
[1] 预处理关键词 · Embedding · 语言检测
[2a] BM25 稀疏检索Top-50
[2b] Vector 密集检索Top-50
[3] 加权 RRF 融合
Reranking?
[5] FSRS-6 加权
[4] Reranking 精排bge-reranker-v2-m3
[6] 返回 Top-K

各步骤说明:

  • • [1] 提取关键词、生成 Embedding、语言检测(如 zh-CN)
  • • [2a] BM25:倒排索引 + jieba-wasm 分词,返回 Top-50
  • • [2b] Vector:cosine 相似度 + HNSW 索引,返回 Top-50
  • • [3] RRF 公式:score = 0.3/(k + rank_bm25) + 0.7/(k + rank_vec),k=60
  • • [4] Cross-Encoder 对 RRF Top-K 候选重排序,最终排名以 Reranker 分数为准
  • • [5]final_score = fusion_score × retrievability
  • • [6] 返回 Top-K 记忆条目,含 L0 摘要 + 元数据

4.2 Embedding 模型选型

模型
维度
大小
速度
质量
text-embedding-3-small (OpenAI)
1536
API 调用
50-200ms (网络)
★★★★★
bge-small-en-v1.5 (本地 ONNX)
384
130MB (本地)
5-20ms (本地)
★★★★
bge-m3 (本地 ONNX, 多语言)
1024
2.2GB (本地)
20-80ms (本地)
★★★★★ (多语言)

默认策略:

  • • 有 OPENAI_API_KEY → text-embedding-3-small(最佳质量)
  • • 无 API Key → bge-small-en-v1.5(本地推理, 零成本)
  • • 多语言需求 → bge-m3(中英日韩全覆盖)
{  "memory": {    "embedding": {      "provider": "openai",      "model": "text-embedding-3-small",      "localModel": "bge-small-en-v1.5",      "dimensions": 1536,      "batchSize": 100    }  }}

API 限流与降级

批量写入记忆时可能一次生成数十条 Embedding,需要处理 API 限流:

策略
说明
批量分片
batchSize
(默认 100)控制单次 API 调用条数,超过则分片串行
指数退避
遇到 429 / 5xx 时,按 1s → 2s → 4s → 8s 退避重试,最多 3 次
本地 fallback
API 连续失败 ≥ 3 次 → 自动切换到 localModel(本地 ONNX),本次 Session 内不再尝试 API
延迟补齐
本地模型维度与 API 模型不同(384 vs 1536)→ 标记 embeddingPending: true,下次网络恢复时由 openclaw memory index --reembed 统一用 API 模型重新生成

4.3 索引实现

  ~/.openclaw/agents/<agentId>/memory/index/  ├── bm25.idx            BM25 倒排索引 (JSON)  ├── vector.idx           HNSW 向量索引 (二进制)  ├── metadata.json        记忆元数据 (id, category, fsrs, ts)  └── config.json          索引配置 (维度, 参数)  BM25 倒排索引结构:  {    "vocab": { "部署": 0, "方案": 1, "typescript": 2, ... },    "df": { "0": 15, "1": 8, "2": 42, ... },    "postings": {      "0": [{"id":"mem-001","tf":2}, {"id":"mem-015","tf":1}],      "1": [{"id":"mem-001","tf":1}, {"id":"mem-023","tf":3}]    },    "totalDocs": 2847,    "avgDocLength": 45  }  HNSW 向量索引:  • 基于 hnswlib-node (C++ 绑定)  • 参数: M=16, efConstruction=200, ef=100  • 支持增量插入 (无需重建全量索引)  • 内存占用: ~6KB/向量 (1536 维 × float32)    384 维 (bge-small) → ~1.5KB/向量  • 2847 条记忆 (1536 维) → ~18MB 索引  文件索引方案 vs sqlite-vec 后端:  ┌──────────────────┬────────────────────┬────────────────────┐  │                  │ 文件索引 (默认)     │ sqlite-vec         │  ├──────────────────┼────────────────────┼────────────────────┤  │ 依赖             │ 零 (纯 JS/WASM)    │ sqlite-vec native  │  │ BM25 调优        │ 完全可控           │ FTS5 内置          │  │ 可观测性         │ cat 直接查看       │ 需 SQL 查询        │  │ 原子性           │ WAL 自实现         │ SQLite 事务        │  │ 10K+ 条性能      │ 开始变慢           │ 稳定               │  │ 适用场景         │ 个人轻量使用       │ 大量记忆/多 Agent   │  └──────────────────┴────────────────────┴────────────────────┘  配置: { "memory": { "backend": "file" | "sqlite-vec" } }

4.4 索引一致性保障

BM25 和 HNSW 是两份独立索引,需确保与磁盘上的记忆记录保持一致:

  ┌──────────────────────────────────────────────────────────────┐  │                 索引一致性机制                                  │  │                                                               │  │  [1] 写入时: 原子化三步写入                                   │  │      ├─ WAL (Write-Ahead Log) 先记录写入意图                  │  │      ├─ 写 records/{user|agent}/<id>.json (源数据)            │  │      ├─ 追加 BM25 posting                                     │  │      └─ 追加 HNSW 向量                                        │  │         写入失败时, 下次启动回放 WAL 补齐缺失索引条目          │  │                                                               │  │  [2] 启动时: 完整性校验                                       │  │      ├─ 扫描 records/ 获取文件计数                             │  │      ├─ 对比 metadata.json 中的 totalDocs                     │  │      └─ 不一致 → 触发增量重建 (仅补齐差异部分)                │  │                                                               │  │  [3] 定期维护: openclaw memory index                          │  │      ├─ 全量校验 BM25 / HNSW / metadata 三者一致              │  │      └─ 需要重建时,执行重新索引流程                           │  └──────────────────────────────────────────────────────────────┘

4.5 多语言全文搜索

v2026.2.22 新增:

语言
分词方案
中文
jieba-wasm (内置, 无外部依赖)
日文
TinySegmenter (内置)
韩文
音节切分 + 形态分析
英/西/葡
Snowball Stemmer
阿拉伯
ArabicStemmer

混合语言处理:

  • • 自动检测文本语言(franc 库)
  • • 同一查询可包含多语言关键词
  • • 每种语言独立分词后合并
  • • BM25 索引按语言分桶(避免跨语言干扰)

5. FSRS-6 遗忘算法

FSRS-6(Free Spaced Repetition Scheduler v6)借鉴自 Anki 记忆卡片系统。

5.1 核心思路: "软遗忘"

  传统遗忘 (硬删除):                 FSRS-6 (软遗忘):  ┌────────────────────┐            ┌────────────────────┐  │ 30天没访问 → 删除   │            │ 30天没访问 → 降级   │  │                     │            │                     │  │ 记忆消失了           │            │ 记忆还在,排名很低  │  │ 永远找不回来         │            │ 如果再次被触发      │  │                     │            │ → 优先级立刻回升    │  │ 风险: 删掉关键信息   │            │ → "想起来"了        │  └────────────────────┘            │                     │                                    │ 安全: 不丢信息       │                                    └────────────────────┘

5.2 算法机制

  每条记忆条目携带 FSRS 字段:  ┌─────────────────────────────────────────────┐  │ memory_record.fsrs = {                       │  │   stability:    12.5,   // 记忆稳定性(天)    │  │   difficulty:   0.3,    // 记忆难度 [0,1]    │  │   lastReview:   "2026-02-20T10:00:00Z",     │  │   nextReview:   "2026-03-04T22:00:00Z",     │  │   reps:         5,      // 被访问次数        │  │   lapses:       1       // 遗忘次数          │  │ }                                            │  └─────────────────────────────────────────────┘  优先级计算:  FSRS 定义 stability S = 记忆保持率降到 90% 所需的天数  retrievability R(t) = 0.9^(t / S)  • 刚被访问 (t≈0) → R ≈ 1.0 → 排名靠前  • t = S 天后 → R = 0.9 → 仍在阈值上  • t = 3S 天后 → R = 0.73 → 开始衰减  • 长时间没访问 → R → 0 → 排名靠后(但不删除)  • 被重新访问 → stability 增加 → 衰减变慢  更新规则 (检索命中时):  ┌────────────────────────────────────────────────┐  │ [1] reps += 1                                  │  │ [2] stability = stability × (1 + factor)       │  │     factor 取决于 difficulty、elapsed、R(t)     │  │ [3] difficulty 微调 (根据检索排名)              │  │ [4] lastReview = now                           │  │ [5] nextReview = now + new_stability           │  │     即: 以更新后的 stability 为间隔安排下次复习 │  │     例: stability=12.5d → 12.5 天后 R 降至 90% │  └────────────────────────────────────────────────┘

5.3 与 Generative Agents 的三维评分对比

OpenClaw
RRF(BM25, Vec) × retrievability
自适应 · stability 自动学习
Generative Agents
α·Recency + β·Importance + γ·Relevance
权重手动调 · 无学习机制

维度映射对比:

维度
Generative Agents
OpenClaw
Recency
α 权重手动调
FSRS 时间衰减(自适应)
Importance
β 权重手动调
stability(从访问模式学习)
Relevance
γ 权重手动调
BM25 + Vector 检索分

5.4 记忆注入 Context 的格式

检索命中的记忆条目在 Context 组装阶段(见 02-agent-runtime.md §6.2 步骤 3)被格式化为 Markdown 块,拼接在 System Prompt 尾部:

  注入模板:  ## 关于此用户的记忆  以下是你过去与此用户交互中积累的记忆,按相关性排序:  - [偏好, 记于 2026-02-15] 用户偏好 TypeScript,不喜欢 Java  - [档案, 记于 2026-01-20] 前端工程师,32 岁,在杭州  - [洞察, 记于 2026-02-28] 用户对视觉体验要求高,倾向简洁暗色设计  - [事件, 记于 2026-02-14] 用户提交了 PR #342,修复了登录 bug  格式规则:  • 每条 = [category, 记于 createdAt] + L0 摘要  • 高相关性条目自动展开为 L1(~500 tokens)  • 洞察条目(insight)排在同分事实条目之前  • 带时间戳 → 让模型自行判断时效性(见去重陷阱 4)  • 总 Token 预算由 memoryMaxTokens 控制(默认 3000)

6. 记忆写入流程

6.1 自动提取

Agent Loop 结束后异步触发。为避免高频对话产生冗余提取,实际有两层限流:(1) 连续 Agent Loop 间隔 < 30s 时合并为一次提取;(2) 同一 Session 每小时最多触发 10 次提取。

取消/超时

闲聊 < 3 条

正常完成

< 0.7

0.7~0.92

重复

矛盾/补充

> 0.92

no-merge

其他策略

本轮对话
[1] 过滤
跳过
跳过
[2] LLM 提取
[3] 分类
[4] 确定性去重hash 匹配?
跳过
语义去重cosine 相似度?
新条目
[4b] LLM 判断
跳过
[4c] 合并策略?
跳过
合并更新
[5] 生成 Embedding
[6] 持久化写入

各步骤说明:

  • • [2] LLM 提取:用轻量模型提取 facts / preferences / events / lessons,~500-1000 tokens
  • • [3] 分类:facts → profile / event,preferences → preference,lessons → lesson
  • • [4] 确定性去重hash(normalized(L0)) 完全匹配则跳过
  • • 语义去重三区间< 0.7 新条目 / 0.7-0.92 灰区走 LLM 判断 / > 0.92 近似重复
  • • [4c] 合并策略no-merge(event/case)跳过;overwrite / latest-wins / append 合并
  • • [5] Embedding:L0 → Embedding 模型 → Float32Array,批量处理
  • • [6] 持久化:写 records/ 文件 + 更新 BM25 / HNSW 索引 + metadata

错误处理与降级

提取流程中任一步骤失败时的降级策略:

失败点
现象
降级策略
LLM 提取超时/5xx
模型无响应或返回错误
重试 1 次(指数退避 2s → 4s);仍失败则跳过本轮,下次 Agent Loop 合并重试
LLM 返回空/格式异常
JSON 解析失败或字段缺失
记录 warn 日志 + 跳过;不写入脏数据
Embedding 生成失败
API 限流或本地 ONNX 崩溃
API 失败 → 自动降级到本地 ONNX 模型;ONNX 也失败 → 仅写入记录文件,Embedding 标记 pending,下次 openclaw memory index 补齐
持久化写入失败
磁盘满或权限错误
WAL 已记录意图 → 下次启动回放补齐;磁盘满则 emit memory:error 事件通知上层
去重 LLM 判断失败
灰区 LLM 调用超时
降级为保守策略:cosine > 0.85 视为重复跳过,≤ 0.85 视为新条目写入

6.2 去重陷阱

陷阱 1:重复判为矛盾 (Mem0 #1674)

  • • 存:"我喜欢咖啡" → 再说:"我喜欢咖啡"
  • • 预期 NOOP,实际 LLM 判为"矛盾" → DELETE → 偏好丢失
  • • 防御:先用确定性去重(hash + cosine),LLM 只在 cosine 0.7-0.92 灰区介入

陷阱 2:First Write Wins (cognee #1831)

  • • 存:"张三是工程师" → 更新:"张三升了经理"
  • • 预期合并更新,实际新属性被静默丢弃
  • • 防御:profile 类用 overwrite 策略,新值总是覆盖旧值

陷阱 3:语义扭曲

  • • 用户说:"我讨厌西兰花" → LLM 重述:"用户喜欢蔬菜"
  • • 记忆不是丢了,是被扭曲
  • • 防御:L2 始终保留原文;L0 摘要仅用于索引,不用于回忆;模型回忆时优先引用 L1/L2

陷阱 4:时间混淆

  • • 用户说:"我上周开始学 Rust" → 提取:"用户在学 Rust"
  • • 3 个月后检索:"用户在学 Rust" → 仍在学?
  • • 防御:记忆条目始终带 createdAt 时间戳,Context 注入时显示 "记于 2026-02-01"

7. 反思机制

定期对累积的事实记忆进行反思,提炼高层洞察:

反思触发条件:

  1. 1. 累积到 MEMORY_REFLECTION_THRESHOLD(50)条未反思的新记忆
  2. 2. 定时触发(每 24 小时)
  3. 3. 手动触发:通过管理端触发反思任务

两层结构
洞察层 (高优先级)
事实层 (正常优先级)
触发条件
≥ 50 条未反思
每 24h 定时
手动触发
[1] 按类型分组
[2] LLM 反思
[3] 存入洞察层
[4] 标记已反思

各步骤说明:

  • • [1] 分组:按 profile / event / preference 等类型分组
  • • [2] 反思:每组独立调用 LLM,提炼高层洞察(最多 5 条),含置信度 + 支撑证据
  • • [3] 存储category: insightmergeStrategy: latest-wins
  • • [4] 标记:原始记忆 FSRS priority 降低,洞察获得更高初始 stability

两层结构示例:

  • • 洞察层张三是项目核心贡献者,提交频率约每周 2 次 / 用户对视觉体验要求高,倾向简洁暗色设计
  • • 事实层2/14 张三提交了 PR #342 / 用户说他更喜欢暗色主题

洞察如何反哺检索:

  ┌──────────────────────────────────────────────────────────┐  │  [1] 初始 stability 加成                                  │  │      洞察条目 stability = 原始条目平均 stability × 3      │  │      → 衰减更慢,在检索结果中长期保持高排名               │  │                                                           │  │  [2] 原始条目 FSRS 降权                                   │  │      已被反思的事实条目 stability × 0.5                    │  │      → 检索时优先返回洞察而非零散事实                      │  │      → 但原始条目不删除,L2 原文可追溯                    │  │                                                           │  │  [3] Context 注入排序                                     │  │      同分时 insight 类型排在 event/preference 之前        │  │      → 模型优先看到高层结论,再看细节                     │  │                                                           │  │  [4] 洞察的自我迭代                                       │  │      下一轮反思可能产生更新的洞察 (latest-wins 合并)       │  │      旧洞察被新洞察覆盖,避免洞察层膨胀                   │  └──────────────────────────────────────────────────────────┘

8. 跨 Agent 记忆

默认每个 Agent 有独立的 Memory(agents/main/memory/agents/coding/memory/)。问题:用户在 "main" 说过的偏好,"coding" Agent 不知道。

coding Agent Memory
lesson / pattern / case
main Agent Memory
lesson / pattern / case
共享 User Memory
profile
preference
event
检索时合并共享记忆优先

存储路径:

  • • 共享层agents/_shared/memory/user/,所有 Agent 共同读写
  • • 独立层agents/<agentId>/memory/agent/,每个 Agent 私有
  • • 检索合并sharedMemory.retrieve + agentMemory.retrieve,去重时共享记忆优先

配置:{ "memory": { "shareUserMemory": true } }


9. 记忆索引与迁移

  ┌──────────────────────────────────────────────────────┐  │             记忆导入/导出                               │  │                                                       │  │  迁移前建议:                                           │  │  1) 备份 ~/.openclaw/agents/<agent>/memory/           │  │  2) 迁移后执行 openclaw memory index                  │  │                                                       │  │  典型记录格式:                                         │  │  {                                                    │  │    "version": "1.0",                                 │  │    "agent": "main",                                  │  │    "exportedAt": "2026-02-26T10:00:00Z",             │  │    "records": [                                       │  │      {                                                │  │        "id": "mem-001",                              │  │        "category": "preference",                     │  │        "content": { "l0": "...", "l1": "...", "l2": "..." },│  │        "fsrs": { ... },                              │  │        "createdAt": "..."                            │  │      },                                               │  │      ...                                              │  │    ]                                                  │  │  }                                                    │  │                                                       │  │  迁移后处理:                                           │  │  → 去重: 按 id 跳过已存在的记忆                      │  │  → 重建 Embedding (模型可能不同 → 维度不兼容)       │  │  → 重建 BM25 + HNSW 索引                            │  │  → FSRS 字段原样保留 (不重置)                       │  │                                                       │  │  用途:                                                │  │  • 迁移到新机器                                       │  │  • 在多台设备间同步记忆                               │  │  • 备份/恢复                                          │  │  • 从旧版本升级 (version 字段做格式兼容)             │  │                                                       │  │  注意事项:                                             │  │  • Embedding 模型变更 → 必须全量重建向量索引          │  │    (bge-small 384 维 ↔ openai 1536 维 不兼容)       │  │  • 导出文件不含 embedding 字段 (体积原因)            │  │    → 导入时自动重新生成                               │  │  • 共享 User Memory 和 Agent Memory 分开导出/导入   │  │  • Session 历史 (messages.jsonl) 不在此导出范围内    │  │    → 用 cp -r sessions/ 直接复制                     │  └──────────────────────────────────────────────────────┘

格式版本升级路径

导出文件的 version 字段用于处理跨版本兼容。升级规则:

版本变更
触发条件
迁移行为
1.0 → 1.x(小版本)
新增可选字段(如 tagssource
向后兼容,导入时缺失字段填默认值
1.x → 2.0(大版本)
Schema 结构变更(如 content 从 string 改为 {l0, l1, l2} 对象)
需迁移脚本:openclaw memory migrate --from 1.x --to 2.0
Embedding 模型变更
配置中 embedding.model 与导出时不同
丢弃旧 Embedding,全量重建:openclaw memory index --reembed

自动升级流程:

  1. 1. 导入时读取 version 字段,与当前运行版本比较
  2. 2. 小版本差异 → 自动补齐缺失字段,静默完成
  3. 3. 大版本差异 → 拒绝导入,提示用户先运行 openclaw memory migrate
  4. 4. 迁移脚本保留原文件备份(*.bak),可回滚

10. 存储位置

  ~/.openclaw/agents/<agentId>/  ├── sessions/  │   ├── s-abc123/  │   │   ├── meta.json          Session 元数据  │   │   ├── messages.jsonl     消息历史 (JSONL)  │   │   └── context.json       修剪后上下文快照  │   └── s-def456/  │       └── ...  └── memory/      ├── index/                 检索索引 (文件后端 / sqlite-vec)      │   ├── bm25.idx           BM25 倒排索引      │   ├── vector.idx         HNSW 向量索引      │   ├── metadata.json      记忆元数据      │   └── config.json        索引配置      ├── records/               记忆条目原文      │   ├── user/              User Memory (可配置共享)      │   │   ├── mem-001.json      │   │   └── ...      │   └── agent/             Agent Memory (独立)      │       ├── mem-100.json      │       └── ...      └── reflections/           反思洞察          └── insights.json  共享 User Memory (如果启用):  ~/.openclaw/agents/_shared/memory/user/  ├── index/  └── records/

11. 性能特征

测试环境参考:M2 MacBook Pro / Node.js 22 / SSD / ~3K 条记忆 / text-embedding-3-small。实际数值因硬件和记忆规模而异。

指标
实测值 (参考)
Session 查找 (内存 Map)
< 0.1ms
Session 加载 (磁盘, 50条消息)
50-150ms
Session 持久化 (追加写)
5-20ms
Memory 检索 (混合, Top-10, 含 Reranking)
200-500ms
Memory 检索 (混合, Top-10, 无 Reranking)
50-150ms
Embedding 生成 (API, 单条)
50-200ms
Embedding 生成 (本地 ONNX, 单条)
5-20ms
HNSW 插入 (单条)
< 5ms
BM25 索引更新
< 10ms
记忆提取 (LLM, 异步)
2-5s
反思 (LLM, 50条记忆)
5-15s
单 Agent 记忆容量100,000+ 条
向量索引内存占用 (10K条, 1536维)~60MB
向量索引内存占用 (10K条, 384维)~4MB
BM25 索引大小 (10K条)~2MB

12. 隐私与数据安全

记忆系统存储高度敏感的用户信息(偏好、事件、档案),安全设计直接影响用户信任。

  ┌──────────────────────────────────────────────────────────┐  │  [1] 存储安全                                             │  │  • 所有数据存在用户本地 (~/.openclaw/),不上传云端       │  │  • 文件权限: 目录 700, 文件 600 (仅 owner 可读写)       │  │  • 加密: 当前版本明文存储; 规划中支持 AES-256-GCM       │  │    静态加密,密钥由用户 passphrase 派生                   │  │                                                           │  │  [2] 数据删除                                             │  │  • openclaw memory delete <id>                            │  │    → 删除 records/ 文件 + 从 BM25/HNSW 索引中移除       │  │  • openclaw memory purge --user <userId>                  │  │    → 批量删除某用户的所有记忆("被遗忘权")              │  │  • 删除后: 索引标记 tombstone,下次 index 时物理清除     │  │                                                           │  │  [3] Embedding 隐私                                       │  │  • 使用 OpenAI API 生成 Embedding 时,L0 摘要会发送到   │  │    外部服务器(已在 L0 层做脱敏/摘要化)                 │  │  • 本地 ONNX 模型: 零数据外泄                            │  │  • 配置建议: 高敏感场景使用本地 Embedding 模型            │  │                                                           │  │  [4] LLM 提取隐私                                        │  │  • 记忆提取和反思需调用 LLM,对话片段会发送到模型        │  │  • 缓解: 使用本地模型 (Ollama) 做提取; 或配置            │  │    memory.extractModel 使用低成本小模型减少暴露面        │  └──────────────────────────────────────────────────────────┘