/goal 目标系统

/goal 是 Codex 扩展架构里很好的示例:它把一个看似 prompt 层的需求,做成了持久化、可记账、可续跑、可被工具控制的系统。

Goal Runtime

三个工具

工具定义在 codex-rs/ext/goal/src/spec.rs

  • get_goal:读取当前 thread goal,包括状态、预算、已用 token/时间。
  • create_goal:显式创建目标,可选 token budget。
  • update_goal:只能把目标标记为 completeblocked

update_goal 的描述故意很严格:不能因为预算快用完就 complete,也不能随意 blocked;pause/resume/budget-limited/usage-limited 由系统或用户控制。

状态持久化

GoalToolExecutorcodex-rs/ext/goal/src/tool.rs 中实现。创建 goal 时,它会写入 codex_state::StateRuntime.thread_goals(),而不是只把目标放进对话历史。

状态字段包括:

  • objective
  • status
  • token budget
  • tokens used
  • time used

这让目标可以跨 turn、跨 resume 存活。

Runtime 生命周期

GoalExtension 实现了多个 contributor:

  • ThreadLifecycleContributor
  • ConfigContributor
  • TurnLifecycleContributor
  • TokenUsageContributor
  • ToolLifecycleContributor
  • ToolContributor

它在 thread start 时初始化 GoalRuntimeHandle,在 thread resume 时恢复 active goal,在 thread idle 时尝试继续目标。

自动续跑

GoalRuntimeHandle::continue_if_idle 的流程:

  1. 检查 goal tools 是否可见。
  2. 加锁,避免外部 goal mutation 与续跑竞争。
  3. 从 state DB 读取当前 active goal。
  4. 构建 continuation_steering_item
  5. 调用 thread.try_start_turn_if_idle(vec![item])
  6. 如果线程忙、Plan mode 或有 pending trigger turn,就放弃本次自动续跑。

这说明 /goal 续跑不是强行插队,而是尊重线程调度和 Plan mode。

预算记账

GoalAccountingState 跟踪:

  • 当前 turn id。
  • turn 内 token baseline 和 delta。
  • wall-clock active goal。
  • 是否已经报告过 budget limit。

记账触发点包括:

  • token usage event:记录当前 total token usage。
  • tool finish:工具完成后结算增量,必要时注入 budget limit steering。
  • turn stop/abort:结算 active goal 进度。
  • turn error:把 active goal 标记为 blocked 或 usage-limited,避免错误循环继续耗 token。

Steering Item

codex-rs/ext/goal/src/steering.rs 把 goal 转成内部上下文 item:

  • continuation
  • budget limit
  • objective updated

这些 item 使用 InternalModelContextFragment 注入模型,来源标记为 goal。这比普通用户消息更清楚:它是系统为了执行目标注入的 steering,不是用户新发的需求。

可学习的设计

/goal 展示了一个优秀扩展应该具备的边界:

  • 用工具显式创建和终止目标。
  • 用 state DB 持久化目标。
  • 用 lifecycle contributor 记账和恢复。
  • 用 idle continuation 续跑,但尊重线程调度。
  • 用 steering item 和模型沟通目标状态。
  • 错误、预算、usage limit 都有明确状态迁移。