Skip to content

本规范说明如何使 Codex app-server 调用 OpenClaw 上下文引擎插件的 assemble、afterTurn、ingest 等生命周期,实现与 PI 嵌入式智能体相同的上下文管理行为。关键操作:配置 agents.defaults.embeddedHarness.runtime: "codex" 或使用 codex/* 模型,并启用上下文引擎插件。注意:Codex 原生线程状态不可直接修改,上下文引擎组装的结果只能通过提示文本注入;若 assemble 失败,回退到原始提示路径;上下文引擎拥有的压缩仍作为 OpenClaw/插件状态的主结果,Codex 原生压缩独立记录。

OpenClaw Codex 上下文引擎集成:生命周期配置与实现

状态

草案实现规范。

目标

使内置的 Codex app-server 嵌入式智能体满足与 OpenClaw 上下文引擎相同的生命周期契约,该契约目前由 PI 嵌入式智能体实现。

使用 agents.defaults.embeddedHarness.runtime: "codex"codex/* 模型的会话,应仍能让选中的上下文引擎插件(如 lossless-claw)控制上下文组装、轮次后 ingest、维护以及在 Codex app-server 允许范围内的 OpenClaw 层级压缩策略。

非目标

  • 不要重写 Codex app-server 内部。
  • 不要让 Codex 原生线程压缩产生 lossless-claw 摘要。
  • 不要要求非 Codex 模型使用 Codex 嵌入式智能体。
  • 不要改变 ACP/acpx 会话行为。本规范仅适用于非 ACP 的嵌入式智能体路径。
  • 不要要求第三方插件注册 Codex app-server 扩展工厂;现有的内置插件信任边界保持不变。

当前架构

嵌入式运行循环在每次运行前解析配置的上下文引擎,再选择具体的低层级智能体:

  • src/agents/pi-embedded-runner/run.ts
    • 初始化上下文引擎插件
    • 调用 resolveContextEngine(params.config)
    • contextEnginecontextTokenBudget 传入 runEmbeddedAttemptWithBackend(...)

runEmbeddedAttemptWithBackend(...) 委托给选中的智能体:

  • src/agents/pi-embedded-runner/run/backend.ts
  • src/agents/harness/selection.ts

Codex app-server 智能体由内置的 Codex 插件注册:

  • extensions/codex/index.ts
  • extensions/codex/harness.ts

Codex 智能体实现接收与 PI 智能体相同的 EmbeddedRunAttemptParams

  • extensions/codex/src/app-server/run-attempt.ts

这意味着所需的钩子点在 OpenClaw 控制的代码中。外部边界是 Codex app-server 协议本身:OpenClaw 可以控制发送给 thread/startthread/resumeturn/start 的数据,并可以观察通知,但无法改变 Codex 内部的线程存储或原生压缩器。

当前差距

PI 嵌入式智能体直接调用上下文引擎生命周期:

  • 运行前 bootstrap/维护
  • 模型调用前 assemble
  • 运行后 afterTurn 或 ingest
  • 成功轮次后的维护
  • 对拥有压缩的引擎进行的上下文引擎压缩

相关 PI 代码:

  • src/agents/pi-embedded-runner/run/attempt.ts
  • src/agents/pi-embedded-runner/run/attempt.context-engine-helpers.ts
  • src/agents/pi-embedded-runner/context-engine-maintenance.ts

Codex app-server 智能体目前运行通用的智能体钩子并镜像转录,但不调用 params.contextEngine.bootstrapparams.contextEngine.assembleparams.contextEngine.afterTurnparams.contextEngine.ingestBatchparams.contextEngine.ingestparams.contextEngine.maintain

相关 Codex 代码:

  • extensions/codex/src/app-server/run-attempt.ts
  • extensions/codex/src/app-server/thread-lifecycle.ts
  • extensions/codex/src/app-server/event-projector.ts
  • extensions/codex/src/app-server/compact.ts

期望行为

对于 Codex 智能体的轮次,OpenClaw 应保持以下生命周期:

  1. 读取 OpenClaw 会话转录镜像。
  2. 当存在之前的会话文件时,bootstrap 活动的上下文引擎。
  3. 在可用时运行 bootstrap 维护。
  4. 使用活动的上下文引擎组装上下文。
  5. 将组装后的上下文转换为 Codex 兼容的输入。
  6. 使用包含上下文引擎 systemPromptAddition 的开发者指令启动或恢复 Codex 线程。
  7. 使用组装后的用户提示启动 Codex 轮次。
  8. 将 Codex 结果镜像回 OpenClaw 转录。
  9. 如果实现了 afterTurn 则调用,否则调用 ingestBatch/ingest,使用镜像的转录快照。
  10. 在成功的非中止轮次后运行轮次维护。
  11. 保留 Codex 原生压缩信号和 OpenClaw 压缩钩子。

设计约束

Codex app-server 仍然是原生线程状态的权威

Codex 拥有自己的原生线程和任何内部扩展历史。OpenClaw 不应试图改变 app-server 的内部历史,除非通过支持的协议调用。

OpenClaw 的转录镜像仍是 OpenClaw 特性(聊天历史、搜索、/new 和 /reset 记录、未来模型或智能体切换、上下文引擎插件状态)的来源。

上下文引擎组装必须投影为 Codex 输入

上下文引擎接口返回 OpenClaw AgentMessage[],而非 Codex 线程补丁。Codex app-server turn/start 接受当前用户输入,而 thread/startthread/resume 接受开发者指令。

因此需要一个投影层。安全的初始版本应避免假装可以替换 Codex 内部历史,而是将组装后的上下文作为确定性提示/开发者指令材料注入到当前轮次周围。

提示缓存稳定性很重要

对于 lossless-claw 等引擎,组装后的上下文应对不变输入具有确定性。不要在生成的上下文文本中添加时间戳、随机 id 或不稳定顺序。

运行时选择语义不变

智能体选择保持不变:

  • runtime: "pi" 强制 PI
  • runtime: "codex" 选择已注册的 Codex 智能体
  • runtime: "auto" 让插件声明支持的供应商
  • 不匹配的 auto 运行使用 PI

本工作改变的是选中 Codex 智能体后发生的行为。

实现计划

1. 导出或重构可重用的上下文引擎智能体辅助函数

目前可重用的生命周期辅助函数位于 PI 运行器下:

  • src/agents/pi-embedded-runner/run/attempt.context-engine-helpers.ts
  • src/agents/pi-embedded-runner/run/attempt.prompt-helpers.ts
  • src/agents/pi-embedded-runner/context-engine-maintenance.ts

如果可以避免,Codex 不应从名称暗示 PI 的实现路径中导入。

创建一个智能体中立的模块,例如:

  • src/agents/harness/context-engine-lifecycle.ts

移动或重新导出:

  • runAttemptContextEngineBootstrap
  • assembleAttemptContextEngine
  • finalizeAttemptContextEngineTurn
  • buildAfterTurnRuntimeContext
  • buildAfterTurnRuntimeContextFromUsage
  • 一个对 runContextEngineMaintenance 的薄封装

通过从旧文件重新导出或更新同一 PR 中的 PI 调用点来保持 PI 导入正常工作。

新的辅助函数名称不应提及 PI。

建议名称:

  • bootstrapHarnessContextEngine
  • assembleHarnessContextEngine
  • finalizeHarnessContextEngineTurn
  • buildHarnessContextEngineRuntimeContext
  • runHarnessContextEngineMaintenance

2. 添加 Codex 上下文投影辅助函数

添加一个新模块:

  • extensions/codex/src/app-server/context-engine-projection.ts

职责:

  • 接受组装后的 AgentMessage[]、原始镜像历史以及当前提示。
  • 确定哪些上下文属于开发者指令,哪些属于当前用户输入。
  • 将当前用户提示保留为最终可操作请求。
  • 以稳定、显式的格式渲染先前的消息。
  • 避免易变元数据。

建议 API:

ts
export type CodexContextProjection = {
  developerInstructionAddition?: string;
  promptText: string;
  assembledMessages: AgentMessage[];
  prePromptMessageCount: number;
};

export function projectContextEngineAssemblyForCodex(params: {
  assembledMessages: AgentMessage[];
  originalHistoryMessages: AgentMessage[];
  prompt: string;
  systemPromptAddition?: string;
}): CodexContextProjection;

推荐的首个投影方案:

  • systemPromptAddition 放入开发者指令。
  • 将组装后的转录上下文放在 promptText 中当前提示之前。
  • 明确标记为 OpenClaw 组装上下文。
  • 将当前提示放在最后。
  • 如果当前用户提示已出现在尾部,则排除重复。

示例提示形状:

text
OpenClaw assembled context for this turn:

<conversation_context>
[user]
...

[assistant]
...
</conversation_context>

Current user request:
...

这不如原生 Codex 历史操作优雅,但它可以在 OpenClaw 内实现并保留上下文引擎语义。

未来改进:如果 Codex app-server 暴露了替换或补充线程历史的协议,可将此投影层切换为使用该 API。

3. 在 Codex 线程启动前接入 bootstrap

extensions/codex/src/app-server/run-attempt.ts 中:

  • 照常读取镜像会话历史。
  • 判断在本次运行前会话文件是否存在。建议在镜像写入前使用辅助函数检查 fs.stat(params.sessionFile)
  • 如果辅助函数需要,打开一个 SessionManager 或使用窄适配器。
  • params.contextEngine 存在时,调用中立的 bootstrap 辅助函数。

伪流程:

ts
const hadSessionFile = await fileExists(params.sessionFile);
const sessionManager = SessionManager.open(params.sessionFile);
const historyMessages = sessionManager.buildSessionContext().messages;

await bootstrapHarnessContextEngine({
  hadSessionFile,
  contextEngine: params.contextEngine,
  sessionId: params.sessionId,
  sessionKey: sandboxSessionKey,
  sessionFile: params.sessionFile,
  sessionManager,
  runtimeContext: buildHarnessContextEngineRuntimeContext(...),
  runMaintenance: runHarnessContextEngineMaintenance,
  warn,
});

使用与 Codex 工具桥和转录镜像相同的 sessionKey 约定。目前 Codex 从 params.sessionKeyparams.sessionId 计算 sandboxSessionKey;除非有理由保留原始 params.sessionKey,否则一致使用它。

4. 在 thread/start / thread/resumeturn/start 前接入 assemble

runCodexAppServerAttempt 中:

  1. 先构建动态工具,使上下文引擎看到实际可用的工具名称。
  2. 读取镜像会话历史。
  3. params.contextEngine 存在时,运行上下文引擎 assemble(...)
  4. 将组装结果投影为:
    • 开发者指令附加内容
    • turn/start 使用的提示文本

现有的钩子调用:

ts
resolveAgentHarnessBeforePromptBuildResult({
  prompt: params.prompt,
  developerInstructions: buildDeveloperInstructions(params),
  messages: historyMessages,
  ctx: hookContext,
});

应变为上下文感知:

  1. 使用 buildDeveloperInstructions(params) 计算基础开发者指令。
  2. 应用上下文引擎组装/投影。
  3. 使用投影后的提示/开发者指令运行 before_prompt_build

此顺序使通用提示钩子看到 Codex 将接收到的相同提示。如果需要严格的 PI 对等性,应在钩子组合之前运行上下文引擎组装,因为 PI 在其提示管线之后将上下文引擎 systemPromptAddition 应用到最终系统提示。重要的不变性是:上下文引擎和钩子都有确定性的、文档化的顺序。

首次实现的推荐顺序:

  1. buildDeveloperInstructions(params)
  2. 上下文引擎 assemble()
  3. systemPromptAddition 追加/前置到开发者指令
  4. 将组装消息投影到提示文本
  5. resolveAgentHarnessBeforePromptBuildResult(...)
  6. 将最终开发者指令传递给 startOrResumeThread(...)
  7. 将最终提示文本传递给 buildTurnStartParams(...)

规范应编码在测试中,以免将来意外更改顺序。

5. 保持提示缓存稳定的格式

投影辅助函数必须为相同输入产生字节稳定的输出:

  • 稳定的消息顺序
  • 稳定的角色标签
  • 无生成的时间戳
  • 无对象键顺序泄漏
  • 无随机定界符
  • 无每次运行的 id

使用固定定界符和显式节。

6. 在轮次后转录镜像后接入 finalize

Codex 的 CodexAppServerEventProjector 为当前轮次构建本地 messagesSnapshotmirrorTranscriptBestEffort(...) 将该快照写入 OpenClaw 转录镜像。

在镜像成功或失败后,使用最佳可用消息快照调用上下文引擎 finalizer:

  • 优先使用写入后的完整镜像会话上下文,因为 afterTurn 期望会话快照而非仅当前轮次。
  • 如果无法重新打开会话文件,回退到 historyMessages + result.messagesSnapshot

伪流程:

ts
const prePromptMessageCount = historyMessages.length;
await mirrorTranscriptBestEffort(...);
const finalMessages = readMirroredSessionHistoryMessages(params.sessionFile)
  ?? [...historyMessages, ...result.messagesSnapshot];

await finalizeHarnessContextEngineTurn({
  contextEngine: params.contextEngine,
  promptError: Boolean(finalPromptError),
  aborted: finalAborted,
  yieldAborted,
  sessionIdUsed: params.sessionId,
  sessionKey: sandboxSessionKey,
  sessionFile: params.sessionFile,
  messagesSnapshot: finalMessages,
  prePromptMessageCount,
  tokenBudget: params.contextTokenBudget,
  runtimeContext: buildHarnessContextEngineRuntimeContextFromUsage({
    attempt: params,
    workspaceDir: effectiveWorkspace,
    agentDir,
    tokenBudget: params.contextTokenBudget,
    lastCallUsage: result.attemptUsage,
    promptCache: result.promptCache,
  }),
  runMaintenance: runHarnessContextEngineMaintenance,
  sessionManager,
  warn,
});

如果镜像失败,仍应将回退快照传递给 afterTurn,但记录日志说明上下文引擎正在使用回退轮次数据。

7. 规范化使用情况和提示缓存运行时上下文

Codex 结果包含来自 app-server 令牌通知(可用时)的规范化使用情况。将该使用情况传递给上下文引擎运行时上下文。

如果 Codex app-server 最终暴露缓存读/写详细信息,将其映射为 ContextEnginePromptCacheInfo。在此之前,省略 promptCache 而非虚构零值。

8. 压缩策略

有两个压缩系统:

  1. OpenClaw 上下文引擎 compact()
  2. Codex app-server 原生 thread/compact/start

不要静默混淆它们。

/compact 和显式 OpenClaw 压缩

当选中的上下文引擎具有 info.ownsCompaction === true 时,显式 OpenClaw 压缩应优先使用上下文引擎的 compact() 结果来更新 OpenClaw 转录镜像和插件状态。

当选中的 Codex 智能体具有原生线程绑定时,我们可以额外请求 Codex 原生压缩以保持 app-server 线程健康,但这必须在 details 中作为单独的后端操作报告。

推荐行为:

  • 如果 contextEngine.info.ownsCompaction === true
    • 先调用上下文引擎 compact()
    • 然后最佳努力地调用 Codex 原生压缩(当存在线程绑定时)
    • 将上下文引擎结果作为主要结果返回
    • details.codexNativeCompaction 中包含 Codex 原生压缩状态
  • 如果活动的上下文引擎不拥有压缩:
    • 保持当前的 Codex 原生压缩行为

这可能需要修改 extensions/codex/src/app-server/compact.ts 或从通用压缩路径中包装它,具体取决于 maybeCompactAgentHarnessSession(...) 的调用位置。

轮次中 Codex 原生 contextCompaction 事件

Codex 可能在轮次期间发出 contextCompaction 条目事件。保持 event-projector.ts 中当前的压缩前后钩子发射,但不要将其视为完成的上下文引擎压缩。

对于拥有压缩的引擎,当 Codex 仍然执行原生压缩时,发出显式诊断:

  • stream/event 名称:现有的 compaction stream 可以接受
  • details:{ backend: "codex-app-server", ownsCompaction: true }

这使得拆分可审计。

9. 会话重置和绑定行为

现有的 Codex 智能体 reset(...) 从 OpenClaw 会话文件中清除 Codex app-server 绑定。保留该行为。

同时确保上下文引擎状态清理继续通过现有的 OpenClaw 会话生命周期路径进行。除非上下文引擎生命周期目前对所有智能体缺少 reset/delete 事件,否则不要添加 Codex 特定的清理。

10. 错误处理

遵循 PI 语义:

  • bootstrap 失败时警告并继续
  • assemble 失败时警告并回退到未组装的管线消息/提示
  • afterTurn/ingest 失败时警告并将后轮次 finalization 标记为不成功
  • 维护仅在进行成功、非中止、非 yield 的轮次后运行
  • 压缩错误不应作为新的提示重试

Codex 特定补充:

  • 如果上下文投影失败,警告并回退到原始提示
  • 如果转录镜像失败,仍尝试使用回退消息进行上下文引擎 finalization
  • 如果上下文引擎压缩成功后 Codex 原生压缩失败,当上下文引擎是主要压缩时,不应导致整个 OpenClaw 压缩失败

测试计划

单元测试

extensions/codex/src/app-server 下添加测试:

  1. run-attempt.context-engine.test.ts

    • 当会话文件存在时,Codex 调用 bootstrap
    • Codex 使用镜像消息、令牌预算、工具名称、引用模式、模型 id 和提示调用 assemble
    • systemPromptAddition 包含在开发者指令中
    • 组装消息在请求之前投影到提示中
    • Codex 在转录镜像后调用 afterTurn
    • 没有 afterTurn 时,Codex 调用 ingestBatch 或逐消息 ingest
    • 成功轮次后运行轮次维护
    • 提示错误、中止或 yield 中止时不运行轮次维护
  2. context-engine-projection.test.ts

    • 相同输入产生稳定输出
    • 当组装历史包含当前提示时,不重复当前提示
    • 处理空历史
    • 保持角色顺序
    • 系统提示补充仅出现在开发者指令中
  3. compact.context-engine.test.ts

    • 拥有压缩的上下文引擎主要结果获胜
    • 当也尝试时,Codex 原生压缩状态出现在 details 中
    • Codex 原生失败不会导致拥有压缩的上下文引擎失败
    • 不拥有压缩的上下文引擎保持当前原生压缩行为

需要更新的现有测试

  • extensions/codex/src/app-server/run-attempt.test.ts(如果存在),否则最近的 Codex app-server 运行测试
  • extensions/codex/src/app-server/event-projector.test.ts 仅当压缩事件详情改变时
  • src/agents/harness/selection.test.ts 除非配置行为改变,否则不应需要更改;应保持稳定
  • PI 上下文引擎测试应继续通过,无需更改

集成本地测试

添加或扩展 Codex 智能体冒烟测试:

  • 配置 plugins.slots.contextEngine 为测试引擎
  • 配置 agents.defaults.modelcodex/* 模型
  • 配置 agents.defaults.embeddedHarness.runtime = "codex"
  • 断言测试引擎观察到:
    • bootstrap
    • assemble
    • afterTurn 或 ingest
    • maintenance

避免在 OpenClaw 核心测试中要求 lossless-claw。使用仓库内的小型假上下文引擎插件。

可观测性

在 Codex 上下文引擎生命周期调用周围添加调试日志:

  • codex context engine bootstrap started/completed/failed
  • codex context engine assemble applied
  • codex context engine finalize completed/failed
  • codex context engine maintenance skipped 含原因
  • codex native compaction completed alongside context-engine compaction

避免记录完整提示或转录内容。

在有用的地方添加结构化字段:

  • sessionId
  • sessionKey 根据现有日志实践脱敏或省略
  • engineId
  • threadId
  • turnId
  • assembledMessageCount
  • estimatedTokens
  • hasSystemPromptAddition

迁移/兼容性

这应该是向后兼容的:

  • 如果没有配置上下文引擎,旧版上下文引擎行为应与当前 Codex 智能体行为等效
  • 如果上下文引擎 assemble 失败,Codex 应继续使用原始提示路径
  • 现有的 Codex 线程绑定应保持有效
  • 动态工具指纹不应包含上下文引擎输出;否则每一次上下文变化都可能强制创建新的 Codex 线程。只有工具目录应影响动态工具指纹

待定问题

  1. 组装后的上下文应完全注入用户提示、完全注入开发者指令,还是拆分?

    推荐:拆分。将 systemPromptAddition 放入开发者指令;将组装后的转录上下文放入用户提示包装器中。这最符合当前的 Codex 协议,且无需修改原生线程历史。

  2. 当上下文引擎拥有压缩时,是否应禁用 Codex 原生压缩?

    推荐:暂时不。Codex 原生压缩可能仍需保持 app-server 线程活动。但必须将其报告为原生 Codex 压缩,而非上下文引擎压缩。

  3. before_prompt_build 应在上下文引擎组装之前还是之后运行?

    推荐:对于 Codex,在上下文引擎投影之后,这样通用智能体钩子看到的是 Codex 将接收到的实际提示/开发者指令。如果 PI 对等性要求相反的顺序,将选定的顺序编码在测试中并在此文档中记录。

  4. Codex app-server 未来能否接受结构化的上下文/历史覆盖?

    未知。如果可以,用该协议替换文本投影层,并保持生命周期调用不变。

验收标准

  • codex/* 嵌入式智能体轮次调用选中的上下文引擎的 assemble 生命周期
  • 上下文引擎 systemPromptAddition 影响 Codex 开发者指令
  • 组装上下文确定性影响 Codex 轮次输入
  • 成功的 Codex 轮次调用 afterTurn 或 ingest 回退
  • 成功的 Codex 轮次运行上下文引擎轮次维护
  • 失败/中止/yield 中止的轮次不运行轮次维护
  • 上下文引擎拥有的压缩对 OpenClaw/插件状态保持首要
  • Codex 原生压缩保持可审计,作为原生 Codex 行为
  • 现有 PI 上下文引擎行为不变
  • 当未选中非旧版上下文引擎或组装失败时,现有 Codex 智能体行为不变

常见问题

我怎么让 Codex 智能体使用 lossless-claw 或其他上下文引擎?

确保在配置中启用了上下文引擎插件(如 plugins.slots.contextEngine),并为模型设置 agents.defaults.embeddedHarness.runtime: "codex" 或使用 codex/* 模型。OpenClaw 会自动将上下文引擎生命周期集成到 Codex 智能体运行中。

Codex 的 assemble 失败了,智能体会怎样?

如果上下文引擎的 assemble 失败,OpenClaw 会记录警告并回退到未组装的管线消息和原始提示。Codex 智能体将继续使用默认的提示构建流程运行。

上下文引擎拥有压缩时,Codex 原生压缩是否仍然执行?

是的,暂时仍会执行。上下文引擎的 compact() 结果是 OpenClaw/插件状态的主数据;Codex 原生压缩作为独立后端操作运行,其状态记录在 details.codexNativeCompaction 中。这不会干扰上下文引擎的压缩结果。