OpenClaw 活跃记忆与上下文引擎:每一轮到底往上下文里塞了什么
配好了记忆插件、embedding 也跑通了,结果还是这样:问「我上次说过喜欢什么口味的鸡翅」,有时候答得出来,有时候一脸茫然。日志里也看不出报错。
这类问题多半不在「记忆库里有没有存」,而在「这一轮到底把什么塞进模型上下文了」。OpenClaw 把这件事拆成了两个独立的东西:**上下文引擎(context engine)**负责每次模型调用时装配哪些消息、老历史怎么摘要;**活跃记忆(active memory)**负责在回复之前要不要额外跑一次深度回忆,把一小段隐藏上下文注进去。两者归属不同的插件槽位,配置项也各管各的。
如果你想先看记忆的整体分层(内置 / 搜索 / 外接三条路各是什么),可以看 OpenClaw 记忆架构。这篇只盯「每一轮塞什么」这一层。
先把两个概念分开:一个负责装配,一个负责补料
上下文引擎控制的是:这次跑模型,哪些消息进去、按什么顺序、超预算了怎么压缩、子代理边界上下文怎么传。OpenClaw 自带一个 legacy 引擎并且默认就用它,选别的引擎要显式装插件、显式在槽位里选。
活跃记忆是另一回事。它是一条「深度召回车道」,只对符合条件的会话生效。默认的 escalate 模式下,它只在两个条件同时成立时才启动那个阻塞式的召回子代理:这条消息在问过去的事,并且确定性记忆车道没有命中强的可信触发。官方文档给的理由是,扁平检索对直接的事实匹配最强、对时间线和跨会话的问题偏弱,文档引用了 LongMemEval 来说明这个差距,又引用了 PrefEval 来说明偏好类提醒的价值——把阻塞的那次模型调用花在真正难召回的形态上。
| 维度 | 上下文引擎 | 活跃记忆 |
|---|---|---|
| 配置位置 | plugins.slots.contextEngine | plugins.entries.active-memory |
| 默认值 | legacy(内置) | 插件,需显式开启;mode 默认 escalate |
| 作用时机 | 每次模型调用都参与 | 只在符合条件的交互式持久会话,且触发条件成立时 |
| 失败后果 | 引擎被隔离,降级回 legacy,回复继续 | 跳过这轮召回,主回复照常,不带召回上下文 |
上下文引擎的四个生命周期点
每次 OpenClaw 要跑模型提示词,引擎会在四个点被调用:
- Ingest:新消息加入会话时调用,引擎可以自己存或建索引;
- Assemble:每次模型运行前调用,返回一组有序消息,外加可选的
systemPromptAddition,要求装进 token 预算里; - Compact:上下文窗口满了、或者用户执行
/compact时调用,把老历史摘要掉腾空间; - After turn:一轮跑完之后调用,可以持久化状态、触发后台压缩、更新索引。
另有一个可选的 maintain() 做转录维护,通过 runtimeContext.rewriteTranscriptEntries() 安全重写;把 info.turnMaintenanceMode 设成 "background" 就变成延后执行,不阻塞回复。子代理边界上还有两个可选钩子:prepareSubagentSpawn(子会话启动前准备共享上下文状态,能拿到父子 session key、contextMode 是 isolated 还是 fork)和 onSubagentEnded(子代理结束或被清扫时收尾)。
默认的 legacy 引擎在这四点上很克制:ingest 是空操作(会话管理器直接负责消息持久化)、assemble 直接透传(走运行时原有的 sanitize → validate → limit 流水线)、compact 交给内置的摘要式压缩、after turn 空操作。它既不注册工具,也不提供 systemPromptAddition。所以在默认配置下,「装配」这一层其实是没有额外逻辑的——这也是很多人以为「记忆没生效」的起点。压缩本身的行为可以参考 上下文压缩与会话修剪。
想确认当前用的是哪个引擎,官方给了两条路:
openclaw doctor
# 或者直接看配置:
cat ~/.openclaw/openclaw.json | jq '.plugins.slots.contextEngine'
换插件引擎的写法是装完之后同时改槽位和 entries,装完配完要重启网关:
{
plugins: {
slots: {
contextEngine: "lossless-claw",
},
entries: {
"lossless-claw": {
enabled: true,
},
},
},
}
有个细节值得记一下:ownsCompaction: true 的引擎会让 OpenClaw 关掉内置的运行内自动压缩和通用的 pre-prompt 溢出预检,压缩全归引擎自己管;而 ownsCompaction: false 并不等于自动回退到 legacy 的压缩路径——文档专门用警告框强调了这点。非拥有模式的引擎如果写了个空的 compact(),等于把 /compact 和溢出恢复这条路一起废了,正确做法是调用 SDK 里的 delegateCompactionToRuntime(...) 委派回运行时。
还有一层保护:非 legacy 引擎缺失、契约校验失败、工厂创建抛错或者生命周期方法抛错,OpenClaw 会把这个引擎在当前网关进程内隔离掉,上下文引擎的活降级给内置 legacy,同时把失败操作记进日志。Agent 不会因此哑掉,但你还是得去修插件。插件安装与槽位机制见 OpenClaw 插件体系。
活跃记忆什么时候才真的会跑
这是最容易踩空的地方。深度召回车道有两条瞄准路径:一是 memory.search.rememberAcrossConversations 生效的 agent(只覆盖私聊直连或持久的显式 UI 会话),二是 plugins.entries.active-memory.config.agents 里列出的 agent ID(受插件的会话类型和会话 ID 控制约束)。两条路都要求插件已启用,且当前是符合条件的交互式持久会话。任一条件不满足,这一轮就不跑,主回复不受影响。
哪些界面会跑、哪些不跑,文档列得很清楚:
| 场景 | 跑活跃记忆吗 |
|---|---|
| 控制台 UI / 网页聊天的持久会话 | 会,只要任一条激活路径瞄准了该 agent |
| 同一持久聊天路径上的其它交互式渠道会话 | 会,只要任一条激活路径允许该会话 |
| 无头一次性运行(headless one-shot) | 不会 |
| heartbeat / 后台运行 | 不会 |
通用的内部 agent-command 路径 | 不会 |
| 子代理 / 内部辅助执行 | 不会 |
所以拿一条 openclaw run 式的一次性命令去测活跃记忆,测不出来是正常的。会话类型这块还有 config.allowedChatTypes,默认只有 ["direct"],group、channel、explicit(门户式的不透明 session id,例如 agent:main:explicit:portal-123)都要显式加进去。想再收窄可以用 allowedChatIds 和 deniedChatIds,注意 allowlist 一旦非空就会同时收窄所有已允许的会话类型(包括私聊),而且解析不出会话 ID 时是跳过这一轮而不是猜。会话与 session key 的概念见 OpenClaw 会话模型。
mode 有三个值:escalate(默认,只在有召回意图且第一条车道没强命中时跑)、always(每个符合条件的目标轮次都跑)、off(停掉深度召回但不卸载插件,确定性可信触发车道仍然可用)。临时排查还可以在当前会话里 /active-memory off 暂停,加 --global 是全局形式,需要 owner 或 operator.admin 权限。
跨会话回忆的边界是硬的
rememberAcrossConversations 打开后,OpenClaw 会索引该 agent 的会话转录,在符合条件的私聊回复前跑一次检索,可以读同一个 agent 其它私聊会话里的相关片段,但会排除当前正在回答的那个会话。边界文档写死了四条:私聊直连与持久显式 UI 会话之间可以互相召回;群和频道既不是召回来源也不是召回目的地;另一个 agent 的转录永远不合格;元数据不足的未知或归档转录会被拒绝。
它也不会合并转录、不改 session key 或投递路由、不放宽 tools.sessions.visibility、不给更大的 sessions_* 工具权限。个人安装场景下这个开关默认是开的,前提是全局 session.dmScope 未设置或为 "main",且没有绑定覆盖它;一旦配了 DM 隔离就默认关闭,显式写 true / false 永远优先。另外只有内置记忆提供方支持这条受保护的转录召回路径,别的记忆提供方保留自己的召回行为但不会自动拿到私有转录授权,openclaw doctor 会报出不支持的提供方或缺失的 memory_search 工具。
调参:给子代理看多少、多严、多久
三个旋钮基本决定了体验。queryMode 控制阻塞子代理能看到多少对话:message 只发最新一条用户消息,最快、最偏向稳定偏好召回,timeoutMs 从 3000-5000 ms 起步;recent 是最新消息加一小段近期对话尾巴,建议 15000 ms 起步;full 把整段对话都发过去,适合召回质量比延迟重要、或者关键设定在很靠前的位置,15000 ms 起步或更高。recent 模式下还能细调 recentUserTurns(0-4,默认 2)、recentAssistantTurns(0-3,默认 1)以及每轮的字符上限。
promptStyle 控制子代理返回记忆的积极程度:balanced 是 recent 模式的默认,strict 最保守、周边上下文渗透最少,contextual 最看重连续性,recall-heavy 在较软但仍可信的匹配上也会给记忆,precision-heavy 除非匹配明显否则强烈倾向返回 NONE,preference-only 专门针对偏好、习惯、日常这类反复出现的个人事实。不显式设置时的映射是 message → strict、recent → balanced、full → contextual。
模型解析是一条链:显式的 config.model → 当前会话模型 → agent 主模型 → 可选的 config.modelFallback。整条链都解析不出来就跳过这轮召回。文档强调 modelFallback 严格只是链条最后一步,不是运行时故障转移——已解析的模型报错时它不会顶上;config.modelFallbackPolicy 是为老配置保留的废弃字段,不再影响运行行为。
一份官方给的进阶安全默认值:
{
plugins: {
entries: {
"active-memory": {
enabled: true,
config: {
enabled: true,
mode: "escalate",
agents: ["main"],
allowedChatTypes: ["direct"],
modelFallback: "google/gemini-3-flash",
queryMode: "recent",
promptStyle: "balanced",
timeoutMs: 15000,
maxSummaryChars: 220,
persistTranscripts: false,
logging: true,
},
},
},
},
}
plugins.entries.*(含 active-memory.config)属于免重启配置类别,网关会自动重载插件运行时。想强制重启也行:openclaw gateway restart。
想看它到底跑没跑,把会话开关打开:/verbose on 会在正常回复之后追加一行 🧩 Active Memory: status=ok elapsed=842ms query=recent summary=34 chars 之类的状态,/trace on 追加一行调试摘要。两者都是作为后续消息发的,不会在回复前先闪一个气泡。/trace raw 还能看到注入的原始隐藏前缀,包在 <active_memory_plugin> 标签里。
什么时候不该用,以及还有哪些坑没解决
活跃记忆是对话增强功能,不是平台级的推理功能。文档明说它不适合自动化、内部工作者、一次性 API 任务,或者任何「隐藏个性化会让人意外」的地方。它适合的场景是持久且面向用户的会话,agent 确实有值得搜的长期记忆,并且连续性和个性化比提示词的确定性更重要。
几个已知的粗糙面:
- 冷启动超时。截至 2026-08-17 的文档写明,v2026.5.2 之前插件会在冷启动时悄悄给
timeoutMs加 30000 ms 宽限;v2026.5.2 把这段宽限挪到了显式的setupGraceTimeoutMs,默认 0。从 v2026.4.x 升上来、并且当初按隐式宽限调过timeoutMs的人,需要手动设setupGraceTimeoutMs: 30000才能恢复原来的有效预算,否则网关重启后第一次召回容易status=timeout且输出为空。最坏阻塞时间是timeoutMs + setupGraceTimeoutMs + 3000ms。 - 换了记忆提供方,工具名对不上。默认
toolsAllow在内置记忆下是["memory_search", "memory_get"],LanceDB 槽位下是["memory_recall"]。用别的记忆插件就得自己写对工具名,写错了这一轮直接跳过召回。而且toolsAllow只接受具体的记忆工具名,通配符、group:*以及read/exec/message/web_search这类核心工具会在子代理启动前被静默过滤掉。 - 多数「召回不准」其实不是活跃记忆的锅。文档直说进阶活跃记忆是骑在记忆插件自己的召回流水线上的,多数意外来自 embedding 提供方。
memory.search.provider未设置时用 OpenAI embeddings;配置的提供方跑不起来时memory_search可能退化成纯词法检索,而且提供方选定之后的运行期故障不会自动回退。 - 转录导出会攒得很快。
persistTranscripts: true能把阻塞子代理的转录导成 JSONL 存到 agent 会话目录下,但这些产物里包含隐藏提示词上下文和被召回的记忆内容,full模式还会重复大量对话,busy 会话上要慎开。
排查顺序照着文档来就行:先确认插件 enabled,再确认瞄准路径(跨会话开关或 config.agents),确认测的是合格的交互式持久会话,记住群和频道永远不走跨会话转录召回,然后 config.logging: true 看网关日志,最后用 openclaw status --deep 验记忆搜索本身和索引健康度。命中太噪就收紧 maxSummaryChars,太慢就降 queryMode、降 timeoutMs、砍近期轮数和每轮字符上限。
延伸阅读
- 从头读起:OpenClaw 架构总览:网关、Agent、渠道、节点、插件五层各管什么,出问题该往哪查
- 本专题共 40 篇,完整分组目录见专题页
- OpenClaw 上下文压缩与会话修剪:compaction 和 pruning 到底谁在动你的历史
- OpenClaw 多 Agent 与专家分线:一个网关跑几个人格,消息怎么路由到对的那个
本文依据 OpenClaw 官方仓库(github.com/openclaw/openclaw)docs/ 下的官方文档整理,核对日 2026-08-17。
我们没有安装或运行过 OpenClaw,因此不涉及界面外观、操作手感与实测耗时的任何描述;
文中的默认值、命令与配置项均为文档口径,不构成对实际运行结果的保证。
该项目迭代很快,请以仓库最新内容为准。接入即时通讯平台前,请自行确认所在平台的规则与合规要求。