工作原理
CodeDAG 怎样把写成工作流的一组任务排队执行、交给 Agent 完成,再把结果交还给你。
你把一组有依赖关系的开发任务写成工作流文件,提交给 CodeDAG,它就在你的电脑上后台推进:按依赖为每个任务启动一个全新的 Claude Code 或 Codex 会话,默认让它在自己的分支上工作;任务的结果通过审核后交给下游,最后汇到一条集成分支上,你的主分支保持不动。审核默认自动通过,所以按计划需要你参与的,只有你另外设定的人工审核和最后合入主分支。同一个项目里一次只执行一个工作流,后提交的排队等候。
工作流
一个目标大到单个 Agent 会话做不好时,就要拆成几个任务,每个任务交给一个会话去做。这些任务之间有依赖,手动按顺序一个个启动会话,你就得一直守着。CodeDAG 的做法是把任务和依赖事先写进一个文件,由它按依赖启动各个任务。这样一个文件所描述的一组任务,就是一个工作流。任务该拆到多大,见为什么要做任务拆分,以及拆分的标准。
“工作流”一词在不同场合含义不同。常见的一种,是指完成一个任务要走的一串步骤,很多时候就写在一段 prompt 或一份技能文档里:这些步骤大多依次执行,往往就在同一个会话里做完,彼此并不隔离。CodeDAG 的工作流与此有两点不同。一是粒度更粗:那样一整个工作流所完成的任务,在这里只是其中的一个节点;任务内部具体怎样做、调用哪些工具,都不体现在工作流的结构里,由执行它的 Agent 自己安排。二是任务可以并行,而且相互隔离:每个任务都是一个全新的会话,默认在自己的分支上工作,前后相接的任务也是如此。
任务和依赖构成一张有向无环图(Directed Acyclic Graph,DAG),产品名 CodeDAG 和命令中的 dag 都由此而来。文档里说“工作流”,命令里写 dag,指的是同一个东西。
下面是一个常见的菱形工作流。任务 1 读规格文档,把要实现的模块与接口写成一份契约;任务 2 和任务 3 都依赖任务 1,一个按契约写测试,一个按契约写实现;任务 4 等两边都完成后运行测试,修到全部通过。
flowchart LR t1["任务 1<br/>起草接口契约"] --> t2["任务 2<br/>按契约写测试"] t1 --> t3["任务 3<br/>按契约写实现"] t2 --> t4["任务 4<br/>跑通测试"] t3 --> t4写成工作流文件是这样的:
tasks:
- id: 1
title: 起草接口契约
prompt: 读 docs/spec.md,把要实现的模块与接口写成 docs/contract.md。
- id: 2
title: 按契约写测试
dependencies: [1]
prompt: 按 docs/contract.md 为每个接口编写测试。
- id: 3
title: 按契约写实现
dependencies: [1]
prompt: 按 docs/contract.md 实现这些模块。
- id: 4
title: 跑通测试
dependencies: [2, 3]
prompt: 运行全部测试,修复失败项,直到全部通过。这张图里有一条直线画不出的安排:任务 2 和任务 3 只共用任务 1 的契约,彼此无关,只要有空闲的工作区,也就是供任务执行的一份代码检出,就可以同时执行;任务 4 则必须等两边都完成。
谁负责什么
工作流提交之后,CodeDAG 推进流程,任务 Agent 完成每个任务,Git 保存所有结果;按计划需要人参与的只有两处:你设定的把关节点,和最后合入主分支。无人值守能够成立,靠的就是这样清楚的分工:
- 你:决定要做什么,决定哪些节点需要停下来由人把关,最后决定是否把结果合入主分支。
- 编排者:操作
cdg的一方,负责写工作流、提交、跟踪进度和审核。编排者可以就是你;也可以是装了 CodeDAG Guide 插件的 Claude Code 或 Codex,由它听你描述目标、替你操作cdg,这时称为编排 Agent。编排 Agent 做的是编排的事,工作流里的代码仍由任务 Agent 来写,做法见让 Agent 帮你编排。 - CodeDAG:在你的电脑上后台运行,负责推进流程:排队,为每个任务分配工作区和分支,启动任务 Agent 的会话,判断任务何时做完,按规则自动接受结果或停下来等待审核,并把结果逐步合并起来。
- 任务 Agent:在一个任务里执行 prompt 的 Claude Code 或 Codex 会话。每个任务都是一个全新的会话,只知道自己的 prompt 和它所在工作区里的内容,原因见任务与会话。
- Git:在默认的隔离分支模式下,每个任务的改动留在自己的任务分支上,整个工作流的结果汇到一个集成分支;你的主分支保持不动,何时合入由你决定。
在默认的隔离分支模式下,一个工作流提交之后的全过程如下:
sequenceDiagram participant O as 编排者 participant C as CodeDAG participant A as 任务 Agent participant G as Git 仓库 O->>C: 提交工作流 Note over C: 排队,轮到后开始执行 loop 每个依赖已满足的任务 C->>G: 分配工作区与任务分支,合入上游结果 C->>A: 启动全新会话,发送 prompt A->>G: 在任务分支上完成工作 C->>C: 判定任务完成 opt 这个任务<br/>设了人工审核 C-->>O: 停下等待审核 O->>C: 审核任务 end end C->>G: 所有任务审核完成后,合并到集成分支 opt 工作流<br/>设了人工审核 C-->>O: 停下等待审核 O->>C: 审核整个工作流 end Note over C: 工作流离开队列,下一个开始 O->>G: 你检查集成分支,合入主分支图上的每一段都有专门的页面展开:一个任务怎样由全新会话执行,见任务与会话;任务在哪里执行、分支怎样建立和合并,见工作区与分支;做完之后怎样被认可,见审核门禁与自动接受。
把关节点由你决定:默认情况下任务和工作流都会自动接受,只有你关掉了自动接受的地方,流程才会停下等人审核。除此之外,工作流会自己一直执行到结果合并进集成分支;只有遇到 CodeDAG 无法自行处理的情况,它才会提前停下,这时状态会告诉你停在了哪里(见状态与子状态)。这一切有一个前提:任务 Agent 不必逐步征求你的同意,否则它会在自己的会话里停下来等你批准,权限模式怎么选见决定无人值守还是有人值守。
提交、队列与阶段
提交与队列
同一个仓库里,你往往会先后写出好几个工作流,它们共用同一份代码和同一批工作区。让它们同时执行而不加隔离,彼此的文件和分支会互相干扰;为每个工作流再做一套隔离,需要成倍的工作区,还要在工作流之间再合并一次结果;一次只执行一个、又不能排队,就得有人守着,等前一个跑完再手动启动下一个。
CodeDAG 选择一次只执行一个,但允许提前提交:前一个还在执行时,后面的就可以提交进来等着。这些已提交的工作流按提交顺序排成的一列,就是队列:排在最前面的那个在执行,它经过审核、离开队列之后,下一个自动开始。你可以在离开前依次提交几个工作流,让它们接连跑完;并行只发生在一个工作流内部的任务之间。
所以提交并不等于立即执行。cdg dag submit 把工作流放进队列,前面没有未结束的工作流时,它才真正开始执行。
“结束”在这里有明确的含义。所有任务都执行完,只说明 Agent 认为自己做完了;结果能不能用,还要经过审核。审核可以由你来做,也可以按规则自动通过,默认是自动通过,门禁怎么设见审核门禁与自动接受。工作流的审核有了结论,它才离开队列,让下一个开始。这也意味着,如果排在最前面的工作流停在等你处理的地方,比如等待审核、被暂停或被搁置,后面的工作流会一直等着。
阶段
提交和审核这两个动作,把工作流的生命周期分成三个阶段:
planning(计划中):写好了但还没提交。文件可以随意修改,不会执行。submitted(已提交):已经进入队列,正在排队、执行,或者执行完等待审核。执行用的是提交那一刻的文件内容,之后再修改文件,不会改变已经提交的这一次。reviewed(已审核):已经给出最终审核结论,离开了队列,不再执行。
阶段由提交和审核推进,顺序固定。任务也有自己的阶段:任务随工作流一起提交,但每个任务单独审核,所以工作流还处在 submitted 时,其中已经审核过的任务就是 reviewed。
cdg status 显示的项目看板正是按阶段分组的:Planning Workflows 列出还没提交的工作流,Submitted Workflows 就是队列,按先后编号,Reviewed Workflows 是审核过的历史。
状态与子状态
状态
提交之后你多半不在场,回来时需要一眼看出每个任务处在什么情况。阶段回答不了这个问题:它只说明“走到了哪一步”,而等待、执行、暂停、做完都发生在 submitted 这一段里。所以已提交的任务还有一个状态,只用来描述 submitted 这一阶段,表示任务此刻在等待、在执行、被停下还是已经做完:
pending(等待中):没有在执行,在等待开始或等待处理。in-progress(执行中):任务 Agent 正在工作。stopped(已停止):被暂停或中断,停在原处等待恢复。done(已完成):执行完了,结果还要经过审核。
状态有一个常规的顺序,也就是任务的生命周期:先是 pending,等上游任务和空闲的工作区;拿到工作区后由全新会话执行,变为 in-progress;做完变为 done;通过审核后进入 reviewed 阶段。但它不像阶段那样只朝一个方向走:暂停、中断、恢复,或者审核时要求任务继续修改,都会让状态折返或跳转。
子状态
只看状态,还不足以判断要不要动手。同样是 pending,一个任务可能只是在等上游,不用管;也可能被搁置了,不处理就一直停着。所以每个状态之下再分子状态,说明任务处在这个状态的原因:pending 分为 blocked(等待上游)与 on-hold(搁置),前者在等上游任务完成并被接受,或者在等空闲的工作区,时机到了会自己开始(如果上游停在 done 等你审核,它也会一直等下去);后者表示任务被搁置,比如启动前发现工作区里有未提交的改动,或者合入上游结果时出现了没能自动解决的冲突,要你处理之后才能恢复。同理,in-progress 分为正常执行的 working 和收到暂停后正在收尾的 draining(排空),stopped 分为被暂停的 paused 和被中断的 killed。全部组合及含义见状态含义。
回来查看时,blocked、working 和 draining 都不需要你介入;on-hold 和 stopped 要等你处理后恢复,遇到具体情况时按症状查故障排除;done 的任务如果设了人工审核,会一直等你给出结论,见自动接受。
cdg status、cdg dag status 和 cdg task status 分别从项目、工作流和任务三个层级显示阶段、状态与子状态。看板上成对出现的 [in-progress] [working],前一个是状态,后一个是子状态。工作流也有自己的状态与子状态,概括它所有任务的整体情况,cdg status 的队列里每一行显示的就是它。一个任务为什么处于 on-hold,具体原因只在 cdg task status 里显示。
项目
同一台电脑上往往有好几个仓库在用 CodeDAG。它们的代码和分支彼此无关,没有理由互相等待:你在一个仓库里提交的工作流,不该排在另一个仓库的工作流后面。而同一个仓库为了让任务并行,往往又不止一份检出:除了原来的仓库,还有你用 git worktree 或 git clone 额外建出来的几份,它们应当和原来的仓库算在一起。
CodeDAG 就以这样一组目录为单位。原来的那个仓库称为主仓库,.codedag/ 目录建在它的根目录下;额外建出来、并登记在它名下的那几份称为副本。主仓库连同它的副本,合称一个项目。
队列和几乎所有 cdg 命令都以项目为边界:不同项目的工作流各排各的队,互不影响;在主仓库、任一副本或 .codedag/ 目录里运行这些命令,操作的都是同一个项目和同一个队列,在还没有初始化的仓库里运行,则会提示先执行 cdg init。执行任务时,主仓库和每个副本各是一个工作区。
一个仓库在运行 cdg init 之后成为项目,副本也在这时登记。副本要由你事先用 Git 创建,CodeDAG 不替你创建;一个项目的副本要么都是 worktree,要么都是 clone。怎样选、怎样准备和登记,见并行执行与工作区。
工作流文件放在 .codedag/dags/ 下,项目设置和运行日志也在 .codedag/ 里。这个目录不随代码提交进项目仓库,原因见边车仓库。
一个工作流只作用于一个项目。一件事涉及几个仓库时,就在每个仓库里分别写工作流、分别运行,仓库之间的衔接由你来安排。
边车仓库
项目的工作流文件和设置都放在主仓库根目录下的 .codedag/ 目录里。它就在仓库里面,看上去应该和代码一起提交进项目仓库。但工作流运行时,CodeDAG 会在你的仓库里切换分支、提交任务的改动:这个目录如果归项目仓库管理,仓库切到另一个分支时,里面的工作流文件会跟着变,任务的提交里也会混进它们。让项目仓库直接忽略这个目录,文件不再受切换分支的影响,却也没有了版本记录,更没法与团队共享。
所以 CodeDAG 让项目仓库忽略 .codedag/,同时把它建成一个独立的 Git 仓库,两个仓库各有各的提交历史。这个独立的仓库,就是项目的边车仓库(sidecar repository)。cdg init 的输出把它写作 sidecar repository,文档里也叫它 .codedag 仓库。
项目仓库是通过 .git/info/exclude 忽略它的,所以你不需要把 .codedag/ 提交进项目仓库,它也不会出现在项目仓库的 git status 里。边车仓库由你自己管理,CodeDAG 不会在里面提交、推送或拉取:想保存工作流的历史,进入 .codedag/ 用普通的 git 命令提交,做法见单独保存工作流与共享配置;想与团队共享,再把它推到一个远程仓库,见团队共享。.codedag/local/ 里的本机设置和日志只属于这台电脑,不进入版本管理。
一个项目只有一个边车仓库:副本里没有 .codedag/,在哪个工作区运行 cdg,用到的都是主仓库里的这一份。
最后更新