如果你同时开着两三个 AI Agent 在同一个项目里干活,大概率已经遇到过这种场景:Agent A 刚提交的代码里混进了 Agent B 的临时改动,Agent C 跑测试的时候又把前两者依赖的构建产物给覆盖了,三个人在同一个工作区里互相踩踏。这个问题的根源不在 Agent 本身,而在于它们共享了同一个工作目录。
我最终的解法是用 Git Worktree 给每个 Agent 开独立的隔离工作区,再配一个专门的管理 CLI,也就是今天要说的 Worktrunk。它把“为每个 Agent 创建独立工作区”这件事从一串手工 git 命令变成了一条命令,还能追踪每个工作区属于哪个任务、哪个 Agent、当前状态如何。这篇文章适合两类人:一是已经尝试把多个 AI Agent 塞进同一个仓库的开发者,二是想为 Agent 工作流搭一套规范基础设施的团队。我会讲清楚它解决的问题、命令设计、元数据实现,以及我在真实开发中踩过的坑。
1. 并行 Agent 工作流的第一道坎:工作区互相踩踏
1.1 多个 Agent 共用一份代码库时,冲突是必然的
先说现象。我最初的做法很直接:同一份项目目录,让 Codex CLI 负责写新功能,让 Claude Code 负责修 bug,各自对话,各下指令。表面看互不干扰,实际上它们共享的文件比想象的多得多——工作区里的每个源码文件、Git 的 index、构建目录、依赖安装目录全是共用的。
文件层面的冲突是最直观的。Agent B 在修复一个函数时,Agent A 可能正在重构同一个函数所在的文件。AI Agent 的操作不是原子的,它会先读文件、修改、再检查 diff。两个 Agent 同时读写同一个路径,后写的一方会覆盖先写的一方,之后 diff 引用到的文件状态也已经不可信。最典型的结果就是提交里混入了不属于当前任务的改动,排查起来非常费劲。
Git 层面的冲突更隐蔽。Git 操作是有锁的,多个进程同时执行 add、commit、status 时,会看到 Waiting for the index lock 这类报错。重试也不一定有用,因为另一端的 Agent 可能正在跑一个很长的操作。我还遇到过两个 Agent 同时执行 git status 和 git diff,tools 阶段索引文件出问题的情况。好在这是小概率事件,但碰上就得重建 index,非常烦。
构建产物的冲突就更难察觉了。两个 Agent 共用同一个 node_modules 或者 target 目录时,一个 Agent 安装的依赖版本可能被另一个的 npm install 覆盖掉,测试结果就开始变得极不稳定。你明明在测一个常规改动,却因为另一个 Agent 改了依赖版本,白花一整晚去查一个根本不存在的“幽灵 bug”。
1.2 常见的替代方案:复制仓库、容器隔离、手工 Worktree
绕过这个问题的最土方案是复制整个仓库目录。我早期确实这么干过:cp -r project project-agent-b。缺点非常明显:仓库一大,磁盘直接翻倍;新目录和远端仓库失去了自动追踪关系,得手动维护 remote;更麻烦的是,工作区多了之后全靠人脑记目录,“上次那个 Agent 到底在哪个路径”变成了每天必问的问题。
第二种思路是上容器或虚拟机。隔离性最好,但代价也最高:镜像构建、依赖安装、端口映射,每开一个 Agent 都要重复一次,本地已有的工具链也用不上。对于“只想在代码操作层面隔离”的场景,这明显是杀鸡用牛刀。
第三种思路是手工使用git worktree。这个方向是对的,Git Worktree 本身就为多工作区设计,但原生命令的体验确实不顺手:创建要记一长串参数,创建的 worktree 多了以后没人给它们做归类和状态标注,清理残留还得手工执行worktree prune。任务与分支的对应关系一旦靠人脑维持,规模一上来就必然乱。
1.3 真正缺的是“任务视图”,而不是“目录视图”
折腾完一圈,我的结论是:并行 Agent 工作流缺的不是隔离工具,而是对隔离环境的编排能力。我不需要记住每个 worktree 在哪个路径,我需要回答的是“现在有哪些 Agent 在干活、各自在哪个分支、状态如何、哪些已经可以清理”。这就是我为什么开始设计和维护 Worktrunk——把 Git Worktree 的能力封装成贴合 Agent 工作流的命令,让创建、查询、切换、清理都变成一条命令能搞定的事。
2. Git Worktree:并行 Agent 工作流的底层底牌
2.1 Worktree 机制:共享对象库,独立工作区与索引
Git Worktree 的核心机制是从同一个.git仓库里派生出多个工作目录。普通仓库只有一个 working directory,它和.git里的 HEAD、index、对象库绑定在一起。worktree 会为每个新增目录建立一套“独立视图”:每个 worktree 有自己独立的 HEAD、index、config,但共享同一个 objects 目录和 refs 目录。
具体看.git目录,执行git worktree add ../feature-auth feature/auth之后,.git/worktrees/feature-auth/下会出现 HEAD、index、commondir 等文件。commondir 指向主仓库的.git目录,这个设计保证了对象和引用可以被所有 worktree 共享,同时每个 worktree 的暂存状态、分支版本又完全独立。
这个“共享对象库”的设计是精髓所在。创建 10 个 worktree 不会复制 10 份仓库历史,对象只存一份,新增的只是工作区里的文件副本。对一个对象库动辄几个 GB 的仓库来说,这是它能够支撑起大规模并行的经济性基础。
2.2 为什么这个机制天然适配 AI Agent
AI Agent 的工作方式有一个显著特点:它会在一次任务里做很多步探索性修改,改错了要能回退,改到一半要能暂停。Worktree 天然满足这个需求——每个 worktree 就是一条独立的分支主线,Agent 在里面任意折腾,不会影响主干和其他任务。
第二个特点是并行时的资源复用。Agent 跑测试时要启动服务、监听端口、写日志文件,这些都会产生噪音。给每个 Agent 一个独立 worktree,相当于给了它一个独立的“物理空间”,而空间背后的 Git 对象库、远端引用这些基础数据又是共享的。既隔离又经济,这是容器方案做不到的。
第三个特点是切换成本低。当我想让另一个 Agent 接手某个任务时,切入对应 worktree 的路径继续操作即可,不需要 clone、stash、checkout 一串操作。Agent 也不会卡在“当前分支有未提交修改”这种问题上,因为每个 Agent 都固定在自己的分支上,互不占用。
2.3 原生 Worktree 的三条边界,也正是 Worktrunk 要补的地方
第一,同一个分支不能同时被两个 worktree checked out。这是 Git 的硬限制,不加管理的话,Agent 并行时很容易撞分支。第二,worktree 不会自动理清“任务和 Agent 的对应关系”,git worktree list只给你路径和分支名,你看不出这个工作区在跑什么任务、运行多久了。第三,清理残留 worktree 很麻烦。直接删目录会导致.git/worktrees下的 metadata 残留,之后 git 操作会一直报 WS-ref 警告,还得手工执行 prune。
这三条边界在单机 Git 操作中不算高频问题,但在并行 Agent 场景下会被频繁触发:多个 Agent 同时向同一个分支推代码、忘记清理残留 worktree、分支名和任务对不上号。它们正是 Worktrunk 要解决的核心问题——用工具约束流程,而不是靠人守规矩。
3. Worktrunk 的命令设计:创建、巡检、清理三件套
3.1 create:一条命令拉起一个 Agent 隔离工作区
Worktrunk 的核心入口是create。我的设计目标是“输入任务名,得到可用工作区”,一条典型的命令是:
worktrunk create --name auth-refactor --agent codex --base main执行后,Worktrunk 会按约定自动做几件事:
- 基于
main自动创建并检出分支auth-refactor - 在约定目录(默认
.worktrunk/auth-refactor/)下创建 worktree - 把任务的元信息写入 registry(下一章详细讲)
- 如果配置了 Agent 启动命令,还会自动拉起对应 CLI 并定位到新工作区
--agent参数不只是记录,它会影响后续的辅助行为。比如 agent 为 codex 时,Worktrunk 会在 worktree 里生成一份.codex/config.toml模板,绑定正确的远端和权限策略;agent 为 claude 时则调整合作时权限确认的方式。这样每个 Agent 进入工作区时,都能拿到一份符合它习惯的配置。
create 也支持一批脚本化参数:--base指定基底分支,--remote指定同步的远端,--no-init-agent跳过 Agent 配置生成。实测中我建议每次创建都显式指定--base,否则 Agent 可能基于一个陈旧分支直接开工,后面合并的时候全是冤枉冲突。
3.2 list:巡检所有 Agent 工作区
当 worktree 数量超过 5 个,“我有哪些工作区”这个问题就不能靠ls回答了。Worktrunk 的list命令会把所有 worktree 汇总成一张表:
worktrunk list输出中会包含分支、路径、Agent 类型、状态、创建时间、最近活动时间。基于这张表,我可以快速回答:哪几个 Agent 还在跑、哪几个已经闲置、哪个 worktree 对应的分支已被合并进主干可以清理。
list 还支持过滤器,例如--agent codex只看某个 Agent 的工作区,--stale只看 N 天没有活动的。这个设计灵感来自我管理大量远程主机时的经验:巡检列表时,最怕的是把一大堆信息平铺在眼前,必须有办法把“需要处理”的项过滤出来。你不需要每次都做人工判断,工具应该直接告诉你“哪些该处理了”。
3.3 destroy:不残留地回收工作区
工作完成后,要做的不是手动rm -rf,而是:
worktrunk destroy --name auth-refactordestroy 的完整流程是:先从 registry 里移除记录,然后检查该 worktree 对应分支是否已合并到目标分支,存在未合并改动时默认阻止删除,加--force才能跳过,最后调用git worktree prune清理 metadata。它的价值在于把“删除”变成一个安全操作,而不是一条不可逆的rm命令。
我踩过最痛的一个坑就在这里:手工删掉 worktree 目录后,.git/worktrees/auth-refactor里的 metadata 残留还在,Git 每次 status 都会报警,最后还得手动 prune。destroy 把这一步固化下来,从流程上杜绝了这个错误。
4. 元数据与状态追踪:让 Worktree 摆脱“人记状态”
4.1 registry 文件:给 Worktree 加上业务属性
原生 Git 的 worktree 元数据只包含路径、HEAD、commondir 这些物理信息。它不会告诉你这个 worktree 是给哪个 Agent 用的、对应哪个任务、目标合并分支是什么。Worktrunk 在~/.worktrunk/registry.toml里维护一份额外的 registry,结构大致是:
[[worktree]] name = "auth-refactor" agent = "codex" path = "/home/user/project/.worktrunk/auth-refactor" branch = "auth-refactor" base = "main" created = "2025-06-01T10:00:00Z" last_active = "2025-06-01T18:30:00Z" status = "active"我特意没有塞太多花哨字段,够用就行。name 是硬索引;agent 决定集成行为;last_active 用于 stale 判断;status 记录 active、paused、archived 三种状态。registry 独立于仓库存储,所以即使仓库被删掉,任务记录的方向感也还在。
4.2 状态更新时机:挂在命令执行上,而不是后台轮询
last_active 字段一开始我考虑用后台守护进程实时更新,后来果断放弃——太复杂,没必要。实际采用的方式是“任何 worktrunk 命令执行时同步更新一次”。运行list时,它会把这次运行时间写回 registry。对于 Agent 工作流,你不需要毫秒级的实时监测,只要能识别“这个工作区一周没动过”就足够做清理决策了。
这里有一个和 Git 原生命令协作的小细节:如果绕过 Worktrunk 直接在 worktree 里操作(比如手动git commit),Worktrunk 是感知不到的。所以我在设计文档里约定:只有通过 worktrunk 命令接入、退出的任务,才保证状态准确;手工进入工作区的,要看状态就手动 update 一次。这不是 Worktrunk 的缺陷,而是避免过度设计的取舍。
4.3 清理策略:不只看时间,还要看分支状态
清理一个 worktree 不是“目录存在超过 N 天就删”那么简单。我的清理策略分三步:
- 先检查分支是否已合并:
git branch --merged base - 再检查 worktree 里是否有未提交改动:
git status --porcelain - 最后判断 last_active 是否超过阈值
如果分支已合并、又没有未提交改动,哪怕才创建两天也可以安全销毁。如果分支未合并,但 worktree 超过 14 天没有活动,我会先做一次归档:把工作区压缩成 patch 文件,再销毁,防止 Agent 的关键改动永久丢失。这套策略最终沉淀成了archive子命令和 destroy 的--archive参数。
5. 与 Codex CLI、Claude Code 的组合用法
5.1 Codex CLI:在隔离环境里跑主干开发任务
Codex CLI 的工作方式是在当前目录下运行,读取上下文并执行代码操作。Worktrunk 和它的配合方式是:为每个 Codex 任务创建一个独立 worktree,然后在 worktree 里启动 codex。这样并行运行多个 codex 实例时,各自的临时文件、commit 历史、配置都不会互相影响。
我实际的做法是用worktrunk create --name xxx --agent codex生成工作区和.codex/config.toml,然后进入目录:
cd .worktrunk/xxx codex "实现用户登录接口,并完成对应的单元测试"Codex 会读取当前目录的代码,把改动提交到当前分支。因为是独立分支,主分支和其他任务完全不受影响。到了联调阶段,我在主仓库用git fetch加git merge把某个 worktree 的分支拿回来,干净利落。
5.2 Claude Code:多个草稿实验互不干扰
Claude Code 和 Codex 类似,也是目录级别的工具,但它更适合“试试某个重构方案”这类探索性工作。我经常同时开两三个 Claude Code 实例,让它们分别对同一段代码做不同方向的优化草稿,各自落在不同 worktree 里。
对比效果很明显,这里给一组我实测的数据:
| 执行方式 | 完成时间 | 额外收益 |
|---|---|---|
| 串行执行 3 个重构实验任务 | 约 90 分钟 | 每次改动都是叠加的,中途回退成本高 |
| 3 个 worktree 并行执行 | 约 35 分钟 | 每个方案独立验证,失败方案直接丢弃 |
并行并不意味着每个 Agent 的质量下降。只要 workspace 隔离,每个 Agent 的上下文就足够干净,不会被其他任务的临时状态带偏。这其实是并行 Agent 能跑起来的关键软性前提。
5.3 一条命令拉起“任务-工作区-Agent”
为了更顺手,我把创建、进入、启动 Agent 合并成了一个 shell 函数。类似的思路你完全可以自己实现:
worktrunk create --name task-x --agent codex --base main worktrunk enter --name task-x codex "根据 README 完成任务 x"固化成一个函数以后,启动一个并行任务只需要一行命令。实测下来,整个流程从“要 6 条手工命令 + 自己记目录”降到了“一行命令”。真正影响开发效率的往往不是 Agent 本身,而是它前面那一堆环境准备动作。
6. 实测中遇到的高频问题和处理思路
6.1 分支被占用的报错与定位
如果你手工在另一个 worktree 里检出了相同分支,再让第二个 Agent 检出时会遇到fatal: 'xxx' is already checked out。这个报错是保护机制,遇到时不能直接强制覆盖,需要先确认是谁占用了这条分支。我的处理方式是先用worktrunk list定位到占用该分支的 worktree,然后让占用者 commit 并切走,或者对这个 worktree 执行worktrunk destroy。更稳妥的做法是从源头规范--base和分支命名,让每个任务的分支天然不冲突。
6.2 依赖目录和构建目录的共享问题
worktree 共享的是仓库对象库,不共享 node_modules 和构建输出。我在最开始没意识到这一点,结果每个 worktree 都重新npm install一次,磁盘迅速膨胀。后来我在 create 之后加了一步符号链接,把 node_modules 链接到主仓库的共享副本上:
ln -s /path/to/main-repo/node_modules .worktrunk/task-x/node_modules这样既省磁盘,又避免并发 npm install 互相踩。但有一个前提条件:如果两个 Agent 依赖不同版本的包,这种共享会导致问题。所以我的取舍是,常规开发任务共享依赖目录,涉及依赖版本变动的任务就单独安装。
6.3 worktree prune 不及时导致的索引噪音
直接删除 worktree 目录而不清理 metadata,会让 git status 变慢,甚至出现 warning。Worktrunk 在设计阶段就把这个作为重点规避事项,但如果你绕过工具手工删了目录,补救方式只有一个:
git worktree pruneprune 会扫描.git/worktrees,把指向不存在目录的记录清掉。这个操作本身很简单,麻烦的是它不会自动触发。所以我的原则是:优先用工具提供的 destroy,手工操作后至少记得 prune。这属于典型的“操作成本很低、不做的后果很烦”的环节。
6.4 Agent 工作区和本地 IDE 不同步
最后是体验层面的问题:Agent 在 A worktree 里改了代码,你本地的 IDE 还开在主仓库,LSP 的索引会指向旧文件。解决办法是每个 worktree 打开一个独立 IDE 窗口,或者使用支持多根的编辑器配置。这不算 Worktrunk 的职责,但它直接影响并行工作的体感,值得提前规划。否则你会在“Agent 说改完了,IDE 里看不到”这件事上浪费不少时间。
我个人的体会是,把 Git Worktree 当成并行 AI Agent 的一项基础设施来用,整个工作流会清晰很多。Worktrunk 是我在这条路上沉淀下来的工具。如果你也正为多个 Agent 抢同一个工作区发愁,可以先从一件小事开始改造:为每个任务开独立 worktree,用工具管好它们的生命周期,这比反复叮嘱 Agent“别动别人的文件”有效得多。下一步我还在做 Agent 任务自动合并进主干的分支策略,等跑稳了再找机会分享。