😱【OpenClaw 源码解析】你的 AI 助手每次都「失忆」?学会这一招,让它记住你所有重要决策,效率直接翻倍!

😱【OpenClaw 源码解析】你的 AI 助手每次都「失忆」?学会这一招,让它记住你所有重要决策,效率直接翻倍!

副标题:深扒 OpenClaw 源码,揭秘顶级 AI Agent 背后不为人知的「记忆黑科技」------普通人永远不知道的财富密码


🔥 引言:那个让我损失惨重的下午

那是一个普通的周四下午。

我正在用 AI 助手处理一个月开了无数次会才定下来的项目架构。规则定好了、命名约定谈妥了、踩过的坑我都手动告诉了它......

然后------

我关掉了对话窗口。

第二天打开新会话,一切归零。🫥

它不记得我们花三个小时定下的接口命名规范。

不记得我们痛苦纠结后选的那个数据库方案。

不记得我说「这个模块以后不准用全局变量」。

就像花了一个月调教出来的实习生,第二天早上进门,满脸问号地看着你:「您好,请问我是来做什么的?」

这不是 AI 笨,这是它根本没有「记忆」。

而我今天要告诉你的,就是那些高阶玩家偷偷在用的 AI 记忆系统------它是如何让 AI 真正「记住你」的。看完这篇文章,你会彻底明白为什么你的 AI 效率只有别人的三分之一。


📖 本文章节目录

💡 阅读指南 :章节之间是层层递进的关系------先搞明白「记什么」,再搞清楚「怎么存」,然后是「怎么找」,最后是「什么时候自动保存」。每一章都在解决上一章留下的新问题。

复制代码
┌─────────────────────────────────────────────────────────┐
│                                                         │
│  第一章:大脑在哪里?                                    │
│  💾 记忆的「物理位置」------文件、数据库、索引长什么样       │
│         │                                               │
│         │ ➡️ 知道存哪了,那怎么「切成合适的块」?         │
│         ▼                                               │
│  第二章:大脑怎么工作?                                  │
│  🔪 把文章切碎再「向量化」------让 AI 理解语义而不只是关键字  │
│         │                                               │
│         │ ➡️ 切完了,那怎么「快速找到」想要的那块?       │
│         ▼                                               │
│  第三章:怎么想起来的?                                  │
│  🔍 混合搜索黑科技------向量 + 关键词,双管齐下              │
│         │                                               │
│         │ ➡️ 能找了,但什么时候「自动触发保存」?         │
│         ▼                                               │
│  第四章:什么时候自动记住的?                            │
│  🧠 临界点自动 Flush------快撑不住时的「遗嘱写入」机制       │
│                                                         │
│  终章:我能用上吗?                                      │
│  🚀 普通人怎么配置这套系统,开始让 AI 真正认识你         │
│                                                         │
└─────────────────────────────────────────────────────────┘

第一章:大脑在哪里?💾

AI 的「记忆宫殿」长这样

「记忆」这个词听起来很玄,但其实就是几个文件夹和数据库。

想象你有个超级助理,他的工位上放着:

东西 对应什么 放在哪
📓 日记本 MEMORY.md ~/.openclaw/workspace/<你的ID>/
📁 专题笔记夹 memory/*.md 同上,按日期/主题分文件
🎙️ 会议录音稿 Session JSONL ~/.openclaw/sessions/
🗂️ 智能索引卡片盒 SQLite 数据库 ~/.openclaw/memory/<你的ID>/index.db

重点来了 👇

前三个是「原始材料」------人类可读的文本。最后那个 SQLite 数据库才是真正的魔法所在。整体架构长这样:
graph TD A["📝 你写的 Markdown 笔记"] --> B["MEMORY.md"] A --> C["memory/*.md"] A --> D["Session JSONL"] B --> E[("🗄️ SQLite index.db")] C --> E D --> E E --> F["📦 chunks 表 文本块 + 向量"] E --> G["🔍 chunks_fts 表 FTS5 全文索引"] E --> H["🧮 chunks_vec 表 向量 KNN 索引"] style E fill:#2d3748,color:#fff style F fill:#2b6cb0,color:#fff style G fill:#276749,color:#fff style H fill:#744210,color:#fff

🔬 chunks 表长什么样?(src/memory/memory-schema.ts

sql 复制代码
CREATE TABLE chunks (
  id          TEXT PRIMARY KEY,
  path        TEXT NOT NULL,           -- 来自哪个文件
  source      TEXT NOT NULL DEFAULT 'memory',  -- 'memory' | 'sessions'
  start_line  INTEGER NOT NULL,        -- 块开始行号
  end_line    INTEGER NOT NULL,        -- 块结束行号
  hash        TEXT NOT NULL,           -- 内容哈希,用来检测变更
  model       TEXT NOT NULL,           -- 用哪个 embedding 模型算的
  text        TEXT NOT NULL,           -- 原始文本
  embedding   TEXT NOT NULL,           -- ⚠️ JSON 序列化的向量数组!
  updated_at  INTEGER NOT NULL
);

注意 embedding 字段------它不是什么神秘二进制,就是一个 JSON 数组,比如 [0.023, -0.187, 0.341, ...],有多少维就有多少个数字。

还有个 files 表专门做变更追踪

sql 复制代码
CREATE TABLE files (
  path    TEXT PRIMARY KEY,
  source  TEXT NOT NULL DEFAULT 'memory',
  hash    TEXT NOT NULL,    -- SHA-256 文件哈希
  mtime   INTEGER NOT NULL, -- 修改时间
  size    INTEGER NOT NULL
);

每次同步时,系统先查这张表------如果文件哈希没变,直接跳过,一个 API 调用都不浪费

TypeScript 里的核心数据结构(src/memory/types.ts

typescript 复制代码
// 一个文本块
type MemoryChunk = {
  startLine: number;
  endLine: number;
  text: string;
  hash: string;   // 块内容的 SHA-256
};

// 搜索返回的一条结果
type MemorySearchResult = {
  path: string;
  startLine: number;
  endLine: number;
  score: number;        // 0-1 的相关性分数
  snippet: string;      // 最多 700 字符的摘要
  source: "memory" | "sessions";
  citation?: string;    // 格式:path#L15 或 path#L15-L30
};

😏 一句话总结:你写的 Markdown 笔记是「原材料」,AI 把它们切碎、编码、建索引之后,才能真正「懂得」里面说的是什么。


第二章:大脑怎么工作?🔪

第一步:把你的笔记「切碎」(src/memory/internal.ts:166-247

不是整篇文章扔进去,而是切成小块------来看实际的函数签名:

typescript 复制代码
function chunkMarkdown(
  content: string,
  chunking: { tokens: number; overlap: number }
) {
  const maxChars = tokens * 4;       // 400 tokens → ~1600 字符
  const overlapChars = overlap * 4;  // 80 tokens  → ~320 字符重叠

  // 按行累积,超过 maxChars 时切分
  // 保留 overlapChars 作为下一个块的开头
}

为什么要重叠? 因为防止一句关键话刚好被切在两块的边界上,结果两块都只有「半句话」,搜索时全部失效。🤦

就像切披萨,你不会让奶酪刚好从中间断掉------重叠部分就是「多留一点边」的意思。🍕

第二步:把每个小块「向量化」(这里是真正的黑魔法)

先看 OpenClaw 定义的 Provider 接口(src/memory/embeddings.ts:21-26):

typescript 复制代码
type EmbeddingProvider = {
  id: string;    // 'openai' | 'gemini' | 'local'
  model: string; // 具体模型名
  embedQuery: (text: string) => Promise<number[]>;        // 单条查询
  embedBatch: (texts: string[]) => Promise<number[][]>;   // 批量处理
};

系统支持三种实现:

复制代码
🤖 OpenAI  → text-embedding-3-small(云端,要钱)
♊ Gemini   → text-embedding-004(云端,免费额度大)
💻 Local   → embeddinggemma-300M(本地,节点推理,零成本)

什么是「向量化」?

简单说:把一段文字变成一串数字(比如 1536 维)。语义相近的文字,对应的数字串也会很「接近」。

所以:

  • 「明天下雨」和「今日有雷阵雨」→ 数字串很接近 ✅
  • 「明天下雨」和「比特币暴涨」→ 数字串差很远 ✅

🧠 记忆原理:你的笔记被变成数字 → 数字存进数据库 → 下次搜索时,你的问题也被变成数字 → 找最接近的数字 → 返回对应的笔记片段

🔬 Provider 自动选择:优雅的降级链(src/memory/embeddings.ts:125-205

auto 模式下,系统按这个顺序自动探测------你不需要手动切换,它自己找能用的:
flowchart TD A["⚙️ provider: auto"] --> B{"本地模型文件存在?"} B -->|"✅ 存在"| C["💻 使用 Local embeddinggemma-300M 零成本"] B -->|"❌ 不存在 / 失败"| D{"OpenAI API 可用?"} D -->|"✅ 可用"| E["🤖 使用 OpenAI text-embedding-3-small"] D -->|"❌ 失败"| F{"Gemini API 可用?"} F -->|"✅ 可用"| G["♊ 使用 Gemini text-embedding-004"] F -->|"❌ 全部失败"| H["⚠️ 按 fallback 配置回退 或返回空结果 + 错误信息"] style C fill:#276749,color:#fff style E fill:#2b6cb0,color:#fff style G fill:#6b46c1,color:#fff style H fill:#c53030,color:#fff

🔬 批量 Embedding:不是一条条算,是打包发(src/memory/manager.ts:1652-1678

typescript 复制代码
// 构建批次,每批最多 8000 tokens
const BATCH_MAX_TOKENS = 8000;

function buildEmbeddingBatches(chunks: MemoryChunk[]): MemoryChunk[][] {
  // 按 token 估算累积,超过 8000 就新开一批
}

// 带缓存的批量 embedding
async function embedChunksInBatches(chunks: MemoryChunk[]) {
  // 1. 先查 embedding_cache 表,找已缓存的
  // 2. 只对「未命中」的调用 provider.embedBatch()
  // 3. 把新结果写回缓存
}

缓存的 key 结构是 (provider, model, provider_key, hash)------同一段文字不管哪次会话,只算一次

OpenAI / Gemini 还支持异步 Batch APIsrc/memory/batch-openai.ts):大规模索引时可以批量提交任务,等结果回来再处理,比同步调用便宜得多。但有个自我保护机制------失败超过 2 次,自动禁用 Batch API,降级回普通调用

🔬 各操作的超时时间表

操作 本地模型 远程 API
单条查询 Embedding 5 分钟 1 分钟
批量 Embedding 10 分钟 2 分钟
向量表加载 30 秒 30 秒

超时后自动重试,策略是指数退避:500ms → 1s → 2s → 4s → 8s,最多 3 次。遇到 Rate Limit 错误也会自动等待重试。


第三章:怎么想起来的?🔍

混合搜索 = 向量搜索 × 0.7 + 关键词搜索 × 0.3

光有向量搜索不够------

复制代码
场景:你问「上次我们决定用 PostgreSQL 还是 MySQL?」

向量搜索:能理解你在问数据库选型问题 ✅
但如果笔记里刚好写的是「选了 PG」,关键词 MySQL 根本搜不到 ❌

关键词搜索:精准匹配 "PostgreSQL" "MySQL" ✅
但如果你问「上次那个数据库决定」,啥也匹配不上 ❌

所以两个都用,然后加权合并。 来看 src/memory/hybrid.ts 里的真实函数签名:

typescript 复制代码
function mergeHybridResults(params: {
  vector: HybridVectorResult[];
  keyword: HybridKeywordResult[];
  vectorWeight: number;  // 默认 0.7
  textWeight: number;    // 默认 0.3
}) {
  // score = vectorWeight * vectorScore + textWeight * textScore
}

一行公式说清楚一切:

复制代码
最终得分 = 向量分 × 0.7 + 关键词分 × 0.3

完整的搜索数据流,一图胜千言:
flowchart TD A["🙋 用户查询 '上次数据库选型结论?'"] --> B["Embed 查询向量 同 provider / 同 model"] B --> C["候选集扩大 maxResults × candidateMultiplier = 6 × 3 = 18"] C --> D["🧮 向量搜索 sqlite-vec KNN 余弦相似度"] C --> E["🔍 关键词搜索 FTS5 BM25\n自动构建 AND 查询"] D --> F["mergeHybridResults 向量分 × 0.7 + 关键词分 × 0.3"] E --> F F --> G{"score ≥ 0.35?"} G -->|"✅ 通过"| H["📄 返回最多 6 条 含路径 + 行号 + snippet"] G -->|"❌ 低于阈值"| I["🗑️ 丢弃"] style D fill:#744210,color:#fff style E fill:#276749,color:#fff style F fill:#2b6cb0,color:#fff style H fill:#2d3748,color:#fff

🔬 向量搜索:底层用的是 sqlite-vec 扩展

不是什么神秘黑箱,就是一句 SQL(src/memory/manager-search.ts):

sql 复制代码
SELECT id, embedding
FROM chunks_vec
WHERE embedding MATCH ?   -- k-nearest neighbor 查询
ORDER BY distance
LIMIT ?

chunks_vec 是一张用 vec0 虚拟表引擎创建的特殊表,支持直接对向量做「最近邻搜索」,底层算余弦相似度。简单、快、不依赖外部向量数据库。

😮 很多人以为向量搜索一定要上 Pinecone、Weaviate 那种独立服务,其实 SQLite 装个扩展就够了。

🔬 关键词搜索:FTS5 + 自动构建查询

src/memory/hybrid.ts:23-34 里有个小而精的函数:

typescript 复制代码
function buildFtsQuery(raw: string): string | null {
  // "hello world" → '"hello" AND "world"'
  const tokens = raw.match(/[A-Za-z0-9_]+/g);
  return tokens.map(t => `"${t}"`).join(" AND ");
}

你输入 PostgreSQL MySQL 选型,它自动变成:

复制代码
"PostgreSQL" AND "MySQL" AND "选型"

然后用 BM25 评分(就是 Google 用了很多年的那套经典算法)来排序,再归一化到 0-1 区间:

typescript 复制代码
function bm25RankToScore(rank: number): number {
  return 1 / (1 + rank);  // 越靠前,rank 越小,分越高
}

📐 一个被低估的参数:candidateMultiplier

源码里有个默认值 candidateMultiplier: 3,意思是:

「如果你最终要返回 6 条结果,我先找出 6 × 3 = 18 条候选,再从里面合并排序、择优返回。」

为什么要这么做?因为向量搜索排第一的和关键词搜索排第一的,可能完全不是同一条记录。先各自扩大候选池,合并时才不会漏掉真正相关的结果。

所有关键参数一览

参数 默认值 作用
vectorWeight 0.7 向量搜索权重
textWeight 0.3 关键词搜索权重
maxResults 6 最终返回条数
minScore 0.35 低于此分直接丢弃
candidateMultiplier 3 候选集扩大倍数

第四章:什么时候自动记住的?🧠

「遗嘱写入」------快撑不住之前先把重要的存下来

每个 AI 对话都有 context window 上限(就是「脑容量」)。

接近满了会发生什么?系统会触发 Memory Flush。

触发判断函数(src/auto-reply/reply/memory-flush.ts:77-105):

typescript 复制代码
function shouldRunMemoryFlush(params: {
  entry?: {
    totalTokens: number;
    compactionCount: number;
    memoryFlushCompactionCount: number;  // 记录「这轮已经 flush 过了」
  };
  contextWindowTokens: number;
  reserveTokensFloor: number;
  softThresholdTokens: number;  // 默认 4000
}) {
  // 触发条件:
  // totalTokens > (contextWindowTokens - reserveTokensFloor - softThresholdTokens)
  // 且当前 compactionCount 未执行过 flush(防止重复触发)
}

用大白话说:

复制代码
当前对话 token 数 > (context 上限 - 预留底线 - 4000 缓冲)
且这轮对话还没 flush 过
→ 触发!

触发之后,AI 会同时收到两条特殊指令

系统提示(system prompt)

"Pre-compaction memory flush turn. The session is near auto-compaction; capture durable memories to disk. You may reply, but usually [NO_REPLY] is correct."

用户提示(user message)

"Pre-compaction memory flush. Store durable memories now (use memory/YYYY-MM-DD.md; create memory/ if needed). If nothing to store, reply with [NO_REPLY]."

翻译成人话:「脑子快装满了,赶紧把重要的东西写进笔记本!没东西写就回复 [NO_REPLY] 就行。」

整个 Flush 时序是这样的:
sequenceDiagram participant M as 系统监控 participant A as AI Agent participant F as 文件系统 participant I as 索引引擎 M->>M: 检测 totalTokens > 阈值 Note over M: contextWindow - reserve - 4000 M->>A: system prompt: "near compaction, capture memories" M->>A: user message: "Pre-compaction memory flush..." A->>A: 判断本次对话有哪些值得保存 alt 有重要内容 A->>F: 写入 memory/YYYY-MM-DD.md A-->>M: 返回写入确认 Note over M: 更新 memoryFlushCompactionCount else 无重要内容 A-->>M: 回复 [NO_REPLY] end F-->>I: 下次 sync 触发(onSearch / onSessionStart) I->>I: 切块 → Embed → 建索引 Note over I: 新记忆正式进入检索系统 ✅

AI 就会主动把这次对话里的关键决策、重要信息写进 memory/2024-01-15.md 这样的文件,然后下次同步时自动被切块、向量化、建索引。

🔬 彩蛋:原子化重建索引(src/memory/manager.ts:1398-1500

这个设计超骚------索引损坏怎么办?答案是「在旁边悄悄建好新的,再瞬间换掉」:
flowchart TD A["🔨 触发索引重建"] --> B["创建临时库 index.db.tmp-&lt;uuid&gt;"] B --> C["在临时库构建全新索引"] C --> D["从旧库拷贝 embedding_cache 保住算过的向量,不重新算 💰"] D --> E{"构建成功?"} E -->|"✅ 成功"| F["rename 原子替换 临时库 → index.db\nOS 保证原子性"] E -->|"❌ 失败"| G["删除临时文件 旧库继续正常服务"] F --> H["🎉 零停机完成重建"] G --> I["⚠️ 回滚,原索引毫发无损"] style F fill:#276749,color:#fff style H fill:#276749,color:#fff style G fill:#c53030,color:#fff style I fill:#c53030,color:#fff

全程旧索引都在正常服务,零停机重建

🔬 Session 增量索引:不是每次都全量重建

会话记录也会被索引,但触发条件很保守,避免频繁 I/O:

复制代码
新增字节数 > 4096 字节(约 4KB)
OR
新增消息数 > 10 条
→ 才触发增量同步

这意味着短对话根本不会触发,长对话也只在「积累足够多」之后才写索引,大幅减少数据库写入频率。

🎯 妙处:不需要你手动整理!AI 自己知道在「快撑不住」之前把重要内容持久化。这才是真正的「记忆」------不是你帮它记,是它自己知道该记什么。


终章:我能用上吗?🚀

普通用户的配置入门

最简单的起手式:

yaml 复制代码
memory:
  backend: "builtin"    # 内置方案,不需要额外服务
  citations: "auto"     # 自动显示引用来源

agents:
  defaults:
    memorySearch:
      provider: "auto"  # 自动选可用的 Embedding 服务
      sync:
        onSessionStart: true  # 每次开始自动同步
        onSearch: true        # 搜索时自动同步
        watch: true           # 文件有变化立刻同步(debounce 1500ms)
      compaction:
        memoryFlush:
          enabled: true
          softThresholdTokens: 4000  # 提前多少 token 开始 flush

只有三种要记住的 Agent 工具:

复制代码
📡 memory_search("你的查询")   ← 语义搜索,返回最多 6 条带行号的片段
📄 memory_get("memory/xxx.md") ← 直接读取指定文件的指定行
✍️  直接写文件                  ← 存进 memory/*.md 即可被自动索引

这套系统真正厉害在哪?

不是技术有多复杂,而是它解决了 AI 最根本的问题

复制代码
❌ 没有这套系统:AI 是个高智商失忆症患者
✅ 有了这套系统:AI 是个有完整工作记录的长期合伙人

每次对话不再从零开始。

你踩过的坑,它记得。

你定下的规范,它记得。

你的偏好和决策风格,它记得。

💡 最后一句话

记忆,是让 AI 从「工具」变成「合伙人」的那道门槛。

而门是开着的------你只需要知道它在哪里。🚪


本文技术细节基于 OpenClaw 项目源码分析,核心文件:src/memory/manager.ts(2400行,硬核)

🔁 觉得有用?转发给你的技术群,让更多人少走弯路

相关推荐
胖头鱼的鱼缸(尹海文)4 小时前
数据库管理-第411期 OpenClaw进阶实战:升级+网关安全+飞书对接一次性搞定(20260315)
安全·飞书·openclaw
研究点啥好呢4 小时前
3月15日GitHub热门项目推荐 | 当AI拥有记忆
人工智能·python·github·openclaw
赋创小助手5 小时前
AMD OpenClaw:本地 AI Agent 运行平台解析,RyzenClaw 与 RadeonClaw 两种架构方案意味着什么?
服务器·人工智能·深度学习·自然语言处理·架构·数据挖掘·openclaw
AI精钢8 小时前
OpenClaw Dashboard 更新头像踩坑记:从 broken data URL 到本地文件 Avatar
自动化·dashboard·avatar·openclaw·control ui
chingho8 小时前
OpenClaw 不会安装的,一键安装包来了,代码开源!
openclaw
村中少年9 小时前
本地模型工具ollama配置使用openclaw指南
llm·nodejs·虚拟机·qwen·ollama·openclaw
清空mega9 小时前
《从 0 到 1:我在 WSL2 中部署 OpenClaw 的完整实战记录(含安全配置、MiniMax 接入、踩坑复盘)》
安全·openclaw
一个平凡而乐于分享的小比特9 小时前
深度拆解OpenClaw:引爆“赛博养虾”狂潮的技术内核、产业重构与暗面危机
openclaw·未来思考·产业重构·风险危机
Bruce_Liuxiaowei9 小时前
深入浅出:清理 OpenClaw 会话记录的完整操作解析
人工智能·大模型·智能体·openclaw