多AI Agent并行开发冲突?用Git Worktree实现工作区隔离
我们经常在同一个仓库里开多个终端同时跑两三个 AI 编程助手一个在修登录模块的 bug一个在写新报表接口还有一个在补测试。单看每个会话都很顺利但一旦它们都在同一个目录里跑git add .混乱就开始了。我自己的 Codex CLI 会话经常出现这种情况Agent A 刚把 auth.ts 改了Agent B 还在拿旧的实现做上下文git status里全是互相覆盖的文件。这个问题的根源不在 AI 工具本身而在开发流程里缺少一层工作区隔离。我后来基于 Git Worktree 做了一个叫 Worktrunk 的 CLI专门用来管理并行 AI Agent 的工作流。这篇文章就是讲这个问题、这个工具的设计思路以及实际跑下来后踩过的坑。1. 多 Agent 并行开发的第一课分支救不了工作区冲突1.1 什么是并行 AI Agent 工作流先说清楚场景。现在主流 AI 编程助手都支持本地 CLI 模式比如 Codex CLI、Claude Code、类似 Gemini CLI 的交互式终端。它们本质上是你在项目目录里启动一个进程AI 通过读文件、改文件、跑命令来完成你交给它的任务。当任务变多、或者任务拆得比较细时很自然的做法是同时开多个会话一个会话帮你修 bug另一个会话帮你加功能。这就是并行 AI Agent 工作流。它听起来很高效但实际操作中有一个被很多人忽略的问题所有 Agent 默认共享同一个工作目录。AI 不是人它不会跟你打招呼说我要改 src/utils.ts 了你那边注意点。它要求的是这个目录里的文件是它预期的状态而你这边同时开着另一个 Agent 也在改同样的文件两边都会读到一个不对的上下文。1.2 分支隔离的是历史不是工作区很多人第一反应是那我给每个 Agent 建一个 Git 分支不就行了比如 Agent A 在fix-login分支上Agent B 在feature/report-export分支上互不干扰。这个想法对了一半。分支在 Git 里本质上是指向提交的可变指针切分支这个动作会把工作目录的文件恢复到那个分支指向的版本。这里的关键点在于一个仓库在一个时间点只有一个工作目录。你可以在fix-login分支上让 Agent A 改得正欢但你没办法同时在同一个目录里让 Agent B 在feature/report-export分支上改另一批文件——除非你先 stash 或 commit把 A 的改动暂时挪走切到 B 的分支。这样来回切两边的未提交改动都会互相污染。更麻烦的是很多 AI 编程工具的上下文管理是基于当前目录的当前状态的。你切一次分支它的索引就乱了它可能还在按照旧文件的路径和内容去写补丁。1.3 我在实践中看到的典型冲突表现在一台开发机上同时跑多个 AI Agent且共用一个工作区时冲突表现通常有以下几类git status 失控两个 Agent 各自创建了新文件、修改了旧文件git status里混在一起你根本分不清哪个改动是谁做的。文件互相覆盖Agent A 和 Agent B 恰好同时改同一个文件后写入的一方直接覆盖前一方。如果其中一个 Agent 在执行git checkout -- .或类似命令另一个 Agent 的所有本地改动瞬间消失。测试互相踩踏Agent A 在跑测试Agent B 改了被测试依赖的源码文件测试结果变得完全不可信。依赖和构建产物混乱Agent A 添加了一个 npm 包触发了package-lock.json更新Agent B 在同一时间安装另一个包锁定文件冲突两边都装不进去。这些都不是 AI 工具的 bug而是并行写一份代码时的结构性问题。解决它的关键不是提升 Agent 的社交能力而是从根上给每个 Agent 一个物理隔离的工作目录。2. Git Worktree 解决了根问题但离Agent 友好还差一步2.1 一条命令把仓库分身成多个Git 在 2.5 版本引入了 worktree 机制解决的就是一个仓库只能有一个工作区的限制。用一句话解释git worktree 允许你在同一台机器上、同一个 .git 存储里同时签出多个分支到多个目录。比如我的 demo 仓库在~/projects/demo我可以执行git worktree add ../demo-fix-login fix-login它就会在~/projects/demo-fix-login目录下创建一个完整可编辑的工作区这个工作区自动关联到一个新的或已存在的fix-login分支。两个目录里的文件互不影响但共享同一套 Git 对象数据库所以提交记录、对象信息都是打通的。这段设计在 Git 内部很有意思。主仓库的.git目录保持不变而每个 worktree 会在~/projects/demo/.git/worktrees/fix-login下创建一个子目录里面有这个 worktree 专属的HEAD、index、MERGE_HEAD等状态文件。worktree 里的.git不是一个目录而是一个普通文本文件内容是指向主仓库对应子目录的路径。理解这一点之后很多worktree 里为什么不能切到已被其他 worktree 检出的分支这类问题就很容易解释了。2.2 为什么不建议用 git clone 替代也有人会想我直接git clone两份仓库不也一样隔离吗隔离是隔离了但代价是对象库完全独立。你在 A 目录里提交一个 commit在 B 目录里要先 fetch 到本地 remote 才能看得到。而且管理多个 clone 之间的 remote 仓库地址、分支同步本身又是一套复杂状态。用 git worktree 的话所有 worktree 共享 .git 对象库任何一个 worktree 里执行git commit其他 worktree 通过git log或git merge-base都能直接看到不需要额外 fetch。这非常契合多 Agent 协作的场景Agent A 在 A 区提交了代码Agent B 在 B 区可以直接读到这个新 commit以此作为自己的工作基础。2.3 已有命令在 Agent 场景下的短板Git 自带的git worktree命令解决的问题是能这样做但离适合 AI Agent 管理还差几层。首先是语义化。git worktree add ../some-path -b some-branch这条命令里分支名、路径、用途之间没有任何结构性关联。我拿到一个 worktree 列表时只能看到路径和分支名很难判断它是哪个任务、对应哪个 Agent。其次是状态追踪。一个 Agent 可能已经跑完了但 worktree 还留在那里或者 worktree 里的分支还没合并但我已经忘了它属于哪个任务。git worktree list只告诉你有哪些 worktree不会告诉你这个 worktree 是什么状态、是否可以清理。第三是对 Agent 进程不友好。AI 编程工具在命令行里的工作方式需要明确的目录参数。每次创建 worktree 后我还要记住路径、切换目录、检查分支状态这些步骤一旦多了很容易出错。这些短板就是我决定写 Worktrunk 的动机不能说 Git Worktree 不好用而是我需要一个更贴近AI Agent 并行工作流语义的封装。3. Worktrunk 的核心设计把 Worktree 变成带语义的沙箱3.1 设计目标我给 Worktrunk 定的目标很清楚用最少的命令把为每个 Agent 准备一个隔离工作区、追踪它的状态、最后回收这件事做完。它不试图替代 Git Worktree而是在 Git Worktree 之上加一层面向 Agent 工作流的语义管理。trunk init为当前仓库初始化 Worktrunk 配置生成.worktrunk.yamltrunk create name为某个任务/Agent 创建一个独立 worktree默认基于当前主干的最新提交trunk list列出所有 worktree 的路径、分支、状态、最近提交时间trunk sync name把主干上的最新变更同步到指定 worktree内部是 rebasetrunk done name合并该 worktree 的分支回主干并自动清理 worktreetrunk drop name放弃该 worktree 的所有改动并清理这套命令设计的核心思路是把创建分支、添加 worktree、记住路径、合并、删除、清理这些 Git 操作打包成一个有状态、可查询的统一流程。Agent 或人只需要关心任务名不需要关心底层分支和路径。3.2 元数据与存储Worktrunk 在.git/worktrunk/registry.json中维护每个 worktree 的元数据大致结构是这样的{ workspaces: [ { name: fix-login, branch: wt/fix-login, path: ../demo-fix-login, base: main, status: active, agent: codex, created_at: 2025-06-10T10:30:0008:00 } ] }路径统一采用相对主仓库的目录布局这样整个项目目录可以整体迁移注册表里的路径不会失效。branch名称统一用wt/name前缀额外的好处是一眼就能看出哪些分支是 Worktrunk 创建的清理时可以按前缀过滤。3.3 为什么选了 Go 而不是 Shell 脚本最初我也想过直接写一套 bash/zsh 脚本但很快就放弃了。原因是状态管理。写一个脚本创建 worktree 很简单但要跨命令维护哪些 worktree 存在、各是什么状态Shell 脚本需要反复解析git worktree list --porcelain的输出还要小心处理各类边界条件比如路径包含空格、分支名非法、worktree 里有未提交改动。Go 的好处是能输出单文件二进制、跨平台而且标准库对 JSON、文件、进程管理支持非常完善。我可以用一个Workspace结构体表示所有状态命令之间的数据交互变得非常可靠。另外因为 AI Agent 的 CLI 环境多种多样Go 编译出来的工具不需要依赖系统里的 Python 或者 Node 环境丢到哪个机器上都能跑。对我有意让它保持零依赖。3.4 与 Agent 工具的衔接方式Worktrunk 本身不关心你用的是 Codex CLI、Claude Code 还是其他 Agent 工具。它只保证一件事每个 Agent 拿到的目录是一个干净、隔离、基于同一起点的完整仓库。用法上我会在给 Agent 的 prompt 里直接写清楚cd /path/to/demo-fix-login # 当前工作区处于 wt/fix-login 分支基于 main你可以任意修改和提交在多层并行时还可以让trunk list --json的输出作为某个编排 Agent 的上下文让它知道现在有哪些 worktree、哪个分支落后于主干。4. 完整实战让两个 Agent 互不干扰地改同一个仓库4.1 场景设定为了验证 Worktrunk 是不是真的能解决问题我在一个真实的个人项目上做了实验。项目是一个 Next.js 应用包含登录模块和一个报表页面大概 40 多个文件。我给它安排了两个任务Agent A修复登录模块在 token 过期时的无限重定向问题Agent B给报表页面增加 CSV 导出功能两个任务都会碰前端逻辑而且 B 很可能会动到 A 改过的 session 管理代码——这正是最容易发生工作区冲突的场景。4.2 初始化进入主仓库目录后执行trunk init它会检查当前仓库状态确认 main 分支是本地的默认分支然后生成.worktrunk.yaml和.git/worktrunk/registry.json。4.3 分别创建两个 Agent 的工作区trunk create fix-login --agent codex trunk create export-report --agent codex执行完之后主仓库目录旁边出现了两个新目录../demo-fix-login和../demo-export-report。两个目录都是独立可行的 Next.js 项目各自处于wt/fix-login和wt/export-report分支。此时trunk list的输出大致如下Name Branch Path Status fix-login wt/fix-login ../demo-fix-login active export-report wt/export-report ../demo-export-report active4.4 Agent A 在 fix-login 工作区工作我进入../demo-fix-login启动 Codex CLI告诉它目录上下文和任务目标。它开始读代码、定位 token 过期处理逻辑修改lib/session.ts运行测试一切正常。这个过程里我特意在另一个终端反复观察主仓库的git status——主仓库始终保持干净。Agent A 在 worktree 里的所有修改、提交都没有污染主仓库。4.5 Agent B 在 export-report 工作区工作同时我在../demo-export-report启动第二个 Codex CLI让它实现 CSV 导出。它需要读取页面组件、调用后端接口、生成 CSV。因为工作区独立它不需要担心当前目录里还有 Agent A 改到一半的 session 代码也不怕自己的git add .会把别人的文件卷进来。这里有一个细节值得留意两个 worktree 的 base 都是同一个 main commit所以它们的初始文件快照完全一致。这直接避免了Agent B 看到的是 Agent A 改动前还是改动后的文件这种歧义。4.6 合并与清理两个 Agent 都完成且自测通过后我在主仓库执行了合并trunk done fix-login trunk done export-reporttrunk done内部做的事大致是检查 worktree 工作区是否干净 → 切到 main → 把wt/fix-login合并进 main → 删除 worktree 和临时分支。第二次执行trunk done export-report的时候因为它的分支 base 已经落后于新的 mainmain 里已经包含 fix-login 的改动Worktrunk 会先尝试 rebase。在这次实验里export-report 改动到的文件和 fix-login 改动到的文件没有重叠所以 rebase 自动成功合并干净完成。实测下来的结果是整个并行过程里两个 Agent 几乎没有感受到对方的存在。彼此的工作区完全独立最终合并也只处理了一次很轻量的 rebase。和以前两个 Agent 挤在一个目录里互相覆盖相比体验好了一个数量级。5. 类型化案例把 Agent 关进 Worktree 后踩过的五类坑5.1 Agent 工作一段时间后会忘记自己当前目录你以为 Agent 在 worktree 里运行就很安全但实际操作中很多 Agent 在尝试定位项目根目录时会向上查找package.json或.git。Worktree 里的.git是一个文件而不是目录有些工具不认识这种布局会继续往上一层找最后定位到主仓库的.git目录造成混乱。我的应对方法是在 prompt 里明确指定绝对路径并在工作区的根目录放一个AGENT.md或CLAUDE.md说明这个目录是独立的 Git Worktree所有操作请在当前目录内完成。5.2 worktree 里不能随意切换分支常规开发中你习惯了在一个目录里切分支。但在 worktree 里如果某个分支正被另一个 worktree 占用Git 会直接拒绝切换。这是一个保护机制但在多 Agent 场景下会带来问题Agent 可能会想我切到 main 看看最新代码——如果 main 正在主仓库被检出它就会报错。所以 Worktrunk 的工作流里我强制每个 worktree 始终留在它自己的wt/name分支上同步主分支统一用trunk sync name做 rebase而不是切换到 main。这从流程上避免了某个分支被另一个 worktree 锁定的困惑。5.3 子模块需要额外的初始化步骤如果你的主仓库包含 Git 子模块submodulegit worktree add出来时子模块的目录往往是空的需要在新 worktree 里手动执行git submodule update --init --recursive。AI Agent 如果没有意识到这一点会直接把空目录当成子模块被删了然后做出各种错误判断。这点在文档里写清楚也行但我更倾向于把它做进工具。Worktrunk 在trunk create后检测到主仓库有子模块时会自动在新的 worktree 中执行子模块初始化。没有这一步并行 Agent 在涉及子模块的项目里几乎没法用。5.4 磁盘空间会被悄悄吃满共享对象库意味着 Git 对象不会因为多个 worktree 而重复存储但每个 worktree 的工作区文件是完整独立的。一个有 500MB 依赖的 monorepo开 5 个 worktree 就是 2.5GB。对前端项目这种依赖很重的场景磁盘膨胀速度远超预期。我的建议是给每个工作区设置用途上限跑了几个实验性的改动就让trunk done或trunk drop及时回收。Worktrunk 的trunk list里会显示每个 worktree 的创建时间和最后提交时间方便一眼看出哪些是僵尸工作区。5.5 与 IDE 索引器抢 CPU如果你在 IDE 里同时打开主仓库和多个 worktree比如 VS Code 同时开了三个窗口每个都在跑 TS 语言服务器和文件 watcherCPU 和内存都会翻倍。这个问题不是 Git 或 Worktrunk 直接造成的但在并行 Agent 工作流里尤其突出——因为 Agent 本身也要读取文件、分析代码。实践上的折中方案是不要同时打开所有 worktree 的 IDE 窗口让 Agent 作为主力消费方使用 worktree人只在需要 code review 或手动修改时打开特定 worktree。这样可以减少索引器冲突也避免编辑器里的git集成干扰多个 worktree 的分支状态。6. 边看场景边取舍Worktrunk 不适合谁6.1 单 Agent 的串行开发如果你只是一个人用一个 Agent 改一个需求从头到尾都住一个目录里完全不需要 Worktrunk。多 worktree 在单一工作流里反而是负担创建、切换、同步的每一步都比直接改多一层心智成本。好处只有在并行时才能体现出来。6.2 需要高频共享上下文的场景并行 Agent 有时候需要共享大量未提交的中间状态。比如 Agent A 在手工调试一个数据结构Agent B 需要立刻基于 A 的临时实现继续开发。worktree 的提交边界会阻碍这种未完成即共享的协作。如果 Agent 之间的任务依赖非常强更适合的方案是让它们串行或者用单一工作区 明确定义的上下文传递。6.3 存储带宽受限的远程开发环境在一些远程开发容器里磁盘配额很紧张。多个 worktree 的重复文件可能直接用光配额导致 Agent 写文件失败。这种环境下优先考虑容量可控的同一个工作区 严格遵守分支纪律模式或者把 worktree 放在数据盘而非系统盘。Worktrunk 解决的是并行 Agent 工作流里最基础、也最痛的工作区互踩问题。它真正提供的价值是把隔离变成一种默认机制让 Agent 不需要通过感知对方来避免冲突。如果你已经体验过多 Agent 并行开发的那种混乱或者正准备尝试引入多个 AI 编程助手从 Worktrunk 这类 worktree 管理工具开始会比在 prompt 里反复叮嘱你们要小心别覆盖彼此的代码可靠得多。