并发运行多个coding agent时共享git树会导致静默文件覆盖,用worktree隔离是根本解法。
Disclosure:我在构建 Ordewell,所以这篇文章讲的是它具体是怎么做的,而不是对业界方案的全面梳理。它是免费软件,采用 Apache 2.0 协议。无论你是否安装,以下机制原理都适用,因为任何运行多个会话的工具链都会面临同一个问题。
第一个版本是一个目录加一个循环:多个会话在 workspace 根目录运行,prompt 要求 planner 把并行任务分散到不同文件。这是对模型的一种建议,而非一道屏障。
这在两个方向上都会失效。两条任务实际需要同一个文件时,会因为一个臆造的依赖关系被串行化,白白损失了你本应享有的并行能力。而当重叠仍然发生时,它失败得悄无声息:runner B 重写了 runner A 刚刚改过的文件,A 的完成标记仍然出现,A 依据的却是一棵已经不含 A 工作成果的树。还有第二个损失:没有每任务的变更记录,所以无法单独撤销某一项任务而不去拆解其他任务。
当 workspace 是一个 git 仓库时,每个任务在自己的 worktree 中运行,从运行时的集成 tip 创建,而非从你的工作树创建。worktree 位于 .ordewell/worktrees/<run>/<task-id>-<plan-order> 下(已被 git-ignore),每个任务携带自己的分支。
runner 只获得一个 cwd,不多不少。Git 从不进入 runner 适配器,所以 Claude Code、Codex 和 OpenCode 不会对工作隔离产生三种不同的理解。包含多个仓库的 workspace 被视为一个组:每个任务在每个仓库各一个 worktree,都在同一个分支名下。
一个没有 node_modules 的全新 worktree 无法运行测试,所以被忽略的产物被链接进来:依赖文件夹、virtualenv、环境文件、agent 配置目录。.ordewell/ 永不链接,因为会话和技能状态属于主根目录,runner 无法将其破坏。在共享会出错的地方,配置好的 setup 命令会替代链接。
有两个细节会咬人。链接按任务记录,因为像 node_modules/ 这样的忽略规则不会匹配符号链接,不记录的话它们就会被提交到任务的分支中。另外,整个文件夹链接对 workspaces 是不对的:npm 和 pnpm 把一个 workspace 包安装为 node_modules 内的一个链接,透过文件夹链接会从主 checkout 解析,导致任务更改一个包时却对着那里的过期副本测试其依赖项。条目是一个一个地镜像的,workspace 链接被重新创建为在 worktree 内部解析。
隔离让重叠安全,但不消除重叠。裁决可能同时到达,所以落地是一个队列:一次一个任务,在等待的任务中 plan order 最低的最先落地。
每个任务作为该运行集成分支上的一个 merge commit 落地,而不是作为松散文件覆盖到你的代码上。merge commit 保留了 runner 自身的任何提交,在 git log 中显示谁做了什么,并使任务分支成为祖先,这使得删除它是安全的。
没有落地的任务并非无可救药。merge 被中止但两个 ref 都保留着,所以工作成果仍在那里可以检查、重试或交给修复尝试。在多个仓库间落地是原子的:如果一个仓库冲突,该任务在其他仓库已做的 merge 会被回滚,所以任务作为整体冲突,而不是落地了一半。
解决冲突意味着决定两个变更中哪个胜出。将其交给模型会悄无声息地重写一个无人审查的 merge,让结果取决于模型的判断——这正是基于证据的裁决存在所要避免的。
所以冲突会连同涉及冲突的文件一起被暴露出来,解决它是可选的。有界限的修复尝试可以作为冲突任务在其自身 worktree 中的新尝试来运行,它仍然通过同一个队列和同样的检查来落地:它的分支必须包含修复起始的 tip,且不能留下任何冲突标记。如果修复并没有真正带动分支,它会再次冲突,而不是被轻信。
Ordewell 从不单独 merge 到你当前检出的分支。那是唯一不可逆的步骤,而你的树可能持有正在推进中的工作。一次运行以集成分支暂停等待审查、其来源的 base ref 和一份落地清单结束。
按请求 merge 会先对每个仓库进行预检:没有正在进行的 merge、与检出内容没有冲突、没有对 merge 会改动的文件的未提交编辑。如果任何仓库预检失败,任何地方都不会 merge。Discarding 会删除 worktree 和任务分支,绝不会删除你自己尚未 merge 的已落地工作。
# a dirty tracked tree in any repo holds the whole run
ordewell run --stash
ordewell run --without-isolation
# end of run: integration branches parked, one per repo
ordewell handoff review
ordewell handoff merge
ordewell handoff discard
worktree 需要一个 git 仓库。缺少 git 可执行文件、没有仓库的文件夹、或没有提交历史的仓库会退回 workspace 根目录并附带提示。容器和临时文件夹是这种情况的典型场景。
有脏跟踪树的仓库会阻塞隔离。两种解决办法是 stash 这些变更,或者在无隔离状态下运行那一次运行。未跟踪文件从不会阻塞:引导过程已将其纳入考虑。
队列是被串行化的。落地一次一个任务,所以你获得的吞吐量受限于 planner 发出的依赖图。隔离让同文件重叠安全,但不能预测它。
共享依赖文件夹仍然是共享的。两个 worktree 同时安装会竞争,因为安装过程是共享的。setup 命令是逃生舱。
在 Windows 上某些共享文件是副本。跨卷无法实现的硬链接会变成副本,对它的编辑停留在那个任务的 worktree 内部。
任何可能是你的东西都不会被清理。持有未落地工作的 worktree 被保留,因为它可能属于另一个主机仍在驱动的 runner。这是一个需要你手动删除的目录。
上述决策已在仓库中记录在案,而非从记忆中概括而来:ADR 0013,worktree 隔离;ADR 0014,多仓库 workspaces;ADR 0015,冲突修复;隔离接口;以及 github.com/ordewell/ordewell。