feat: add plan mode (enter/exit/completion check/verification gate/periodic hints)

This commit is contained in:
Jiaqing Liang
2026-04-15 17:02:36 +08:00
parent d482f30188
commit e3a2526932
4 changed files with 279 additions and 66 deletions

View File

@@ -1,13 +1,8 @@
"""Desktop Pet with Skin System — Cross-platform with True Transparency"""
import os
import re
import sys
import json
import threading
import os, re, sys, json, threading, io
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
from PIL import Image, ImageDraw, ImageFont, ImageOps
import io
PORT = 51983
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -784,7 +779,7 @@ if __name__ == '__main__':
pass
if sys.platform == 'darwin':
pet = MacPet()
pet = MacPet('vita')
pet.run()
else:
pet = WinPet('vita')

View File

@@ -56,9 +56,9 @@ def render_sidebar():
agent._pet_req = _pet_req
if not hasattr(agent, '_turn_end_hooks'): agent._turn_end_hooks = {}
def _pet_hook(ctx):
parts = [f"🔄 Turn {ctx.get('turn','?')}"]
parts = [f"Turn {ctx.get('turn','?')}"]
if ctx.get('summary'): parts.append(ctx['summary'])
if ctx.get('exit_reason'): parts.append('任务已完成')
if ctx.get('exit_reason'): parts.append('任务已完成')
_pet_req(f'msg={quote(chr(10).join(parts))}')
if ctx.get('exit_reason'): _pet_req('state=idle')
agent._turn_end_hooks['pet'] = _pet_hook

28
ga.py
View File

@@ -415,6 +415,16 @@ class GenericAgentHandler(BaseHandler):
next_prompt += "\n[SYSTEM TIPS] 正在读取记忆或SOP文件若决定按sop执行请提取sop中的关键点特别是靠后的update working memory."
return StepOutcome(result, next_prompt=next_prompt)
def _in_plan_mode(self): return self.working.get('in_plan_mode')
def _exit_plan_mode(self): self.working.pop('in_plan_mode', None)
def enter_plan_mode(self, plan_path):
self.working['in_plan_mode'] = plan_path; self.max_turns = 80
print(f"[Info] Entered plan mode with plan file: {plan_path}"); return plan_path
def _check_plan_completion(self):
if not os.path.isfile(p:=self._in_plan_mode() or ''): return None
try: return len(re.findall(r'\[ \]', open(p, encoding='utf-8', errors='replace').read()))
except: return None
def do_update_working_checkpoint(self, args, response):
'''为整个任务设定后续需要临时记忆的重点。'''
key_info = args.get("key_info", "")
@@ -439,6 +449,12 @@ class GenericAgentHandler(BaseHandler):
return StepOutcome({}, next_prompt="[System] Incomplete response. Regenerate and tooluse.")
if 'max_tokens !!!]' in content[-100:]:
return StepOutcome({}, next_prompt="[System] max_tokens limit reached. Use multi small steps to do it.")
if self._in_plan_mode() and any(kw in content for kw in ['任务完成', '全部完成', '已完成所有', '🏁']):
if 'VERDICT' not in content and '[VERIFY]' not in content and '验证subagent' not in content:
yield "[Warn] Plan模式完成声明拦截。\n"
return StepOutcome({}, next_prompt="⛔ [验证拦截] 检测到你在plan模式下声称完成但未执行[VERIFY]验证步骤。请先按plan_sop §四启动验证subagent获得VERDICT后才能声称完成。")
# 2. 检测"包含较大代码块但未调用工具"的情况
# 关键特征恰好1个大代码块 + 代码块直接结尾(后面只有空白)
code_block_pattern = r"```[a-zA-Z0-9_]*\n[\s\S]{50,}?```"
@@ -461,7 +477,12 @@ class GenericAgentHandler(BaseHandler):
"并明确是否还需要额外的实际操作。"
)
return StepOutcome({}, next_prompt=next_prompt)
# 3. 正常情况:直接将回复返回给用户并结束循环
if self._in_plan_mode():
remaining = self._check_plan_completion()
if remaining == 0:
self._exit_plan_mode(); yield "[Info] Plan完成plan.md中0个[ ]残留退出plan模式。\n"
yield "[Info] Final response to user.\n"
return StepOutcome(response, next_prompt=None)
@@ -510,6 +531,11 @@ class GenericAgentHandler(BaseHandler):
elif turn % 7 == 0:
next_prompt += f"\n\n[DANGER] 已连续执行第 {turn} 轮。禁止无效重试。若无有效进展必须切换策略1. 探测物理边界 2. 请求用户协助。如有需要,可调用 update_working_checkpoint 保存关键上下文。"
elif turn % 10 == 0: next_prompt += get_global_memory()
if (_plan := self._in_plan_mode()) and turn >= 10 and turn % 5 == 0:
next_prompt = f"[Plan Hint] 你正在计划模式。必须 file_read({_plan}) 确认当前步骤,回复开头引用:📌 当前步骤:...\n\n" + next_prompt
if _plan and turn >= 70: next_prompt += f"\n\n[DANGER] Plan模式已运行 {turn} 轮,已达上限。必须 ask_user 汇报进度并确认是否继续。"
injkeyinfo = consume_file(self.parent.task_dir, '_keyinfo')
injprompt = consume_file(self.parent.task_dir, '_intervene')
if injkeyinfo: self.working['key_info'] = self.working.get('key_info', '') + f"\n[MASTER] {injkeyinfo}"

View File

@@ -1,69 +1,261 @@
# Plan Mode SOP
**触发**3步以上有依赖/多文件协同/条件分支/需并行 | **禁用**1-2步简单任务直接做
**工作目录**`./plan_XXX/`XXX=任务英文短名)
## 规划态(按序执行
**步骤1创建文件夹 + 存checkpoint**
```
[任务] 产出 ./plan_XXX/plan.md | [需求] 一句话 | [约束] 关键限制
[进度] 即将读领域SOP | ⚡写完plan后必须重读本SOP步骤3切换执行态
```
**步骤2读领域SOP → 写plan骨架**
先读相关SOP立即写骨架。允许"⚠待确认",禁止以"没调研清楚"推迟。
**骨架格式**
```
任务开始前必须先创建工作目录 `./plan_XXX/`XXX=任务英文短名)
使用code_run({'_inline_eval':True, 'script':'handler.enter_plan_mode("./plan_XXX/plan.md")'})进入plan模式使用未在schema中描述的秘密参数_inline_eval
---
## 一、探索态(规划前置,必须执行)
**硬性规则(先读再做)**
- **主agent禁止直接执行环境探测**必须委托subagent无例外
- 主agent只做创建目录、读SOP索引、启动subagent、读取结论
- subagent只读探测禁止修改任何文件、执行有副作用的操作
- **探索subagent启动失败时排查原因→重试最多2次。禁止主agent回退为自己探测**
**目标**在写任何计划之前搞清3件事
① 环境现状(有什么、缺什么) ② 可用SOP ③ 关键不确定点
**为什么必须用subagent**主agent上下文是最稀缺资源探测长输出会挤占规划执行空间。
### 步骤1创建目录必做 + SOP匹配 + 设置plan标志主agent直接做
1. 创建工作目录 `mkdir plan_XXX/`
2.`sop_index.md` 匹配可用领域SOP
3. 更新checkpoint`[任务] XXX | [需求] 一句话 | [约束] 关键限制 | [匹配SOP] ... | [进度] 探索态`
### 步骤2启动探索subagent监察模式
按 subagent.md 启动探索subagent**加 `--verbose`** 开启监察模式input要点
- **任务**:探测环境信息,写入 `plan_XXX/exploration_findings.md`
- **探测项**(按任务类型选做,不是全做):
- 代码类 → 关键文件结构、依赖、入口点
- 浏览器类 → 目标页面当前状态、可交互元素
- 自动化类 → 环境检查(which/pip/路径/权限)
- 数据类 → 抽样数据(首5行+尾5行+总量)
- **输出格式**`## 环境现状` / `## 关键发现` / `## 风险/不确定点`
- **约束**只读探测禁止修改文件≤10次工具调用
- **复杂度评估**探测时注意记录数据规模文件数、行数、页面数写入findings供规划时判断委托
### 步骤3监察等待 + 读取结论
主agent主动观察output.txt进度`--verbose`输出含原始工具结果而非无脑sleep轮询
1. **观察**读output.txt审查subagent的探测方向和原始数据
2. **纠偏**(按需):
- 方向偏了 → 写 `_intervene` 追加指令纠正
- 缺少关键上下文 → 写 `_keyinfo` 注入信息
- 已获取足够信息 → 写 `_stop` 提前终止,节省轮次
3. **收取**:等待 `[ROUND END]`,读取 `exploration_findings.md`
**产出**`exploration_findings.md`结构化发现报告主agent基于此进入规划态写入plan.md头部的「探索发现」段。主agent在监察过程中获得的一手认知也可直接用于规划。
---
## 二、规划态(含审查门)
### 步骤4读领域SOP → 写plan.md
先读探索态匹配到的SOP然后写plan骨架。允许"⚠待确认",禁止以"没调研清楚"推迟。
**[D] 委托标注规则**:写每个步骤时,结合探索发现评估操作量,符合以下任一条件则标 `[D]`
- 需要读取大量代码/文件(预估 >3个文件或 >100行
- 需要浏览网页并提取信息
- 需要执行 3 次以上重复性操作
- 需要运行测试/构建并分析输出
不标 `[D]` 的情况:读/更新 plan.md、单文件小幅修改、ask_user、简单一次性命令
**plan.md格式**
```markdown
<!-- EXECUTION PROTOCOL (每轮必读,这是你的执行指南)
1. file_read(plan.md),找到第一个 [ ] 项
2. 该步标注了SOP → file_read 该SOP的🔑速查段
3. 执行该步骤 + Mini验证产出
4. file_patch 标记 [ ] → [✓]+简要结果然后回到步骤1继续下一个[ ]
5. 所有步骤(包括验证步骤)标记完成后 → 终止检查file_read(plan.md)确认0个[ ]残留
⚠ 禁止凭记忆执行 | 禁止跳过验证步骤 | 禁止未经终止检查就结束 | 禁止停下来输出纯文字汇报
💡 搬砖活(读大量代码/文件/网页/重复操作优先委托subagent保持主agent上下文干净
-->
# 任务标题
需求:一句话 | 约束:关键限制
1. [ ] 步骤1简述
2. [P] 步骤2简述并行读subagent_sop.md执行Map模式
依赖1
3. [?] 步骤3条件分支
条件X成功→3.1否则→3.2
```
**标记**`[ ]`待做 | `[✓]`完成 | `[✗]`失败 | `[P]`并行 | `[?]`条件分支
**[P]标记条件**全YES才可标
□ 2+步骤可同时? □ 无数据依赖? □ 产出不同文件? □ 节省>20%时间?
**子任务目录**subagent产出放 `./plan_XXX/subtask_name/`
**探索规则**有⚠项→逐项探索每轮更新plan+checkpoint | 连续3轮无进展→用当前最佳方案
**⛔ 写完骨架后禁止执行。必须先完成步骤3。**
**步骤3转入执行态**
## 探索发现
- 发现1XXX来源file_read/web_scan/code_run
- 发现2YYY
- 不确定点ZZZ
## 执行计划
1. [ ] 步骤1简述
SOP: xxx_sop.md
2. [D] 步骤2简述委托subagent执行
SOP: yyy_sop.md
依赖1
3. [P] 步骤3简述并行读subagent.md执行Map模式
SOP: yyy_sop.md
4. [?] 步骤4条件分支
SOP: (无) ← 高风险
条件X成功→4.1否则→4.2
---
## 验证检查点
N+1. [ ] **[VERIFY] 启动独立验证subagent**
SOP: verify_sop.md
操作准备verify_context.json → 启动验证subagent → 读取VERDICT → 按结果处理
⚠ 不可跳过不可在未启动subagent的情况下标记[✓]
---
```
CALL update_working_checkpoint(
key_info='[执行] plan.md | 当前1.1 | ⚡有[P]标记必须读subagent_sop.md执行Map模式'
)
```
## 执行态循环
1. 读plan.md → 定位当前`[ ]`
2. **检查并行**
```
IF has_mark('[P]'):
CALL file_read('subagent_sop.md')
# 按SOP创建context.json含绝对路径+ input.txt
# ⚠ subagent必须从context.json读取绝对路径写入文件
ELSE:
执行当前步骤
```
3. 执行该项
4. **收尾(必须执行,不可跳过)**
```
# ⚠ 进度标记更新是强制要求,每步完成后必须执行
file_patch(plan, '[ ] 当前步骤' → '[✓] 当前步骤')
file_read(plan) # 验证标记已更新 + 找下一步
update_working_checkpoint('[进度] 步骤N已完成 | 下一步:...')
```
5. **Checkpoint验证每3步或关键节点**
```
file_read(plan) # 检查:所有已执行步骤是否标[✓]
IF 有遗漏标记:
立即补标 file_patch(...)
```
6. 全部完成 → 汇总结果 → 清理checkpoint
## 失败处理
### 步骤5自检清单主agent逐项检查
- □ 探索发现是否都反映在plan中没遗漏关键约束
- □ 每步的SOP标注是否合理SOP真的能解决该步
- □ 步骤间依赖是否正确?(有没有隐含依赖没写出来)
- □ 高风险步骤SOP:无/不可逆)有没有清晰的执行思路?
- □ 步骤粒度是否合适?(禁止"处理所有文件",必须展开具体条目)
-**复杂/繁琐步骤是否标注了[D]**(读大量代码/网页/重复操作必须委托subagent
-**是否包含"验证检查点"section且有[VERIFY]步骤?(必须有,这是强制步骤)**
### 步骤6用户确认
ask_user 确认plan后才能转入执行态。**⛔ 用户未确认不得执行。**
### 步骤7转入执行态
更新checkpoint`[执行] plan.md | 当前步骤1 | ⚡有[P]标记必须读subagent.md执行Map模式`
---
## 三、执行态循环
> **核心原则:连续执行,不停顿汇报。** 做完一步立即 file_read(plan.md) 找下一个 `[ ]`,直到全部完成。
### 每轮流程
1. **读plan**`file_read(plan.md)` 定位第一个 `[ ]`
2. **读SOP** — 该步标注了SOP → 先 file_read 该SOP
3. **检查标记**`[D]`标记 → 必须委托subagent执行主agent只收结果摘要`[P]`标记 → 读 subagent_sop.md 执行Map模式`[?]`条件 → 评估条件选分支,未选标[SKIP]
4. **执行** — 无特殊标记的步骤由主agent自己执行
5. **Mini验证** — 快速确认产出存在且合理file_read确认非空、检查exit code等
6. **标记完成**`file_patch` 标记 `[ ]``[✓ 简要结果]`进度写入plan.md
7. **继续** — 立即回到步骤1file_read(plan.md) 执行下一个 `[ ]`
### 终止检查(最后一步标记后,不可跳过)
file_read(plan.md) 全文扫描,确认所有步骤(含[VERIFY])均为 `[✓]`/`[✗]`0个 `[ ]` 残留。
输出:`🏁 终止检查:[总步数]步全部完成0个[ ]残留 → 任务结束`
若发现遗漏 → 继续执行,禁止声称完成。
### ⚠ 执行态禁令
- **禁止凭记忆执行**:每次做新步骤前必须 `file_read(plan.md)`,不可"我记得下一步是..."
- **禁止跳过验证步骤**[VERIFY]步骤是强制的,不可以"任务都做完了"为由跳过
- **禁止未经终止检查就结束**:最后一步标记后必须 file_read 全文扫描确认0个[ ]残留,输出🏁终止确认行
- **禁止停下来输出纯文字汇报**:做完一步后必须立即 file_read(plan.md) 继续,不要输出进度总结
### 💡 动态委托原则
即使步骤未标 `[D]`,执行中发现以下情况时,主动委托 subagent 处理:
- 需要读取大量代码/文件才能理解上下文(>3个文件或预估 >100行
- 需要反复试错调试
- 需要浏览网页提取信息
做法:起 subagent 完成具体操作,要求返回精简摘要,主 agent 基于摘要继续决策。保持主 agent 上下文干净是第一优先级。
---
## 四、验证态subagent独立验证
> 全部步骤[✓]后进入。**强制**启动独立subagent做对抗性验证避免上下文污染。
### 触发条件
- 所有执行步骤标记为 `[✓]`
- **所有plan模式任务必须经subagent验证**主agent有确认偏误易被表面成功迷惑
### 步骤8准备验证上下文
`./plan_XXX/` 下创建 `verify_context.json`,包含:
- task_description原始任务描述用户原话
- plan_fileplan.md绝对路径
- task_typecode|data|browser|file|system
- deliverables交付物列表type/path/expected
- required_checks必做检查列表check/tool
**传什么**任务描述、plan路径、交付物清单、必做检查。**不传**:执行过程、调试记录。
### 步骤9启动验证subagent
按 subagent.md 标准流程启动验证subagentinput要点
- **角色**:你是独立验证者,工作是对抗性验证(证明交付物不能用)
- **第一步强制**file_read verify_sop.md 完整阅读验证SOP
- **按 verify_sop.md 第3节**选择对应task_type的验证策略执行
- **每个检查必须有工具调用证据**(实际执行,不是叙述)
- **任务描述**:(填入原始任务描述)
- **交付物清单**填入deliverables列表
- **输出**:在 result.md 中按 verify_sop.md 第6节格式输出最后一行 `VERDICT: PASS / FAIL / PARTIAL`
- **约束**3轮内完成每轮至少1个实际工具调用
同时传入 verify_context.json 的路径让subagent自行读取详细上下文。
### 步骤10收集验证结果
轮询 output.txt 等待 `[ROUND END]`,然后读取 result.md
1. **找VERDICT行**读取result.md最后几行提取 `VERDICT: PASS/FAIL/PARTIAL`
2. **检查有效性**如果所有PASS项都没有工具调用输出只有叙述视为验证无效按FAIL处理
3. **按结果处理**
- **PASS** → 进入任务完成收尾
- **FAIL** → 进入修复循环
- **PARTIAL** → 主agent判断可接受则完成否则修复
- **无VERDICT行** → 从output.txt提取关键信息主agent自行判断PASS/FAIL
**任务完成收尾**验证PASS后执行
1. 标记plan.md中 `[VERIFY]` 步骤为 `[✓]`
2. 更新checkpoint`[完成] XXX任务 | [产出] ... | [经验] ...`
3. 向用户确认任务完成
**重要**只有在验证PASS后才能标记[VERIFY]为[✓]并声称任务完成。如果验证FAIL需要进入修复循环。
**Fallback**若subagent未产出result.mdturn耗尽从output.txt提取VERDICT关键信息。
### 修复循环FAIL后
FAIL → 提取具体失败项 → 回执行态修复(不重新规划) → 修复完成 → 再次启动验证subagent → 最多2轮FAIL-重试,超过 ask_user 介入
修复时:
1. 将FAIL项作为新步骤追加到plan.md标记为 `[FIX]`
2. 只修复失败项不重做已PASS的部分
3. 修复完成后重新准备verify_context.json只含失败项
### 特殊场景处理
浏览器/键鼠/定时任务等场景主agent执行操作并导出证据截图/录屏/日志)→ subagent验证证据文件。**禁止主agent自行判断PASS/FAIL**。
---
## 五、失败处理
1. **记录**checkpoint中 `step_X: [FAILED] 原因 (retry: N/3)`
2. **重试**网络超时→自动重试3次(2s/4s/8s) | 配置错误→询问用户 | 其他→标[✗]跳过
3. **subagent失败**查stderr.log→明确错误主agent修正重启 | 未知错误重试1次 | 最多重启2次
4. **依赖传播**:步骤失败后,后续依赖项标[SKIP]
5. **plan有误**回退到规划态修正plan.md重新过审查门
## 强制约束
- 每项必须有独立完成判据
- 禁止"处理所有文件",必须展开具体条目
- 一次只做一项;计划有误回规划态修正