源码调查日期:2026-09-26。本文的“Pi goal”特指第三方扩展
code-yeongyu/pi-goal,并非 Pi 核心自带的一套唯一实现。DSH 指deepseek-ai/deepseek-harness。以下结论来自固定提交的静态源码检查;操作步骤未调用付费模型做端到端实测。
先给结论
/goal 不是让模型突然拥有可靠的“长期记忆”。它通常由五部分组成:目标状态、模型可见的目标工具、人类命令、空闲后的下一轮调度、完成或阻塞的退出规则。模型仍在普通 agent turn 中读文件、改代码、运行工具;框架负责在 turn 结束后判断能否再送入一条续跑消息。完成判断主要仍由模型提出,因此“状态是 complete”不能代替验收证据。


图中三条路线分别标出状态存储和续跑入口;各节点依据下文所列源码。
调查范围与复核方法
| 项目 | 固定提交 | 本地源码 | 主要入口 |
|---|---|---|---|
| Codex | e72da2b53805894878023d01949a25a082e0a5cb | sources/codex | ext/goal、GoalStore |
| Pi 核心 | d6af72e1857cfb10b41d8ff8e69f0d72b4cf6d31 | sources/pi | 扩展 API 文档 |
| Pi goal 扩展 | c2e1e732fa12eec1da2a47170c59dfa61fc547c9 | sources/pi-goal | src/index.ts |
| DSH | 477b4f420553e8a52c2fbccc464d7561b239c443 | sources/deepseek-harness | packages/goal |
下载时采用浅克隆,以下命令可在空目录复现。若仓库 main 已前进,需先抓取表中的提交,再切换到它;仅在新的浅克隆上直接 git checkout <旧 SHA> 可能失败。
1 | git clone --depth 1 https://github.com/openai/codex.git |
这里的 Pi 主仓库以当前 pi.dev 安装页指向的 @earendil-works/pi-coding-agent 为准。Pi 提供 registerCommand、registerTool、sendMessage 和 appendEntry 等扩展原语;本文锁定的 /goal 行为由第三方扩展注册。Pi 的官方扩展文档还特别提醒:若状态应跟随会话分支,需要从活动分支重建状态。依据:扩展 API 与状态章节。
一、Codex:线程 SQLite 状态加空闲续跑
入口。 TUI 的 /goal 受 Feature::Goals 控制;本次提交把 goals 标成稳定功能且默认开启。裸 /goal 显示目标摘要与可用命令,/goal edit、pause、resume、clear 由 TUI 分发;模型侧另有 get_goal、create_goal、update_goal 三个工具。Goal 要求持久线程,临时线程不能创建它。依据:功能开关、TUI 分发、摘要呈现、持久线程要求、工具定义。
状态。 一条线程对应 thread_goals 表的一行,保存 goal_id、目标、状态、token 预算、已用 token 与耗时。insert_thread_goal 的 SQL 只允许替换已完成目标,所以并发创建未完成目标时数据库会拒绝第二次写入。模型工具允许创建、完成、阻塞;“暂停须由用户明确请求”是工具说明中的模型行为规则,handle_update 本身只校验状态枚举,没有验证请求来源。恢复与预算限制由宿主控制。依据:表结构、条件插入、工具说明、工具执行。
续跑。 扩展接到 thread idle 事件后,先读持久状态、检查延迟标记和当前线程,再调用 start_turn_if_idle 投入一条内部目标消息。普通会话恢复时,如果数据库状态仍为 active,运行时会重新标记目标并允许之后续跑。fork 有一个默认关闭的实验选项 deferGoalContinuation:仅在调用方启用它、存在持久 rollout 路径且 Goals 启用时,fork 才继承目标并写入延迟标记,阻止自动续跑直到首次显式 turn 启动。目标正文在模板中被 XML 转义,并明确声明为用户数据;模板要求逐项检查交付物后才标记完成。模型自行报告“同一阻碍持续三轮”由工具说明和提示词约束;另有执行失败、空 turn 等宿主自动阻塞路径,不能把三轮规则理解成所有 blocked 状态的统一硬门槛。依据:生命周期钩子、fork 参数、继承条件、延迟写入、显式 turn 清除、空闲调度、续跑模板、自动阻塞计数。
二、Pi:扩展拼装 Goal,本文实例用会话目录 JSON
入口。 pi-goal 在扩展入口分别注册命令、工具和生命周期处理器。/goal <objective>、/goal、pause、resume、clear 由命令解析;模型工具有 create_goal、get_goal、update_goal。模型可设置 complete 或 blocked,人类命令负责暂停与恢复。依据:扩展入口、命令解析、工具注册。
状态。 扩展把目标写到会话目录下、以 session ID 编码命名的 .json 文件;无持久会话时回退到 Pi 配置目录,但每次新会话仍有新 ID,不能据此推断跨进程会自动恢复。readGoal → 修改 → writeFile 是普通文件读写,代码没有在这些函数中使用文件锁、版本 CAS 或原子重命名。这个事实意味着并发写入可能丢更新,但本文未做并发运行试验,故这是源码推出的风险,而非已复现缺陷。依据:路径、读写与更新、会话 ID 生成。
续跑。 session_start 读到 active 目标且 agent 空闲、没有待处理消息时,以及 agent_end 后目标仍 active 且没有待处理消息时,扩展用 pi.sendMessage({display:false}, {triggerTurn:true, deliverAs:'followUp'}) 排入隐藏消息。暂停的目标在交互式 resume 时询问用户。中断 turn 被扩展识别为 blocked 并停止续跑;README 自述扩展 API 缺少中断来源,因此也可能把系统中断标成“用户中断”。依据:生命周期、排队条件、投递、项目说明。
三、DSH:事件溯源状态、进程内授权和有编号的续跑轮
DSH 将功能拆成四个包:goal 管状态、tool-goal 供模型调用、command-goal 供人类使用 /goal、goal-round-driver 负责续跑。完整功能要求宿主与 agent 预设的组合中四包齐备;Web 组合把 goal 和 driver 装在宿主层,standard 预设提供命令和模型工具。依据:goal 包组、宿主配置、Web 组合调整、standard 预设。
持久层。 每次 goal 变更写入 Session 的 goal/change 事件;投影重放得到当前目标。更新必须带当前 {id, revision},旧 revision 会被拒绝。armed/disarmed 则只存在进程内:新 agent 或恢复、fork 后,即使持久 phase 是 active,也不会自动续跑,必须明确 resume 重新授权。依据:投影与事件、激活与修订检查、CAS 检查。
调度层。 driver 等整个 agent idle;若有待持久化检查点,先 flush Session,然后为下一轮预留 {goalId, revision, round},用 agent.followup 排队;agent/pre-step 再核对预留是否仍有效,用户消息或目标变更能使过期轮失效。真正进入会话的 goal 消息才消耗轮次。目标仍 active + armed 且下一次调度时,达到 maxGoalRounds 才写入 round-limit 阻塞;默认上限为 256。宿主发起 pause 会取消正在运行的 turn,模型自己在 turn 内 pause 则正常结束该 turn。依据:调度与预留、竞争与过期校验、默认上限。
权限。 DSH 允许模型从明确的人类长期请求推断建 Goal;create_goal 会调用 requireDirectHuman,更新使用 ID/revision。自动 goal round 报 blocked 时,代码硬检查累计已进入会话的 goal 轮数,默认至少 3 轮;没有代码验证同一阻塞条件连续存在三轮,这部分仍靠提示词。直接人类轮次不受该轮数检查限制。依据:创建策略和配置、创建权限、阈值执行、直接人类轮次测试。
四、差异一览
| 维度 | Codex | Pi + 本文扩展 | DSH |
|---|---|---|---|
| 功能归属 | 主仓库 Goal 扩展,默认稳定开关 | Pi 扩展 API + 第三方插件 | 独立 goal 插件组 |
| 状态源 | SQLite thread_goals | 会话目录 JSON 文件 | Session 事件日志及投影 |
| 自动续跑 | thread idle 后 start_turn_if_idle | agent_end / session_start 后隐藏 followUp | agent idle,预留并验收 goal round |
| 恢复会话 | 普通 active 会话可续跑;启用实验选项的 fork 才继承并延迟目标 | active 且空闲、无待处理消息时排队;fork 通常另用新 ID | active 但 disarmed,待明确 resume |
| 预算 | token 阈值、使用量、耗时;当前 turn 可能超预算 | 记录 token 和耗时;当前命令与 create 工具没有预算参数 | 轮数上限,默认 256;goal 子系统不计 token 预算 |
| 并发防护 | 创建有 SQLite 条件写入、续跑有 idle 检查;模型更新无版本 CAS | 目标文件读改写,无显式 CAS | goal ID/revision、预留身份、前置校验 |
| 模型报告 blocked | “同一阻碍三轮”主要由提示词约束;宿主错误另有自动阻塞 | “同一阻碍三轮”由工具描述约束 | 自动 goal round 硬检查累计轮数 ≥3;不检验阻碍是否相同 |
最后一行只比较本文检查到的模型报告路径,不表示其他宿主层一定没有附加约束。DSH 的直接人类轮次还可在累计 goal 轮数为 0 时报告 blocked。Codex 工具 schema、Pi 工具注册、DSH 阈值执行和测试。
五、操作手册
Codex
启动 Codex CLI,在已经开始的会话输入:
1 | /goal 为 API 的错误返回补齐设计、实现和回归验证;完成条件:指定测试通过且逐项检查边界情形 |
裸 /goal 显示当前摘要与可用命令,/goal edit 打开编辑入口。也可直接明确要求 agent 创建 Goal;模型工具接受 objective 和可选 token_budget,工具说明要求模型仅在用户明确提出预算时设置参数,但宿主配置 goals.max_goal_token_budget 也可能成为默认预算。请在持久会话中使用;临时线程会拒绝 Goal。预算是停止后续工作的阈值,并非当前 turn 的严格用量上限。若 /goal 不出现,先核对所运行版本的 goals 功能开关;本文固定版本默认开启。依据:TUI 分发、工具 schema、预算默认值、预算记账。
Pi 与 pi-goal
先按 Pi 安装页安装 Pi,再在 Pi 环境运行:
1 | pi install npm:pi-goal |
进入交互界面后:
1 | /goal 修复登录过期后的重复刷新,并用真实回归用例证明无死循环 |
扩展支持 pi -e ./src/index.ts 从本地检出的扩展源码加载。目标超过 4,000 个 Unicode 码点时会被截断;长需求宜写进仓库文件,并在简短目标中引用文件和验收条件。第三方扩展可执行代码,安装前检查来源与锁定版本。依据:扩展 README、截断实现、Pi 官方包文档。
DSH
按 官方仓库说明使用 Node.js 运行 npx @deepseek-ai/dsh web,或从源码 pnpm install && pnpm run build && pnpm dsh web。Web 默认 standard 预设提供命令与工具,宿主组合提供状态服务和续跑 driver;使用包含这些能力的组合。依据:默认预设
1 | /goal 修复登录过期后的重复刷新,并检查所有相关调用路径 |
/goal 状态页应同时显示 phase、Rounds: 已用/上限、Activation: armed/disarmed。会话恢复后如果是 active + disarmed,由人明确 /goal resume;单看 active 不代表自动执行中。若命令不存在,核查预设是否装入 command-goal;若模型没有目标工具,核查 tool-goal;若不续跑,核查宿主组合的 goal-round-driver。依据:命令实现、Web 预设、宿主组合。
六、问题与坑:排查时先看哪一层
- 目标明明 active,却没有继续。 Codex 检查线程是否 idle、功能开关、延迟标记和 live thread;Pi 扩展检查是否有待处理消息;DSH 还检查
armed、全 agent idle、轮数上限与预留是否失效。先读状态,再找调度入口,别只改提示词。Codex、Pi、DSH。 - 恢复后意外继续或意外停下。 Codex 普通 active 会话恢复后可进入续跑;只有启用默认关闭的实验选项
deferGoalContinuation且满足持久化条件的 fork 才继承并延迟目标。本文 Pi 扩展在空闲且无待处理消息时续跑,Pi fork 通常换新 session ID;DSH 恢复后主动 disarm。跨项目迁移时先核对这些条件。Codex fork 参数、继承条件、延迟写入、Pi 排队条件、DSH。 - Pi 分支与目标文件不一致。 树分支导航仍使用同一 session ID,本文扩展的目标文件可能沿用其他分支的目标;fork 通常新建 session ID,因此更可能读不到父目标。无持久会话每次新建 ID,也不能因为配置目录中残留目标文件就认为能自动恢复。切换分支或 fork 后应重新核对
/goal与当前任务。这是源码推论,未做分支运行复现。Pi 状态指南、会话 fork、扩展存储路径。 - “完成”过早。 三种实现都会要求模型检验目标,但源码没有展示一个能理解所有交付物的独立验收器。把目标写成可核查的交付物、命令和结果;结束时亲自查看当前文件、测试范围和生成物。通过狭窄测试不能证明宽泛目标完成。Codex 完成审计提示、Pi 完成审计提示、DSH 续跑提示。
- 预算口径混淆。 Codex 的
token_budget是停止续跑的目标阈值,当前 turn 可能超出;DSH 的max_goal_rounds是自动续跑轮数,两者无法直接换算。Pi 本文扩展会记录用量,但公开的/goal与create_goal入口没有预算参数。迁移配置时先写清楚限制的是 token、轮数、时间还是费用。Codex 记账、DSH goal 限制、Pi 工具参数。 - Pi 的长目标被截断。 本文扩展把超过 4,000 个 Unicode 码点的目标截断;全文另存文件,续跑提示却只放截断后的正文。把长需求与验收条件写进仓库文件,在短目标中明确引用该文件,并在结束时检查全文。截断规则、续跑提示。
若自己实现 Goal,最小可行设计
把“当前目标”做成持久记录,至少含 goalId、objective、status、revision;把“是否允许自动继续”作为独立授权状态。所有更新验证旧 revision;每次准备续跑时再读状态,并在真正进入模型请求前复验目标 ID 与 revision。只在整个 agent 空闲、没有更优先的用户消息时排队;请求结束后依据明确的完成证据、阻塞规则与预算停止。为目标内容标注低信任来源。最后设计会话恢复与 fork 的行为:自动续跑还是等待人类重新授权,必须是显式产品决定。DSH 的 状态服务和预留校验可作为并发设计参考;Codex 的 条件写入和 idle hook则是更紧凑的宿主集成路径。