feat: goal 模式新语义——prompt 即目标,无验收命令,不限轮次,模型宣告完成 (#67)

console 打勾 goal 模式时不再有专用 goal 文本、验收命令、轮次
三个输入(已移除,换成说明行),只发 goal_mode=1;server 侧
goal_mode=1 时 goal 存 prompt、验收命令空、轮次存 0,带验收
命令或与计划模式同用时 400,旧的带验收命令任务照原样可用。

host.py goal 循环:verify 为空时不跑验测,轮次提示里写明
标记约定;模型在完整跑完的回合末尾单独一行输出“目标已
达成”即达标(超时回合的部分文本含标记也不算);--max-rounds
0 为不限轮次,goal-resume 续跑按 0 处理(不用 or 兜底成 3)。
刹车只有取消与预算耗尽(后者记 interrupted、可续跑)。

迁移与 worker:tasks 加 goal_mode 列(server 迁移、worker 列
补齐、PG schema 三处同步);worker 按 goal_mode 或
verify_command 判定 goal 路径,重试与后续消息排空对新语义
同样只跑一次/不排空;Local/Docker goal_task 的 --verify 可选、
--max-rounds 0 原样传。

Co-authored-by: host <host@test.local>
This commit is contained in:
openqjandhost authored and GitHub committed 2026-10-27 03:54:48 +08:00
1 parent 4179fb1a0a
commit c86b450851
10 files changed
+519 -108

No files matched your search

+16 -8
View File
@@ -234,6 +234,9 @@ def _task_from_row(row):
"goal": _col("goal"),
"verify_command": _col("verify_command"),
"goal_max_rounds": _col("goal_max_rounds"),
# 新 goal 语义:goal_mode=1 时 prompt 即目标,无验收命令、
# 不限轮次;老行缺列时 None = 按 verify_command 判定。
"goal_mode": _col("goal_mode"),
# 任务级选项:老行缺列时 None = 跟随配置(见 server 侧)。
"model": _col("model"),
"effort": _col("effort"),
@@ -467,7 +470,7 @@ class Worker:
"SELECT id, project_id, prompt, status, idempotency_key,"
" depends_on, priority, created_by, created_at, updated_at,"
" last_heartbeat, executor, goal, verify_command,"
" goal_max_rounds FROM tasks"
" goal_max_rounds, goal_mode FROM tasks"
" WHERE status IN ('queued', 'completed', 'merged')"
" ORDER BY created_at, id").fetchall()
finally:
@@ -562,6 +565,7 @@ class Worker:
"pr_number": "INTEGER", "goal": "TEXT",
"verify_command": "TEXT",
"goal_max_rounds": "INTEGER",
"goal_mode": "INTEGER",
"worktree_host": "TEXT",
"model": "TEXT",
"effort": "TEXT",
@@ -1326,10 +1330,12 @@ class Worker:
task_id = task["id"]
result = None
error = None
# goal 模式:任务带 verify_command 时走 host.py goal 循环,
# 不走普通 dispatch。goal 循环内部已多轮重试,worker 级
# goal 模式:goal_mode=1(新语义,prompt 即目标)或带
# verify_command(旧语义)时走 host.py goal 循环,不走
# 普通 dispatch。goal 循环内部已多轮重试,worker 级
# 的已存在续跑(run)对它没有意义(goal 没有 run 语义)。
goal_mode = bool(task.get("verify_command"))
goal_mode = bool(task.get("goal_mode")
or task.get("verify_command"))
# Docker 同步路径:dispatch 阻塞在 docker run 里时,取消
# 线程靠容器名杀容器(见 cancel())。容器名按任务派生,
# 与执行器本身无关,所以这里只记名不碰执行器状态。
@@ -1632,10 +1638,11 @@ class Worker:
if self._was_cancelled(task_id) or \
result.get("status") == "cancelled":
break
if task.get("verify_command"):
if task.get("verify_command") or task.get("goal_mode"):
# goal 模式只跑一次:循环内部已多轮重试(turn→验测→
# 喂回),worker 级重试/fallback 不再叠加——同任务重跑
# 会撞 host 任务名已存在,第二次尝试必失败且污染错误文本。
# 喂回,或新形态的模型自决多轮),worker 级重试/fallback
# 不再叠加——同任务重跑会撞 host 任务名已存在,第二
# 次尝试必失败且污染错误文本。
break
if result.get("status") == "completed":
break
@@ -1698,7 +1705,8 @@ class Worker:
# 剩余 pending 留给重试入口(重试走已存在续跑,跑完继续
# 排空剩余消息);取消时该条回 pending,任务走中断收尾。
if (result is not None and result.get("status") == "completed"
and not task.get("verify_command")):
and not task.get("verify_command")
and not task.get("goal_mode")):
while True:
fu = self._claim_next_followup(task_id)
if fu is None: