OpenClaw 把上下文管理从核心代码里抽出来,做成了排他的插件槽位(slot),目的是让上下文策略可替换、可组合、可独立演进,不必改一行核心代码。同一个 ContextEngine 接口,能跑默认的滑动窗口压缩,也能跑基于 DAG 的无损保留、RAG 检索组装、冷热分层存储和任务感知裁剪。下文拆解这层抽象的动机、接口契约和可落地的策略。
一、为什么要抽这层
OpenClaw 2026.3.7 之前,上下文逻辑写死在核心里,只有滑动窗口压缩一条路:对话过长就摘要旧消息,给新消息让路。这条路能跑,但三个问题躲不开。
| 痛点 | 具体表现 | 后果 |
|---|---|---|
| 压缩必丢信息 | 50 轮前定义的函数被摘要抹掉 | 写代码场景引用失效 |
| 关了就忘 | 会话结束上下文清零 | 没有跨会话记忆 |
| 换策略无门 | RAG、对话分支、自管 token 预算都无扩展口 | 只能 fork 或 monkey-patch |
社区被迫用 monkey-patch 内部模块、fork 核心代码、外面套一层编排来绕,都不是长久之计。把上下文生命周期抽成接口、开放为槽位,是为了把这层从“碰不得的黑盒”变成“可插拔的能力”。
二、接口契约与槽位机制
2.1 槽位不是钩子
ContextEngine 在插件体系里是槽位(slot),不是钩子(hook)。区别决定行为:钩子叠加,十个插件能同时监听 onMessage;槽位排他,同一时间只有一个 ContextEngine 在跑。启动时 OpenClaw 读 plugins.slots.contextEngine 配置值,找到对应工厂方法实例化,找不到直接报错退出,不偷偷回退默认引擎。
{
plugins: {
slots: {
// 选当前上下文引擎,默认 legacy
// 设为插件 id 即启用插件引擎
contextEngine: "legacy"
}
}
}
2.2 七个钩子管住上下文一生
ContextEngine 接口给出七个钩子,从引擎启动到子 agent 收工,每个关键节点都能插手。
bootstrap:引擎初始化跑一次,连数据库、建图结构、加载上次状态;ingest(message):每来一条新消息触发,决定怎么存、怎么建索引;assemble(budget):调模型前调用,带着 token 预算问你要给模型看什么;compact:上下文撑破硬上限时瘦身,可摘要、可砍图节点、可不做事;afterTurn(turn):一轮结束,存状态、写持久化、跑后台索引;prepareSubagentSpawn(parentContext):派子 agent 时控制带走哪些信息;onSubagentEnded(result):子 agent 回来时决定结果怎么合进父级上下文。
插件通过 api.registerContextEngine(id, factory) 注册,核心零侵入。
export default function (api) {
api.registerContextEngine("lossless-claw", (ctx) => ({
info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true },
async ingest { return { ingested: true }; },
async assemble({ messages, sessionKey, availableTools, citationsMode }) {
return {
messages,
estimatedTokens: 0,
systemPromptAddition: buildMemorySystemPromptAddition({
availableTools: availableTools ?? new Set,
citationsMode,
agentId: resolveSessionAgentId({ config: ctx.config, sessionKey }),
agentSessionKey: sessionKey,
}),
};
},
async compact { return { ok: true, compacted: false }; },
}));
}
2.3 ownsCompaction 的两种模式
压缩是 ContextEngine 的职责之一,ownsCompaction 标记决定谁干。设为 true 走自有模式,自己实现压缩算法;设为 false 走委托模式,compact 调 delegateCompactionToRuntime(...) 用 OpenClaw 内置压缩。注意 false 不等于自动回退,no-op 的 compact 不安全,会关掉正常压缩路径。
三、能支撑哪些策略
同一套接口,能跑出完全不同的上下文管理思路。
| 策略类型 | assemble 思路 | compact 思路 | 适用场景 |
|---|---|---|---|
| legacy 滑动窗口 | 按时间顺序拼最近消息 | 线性摘要最早消息 | 默认、轻量对话 |
| RAG 检索组装 | 按语义相关性捞历史 | 不主动压缩 | 长对话多话题 |
| 冷热分层 | 按需从不同层拉取 | 旧数据下沉到冷层 | 超长会话 |
| 任务感知 | 按任务类型动态取舍 | 保留任务关键节点 | 写代码 vs 闲聊 |
| DAG 无损保留 | 图节点遍历 | 旧片段摘要、原文留图 | 长程引用、合规审计 |
3.1 已落地的两个插件
Lossless-Claw(Martian Engineering 出品)是第一个有分量的 ContextEngine 插件。每条消息变成 DAG 节点,按话题相关性分组成片段,预算超了摘要旧片段但原文留图里,后面再聊到旧内容直接取回原文,不将就摘要。MemOS Cloud Plugin(MemTensor 出品)更轻,启动时从 MemOS Cloud 召回相关记忆,每轮结束存回,组装时注入,做跨会话记忆。
3.2 自己接一个引擎的步骤
- 实现 ContextEngine 接口,七个钩子按需实现;
- 在插件 bootstrap 里调
api.registerContextEngine("my-engine", factory); - 配置
plugins.slots.contextEngine: "my-engine"切换; - 用
openclaw doctor验证引擎加载正常; - 出错时引擎被隔离,OpenClaw 回退 legacy 保证用户轮次能回复,修复后再上。
这层抽象的真正价值不在某个策略多强,而在于把 agent 体验里最难也最值钱的一层——上下文质量——开放给生态。插件开发者、企业合规、研究者、模型厂商各自能在自己关心的点上发力。
常见问题(FAQ)
Q1:换引擎后旧会话怎么办?
旧会话沿用当前历史,新引擎接管后续运行,无需迁移。
Q2:ownsCompaction 设 false 能不实现 compact 吗?
不能,no-op 会关掉压缩路径,须委托给 runtime。
Q3:插件引擎崩了会拖垮整个 agent 吗?
不会,引擎被隔离,回退 legacy 保证回复,修复后再上。