DAG Engineering
把较大的工程任务拆成有依赖关系的子任务(节点),每个节点由子代理在自己的 worktree 和分支里实现,主代理在集成 worktree 中逐个合并验收。返修也遵守这项隔离,子代理不进入集成 worktree 修改或提交。主代理负责调查规划、派发、集成、返修安排和最终验收;调查子代理只读回答具体问题;节点子代理先按 refine 细化,再实现并提交单个节点;审查子代理只读审查。
调查深度、子代理数量和验证范围按任务实际复杂度决定;没有独立工作时不为使用子代理而拆分。
Codex 派发任何子代理时显式设置 fork_turns="none"。
0. 继续已有 plan
开始前查看主工作区的 .dag/。存在“plan 状态:进行中”的 plan 时,读取它,结合 Git 实际状态从当前进度继续,不重新拆分。plan 与 Git 不一致时,以核实后的代码状态修正 plan。
- 集成 worktree 存在
MERGE_HEAD(git -C <集成wt> rev-parse -q --verify MERGE_HEAD):集成中断,验证结果未知,执行git merge --abort后把该节点按submitted重新集成。 - 逐个核对节点:
done:确认节点提交已合入集成分支(git -C <集成wt> merge-base --is-ancestor <提交> HEAD),不重复执行。submitted:节点提交已合入集成分支时,直接标为done(合并提交只在验证通过后产生);否则按第 6 步集成。running:本会话中代理仍在执行就继续等待。代理已不存在时,查看节点 worktree 的提交和未提交改动,派新代理在同一 worktree 从现有成果继续,不新建 worktree。blocked:读取原因,判断条件是否已经解除。pending:按第 5 步调度。节点还是旧写法(目标、范围、步骤、验收、验证)时,派发前改写成第 4 步的模板。
- 所有节点都已
done:从第 7 步继续。
状态不明时不删除 worktree、分支或未提交改动。
1. 开集成 worktree
每个 DAG 开一个集成 worktree 和分支,只由主代理写入,主工作区不改动。
git worktree add "../<repo>-<plan-id>" -b "dag/<plan-id>"
<plan-id>用简短的 kebab-case 描述本次工作;分支或目录已存在时换一个名字。- 主工作区有未提交改动时,告诉用户这些改动不会进入 worktree,由用户决定先提交还是继续。
- 验证命令依赖安装步骤(如
node_modules、虚拟环境)时,先在集成 worktree 完成安装。 - 记下起点提交(
git rev-parse HEAD),整体审查以它为基准。
2. 调查与关键设计
顺序:需求分析 → 定向代码调查 → 统一关键设计。
主代理先根据需求定位现有实现、相关模块、调用关系和测试,列出影响规划的未知问题。歧义会改变目标、范围或验收标准时,先问用户;其余不确定项写进 plan 的“假设”。
存在多个相互独立的调查问题时,在同一条消息里派发只读调查子代理并行调查;问题少或相互依赖时主代理自己查。调查按具体问题划分(已有实现与复用、关键接口与调用链、共享状态与外部资源、测试与工程约束等),不按目录机械划分。调查子代理直接读集成 worktree,不需要自己的 worktree。指令写明:
只读调查
<集成wt>,不修改任何文件,不派发其他代理。目标:<…>。范围:<…>。需要回答:<问题列表>。问题回答完即停止,不做范围外的探索。只汇报:关键事实及位置(文件:符号)、可复用的现有实现、工程约束、未能确认的事项;区分确认的事实和推测。
主代理整合结果,处理相互矛盾的结论,只对影响规划的缺口补查。事实足以写出节点的 goal、scope、verify 和 stop 时就停止调查;steps 的细节留给节点子代理开工时细化(第 5 步)。规划时的调查只到能定节点边界的深度:用结构查询(文件与行数、调用关系、依赖边、清单条目)回答,不读函数实现;读懂实现是节点子代理的事。
跨模块事实(某个符号被谁调用、某个模块依赖什么、一组文件连同依赖有多大)用语言服务器、ast-grep 或搜索按需查,不整份读源码推导;查到一次就记下复用:主代理或调查子代理查到的,写进相关节点的 refine 结果或 note;节点子代理在汇报里列出新发现的跨节点事实,主代理记进会用到它的下游节点的 refine。下游开工时先看这些记录,记录不足或可能已过时(相关代码已被后续节点改动)再重查。
然后确定并写进 plan 的“关键设计”:
- 目标行为和非目标;
- 直接复用的已有实现、依赖或平台能力;
- 功能及代码的归属;
- 跨节点必须一致的接口、数据结构和架构约束;
- 共享资源和集成条件。
只固定跨节点协作必需的设计,节点内部实现留给节点子代理。不为未来需求增加抽象,不重复实现已有能力。
3. 拆分 DAG
- 节点粒度:以一个可独立验收的行为为单位,包含使它可运行、可验证的必要接入。配置、脚手架、文档只有一个节点使用时并入该节点,多个节点使用时提成一个上游节点。高耦合、拆开会产生无法验证的中间状态或大量重复修改的工作合并成一个节点;低耦合的工作拆开以便并行。不为增加并行度强行拆分。
- 逐项核对:节点需要覆盖大量文件或条目时,先明确核对项和逐项验收标准;可独立核对的范围交给只读子代理并行处理,汇总缺口后再实施。已有核查结论仍有效时直接复用,不重复扫描。
- 依赖:只有下游真正需要上游的代码、决定或已验证的行为,或者上游失败会让下游作废时,才写依赖。共享接口已在关键设计中确定、双方能各自实现和测试时,不写依赖,允许并行,由集成验证检查真实对接。不为控制顺序编造依赖。
- 冲突:没有依赖但不能安全同时执行的节点才标冲突:逻辑上互相影响的修改,数据库、端口等共享运行资源,迁移序号等必须串行分配的资源。同一文件的普通修改重叠不算冲突,允许并行,由 Git 合并处理;只有重叠集中、合并成本明显高于串行时才标冲突。
- 仍需实验才能确定的方案:建一个调查节点产出结论,下游依赖它。
每个节点按第 4 步的模板写。规划时写 goal、scope、verify、stop、refine;steps 由节点子代理开工时按 refine 细化(第 5 步),规划时已能确定的步骤可以先写。
- goal:一两句,做完后什么行为成立。
- scope:允许修改的文件或目录、明确不许改的部分,以及必须遵守的关键设计和业务约束。
- verify:可观察的验收结果,以及能直接运行的真实命令,包含受本节点改动影响的现有守卫,如文档体量、代码复杂度、分层和契约检查。优先用窄入口;没有窄入口才运行完整检查,完整检查不与其他验证并发。
- stop:本节点特有的停下汇报条件,如“归属按规则判不出”“某接口不存在”。通用条件已在派发指令里,不重复写。
- refine:写出 steps 需要查清的事项,写成可执行的查询,如“列出 X 前缀下的全部条目及其依赖的未处理模块”“列出 Y 的全部调用方”。由节点子代理在开工第一段完成。规划或调查时已经查到的结论直接写在 refine 里并标明已查,子代理直接用,不重查;refine 只把尚未回答的项留作查询。没有待查事项写“无”。
- steps:有序步骤,每条只做一件可核对的事,写到模块、命令、接口一级:做什么、落在哪、对外行为是什么。函数一级只在边界处写:同一文件要分给不同节点,或某个函数的归属有争议时,列出边界两侧各有哪些函数。
来源材料和关键设计已经确定的值、条件、边界,直接写进相关节点,不留给子代理推导。文件、符号和命令必须来自实际代码,不得编造。以下写法要改掉:
- “酌情实现”“处理边界情况”这类不可执行的描述;
- 验收标准只复述实现动作,如“实现了 X”;
- 行为类验收只靠文件存在、能导入或关键词搜索来证明;
- 分类或取舍只引用规则名、文档章节(如“按 D7 判”),不写默认动作和例外条件。
派发前检查 plan,回答两组问题。
**计划全部完成后,全局验收标准是否必然达成。**按以下各项找缺口:
- 范围从实际来源枚举,不只从需求推:涉及的代码、配置、版本库外的文件与本机状态、安装与部署方式、其他运行平台、外部调用方。每一项恰好归一个节点,或写进非目标。
- 每条验证都成立:能在目标环境运行;运行时不写生产数据或外部系统,确需写入的节点写明隔离方式;通过即能推出对应的验收结论;验证所用的工具本身已被验证。
- 计划引入的临时豁免、兼容层、过渡数据和工具,都有节点负责撤除或转正。
- 写外部系统、切换生产环境、只在其他平台运行的路径,交付前至少有一个节点在真实环境实际运行一次。
- 节点之间接口与架构一致;依赖、冲突没有错误或遗漏;节点之间的集成有节点负责;每个节点的 refine 能查出 steps,verify 都能执行。
缺口补成节点。补不了,或补上的代价需要用户取舍时,写进 plan 的“完成度缺口”,交用户决定。
设计是否浪费时间。
- 每个节点的产出都有下游使用者,验证也算;没有使用者的节点删掉。
- 需要用户决定或在场、会改变既有行为的节点,不让其他节点等它;有下游必须等它时,拆成不改行为的部分(先做)和改行为的部分(后做)。
- 两个以上节点要用的同一能力或工具,提成一个上游节点,不让各节点各做一遍。逐个节点确认它的 verify 靠什么证明:两个以上节点要用同一种验证手段(新旧对照、回放、产物比对等)时,也提成上游工具节点,该节点的 verify 包含用一个已知有差异的样本证明工具能报出差异。两个以上节点要反复查同一份大型资料(清单、映射表、决策文档),而项目没有按需查询的入口时,也提一个上游节点补上查询入口。
- 估出每个节点需要读懂和修改的量(按 scope 内文件加它依赖的、尚未处理的代码计算)。量大到一个子代理难以稳妥完成,或横跨互不相关、能各自独立验收的模块时,拆开;内聚紧、拆开会产生无法验证的中间状态的,量大也不拆。行数只是判断依据,不设固定阈值。估不出的,把要算的内容写进 refine,由节点子代理开工第一段估算;拆法要在建 worktree 前定时,按第 5 步先派调查子代理估算。
- 没有重复实现已有能力,没有不必要的拆分或过度设计,可以安全并行的节点没有被串行。
节点不超过 5 个时由主代理检查。超过 5 个时,先把草稿按第 4 步写到 .dag/<plan-id>.md,再派一个只读子代理审查,只给它 plan、需求和代码位置,不给主代理的推理过程:
只读审查
<plan 路径>,不修改任何文件,不派发其他代理。需求:<原始需求或其位置>。代码:<集成wt>。按以下各项逐条检查:<上面 1–10 项原文>。只汇报:完成目标的缺口、浪费时间的设计、需要用户决定的事项;每条写位置、证据(文件:符号、命令输出或 plan 原文)和最小修改建议,区分确定的问题和推测。
主代理只修改有具体证据的问题。
4. 保存 plan
写到主工作区的 .dag/<plan-id>.md,不放进任何 worktree,避免被提交。.dag/ 未被 Git 忽略时,把它加到 .git/info/exclude。
# <plan-id>
- 目标:
- 全局验收标准:
- 非目标:
- 假设:
- 完成度缺口:<计划做完仍不能保证达成的部分,及用户的决定;没有写“无”>
- 关键设计:
- plan 状态:进行中
- 集成 worktree:<路径> 分支:dag/<plan-id> 起点:<sha>
- 接口记录:`.dag/<plan-id>-interfaces.md`
## 节点
### N1 <标题>
#### goal
#### scope
#### steps
<节点子代理开工时细化>
#### verify
#### stop
#### refine
#### state
- 依赖:无
- 冲突:无
- 状态:pending
- worktree: 分支: 提交:
## 执行记录
- N1:<合并提交 sha> <一句话结果>。<返修、异常或遗留问题;没有不写>
接口记录文件与 plan 放在一起,每个节点合入时追加一节,节点子代理可以只读它:
## N1(<合并提交 sha>)
- <接口、命名与位置>:<形状与用法>
执行记录只写结果和遗留;对外接口写进接口记录文件,跨节点事实写进会用到它的下游节点的 refine,对照差异等细节留在节点的提交说明和汇报文件里。
goal 到 refine 都传给节点子代理;state 只供主代理使用。
状态取值:
pending:未派发;running:子代理正在实现或返修;submitted:子代理已提交,等待集成;done:已合入集成分支并通过主代理验收;blocked:无法继续,原因写进执行记录。
plan 只由主代理修改。每次状态变化都立即更新,它是跨会话继续工作的依据。
保存后向用户简要展示节点、依赖、可并行的节点、完成度缺口,以及全部需要用户决定的事项(一次列全,不分轮询问)。用户只要求出计划时在这里停下;否则继续执行。
5. 动态派发
依赖全部 done、且与 running/submitted 节点没有冲突的 pending 节点即为就绪节点。找出全部就绪节点,在同一条消息里并行派发,不设固定的并发上限,实际并发以代理工具和环境允许为准。之后每有节点 done,立即重新找就绪节点并派发,不等待其他独立节点结束。
派发每个节点前,主代理只做前置核对:列出本节点用到的上游决定、接口、命名和规则,确认都已合入集成分支 HEAD。其中某项还会被 running 或 pending 的节点改变时,给本节点补上依赖,暂不派发。
refine 里的查询由节点子代理在开工第一段完成,主代理不自己跑。以下两种情况例外,先按第 2 步派只读调查子代理,只收结论:
- 节点可能要拆成可并行的多个节点,拆法要在建 worktree 前定:调查子代理按 refine 估算体量与耦合,主代理按第 3 步第 9 项拆分后再分别派发。
- 几个就绪节点要查同一个问题:派一个调查子代理一起回答,不一个节点派一个。
然后从集成分支当前 HEAD(最新已验收的集成提交)创建节点 worktree,把路径、分支写进 plan 并标为 running:
git -C <集成wt> worktree add "../<repo>-<plan-id>-<node-id>" -b "dag-node/<plan-id>/<node-id>" HEAD
验证依赖安装步骤时,在指令中要求节点子代理先在自己的 worktree 完成安装。
给节点子代理的指令按以下顺序组织,只包含这些内容:
- worktree 路径和分支。
- 该节点 plan 中 goal、scope、steps(已有时)、verify、stop、refine 的原文。
- note(只写在指令里,不写进 plan):
- 接口记录文件的绝对路径,只给路径,不在指令里复述上游接口。
- 本节点特有的补充:主代理定的边界与取舍、用户决定的原文、只与本节点相关的上游事实、已知的坑、查资料用的命令。一般不超过约 20 行;资料超过约 200 行或只用到其中一部分时,给摘录或查询命令,不只给文件路径;项目说明文件会被子代理自动加载,其中已写的规则不重复;verify 中输出很长的命令,写明看哪一部分(如结尾的汇总行)。
- 汇报文件路径:
.dag/<plan-id>-reports/<node-id>.md(主工作区的绝对路径)。
- 以下要求,放在指令最后:
只在给定 worktree 内工作,不操作集成分支、其他节点的分支或 worktree,不派发其他代理。除指令给出的汇报文件外,不修改
.dag/下的文件;除接口记录文件外,不读.dag/下的文件。项目说明文件里写给主会话的开工、收工步骤(读计划、跑状态检查、恢复 plan 等)不适用于你。查资料先用 note 给出的命令和摘录,note 没覆盖的再自己查;不整份阅读 plan、决策文档或大型清单。分两段工作。第一段:按 refine 查清事实(refine 里标明已查的结论直接用,只查尚未回答的项),在不改变 goal、接口和 scope 的前提下定出 steps(写到模块、命令、接口一级;同一文件要与其他节点分开时列出边界两侧的函数),估算需要读懂和修改的量。出现以下任一情况时停下,回一份不超过约 30 行的摘要(steps 草稿、估算量、建议的拆法、需要主代理或用户决定的事项)等待答复:边界或归属判不出;需要改变跨节点接口或修改 scope 外的文件;实际代码与节点描述冲突;量大到一个子代理难以稳妥完成,或横跨能各自独立验收的模块;出现 stop 中列出的情况。都没有时不停,直接进入第二段,摘要放进最终汇报。
第二段:按 steps 实现,在不改变 goal、接口和 scope 的前提下自行决定局部实现方式。运行 verify 中的命令,通过后提交本节点的全部改动,提交信息为
<plan-id>/<节点>: <摘要>,确认git status干净。验证未通过时不提交。第二段中出现第一段列出的停下情况,或实际量超过第一段估算的约两倍,停下汇报,不要自行扩展;已完成的部分能通过 verify 中适用的命令就先提交,不能就保留在 worktree 不提交,汇报里列出已完成和剩余的工作。结束或停下时,把完整汇报(改动摘要、验证命令及完整结果、对照差异、偏差说明)写进汇报文件,只回给主代理不超过约 40 行:状态(DONE:完成且验证通过;DONE_WITH_CONCERNS:完成但有疑虑;BLOCKED:无法完成或停下)、提交 sha、验证结果一行、对外接口的新增或改变、新发现的跨节点事实(如某个符号还被哪些模块使用、某个接口实际的形状)、疑虑或阻塞原因、需要决定的事项。
子代理第一段停下回摘要时,主代理核对边界和需要决定的事项(需要用户决定的先问用户),把确认的 steps 写回 plan,再用代理工具的续接能力(Claude Code 的 SendMessage;Codex 向同一代理发送后续输入)让同一个子代理继续第二段,不新派代理,避免重读它已读过的代码。建议拆分时,按第 6 步“plan 本身有误”的规则拆:原子代理继续其中一部分,其余部分作为新节点从第 5 步派发。原代理无法续接时,派新代理在同一 worktree 继续,并附上摘要。
子代理返回提交后,把提交 sha 写进 plan,标为 submitted,进入第 6 步。
等待规则(调查、审查子代理同样适用):
- 有具体且不与子代理范围冲突的独立工作(如集成已提交的节点)时,先完成它;没有时进入原生等待。
- Claude Code 使用后台代理的完成通知或原生等待。Codex 直接调用代理工具,不放入
functions.exec或functions.wait;wait_agent设置timeout_ms=600000,若工具参数不支持该值,使用允许的最长等待。工具只能批量返回结果时按其能力处理,不模拟不存在的通知。 - 不用循环短暂 sleep、查询代理列表或重复查看 Git 状态代替等待,也不为发送进度更新而缩短等待。
- 收到结果或用户消息后立即处理。仅等待超时不代表失败,继续等待,不重新派发仍在执行的节点。
6. 逐节点集成验收
主代理在集成 worktree 中一次集成一个 submitted 节点,其他子代理继续工作。不直接采信子代理的汇报:
git -C <集成wt> diff --stat HEAD...<节点分支>:改动的文件都在节点 scope 内;节点 worktree 的git status没有遗留的未提交改动。再看与其他节点共用的文件(公共模块、配置、守卫基线、清单、契约测试)的完整 diff,确认符合 goal 和关键设计。节点自有的新代码靠第 3 步的验证和实测行为判断,不逐行阅读;需要细节时读汇报文件里相关的段落。- 合并但先不提交:
git -C <集成wt> merge --no-ff --no-commit "dag-node/<plan-id>/<node-id>" - 在集成 worktree 运行该节点 verify 中的命令,以及受合并影响的集成检查;对照 verify 的验收结果逐条核对,至少实际验证一条行为。受影响守卫的新增失败在本节点修复,不留到最终验收;已有失败单独记录,不放宽阈值或基线。不要求每次都跑完整测试套件。
- 全部通过:
git -C <集成wt> commit --no-edit,节点标为done,写一行执行记录,把对外接口追加进接口记录文件,把跨节点事实写进会用到它的下游节点的 refine,然后回到第 5 步派发新解锁的节点。
清单覆盖类验收须逐项记录已核实的结论、待核实项和缺口。只有全部声明范围已核实并满足验收标准,才能标为 done;文件存在、正文非空或抽样通过不能代替逐项覆盖。
合并出现冲突时,双方意图清楚的普通重叠由主代理直接解决,再执行第 3 步。
其余冲突或检查未通过时,撤销未完成的合并(git -C <集成wt> merge --abort),集成分支保持在上一个已验收提交,节点提交保留在节点分支上。按问题归属处理:
- 节点实现缺陷:节点标回
running,把具体问题交回原节点子代理在原 worktree 修复后重新提交;原代理不可续接时,派新代理在同一 worktree 继续。主代理不默认亲自重写节点实现。 - 需要节点参考最新集成成果的冲突:让节点子代理在自己的 worktree 合并
dag/<plan-id>后解决冲突、重新验证并提交。 - 跨节点接口或逻辑冲突:主代理确定正确设计,更新 plan 的关键设计和相关节点,再交给相关节点子代理调整。
- plan 本身有误(scope、steps、依赖或 verify 与实际代码不符):根据新发现的代码事实只修改受影响的节点、依赖和验收,保留有效的已完成成果,不重新设计整个 DAG。修改涉及全局验收标准或非目标时,先问用户。
- 子代理汇报
DONE_WITH_CONCERNS:疑虑影响验收标准时按未通过处理;否则照常集成,把疑虑写进执行记录。 - 子代理因实际量超出估算而停下:已提交的部分能独立验收时,把本节点的 goal、scope、verify 改成已完成的部分,按本步集成;剩余工作按“plan 本身有误”拆成新节点,从第 5 步派发。已提交部分不能独立验收时,不集成,节点保持
running,把剩余工作写进该节点的 steps,派新代理在同一 worktree 从现有成果继续。 - 子代理汇报
BLOCKED或节点无法继续:标为blocked并记录具体原因,依赖它的节点不派发,其他节点继续。没有就绪或运行中的节点、但仍有blocked节点时,停下向用户报告阻塞原因,等用户决定后再继续;不进入第 7 步。
节点 done 后清理它的 worktree 和分支。worktree 有未提交改动时停下检查,不使用 --force 或 -D:
git -C <节点wt> status --porcelain
git -C <集成wt> worktree remove "<节点wt>"
git -C <集成wt> branch -d "dag-node/<plan-id>/<node-id>"
blocked、返修中或状态不明的节点保留 worktree 和分支。
7. 整体审查
所有节点都 done 后,派一个只读子代理审查集成分支的完整改动:
只读审查
<集成wt>中<起点sha>..HEAD的全部改动,不修改任何文件,不派发其他代理。对照以下目标、全局验收标准和非目标检查:需求覆盖、跨节点接口和行为的一致性、重复实现、偏离关键设计的架构、正确性缺陷、遗留的调试或临时代码、重要测试是否缺失或没有真正覆盖声称的行为。每个问题给出位置、证据和涉及的节点,区分确定的问题和推测;另列需要用户决定的事项。
指令中附上 plan 的目标、全局验收标准、非目标和关键设计。
主代理核实每个问题后按归属安排修复:需要实现的问题作为修复节点(如 F1)写进 plan,按第 5–6 步派发和集成;局部且明显的小问题(如遗留的调试语句)主代理可以直接在集成 worktree 修改并提交。需要用户决定的事项不自行决定,写进报告。
8. 最终验收并报告
主代理在集成 worktree 完成以下检查:
- 查看
<起点sha>..HEAD的完整 diff,确认没有超出 plan 目标的改动。 - 运行所有节点的验证命令,以及项目中覆盖改动模块的测试、构建和 lint 命令。
- 逐条核对全局验收标准。
检查失败时:问题在 plan 目标内,按第 7 步的归属规则修复后重新检查;问题不在 plan 目标内(如起点就已失败的测试),不修复,写进报告。全部完成后把“plan 状态”改为“已完成”。
最终报告包括:完成的节点和对应合并提交、审查发现和处理、验证结果、未完成或有风险的事项、需要用户决定的事项、集成 worktree 路径和分支、保留的节点 worktree。合并到目标分支和删除集成 worktree 只在用户要求时进行。