omp packages

@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.

ggbondex Published 14 days ago MIT ~562 downloads/mo

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.

Did it work for you?

Result

One report per person. A new report replaces your old one.

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)。

每一轮发生什么

  1. /goal <目标> 把目标当作普通用户消息发出,agent 开始干活。
  2. 当这轮 agent 真正要收尾时(agent_before_settle),扩展把目标 + 当前对话记录交给判定模型,要求它只返回一个 JSON: {"done": bool, "blocked": bool, "reason": "...", "next": "..."}。
  3. 判定为 done → 停止,并提示"目标已完成"。 blocked → 暂停(不是结束):把"需要你决定什么"告诉你;你直接回答即可,下一轮会自动恢复并接着跑。想彻底停下用 /goal stop。 都不是 → 注入一条续跑消息(含判定原因、下一步、以及原始目标原文),agent 继续。
  4. 瞬时错误自愈:provider 错误(429 限流、超时、掉线——用 pi 内置重试同一套判定)不会让循环静默停摆:扩展在收尾边界内退避冷却(60s 起指数、5min 封顶),最多自动重试 5 次(约 17 分钟),每次把失败那轮从上下文抠掉再续跑。预算烧完或错误非瞬时 → 暂停:状态行显示「运行出错 · 等你答复」,你直接回话即自动恢复。判定调用有 90s 超时;判定失败也走同样的冷却重试,连续 3 次才停。
  5. 硬上限保护:最多自动续跑 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/,不下载任何东西;已存在的真实安装不会被覆盖。