source: Young 提供的配置文档

OpenClaw 用 QMD 作为记忆搜索引擎的配置指南

> 基于 openclawqmd 当前源码整理。openclaw 已内置 QMD backend,不用自己写集成——所有逻辑在 packages/memory-host-sdk/src/host/ 下,通过子进程方式调用 qmd CLI。

一句话版本: 一份把 QMD 记忆引擎接入 OpenClaw 的完整配置手册——一行启用、三种搜索模式、完整 Schema、中文调优到排错验证,不需写集成代码。

1. 工作原理速览

关键源码入口:

文件作用
`packages/memory-host-sdk/src/host/backend-config.ts`配置解析与默认值
`packages/memory-host-sdk/src/host/qmd-process.ts`子进程 spawn / 二进制可用性探测
`packages/memory-host-sdk/src/host/qmd-query-parser.ts`解析 `qmd ... --json` 输出
`packages/memory-host-sdk/src/host/qmd-scope.ts`作用域规则(哪些会话能触发搜索)
`docs/concepts/memory-qmd.md`概念文档
`docs/reference/memory-config.md`完整 schema 参考

2. 前置条件

`sh

sudo ln -s ~/.bun/bin/qmd /usr/local/bin/qmd

`

或在配置里写绝对路径:memory.qmd.command: "/usr/local/bin/qmd"

3. 最小配置:一行启用

在 openclaw 配置(顶层 memory 段):


{
  memory: {
    backend: "qmd",
  },
}

只需这一行,openclaw 会自动:

4. 三种搜索模式

通过 memory.qmd.searchMode 切换,性能/质量取舍:

模式QMD 命令说明
`search`(默认)`qmd search`纯 BM25,最快;不触发向量就绪检查/embed 维护
`vsearch``qmd vsearch`向量语义搜索,需先跑过 `qmd embed`
`query``qmd query`混合 + LLM 重排,质量最高;CPU 上慢;首次会下载约 2 GB GGUF

> openclaw 有兜底:配的模式失败时会自动用 qmd query 重试一次。

> ⚠ 中文/CJK 警示:QMD 的 FTS5 tokenizer 在 src/store.ts:837 硬编码为 porter unicode61不切分中文——一段连续汉字会被当成一个 token,BM25 排序基本失效。中文内容请使用 vsearchquery,并换 Qwen3 Embedding(见 §8)。

5. 完整配置 schema

字段全部来源于 config-utils.ts 中的 MemoryQmdConfig 类型。默认值见 backend-config.ts


{
  memory: {
    backend: "qmd",
    citations: "auto",            // auto | on | off — 片段是否追加 Source: path#line

    qmd: {
      command: "qmd",             // 可写绝对路径,例如 "/usr/local/bin/qmd"
      searchMode: "search",       // search | vsearch | query
      includeDefaultMemory: true, // 自动索引 MEMORY.md + memory/**

      // 额外索引目录
      paths: [
        { name: "docs",  path: "~/notes",     pattern: "**/*.md" },
        { name: "specs", path: "~/work/docs", pattern: "**/*.md" },
      ],

      // 历史会话转录索引(跨会话回忆)
      sessions: {
        enabled: false,
        exportDir: "~/.openclaw/transcripts",
        retentionDays: 90,
      },

      // 增量/重建调度
      update: {
        interval: "5m",            // 周期更新频率
        debounceMs: 15000,
        onBoot: true,              // manager 打开时立即刷新
        startup: "off",            // off | idle | immediate — 网关启动时预热
        startupDelayMs: 120000,
        waitForBootSync: false,
        embedInterval: "60m",
        commandTimeoutMs: 30000,
        updateTimeoutMs: 120000,
        embedTimeoutMs: 120000,
      },

      // 注入到 prompt 的限额
      limits: {
        maxResults: 4,
        maxSnippetChars: 450,
        maxInjectedChars: 2200,
        timeoutMs: 4000,           // 单次搜索超时;CPU 慢时调到 120000
      },

      // 哪些会话能触发 QMD 搜索(同 session.sendPolicy schema)
      scope: {
        default: "deny",
        rules: [
          { action: "allow", match: { chatType: "direct" } },
        ],
      },

      // 可选:把 QMD 以 MCP server 暴露给 agent
      mcporter: {
        enabled: false,
        serverName: "qmd",
        startDaemon: true,
      },
    },
  },
}

6. 多 agent / 跨 agent 共享集合

每个 agent 默认有独立的 collection 命名空间(自动加 - 后缀)。需要让多个 agent 共享同一索引时,用 agent 级配置而不是顶层 memory.qmd.paths


{
  agents: {
    defaults: {
      memorySearch: {
        qmd: {
          extraCollections: [
            { name: "shared-handbook", path: "~/team/handbook", pattern: "**/*.md" },
          ],
        },
      },
    },
    list: [
      {
        id: "alice",
        memorySearch: {
          qmd: {
            extraCollections: [
              { name: "alice-private", path: "~/alice/notes" },
            ],
          },
        },
      },
    ],
  },
}

合并顺序:agents.defaults.memorySearch.qmd.extraCollections → 各 agent 的 extraCollectionsmemory.qmd.paths。同一 path 重复时保留首条。

7. 模型覆盖(不在 openclaw 配置里)

QMD 的 GGUF 模型用 环境变量覆盖,openclaw 透传给子进程。中文/多语料库强烈建议换 embedding 模型:


export QMD_EMBED_MODEL="hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf"
export QMD_RERANK_MODEL="/abs/path/reranker.gguf"
export QMD_GENERATE_MODEL="/abs/path/generator.gguf"

# 换嵌入模型后必须重建索引
qmd embed -f

8. 中文场景:必须走向量

为什么不能用 `search`

QMD 在 qmd/src/store.ts:837 硬编码了 FTS5 tokenizer:


CREATE VIRTUAL TABLE ... USING fts5(filepath, title, body, tokenize='porter unicode61')

而且 QMD 没有 trigram 切换的开关(grep -rn 'trigram\|jieba\|cjk' src/ 全空)。结论:中文内容上 QMD,只能走 vsearchquery

推荐配置:vsearch + Qwen3 Embedding

第 1 步:换 embedding 模型(环境变量,全局生效)

加进 ~/.zshrc 或 gateway 的 systemd/launchd unit:


export QMD_EMBED_MODEL="hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf"

> Qwen3-Embedding-0.6B 是 QMD README 明确推荐的多语言(含 CJK)模型,MTEB 排名靠前;约 600 MB,首次 qmd embed 时自动从 HuggingFace 下载到 ~/.cache/qmd/models/

第 2 步:openclaw 配置切到 `vsearch`


{
  memory: {
    backend: "qmd",
    citations: "auto",
    qmd: {
      searchMode: "vsearch",                // 纯向量;中文最稳的选择
      includeDefaultMemory: true,
      paths: [
        { name: "notes", path: "~/notes", pattern: "**/*.md" },
      ],
      limits: {
        maxResults: 6,
        timeoutMs: 30000,                   // 向量编码 + 检索慢,放宽
      },
      update: {
        interval: "5m",
        embedInterval: "60m",               // 单独的 embed 节奏
        startup: "idle",                    // 启动后空闲时再预热
        startupDelayMs: 60000,
      },
    },
  },
}

需要更高质量召回时,把 searchMode 换成 query——会启用 LLM 重排和查询扩展,但 CPU 机器上单次可能 5–30 秒。

第 3 步:预热索引(手动执行一次)

> 重要:openclaw 的 CLAUDE.md 明确禁止它自动跑 qmd embed。第一次切换 embedding 模型必须手动重建。


# 1. 先确认 collections 已就位
qmd status

# 2. 拉取/更新所有 collection 的文件元数据
qmd update

# 3. 用 Qwen3 重建全量向量(-f 强制重嵌入)
qmd embed -f

# 4. 跑一次中文查询确认链路
qmd vsearch "项目时间线" -n 5

之后 openclaw 启动时会自己按 update.embedInterval 周期增量 embed。

第 4 步:验证


openclaw memory status --deep

输出里应能看到 backend = qmd、searchMode = vsearch、collections 列出来、vector_ready: true

中英混合内容怎么办

英文段落 BM25 仍然好用,混合内容把 searchMode 设为 query


{ memory: { backend: "qmd", qmd: { searchMode: "query" } } }

query 模式同时跑 BM25 + 向量并用 RRF 融合(见 QMD README 的 Hybrid Search Pipeline 图):英文走 BM25 拿精确匹配,中文走向量拿语义召回,最后 LLM 重排。代价是首次需要下额外的 ~640 MB rerank 模型 + ~1.1 GB query expansion 模型。

索引质量进一步优化(可选)

QMD 1.x 之后的 chunking 默认按 markdown 标题/段落切,900 token/块、15% overlap。中文一个 token 通常对应 0.5–1 个汉字,所以一块约 600–900 字,对中文长文档基本够用。代码文件想按函数/类边界切,加:


qmd embed -f --chunk-strategy auto

(仅 .ts/.tsx/.js/.jsx/.py/.go/.rs 生效;中文 markdown 不受影响)

9. 验证与排错


# openclaw 视角检查 backend / collections / 二进制可用性
openclaw memory status --deep

# QMD 自身的索引健康
qmd status

# 确认是否支持多 -c 过滤(影响 openclaw 是否能合并子进程)
qmd --help | grep -i collection

常见问题(汇总自 docs/concepts/memory-qmd.md Troubleshooting):

现象处理
`spawn qmd ENOENT`gateway 的 PATH 与 shell 不同。配 `memory.qmd.command` 写绝对路径,或建 symlink
首次搜索极慢`qmd query` 在下载 ~2 GB GGUF。提前在 shell 里跑一次 `qmd query "test"` 预热
搜索超时调高 `memory.qmd.limits.timeoutMs`(默认 4000,慢机器用 120000)
BM25-only 也试图编译 llama.cpp显式设 `searchMode: "search"`,openclaw 会跳过向量就绪检查
group chat 永远空结果默认 scope 仅 direct + channel,按需放开 group
子进程数量过多升级 QMD 到支持多 `-c` 过滤的版本,openclaw 会自动用单进程多 collection

10. 三段式上手路径

按从轻到重推荐:

1. 试水:只配 memory.backend: "qmd",用 searchMode: "search",体感 BM25 召回。

2. 加额外目录memory.qmd.paths 把笔记/文档/会议纪要纳入索引;按需开 sessions.enabled

3. 上语义/重排:换中文 embedding 模型 → qmd embed -fsearchMode: "query",并放宽 limits.timeoutMs

附录 A:trigram tokenizer 速览

§4 提到 QMD 硬编码 porter unicode61,§8 提到 builtin backend 支持 trigram。这里解释下 trigram 是什么、为什么能救中文 BM25。

定义

trigram = 3-gram = 用 3 个字符的滑动窗口把字符串切成重叠片段,每段当一个索引 token。


"hello"      → hel, ell, llo
"项目时间线" → 项目时, 目时间, 时间线

查询时 query 也走同样规则,最后取 trigram 集合交集做匹配,BM25 在交集大小上排序。

为什么对中文比 `unicode61` 好

维度`unicode61`(QMD 当前)`trigram`
切分依据空白 + 标点固定 3 字符滑窗
中文一段汉字的 token 数1(整段视为一个词)N − 2(N 是字符数)
不同文档间是否共享 token几乎不共享共享大量 trigram
BM25 IDF 是否有效退化为子串包含重新有效
任意子串能否命中仅整段匹配任意 ≥ 3 字符子串

核心:trigram 让"不同文档共享 token"重新成立,BM25 的 TF/IDF 才有意义。

代价

1. 索引膨胀:一篇 1000 字中文文档约产生 998 个 trigram,索引体积通常是源文 3–5×。

2. 没有语义:"汽车" 与 "轿车" 没有共享 trigram,仍互不召回——这是 vsearch/query 的位置。

3. 查询长度门槛:< 3 字符走不了索引,会退化为 LIKE 全扫;避免 1–2 字关键词。

4. 跨语言混搭:英文上 trigram 不如 porter unicode61 精准,更适合 CJK / 子串 / 代码片段。

一行验证

需要 SQLite ≥ 3.34(macOS Homebrew 版都满足):


sqlite3 :memory: <<'SQL'
CREATE VIRTUAL TABLE t USING fts5(body, tokenize='trigram');
INSERT INTO t VALUES('公司项目时间线安排'), ('项目周报');
SELECT body FROM t WHERE t MATCH '项目时间';
SQL

应返回 公司项目时间线安排——trigram 项目时目时间 同时命中。

在选型矩阵里的位置


中文 BM25
├─ unicode61(QMD 当前硬编码)            → 基本不可用
├─ trigram(openclaw builtin 可切换)     → 可用,索引大、无语义
└─ 向量 embedding(QMD vsearch/query)    → 最准、有语义、慢且需模型

trigram 是"穷人的中文分词"——无分词器、无词典、无 GPU,靠暴力滑窗换可用性。解不了同义词,但能让 BM25 在中文上重新工作。

> 跟踪项QMD PR #623(kechol 提,2026-05-04 OPEN)正是给 store.ts:837QMD_FTS_TOKENIZER 环境变量并把 trigram 加入白名单。合并后,本文档 §4 的 CJK 警示和 §8 的"必须走向量"结论都会松动——search 模式可重新进入中文场景的可选集。

附录 B:trigram vs 词级分词

附录 A 把 trigram 和 unicode61 比,是想说服你"中文 BM25 起码要 trigram 起步"。但很多人会问:为什么不直接上 jieba 这种真正的中文分词?这里把 trigram 和分词路线放在一起对比。

核心区别

维度对比

维度trigram词级分词(jieba 类)
是否需要语言知识是(词典 + 统计模型)
token 粒度3 字符滑窗(重叠)中文词,平均 2 字
N 字汉字产生的 token 数N − 2≈ N / 2
索引膨胀(vs 源文 byte)~3–5×~1.5–2×
召回完整性高(任意 ≥3 字符子串)取决于分词质量
精度低("机器学习" 与 "机器人" 共享 `机器`)高(边界对了就不会乱召回)
短查询(1–2 字)走索引❌(退化 LIKE 全扫)
未登录词 / 新词✅ 天然支持❌ 词典外切错
同义词 / 近义词❌(BM25 范畴外,靠 query expansion 或向量)
部署复杂度零依赖(SQLite 自带)需 SQLite 扩展或预分词 pipeline
词典维护持续维护(领域词、人名、产品名)
中英混合二者一视同仁,英文上不如 porter英文需另走 unicode61 / porter
查询时延慢一档(token 多)

一个能看出区别的例子

文档:机器学习正在改变世界

查询trigramjieba 默认(精确模式)
`机器学习`trigram `机器学` + `器学习` 命中token `机器学习` 命中
`机器人`文档没有 `机器人` trigram → ❌ 不误召词不存在 → ❌
`学习正在``学习正` + `习正在` 命中`[学习, 正在]` 短语查询命中
`机器`< 3 字符走 LIKE,仍可命中被吞进 `机器学习` → ❌ **召回不到**
`深度学习`共享 `度学习` 1 个 trigram → 弱命中(**误召回**)词典里独立词,文档没有 → ❌ 不误召

真实场景下方向相反:trigram 偏召回,分词偏精度。jieba 的"最大粒度"特性反而让 2 字子词搜不到(除非用 cut_for_search 模式)。

QMD 当前状态下各路线的成本

路线实现成本状态
trigramenv var 切换,零改造[PR #623](https://github.com/tobi/qmd/pull/623) OPEN
分词 + SQLite FTS5 自定义 tokenizer加载 C 扩展(如 `wangfenjin/simple-tokenizer`、`signalapp/Signal-FTS5-Extension`),改 QMD 的 `Database` 初始化 + `getFtsTokenizer` 放开扩展名校验无人做
预分词 pipeline在 QMD indexer 前注入 jieba,token 间用空格连接,仍用 `unicode61`;查询端也要同样分词无 hook,需 fork
向量 (vsearch)换 Qwen3-Embedding,env var + `qmd embed -f`现成可用

结论:QMD 上没有"开关式"分词选项。想要词级分词,要么写 SQLite 扩展,要么 fork QMD 改 indexer。trigram 路线(PR #623)是几乎零成本的折中。

决策建议


中文检索目标
├─ 只要搜得到(召回 > 精度)          → trigram(PR #623 合并后开 env var)
├─ 精确语义匹配 + 同义词              → vsearch + Qwen3-Embedding(QMD 现成)
├─ 精确词边界 + 零误召回              → 分词,但 QMD 上要自写扩展;
│                                       此时不如换 Elasticsearch + IK / Meilisearch
└─ 召回与精度都要                      → query 模式 = trigram BM25 ⊕ 向量 ⊕ rerank
                                         (PR #623 合并后才真正成立)

实战上最常见的最佳组合是 trigram + 向量混合

这正是 QMD query 模式的设计意图。但因为 BM25 lane 当前对中文失效,混合检索还没真正发挥——PR #623 合并后这套架构才完整。真要给中文召回质量做一次跃迁,等这个 PR 比换 embedding 模型更值得跟进。

完整字段权威来源仍以 packages/memory-host-sdk/src/host/config-utils.tsbackend-config.ts 为准;行为权威来源以 docs/concepts/memory-qmd.mddocs/reference/memory-config.md 为准。

评分

维度评分说明
实用性⭐⭐⭐⭐⭐操作指令从配到排错都覆盖了
深度⭐⭐⭐⭐⭐源码级分析 + trigram 附录深入
中文适用性⭐⭐⭐⭐⭐专门花了 §8 + 两个附录讲中文
独特性⭐⭐⭐⭐同类配置指南很少见
**综合****4.8/5**这份 OpenClaw + QMD 的记忆配置指南是目前最完整的