Files
memory_research/OpenClaw对话和记忆优化初步方案.md

16 KiB
Raw Blame History

OpenClaw 对话和记忆优化初步方案

研究日期2026-02-17
研究者Ami + 阿米狗
状态:已实施并上线


背景与目标

OpenClaw 默认配置下存在两个核心痛点:

  1. 对话 context 超限会直接中断会话,体验割裂
  2. 记忆搜索被禁用(需要 OpenAI/Gemini/Voyage API key成本高

本方案目标:在不依赖任何外部 API key 的前提下,实现:

  • 对话 context 的主动管理(不被动等爆)
  • 本地向量语义搜索(完全离线)
  • 跨会话记忆自动恢复

核心约束

我们假设暂时解决不了对话 context 上限这一概念和顶层限制200k token 硬上限由模型决定,无法改变)。 因此,所有优化都是在这个硬上限内做最大化利用。


三步方案

Step 1 — Token 优化 + 自动压缩

问题context 默认无上限地增长,直到 API 报错才触发压缩,体验差。

解决

配置项 作用
contextTokens 120000 软上限,到达后触发剪枝(低于模型 200k 硬上限,留出安全边际)
contextPruning.mode cache-ttl 在 Anthropic 缓存 TTL 到期前删除老 tool output减少 cacheWrite 费用
contextPruning.keepLastAssistants 3 保留最近 3 条 assistant 消息,删除更早的
compaction.mode safeguard 确保 Pi runtime 的 compaction 有足够预留空间
compaction.reserveTokensFloor 20000 compaction 预留底线 20k token防止压缩时空间不足

原理:把原来的"被动爆仓"改成"主动管理",在 context 超限前就开始清理。


Step 2 — 新会话自动恢复历史QMD 本地向量搜索)

问题memory_search 工具默认需要 OpenAI/Gemini/Voyage embedding API没有 key 则完全禁用。

解决:安装并启用 QMDQuick Markdown / 本地向量搜索引擎)

QMD 是什么

  • GitHub: tobi/qmd
  • OpenClaw 官方支持的实验性本地搜索后端
  • 技术栈BM25 + 向量node-llama-cpp+ reranking
  • 完全本地运行GGUF 模型首次自动下载,无 API key 需求

安装步骤

# 1. 安装 BunQMD 运行时)
curl -fsSL https://bun.sh/install | bash

# 2. 安装 QMD
bun install -g https://github.com/tobi/qmd

# 3. 解锁 postinstallbun 默认阻止)
cd ~/.bun/install/global && bun pm trust --all

# 4. 补充缺失的 @types/node 并 build
cd ~/.bun/install/global/node_modules/@tobilu/qmd
bun add -d @types/node && bun run build

# 5. 验证
qmd --version  # → qmd 1.0.6

注意bun install -g 安装的 qmd 包没有预编译 dist需要手动 build TypeScript 源码。这是一个安装坑,已记录。

OpenClaw 配置

{
  "memory": {
    "backend": "qmd",
    "citations": "auto",
    "qmd": {
      "command": "/Users/mini/.bun/bin/qmd",
      "includeDefaultMemory": true,
      "sessions": {
        "enabled": true,
        "retentionDays": 30
      },
      "update": {
        "interval": "5m",
        "debounceMs": 15000
      },
      "limits": {
        "maxResults": 6,
        "timeoutMs": 4000
      },
      "scope": {
        "default": "deny",
        "rules": [{ "action": "allow", "match": { "chatType": "direct" } }]
      }
    }
  }
}

关键配置说明

  • backend: "qmd" — 切换为本地引擎(替代内置 SQLite 索引)
  • sessions.enabled: true — 自动导出 session 对话记录到 QMD 索引(实现跨会话搜索)
  • retentionDays: 30 — 保留 30 天历史
  • scope.rules — 仅限直接私聊可搜索(不在群组里暴露私人记忆)
  • citations: "auto" — 搜索结果自动附带来源文件路径

效果:新会话开始时,memory_search 可以检索过去 30 天的对话历史摘要和记忆文件,自动恢复上下文。


Step 3 — Context 快满时自动提醒

问题:用户不知道 context 什么时候快满,只能被动等待中断。

解决:在压缩前的"软阈值"触发主动通知

工作流程

context 使用量
    ↓
[120k 软上限] → contextPruning 开始清理老消息
    ↓
[还剩 ~5k token] → memoryFlush 触发(比 compaction 更早)
    ↓
    ├── 1. 把重要上下文写入 memory/YYYY-MM-DD.md
    └── 2. 发 Telegram 通知:⚠️ 对话快满了,建议发 /new
    ↓
[用户发 /new 开启新会话]
    ↓
[新会话] → memory_search 自动检索历史 → 无缝续接

配置

{
  "agents": {
    "defaults": {
      "compaction": {
        "memoryFlush": {
          "enabled": true,
          "softThresholdTokens": 5000,
          "prompt": "Context is nearing the compaction limit. Do TWO things:\n1. Write important context/decisions from this conversation to memory/YYYY-MM-DD.md (today's date).\n2. Use the message tool to send a Telegram notification to the user (target: 5588544200, channel: telegram) saying: '⚠️ 对话快满了,建议发 /new 开启新会话,我会自动从历史记忆中恢复上下文。'\nReply with NO_REPLY after completing both steps.",
          "systemPrompt": "Pre-compaction housekeeping: persist memories and notify user. Silent turn."
        }
      }
    }
  }
}

原理

  • softThresholdTokens: 5000 — 在 Pi runtime 触发 compaction 之前 5000 token 就介入
  • prompt 中的双任务:写记忆 + 发通知
  • NO_REPLY — 对话中不显示任何内容(静默操作),但 tools 仍然执行Telegram 消息照常发)

整体架构图

用户对话
  │
  ▼
[OpenClaw Gateway]
  │
  ├── contextTokens: 120k → 软上限保护
  ├── contextPruning: cache-ttl → 清理老 tool output
  │
  ├── [context 剩 5k] → memoryFlush 触发
  │     ├── 写 memory/YYYY-MM-DD.md
  │     └── 发 Telegram 通知 → 用户
  │
  ├── compaction: safeguard → 自动压缩摘要(最后防线)
  │
  └── [用户 /new 开新会话]
        │
        ▼
  [新会话启动 — AGENTS.md 规则]
        ├── 自动读取 MEMORY.md系统注入
        ├── 自动读取 memory/今日+昨日.md系统注入
        └── 自动调用 memory_search("最近对话 上下文")
              │
              ▼
        QMD 本地向量索引(上限 5k tokens
          ├── MEMORY.md长期记忆
          ├── memory/*.md日志
          └── sessions/*.jsonl30天对话历史
              │
              ▼
        自动恢复上下文,无缝续接

Step 4 — 新会话自动触发历史检索AGENTS.md 规则)

问题QMD 搜索是"按需"的,新会话开始时需要主动触发,否则历史上下文不会自动加载。

解决:在 AGENTS.md 的"每次会话"规则中加入自动搜索指令。

触发机制说明

层级 内容 加载方式
MEMORY.md 长期记忆(核心事实、偏好) 系统自动注入 context
memory/YYYY-MM-DD.md 今日 + 昨日日志 系统自动注入 context
QMD session 历史 近 30 天对话记录 需调用 memory_search 触发

AGENTS.md 新增规则

## Every Session

Before doing anything else:
1. Read SOUL.md — this is who you are
2. Read USER.md — this is who you're helping
3. Read memory/YYYY-MM-DD.md (today + yesterday) for recent context
4. **If in MAIN SESSION**: Also read MEMORY.md
5. **If in MAIN SESSION**: Run memory_search("最近对话 上下文") to retrieve
   relevant history from QMD — restore any ongoing topics or tasks automatically

Token 消耗评估

场景 消耗
QMD 本地搜索计算 0纯本地
搜索结果注入 context 1k5k tokens
上限(maxInjectedChars: 20000 ~5000 tokens 硬上限

maxInjectedChars 的作用:无论搜到多少内容,最终注入 context 的字符数不超过 20000约 5000 tokens防止历史检索本身消耗过多 context 空间。

QMD limits 完整配置

{
  "memory": {
    "qmd": {
      "limits": {
        "maxResults": 6,
        "maxSnippetChars": 700,
        "maxInjectedChars": 20000,
        "timeoutMs": 4000
      }
    }
  }
}

效果:开新会话后无需任何提示,我会自动检索近期历史并将相关上下文带入当前会话,实现无缝续接。


当前局限性

  1. QMD 首次搜索较慢:需要自动下载 GGUF 模型reranker + query expansion约几百 MB
  2. session 索引延迟:对话结束后才导出,不是实时的
  3. 根本上限无法突破200k token 硬上限由 Anthropic 决定,所有优化只是更好地利用这 200k
  4. QMD build 依赖:安装时需要手动 build TypeScriptbun 全局安装的包缺少 dist 目录,已知坑)

文件位置

文件 说明
~/.openclaw/openclaw.json 所有配置的实际存储位置
/Users/mini/clawd/MEMORY.md 长期记忆(每次主会话加载)
/Users/mini/clawd/memory/YYYY-MM-DD.md 每日日志
~/.openclaw/agents/main/qmd/ QMD 索引数据库 + 缓存
~/.openclaw/agents/main/sessions/*.jsonl 对话历史(被 QMD 索引)

后续可探索方向

  • 3层记忆架构@Ktaohzk 方案Fact/Belief 分层 + 衰减评分,进一步减少 token 占用
  • session memory search 实验性功能memorySearch.experimental.sessionMemory: true(内置 SQLite 版的 session 索引,与 QMD sessions 二选一)
  • 混合搜索调优:调整 vectorWeight / textWeight 比例优化检索精度
  • embedding 缓存memorySearch.cache.enabled: true 避免重复 embedding

本文档由 Ami + 阿米狗共同研究整理2026-02-17v1.1 更新于同日v1.2 QMD 修复记录追加于同日


附录QMD 调试修复全历程v1.2

记录于 2026-02-17修复过程约 1 小时

背景

按照本文 Step 2 配置完 QMD 并重启 gateway 后,memory_search 工具仍然返回:

{
  "results": [],
  "disabled": true,
  "error": "No API key found for provider \"openai\"..."
}

QMD 没有接管搜索,系统仍然在走旧的 embedding 路径。


问题一:memory_search 工具被标记为 disabled

根因OpenClaw 的工具启用判断与后端配置是两条独立链路:

  • memory.backend = "qmd" 只控制实际搜索走哪个引擎
  • 工具本身的启用检查(createMemorySearchTool)还是会校验 embedding API key

两者互不感知。如果没有 OpenAI/Gemini/Voyage API keymemory_search 工具直接在注册阶段就被禁用,根本不会尝试 QMD。

验证方法:在 wrapper script 加日志 → 调用 memory_search → 日志为空,说明 wrapper 从未被执行。

修法:额外配置本地 embedding 模型,让工具判断认为"搜索可用"

{
  "agents": {
    "defaults": {
      "memorySearch": {
        "provider": "local",
        "local": {
          "modelPath": "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf"
        },
        "fallback": "none"
      }
    }
  }
}

注意:local provider 的配置只用于"激活"工具,实际搜索仍由 QMD 接管(memory.backend = "qmd" 优先级更高)。


问题二:qmd query --json 崩溃SIGABRT

根因OpenClaw 内部调用的是 qmd query --json(带 reranking + 向量 + 查询扩展)。但 session 文件是完整对话记录,单个 chunk 长度超过 reranker 的上下文窗口2062 tokens导致

  1. JavaScript 层:LlamaRankingContext 抛出错误
  2. Metal GPU 层:GGML_ASSERT([rsets->data count] == 0) failed → SIGABRT 崩溃

错误信息:

Error: The input lengths of some of the given documents exceed the context size.
Try to increase the context size to at least 2062 or use another model that supports longer contexts.

之后 QMD 进程崩溃OpenClaw 检测到 QMD 失败 → 回退到内置 SQLite 引擎 → 内置引擎因缺少 API key → disabled: true

修法:创建 wrapper scriptqmd query(含 reranking替换为 qmd vsearch(向量搜索,不含 reranking

# /Users/mini/clawd/scripts/qmd-wrapper.sh
#!/bin/bash
# QMD wrapper: replaces 'qmd query --json' with 'qmd vsearch --json'
# Workaround for reranker crash on long session chunks (>2062 tokens)

QMD_BIN="/Users/mini/.bun/bin/qmd"
ARGS=()

for arg in "$@"; do
  if [ "$arg" = "query" ]; then
    ARGS+=("vsearch")
  else
    ARGS+=("$arg")
  fi
done

exec "$QMD_BIN" "${ARGS[@]}"

然后在 OpenClaw 配置中将 memory.qmd.command 指向 wrapper

{
  "memory": {
    "qmd": {
      "command": "/Users/mini/clawd/scripts/qmd-wrapper.sh"
    }
  }
}

qmd vsearch = 向量相似度搜索 + 查询扩展,无 reranking输出相同 JSON 格式OpenClaw 可直接消费。


问题三GGUF 模型重复下载 + timeout

根因

  • GGUF 模型已下载到系统默认路径 ~/.cache/qmd/models/
  • OpenClaw 以独立 XDG 环境运行 QMD~/.openclaw/agents/main/qmd/xdg-cache/
  • 两个路径不共享,每次 OpenClaw 调用 QMD 都会尝试重新下载模型
  • 默认 timeoutMs: 40004秒下载没完成就超时

修法

  1. 软链模型文件到 OpenClaw 的 XDG cache 目录:
mkdir -p ~/.openclaw/agents/main/qmd/xdg-cache/qmd/models
ln -sf ~/.cache/qmd/models/hf_ggml-org_embeddinggemma-300M-Q8_0.gguf \
    ~/.openclaw/agents/main/qmd/xdg-cache/qmd/models/
ln -sf ~/.cache/qmd/models/hf_ggml-org_qwen3-reranker-0.6b-q8_0.gguf \
    ~/.openclaw/agents/main/qmd/xdg-cache/qmd/models/
ln -sf ~/.cache/qmd/models/hf_tobil_qmd-query-expansion-1.7B-q4_k_m.gguf \
    ~/.openclaw/agents/main/qmd/xdg-cache/qmd/models/
  1. 把超时时间调大:
{
  "memory": {
    "qmd": {
      "limits": {
        "timeoutMs": 30000
      }
    }
  }
}

最终生效配置(完整版)

{
  "memory": {
    "backend": "qmd",
    "citations": "auto",
    "qmd": {
      "command": "/Users/mini/clawd/scripts/qmd-wrapper.sh",
      "includeDefaultMemory": true,
      "sessions": {
        "enabled": true,
        "retentionDays": 30
      },
      "update": {
        "interval": "5m",
        "debounceMs": 15000
      },
      "limits": {
        "maxResults": 6,
        "maxSnippetChars": 700,
        "maxInjectedChars": 20000,
        "timeoutMs": 30000
      },
      "scope": {
        "default": "deny",
        "rules": [{ "action": "allow", "match": { "chatType": "direct" } }]
      }
    }
  },
  "agents": {
    "defaults": {
      "memorySearch": {
        "provider": "local",
        "local": {
          "modelPath": "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf"
        },
        "fallback": "none"
      }
    }
  }
}

验证结果

修复后 memory_search 返回:

{
  "results": [...],
  "provider": "qmd",
  "model": "qmd",
  "citations": "auto"
}

provider: "qmd" 确认 QMD 已接管搜索,不再走旧的 embedding 路径。


已知遗留问题

问题 说明
vsearch 精确短语召回弱于 BM25 用向量搜索替代了全文搜索,对精确词组召回率稍低
qmd queryreranking崩溃未根治 根本原因是 session chunk 过长超过 reranker 上下文,暂无优雅解法
local embedding 模型双加载 memorySearch.provider = "local" 让 OpenClaw 也加载了 embedding 模型,与 QMD 冗余,未来版本可能会有更干净的 enabled: true 开关

经验教训

  1. memory.backend = "qmd" ≠ 工具启用:两个配置项独立,必须同时配置 memorySearch.provider 才能让工具真正可用。
  2. QMD 的两个 XDG 环境完全隔离CLI 工具用 ~/.cache/qmd/OpenClaw 用 ~/.openclaw/agents/main/qmd/xdg-cache/,模型需要手动同步。
  3. qmd query 不适合长文档session 对话记录会产生超大 chunk直接用 reranker 会崩溃,qmd vsearch 是更安全的替代。
  4. OpenClaw 的降级机制会掩盖真实错误QMD 崩溃 → 回退内置 SQLite → 内置失败 → 显示 disabled: true,看起来像是"没配置",实际是 QMD 崩了。