记忆

OpenClaw 的记忆就是工作区里的纯 Markdown 文件。这些文件是唯一的事实来源;模型只”记得”写到磁盘上的内容。

记忆搜索工具由当前激活的记忆插件提供(默认:memory-core)。可以通过 plugins.slots.memory = "none" 禁用记忆插件。

记忆文件(Markdown)

默认工作区布局使用两层记忆:

  • memory/YYYY-MM-DD.md
    • 每日日志(追加写入)。
    • 会话开始时读取今天和昨天的文件。
  • MEMORY.md(可选)
    • 经过整理的长期记忆。
    • 仅在主会话(私人会话)中加载(不要在群组上下文中加载)。

这些文件位于工作区下(agents.defaults.workspace,默认 ~/.openclaw/workspace)。完整布局详见 Agent 工作区

记忆工具

OpenClaw 为 agent 提供两个记忆工具:

  • memory_search — 对已索引的片段进行语义搜索。
  • memory_get — 读取特定 Markdown 文件/行范围。

memory_get 现在在文件不存在时会优雅降级(比如今天的日志在第一次写入前还不存在)。内置管理器和 QMD 后端都会返回 { text: "", path } 而不是抛出 ENOENT,这样 agent 可以处理”还没记录任何内容”的情况,继续工作,而不需要把工具调用包在 try/catch 里。

什么时候写记忆

  • 决定、偏好和持久性事实写入 MEMORY.md
  • 日常笔记和运行上下文写入 memory/YYYY-MM-DD.md
  • 如果有人说”记住这个”,就写下来(不要只放在内存里)。
  • 这方面还在演进中。提醒模型存记忆会有帮助,它知道该怎么做。
  • 如果你希望某件事被记住,让机器人把它写到记忆里

自动记忆刷写(compaction 前提醒)

当会话接近自动 compaction 时,OpenClaw 会触发一轮静默的 agent 对话,提醒模型在上下文被压缩之前把持久化的记忆写到磁盘。默认提示词明确说模型_可以_回复,但通常 NO_REPLY 才是正确的响应,这样用户不会看到这轮对话。

通过 agents.defaults.compaction.memoryFlush 控制:

{
  agents: {
    defaults: {
      compaction: {
        reserveTokensFloor: 20000,
        memoryFlush: {
          enabled: true,
          softThresholdTokens: 4000,
          systemPrompt: "Session nearing compaction. Store durable memories now.",
          prompt: "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store.",
        },
      },
    },
  },
}

细节:

  • 软阈值:当会话 token 估算超过 contextWindow - reserveTokensFloor - softThresholdTokens 时触发刷写。
  • 默认静默:提示词包含 NO_REPLY,不会产生用户可见的输出。
  • 两个提示词:一个用户提示词加一个系统提示词附加提醒。
  • 每个 compaction 周期只刷写一次(在 sessions.json 中追踪)。
  • 工作区必须可写:如果会话以 workspaceAccess: "ro""none" 在沙箱中运行,刷写会被跳过。

完整的 compaction 生命周期详见 会话管理与 compaction

向量记忆搜索

OpenClaw 可以对 MEMORY.mdmemory/*.md 建立小型向量索引,让语义查询即使措辞不同也能找到相关笔记。

默认行为:

  • 默认启用。
  • 监听记忆文件变化(有防抖)。
  • agents.defaults.memorySearch 下配置(不是顶层的 memorySearch)。
  • 默认使用远程 embedding。如果没设置 memorySearch.provider,OpenClaw 自动选择:
    1. local(如果配置了 memorySearch.local.modelPath 且文件存在)。
    2. openai(如果能解析到 OpenAI key)。
    3. gemini(如果能解析到 Gemini key)。
    4. voyage(如果能解析到 Voyage key)。
    5. mistral(如果能解析到 Mistral key)。
    6. 否则记忆搜索保持禁用状态,等待配置。
  • 本地模式使用 node-llama-cpp,可能需要 pnpm approve-builds
  • 支持 sqlite-vec(可用时)加速 SQLite 中的向量搜索。
  • memorySearch.provider = "ollama" 也支持,用于本地/自托管的 Ollama embedding(/api/embeddings),但不会被自动选择。

远程 embedding 需要对应 embedding provider 的 API key。OpenClaw 从认证配置、models.providers.*.apiKey 或环境变量中解析 key。Codex OAuth 只覆盖 chat/completions,不满足记忆搜索的 embedding 需求。Gemini 使用 GEMINI_API_KEYmodels.providers.google.apiKey。Voyage 使用 VOYAGE_API_KEYmodels.providers.voyage.apiKey。Mistral 使用 MISTRAL_API_KEYmodels.providers.mistral.apiKey。Ollama 通常不需要真正的 API key(本地策略需要时用 OLLAMA_API_KEY=ollama-local 这样的占位符就行)。使用自定义 OpenAI 兼容端点时,设置 memorySearch.remote.apiKey(以及可选的 memorySearch.remote.headers)。

QMD 后端(实验性)

设置 memory.backend = "qmd" 可以把内置的 SQLite 索引器替换为 QMD:一个本地优先的搜索 sidecar,结合了 BM25 + 向量 + 重排序。Markdown 仍然是事实来源;OpenClaw 通过调用 QMD 来检索。要点:

前提条件

  • 默认禁用。需要在配置中启用(memory.backend = "qmd")。
  • 单独安装 QMD CLI(bun install -g https://github.com/tobi/qmd 或下载 release),确保 qmd 在 Gateway 的 PATH 上。
  • QMD 需要支持扩展的 SQLite 构建(macOS 上 brew install sqlite)。
  • QMD 通过 Bun + node-llama-cpp 完全本地运行,首次使用时自动从 HuggingFace 下载 GGUF 模型(不需要单独的 Ollama 守护进程)。
  • Gateway 在 ~/.openclaw/agents/<agentId>/qmd/ 下运行 QMD,通过设置 XDG_CONFIG_HOMEXDG_CACHE_HOME 实现自包含的 XDG 目录。
  • 平台支持:macOS 和 Linux 安装好 Bun + SQLite 后开箱即用。Windows 建议通过 WSL2 使用。

Sidecar 运行方式

  • Gateway 在 ~/.openclaw/agents/<agentId>/qmd/ 下创建自包含的 QMD 目录(配置 + 缓存 + SQLite 数据库)。
  • 通过 qmd collection addmemory.qmd.paths(加上默认的工作区记忆文件)创建集合,然后在启动时和可配置的间隔内运行 qmd update + qmd embedmemory.qmd.update.interval,默认 5 分钟)。
  • Gateway 现在在启动时就初始化 QMD 管理器,所以周期性更新计时器在第一次 memory_search 调用之前就已就绪。
  • 启动刷新默认在后台运行,不会阻塞聊天启动;设置 memory.qmd.update.waitForBootSync = true 可以保持之前的阻塞行为。
  • 搜索通过 memory.qmd.searchMode 执行(默认 qmd search --json;也支持 vsearchquery)。如果选定模式在你的 QMD 版本上不支持某些参数,OpenClaw 会回退到 qmd query。如果 QMD 失败或二进制文件缺失,OpenClaw 自动回退到内置的 SQLite 管理器,确保记忆工具继续工作。
  • OpenClaw 目前不暴露 QMD 的 embed batch-size 调优;批处理行为由 QMD 自己控制。
  • 首次搜索可能较慢:QMD 可能在第一次 qmd query 时下载本地 GGUF 模型(重排序器/查询扩展)。
    • OpenClaw 在运行 QMD 时自动设置 XDG_CONFIG_HOME/XDG_CACHE_HOME

    • 如果你想手动预下载模型(并预热 OpenClaw 使用的同一个索引),可以用 agent 的 XDG 目录执行一次查询。

      OpenClaw 的 QMD 状态位于你的 state 目录(默认 ~/.openclaw)。你可以通过导出相同的 XDG 变量来让 qmd 指向完全相同的索引:

      # 使用 OpenClaw 使用的相同 state 目录
      STATE_DIR="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
      
      export XDG_CONFIG_HOME="$STATE_DIR/agents/main/qmd/xdg-config"
      export XDG_CACHE_HOME="$STATE_DIR/agents/main/qmd/xdg-cache"
      
      # (可选)强制刷新索引 + embedding
      qmd update
      qmd embed
      
      # 预热 / 触发首次模型下载
      qmd query "test" -c memory-root --json >/dev/null 2>&1

配置项(memory.qmd.*

  • command(默认 qmd):覆盖可执行文件路径。
  • searchMode(默认 search):选择哪个 QMD 命令作为 memory_search 后端(searchvsearchquery)。
  • includeDefaultMemory(默认 true):自动索引 MEMORY.md + memory/**/*.md
  • paths[]:添加额外目录/文件(path,可选 pattern,可选稳定 name)。
  • sessions:启用会话 JSONL 索引(enabledretentionDaysexportDir)。
  • update:控制刷新节奏和维护执行(intervaldebounceMsonBootwaitForBootSyncembedIntervalcommandTimeoutMsupdateTimeoutMsembedTimeoutMs)。
  • limits:限制召回载荷(maxResultsmaxSnippetCharsmaxInjectedCharstimeoutMs)。
  • scope:与 session.sendPolicy 相同的 schema。默认仅限私聊(deny 所有,allow 私聊);放宽它可以在群组/频道中显示 QMD 结果。
    • match.keyPrefix 匹配规范化的会话 key(小写,去掉前缀 agent:<id>:)。例如:discord:channel:
    • match.rawKeyPrefix 匹配原始会话 key(小写),包含 agent:<id>:。例如:agent:main:discord:
    • 旧版写法:match.keyPrefix: "agent:..." 仍然被当作 raw-key 前缀处理,但建议用 rawKeyPrefix 更清晰。
  • scope 拒绝搜索时,OpenClaw 会记录一条包含 channel/chatType 的警告日志,方便排查空结果。
  • 工作区外的来源片段在 memory_search 结果中显示为 qmd/<collection>/<relative-path>memory_get 理解这个前缀,从配置的 QMD 集合根路径读取。
  • memory.qmd.sessions.enabled = true 时,OpenClaw 导出清理后的会话记录(User/Assistant 轮次)到 ~/.openclaw/agents/<id>/qmd/sessions/ 下的专用 QMD 集合,这样 memory_search 可以回忆最近的对话,而不需要触碰内置的 SQLite 索引。
  • memory_search 片段现在在 memory.citationsauto/on 时包含 Source: <path#line> 尾注;设置 memory.citations = "off" 可以保持路径元数据仅供内部使用(agent 仍然会收到路径用于 memory_get,但片段文本会省略尾注,系统提示词会提醒 agent 不要引用它)。

示例

memory: {
  backend: "qmd",
  citations: "auto",
  qmd: {
    includeDefaultMemory: true,
    update: { interval: "5m", debounceMs: 15000 },
    limits: { maxResults: 6, timeoutMs: 4000 },
    scope: {
      default: "deny",
      rules: [
        { action: "allow", match: { chatType: "direct" } },
        // 规范化的会话 key 前缀(去掉 `agent:<id>:`)。
        { action: "deny", match: { keyPrefix: "discord:channel:" } },
        // 原始会话 key 前缀(包含 `agent:<id>:`)。
        { action: "deny", match: { rawKeyPrefix: "agent:main:discord:" } },
      ]
    },
    paths: [
      { name: "docs", path: "~/notes", pattern: "**/*.md" }
    ]
  }
}

引用与回退

  • memory.citations 无论后端是什么都适用(auto/on/off)。
  • QMD 运行时,我们标记 status().backend = "qmd" 方便诊断显示结果来自哪个引擎。如果 QMD 子进程退出或 JSON 输出无法解析,搜索管理器记录警告并回退到内置 provider(已有的 Markdown embedding),直到 QMD 恢复。

额外记忆路径

如果你想索引默认工作区布局之外的 Markdown 文件,添加显式路径:

agents: {
  defaults: {
    memorySearch: {
      extraPaths: ["../team-docs", "/srv/shared-notes/overview.md"]
    }
  }
}

注意:

  • 路径可以是绝对路径或相对于工作区的路径。
  • 目录会递归扫描 .md 文件。
  • 默认只索引 Markdown 文件。
  • 如果 memorySearch.multimodal.enabled = true,OpenClaw 也会索引 extraPaths 下支持的图片/音频文件。默认记忆根目录(MEMORY.mdmemory.mdmemory/**/*.md)仍然只索引 Markdown。
  • 符号链接会被忽略(文件和目录都是)。

多模态记忆文件(Gemini 图片 + 音频)

使用 Gemini embedding 2 时,OpenClaw 可以索引 memorySearch.extraPaths 中的图片和音频文件:

agents: {
  defaults: {
    memorySearch: {
      provider: "gemini",
      model: "gemini-embedding-2-preview",
      extraPaths: ["assets/reference", "voice-notes"],
      multimodal: {
        enabled: true,
        modalities: ["image", "audio"], // 或 ["all"]
        maxFileBytes: 10000000
      },
      remote: {
        apiKey: "YOUR_GEMINI_API_KEY"
      }
    }
  }
}

注意:

  • 多模态记忆目前只支持 gemini-embedding-2-preview
  • 多模态索引只应用于通过 memorySearch.extraPaths 发现的文件。
  • 当前阶段支持的模态:图片和音频。
  • 启用多模态记忆时,memorySearch.fallback 必须保持 "none"
  • 匹配的图片/音频文件字节会在索引时上传到配置的 Gemini embedding 端点。
  • 支持的图片格式:.jpg.jpeg.png.webp.gif.heic.heif
  • 支持的音频格式:.mp3.wav.ogg.opus.m4a.aac.flac
  • 搜索查询仍然是文本,但 Gemini 可以将文本查询与已索引的图片/音频 embedding 进行比较。
  • memory_get 仍然只读取 Markdown;二进制文件可搜索但不会作为原始文件内容返回。

Gemini embedding(原生)

设置 provider 为 gemini 可以直接使用 Gemini embedding API:

agents: {
  defaults: {
    memorySearch: {
      provider: "gemini",
      model: "gemini-embedding-001",
      remote: {
        apiKey: "YOUR_GEMINI_API_KEY"
      }
    }
  }
}

注意:

  • remote.baseUrl 可选(默认使用 Gemini API 基础 URL)。
  • remote.headers 可以按需添加额外头部。
  • 默认模型:gemini-embedding-001
  • 也支持 gemini-embedding-2-preview:8192 token 限制,可配置维度(768 / 1536 / 3072,默认 3072)。

Gemini Embedding 2(预览版)

agents: {
  defaults: {
    memorySearch: {
      provider: "gemini",
      model: "gemini-embedding-2-preview",
      outputDimensionality: 3072,  // 可选:768、1536 或 3072(默认)
      remote: {
        apiKey: "YOUR_GEMINI_API_KEY"
      }
    }
  }
}

注意:需要重新索引。gemini-embedding-001(768 维)切换到 gemini-embedding-2-preview(3072 维)会改变向量大小。在 768、1536 和 3072 之间切换 outputDimensionality 时也是如此。OpenClaw 检测到模型或维度变化时会自动重新索引。

如果你想使用自定义 OpenAI 兼容端点(OpenRouter、vLLM 或代理),可以在 OpenAI provider 下使用 remote 配置:

agents: {
  defaults: {
    memorySearch: {
      provider: "openai",
      model: "text-embedding-3-small",
      remote: {
        baseUrl: "https://api.example.com/v1/",
        apiKey: "YOUR_OPENAI_COMPAT_API_KEY",
        headers: { "X-Custom-Header": "value" }
      }
    }
  }
}

如果不想设置 API key,使用 memorySearch.provider = "local" 或设置 memorySearch.fallback = "none"

回退:

  • memorySearch.fallback 可以是 openaigeminivoyagemistralollamalocalnone
  • 回退 provider 仅在主 embedding provider 失败时使用。

批量索引(OpenAI + Gemini + Voyage):

  • 默认禁用。设置 agents.defaults.memorySearch.remote.batch.enabled = true 启用大规模索引(OpenAI、Gemini 和 Voyage)。
  • 默认行为等待批处理完成;可通过 remote.batch.waitremote.batch.pollIntervalMsremote.batch.timeoutMinutes 调优。
  • 设置 remote.batch.concurrency 控制并行提交的批处理任务数(默认 2)。
  • 批处理模式在 memorySearch.provider = "openai""gemini" 时生效,使用对应的 API key。
  • Gemini 批处理任务使用异步 embedding 批处理端点,需要 Gemini Batch API 可用。

为什么 OpenAI 批处理又快又便宜:

配置示例:

agents: {
  defaults: {
    memorySearch: {
      provider: "openai",
      model: "text-embedding-3-small",
      fallback: "openai",
      remote: {
        batch: { enabled: true, concurrency: 2 }
      },
      sync: { watch: true }
    }
  }
}

工具:

  • memory_search — 返回包含文件路径和行范围的片段。
  • memory_get — 按路径读取记忆文件内容。

本地模式:

  • 设置 agents.defaults.memorySearch.provider = "local"
  • 提供 agents.defaults.memorySearch.local.modelPath(GGUF 或 hf: URI)。
  • 可选:设置 agents.defaults.memorySearch.fallback = "none" 避免远程回退。

记忆工具工作原理

  • memory_searchMEMORY.md + memory/**/*.md 的 Markdown 分块(约 400 token,80 token 重叠)进行语义搜索。返回片段文本(上限约 700 字符)、文件路径、行范围、分数、provider/模型,以及是否从本地回退到了远程 embedding。不返回完整文件内容。
  • memory_get 读取指定的记忆 Markdown 文件(工作区相对路径),可选从指定行开始读取 N 行。MEMORY.md / memory/ 之外的路径会被拒绝。
  • 两个工具仅在 memorySearch.enabled 对 agent 生效时启用。

索引什么以及何时索引

  • 文件类型:仅 Markdown(MEMORY.mdmemory/**/*.md)。
  • 索引存储:每个 agent 一个 SQLite,位于 ~/.openclaw/memory/<agentId>.sqlite(可通过 agents.defaults.memorySearch.store.path 配置,支持 {agentId} 占位符)。
  • 新鲜度:监听 MEMORY.md + memory/ 的变化标记索引为脏(防抖 1.5 秒)。同步在会话开始时、搜索时或按间隔调度,异步运行。会话记录使用增量阈值触发后台同步。
  • 重新索引触发条件:索引存储了 embedding 的 provider/模型 + 端点指纹 + 分块参数。其中任何一个改变,OpenClaw 都会自动重置并重新索引整个存储。

混合搜索(BM25 + 向量)

启用时,OpenClaw 结合两种信号:

  • 向量相似度(语义匹配,措辞可以不同)
  • BM25 关键词相关性(精确匹配 ID、环境变量、代码符号等)

如果你的平台不支持全文搜索,OpenClaw 会回退到纯向量搜索。

为什么要混合搜索?

向量搜索擅长”意思一样但说法不同”:

  • “Mac Studio gateway 主机” vs “运行 gateway 的那台机器”
  • “文件更新防抖” vs “避免每次写入都索引”

但在精确、高信息量的 token 上比较弱:

  • ID(a828e60b3b9895a…
  • 代码符号(memorySearch.query.hybrid
  • 错误字符串(“sqlite-vec unavailable”)

BM25(全文)正好相反:精确 token 强,同义表达弱。混合搜索是务实的折中:同时使用两种检索信号,让”自然语言”和”大海捞针”两种查询都能得到好结果。

结果合并方式(当前设计)

实现思路:

  1. 从两边获取候选池:
  • 向量:按余弦相似度取前 maxResults * candidateMultiplier 个。
  • BM25:按 FTS5 BM25 排名取前 maxResults * candidateMultiplier 个(排名值越低越好)。
  1. 把 BM25 排名转换为 0..1 范围的分数:
  • textScore = 1 / (1 + max(0, bm25Rank))
  1. 按 chunk id 取并集,计算加权分数:
  • finalScore = vectorWeight * vectorScore + textWeight * textScore

说明:

  • 配置解析时 vectorWeight + textWeight 会归一化到 1.0,所以权重可以理解为百分比。
  • 如果 embedding 不可用(或 provider 返回零向量),仍然会运行 BM25 并返回关键词匹配结果。
  • 如果 FTS5 创建失败,保持纯向量搜索(不会硬性报错)。

这不是信息检索理论上完美的方案,但够简单、够快,在真实笔记上效果通常不错。以后如果要精进,常见的下一步是互惠排名融合(RRF)或分数归一化(min/max 或 z-score)再混合。

后处理管线

向量和关键词分数合并后,还有两个可选的后处理阶段可以在结果到达 agent 之前进行精炼:

Vector + Keyword → Weighted Merge → Temporal Decay → Sort → MMR → Top-K Results

两个阶段默认关闭,可以独立启用。

MMR 重排序(多样性)

混合搜索返回结果时,多个分块可能包含相似或重叠的内容。比如搜索”家庭网络配置”可能返回五条几乎相同的片段,来自不同的日记但都提到了同一个路由器配置。

MMR(最大边际相关性) 重排序结果以平衡相关性和多样性,确保排在前面的结果覆盖查询的不同方面,而不是重复相同的信息。

工作原理:

  1. 结果按原始相关性打分(向量 + BM25 加权分数)。
  2. MMR 迭代选择满足以下条件的结果:最大化 λ × relevance − (1−λ) × max_similarity_to_selected
  3. 结果之间的相似度使用分词内容的 Jaccard 文本相似度衡量。

lambda 参数控制权衡:

  • lambda = 1.0 → 纯相关性(无多样性惩罚)
  • lambda = 0.0 → 最大多样性(忽略相关性)
  • 默认值:0.7(平衡,略偏相关性)

示例 — 查询:“家庭网络配置”

假设有以下记忆文件:

memory/2026-02-10.md  → "Configured Omada router, set VLAN 10 for IoT devices"
memory/2026-02-08.md  → "Configured Omada router, moved IoT to VLAN 10"
memory/2026-02-05.md  → "Set up AdGuard DNS on 192.168.10.2"
memory/network.md     → "Router: Omada ER605, AdGuard: 192.168.10.2, VLAN 10: IoT"

不用 MMR 时 — 前 3 个结果:

1. memory/2026-02-10.md  (score: 0.92)  ← 路由器 + VLAN
2. memory/2026-02-08.md  (score: 0.89)  ← 路由器 + VLAN(近似重复!)
3. memory/network.md     (score: 0.85)  ← 参考文档

用 MMR(λ=0.7)时 — 前 3 个结果:

1. memory/2026-02-10.md  (score: 0.92)  ← 路由器 + VLAN
2. memory/network.md     (score: 0.85)  ← 参考文档(多样!)
3. memory/2026-02-05.md  (score: 0.78)  ← AdGuard DNS(多样!)

2 月 8 日那条近似重复的结果被挤掉了,agent 拿到了三条不同的信息。

什么时候启用: 如果你发现 memory_search 返回冗余或近似重复的片段,特别是日记中经常跨天重复类似信息时。

时间衰减(新近度加成)

使用日记的 agent 会随时间积累数百个带日期的文件。没有衰减的话,六个月前写得好的笔记可能比昨天同一话题的更新排名更高。

时间衰减根据每条结果的年龄施加指数衰减乘数,让近期记忆自然排在前面,旧的逐渐淡出:

decayedScore = score × e^(-λ × ageInDays)

其中 λ = ln(2) / halfLifeDays

以默认半衰期 30 天为例:

  • 今天的笔记:原始分数的 100%
  • 7 天前:~84%
  • 30 天前:50%
  • 90 天前:12.5%
  • 180 天前:~1.6%

常青文件永远不衰减:

  • MEMORY.md(根记忆文件)
  • memory/ 下非日期命名的文件(如 memory/projects.mdmemory/network.md
  • 这些包含持久性参考信息,应该始终保持正常排名。

日期格式的每日文件memory/YYYY-MM-DD.md)使用从文件名提取的日期。其他来源(如会话记录)回退到文件修改时间(mtime)。

示例 — 查询:“Rod 的工作时间表?”

假设有以下记忆文件(今天是 2 月 10 日):

memory/2025-09-15.md  → "Rod works Mon-Fri, standup at 10am, pairing at 2pm"  (148 天前)
memory/2026-02-10.md  → "Rod has standup at 14:15, 1:1 with Zeb at 14:45"    (今天)
memory/2026-02-03.md  → "Rod started new team, standup moved to 14:15"        (7 天前)

不用衰减:

1. memory/2025-09-15.md  (score: 0.91)  ← 语义匹配最好,但过时了!
2. memory/2026-02-10.md  (score: 0.82)
3. memory/2026-02-03.md  (score: 0.80)

用衰减(halfLife=30):

1. memory/2026-02-10.md  (score: 0.82 × 1.00 = 0.82)  ← 今天,无衰减
2. memory/2026-02-03.md  (score: 0.80 × 0.85 = 0.68)  ← 7 天,轻微衰减
3. memory/2025-09-15.md  (score: 0.91 × 0.03 = 0.03)  ← 148 天,几乎消失

尽管 9 月那条笔记原始语义匹配最好,但衰减后跌到了最后。

什么时候启用: 如果你的 agent 有几个月的日记,且发现旧的、过时的信息排名高于最近的上下文。半衰期 30 天适合以日记为主的工作流;如果你经常引用较旧的笔记,可以调大(比如 90 天)。

配置

两个特性都在 memorySearch.query.hybrid 下配置:

agents: {
  defaults: {
    memorySearch: {
      query: {
        hybrid: {
          enabled: true,
          vectorWeight: 0.7,
          textWeight: 0.3,
          candidateMultiplier: 4,
          // 多样性:减少冗余结果
          mmr: {
            enabled: true,    // 默认:false
            lambda: 0.7       // 0 = 最大多样性,1 = 最大相关性
          },
          // 新近度:提升较新的记忆
          temporalDecay: {
            enabled: true,    // 默认:false
            halfLifeDays: 30  // 分数每 30 天减半
          }
        }
      }
    }
  }
}

两个特性可以独立启用:

  • 仅 MMR — 当你有很多相似笔记但时间不重要时适用。
  • 仅时间衰减 — 当新近度重要但结果已经足够多样时适用。
  • 两者都用 — 推荐给有大量、长期日记历史的 agent。

Embedding 缓存

OpenClaw 可以在 SQLite 中缓存分块 embedding,这样重新索引和频繁更新(特别是会话记录)不需要对未改变的文本重新 embed。

配置:

agents: {
  defaults: {
    memorySearch: {
      cache: {
        enabled: true,
        maxEntries: 50000
      }
    }
  }
}

会话记忆搜索(实验性)

你可以选择索引会话记录并通过 memory_search 暴露它们。这个功能在实验性标志后面。

agents: {
  defaults: {
    memorySearch: {
      experimental: { sessionMemory: true },
      sources: ["memory", "sessions"]
    }
  }
}

注意:

  • 会话索引是可选的(默认关闭)。
  • 会话更新有防抖,达到增量阈值后异步索引(尽力而为)。
  • memory_search 不会阻塞索引过程;在后台同步完成前结果可能稍有滞后。
  • 结果仍然只包含片段;memory_get 仍然仅限记忆文件。
  • 会话索引按 agent 隔离(只索引该 agent 自己的会话日志)。
  • 会话日志存储在磁盘上(~/.openclaw/agents/<agentId>/sessions/*.jsonl)。任何有文件系统访问权限的进程/用户都可以读取,所以磁盘访问就是信任边界。如果需要更严格的隔离,把 agent 运行在不同的操作系统用户或主机上。

增量阈值(显示默认值):

agents: {
  defaults: {
    memorySearch: {
      sync: {
        sessions: {
          deltaBytes: 100000,   // ~100 KB
          deltaMessages: 50     // JSONL 行数
        }
      }
    }
  }
}

SQLite 向量加速(sqlite-vec)

当 sqlite-vec 扩展可用时,OpenClaw 把 embedding 存储在 SQLite 虚拟表(vec0)中,在数据库内执行向量距离查询。这样不用把每个 embedding 都加载到 JS 中也能保持搜索速度。

配置(可选):

agents: {
  defaults: {
    memorySearch: {
      store: {
        vector: {
          enabled: true,
          extensionPath: "/path/to/sqlite-vec"
        }
      }
    }
  }
}

注意:

  • enabled 默认为 true;禁用时搜索回退到进程内的余弦相似度计算。
  • 如果 sqlite-vec 扩展缺失或加载失败,OpenClaw 记录错误并继续使用 JS 回退(不创建向量表)。
  • extensionPath 覆盖内置的 sqlite-vec 路径(适用于自定义构建或非标准安装位置)。

本地 embedding 自动下载

  • 默认本地 embedding 模型: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",会自动切换到远程 embedding(openai/text-embedding-3-small,除非有覆盖设置)并记录原因。

自定义 OpenAI 兼容端点示例

agents: {
  defaults: {
    memorySearch: {
      provider: "openai",
      model: "text-embedding-3-small",
      remote: {
        baseUrl: "https://api.example.com/v1/",
        apiKey: "YOUR_REMOTE_API_KEY",
        headers: {
          "X-Organization": "org-id",
          "X-Project": "project-id"
        }
      }
    }
  }
}

注意:

  • remote.* 优先于 models.providers.openai.*
  • remote.headers 与 OpenAI 头部合并;冲突时 remote 的优先。省略 remote.headers 则使用 OpenAI 默认值。