docs/design/openclaw-context-engine-refactor.md
当前 examples/openclaw-plugin 已经具备 OpenViking 记忆写入、自动召回和工具式记忆操作能力,但整体仍然是“记忆插件 + 一个很薄的 context-engine 外壳”,而不是由 context-engine 生命周期统一负责上下文管理的真正上下文引擎。
现状特征:
before_prompt_buildafterTurnassemble() 目前基本没有承担上下文组装职责compact() 仍然委托 legacy engine,OpenViking 只参与记忆提取这会带来 4 类问题:
kind=memory slot 才能使用”的能力。当前问题不是要去掉 hook,而是要把 hook 与 context engine 生命周期之间的职责分工、调用优先级和数据流转规则明确收口。prependContext 文本块直接注入,模型只能看到结果内容,看不到检索动作本身。这使自动召回与显式 memory_recall 工具在模型视角下不一致,模型也无法基于当前 recall 的 query 和结果边界判断是否需要继续检索。后续实现统一遵循以下原则。
OpenViking 统一注册为 context engine,但继续保留原有 hook 链路。后续需要明确的是二者的职责边界:hook 继续承接兼容路径和局部增强能力;完整的上下文组装、compact、历史回放和主生命周期协同由 context engine 统一编排。
优先复用 OpenViking 已有的 Session -> Commit -> Archive -> Memory Extract 体系,不在插件层额外创造第二套上下文生命周期,也不新增独立数据库。实现上尽量沿用以下能力完成闭环:
session.add_messagesession.commit每轮参与推理的原始上下文必须先保存,再考虑抽取和压缩。compact 压缩的是“模型接下来看到的工作上下文”,不是 OpenViking 中已经无损保存的原始 turn / session 数据;压缩结果也不能替代原始消息本体。
原始数据是后续追溯、重新抽取和历史展开的事实基座。无论长期记忆、compact summary 还是后续 expand,都必须保留到原始 turn/session 的稳定引用。
先把插件升级成真正的 context-engine 闭环,再引入 skill/tool memory 等增强能力。Phase 1 完成后,插件就应当已经具备“真正上下文引擎”的最小定义。
本文档作为后续开发执行规范,聚焦 OpenClaw 插件升级为真正上下文引擎时的目标架构、数据模型和落地步骤,并优先按各个 hook 的实施职责组织说明。
参考资料:
当前相关实现主要分布于:
before_prompt_build: 兼容 fallback 与迁移约束当前 before_prompt_build 会执行搜索,并把 recall 结果拼接为 <relevant-memories> 文本块注入。
在 Phase 1 期间保留 fallback recall,保证旧环境仍可工作;一旦 assemble() 成为主链路,这个 hook 只保留兼容职责,不再承担默认实现。
assemble(),不再继续扩展 before_prompt_build 主逻辑。viking://user/memories 与 viking://agent/memories 的做法,并在本地完成去重、筛选、排序和预算裁剪。level === 2 作为最终可注入 detail memory 的判断标准,不把 L0/L1 直接当作最终注入正文。recallScoreThreshold、recallMaxContentChars、recallPreferAbstract、recallTokenBudget 这些配置约束继续生效。assemble() 时,迁移的是并行检索、去重、level === 2 过滤、score threshold、query-aware ranking、摘要优先、单条截断、总 token budget 这些规则本身;迁移完成后,before_prompt_build 只保留一个最小 fallback,而不再作为默认主入口。afterTurn: 无损写入主链路当前 afterTurn 会提取本轮增量 user/assistant 文本,创建临时 OpenViking session,并调用 /extract。
afterTurn 在新架构中承担两件事:
session.commit() 归档消息和提取记忆。
两个职责顺序执行:先写入,再评估。评估结果可能导致同步或异步的 compact 调用,但不影响写入的完成。重构逻辑: 管理一个OV的Session状态数组,每个Session对应一个OV的Session状态和信息:(下面涉及到的session都是指OV内部定义的session) 0. Compact状态检查,检查Session数组的状态:
Compact 评估与触发: 7. 按优先级从高到低逐条判断(命中第一个即触发,不继续往下):
session.commit(wait=false) 提交 OpenViking session兜底 flush:进程退出时(service.stop() / SIGTERM / SIGINT),遍历所有 buffer 执行 commitAndReset,确保未提交的累积内容不丢失
触发条件
| 条件 | 场景 | 阈值 | 执行方式 |
|---|---|---|---|
| 预算阈值 | 上下文达到预警线 | estimatedTokens >= budget × 0.75 | 异步后台 |
| 新增 token 积累 | 大量新内容(大文件、长工具输出等) | tokensAdded >= 30,000 | 异步后台 |
| 轮次积累 | 短对话多轮,token 阈值不触发 | turns >= 20 | 异步后台 |
| 最长间隔 | 长时间未 compact | interval >= 30min 且 turns >= 1 | 异步后台 |
典型场景:
session.commit() 是全量归档的重操作。保护机制
| 机制 | 场景 | 说明 |
|---|---|---|
| 并发互斥 | 上一轮的异步 compact 还在跑,本轮又触发了条件 | 同一 session 同时只允许一个 compact,避免重复提交 |
| 异常退出 | 进程退出时(service.stop() / SIGTERM / SIGINT),遍历所有 session 执行 commit,确保未提交的累积内容不丢失 |
assemble: 自动召回与上下文组装主链路当前 assemble() 基本直接回传原始 messages,不承担上下文编排职责。
assemble() 是真正的上下文组装入口,负责把 profile、长期记忆、recent raw turns、compact summary 统一编排成最终返回给 OpenClaw 的 messages。
messagesestimatedTokenssystemPromptAdditionprofile.md 和稳定偏好类高质量记忆。assembleRecallWindow 条 user turns 构造 recall query,并做轻量 skip 判断,跳过问候、无内容、纯短句。viking://user/memories 与 viking://agent/memories。level === 2 过滤、score threshold、query-aware ranking、摘要优先、单条截断、总 token budget。自动召回结果不再用:
<relevant-memories> ... </relevant-memories>
而改为注入为合成消息,形态类似:
assistant: [tool_call] memory_recall_auto({"query":"..."})
tool: [tool_result] {"memories":[...], "source":"openviking-auto-recall"}
memory_expand 提供 summary 到 raw session 的桥梁compact: 正式 commit 边界当前 compact() 仍调用 legacy context engine 的 compact,OpenViking 只是在其过程中附带参与记忆提取。
compact() 负责触发 OpenViking session.commit(),把 session 写入、归档、summary 生成和长期记忆提取统一收口到一个正式同步点。
session.commit()。CompactCheckpoint,保存本次 compact 的时间、来源 turn 范围和 summary 引用。recentRawTurnCount 条 raw turns、最新 compact summary、必要的 pending tasks / active instructions。afterTurn 主路径迁移到 compact 主路径compact() 成为 OpenViking 与 OpenClaw session 边界的正式同步点OpenClaw turn
│
├─ afterTurn
│ └─ 将本轮真实上下文写入 OpenViking session(无损保存)
│
├─ assemble
│ ├─ 读取 profile / stable memories
│ ├─ 按 query 检索 user/agent memories
│ ├─ 读取最近 raw turns + compact summaries
│ └─ 组装成新的 messages 返回给 OpenClaw
│
└─ compact
├─ 调用 OpenViking session.commit()
├─ 归档旧消息并生成 session summary
├─ 提取长期记忆
└─ 返回压缩后的工作上下文
本方案不引入新的顶层存储系统,直接复用 OpenViking:
viking://session/{user_space}/{session_id}:保存完整对话 turn、工具调用、上下文使用记录、compact historyviking://user/.../memories:用户画像、偏好、实体、事件等长期记忆viking://agent/.../memories:cases、patterns 等 agent 记忆viking://agent/.../skills:后续 skill memory Phase 3 的锚点session.commit(),而不是 legacy compact + 附带提取。memory_store 仍保留,但只是“显式强制写入”的辅助手段,不再承担主数据链路。为了让实现可执行,插件内部需要统一最小数据结构。
每个进入 OpenViking session 的 turn 都按统一结构落盘。
type TurnEnvelope = {
turn_id: string;
session_id: string;
sequence: number;
timestamp: string;
role: "user" | "assistant" | "system" | "tool";
parts: Array<
| { type: "text"; text: string }
| { type: "tool_call"; tool_name: string; arguments: unknown }
| { type: "tool_result"; tool_name: string; result: unknown }
| { type: "context_ref"; uri: string; abstract?: string }
| { type: "meta"; key: string; value: unknown }
>;
used_context_uris: string[];
used_skills: Array<{
uri: string;
input?: string;
output?: string;
success?: boolean;
}>;
tool_calls: Array<{
tool_name: string;
arguments: unknown;
status?: "success" | "error";
}>;
tool_results: Array<{
tool_name: string;
result: unknown;
status?: "success" | "error";
}>;
compaction_marker?: {
source: "raw" | "post_compact";
compact_checkpoint_id?: string;
};
};
约束:
parts 必须能完整表示本轮输入给模型和模型产出的关键上下文used_context_uris 记录本轮真正注入的外部上下文 URIused_skills 和 tool_calls/tool_results 用于后续 patterns/cases 抽取与可追溯分析assemble() 内部统一生成如下结构,再转换成 OpenClaw messages:
type AssembledContextPacket = {
profile_blocks: string[];
recalled_memories: Array<{
query: string;
uri: string;
abstract?: string;
score?: number;
}>;
compact_summaries: Array<{
session_uri: string;
abstract?: string;
overview?: string;
}>;
recent_raw_turns: TurnEnvelope[];
injected_messages: Array<Record<string, unknown>>;
estimated_tokens: number;
};
每次 compact 后记录一个 checkpoint,用于之后的可展开与调试:
type CompactCheckpoint = {
checkpoint_id: string;
session_id: string;
committed_at: string;
archive_index: number;
source_turn_range: {
start_sequence: number;
end_sequence: number;
};
summary_uri?: string;
extracted_memory_uris: string[];
};
保留以下工具,并继续由插件注册:
memory_recallmemory_storememory_forget其中:
memory_recall:显式检索长期记忆memory_store:用户明确要求“记住这件事”时强制写入memory_forget:删除或撤销错误记忆memory_expandPhase 2 新增:
memory_expand({
summaryOrMemoryUri: string,
query?: string,
limit?: number
})
返回:
作用:
assemble() 触发memory_recall以下现有配置保持保留:
modeconfigPathportbaseUrlapiKeyagentIdtimeoutMsautoRecallautoCapturerecallLimitrecallScoreThresholdrecallMaxContentCharsrecallPreferAbstractrecallTokenBudget新增最小集合:
type ContextEngineRefactorConfig = {
recentRawTurnCount?: number;
assembleRecallWindow?: number;
compactCommitThreshold?: number;
memoryExpandMaxResults?: number;
};
建议默认值:
recentRawTurnCount = 8assembleRecallWindow = 5compactCommitThreshold = 0.75memoryExpandMaxResults = 6Phase 1 期间采用双轨兼容:
assemble() / compact() 承担完整上下文生命周期before_prompt_build recall 方案默认目标不是移除 hook,而是让 hook 与 context engine 的职责边界和调用优先级稳定下来,避免重复实现和相互覆盖。
以下实现被标记为 deprecated:
afterTurn 中临时 session + /extract 的主逻辑tryLegacyCompact() 依赖 legacy context engine说明:
before_prompt_build hook 本身不废弃;被弃用的是把完整自动 recall 主链路长期放在该 hook 中的做法。完成“写入、组装、compact”三条主链路收口。
afterTurn 改为持久化 TurnEnvelopeassemble() 接管自动 recall 和上下文组装compact() 改为调用 OpenViking session.commit()before_prompt_build 也能完成自动 recall让压缩历史真正可回钻。
CompactCheckpointassemblememory_expandmemory_expand 找回原始细节这一阶段不阻塞 context-engine 升级成功。
范围包括:
ov ls viking:// 目录预注入assemble()afterTurn()compact()before_prompt_build recall 降级为 fallbackmemory_expand插件实现默认复用现有 session API,但以下能力若现有返回不够用,需要补充:
原则:
session / content / fs API 就不新增新协议新增或补齐以下测试:
TurnEnvelope 构造与字段完整性memory_expand 参数校验与结果格式覆盖以下场景:
afterTurn 成功无损写入 sessionassemble() 能召回 compact 前历史memory_store 显式提交与自动管线不冲突agentId 隔离不破坏必须验证:
以下场景全部成立,才算升级成功。
一个长对话触发 compact 后,后续会话仍能回答:
用户说“记住我偏好深色主题”,插件通过 memory_store 立即写入;之后普通对话中的重要决策,在 compact 时通过自动管线进入长期记忆。
模型先依据 compact summary 回答,再在需要时通过 memory_expand 找回原始 turn 细节,而不是只能依赖抽象 memory。
禁用 before_prompt_build 主 recall 路径后,context-engine 仍能正常工作。
本设计不包含以下内容:
建议按以下顺序开发:
afterTurn 的 TurnEnvelope 持久化assemble() 的 recall 注入与 session summary 读取compact() 到 OpenViking session.commit()memory_expand 与追溯元数据原因:
assemble() 成为主链路,再移除 hook 才安全compact() 最后切换,便于逐步回归验证为避免后续开发再次出现未收敛讨论,本文固定以下默认决策:
compact(),不是每轮 afterTurn。afterTurn 的第一职责是无损保存,不是立即抽取。以上决策除非后续出现明确阻塞,否则实现阶段不再重新讨论。