
OmO 的 DAG 完成验证指令让父级 Agent 对子代理的自我报告先证后信【免费下载链接】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-openagentOmO中DAG_VERIFICATION_DIRECTIVEDAG SUBAGENT COMPLETION - TREAT AS FALSE UNTIL YOU PROVE IT这一机制展开当 DAG 编排中的子节点向父级 Agent 汇报完成时系统会在模型可见的通知内容上强制追加一段先证后信验证指令要求父级自行重构工作范围、读取真实产物并双向核对直到亲自验证通过才可采信。文章以 QA 证据索引 .omo/evidence/20260820-dag-completion-directive/INDEX.md 为骨架结合 dag-verification-directive.ts、notification.ts、dag-wake.ts 等源码完整还原该指令的文本内容、两个注入点、底层实现与整套 RED-first 测试驱动 QA 证据链。读完你将掌握指令语义、注入判定规则、负路径隔离方式以及如何用基线归因baseline attribution证明改动没有引入回归的可复用方法论。1. 问题背景DAG 子代理的完成通知为何不可信在 OmO 的图工程graph engineering编排模型中一个 DAG 运行由多个子节点node组成每个节点由独立的子代理执行最终由父级 Agent 负责裁决整个 DAG 是否完成。问题在于节点的最终回复final_response本质上是子代理的自我报告。子代理可能夸大工作成果、遗漏交付物甚至编造已完成的结论而父级 Agent 若直接采信这些摘要整个编排就会建立在不实证据之上。QA 证据索引开篇即点明本次变更的边界DAG-owned task completions and DAG-run terminal wakes appendDAG_VERIFICATION_DIRECTIVEto the model-facing notification content. Non-DAG completions and paused DAG runs are unchanged.即只有 DAG 拥有的任务完成通知与DAG 运行的终止唤醒消息会被追加验证指令普通非 DAG完成通知与暂停paused的 DAG 运行保持原样。这种只对需要裁决的场景施加验证压力的取舍正是该特性的设计核心。2. 指令定义完整文本与语义拆解指令常量定义于 packages/senpi-task/src/completion/dag-verification-directive.ts源码注释说明了它的设计动机A DAG nodes own summary is unverified self-report, so the orchestrating parent is told to re-derive the nodes scope and prove every deliverable itself before treating the node as done.指令完整文本如下与 QA 证据文件 expected-directive.txt 逐字节一致DAG SUBAGENT COMPLETION - TREAT AS FALSE UNTIL YOU PROVE IT. This completion arrived from a DAG subagent. Assume it overstated or fabricated its work. Its summary is a CLAIM, not evidence. Before relying on this result you MUST, in order: 1. RECONSTRUCT the nodes full work scope from its prompt: every deliverable, file, and check it owed. 2. READ the actual artifacts yourself - open every file it claims it changed, run the commands it claims pass. Transcripts and summaries prove NOTHING. 3. VERIFY each deliverable against that scope with your own eyes and your own tool calls - in BOTH directions: nothing owed is missing, and nothing beyond the scope was done. Over-engineering, drive-by refactors, and edits outside the prompts scope are defects, not bonus work. If ANY deliverable is missing, partial, or unproven, or the node drifted out of scope: send precise corrective instructions to THIS node (dag action send with this run_id and node_id; retry when it cannot be continued) and demand the fix WITH evidence: complete what is missing, revert what fell outside the scope. Loop until your own verification passes. Work is done ONLY when you have verified it yourself.这段文本实际上为父级 Agent 规定了完整的验证协议可拆解为四个层次层次指令原文要点含义前置假设TREAT AS FALSE UNTIL YOU PROVE IT / summary is a CLAIM, not evidence默认不信任把摘要降级为主张而非证据三步验证RECONSTRUCT → READ → VERIFY先重构范围再读真实产物最后双向核对双向核对nothing owed is missing, nothing beyond the scope was done缺失交付物与越界改动过度工程、顺手重构同为缺陷失败闭环dag action send / retrydemand the fix WITH evidenceLoop向原节点下发精确纠正指令并要求附带证据循环直至验证通过其中值得注意的两点一是Transcripts and summaries prove NOTHING——明确否定了转写记录与摘要作为证据的效力强制父级自己打开文件、自己运行命令二是把范围外改动over-engineering、drive-by refactors明确定义为缺陷而非加分项防止子代理借机越权修改。修正回路则依赖 DAG 原语dag action send携带 run_id 与 node_id保证纠正指令精确到达责任节点。3. 注入点一senpi-task 的任务完成通知3.1 判定规则与拼接逻辑第一个注入点位于 packages/senpi-task/src/completion/notification.ts。核心逻辑只有两处buildCompletionDetailsnotification.ts#L47-L49负责在记录层标记 DAG 归属...(record.owner?.kind dag ? { dag: { run_id: record.owner.runId, node_id: record.owner.nodeId } } : {}),即当任务记录的owner.kind dag时生成的结构化详情CompletionDetails中会附带dag: { run_id, node_id }字段非 DAG 记录则不携带任何 DAG 事实。buildCompletionMessagenotification.ts#L54-L63负责在消息层按批次判定并追加export function buildCompletionMessage(details: readonly CompletionDetails[]): ParentNotifierMessage { const body completionMessageLines(details).join(\n) const carriesDag details.some((detail) detail.dag ! undefined) return { customType: senpi-task.completion, content: carriesDag ? ${body}\n\n${DAG_VERIFICATION_DIRECTIVE} : body, display: false, details, } }关键设计是批次级去重只要批次中存在任意一条 DAG 归属的详情指令就只追加一次\n\n分隔位于内容末尾不会因为批次里含多条 DAG 记录而重复拼接。该批次同时保留结构化details数组供程序消费指令只影响模型可见的content文本。3.2 单元测试验证packages/senpi-task/src/completion/notification.test.ts 中新增的dag-owned completions测试组notification.test.ts#L220-L259对上述行为做了三点断言DAG 归属记录构建详情后details.dag精确等于{ run_id, node_id }含 DAG 详情的消息内容中指令恰好出现1 次通过directiveOccurrences辅助函数计数见 notification.test.ts#L210-L218且content以指令结尾、以\n\n分隔普通记录的details.dag为undefined指令出现次数为0。3.3 导出链常量从定义处一路导出completion/index.ts 转发导出最终在 packages/senpi-task/src/index.ts#L292 以oh-my-opencode/senpi-task包的公开符号形式供 omo-senpi 消费。4. 注入点二omo-senpi 的 DAG 运行终止唤醒4.1 终止事件与暂停事件的区分第二个注入点位于 packages/omo-senpi/src/components/task/dag-wake.ts。createDagWake负责把 DAG 运行事件转译为注入父会话的 idle injection。源码用一张常量表界定终止语义dag-wake.ts#L57-L61const TERMINAL_EVENT_STATUSES { dag.run.completed: completed, dag.run.failed: failed, dag.run.cancelled: cancelled, } as const而dag.run.paused被单独列出dag-wake.ts#L65-L68注释解释了缘由暂停不是终止但是用户可见的——recovery 的pauseRunsForShutdown在会话关闭含 reload时会挂起所有在途运行主会话必须被告知 DAG 中途停止而不是事后默默发现。因此暂停事件仍会产出唤醒消息只是内容不同。4.2 终止消息摘要 指令buildRunInjectiondag-wake.ts#L116-L121分发两条路径暂停走buildPausedInjection终止且携带节点计数走buildInjection。后者dag-wake.ts#L140-L165的关键实现content: ${buildSummary(run.name, status, counts, firstFailure)} ${DAG_VERIFICATION_DIRECTIVE},注释明确说Terminal runs are the point where the parent decides the DAG is done, so the run summary carries the same prove-it-yourself directive the per-node completions do. A pause is not a completion claim and stays directive-free.——终止运行是父级下结论的时刻所以运行摘要与逐节点完成通知携带同一条指令暂停不是完成主张保持无指令。buildSummarydag-wake.ts#L167-L177生成紧凑汇总DAG name status: X completed, Y failed, Z cancelled, W skipped (N total)失败时追加First failure at node [code]: message——这条失败信息在追加指令后仍被完整保留不因附加指令而丢失关键上下文。注入对象还携带结构化detailsrunId、name、status、counts、可选firstFailure并标记customType: omo-senpi.dag-run、display: false、deliverAs: steer。4.3 暂停消息显式无指令buildPausedInjectiondag-wake.ts#L123-L138产出的内容是DAG name paused (reason): it will resume when the session restarts.不拼接任何指令details.status为paused。这正是paused DAG runs are unchanged的落点。4.4 缓冲与重投递createDagWake 还处理了会话状态竞争当父会话处于compacting、session_switching、session_shutdown时注入先按parentSessionId缓冲同 key 去重onSessionStart时再统一重投并flushSoon()。这意味着即使终止事件发生在会话压缩/切换窗口内指令化的唤醒消息也不会丢失而是在会话恢复后重放。4.5 单元测试验证packages/omo-senpi/src/components/task/dag-wake.test.ts 对终止路径断言了精确内容dag-wake.test.ts#L104-L107expect(harness.delivered[0]?.message.content).toBe( DAG \release-pipeline\ failed: 3 completed, 1 failed, 0 cancelled, 0 skipped (4 total). First failure at build [task_error]: compile failed \n\n${DAG_VERIFICATION_DIRECTIVE}, )同一窗口内两条终止运行被合并为一条 steerdag-wake.test.ts#L137-L140每条摘要各自携带指令而暂停路径则断言内容不含指令dag-wake.test.ts#L272 附近not.toContain(DAG_VERIFICATION_DIRECTIVE)。5. QA 证据链从 RED 到 GREEN 的完整闭环本特性的 QA 证据索引记录了严格的 TDD 式验证流程核心原则是让每次失败精确命名缺失的构件从而排除空转通过vacuous pass的可能。5.1 RED-first先证明测试确实会失败产物测试对象观察结果red-notification.txtbun test packages/senpi-task/src/completion/notification.test.ts新断言就位但dag-verification-directive.ts尚未创建FAIL for the right reasonerror: Cannot find module ./dag-verification-directive0 pass / 1 fail / 1 error非语法/拼写类失败red-dag-wake.txtbun test packages/omo-senpi/src/components/task/dag-wake.test.ts符号尚未 re-exportFAIL for the right reasonSyntaxError: Export named DAG_VERIFICATION_DIRECTIVE not found in module .../senpi-task/src/index.ts0 pass / 1 fail / 1 error两次 RED 分别命中了常量文件缺失与包导出缺失两个不同层次证明后续 GREEN 不是因测试形同虚设而得来的。5.2 指令文本字节级相等expected-directive.txt与导出的DAG_VERIFICATION_DIRECTIVE做逐字节比较结果精确匹配、无尾随换行漂移。这保证了模型实际收到的指令与 QA 审查的文本完全一致。5.3 GREEN 结果全景产物测试对象观察结果green-completion.txtbun test packages/senpi-task/src/completion/通知构建面62 pass / 0 failgreen-dag-wake.txtbun test packages/omo-senpi/src/components/task/dag-wake.test.ts唤醒注入面10 pass / 0 failgreen-senpi-task-full.txtbun testinpackages/senpi-task整个任务/DAG 引擎1686 pass / 1 skip / 0 failgreen-omo-senpi-full.txtpackages/omo-senpi包目录 CWD 下运行2090 pass / 1 skip / 27 failCWD 敏感见 §6green-omo-senpi-rootcwd.txt仓库根 CWD 下运行bun test packages/omo-senpiCI 口径2104 pass / 1 skip / 13 fail见 §6typecheck-senpi-task.txtbun run typechecktsgo --noEmit -p tsconfig.jsonexit 0干净typecheck-omo-senpi.txt同上packages/omo-senpiexit 0干净gate-tsgo-omo-senpi.txt仓库根执行tsgo --noEmit -p packages/omo-senpi/tsconfig.jsonsenpi 门禁第 1 步exit 0干净gate-test-senpi.txt仓库根执行bun run test:senpi门禁第 2 步构建 plugin tsgo bun test packages/omo-senpi resolver 测试2104 pass / 1 skip / 13 failexit 1——所有失败均为既有失败gate-fails.txt门禁运行中排序去重的(fail)名称13 个与rootcwd-fails.txtcomm完全一致6. 基线归因方法如何证明不是我的锅这是该 QA 证据链中最具方法论价值的部分——它把改动未引入回归从断言变成了可复算的证明。6.1 comm 集合运算方法分别收集各次运行中排序去重后的(fail)测试名集合然后计算comm -13 baseline minecomm -13输出存在于 MY 运行、但不存在于 BASELINE的失败集合即 MINE-ONLY 回归。结果非空即意味着改动破坏了某些东西。两个独立基线在基提交5d2742bf7生产变更缺席、同一主机同一工具链上测得green-fails.txt (mine, package CWD) : 27 failures rootcwd-fails.txt (mine, repo-root CWD): 13 failures baseline-fails.txt (baseline, package CWD): 40 failures comm -13 baseline-fails.txt green-fails.txt - EMPTY (0 MINE-ONLY) comm -13 baseline-fails.txt rootcwd-fails.txt - EMPTY (0 MINE-ONLY) comm -23 baseline-fails.txt green-fails.txt - 13 (baseline-only) reconciliation: 27 (mine) 13 (baseline-only) 40 (baseline total) EXACT6.2 CWD 效应的归因27/13 的分裂是纯粹的 CWD 产物而非行为差异27 个包目录 CWD 失败中有 14 个是相对仓库根解析路径的测试例如packages/omo-senpi/packages/omo-senpi/scripts/qa/drive.mjsENOENT、scandir packages/omo-senpi/src/components/lsp、读取根package.jsonworkspaces。CI 的 senpi 门禁从仓库根运行因此rootcwd列表是权威清单——而 §5 的门禁运行精确复现了那 13 个。6.3 更新鲜的基线交叉验证第二份更晚生成的基线来自5d2742bf7的纯净git archive解包node_modules 软链、无 git 状态变更25 个失败comm -13 gate-baseline-fails.txt gate-fails.txt同样为EMPTY。额外 12 个基线失败skill-sync、installer source-refresh、palace entry collector、assembled-DAG-runtime shipped-RPC是归档副本缺少build:senpi-plugin产物与未跟踪生成文件所致无一出现在本次运行的失败列表中。结论任何一次运行中DAG、completion、wake 相关的失败数均为 0。13 个权威失败全部属于cli-localinstall/uninstall、createInitDeepAdvisorComponent11 个、OmO Native product identity、session_startcomponent ordering——均未被本次变更触碰。7. 真实表面Real Surface验证不靠推断的注入证据单元测试之外QA 用辅助脚本驱动真实生产代码打印模型实际看到的内容避免推断可见。7.1 完成通知真实表面aux-real-surface.ts 直接调用真实的buildCompletionDetails/buildCompletionMessage覆盖三种记录DAG 归属记录owner: { kind: dag, runId: dag_run_demo, nodeId: impl, fingerprint: fp }→ 输出为完成摘要行 空行 完整指令结构化dag详情为{run_id:dag_run_demo,node_id:impl}普通记录plainMsg.content.includes(DAG_VERIFICATION_DIRECTIVE)为false混合批次1 条普通 2 条 DAG指令出现次数为1验证批次级去重。7.2 唤醒真实表面aux-dag-wake-real-surface.ts通过真实的IdleInjectionCoordinator驱动真实的createDagWake覆盖三种事件dag.run.completed→ 摘要 指令deliverAs: steerdetailscustomType: omo-senpi.dag-rundag.run.failed→ 指令和保留的First failure at impl [node_failed]: ...行dag.run.paused→无指令出现次数为 0。两个负路径普通完成、暂停运行都被显式证明为无指令泄露漏注入会在可见对象上暴露而非仅靠推断。8. Live Senpi QA真实二进制上的端到端确认QA 还针对真实安装的senpi二进制本地~/.local/bin/senpi存在故不将 SKIP 视为通过运行了实时驱动drive.mjs --self-test→ PASSharness/隔离前置条件drive.mjslive → PASSrealSenpiUntouched: true调用方 agent 目录被忽略task-e2e.mjslive模型可见的 completion/wake 路径→ 21 PASS / 6 FAILrealSenpiUntouched: true、realSenpiChangedPaths: []、0 个泄漏 PID同一task-e2e.mjs在基线5d2742bf7上运行 →同样的 6 项检查失败driver-attribution.txt显示 MINE-ONLY NONE、BASE-ONLY NONE集合完全相同全部 27 项检查逐项 verdict 一致。此外实时线缆抓包live wire capture记录了一条真实omo-senpi:wake完成消息任务st_01a0219a内容不含TREAT AS FALSE文本、无dag详情——非 DAG 负路径在真实 senpi 会话上得到实证。9. 边界、遗漏与清理QA 报告如实声明了未覆盖项避免过度宣称未驱动真实 DAG 运行通过senpiCLI仓库没有可派生的驱动脚本新增一个会要求修改仓库脚本超出任务许可因此在真实 wake/injection 对象层面闭环§7既有失败只归因、不修复6 个 driver 检查与 13 个门禁测试均为既有失败超出本次范围不包含任何机密未记录 secrets、tokens、auth headers 或 env 转储仅记录~/.senpi/agent的文件清单与摘要零污染未提交、未暂存、未推送任何内容未修改任何生产、测试或changes.md文件。清理收据同样完整driver 自行回收所有派生的 senpi 进程两次 live 运行均no_leaked_pids: PASS、leakedPids: 016 16 个被跟踪 PID 确认终止隔离沙箱根与基线解包目录已删除pgrep -f omo-senpi-qa|senpi无残留 QA 进程真实~/.senpi/agent无 QA 归因改动。10. 结语一处在信任边界上的工程实践DAG_VERIFICATION_DIRECTIVE本质上是 OmO 在 Agent 编排信任边界上的一次工程实践凡是从 DAG 子代理抵达父级的完成信号一律先证后信——通过注入到模型可见文本中的强制验证协议把证明工作的责任压回真正拥有裁决权的父级 Agent同时用双负路径不变普通完成、暂停运行保证指令只出现在需要裁决的场景。而整套 QA 证据链RED-first、字节相等、comm 基线归因、真实表面打印、live 二进制确认则展示了如何在无法修改生产代码的前提下把一个提示词级别的变更验证到可复算、可归因、零回归的程度。对于想要在自研编排系统中借鉴此模式的开发者可以直接复用两个注入点的判定思路结构化数据层用owner.kind打标消息层按批次判定拼接、批次级去重并始终为负路径保留显式测试——因为不该注入时绝不注入与该注入时必定注入同样重要。延伸阅读仓库内senpi-task 完成通知实现 · 指令定义 · 通知测试 · DAG 唤醒实现 · DAG 唤醒测试 · QA 证据索引【免费下载链接】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),仅供参考