oh-my-openagent 的 ULW 自动续跑指令:深入解析 omo-codex 的 ulw-execute-continuation 机制
【免费下载链接】oh-my-openagentOmO: Just type "mass ulw" keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent
导读
在 oh-my-openagent(OmO)的 omo-codex 插件中,ulw-execute-continuation组件为 Codex 会话提供了一条"自动续跑"指令(directive):当代理正处于一份 Prometheus 工作计划(ULW plan)的执行中途时,每次 Codex Stop hook 触发都会把这条指令注入模型上下文,要求它不询问、不停顿,而是持续推进直至顶层复选框全部勾选。本文以 directive.md 为骨架,结合组件源码与测试,完整讲解指令的字段结构、执行协议、硬性约束、最终门禁(Final gate)与停止条件,并给出可复现的本地验证方法。读完本文,你将掌握该指令的每个占位符含义、hook 注入链路、计划清单解析规则,以及"外部阻塞逃生口"的准确用法。
一、背景:为什么需要一条"自动续跑"指令
ULW(在 OmO 中对应 "mass ulw" 关键词触发的工作模式)要求代理把一次大任务拆解为带顶层复选框(top-level checkbox)的 Markdown 计划,并逐项推进。但 Codex 的会话存在天然的回合(turn)边界:一轮回复结束、用户没有新输入时,会话可能停下来等待。ulw-execute-continuation要解决的就是这个缝隙——让代理在"本回合"结束时自动继续执行下一项任务,直到整份计划完成。
这条指令的载体是 Codex Stop hook。注册关系见 hooks/hooks.json:
{ "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "node \"${PLUGIN_ROOT}/components/ulw-execute-continuation/dist/cli.js\" hook stop", "timeout": 10, "statusMessage": "(OmO 5.0.0-beta.74) Checking Ulw-Execute Continuation" } ] } ] } }每当 Codex 准备停止(Stop 事件)时,该命令被调用。CLI 入口在 cli.ts 中实现:从 stdin 读取 JSON payload,调用runStopHook,若返回非空则原样输出。CLI 同时支持hook subagent-stop子命令,但该通道被刻意"去接"(de-wired),即不注入根计划、不输出任何内容,防止子代理被错误续跑。
二、指令的模板结构与占位符
directive.md本身是一个模板:每次注入前,codex-hook.ts 中的renderDirective会读取原始文件(见 directive.ts),并把{{PLACEHOLDER}}替换为当前计划的实际状态。模板支持以下占位符:
| 占位符 | 含义 | 替换来源 |
|---|---|---|
{{PLAN_NAME}} | 活动计划的名称 | boulder.json中works[].plan_name |
{{PLAN_PATH}} | 活动计划的 Markdown 路径 | works[].active_plan解析后的路径 |
{{BOULDER_PATH}} | Boulder 状态文件路径 | 固定为<cwd>/.omo/boulder.json |
{{REMAINING_COUNT}} | 剩余顶层复选框数 | getPlanChecklist统计 |
{{TOTAL_COUNT}} | 顶层复选框总数 | getPlanChecklist统计 |
{{NEXT_TASK_LABEL}} | 下一个未完成任务的标签 | 首个未勾选复选框的文本,无则显示none (final gate pending) |
{{WORKTREE_BLOCK}} | 工作树声明块 | 当works[].worktree_path存在时渲染为一条- Worktree: ...行,否则为空 |
{{LEDGER_PATH}} | 证据台账路径 | 固定为<cwd>/.omo/ulw-execute/ledger.jsonl |
{{SESSION_ID}} | 当前 Codex 会话 id | 替换为带codex:前缀的会话 id |
从源码看,renderDirective用String.prototype.replaceAll逐项替换(codex-hook.ts),因此即使模板被修改,只要占位符约定不变,注入逻辑即可复用。
三、指令要求代理"这一回合做什么"
指令的核心执行协议分为 10 步,任何一轮续跑都必须遵循:
- 先读计划与台账:
{{PLAN_PATH}}与{{LEDGER_PATH}}是唯一事实来源,禁止依赖对先前回合的记忆。 - 判断剩余数:剩余数为 0 时跳过复选框执行,直接进入 Final gate;否则选择
## TODOs或## Final Verification Wave下第一个未勾选的顶层复选框,忽略 Acceptance Criteria / Evidence / Definition of Done 下的嵌套复选框。 - 完整遵循
ulw-execute技能:技能文件位于 SKILL.md(组件 README 明确指向该路径),上下文丢失时可重新读取。 - 划分任务层级(tier):默认 LIGHT——窄变更、存在于现有层内,只需一条真实表面(real-surface)证据;HEAVY——新模块/抽象、认证安全、外部集成、schema/迁移、并发、跨域重构等,必须执行完整的逐标准验证体制。不确定时取 HEAVY,绝不降级。
- 原子子任务并行派发:通过
multi_agent_v1.spawn_agent并行派发,除非子任务存在具名阻塞依赖;fork_context: false优先;若使用multi_agent_v2,需传task_name、fork_turns: "none",且wait_agent只接受timeout_ms。 - 子任务消息必须自包含:以
TASK: <指令>开头,包含DELIVERABLE、SCOPE、VERIFY全部七段,并带一个 Manual-QA 通道(HTTP 用curl -i;TUI 用send-keys做启动冒烟、xterm.js Web 终端做色彩/视觉证据,禁止tmux capture-pane;浏览器在 Codex 中用browser:control-in-app-browser,其余场景用 playwright-core 脚本等),给出精确调用方式与 PASS/FAIL 可观测结果。 - 不信任 DoneClaim:任何 worker 声称完成都必须经过独立的 AdversarialVerify,只有
confirmed是唯一通过判定;false-positive、needs-fix、needs-human-review会带精确反馈打回执行者。 - 正确解读 mailbox 信号:
wait_agent超时只代表"没有新消息",不代表子代理完成;长任务要求先发WORKING: <task> - <phase>,卡住才发BLOCKED: <reason>;对已完成但缺交付物的子代理发送TASK STILL ACTIVE: return <deliverable> or BLOCKED: <reason>,仍无响应则记录 inconclusive、安全关闭并以更小粒度重新派发。 - 勾选与台账:验证全部子任务后,用
apply_patch将- [ ]改为- [x],重读计划确认计数下降,再向台账追加一条task-completed记录。 - 失败不重开:子代理失败时以
FAILED: <exact error>+Diagnosis: <observation>+Fix: <instruction>的修复消息重新派发,而不是从头再来。
四、硬性约束:什么才算真实证据
指令对"证据"的定义极其严格,这部分对应了组件测试中反复验证的质量门槛:
- 先有失败证明,后有产品代码:必须有一条接缝处的单元测试,或子任务 Manual-QA 场景先录得失败;仅镜像实现(mock 调用断言、固定常量)的测试不算证据。改动既有行为时先做 PIN——在未改动代码上跑通的基线特征化测试,要求精确输入、精确可观测结果、精确断言,再按 PIN → RED → GREEN → SURFACE 推进。
--dry-run不算证据,should work不算证据,tests pass不算完成证明。- TUI 视觉证据必须走真实 xterm.js Web 终端:运行
node script/qa/web-terminal-visual-qa.mjs --title "<surface>" --command "<cmd>" --input "{Enter}" --evidence-dir <dir>(live pty + Chrome 内 xterm.js;--from-file <capture>可重放原始流),并引用terminal.png、terminal.txt、metadata.json。 - 禁止
as any/@ts-ignore/@ts-expect-error,禁止删除失败测试。 - 逐一探测 ultraqa 对抗类:凡是触发事实成立的对峙类(畸形输入、提示注入、取消/恢复、陈旧状态、脏工作树、挂起或长命令、flaky 测试、误导性成功输出、反复中断)都必须记录可观测结果;适用类未探测时,仅靠干净的 happy-path 产物不算 PASS,跳过的类要写一行 not-applicable 理由。
- 清理回执(cleanup receipt)是强制的:QA 产生的一切 teardown(脚本、tmux、浏览器上下文、PID、端口、容器、临时目录)要注册为 todo 并执行,残留 QA 状态 = BLOCKED 而非 PASS。
- 工作树边界:若
boulder.json设置了 worktree 路径,所有文件编辑与命令都必须在其中进行,不得越入主仓库;PR/分支相关的工作必须使用任务所有的 git worktree,主工作树只作只读上下文。 - 会话 id 前缀:写入
boulder.json的session_ids必须带codex:前缀;裸 id 在读取时按opencode:旧格式处理。
五、Final gate:完成前的最后一道门
当所有顶层复选框勾选后,指令要求代理先完成以下动作再宣告结束:
- 在真实表面(real surface)上运行自己的手动 QA,并对照每一条验收标准做自审;
- 只有当用户明确要求 strict / rigorous / high-accuracy 审查时,才派发一个门禁审查者(gate reviewer),且每个子任务最多一次;
- 仅在观察到失败时才运行
debugging运行时审计; - 将可观测证据记录到
{{LEDGER_PATH}}; - 未通过门禁前,不得创建 PR、不得做 PR/分支交接、不得合并、不得输出最终完成答复。
PR/分支工作的收尾也有明确模式:停留在任务所有的 worktree 中创建/更新 PR,等待 CI/review/Cubic 门禁,默认合并(除非显式退出),然后清理。门禁与生命周期通过后,在最终答复前把 Boulder 工作标记为 completed。最后必须脱敏 secrets、tokens、凭据、认证头、cookies、环境转储、日志与 PII,并遵循会话开始时记录的交付模式:--make-pr在 PR 打开后以 PR URL 交接(仅用户显式要求才合并);--ship则持续工作直到 PR 被 MERGED(修复 CI 与 review 门禁),随后删除 worktree 并同步.omo/状态。
六、本回合的停止条件:什么情况下回合真的结束
指令定义了五种可被 Stop hook 识别并放行的停止情形:
- 正常推进:某顶层复选框在五阶段 QA 门禁(Phase 1 读取、Phase 2 自动化、Phase 3 通道场景、Phase 4 对抗类探测、Phase 5 门禁决策)后翻转为
- [x],Stop hook 重新评估,若仍有复选框则再次续跑。 - 外部阻塞:只有用户或外部状态才能清除的阻塞(缺硬件、凭据、授权或服务不可用)——经过一次权威检查后停止重试与派发审查者,以
<ulw-execute-blocked-external>作为答案的整首行,随后说明精确阻塞点与恢复工作的可观测条件。 - 三次失败:同一 agent 可控子任务上出现 3 次实质不同的失败修复尝试后,最多派发一次严格审查者;仍被阻塞则按第 2 种标记交接。
- 安全边界:遇到破坏性命令、机密外泄或生产写入时停止,并提供安全的替代方案。
- 全部完成:所有顶层复选框
- [x]且 Final gate 通过,输出 ORCHESTRATION COMPLETE 块并结束。
输出纪律同样重要:只汇报状态变化(派发了哪个子代理、场景 PASS/FAIL 及产物路径、勾选了哪个复选框、追加了什么证据),禁止打印 "Should I continue?"、复述计划或回顾先前回合——续跑由 Stop hook 驱动,台账与计划才是持久记录。
七、Hook 侧实现:boulder 状态读取与清单解析
指令的注入并非无条件。runStopHook(codex-hook.ts)在任何以下情况返回空串(即不阻塞、不续跑):
- payload 不是合法的 Stop 输入(
stop_hook_active为 true、字段不完整等); - 上一条助手消息带有合法的外部阻塞标记;
- transcript 中出现上下文压力标记(如
context compacted、context_length_exceeded、codex ran out of room in the model's context window等,见 codex-hook.ts)——这是防止在上下文被压缩后继续蛮干的自保机制; boulder.json中不存在活动工作、工作已完成、工作不属于当前codex:<session_id>,或计划无可读的顶层清单。
状态解析在 boulder-reader.ts 中完成:读取<cwd>/.omo/boulder.json,同时兼容works映射结构与单工作(mirror)结构;按updated_at/started_at选中最新的属于当前会话的工作;只有active或paused状态可续跑。若工作声明了worktree_path,计划路径会优先解析到 worktree 内(不存在时才回退主仓库)。
清单计数在 plan-checklist.ts 中实现,规则与指令第 2 步完全一致:
- 结构化模式下只统计
## TODOs与## Final Verification Wave两个节下的列 0 复选框(TODO 项形如- [ ] N. ...,Final Wave 项形如- [ ] F1. ...),###级嵌套复选框不计; - 无结构化节时退化为简单清单解析(任意顶层
- [ ]/- [x]); - 代码块栅栏内的内容被跳过,避免把示例误当任务;
- 结果输出
completed / remaining / total / nextTaskLabel,正是{{REMAINING_COUNT}}、{{TOTAL_COUNT}}、{{NEXT_TASK_LABEL}}的数据来源。
八、外部阻塞逃生口:正确书写与 hook 识别
<ulw-execute-blocked-external>是让回合正常结束的唯一"用户介入"通道。指令要求它在两种位置出现:
- 作为答案的整首行(第一行);
- 若 ultrawork 需要
ULTRAWORK MODE ENABLED!作为第一行,则标记单独放在第二行。
hook 侧(codex-hook.ts)不仅校验标记位置,还要求标记之后的后续行中存在非空内容——即必须写出精确阻塞点与恢复条件。这一设计避免了"裸回声"(代理把提示词中的标记原样复读)绕过续跑守卫。只有标记出现在正文中间、后续讨论中时,计划仍会继续。
九、本地验证:Smoke Test 复现注入行为
组件 README 给出了完整的本地验证流程,可在不启动 Codex 的情况下观察 hook 输出。核心步骤:创建临时目录与计划文件、写入boulder.json、把 Stop payload 通过 stdin 管道交给 CLI:
TMP=$(mktemp -d) mkdir -p "$TMP/.omo/plans" cat > "$TMP/.omo/plans/test.md" <<EOF ## TODOs - [ ] Task one - [ ] Task two EOF cat > "$TMP/.omo/boulder.json" <<EOF {"schema_version":2,"active_work_id":"w1","works":{"w1":{"work_id":"w1","active_plan":".omo/plans/test.md","plan_name":"test","session_ids":["codex:smoke-session"],"status":"active"}}} EOF PAYLOAD='{"session_id":"smoke-session","turn_id":"t1","transcript_path":"","cwd":"'"$TMP"'","hook_event_name":"Stop","model":"gpt-5.5","permission_mode":"default","stop_hook_active":false}' npm run build echo "$PAYLOAD" | node dist/cli.js hook stop PAYLOAD_LOOP='{"session_id":"smoke-session","turn_id":"t1","transcript_path":"","cwd":"'"$TMP"'","hook_event_name":"Stop","model":"gpt-5.5","permission_mode":"default","stop_hook_active":true}' echo "$PAYLOAD_LOOP" | node dist/cli.js hook stop rm -rf "$TMP"预期行为:第一条命令输出包含"decision":"block"的 JSON(reason 即渲染后的 directive 全文);第二条(stop_hook_active: true,即正在运行 Stop hook 的防循环场景)输出为空;向hook subagent-stop传入 SubagentStop payload 同样无输出。组件测试 codex-hook.test.ts 与 boulder-reader.test.ts 覆盖了这些分支,包括计划全部完成时仍阻塞直至 Final gate 通过、以及 worktree 路径解析等细节。
十、设计要点小结
- 无网络、无遥测:该组件只读取本地 hook payload、
.omo/boulder.json、活动计划与内置 directive,不做任何网络调用,也不存储遥测(见 README.md 的 Privacy 一节)。 - 注入即续跑:
block+ 完整指令正文 = 告诉 Codex "继续工作",这与普通的"阻塞等待用户"语义相反,是 ULW 自动推进的关键。 - 证据优先:指令用大量篇幅约束证据标准(PIN → RED → GREEN → SURFACE、对抗类探测、清理回执),从源头防止"看起来完成了"的假阳性。
- 会话隔离:
codex:前缀与session_ids匹配确保每个会话只续跑自己的活动工作,多平台会话(opencode:旧格式)在读取时被归一化兼容。
【免费下载链接】oh-my-openagentOmO: Just type "mass ulw" keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考