@ggbondex/pi-goal0.3.0
Autonomous goal loop for pi: an AI judge decides whether the goal is done, otherwise the original goal is re-sent to the agent.
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
goal-loop
自主目标循环扩展:给 pi 一个目标,它停下来时由 AI 判定目标是否真的完成;没完成就把目标再发一遍,直到完成、受阻、或达到续跑上限。
为什么
pi 做完一个小项就会停下来问"要不要继续"。跑长计划(例如 docs/IMPLEMENTATION_PLAN.md 的 29 个切片)时得一直盯着回车。
这个扩展把"要不要继续"的判断交给一个判定模型,而不是交给人。
用法
/goal 按 docs/IMPLEMENTATION_PLAN.md 从当前进度顺序完成,直到所有切片的判定都跑通过
只想给个目标、直接开跑(不规划):
/goal <目标>
想先让 pi 把目标想清楚、写成一份计划再开跑:
/goal plan 给助手加记笔记能力
/goal plan 完成 docs/IMPLEMENTATION_PLAN.md
/goal plan @docs/IMPLEMENTATION_PLAN.md # 显式指定计划文件(只读引用)
之后:
/goal 查看状态
/goal plan 用法
/goal stop 停止循环(保留目标,可 resume)
/goal resume 从断点接着跑
/goal clear 彻底清除目标(不再记忆,含重启/切分支后)
/goal model <spec> 指定判定/规划模型:auto | provider/modelId | modelId
/goal max <n> 续跑上限(默认 15)
/goal help 用法
一次性默认值可用环境变量:PI_GOAL_MAX、PI_GOAL_MODEL、PI_GOAL_RETRY_BASE_MS(错误冷却退避基数,默认 60s)、PI_GOAL_JUDGE_TIMEOUT_MS(判定调用超时,默认 90s)。
每一轮发生什么
/goal <目标>把目标当作普通用户消息发出,agent 开始干活。- 当这轮 agent 真正要收尾时(
agent_before_settle),扩展把目标 + 当前对话记录交给判定模型,要求它只返回一个 JSON:{"done": bool, "blocked": bool, "reason": "...", "next": "..."}。 - 判定为
done→ 停止,并提示"目标已完成"。blocked→ 暂停(不是结束):把"需要你决定什么"告诉你;你直接回答即可,下一轮会自动恢复并接着跑。想彻底停下用/goal stop。 都不是 → 注入一条续跑消息(含判定原因、下一步、以及原始目标原文),agent 继续。 - 瞬时错误自愈:provider 错误(429 限流、超时、掉线——用 pi 内置重试同一套判定)不会让循环静默停摆:扩展在收尾边界内退避冷却(60s 起指数、5min 封顶),最多自动重试 5 次(约 17 分钟),每次把失败那轮从上下文抠掉再续跑。预算烧完或错误非瞬时 → 暂停:状态行显示「运行出错 · 等你答复」,你直接回话即自动恢复。判定调用有 90s 超时;判定失败也走同样的冷却重试,连续 3 次才停。
- 硬上限保护:最多自动续跑
max次;你在判定或干活时按 Esc 会直接停止循环。
判定提示词里专门写明:agent 问"要不要继续"不算完成、也不算受阻——这正是要修的行为。
计划(/goal plan,可选)
规划是可选的一段开场,不是一种模式:只有显式敲 /goal plan 才走它,普通 /goal 一个字不变。
它只做一件事:让 pi 先把计划写好。
/goal plan <意图|@文件> 让 pi 先调研仓库(读合同、读测试、看真实存在哪些命令),然后把计划写成一份人读的文件 —— 插件自己的 .pi/goal-plans/<slug>.md,头两行是两个字段:
目标:<终态,一句话>
出口:<证明做到的准确命令>
下面是正文:路线(按依赖排序的各个阶段,每个写清做什么、动哪些文件、它自己的验收命令)、已经定下的决定、看得见风险,以及一节「未定项」。规划就是一次普通的 pi turn,不限制工具,只在提示词里要求「先别动手实现,这一步只出计划」。
计划是路线图,不是账本。 插件不跟踪任何里程碑状态,也不数「完成了几片」:进度只有「第 n/15 轮」和上次判定的原因。这是刻意的取舍 —— 计划一旦成了完成判据,当初多写的一条就会让目标永远完不成;而且账本要准就得配一整套规则(按区间认领、片不能改名、判定要回答"认到哪一片"……),那些规则本身就长出了好几个 bug。计划可以随认知更新,agent 也可以改它。
写完之后:打印目标、出口、文件路径和开头十几行,等你确认。你回一句「同意」就开始跑 —— 不需要再敲一次 /goal plan,扩展用 input 事件接住那句普通回复(同意 / 取消 / 任何修改意见,后者会带上去重新规划)。确认后起的是一个普通 goal:判定模型对「目标 + 出口」判完成与否,循环把它迭代到完成为止。
- 信息不够就先问:如果 pi 判断不问你没法写出忠实的计划,它不写文件,直接在回复里问;这句话会被显眼地摆出来等你回答,然后它带着答案重新规划。连问三轮还写不出文件就停下报错,不空转。
- 没有「目标」字段的文件不算计划:会被当成"它问了什么",而不是当成一份空计划 —— 判据只有一条。
- 不动你的文件:你
@的那份只做只读引用。 - 规划失败(读不到文件 / 出错 / 中断)不启动,直接告诉你。
设计上的取舍
- 判定基于证据:要求 transcript 里有实证(文件改动、命令输出、测试结果)。目标里写清验收标准,判定会准得多。
- 默认跟随会话模型:判定调用用的是当前会话模型;想省钱可以在
/goal model里换成便宜模型。 - 只读 transcript 尾部 40k 字符:目标很长时前面的上下文可能被截断,所以续跑消息里每次都重申原始目标。
- 状态存在会话里:以
custom条目写进 transcript,不落额外文件、不发网络、不起进程。分支/恢复时状态自动正确。 - 不干预无目标会话:没有活动目标时,扩展在所有事件上都是空操作。
结构
index.ts— 循环接线与命令(单文件)plan.ts— 计划模块:规划提示词、读回计划文件的两个字段、预览、交接、确认分类(纯函数,只有读文件有副作用)- 依赖:只 import pi 自带供给的包(
@earendil-works/pi-ai、@earendil-works/pi-coding-agent、@earendil-works/pi-tui)+node:fs
安装
作为 pi 包装(推荐):
pi install npm:@ggbondex/pi-goal
# 或直接装 GitHub 上的源码:
pi install git:github.com:ggbondex/pi-goal
或手动放到用户级扩展目录(所有项目生效):
ln -s "$PWD" ~/.pi/agent/extensions/goal-loop
改完在 pi 里执行 /reload。
测试
npm test # 先 test/setup.mjs 复用本机已装的 pi,再跑两个测试
test/mock.test.mjs— 用假 model registry 覆盖各分支(启动 / 续跑 / 完成 / 受阻 / 中断 / 上限 / 恢复 / 判定失败 / 停止 / 状态行 / 规划→确认→驱动→完成),不发网络、不花钱。test/plan.test.mjs—plan.ts的纯函数(字段解析 / 预览 / 交接 / 确认分类 / 文件路径 / 提示词)。test/integration.test.mjs— 在真实 pi 运行时里用假 provider 跑完整循环,验证续跑消息确实被下一轮看到。
test/setup.mjs 只是把本机已安装的 pi 包软链进 node_modules/,不下载任何东西;已存在的真实安装不会被覆盖。