Codex 把对话消息、工具调用和工具结果放在同一份历史中,每次请求模型前,再结合当前指令和可用工具组装上下文。项目环境或设置变化时,可以追加变化部分;历史过长时,通过压缩缩减后续请求中的内容,同时保留本地的完整会话记录。
02 上下文结构
Codex 在请求模型前创建 Prompt 对象。其中,提示词、输入列表和工具定义提供本次工作的上下文;其他字段保存部分调用配置。下面先看这三部分。
提示词 base_instructions
输入列表 input
工具定义 tools
02.1 提示词
提示词的加载机制、结构、完整译文和组合示例已移到第六章 · 提示词,请看第六章。
02.2 输入列表 input
Prompt.input 是一个数组,保存当前请求需要的用户输入、已有模型回复、工具调用和工具结果,以及适用的推理或压缩内容。用户要求和历史回复都可以是消息;工具调用与结果则有独立类型。
input[]
├─ message
│ ├─ role
│ └─ content[]
│ ├─ type
│ └─ text / 图片引用 / 音频引用
├─ function_call
│ ├─ name、arguments
│ └─ call_id
└─ function_call_output
├─ call_id
└─ output
上面用消息、函数调用及结果展示嵌套关系。每个数组元素使用自己类型对应的字段;不是把下表的全部字段放进一个对象中。
02.2.1 输入项类型与字段
Pi 的消息类型
Pi 的公共模型调用层使用三种消息:user 保存用户输入,assistant 保存模型回复,toolResult 保存工具执行结果。模型提出的工具调用没有单独成为一条顶层消息,而是放在 assistant.content 中,以 type: "toolCall" 表示,并保存 id、name 和 arguments。模型文字和思考内容也可以放在这条助手消息里。
不同本地工具的结果都使用 toolResult:toolName 表示哪个工具,toolCallId 对应哪次调用,content 保存文字或图片,isError 表示执行是否出错。这里不是通过 type 区分工具名称;内容项中的 type 区分的是文字、图片等格式。Pi 的运行时还支持扩展消息,例如压缩摘要 compactionSummary 和分支摘要 branchSummary;它们在转换为模型输入时变成 user 消息。因此,“三种消息”说的是公共调用层,不是运行时和会话文件只有三种记录。
Codex 的输入项类型
Codex 的 input 将更多内容保存为独立记录。普通对话使用 message;函数工具调用与结果分别使用 function_call 和 function_call_output,通过 call_id 对应。自由格式工具使用 custom_tool_call 和 custom_tool_call_output,工具发现使用 tool_search_call 和 tool_search_output。这些是数组元素的不同 type,不是模型请求顶层新增的多个字段。
此外,协议还单独列出以下记录:
web_search_call:保存服务端网页搜索的动作与状态。服务端执行搜索,Codex 不需要像本地工具一样执行后再提交一个同名结果项。这份类型定义没有单独的 web_search_output。
image_generation_call:保存图像生成的状态、调整后的提示词和 result,生成结果直接在这一项中。
local_shell_call:兼容旧协议的终端调用,保存命令和状态,本身没有 output。当前以函数工具形式调用的终端操作,执行结果仍可以使用 function_call_output,不能理解为所有终端结果都使用专用类型。
reasoning:保存推理摘要、可选正文及加密推理内容;compaction 和 context_compaction 保存协议中的压缩内容。
agent_message:保存 Agent 之间的消息;additional_tools 在历史中补充工具定义;configuration_update 在对应历史位置更新推理设置。compaction_trigger 则是压缩请求的控制标记,不是普通对话消息。
两种结构的区别
Pi 把模型消息和本地工具结果整理成一套公共结构,再由 Provider 转换成各家服务的请求格式。这让 Agent 主循环可以使用同一套消息类型处理不同供应商。Codex 的 ResponseItem 则保留了 Responses 协议中更细的类型,让服务端搜索、图像生成、推理状态和工具发现等内容使用各自的字段,不必都转换成普通文字或通用工具结果。这是从代码结构可以看出的差别;独立类型的数量并不直接代表能力多少。
两边的工具定义与执行记录都要区分:tools 告诉模型有哪些工具、怎样传参数;消息或输入历史记录已经发生的调用及结果。工具结果不放进工具定义里。Pi 与 Codex 的差异主要在于这些历史内容如何分类,以及怎样转换为模型服务接受的协议。
02.2.2 消息内容与工具结果
message.content 还要继续往下拆,一条消息可以包含多段内容。
工具结果在内部用 FunctionCallOutputPayload 保存 body 和可选 success。body 是文字或内容数组;发送时,output 直接序列化为这个文字或数组,success 保留为内部状态,不额外作为服务协议字段发送。
web_search_call.action 可为 search(query、queries)、open_page(url)或 find_in_page(url、pattern),其中这些参数可选。旧终端调用的 action.exec 包含 command 和可选的 timeout_ms、working_directory、env、user。这些动作的实际处理在工具章节展开。
02.3 工具定义 tools
tools 是 ToolSpec 列表,每项同样通过 type 区分。下面列出这份源码中的五种定义。
05 上下文压缩
一次任务会不断加入模型回复、代码和工具结果。Codex 在请求前和执行过程中检查当前用量,需要缩减时另行处理历史,再用新的历史继续任务。压缩有两条主要路径:服务支持远程压缩时生成压缩项;不支持时请求模型生成文字摘要。两条路径保留原文的规则不同。
Codex 不先划出一段“最新完整对话”留在压缩范围之外。它将整理后的当前历史送去压缩,再另外挑选原文,与压缩结果一起用于后续请求。被保留的原文也可能已经参与了压缩。
当前历史:从最早记录到最新记录用户消息 · 模型回复 · 工具调用与结果 · 适用的推理及旧压缩记录
↓
生成压缩结果整理后的当前历史
↓
远程压缩项 / 本地文字摘要
另选原文保留按消息类型筛选
↓
从最新向前分配原文预算
↓
后续历史 = 保留的原文 + 压缩结果提示词、规则、环境说明和工具定义按当前状态重新提供
压缩范围是本次请求取得的当前历史,不是磁盘中全部旧记录。摘要请求过长时可能缩减这份副本;远程服务内部怎样取舍信息没有公开。
05.1 压缩的触发时机
- 用户提交新任务后,请求模型之前。先检查已有历史和模型变化。固定源码中,这次预检查发生在新用户消息及本轮环境更新写入之前,并未完整估算这些尚未加入的内容。
- 任务还需要继续时。模型返回、工具处理后,用最新用量判断是否需要先压缩,再进行后续模型请求。
- 切换模型时。两次运行都提供压缩兼容标识且标识不同,可能先处理旧历史。换到窗口更小的模型时,还会检查已有用量是否超过新模型的条件;不是每次切换模型都压缩。相应路径由旧模型处理旧历史,再用当前模型的规则重建上下文。
- 用户主动输入 /compact。手动请求不要求先达到自动阈值。
- 任务结束后。可选配置允许完成答复后压缩,为之后的任务准备空间。默认关闭;启用后还要求没有排队输入、未取消,并且没有采用 TokenBudget 新窗口模式。
这些是不同检查点,共用相应压缩实现。尤其要区分“任务还在继续,必须先压缩”和“答复已经完成,额外做一次压缩”:后者失败一般不丢掉已经完成的答复。
05.2 Token 计算与触发条件
默认统计完整上下文,也可以配置为只统计本窗口新增量。在前面列出的检查点计算用量,满足对应条件时才压缩。
05.2.1 第一种模式:统计完整上下文(默认)
当前用量是整份上下文的 token 数。下面两条条件是“或”的关系,满足任意一条就达到用量限制:
条件一:当前用量 ≥ 自动触发线 + 额外缓冲
或
条件二:当前用量 ≥ 有效窗口上限
当前用量的计算
当前用量 ≈ 服务最近报告的用量 + 新增内容估算 + 未计入的历史推理估算
- 服务最近报告的用量:最近一次响应报告的本次总 token 数。
- 新增内容估算:最后一个模型生成项之后加入、尚未被服务统计的用户补充、工具结果等内容。
- 未计入的历史推理估算:只有服务未将这些推理项计入用量时才补上,避免重复计算。
例如服务报告 175,000,之后工具返回约 6,000,且没有未计入的历史推理,当前用量约为 181,000。没有可用的服务统计时,按当前输入项估算,并补计尚未记入历史的主要提示词。
未统计的内容按类型估算:
文字估算 token = 向上取整(UTF-8 字节数 ÷ 4)
- 消息与工具:计算文字、调用名称和参数等实际内容;结构化参数按 JSON 内容估算。4 字节不等于 4 个汉字。
- 图片:普通图片按约 1,844 token 估算;原图模式可按 32×32 图块和尺寸规则计算。
- 加密推理与压缩项:根据编码长度估算,不解密。
估算不调用模型分词器,可能偏离真实用量;后续响应提供新的服务统计。
自动触发线与有效窗口上限
自动触发线 = min(配置的触发 token 数,向下取整(模型窗口 × 90%))
未配置时:自动触发线 = 向下取整(模型窗口 × 90%)
有效窗口上限 = 向下取整(模型窗口 × 可用比例(默认 95%))
- 90%:当前源码固定比例。可以配置更小的触发 token 数,不能用这项配置提高到 90% 以上。
- 可用比例:由模型资料提供,默认 95%,具体模型可以不同。
- 额外缓冲:没有对应回退提示词配置时为 0;启用后加到自动触发线上,不扩大有效窗口。
例如窗口为 200,000,采用默认比例且没有额外缓冲,达到 180,000 就满足第一条条件;190,000 是有效窗口上限。在这组配置下,第二条被第一条覆盖。若配置使自动触发线加缓冲高于有效窗口上限,第二条就会先满足。无法确定的限制不参与判断。
05.2.2 第二种模式:只计算本窗口新增量
这种模式需要配置启用,用来单独限制“接下来允许新增多少内容”。关键数据是初始用量:
新增量 = max(完整上下文用量 − 初始用量,0)
条件一:新增量 ≥ 自动触发线 + 额外缓冲
或
条件二:完整上下文用量 ≥ 有效窗口上限
初始用量取本窗口第一次请求的输入 token 数,包含当时带入的提示词和消息等。压缩重建上下文后重新建立基准;可以先估算,再用本窗口第一次服务统计校准。后续回复、工具结果和用户补充都计入增长,不在每次请求后重新计数。尚无基准时,新增量暂按 0 计算。
例如初始为 50,000,完整用量增长到 75,000,新增量就是 25,000。将触发线设为 25,000、缓冲设为 0,就满足第一条条件;若完整用量先达到有效窗口上限,则按第二条处理。
本模式直接使用配置的触发 token 数,没有配置时才取模型默认值;显式值不受上述 90% 限制。初始内容仍占模型窗口,所以始终保留第二条检查。完整用量的估算和额外缓冲规则与默认模式相同。
这种设计把已有背景和后续增长分开,便于按新增内容控制整理频率。例如初始内容分别为 10,000 和 50,000 的两个窗口,都可以设置每新增 25,000 token 就检查压缩,分别在总量 35,000 和 75,000 时达到条件。它的优势是能给不同初始大小的窗口设置相同的新增额度;实际可增长多少仍受有效窗口上限限制,不保证更省 token 或比默认模式更晚压缩。
05.3 远程压缩
Codex 的常规上下文压缩有两种方式:远程压缩和本地压缩。服务商能力配置 capabilities.remote_compaction 决定使用哪一种:
v2:远程压缩。服务必须支持对应协议,Codex 向服务发送压缩触发项,由服务生成压缩结果。unsupported:本地压缩。Codex 在本地准备总结指令、发送摘要请求,再用返回的文字重建历史。
没有显式配置时,OpenAI 和被识别为 Azure Responses 的服务默认使用远程压缩,普通自定义服务默认使用本地压缩。自定义服务只有实现了远程压缩协议,才能将这项配置设为 v2。“本地压缩”指请求与历史由客户端组织,不表示模型在本机运行。
[model_providers.my_provider.capabilities]
remote_compaction = "unsupported" # 本地压缩;支持远程协议时可设为 "v2"
05.3.1 压缩请求与结果
远程路径通过模型响应流接口发送专门的压缩请求。代码按服务声明的 RemoteCompactionSupport::V2 选择这条路径,不会在这次请求中继续原任务的工具循环。
- 准备历史副本。按当前模型支持的输入类型整理历史,补入实际执行工具的信息。请求前还会按对应规则处理部分工具输出。
- 准备指令和工具说明。加入适用的基础指令;工具已通过历史声明时,独立工具列表可以为空,否则提供当前模型可见的工具定义。
- 加入压缩触发项。在 input 末尾追加下面的 compaction_trigger,告诉服务这是一次压缩请求。生成压缩内容的服务端提示词没有在客户端源码中公开。
- 接收并检查结果。一直读到 response.completed,要求结果中恰好有一个 compaction 项。流提前中断、没有压缩项或出现多个压缩项,都不能当作成功结果。
- 重建后续输入。按下面的原文保留规则选择消息,并追加服务返回的压缩项。
{"type":"compaction_trigger"}
返回项的结构如下,ID 和加密内容由服务提供。客户端不解密,后续请求将该项重新放入 input。
{
"type": "compaction",
"id": "<服务返回的 ID>",
"encrypted_content": "<服务返回的压缩内容>"
}
这里值得关注的是历史如何替换和任务如何继续。加密内容的内部格式并未公开,不能据此推断它完整保留了每条旧消息。
05.3.2 原文保留
远程压缩后,先筛选真实用户消息、钩子输入及符合条件的 Agent 间消息;客户端编写的开发者消息由相应功能开关决定是否保留。历史助手回复和工具结果不作为近期完整对话逐条保留,由服务返回的压缩项承接。
筛选后的原文共享 64,000 token 预算,从最新向前扣减。下面用三条符合保留条件的纯文字消息演示,数字均为估算 token:
较早消息
20,000
较新消息
25,000
最新消息
30,000
← 从最新向前选择,预算 64,000
边界剩余 = 64,000 − 30,000 − 25,000 = 9,000
↓
较早消息截短
保留约 9,000
完整保留
25,000
完整保留
30,000
↓
恢复时间顺序的原文 + 服务返回的压缩项
这里的切口是原文保留的预算边界,不是服务只压缩较早部分的分界。文字放不下时保留开头和结尾,中间加入截断标记;媒体及附带说明按各自规则计入预算。启用图片预算时,过大的图片也可能被舍弃。64,000 只限制保留原文,不包括压缩项和重新加入的运行说明。
05.3.3 远程压缩的优势
OpenAI 官方文档对压缩项的作用有直接说明:
The returned compaction item carries forward key prior state and reasoning into the next run using fewer tokens.
中文译文:返回的压缩项用更少的 token,将此前的关键状态和推理带入下一次运行。
原文:OpenAI Compaction 文档 · Server-side compaction ↗
- 保留任务续接所需的状态与推理。官方说明,压缩项用更少 token 将此前的关键状态和推理带入后续请求。它面向模型继续执行任务,具体内部格式没有公开。
- 减少客户端的摘要组织工作。客户端不必另写总结模板来规定摘要格式,而是请求服务提供的压缩能力,并按协议保存、传回结果。服务内部可以调整压缩实现。
- 客户端可以自行管理会话状态。加密压缩项可以由客户端保存,下次原样传回,支持不依赖服务端保存响应的续接方式,可与不保存响应的设置配合使用。
压缩还能减少后续输入,让长任务继续执行;本地摘要也有这一效果。官方将远程压缩描述为会考虑信息损失的压缩,并不保证无损。现有资料不足以证明它在所有任务中更快、更便宜或更准确,仍需结合任务结果验证;加密内容也不便于人工检查,且依赖服务协议。
05.4 本地压缩
本地压缩由 Codex 组织一次独立的模型请求,把当前历史总结成文字,再用摘要和保留的用户要求重建上下文。摘要仍由当前配置的模型生成,不在电脑上直接用规则压缩文字。
05.4.1 压缩范围
送去总结的是当前上下文里的整份历史,包括最新消息;没有先留出一段“不参与总结的近期对话”。已经被以前的压缩移出上下文的旧原文,不会从磁盘重新加载。
当前历史 · 从旧到新
已有消息或旧摘要
用户要求
模型回复与工具调用
工具结果与最新消息
以上内容按模型能力整理后,一起送去总结
Pi 会先选切点,用摘要替换较早部分,保留切点之后的完整消息。Codex 本地压缩的原文选择发生在摘要生成后,主要保留用户要求,而非最新一整段模型与工具往返。
05.4.2 摘要请求
一次独立的模型请求
当前主要提示词
↓
模型返回文字摘要
主要提示词已在历史中时不重复加入。请求不另行提供可执行工具定义,收到回复后也不进入原任务的工具循环。输入过长导致失败时,可以移除副本中最旧的输入项再试。
压缩提示词的完整中文译文如下:
你正在执行上下文检查点压缩。请为接下来继续任务的另一个 LLM 生成交接摘要。
包括:
- 当前进展和已经作出的关键决定
- 重要背景、约束或用户偏好
- 尚未完成的工作,以及清楚的下一步
- 继续任务所需的关键数据、示例或参考资料
保持简洁、结构清晰,重点帮助后续 LLM 顺利继续工作。
可以配置替换这段提示词。默认没有固定摘要标题、字数或压缩比例,摘要长度由模型输出与请求限制共同决定。
05.4.3 用户原文的保留与切口
摘要生成后,从当前历史里取出真实用户消息,排除旧摘要。按从最新到最旧的顺序,使用 20,000 token 预算选择原文;模型回复和工具结果不作为一段完整近期对话保留。
用户文字估算 token = 向上取整(UTF-8 字节数 ÷ 4)
边界消息可用预算 = 20,000 − 已选中用户消息的估算 token
只在筛选出的用户消息中选原文 · 数字为估算 token
较早用户消息
8,000
较新用户消息
7,000
最新用户消息
9,000
← 从最新向前扣预算:20,000 − 9,000 − 7,000 = 4,000
预算边界
截短为约 4,000
整条保留
7,000
整条保留
9,000
整条放得下就保留;放不下的边界消息保留开头和结尾,删除中间。保留边界消息后停止向前选择;预算恰好用完时也停止。
边界字节预算 ≈ 剩余 token × 4
开头与结尾大致各分一半,截取位置对齐完整 UTF-8 字符
8,000 token 的边界消息,只剩 4,000 预算
开头约 2,000
中间删除
插入截断标记
结尾约 2,000
这不是按句子或轮次找语义切点。截断标记额外占空间,媒体消息按文字替代规则处理;20,000 是用户原文预算,不是摘要或最终请求的长度上限。
05.4.4 重建后的上下文
选中的用户消息恢复原有时间顺序,再追加一条带续接说明的摘要消息。
压缩后的历史 · 从上到下
保留的较早用户要求(边界可能截短)
保留的较新用户要求
保留的最新用户要求
续接说明 + 新摘要(一条用户消息)
↓
按当前状态重新插入提示词、规则和环境说明
↓
下一次模型请求:新历史 + 当前工具定义与调用设置
提示词、规则和环境说明按各自位置重新加入,不是统一追加在摘要后面。工具定义另行提供;已发生的工具结果通常由摘要承接,项目文件的实际修改仍保留。
摘要前固定加入的续接说明,完整中文译文如下:
另一个语言模型已经开始处理这个问题,并生成了其思考过程的摘要。你还可以访问那个语言模型使用过的工具状态。请据此继续已经开展的工作,避免重复。下面是那个模型生成的摘要,请利用其中的信息辅助自己的分析:
这段话要求模型沿用已有进展,不表示实际更换了模型。压缩结果写入会话检查点后,后续请求使用这份新历史继续任务。
05.5 压缩结果的保存与后续请求
- 生成新的初始说明。按替换后的模型、工具、项目和环境状态重建提示词说明。它们不是全部被摘要后就不再提供。
- 插入说明。优先放在最后一条真实用户或 Agent 消息之前;没有真实用户消息时,放在摘要或压缩项之前;都没有时才追加在末尾。需要前置的指令或声明仍放在历史前部。
- 替换当前历史。将新的历史写入运行时,更新上下文基准、窗口标识和压缩兼容信息。压缩准备期间后来接受的部分目标更新也会按代码规则保留。
- 保存检查点。会话记录写入 compacted、替换历史及相关恢复状态,供恢复会话时使用,并重新处理用量统计。
- 继续请求模型。下一次请求使用这份历史,加上本次有效工具定义与调用配置。
压缩改变的是模型接下来使用的历史,不会回滚已经修改的项目文件。普通压缩也不是删除整个会话文件;能否查看全部旧原文,还取决于记录保存策略。任务仍属于原会话,模型输入窗口已经更新。
05.6 压缩失败与重试
- 远程响应流失败:交给共享响应流错误处理代码判断是否可重试。这条路径的流重试预算为 min(Provider.stream_max_retries, 2),即最多额外重试两次;底层传输重试另行处理,不能把所有请求次数混成一个数字。
- 摘要请求超过窗口:有多条历史输入时,从前面移除最旧输入项,再尝试摘要;无法再缩减时返回错误。
- 摘要请求的其他错误:按 Provider 的流重试预算处理。取消、终止或会话预算耗尽会直接返回,不继续普通重试。
- 新任务开始前压缩失败:保存本次用户输入,报告错误,本轮不按成功路径继续。
- 任务结束后的压缩失败:通常保留已完成答复;取消与额度限制有各自的收尾处理。
这些重试针对压缩请求,不表示重新执行原任务已经完成的工具操作。远程请求未返回有效压缩项时,也不能把尚未验证的结果写成成功检查点。
05.7 压缩配置与新窗口模式
压缩触发比例和压缩后的缩减比例是两件事。窗口 90% 的规则决定什么时候开始压缩,不表示“压缩到原来的 90%”或“只保留 10%”。
当前实现没有一个统一的用户配置,能要求把上下文压到原来的某个百分比。最终大小由服务返回的压缩项或模型生成的摘要、保留的原文以及重新加入的运行说明共同决定。代码中的 64,000 远程原文预算和 20,000 摘要路径用户文字预算是固定常量,不是本章已列出的普通配置项;它们也不是最终整个上下文的上限。
如果想评估一次压缩的效果,可以在压缩前后分别统计用量:
剩余比例 = 压缩后用量 ÷ 压缩前用量
减少比例 = 1 − 剩余比例
例如从 180,000 缩减到 45,000,剩余 25%,减少 75%。这是用来解释效果的假设例子,不是 Codex 保证达到的目标。改变摘要提示词能影响摘要内容,却不能保证最终 token 比例。
另外,启用 TokenBudget 功能后,自动压缩入口可以改为建立新的上下文窗口,重新加入运行说明,而不请求模型生成摘要。该模式需要独立的信息续接机制,不能认为旧历史已经自动概括进新窗口。源码中的“压缩”事件或入口名称,不足以判断一定生成了摘要。
05.8 与 Pi 的区别
两者都在减少后续请求的历史,但保留哪些原文、怎样生成压缩结果以及怎样恢复当前规则,不能使用同一套说明。产品上尤其需要明确:用户要求是否保留原文、工具结果是否还可直接访问、压缩失败后任务停在哪里。
Q&A
压缩替换的是模型后续使用的历史,并保存压缩检查点。会话已保存的旧记录与模型下一次读取的内容不是同一份列表;它也不会撤销文件修改。
不会。记忆使用独立的旧会话筛选、提取和合并过程,不能把压缩检查点当成长期记忆。
两者都要让模型知道工作要求、已有进展和可用工具。真正需要比较的是同一个处理阶段:Pi 的 AgentContext 与 Codex 的 Prompt 都是内部数据结构;供应商收到的请求是后续转换结果,不能直接拿 Pi 的内部对象和 Codex 的完整请求比较。
共同点是:原始会话记录、当前模型输入和界面显示并不等同。压缩处理后续模型请求的内容,不会自动撤销文件改动;工具定义参与模型输入,也不意味着它会按旧对话相同的方式被摘要。
如果目标是自己组装 Agent、接入不同模型服务,并让团队容易观察和修改上下文,Pi 的结构更直接。系统提示词、消息和工具三个入口清楚,供应商差异集中在适配层;可读摘要也方便排查压缩后遗失了什么。这些优点来自接口与实现方式,不代表压缩质量一定更高。
代价是,产品需要自己安排环境变化、消息注入、工具配置和压缩后的状态一致性。某些模型专有的输入能力也需要适配,不能因为入口统一就假定所有供应商行为一致。
如果目标是围绕 Codex 现有内核设计长任务产品,Codex 对状态变化、输入类型和压缩续行的组织更细。它已有环境基准与变化说明、调用与结果整理、不同压缩路径及检查点,可以减少产品自行实现这些机制的工作。
代价是结构和分支更多,阅读和排查成本更高。Responses 类型及远程压缩行为与具体服务有关;使用其他服务时不能假定这些能力仍可用。加密压缩内容无法直接人工审阅,也不能仅凭格式判断信息保留质量。
对产品设计,建议借鉴 Pi 的清楚入口,也借鉴 Codex 对状态更新与恢复的约束。评估效果时分别观察任务完成率、压缩后遗漏、请求成本和失败恢复,而不是用字段多少或摘要是否加密判断先进程度。以上是根据源码结构作出的设计取舍分析,不是两者效果的实测排名。