想象一个很常见的场景:你让 Codex 帮你重构一个模块,它一口气干了十几轮,改动了十几个文件。聊到一半你突然意识到,第五轮的思路才是对的,后面全都跑偏了。你想回到第五轮那个节点重新出发,但又不希望它已经改过的那些文件被一并抹掉。这时候你最想要的是什么?一个「对话回滚」按钮,能把聊天历史退回到某个时间点,重新开始。

这样的能力,Codex 最近真的做出来了。8 月 13 日,OpenAI 在 codex 仓库合入了一个提交(#38440),给 app-server 协议加了一个实验性的 thread/revert 请求。它的作用一句话就能讲清:把一个加载中的分页线程的持久化历史,替换成某个 turn 之前的「前缀」,同时保留线程 ID 不变。

先搞清楚「线程」和「分页线程」是什么#

在 Codex 里,thread(线程)可以理解成一次和 agent 的完整对话会话。你启动一个任务,它会经历很多轮 turn(轮次),每一轮里 agent 会读文件、写文件、跑命令,然后给你一个结果。所有这些轮的记录,构成了这个 thread 的历史。

app-server 则是 Codex 桌面端等客户端与 Codex 后端之间的协议层,走的是 JSON-RPC。客户端通过 thread/resume 恢复一个会话,通过 turn/start 发起新的一轮,诸如此类。也就是说,你想回滚对话,最终都要落到这个协议层的某个请求上。

这里有个背景值得一提:Codex 正在把线程从「内存态」迁到「持久化 + 分页」。老式线程会把整个历史投影到内存里,客户端一次拿到全部 turns。而较新的「分页线程」(paginated thread)把历史持久化存储,客户端可以像翻页一样按需、通过游标(cursor)向后拉取历史,而不是一次性全量加载。这样做的收益很明显,超长会话不会撑爆内存;代价是,像「回滚」这类操作变复杂了,因为历史不再是一段随手可切的数组,而是分散在持久化存储里的、带游标的分页数据。

thread/revert 到底做了什么#

thread/revert 的语义可以拆成几条来看:

  • 它接收两个参数:thread_idbefore_turn_id。后者是「分界点」,从这一轮开始,连同之后的所有轮次,都会被从持久化历史里砍掉,只留下它之前的「前缀」。
  • 它只改「持久化的对话历史」,不动本地文件。这是最关键、也最容易误解的一点:回滚的是「聊天的记忆」,不是「改过的代码」。
  • 如果此刻有一轮 turn 正在跑,它会先把这一轮打断。
  • 它保留线程 ID 不变,也保留线程的那些可变设置,只是把历史重载一遍。
  • 完成后,它会发一条 thread/reverted 通知,并返回两个向后翻页的游标(turns 和 items 各一个),方便客户端继续按需拉取剩下的历史。
  • 旧的 rollout 文件保持不可变;revert 之后,那些指向「已被砍掉的历史」的旧路径会被判定为过期而拒绝。

和已废弃的 thread/rollback 有什么区别#

thread/revert 之前,Codex 其实已经有一个 thread/rollback,但它被标记为 deprecated,很快就要移除。两者的区别值得说清楚。

rollback 做的是「从内存里的上下文丢掉最后 N 轮,并在 rollout 里写一个 rollback 标记,让未来的 resume 看到被裁剪后的历史」。它只适用于老式线程,分页线程根本不支持 rollback。而且它是粗粒度的,只能「丢掉最后 N 轮」,不能精确到某个具体的轮。

thread/revert 恰恰是给分页线程补上的那一块。它用「替换持久化历史」的方式实现回滚,粒度更细,可以直接指定到某个 turn 作为分界点。某种意义上,revert 是 rollback 在分页线程时代的精神继承者,而且做得更精确。

我的解读:为什么这件事值得关注#

剥开协议层的细节,这个功能背后有三个信号,我觉得比功能本身更值得看。

第一,对话状态和文件状态被明确拆开了。 文档里特意强调「不回退本地文件改动」,这个默认值很克制,也很正确。它说明 Codex 团队把「agent 的记忆」和「agent 的产出」当成两套不同的状态来管理。这跟 git 的思路很像:git reset 改的是提交历史,工作区里的文件要不要动是另一回事,取决于你用的是 --soft 还是 --hard。Codex 这里选择了「只动记忆,不动产出」,把文件系统留给你自己处理。对一个还在实验期的功能来说,这是最安全的默认值。

第二,回滚是 agent 协作的底层能力。 一个 agent 要能「试错」,就得能「退回」。没有回滚,每一次跑偏的代价都很高,要么手动清理一堆改动,要么干脆重开一个会话。thread/revert 把「从某个节点重新出发」变成协议里的一等公民,等于为后面做「对话分支」「自动回溯」「A/B 试错」这些更高级的玩法铺了路。分支和回滚,从来都是一对。

第三,分页线程正在成为新的默认。 rollback 被废弃、revert 只针对分页线程,这两个信号放到一起看,说明 Codex 的重心正在从「把历史塞进内存」转向「持久化 + 分页」。这对超长会话、对 agent 长时间自治运行都是必要的地基。你想想,让 Codex 连跑十几天的自动研究那种场景,历史一定长得惊人,没有分页和回滚,中间想纠正一次方向都无从下手。

结语#

thread/revert 目前还藏在 capabilities.experimentalApi 后面,属于实验能力,协议和语义未来都可能变。但它指出的方向很清晰:Codex 正在把「对话」当成一种可以被持久化、分页、回滚、甚至将来可能被分支的状态来对待。

对普通用户来说,这意味着将来你在 Codex 里聊到一半发现方向错了,也许真能一键回到某个节点重新来,而不是靠删文件、删会话这种粗暴的方式。对做 agent 基础设施的人来说,这是一个值得盯着的信号:可回滚的对话状态,是长期自主 agent 拼图里很关键的一块。

参考来源:

  • GitHub commit openai/codex@4343b2b(#38440):Add app-server support for reverting paginated threads
  • Hacker News:Codex adds experimental thread/revert support
  • codex-rs/app-server/README.md(app-server 协议文档)