
1. 从 Codex Goal 到 Loop Engineering为什么你的 Agent 循环总在失控如果你最近在折腾 Codex 的 Goal 功能大概率会有一种感觉它看起来只是给对话加了个“长期目标”但实际用起来任务的推进方式、停止条件、恢复逻辑都跟普通 chat loop 完全不是一回事。这背后其实藏着一个正在被反复讨论的概念——Loop Engineering也就是把开放式 Agent 循环改造成可恢复、可转向、可限流、可验收、可审计的程序化运行系统。Codex Goal 是理解 Loop Engineering 最好的入口。它没有把 Agent Loop 写成“while 模型没完成就继续”的自然语言循环而是把长期任务拆成可持久化目标、可审计状态机、可拒绝的 idle continuation、可注入的 steering、可计量的预算和可观察事件。换句话说它关心的不是让模型更努力地循环而是把每一次继续、转向、停止、恢复和完成都关进明确的状态边界与证据边界。这篇内容面向想让 Codex 任务循环可控可复现的开发者。我会先讲清楚 Goal、Thread、Session、Turn 这几个容易混淆的名词然后给出一个config.toml骨架把状态机流转配置落成可复制的文件最后演示一次从 Goal 触发到状态收敛的验证动作。你不需要读完 Codex 全部源码但跟着配置走一遍能对“Agent Loop 到底该怎么工程化”有一个可操作的认知。2. 前置准备TaoToken 接入与 Codex 环境确认在动手写config.toml之前先把模型接入这一层理顺。Codex 的 Goal 状态机本身是 runtime 行为但它依赖模型在 turn 内完成 tool call、update_goal 等动作所以模型端点的稳定性会直接影响状态收敛的可复现性。我这边用的是 TaoToken 的 API 端点官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。它的好处是兼容 OpenAI 风格的请求格式Codex 这类工具在配置模型端点时不需要额外适配层。你需要先拿到一个可用的 API Key。进入控制台创建密钥的路径是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 创建完成后把 key 保存到环境变量里不要直接写进config.toml明文。具体密钥管理页面在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。环境变量建议这样设置export TAOTOKEN_API_KEYsk-你的密钥 export TAOTOKEN_BASE_URLhttps://taotoken.net/api如果你还没装 Codex CLI先确认版本。Goal 相关的 lifecycle hook 在较新版本里才完整暴露旧版本可能只有基础的 chat 能力。可以用下面的命令检查codex --version确认版本后把模型端点指向 TaoToken。Codex 的模型配置通常走~/.codex/config.toml我们下一步就在这个文件里同时完成模型接入和 Goal 状态机配置。3. config.toml 骨架把 Agent Loop 状态机写进配置Codex 的config.toml不只是模型参数文件它同时承载了 Goal 状态机的运行时行为。下面这份骨架是我实测下来比较稳的结构分成模型接入、Goal 状态机、continuation gate、预算与停止线四块。你可以直接复制后按需改。# ~/.codex/config.toml # ---------- 模型接入 ---------- model gpt-5-codex model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api responses # ---------- Goal 状态机 ---------- [goal] enabled true # 目标持久化到 thread 级状态表而不是只放在 prompt 里 persist true # 模型只能声明 complete / blocked不能自行 pause / resume model_can_set [complete, blocked] # 用户或外部 API 才能设置的状态 system_owned [paused, usage_limited, budget_limited] # ---------- continuation gate ---------- [goal.continuation] # 只在 thread idle 时尝试续跑 require_idle true # Plan mode 下拒绝自动续跑 allow_in_plan_mode false # 有 pending trigger-turn mailbox 时拒绝 allow_with_pending_mailbox false # 已有 active turn 时拒绝 allow_with_active_turn false # ---------- 预算与停止线 ---------- [goal.budget] # 单个 goal 的 token 上限 token_budget 200000 # 时间上限单位秒 time_budget_seconds 3600 # 触顶后只允许总结不允许开新实质工作 on_limit summarize_only # ---------- 可观测事件 ---------- [goal.events] emit_thread_goal_updated true emit_turn_attribution true这份配置里最关键的是[goal.continuation]这一段。它对应的是 Codex 源码里try_start_turn_if_idle的拒绝条件input 为空、有 pending trigger-turn mailbox、当前是 Plan mode、已经有 active turn这四种情况都会让自动续跑被拒绝。把这些条件显式写进配置好处是状态机的边界不再藏在代码里而是变成可审计的声明。[goal.budget]这一段对应的是状态库里的account_thread_goal_usage逻辑每次 token usage 累加后如果tokens_used token_delta token_budget状态就从 active 转成 budget_limited。配置里写死预算比让模型自己“感觉差不多了就停”要可靠得多。[goal]里的model_can_set和system_owned是权限分权的核心。模型可以判断“我认为目标完成了”或“我被阻塞了”但不能判断“我要暂停”“我要恢复”“我触发 usage limit”。这些状态归用户或系统所有。Loop Engineering 的重点之一就是分清“模型判断”和“runtime 裁决”。4. 验证请求从 Goal 触发到状态收敛的完整动作配置写完后需要跑一次完整的 Goal 生命周期确认状态机真的按预期流转。下面用一个最小可复现的任务来演示让 Codex 修一个测试文件里的失败用例并提交可验证结果。第一步启动 Codex 并创建一个 goal。你可以用模型对话入口先确认端点连通 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。如果只是想快速验证模型是否正常响应这一步就够了。第二步在 Codex 里创建长期目标。Goal 的创建走create_goaltool模型会把它写入当前 thread 的thread_goals。你可以用这样的 prompt 触发创建一个 goal把 tests/test_parser.py 里失败的用例修到全绿 并给出可验证的测试输出。完成前必须逐项审计证据。第三步观察 turn 绑定。当 goal 创建后下一次 turn start 会读取当前 thread goal。如果状态是 Active 或 BudgetLimitedruntime 会把当前 turn 绑定到 goal_id。这一步在配置里对应persist true它保证 token usage、tool finish、turn stop 的消耗能归因到正确的 goal_id。第四步触发一次 idle continuation。当 turn 结束、thread 进入 idle 后runtime 会调用continue_if_idle。如果配置里require_idle true且没有 pending mailbox它会构造 continuation steering然后调用try_start_turn_if_idle启动下一轮 regular turn。你可以在日志里看到类似这样的流转[goal] thread idle detected [goal] continuation steering constructed [goal] try_start_turn_if_idle accepted [goal] new regular turn started, bound to goal_idgoal_abc123第五步验证状态收敛。当模型完成修复并调用update_goal complete后状态从 Active 转为 Complete。如果中途 token 触顶状态会转为 BudgetLimited并且 runtime 会注入 budget limit steering告诉模型收束不要再开启新实质工作。你可以用下面的命令检查状态表codex goal status --thread-id 你的thread_id预期输出类似goal_id: goal_abc123 status: complete objective: 把 tests/test_parser.py 里失败的用例修到全绿 tokens_used: 48213 token_budget: 200000 time_used_seconds: 312如果状态停在budget_limited而不是complete说明预算设置偏紧或者模型在无效路径上消耗了太多 token。这时候不要直接调大预算先看 turn attribution确认 token 花在了哪个 tool call 上。5. 本篇常见错排查状态机不收敛的几种典型情况配置和验证跑通后实际使用中还是会遇到状态机不按预期流转的情况。下面是我踩过的几个坑按出现频率排序。第一种continuation 一直不触发。最常见的原因是 thread 没有真正进入 idle或者有 pending trigger-turn mailbox。检查配置里allow_with_pending_mailbox是否为 false以及是否有未处理的用户 steer。如果当前处于 Plan modeallow_in_plan_mode false也会让自动续跑被拒绝。Plan mode 会清掉当前 turn goal这是 Codex 区分“规划”和“执行”的设计不要把 plan turn 当成 goal progress。第二种goal 状态卡在 active 但模型不再推进。这通常是因为模型没有正确调用update_goal或者 tool schema 里model_can_set配置过窄。确认配置里允许complete和blocked这两个是模型唯一能设置的状态。如果模型反复声称完成但状态没变检查update_goal的调用是否带了正确的expected_goal_id旧 turn 或旧 mutation 误伤新 goal 的情况在并发场景下会出现。第三种token 消耗远超预期。Goal 的记账逻辑是累加式的每次on_token_usage都会把当前 turn 的 usage 加到tokens_used上。如果发现消耗异常先看time_used_seconds和tokens_used的比例。如果时间很短但 token 很高可能是 tool output 太长或者模型在重复读取同一个文件。这时候可以在配置里加一个 tool output 截断策略或者把token_budget调低让状态更早进入 budget_limited。第四种turn error 后 goal 没有转为 blocked。stop_active_goal_for_turn会在 turn error 或 usage limit 时先记账再用expected_goal_id更新状态并清掉 active goal。如果状态没变检查 event emitter 是否正常以及emit_thread_goal_updated是否为 true。外部观察依赖事件事件丢了UI 和 SDK 就看不到状态变化。如果你在接入或排障过程中遇到端点层面的问题可以先到 API Keys 页面确认密钥状态 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 接入细节看文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。如果只是验证模型是否正常响应用模型对话入口最快 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。6. 长期编码与 Agent 场景把状态机配置沉淀成可复用资产如果你打算把 Codex Goal 用在长期编码任务或 Agent 工作流里单次配置是不够的。你需要把状态机配置沉淀成可复用资产让不同的 thread 和 goal 都能继承同一套边界。一个实用的做法是把config.toml里的[goal]段拆成独立的 profile 文件按任务类型加载。比如修 CI 的任务用一套预算和 continuation 策略重构任务用另一套。Codex 支持通过环境变量或命令行参数指定 profile这样你不需要每次手动改配置。对于需要长期运行的编码 Agent建议把 Coding Plan 纳入考虑 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。它更适合有持续 token 消耗和状态恢复需求的场景配合 Goal 的持久化状态可以在进程重启后从thread_goals恢复 active goal而不是从头开始。如果你在用 Claude Code 或 Anthropic 风格的 Agent 工作流TaoToken 也提供了对应的接入入口 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode-anthropicutm_campaignrewrite 。状态机的设计思路是通用的区别只在于 tool schema 和 lifecycle hook 的挂载点。最后回到 Loop Engineering 本身。Codex Goal 最值得学习的不是 goal 这个功能名而是它的构建顺序先定义目标契约再定义状态所有权再定义执行线边界再定义 continuation gate再定义预算和停止线最后定义可观测事件。这个顺序反过来就是大多数 Agent 循环失控的原因——先写了循环再补边界结果边界永远补不齐。把config.toml当成状态机的声明文件来写而不是当成模型参数来写是让 Agent Loop 可控可复现的第一步。