← 返回 NeuroStack
源码基准:/Users/ryanchen/codespace/codex

Codex CLI internals

Goal 功能怎样把一次任务变成 Agent Loop

结论:goal 不是让模型自己“记住”一切,而是把目标持久化到线程状态库, 在每轮空闲时由运行时重新注入目标、预算和完成审计要求,再启动下一轮 turn。 这让 agent 可以跨多轮、跨压缩、跨 resume 继续工作,直到明确调用 update_goal(status = complete)

真正的循环

点击流程图节点查看源码角色。绿色回路是长时间工作的核心: 只要目标仍是 active 且线程空闲,运行时就会注入 continuation prompt 并启动下一轮。

Codex goal agent loop flowchart 带箭头的流程图,展示 goal 从设置、持久化、运行时注入、工具执行、turn 完成到再次 continuation 的循环。 /goal 或 create_goal 写入 thread_goals TurnStarted 工具完成后记账 模型继续行动 TurnFinished MaybeContinueIfIdle active + idle 回路 完成或预算停止 设置目标 /goal objective 持久化目标 thread_goals sqlite 启动或恢复运行时 mark active accounting 注入 continuation developer prompt 执行一轮 turn model + tools 进度记账 tokens + wall clock Turn 完成 active_turn cleared 空闲检查 no queued input 目标仍 active? yes: loop back 停止循环 complete / paused / budget

为什么它能长时间工作

长时间工作的能力来自运行时调度,不是来自一次模型请求的超长输出。 每轮结束后,系统主动决定是否再开一轮。

1. 目标在模型外持久化

thread_goals 表以 thread_id 为主键,保存 objective、 status、token budget、tokens used、time used。即使上下文被压缩,目标状态仍在 sqlite。

2. 每轮都会重新注入目标

continuation prompt 把 objective 包进 <untrusted_objective>, 附带预算与完成审计要求。模型每次循环看到的是当前持久目标,不依赖压缩摘要是否写全。

3. 空闲时自动开下一轮

TurnFinished 清掉 active turn 后调用 MaybeContinueIfIdle。 如果没有排队输入、没有触发消息、目标仍 active,就创建新 turn 并继续。

底层机制拆解

下面三个视角分别回答:命令怎么进来、状态怎么保存、压缩后怎么恢复。

命令层只负责入口,模型工具负责完成协议

TUI 的 /goal 会打开目标菜单或把 objective 交给 app-server 的 thread/goal/set。模型侧有三个 Responses API 工具: get_goal 读取状态,create_goal 创建目标, update_goal 只能设置 complete

get_goal 读取当前目标、预算和剩余 token。
create_goal 仅在无线程目标时创建 active goal。
update_goal 模型只能用它标记 complete。
thread/goal/set 外部 UI 可暂停、恢复、替换或清除。

Session runtime 是 loop 的发动机

GoalRuntimeEvent 把 turn 生命周期抽象成事件: TurnStarted 建立记账基线,工具完成时累计 token 和 wall clock, TurnFinished 结束本轮并在空闲时触发 MaybeContinueIfIdle。 continuation 候选成立时,运行时把 developer prompt 放进下一轮 pending input, 然后 start_task

active 允许 continuation loop。
paused 用户或中断停止自动继续。
budgetLimited 达到 token budget 后要求收尾。
complete 任务审计完成后终止 loop。

压缩只压缩对话历史,不压缩 goal 状态

compaction 会生成 summary,并用 replacement_history 替换内存中的对话历史;resume 时也优先按 compaction 记录重建历史。 但 goal 的 objective/status/usage 不在这段历史里,而在 state-db。 所以压缩后只要 goal 仍 active,下一轮 continuation prompt 会再次把目标显式注入模型上下文。

summary 保存历史语义,但可能丢细节。
state-db 保存不可丢的目标状态。
resume 恢复 runtime accounting 并发送 goal snapshot。
prompt 每次循环重新给出 objective。

完成判定

Codex 没有把“模型想停了”当作完成。完成需要显式工具调用,并且 prompt 要求先做审计。

怎样才算完成?

continuation prompt 要求模型把 objective 拆成可验证交付物,建立 prompt-to-artifact checklist,检查文件、命令输出、测试、PR 状态等真实证据。只有确认“目标已经达成且没有剩余必需工作”时, 才能调用 update_goal,并且唯一可写状态是 complete

预算到了算完成吗?

不算。达到 token budget 时状态变为 budget_limited,系统注入 budget-limit steering,要求停止新增实质工作、总结进度和剩余事项。源码和 prompt 都明确禁止因为预算接近或耗尽而标记完成。

用户打断会怎样?

中断会先进行当前进度记账,然后把 active goal 暂停为 paused。 暂停状态不会自动 continuation,除非用户或外部 API 再把它恢复为 active。

为什么不会无限空转?

continuation 需要满足空闲、无排队输入、无 trigger-turn mailbox、goal 仍 active。 源码还记录 continuation turn;如果某些 continuation 没有产生可计入的自主活动,会抑制下一次自动 continuation, 直到用户、工具或外部活动重置这个状态。

源码索引

这些文件是理解 goal 功能的最短阅读路径。

核心实现

  • codex-rs/core/src/goals.rs:goal runtime、continuation、记账、resume。
  • codex-rs/state/src/runtime/goals.rs:sqlite CRUD、预算状态迁移、usage accounting。
  • codex-rs/core/templates/goals/continuation.md:每次 loop 注入给模型的任务续跑 prompt。
  • codex-rs/core/templates/goals/budget_limit.md:预算耗尽时的收尾 steering。

入口和协议

  • codex-rs/tools/src/goal_tool.rs:Responses API 工具定义。
  • codex-rs/core/src/tools/handlers/goal.rs:工具调用处理,尤其是 complete 限制。
  • codex-rs/tui/src/chatwidget/slash_dispatch.rs/goal slash command。
  • codex-rs/app-server-protocol/src/protocol/v2.rs:app-server v2 goal payload。