乐于分享
好东西不私藏

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

本文最后更新于2026-03-10,某些文章具有时效性,若有错误或已失效,请在下方留言或联系老夜

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

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


🔥 引言:那个让我损失惨重的下午
那是一个普通的周四下午。
我正在用 AI 助手处理一个月开了无数次会才定下来的项目架构。规则定好了、命名约定谈妥了、踩过的坑我都手动告诉了它……
然后——
我关掉了对话窗口。
第二天打开新会话,一切归零。🫥
它不记得我们花三个小时定下的接口命名规范。 不记得我们痛苦纠结后选的那个数据库方案。 不记得我说「这个模块以后不准用全局变量」。

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

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

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

第一章:大脑在哪里?💾

AI 的「记忆宫殿」长这样
「记忆」这个词听起来很玄,但其实就是几个文件夹和数据库。
想象你有个超级助理,他的工位上放着:
东西
对应什么
放在哪
📓 日记本
MEMORY.md ~/.openclaw/workspace/<你的ID>/
📁 专题笔记夹
memory/*.md
同上,按日期/主题分文件
🎙️ 会议录音稿
Session JSONL
~/.openclaw/sessions/
🗂️ 智能索引卡片盒
SQLite 数据库
~/.openclaw/memory/<你的ID>/index.db
重点来了👇
前三个是「原始材料」——人类可读的文本。最后那个 SQLite 数据库才是真正的魔法所在。整体架构长这样:
🔬 chunks 表长什么样?(src/memory/memory-schema.ts)
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 表专门做变更追踪
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)
// 一个文本块type MemoryChunk = {  startLinenumber;    endLinenumber;    textstring;    hashstring;   // 块内容的 SHA-256};// 搜索返回的一条结果type MemorySearchResult = {  pathstring;    startLinenumber;    endLinenumber;    scorenumber;        // 0-1 的相关性分数    snippetstring;      // 最多 700 字符的摘要    source"memory" | "sessions";    citation?: string;    // 格式:path#L15 或 path#L15-L30};

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

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

第一步:把你的笔记「切碎」(src/memory/internal.ts:166-247)
不是整篇文章扔进去,而是切成小块——来看实际的函数签名:
function chunkMarkdown(  contentstring,    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):
type EmbeddingProvider = {  idstring;    // 'openai' | 'gemini' | 'local'    modelstring// 具体模型名    embedQuery(textstring) => Promise<number[]>;   // 单条查询    embedBatch(textsstring[]) => Promise<number[][]>;   // 批量处理};
系统支持三种实现:
🤖 OpenAI  → text-embedding-3-small(云端,要钱)
♊ Gemini   → text-embedding-004(云端,免费额度大)
💻 Local   → embeddinggemma-300M(本地,节点推理,零成本)
什么是「向量化」?
简单说:把一段文字变成一串数字(比如 1536 维)。语义相近的文字,对应的数字串也会很「接近」。
所以:
「明天下雨」和「今日有雷阵雨」→ 数字串很接近 ✅
「明天下雨」和「比特币暴涨」→ 数字串差很远 ✅

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

🔬 Provider 自动选择:优雅的降级链(src/memory/embeddings.ts:125-205)
auto 模式下,系统按这个顺序自动探测——你不需要手动切换,它自己找能用的:
🔬 批量 Embedding:不是一条条算,是打包发(src/memory/manager.ts:1652-1678)
// 构建批次,每批最多 8000 tokensconst BATCH_MAX_TOKENS = 8000;function buildEmbeddingBatches(chunks: MemoryChunk[]): MemoryChunk[][] {  // 按 token 估算累积,超过 8000 就新开一批}// 带缓存的批量 embeddingasync function embedChunksInBatches(chunks: MemoryChunk[]) {  // 1. 先查 embedding_cache 表,找已缓存的    // 2. 只对「未命中」的调用 provider.embedBatch()    // 3. 把新结果写回缓存}
缓存的 key 结构是 (provider, model, provider_key, hash)——同一段文字不管哪次会话,只算一次
OpenAI / Gemini 还支持异步 Batch API(src/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 里的真实函数签名:
function mergeHybridResults(params: {  vector: HybridVectorResult[];    keyword: HybridKeywordResult[];    vectorWeight: number;  // 默认 0.7    textWeight: number;    // 默认 0.3}) {    // score = vectorWeight * vectorScore + textWeight * textScore}
一行公式说清楚一切:
最终得分 = 向量分 × 0.7 + 关键词分 × 0.3
完整的搜索数据流,一图胜千言:
🔬 向量搜索:底层用的是 sqlite-vec 扩展
不是什么神秘黑箱,就是一句 SQL(src/memory/manager-search.ts):
SELECT id, embeddingFROM chunks_vecWHERE embedding MATCH ?   -- k-nearest neighbor 查询ORDER BY distanceLIMIT ?
chunks_vec 是一张用 vec0 虚拟表引擎创建的特殊表,支持直接对向量做「最近邻搜索」,底层算余弦相似度。简单、快、不依赖外部向量数据库。

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

🔬 关键词搜索:FTS5 + 自动构建查询
src/memory/hybrid.ts:23-34 里有个小而精的函数:
function buildFtsQuery(rawstring): 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 区间:
function bm25RankToScore(ranknumber): 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):
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 时序是这样的:
AI 就会主动把这次对话里的关键决策、重要信息写进 memory/2024-01-15.md 这样的文件,然后下次同步时自动被切块、向量化、建索引。
🔬 彩蛋:原子化重建索引(src/memory/manager.ts:1398-1500)
这个设计超骚——索引损坏怎么办?答案是「在旁边悄悄建好新的,再瞬间换掉」:
全程旧索引都在正常服务,零停机重建
🔬 Session 增量索引:不是每次都全量重建
会话记录也会被索引,但触发条件很保守,避免频繁 I/O:
新增字节数 > 4096 字节(约 4KB)
OR
新增消息数 > 10 条
→ 才触发增量同步
这意味着短对话根本不会触发,长对话也只在「积累足够多」之后才写索引,大幅减少数据库写入频率。

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

终章:我能用上吗?🚀

普通用户的配置入门
最简单的起手式:
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行,硬核)
🔁 觉得有用?转发给你的技术群,让更多人少走弯路
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 【OpenClaw 源码解析第3期】你的 AI 助手每次都「失忆」?学会这一招,让它记住你所有重要决策,效率直接翻倍!

猜你喜欢

  • 暂无文章

评论 抢沙发

4 + 9 =