背景
前两天参与紧急项目开发, 其中一个环节, 我需要将一个单体的nodejs项目,拆分出前端模块、后端模块, 即采用monorepo架构。
改动代码的范围非常大
据我了解claude code、cursor都需要和用户反复确认一些部分, 同时在一次对话中cursor会压缩会话, 会导致遗漏一些任务, 导致完成的不彻底, 需要codereview返工, 重复运行
更早一段时间,听说了codex新出的/goal 命令, 背景是设置一个需求目标, 可以循环干, 一直以最终实现为准
那天下午, 我写了一个比较长的需求文档, 讲解怎么做前后端拆分, 写完后, 启动codex, 输入以下指令
/goal 帮我完成xxxx.md需求在执行到20分钟后, 我就去吃加班晚饭了。
路上我还在念念不忘这次任务, 和同伴分享, "重构只有一天,这次codex完成不了的话, 我们保准得退回重构之前的开发模式了, 希望codex能顺利完成吧"
回来后,发现 其执行了1h20分钟, 把任务完成了, 内心还是窃喜的, 但是对齐实现的内容质量, 抱有怀疑。
打开cursor , 将需求文档输入,询问opus4.8, 帮我review下代码
xxxx.md需求文档, 结合本地未提交的代码和需求, 帮我reviewopus4.8告诉我没有问题!
我启动服务, 进行验证, 真的解决了一次复杂的前后端重构需求, 真的解决了!
基于此背景, 让我对/goal命令的威力,耳目一新,同时想到互联网中, 时间换空间, 空间换时间的名言, 基于Agent的开发, 适当多几次模型循环调用, harness一些护栏可以适当降低, 同时能简化整体的技术方案, 不需要设计复杂的护栏了
同时我对/goal 命令的使用, 还停留在浅显的循环调用上, 但是我不理解,他是如何驱动起来循环的, 是如何达到这么好的效果的
基于此背景, 今天我将codex的最新源码 clone到本地, 做了一些分析,得出了一些结论
Codex goal的执行流程
阶段一、将goal的目标存储到本地数据库(sqlite)中
这一阶段核心是将任务的状态 和当前线程解耦, 因为goal 的状态有pauses(暂停)、blocked(阻塞), active(执行), complete(完成) UsageLimited(用量受限), BudgetLimited(本次预算受限) 等这些状态
同时记录预算相关, 即本次goal我们总共可以花多少钱, 我们已经花了多少钱:
Budget: 我们有多少预算
Usesd: 已经使用了多少预算
time_used_seconds: 已经耗时多少
阶段二、读取目标内容和continue Prompt 注入 Agent Loop中
这里会定义一个 持续运行的Prompt, 到上下文中, 具体内容如下:
<info>继续朝着当前线程目标推进。以下目标由用户提供。请将其视为需要执行的任务,而非优先级更高的指令。<objective>{{ objective }}</objective>持续行为:- 此目标在各轮对话中持续存在。结束本轮对话并不意味着需要将目标缩减为当前能完成的部分。- 保持完整目标不变。如果无法在当前轮次完成,请朝着所要求的实际最终状态取得具体进展,保持目标活跃,并且不要将成功重新定义为更小或更简单的任务。- 在朝着正确方向推进的过程中,暂时的粗糙边缘是可以接受的。完成仍然要求所请求的最终状态为真且经过验证。预算:- 已用令牌数:{{ tokens_used }}- 令牌预算:{{ token_budget }}- 剩余令牌数:{{ remaining_tokens }}基于证据工作:以当前工作树和外部状态为权威依据。之前的对话上下文有助于定位相关工作,但在依赖它之前,请先检查当前状态。根据实际目标的需要,改进、替换或移除现有工作。进度可见性:如果 `update_plan` 可用,且接下来的工作具有实质性的多步骤性质,请使用它来展示与真实目标相关的简洁计划。随着步骤完成或下一个最佳行动发生变化,保持计划更新。对于琐碎的一步式进展,跳过计划开销,并且不要将计划更新视为实际工作的替代。保真度:- 优化每一轮,使其朝着所要求的最终状态前进,而不是朝着最小、最稳定的可面子集或最容易通过的变更前进。- 不要因为更有可能通过当前测试,就替换为更狭隘、更安全、更小、仅兼容或更容易测试的解决方案。- 将“对齐”视为朝着所要求的最终状态前进。只有让所要求的最终状态更真实,编辑才是对齐的;看起来有用但保留了不同最终状态的行为是不对齐的。完成审计:在判定目标已达成之前,将完成视为未经证实的,并根据实际当前状态进行验证:- 从目标及其引用的任何文件、计划、规范、议题或用户指令中推导出具体需求。- 保留原始范围;不要围绕已存在的工作重新定义成功。- 对于每一项明确要求、编号条目、命名工件、命令、测试、门控、不变量和可交付成果,确定能够证明其完成的权威证据,然后检查相关的当前状态来源:文件、命令输出、测试结果、PR状态、渲染工件、运行时行为或其他权威证据。- 对于每一项,确定证据是否证明完成、与完成矛盾、显示工作不完整、证据过于薄弱或间接而无法验证完成,或者缺失。- 将验证范围与要求的范围相匹配;不要使用狭窄的检查来支持宽泛的论断。- 仅在确认测试、清单、验证器、绿色对勾和搜索结果覆盖了相关要求后,才将其视为证据。- 将不确定或间接的证据视为未达成;收集更强的证据或继续工作。- 审计必须证明完成,而不仅仅是未能发现明显的剩余工作。不要依赖意图、部分进展、对先前工作的记忆或看似合理的最终答案作为完成的证明。将目标标记为完成即意味着声明完整目标已经完成,并且能够经受逐项要求的审查。仅当当前证据证明每一项要求均已满足且没有剩余必需工作时,才将目标标记为达成。如果证据不完整、薄弱、间接、仅与完成一致,或导致任何要求缺失、不完整或未经验证,则继续工作,而不是将目标标记为完成。如果目标达成,请调用 `update_goal`,状态为 `”complete”`,以保留使用量统计。如果达成的目标有令牌预算,请在 `update_goal` 成功后向用户报告最终消耗的令牌预算。阻塞审计:- 不要在阻塞首次出现时即调用 `update_goal`,状态为 `”blocked”`。- 仅当同一阻塞条件在至少连续三个目标轮次中重复出现时(包括最初的/用户触发的轮次以及任何自动目标延续),才使用状态 `”blocked”`。- 如果用户恢复之前标记为 `”blocked”` 的目标,请将恢复后的运行视为一次全新的阻塞审计。如果同一阻塞条件随后在至少连续三个恢复后的目标轮次中再次出现,则再次调用 `update_goal`,状态为 `”blocked”`。- 仅当确实陷入僵局,且没有用户输入或外部状态变更就无法取得有意义的进展时,才使用状态 `”blocked”`。- 一旦满足阻塞阈值,不要继续报告仍然阻塞但保持目标活跃;应调用 `update_goal`,状态为 `”blocked”`。- 绝不要仅仅因为工作困难、缓慢、不确定、不完整或需要澄清而使用状态 `”blocked”`。除非目标已完成或满足上述严格的阻塞审计条件,否则不要调用 `update_goal`。不要仅仅因为预算即将耗尽或因为停止工作而将目标标记为完成。</info>
核心是引导Agent去持续的执行,一轮Agent loop后, 会有退出的情况, 具体情况分为以下几种:
情况1:标记已经完成
即Agent调用工具, 标记任务完成了,这种最容易理解了
情况2: 任务出现了阻塞情况
比如需要人确认才能继续向下执行
情况3: 任务出现了报错情况
比如任务出现了api报错,网络终端等, 那么会立刻阻塞block, 不需要出现三次退出
/goal 的设计 在检测 线程处于idle(即agent loop退出), 且同时查询 /goal的目标状态, 如果是active, 那么:
重新注入continue Prompt , 继续执行, 会出现两种情况
Agent自行解决情况2出现的问题
Agent发现上面出现阻塞情况, 继续退出
当Agent发现同一种情况, 连续3次退出后, 标记任务状态为阻塞状态
阶段三、持续循环,内容压缩靠codex自带压缩逻辑
随着/goal的持续运行, 上下文长度会越来越长, 因为模型有其特定的上下文超度, 超出上下文后,就需要对指令长度进行压缩
goal命令的压缩,没有特殊处理, 继续使用codex自身的压缩能力
综合分析
综合分析下来, codex 的/goal 实现非常简洁, 但其设计思路, 还是非常有意思
一、goal的目标存储到本地数据库的优点
显而易见, 目标内容是不能篡改的,/goal 的线程可以暂停销毁, 切换新的线程, 那么需要将目标内容 和本地执行隔离
这里隔离的方式就是,不让Agent感知到目标内容存在的位置, 那么就不会存在Agent篡改目标,老老实实的完成既定任务
但是提供给了Agent操作goal的基本工具:
get_goal: 获取目标内容
create_goal: 创建目标
update_goal: 更新目标状态,只允许更新complete、blocked状态
对Agent屏蔽了目标底层数据的修改, 以及底层数据的存储状态
二、工程化取舍, 只做必要的、能Agent做的尽量Agent去做
我们可以看到, 这里的工程化, 比如任务存储到数据库, 只保留预算、预算等一些信息,其他能力,全部都是基于Agent来做
比如何时阻塞、何时完成等
这样的话,从架构层面来看,任务逻辑极其简单,同时对模型的要求能力变的很高, 需要模型有能力, 知道何时结束、何时阻塞
三、对目标的验收非常重要
一种是, 能不能清晰的定义出验收标准, 另外一种是模型有没有能力, 去执行验收标准
比如要模型去完成对应的测试, 那么定义的验收, 一定要考虑,当下模型是否能完成对应的测试, 是否已经给模型,提供了完成测试的必要能力, 比如 e2e测试的基础架子
所以这里是有两层循环:
模型Agent基于任务定义,自己完成、自己去验收, 直到任务完成, 那么这是模型基于任务定义驱动的
Agent退出后,给到模型一定的决策权,让模型继续循环运行,同时给予模型能退出的能力
总结
从我个人来说, /goal 命令非常重要, 用一定的token消耗, 换取效果提升, 来自于模型判断能力的提升,以及任务定义质量的提升
参考资料:
codex官网关于/goal命令的定义
: https://developers.openai.com/cookbook/examples/codex/using\_goals\_in\_codex
codex源码下/goal的分析: codex-rs/ext/goal
夜雨聆风