Context Engine Abstraction 整理
Context_Engine_Abstraction
问题
Context_Engine_Abstraction
标准回答
OpenClaw 的可插拔 Context Engine:为什么需要抽象?
支持哪些策略?
核心原因:Context 管理没有万能方案。 不同的应用场景、模型窗口大小、任务类型,最优策略差异巨大。将 Context 管理抽象成接口(定义“做什么”),让策略实现(“怎么做”)可独立替换,既方便内部迭代,也方便社区扩展。这是经典的策略模式。
OpenClaw 的 ContextEngine 接口覆盖完整生命周期:
| 阶段 | 方法 | 作用 |
|---|---|---|
| 初始化 | bootstrap | 会话首次创建时初始化(如导入历史) |
| 存消息 | ingest/ingestBatch | 新消息进来时如何存储、是否做额外处理(如向量化) |
| 挑消息 | assemble | 发给模型前,在 token 预算内挑选最合适的消息 |
| 压消息 | compact | 历史太长时如何压缩(摘要、裁剪、归档) |
| 轮后处理 | afterTurn | 每轮对话结束后的收尾工作(持久化、触发后台压缩) |
| 子 Agent | prepareSubagentSpawn/onSubagentEnded | 管理子 Agent 的上下文隔离与回收 |
| 销毁 | dispose | 释放引擎持有的资源 |
核心调度逻辑只依赖这套接口,不关心具体实现。通过 registerContextEngine(id, factory) 注册新引擎,在配置中通过 plugins.slots.contextEngine 一行切换,无需改动核心代码。
支持的不同策略方向:
- 默认 legacy 策略:全部塞进去,塞不下线性压缩最早消息,简单粗暴
- 基于检索的 RAG 策略:消息入库时向量化,组装时按语义相关性捞历史,适合长对话多话题场景
- 分层存储策略:冷热分离,最近几轮放内存、摘要放本地、更早的放云端,按需拉取
- 任务感知策略:根据当前任务类型(写代码 vs 闲聊)动态决定保留哪些历史,提升 token 预算质量
- 自定义压缩策略:通过
ownsCompaction标记接管压缩,可实现树状摘要、按话题分支压缩等高级方式
扩展知识
1. 内置的 legacy 引擎
当前默认引擎,实现直白:
ingest是 no-op(消息持久化由 SessionManager 负责)assemble直接透传消息列表,不做筛选compact委托给compactEmbeddedPiSessionDirect()做线性压缩
策略粗糙:“全部塞进去,塞不下压缩最早的”。短对话够用,长对话或多话题场景效果不佳。
2. 高级策略详解
基于检索的 Context Engine(RAG 风格)
ingest时向量化每条消息,存入向量库assemble时不按时间顺序,而是根据当前 query 做语义检索,捞最相关的历史片段- 对跨多天、换过多个话题的长对话,比线性截断有效得多
分层存储引擎(冷热分离)
- 热数据:最近 3-5 轮对话,直接放内存
- 温数据:最近几个 compaction 周期的摘要,放本地文件
- 冷数据:更早的历史,可扔外部存储甚至云端
assemble时按需从不同层拉取,保证最近上下文完整,又不撑爆内存
任务感知引擎
- 根据当前任务类型动态调整组装策略
- 写代码时:优先保留代码相关历史、文件路径、报错信息
- 闲聊时:优先保留情感偏好和个人信息
- 相同 token 预算,内容质量更高
自定义 Compaction 引擎
- 接口中的
ownsCompaction: true标记让引擎完全接管压缩策略 - 默认线性压缩是”最早消息一坨压成摘要”
- 可替换为树状摘要:按话题分支组织,每个分支独立压缩,保留更多结构化信息
3. 插件注册机制
切换过程对核心代码零侵入:
实现 ContextEngine 接口
调用 registerContextEngine("my-engine-id", factory) 注册
配置文件里将 plugins.slots.contextEngine 指向引擎 ID
类比 Webpack plugin 体系、VS Code 扩展机制。
面试官追问
Q1:legacy 引擎的 ingest 是 no-op,消息谁在管?换成 RAG 引擎职责怎么迁移?
A:legacy 引擎的消息持久化由 SessionManager 负责。换成 RAG 引擎后,ingest 需真正接管消息处理(至少做向量化和入库)。迁移关键是 SessionManager 需让出”写消息”动作,或双方做好协调避免重复写。这正是抽象成接口的原因——职责边界可随引擎实现灵活调整。
Q2:assemble 时 token 预算给得很紧,不同引擎的降级策略有何差异?
A:
- legacy 引擎:暴力从最早消息开始砍,砍到塞下为止
- RAG 引擎:按相关性排序,预算紧就少捞几条,质量衰减平滑
- 分层存储引擎:优先砍冷数据层,保住热数据
- 任务感知引擎:根据任务权重动态决定哪些历史先丢(如写代码时闲聊记录优先级最低)
Q3:ownsCompaction 标记具体怎么生效?不设标记时压缩谁触发?
A:压缩触发分两层:
- 不设
ownsCompaction:底层 Pi runtime 内置 auto-compaction 监控 token 用量,超阈值自动压缩,外层 Runner 不介入 - 设
ownsCompaction: true:Pi 的内置 auto-compaction 被禁用,改由引擎通过afterTurn等钩子自主决定压缩时机和方式 - 兜底机制:无论是否设标记,Runner 在上下文快炸时会直接调
contextEngine.compact()做紧急压缩
ownsCompaction 控制”日常谁来管压缩”,但”快炸了”时 Runner 一定会兜底。这对有自己存储和索引体系的引擎特别重要。
关键点
**核心原因:Context 管理没有万能方案。
- ** 不同的应用场景、模型窗口大小、任务类型,最优策略差异巨大。
- 将 Context 管理抽象成接口(定义“做什么”),让策略实现(“怎么做”)可独立替换,既方便内部迭代,也方便社区扩展。
备注
Context_Engine_Abstraction
OpenClaw 的可插拔 Context Engine:为什么需要抽象?支持哪些策略?
核心原因:Context 管理没有万能方案。 不同的应用场景、模型窗口大小、任务类型,最优策略差异巨大。将 Context 管理抽象成接口(定义“做什么”),让策略实现(“怎么做”)可独立替换,既方便内部迭代,也方便社区扩展。这是经典的策略模式。
- OpenClaw 的
ContextEngine接口覆盖完整生命周期:
| 阶段 | 方法 | 作用 |
|---|---|---|
| 初始化 | bootstrap | 会话首次创建时初始化(如导入历史) |
| 存消息 | ingest/ingestBatch | 新消息进来时如何存储、是否做额外处理(如向量化) |
| 挑消息 | assemble | 发给模型前,在 token 预算内挑选最合适的消息 |
| 压消息 | compact | 历史太长时如何压缩(摘要、裁剪、归档) |
| 轮后处理 | afterTurn | 每轮对话结束后的收尾工作(持久化、触发后台压缩) |
| 子 Agent | prepareSubagentSpawn/onSubagentEnded | 管理子 Agent 的上下文隔离与回收 |
| 销毁 | dispose | 释放引擎持有的资源 |
核心调度逻辑只依赖这套接口,不关心具体实现。通过 registerContextEngine(id, factory) 注册新引擎,在配置中通过 plugins.slots.contextEngine 一行切换,无需改动核心代码。
- 支持的不同策略方向:
- 默认 legacy 策略:全部塞进去,塞不下线性压缩最早消息,简单粗暴
- 基于检索的 RAG 策略:消息入库时向量化,组装时按语义相关性捞历史,适合长对话多话题场景
- 分层存储策略:冷热分离,最近几轮放内存、摘要放本地、更早的放云端,按需拉取
- 任务感知策略:根据当前任务类型(写代码 vs 闲聊)动态决定保留哪些历史,提升 token 预算质量
- 自定义压缩策略:通过
ownsCompaction标记接管压缩,可实现树状摘要、按话题分支压缩等高级方式
当前默认引擎,实现直白:
ingest是 no-op(消息持久化由 SessionManager 负责)assemble直接透传消息列表,不做筛选compact委托给compactEmbeddedPiSessionDirect()做线性压缩
策略粗糙:“全部塞进去,塞不下压缩最早的”。短对话够用,长对话或多话题场景效果不佳。
基于检索的 Context Engine(RAG 风格)
ingest时向量化每条消息,存入向量库assemble时不按时间顺序,而是根据当前 query 做语义检索,捞最相关的历史片段- 对跨多天、换过多个话题的长对话,比线性截断有效得多
分层存储引擎(冷热分离)
- 热数据:最近 3-5 轮对话,直接放内存
- 温数据:最近几个 compaction 周期的摘要,放本地文件
- 冷数据:更早的历史,可扔外部存储甚至云端
assemble时按需从不同层拉取,保证最近上下文完整,又不撑爆内存
任务感知引擎
- 根据当前任务类型动态调整组装策略
- 写代码时:优先保留代码相关历史、文件路径、报错信息
- 闲聊时:优先保留情感偏好和个人信息
- 相同 token 预算,内容质量更高
自定义 Compaction 引擎
- 接口中的
ownsCompaction: true标记让引擎完全接管压缩策略 - 默认线性压缩是”最早消息一坨压成摘要”
- 可替换为树状摘要:按话题分支组织,每个分支独立压缩,保留更多结构化信息
切换过程对核心代码零侵入:
实现 ContextEngine 接口
调用 registerContextEngine("my-engine-id", factory) 注册
配置文件里将 plugins.slots.contextEngine 指向引擎 ID
类比 Webpack plugin 体系、VS Code 扩展机制。
-
本文已做格式统一与噪声清理,保留原始语义。
-
核心原因:Context 管理没有万能方案。 不同的应用场景、模型窗口大小、任务类型,最优策略差异巨大。将 Context 管理抽象成接口(定义“做什么”),让策略实现(“怎么做”)可独立替换,既方便内部迭代,也方便社区扩展。这是经典的策略模式。
-
OpenClaw 的
ContextEngine接口覆盖完整生命周期: -
| 阶段 | 方法 | 作用 |
-
|------|------|------|
-
| 初始化 |
bootstrap| 会话首次创建时初始化(如导入历史) | -
本文已做格式统一与噪声清理,保留原始语义。
Share Article
If this article helped you, please share it with others!