内存
OpenClaw 的内存是以 Markdown 格式存储于代理工作区。这些文件是事实的源头;模型仅“记住”写入磁盘的内容。 内存搜索工具由活跃的内存插件(默认:memory-core)提供。可通过 plugins.slots.memory = "none" 禁用内存插件。
内存文件(Markdown)
默认工作区布局使用两层内存:memory/YYYY-MM-DD.md- 每日日志(追加式)。
- 会话开始时读取当天和前一天的日志。
MEMORY.md(可选)- 精选的长期记忆。
- 仅在主私有会话中加载(群组上下文绝不加载)。
agents.defaults.workspace,默认 ~/.openclaw/workspace)。完整布局详见代理工作区。
内存工具
OpenClaw 提供两个面向代理的工具操作这些 Markdown 文件:memory_search— 对已索引片段进行语义检索。memory_get— 针对特定 Markdown 文件/行范围的精确读取。
memory_get 在文件不存在时现支持优雅降级(例如,首次写入前的当天日志)。内置管理器和 QMD 后端都会返回 { text: "", path },而不是抛出 ENOENT 异常,方便代理处理“尚无记录”状态,避免用 try/catch 包裹调用。
何时写入内存
- 决策、偏好和持久事实写入
MEMORY.md。 - 日常笔记和持续上下文写入
memory/YYYY-MM-DD.md。 - 若有人说“记住这个”,就写入磁盘(不要只存于内存中)。
- 这部分仍在发展中,帮助模型存储记忆会让它知道该怎么做。
- 想让信息持久化,务必让机器人写入内存。
自动内存刷新(预压缩提醒)
当会话接近自动压缩时,OpenClaw 会触发静默、代理式的回合,提醒模型在上下文被压实前写入持久内存。默认提示语明确说明模型“可以回复”,但通常NO_REPLY 是正确答案,这样用户看不到这回合。
该行为由 agents.defaults.compaction.memoryFlush 控制:
- 软阈值:当会话令牌估计数越过
contextWindow - reserveTokensFloor - softThresholdTokens时触发刷新。 - 默认静默:提示包含
NO_REPLY,因此不会输出响应。 - 包含两条提示:用户提示 + 系统提示追加提醒。
- 每个压缩周期仅触发一次刷新(状态记录于
sessions.json)。 - 工作区必须可写:如会话沙箱运行且
workspaceAccess设为"ro"或"none",则跳过刷新。
向量内存检索
OpenClaw 可为MEMORY.md 和 memory/*.md 构建小型向量索引,
语义查询可找到用词不同但相关的笔记。
默认配置:
- 默认启用。
- 监听内存文件变更(有防抖处理)。
- 内存检索配置于
agents.defaults.memorySearch(非顶层memorySearch)。 - 默认使用远程向量嵌入。如未设置
memorySearch.provider,OpenClaw 按优先级自动选择:- 若配置且文件存在
memorySearch.local.modelPath,使用local。 - 如果检测到 OpenAI API 密钥,使用
openai。 - 如果检测到 Gemini API 密钥,使用
gemini。 - 如果检测到 Voyage API 密钥,使用
voyage。 - 如果检测到 Mistral API 密钥,使用
mistral。 - 否则内存检索保持禁用,直到被配置。
- 若配置且文件存在
- 本地模式使用 node-llama-cpp,可能需要执行
pnpm approve-builds。 - 使用 sqlite-vec(若支持)加速 SQLite 内的向量搜索。
- 也支持设置
memorySearch.provider = "ollama"用于本地/自托管 Ollama 嵌入(/api/embeddings),但不会自动选中。
models.providers.*.apiKey 或环境变量解析密钥。Codex OAuth 只覆盖聊天/补全,不满足内存搜索嵌入要求。Gemini 使用
GEMINI_API_KEY 或 models.providers.google.apiKey,Voyage 使用
VOYAGE_API_KEY 或 models.providers.voyage.apiKey,Mistral 使用
MISTRAL_API_KEY 或 models.providers.mistral.apiKey。Ollama 通常不需要真实密钥(占位符如
OLLAMA_API_KEY=ollama-local 即可满足本地策略需求)。
使用自定义 OpenAI 兼容端点时,需设置 memorySearch.remote.apiKey(及可选的 memorySearch.remote.headers)。
QMD 后端(实验性)
将memory.backend = "qmd" 可用 QMD 替换内置 SQLite 索引器:QMD 是一个本地优先的搜索伴生程序,融合 BM25 + 向量 + 重排序。Markdown 保持事实源头,OpenClaw 调用 QMD 进行检索。重点说明:
先决条件
- 默认禁用,需要显式启用
memory.backend = "qmd"。 - 需单独安装 QMD CLI(
bun install -g https://github.com/tobi/qmd或手动下载发布版本),并保证命令行能识别qmd。 - QMD 需支持扩展的 SQLite 版本(macOS 可用
brew install sqlite)。 - QMD 完全本地运行,基于 Bun +
node-llama-cpp,初次使用会自动从 HuggingFace 下载 GGUF 模型(不依赖 Ollama 守护进程)。 - 网关将通过设置
XDG_CONFIG_HOME和XDG_CACHE_HOME在~/.openclaw/agents/<agentId>/qmd/创建 QMD 自包含运行环境。 - 支持的平台:macOS、Linux 原生,Windows 推荐通过 WSL2。
- 网关在
~/.openclaw/agents/<agentId>/qmd/下写入包含配置、缓存及 SQLite 数据库的 QMD 运行环境。 - 利用
qmd collection add创建集合,来源于memory.qmd.paths加默认的工作区内存文件,启动及定时运行qmd update和qmd embed(通过配置项memory.qmd.update.interval,默认 5 分钟)。 - 启动时初始化 QMD 管理器,定时器提前激活,即使没调用
memory_search也能运行。 - 启动刷新默认异步后台执行,避免阻塞聊天启动;如需同步阻塞可设置
memory.qmd.update.waitForBootSync = true。 - 搜索通过
memory.qmd.searchMode执行(默认qmd search --json,也支持vsearch和query)。若选中的模式不支持某些标志,OpenClaw 会尝试用qmd query。QMD 出错或缺失二进制时,自动回退到内置 SQLite 管理器,保证内存工具正常。 - 当前不支持调整 QMD 嵌入批量大小,批量行为由 QMD 控制。
- 首次搜索或较慢:QMD 可能在首次
qmd query调用时下载本地 GGUF 模型(重排序器/查询扩展)。-
OpenClaw 自动设置
XDG_CONFIG_HOME/XDG_CACHE_HOME运行 QMD。 -
若想提前手动下载模型(预热 OpenClaw 使用的相同索引),可以在代理的 XDG 目录下运行一次查询:
OpenClaw 的 QMD 状态位于你的 状态目录(默认
~/.openclaw)。
你可以使用以下命令指向同一个索引并执行预热:
-
OpenClaw 自动设置
memory.qmd.*)
command(默认qmd):覆盖可执行文件路径。searchMode(默认search):选择支持memory_search的 QMD 命令(search、vsearch、query)。includeDefaultMemory(默认true):自动索引MEMORY.md和memory/**/*.md。paths[]:增加额外目录/文件(支持path,可选pattern和稳定的name标识)。sessions:启用会话 JSONL 索引支持(enabled、保留天数retentionDays、导出目录exportDir)。update:控制刷新周期和维护行为(interval,防抖debounceMs,启动时执行onBoot,启动同步等待waitForBootSync,嵌入频率embedInterval,命令超时commandTimeoutMs,更新超时updateTimeoutMs,嵌入超时embedTimeoutMs)。limits:限制检索负载(maxResults、maxSnippetChars、maxInjectedChars、timeoutMs)。scope:与session.sendPolicy结构相同,默认为仅 DM(黑名单全部,允许直接聊天),可放宽以支持群组/频道检索。match.keyPrefix匹配归一化后的会话键(小写,且去除前缀agent:<id>:),例:discord:channel:。match.rawKeyPrefix匹配原始会话键(小写,包括前缀),例:agent:main:discord:。- 兼容:
match.keyPrefix: "agent:..."被视作原始键前缀,推荐使用rawKeyPrefix更明确。
- 搜索被 scope 拒绝时,会在日志输出带有频道和聊天类型的警告,便于调试。
- 工作区外的片段源在
memory_search结果中前缀显示为qmd/<collection>/<relative-path>,memory_get支持此类前缀并从对应 QMD 集合根路径读取。 memory.qmd.sessions.enabled = true时,OpenClaw 会将清理后的会话对话记录(用户/助理回合)导出到专门 QMD 集合~/.openclaw/agents/<id>/qmd/sessions/,使memory_search可检索近期对话而无需使用内置 SQLite 索引。- 当
memory.citations为auto/on,memory_search片段包括Source: <path#line>脚注;设置为"off"可隐藏路径元数据(代理仍可通过memory_get获得路径,但片段文本无脚注,系统提示提醒代理不引用路径)。
memory.citations无论后端如何都有效 (auto/on/off)。- 运行 QMD 时,
status().backend = "qmd"帮助诊断展示使用的引擎。若 QMD 子进程退出或 JSON 解析失败,索引管理器记录警告并启用内置提供器(原有 Markdown 嵌入),直到 QMD 恢复。
额外内存路径
若需索引工作区默认布局外的 Markdown 文件,可添加额外路径:- 路径可为绝对或相对于工作区。
- 目录会递归扫描
.md文件。 - 仅索引 Markdown 文件。
- 忽略符号链接(文件或目录)。
Gemini 嵌入(原生)
将提供者设为gemini,直接使用 Gemini 嵌入 API:
remote.baseUrl可选(默认 Gemini API 基础地址)。remote.headers支持添加额外请求头。- 默认模型为
gemini-embedding-001。
remote 配置:
memorySearch.provider = "local" 或设置 memorySearch.fallback = "none"。
回退:
memorySearch.fallback支持openai、gemini、voyage、mistral、ollama、local或none。- 仅在主嵌入提供者失败时启用回退服务。
- 默认禁用。设置
agents.defaults.memorySearch.remote.batch.enabled = true以支持大规模索引(OpenAI、Gemini、Voyage)。 - 默认阻塞批处理完成;可调节
remote.batch.wait、remote.batch.pollIntervalMs和remote.batch.timeoutMinutes。 - 设置
remote.batch.concurrency控制并发批处理数(默认 2)。 - 批处理模式适用
memorySearch.provider为openai或gemini,并需对应 API 密钥。 - Gemini 批处理调用异步嵌入批量端点,需启用 Gemini 批处理 API。
- 大型补录时,OpenAI 通常最速,因为可在单个批次提交大量请求,异步处理。
- OpenAI 针对 Batch API 有折扣,批量大规模索引成本通常低于同步请求。
- 相关文档:
memory_search— 返回带文件和行范围的片段。memory_get— 根据路径读取内存文件内容。
- 设置
agents.defaults.memorySearch.provider = "local"。 - 提供
agents.defaults.memorySearch.local.modelPath(GGUF 或hf:URI)。 - 可选设置
agents.defaults.memorySearch.fallback = "none"禁止远程回退。
内存工具工作原理
memory_search对来自MEMORY.md和memory/**/*.md的 Markdown 块(目标约 400 令牌,重叠 80 令牌)执行语义搜索,返回片段文本(限制约 700 字符)、文件路径、行范围、得分、提供者/模型和是否从本地切换远程。不会返回完整文件内容。memory_get读取指定的工作区相对路径 Markdown 内存文件,可选指定起始行和读取行数。路径必须在MEMORY.md或memory/内,其他拒绝访问。- 仅当
memorySearch.enabled解析为真时,工具才启用。
索引内容及时机
- 文件类型:仅 Markdown 文件(
MEMORY.md,memory/**/*.md)。 - 索引存储:每代理一个 SQLite 文件,位于
~/.openclaw/memory/<agentId>.sqlite(可通过agents.defaults.memorySearch.store.path配置,支持{agentId}变量)。 - 新鲜度:监控
MEMORY.md和memory/变更(防抖 1.5秒),标记索引失效。同步在会话启动、搜索时或定时异步执行。会话记录使用差异阈值触发后台同步。 - 重新索引触发:索引存储嵌入提供者/模型及端点指纹与拆分参数,若参数更改则重置索引并重新构建。
混合搜索(BM25 + 向量)
启用后,OpenClaw 结合:- 向量相似度(语义匹配,用词可不同)
- BM25 关键词相关度(精确匹配令牌,例如 ID、环境变量、代码符号)
为什么混合?
向量搜索优于“意思相同”的匹配:- “Mac Studio 网关主机” vs “运行网关的机器”
- “防抖文件更新” vs “避免每次写入都索引”
- ID(
a828e60、b3b9895a...) - 代码符号(
memorySearch.query.hybrid) - 错误字符串(“sqlite-vec unavailable”)
我们如何合并结果(当前设计)
实现思路:-
双侧分别检索候选集:
- 向量:Cosine 相似度排序取前
maxResults * candidateMultiplier; - BM25:FTS5 BM25 排名(越低越好)取前同样数量。
- 向量:Cosine 相似度排序取前
-
将 BM25 排名转换为大概 0..1 分值:
textScore = 1 / (1 + max(0, bm25Rank))
-
按块 ID 合并候选并计算加权得分:
finalScore = vectorWeight * vectorScore + textWeight * textScore
- 配置中
vectorWeight+textWeight会被归一化为 1.0。 - 若嵌入不可用(或提供者返回零向量),仍执行 BM25 返回关键词匹配。
- 无法创建 FTS5 则只用向量搜索,非严重失败。
后处理流程
合并向量和关键词得分后,两个可选后处理阶段对结果排序进行优化:MMR 重排序(多样性)
混合搜索结果中多个结果可能包含相似或高度重叠内容。举例“家庭网络设置”查询,可能返回多条几乎一样的不同日期笔记,重复描述同一路由器配置。 MMR(最大边际相关) 重新排序结果,平衡相关性和多样性,保证顶级结果覆盖查询的不同方面,避免重复。 工作原理:
- 对结果按原始相关排序(向量 + BM25 加权得分)。
- MMR 迭代选择最大化
λ × 相关性 − (1−λ) × 与已选结果最大相似度的结果。 - 结果相似度采用分词后文本的 Jaccard 相似度计算。
lambda = 1.0→ 纯相关(不考虑多样性)lambda = 0.0→ 最大多样性(忽视相关性)- 默认
0.7(平衡,稍偏向相关性)
memory_search 返回大量冗余或近似片段,尤其每日笔记常常跨天重复类似信息时。
时间衰减(新鲜度提升)
带有每日笔记的代理会随着时间积累数百条带日期的文件。无衰减时,即使 6 个月前写的表达优美的笔记,也可能排在昨天更新笔记之上。 时间衰减 通过指数乘数根据记忆条目年龄降低得分,使最近记忆自然排名更靠前,旧记忆权重衰减:
λ = ln(2) / halfLifeDays。
默认 30 天半衰期时:
- 当天笔记:保留**100%**原得分
- 7 天前:约84%
- 30 天前:50%
- 90 天前:12.5%
- 180 天前:约1.6%
MEMORY.md(顶层内存文件)memory/中未带日期的文件(如memory/projects.md,memory/network.md)- 这些文件内容持久且应维持正常排名。
memory/YYYY-MM-DD.md)用文件名提取日期,其他来源(如会话日志)回退使用文件修改时间(mtime)。
示例 — 查询:“Rod 的工作安排?”
文件示例(今天为 2 月 10 日):
默认 30 天半衰期适合每日笔记密集场景;参考需求可调长(如 90 天)。
配置示例
两者配置位于memorySearch.query.hybrid:
- 仅 MMR — 当有大量相似笔记但时间不敏感时有用。
- 仅时间衰减 — 时间重要但结果本身已够多样性时有用。
- 两者同时启用 — 对拥有庞大、长期每日笔记历史的代理推荐。
嵌入缓存
OpenClaw 可将文本块嵌入缓存至 SQLite,避免重索引和频繁更新时重复嵌入未变内容(特别是会话记录)。 配置示例:会话内存检索(实验性)
可选索引会话日志,并通过memory_search 展示。该功能处于实验阶段。
- 会话索引为需显式开启,默认关闭。
- 会话更新防抖且异步索引,达阈值时后台尽力同步。
memory_search不等待索引完成,结果可能稍微滞后。- 结果仍只含片段,
memory_get仅限内存文件。 - 会话索引对单代理隔离,仅索引该代理对应日志。
- 会话日志存于磁盘(
~/.openclaw/agents/<agentId>/sessions/*.jsonl),有文件系统访问权限的用户和进程均可读取,磁盘访问即为信任边界。若需更严格隔离,请在不同操作系统用户或主机上运行代理。
SQLite 向量加速(sqlite-vec)
当支持 sqlite-vec 扩展时,OpenClaw 将嵌入存入 SQLite 虚拟表(vec0),实现数据库内的向量距离查询。保持搜索快速,无需将全部嵌入加载到 JS 中。
可选配置:
enabled默认为 true;禁用时退回 JS 进程内的余弦相似度计算。- 缺少或加载失败 sqlite-vec 扩展时,OpenClaw 输出错误日志并使用 JS 方式继续,不影响功能。
extensionPath可覆盖绑定的 sqlite-vec 路径,方便自定义或非标准安装位置。
本地嵌入模型自动下载
- 默认本地嵌入模型:
hf:ggml-org/embeddinggemma-300m-qat-q8_0-GGUF/embeddinggemma-300m-qat-Q8_0.gguf(约 0.6 GB)。 - 当
memorySearch.provider = "local"时,node-llama-cpp解析modelPath,若缺失 GGUF 文件,会自动下载至缓存目录(或local.modelCacheDir,如果设置),然后加载。下载支持重试续传。 - 本地构建要求:执行
pnpm approve-builds,选中node-llama-cpp,再pnpm rebuild node-llama-cpp。 - 回退方案:本地失败且设置了
memorySearch.fallback = "openai"时,自动切换远程嵌入(默认openai/text-embedding-3-small),并记录原因。
自定义 OpenAI 兼容端点示例
remote.*配置优先于models.providers.openai.*。remote.headers与 OpenAI 默认头合并,冲突时以remote为准。不设置时使用默认头。