说实话,Codex CLI、Claude Code 这类 AI 编程工具出来之后,我一度觉得一个人就能撑起一个小团队的工作量。但真正把多个 AI Agent 放到同一个仓库里并行干活时,问题就来了:它们互相覆盖文件、抢工作目录、提交历史乱成一锅粥。后来我把 Git Worktree 捡起来,又写了 Worktrunk 这个 CLI 来管理这些 worktree,才算真正把“并行 AI Agent 工作流”这条路走通。
这篇内容记录的就是 Worktrunk 从动机到设计、再到日常使用踩坑的全过程。如果你也是重度 AI 编程用户,或者正琢磨怎么让多个 Agent 在同一仓库里安全协作,这篇应该能帮你省不少事。我会从最核心的痛点讲起,然后拆解工具的功能设计,最后给出完整可复现的实操流程。
1. 并行 AI Agent 工作流的痛点与 Worktrunk 的由来
1.1 同一个仓库,多个 Agent,冲突是必然的
先说清楚我遇到的真实场景。以前用 AI 编程,基本是单线程:一个 Claude Code 或 Codex CLI 会话挂在终端里,我给它一个任务,它噼里啪啦改完,我 review 一下,提交,完事。这是最舒服的状态,也是大多数 AI 编程工具的默认适用场景。
但到了项目中期,需求多起来之后,单线程就扛不住了。一个 Agent 在改登录模块,我就只能干等着。能不能同时开三个 Agent,一个改前端样式,一个修后端接口,一个写测试?理论上可以,实测下来全是坑。
最大的坑是工作目录的冲突。Git 分支只能帮你隔离提交历史,隔离不了磁盘上的物理文件。两个 Agent 如果 checkout 到同一个目录,哪怕在不同分支,它们读取和写入的都是同一份工作区文件。运行到一半,Agent A 刚把auth.ts改成新版本保存,Agent B 基于旧版本重新生成了同样的文件,直接覆盖掉。等你回头一测,功能莫名其妙就坏了,还说不清是谁改的。
更麻烦的是依赖目录。前端工程里那个node_modules,如果两个 Agent 同时跑npm install,一个在装 A 包的 v2,另一个在装 A 包的 v1,最后装完的结果取决于谁后落地,整个环境直接处于不可复现的状态。这种问题排查起来极度痛苦,因为每次出错的现象都可能不一样。
1.2 为什么是 Git Worktree 而不是多目录克隆
遇到冲突之后,我的第一反应是:干脆 clone 多个仓库副本,一个 Agent 一份,各干各的。这个思路能解决目录冲突,但引入了新的麻烦。
首先,磁盘占用直接翻好几倍。一个稍微大点的前端仓库,带着node_modules和构建缓存,动辄好几个 GB,clone 三份就是三倍的磁盘。其次,版本同步是个大坑。主仓库更新了,各个副本要手动pull,漏掉一次,Agent 就在陈旧代码上开发,最后合并的时候冲突多到你不想看。
Git Worktree 恰好同时解决这两个问题。它是 Git 2.5 引入的官方功能,允许同一个仓库(同一份.git数据)派生多个工作目录,每个目录可以 checkout 到不同的分支。
关键点在于:所有 worktree 共享同一份对象数据库和引用(refs),但拥有各自独立的工作区和索引。这意味着:
- 分支切换互不影响,每个 worktree 可以自由 checkout 自己的分支。
- 代码是同一份底子演进出来的,不需要手动同步,
fetch一次全部可见。 - 软链接和对象复用让磁盘占用远远小于多份 clone。
- 物理隔离了工作目录,Agent 之间不会互相踩到工作区文件。
所以,Git Worktree 几乎是为“多 Agent 并行改同一个仓库”量身定做的方案。它既保留了分支开发的好处,又增加了物理空间的隔离。
1.3 Worktrunk 的设计定位:面向 Agent 的 worktree 管理器
有了 Git Worktree 还不够。说实话,Git Worktree 的原生命令用起来有点繁琐。手动场景下用一两次没问题,但一旦你有七八个并行任务,每个任务对应一个 worktree,管理起来就头大了:这个 worktree 对应哪个分支?那个分支合并没有?哪些 worktree 可以清理了?
如果这些 worktree 还是给 AI Agent 用的,那还得考虑另外一层:Agent 需要用哪个目录来跑?任务结束之后怎么把产物收回来?怎么防止 Agent 把 worktree 搞乱?
Worktrunk 就是冲着这些具体问题来的。它的核心定位不是重复造 git 的轮子,而是在 git worktree 之上做一层“任务态管理”。你可以把它理解为:用一条命令创建“任务”,自动生成对应的分支和 worktree 目录;用一条命令查看所有任务的进度和分支状态;任务合并完成后,再用一条命令安全清理。
听起来很直白对吧?但实际操作中,这里的很多细节做得不好就会很别扭。我在下面章节慢慢拆。
2. Worktrunk 核心功能拆解
2.1 一个命令,五秒建好隔离环境
Worktrunk 的第一个核心功能,是把“从任务到隔离环境”这个链路压缩成一条命令。假设你现在接到一个需求:“优化用户列表页的加载速度”。在没有 Worktrunk 的情况下,你得先手动想一个分支名,比如feature/user-list-optimize,然后执行:
git branch feature/user-list-optimize git worktree add ../user-list-optimize feature/user-list-optimize如果还要让某个 AI Agent 进去干活,你还得手动 cd 到那个目录,然后在里面启动 Agent 会话。每个 Agent 还要确保它不会跑回主工作目录去。这些事情零零碎碎,一旦任务多起来,全是心智负担。
用 Worktrunk 的话,大致是这样:
wtrk new "优化用户列表页加载速度"这条命令内部会帮你做几件事:
- 从当前主分支拉一个最新代码(fetch + reset),确保新分支不是基于过期代码创建的。
- 根据任务名生成一个可读的分支名,比如
wtrk/user-list-load-optimize。 - 在同一仓库下创建一个新的 worktree 目录,路径默认是
../仓库名 + 分支短名,或者按你的配置来。 - 输出新 worktree 的完整路径,以及可以直接粘贴给 AI Agent 的启动命令。
这个过程中最容易被忽略的,是“基于最新代码”这一点。我踩过坑:有时候主分支已经被其他 Agent 合入过新功能了,如果直接基于旧 commit 建分支,Agent 相当于在过期代码上开发,后续合并极易冲突。所以 Worktrunk 在创建新 worktree 前强制执行一次git fetch origin和git reset --hard origin/main(具体主分支名可配置),保证每个 Agent 都从同一起点出发。
2.2 全量可视:一眼看清所有并行任务
任务多了以后,最苦恼的不是创建,而是“全局视野”。你有没有遇到过这种情况:分支建了七八个,分布在两三个 worktree 里,有的已经合并了,有的改了一半忘了,有的连名字都记不清是干嘛的。
我开发 Worktrunk 时最在意的一个功能,就是wtrk list(或者wtrk status),它会像git status一样给人一个即时反馈:
ID 分支名 状态 更新时间 目录 wtrk01 wtrk/user-list-load-optimize in-progress 2分钟前 ../user-list-load-optimize wtrk02 wtrk/payment-refund-fix in-progress 15分钟前 ../payment-refund-fix wtrk03 wtrk/readme-cleanup merged 1天前 ../readme-cleanup wtrk04 wtrk/experiment-rate-limit abandoned 3天前 ../experiment-rate-limit这个列表看起来简单,但要做到准确,背后得解决几个问题。
一是分支与任务的对应关系。Worktrunk 会在创建时把任务 ID 和分支名绑定起来,存到仓库的 git config 里。这样即使 worktree 目录被重命名,分支和任务的关系依然可追溯。
二是状态的自动推断。一个 worktree 分支是否已经合并到主分支,可以通过git branch --merged来判断。Agent 开发的中间状态,则通过该分支是否领先于主分支来判断。如果是空提交、未修改状态,Worktrunk 会标记为“待处理”;如果有提交但尚未合并,标为“进行中”;已经合入主分支的,直接标为“可清理”。
三是 stale 识别。如果一个 worktree 很久没动过,列表里应该体现出“stale”信号。我实际开发中,会根据该 worktree 最后一次提交时间,超过 7 天未更新就标记为过期,提醒我及早决策是继续做还是废弃。
2.3 生命周期管理:从创建到合并再到清理
一个任务从启动到结束,在 Worktrunk 里对应三个动作:new(创建)、done(标记完成并合并)、prune(清理)。
wtrk done是我用得最多的命令。它的内部流程大致是:
- 检查目标 worktree 是否还有未提交的改动。如果是 Agent 在跑,先确认你已经 review 过并 commit 了。
- 切换到主 worktree(通常是
main或main分支所在的目录),把目标分支合入。 - 如果有合并冲突,立即中止操作,提示你手动处理,而不是自作主张地
--force合并。 - 合并成功后,自动删除该分支的远程引用(如果有的话),并把本地 worktree 标记为“已合并”。
wtrk prune则负责物理清理。它会扫描所有并行 worktree,找出那些已经标记为“merged”或者“abandoned”的,依次执行git worktree remove。这个阶段有个细节坑:如果 worktree 目录里有未跟踪文件(比如 Agent 生成的临时文件),git worktree remove会报错。Worktrunk 的做法是在删除前给出确认提示,并展示会有哪些文件被连带删除。
这三个命令串起来,就是一个任务的完整生命周期:创建隔离环境、并行开发、合并回收。用熟了之后,整个流程基本就是三句话的事:
wtrk new "修复支付回调超时" # Agent 在新建的目录里干活 wtrk done "修复支付回调超时" wtrk prune3. 安装与实操全流程
3.1 安装方式与前置依赖
Worktrunk 是一个单文件的 CLI 工具,核心依赖只有 Git(版本不低于 2.5)。我用 Go 写的,编译出来就是一个二进制,没有运行时依赖。安装方式上,提供两种途径:
一种是通过包管理器安装:
brew install worktrunk # 或者 npm install -g worktrunk另一种是直接下载编译好的二进制,扔到 PATH 里:
curl -L https://github.com/yourname/worktrunk/releases/latest/download/worktrunk_linux_amd64.tar.gz | tar xz mv worktrunk /usr/local/bin/安装完成后,建议先跑一次wtrk init。这个命令会在当前仓库的.git/config里写入 Worktrunk 的配置段,同时校验你的 Git 版本是否支持 worktree。如果你有多个常用主分支(比如main和develop),也可以在 init 时配置默认主分支。
3.2 命令速查与关键参数
我平时最常用的命令和参数整理如下:
| 命令 | 作用 | 常用参数 |
|---|---|---|
wtrk new <任务名> | 创建新 worktree 和对应分支 | --base指定基线分支,--path指定工作目录 |
wtrk list | 列出所有任务及状态 | --all包含已清理任务历史,--json输出结构化数据 |
wtrk done <任务名> | 合并任务分支到主分支 | --no-merge只标记不合并,--keep-branch合并不删分支 |
wtrk prune | 清理已合并/废弃的 worktree | --dry-run预览将删除的目录,--force跳过删除前确认 |
wtrk switch <任务名> | 进入某个 worktree 目录 | 直接输出该目录的 shell 会话 |
wtrk info <任务名> | 查看单个任务的详细信息 | --show-path输出 worktree 绝对路径 |
这里说下--json这个参数。我写 Worktrunk 时特意留了这个口子,因为它能跟脚本或者 CI 流程集成。比如你想在 CI 里检查是否所有任务都已合并,可以跑:
wtrk list --json | jq '[.[] | select(.status != "merged")] | length'如果输出结果大于 0,说明还有未合并任务,CI 就可以决定阻止发布。这个集成价值在团队协作时尤其明显。
3.3 一个完整场景演示:三个 Agent 并行处理三个任务
直接来一个我实际操练过很多次的场景。假设我在一个叫shop-server的仓库里,主分支是main,现在有三个任务要并行处理:
- 任务 A:修复订单金额精度丢失问题
- 任务 B:优化商品搜索接口响应时间
- 任务 C:补充支付回调的单元测试
第一步,创建三个 worktree:
cd shop-server wtrk init --main-branch main wtrk new "修复订单金额精度丢失" wtrk new "优化商品搜索接口响应时间" wtrk new "补充支付回调的单元测试"每条命令执行完,终端会输出类似这样的信息:
[Worktrunk] 分支 wtrk/order-amount-precision 已创建 [Worktrunk] worktree 目录: /path/to/shop-server-order-amount-precision [Worktrunk] 进入目录后即可开始开发第二步,我把三个目录分别告诉三个 Claude Code / Codex CLI 会话。比如用 Codex CLI 时,我会在对应目录里执行:
cd /path/to/shop-server-order-amount-precision codex "修复订单金额精度丢失问题,涉及金额计算与数据库存储,请补充测试"这样三个 Agent 各占一个目录,互不干扰。它们的 git 操作(提交、建分支)都在自己的工作区里完成,不会影响主目录。
第三步,Agent 完成后,我逐个 review。确认没问题后执行wtrk done。Worktrunk 会切到主分支,把对应分支合进来:
wtrk done "修复订单金额精度丢失" wtrk done "优化商品搜索接口响应时间" wtrk done "补充支付回调的单元测试"三个任务全部合并后,跑一次wtrk prune,把三个 worktree 清理掉,仓库恢复到干净状态。
整个过程差不多 15 分钟搞定。换作以前单线程,三个任务串行做,怎么也得一上午。这就是并行 AI Agent 工作流的价值,也是 Worktrunk 存在的意义。
4. 与常见 AI 编程工具的搭配实战
4.1 我如何让 Claude Code / Codex CLI 在 worktree 中工作
Worktrunk 的核心使用场景不是人肉开发,而是跟 AI 编程工具配合。我这边长时间在用 Claude Code 和 Codex CLI,简单说下我的搭配方式。
最朴素的用法,就是人工把 Worktrunk 创建好的目录路径复制给 Agent。Worktrunk 在new之后会明确输出一个绝对路径,我直接把它贴进 Agent 的会话里,说“在这个目录下工作”。
但更顺手的方式,是把 Worktrunk 整合进 Agent 的工具链。Claude Code 支持自定义 skill 或 MCP 工具,Codex CLI 也有类似的能力。我在 Claude Code 里定义过一个 skill,它的作用就是:
- 接收自然语言任务,比如“修复用户头像上传失败”。
- 内部调用 Worktrunk 的
wtrk new拿到新的工作目录。 - Claude Code 把自身的会话工作目录切换到该 worktree。
- 开发完成后,我在外层调用
wtrk done完成合并回收。
这样做的最大好处是:Agent 不会直接摸到主工作区,它的一切操作都被限制在隔离目录里。哪怕它乱生成了一堆临时文件,删掉整个目录也不心疼。
4.2 用 Worktrunk 组织多 Agent 并行开发的几条避坑建议
多 Agent 并行开发踩了不少坑之后,我沉淀了几条经验,写在这里给后来人参考。
第一,不要把两个有静态依赖关系的任务丢给不同 Agent。比如任务 A 要改一个公共工具函数的签名,任务 B 要基于这个函数开发新功能。如果并行做,B 极大概率基于旧签名写代码,合入时冲突一堆。正确做法是:有依赖关系的任务串行做,独立的任务才并行。
第二,不要让 Agent 在 worktree 里跑长驻进程。比如npm run dev这种会持续监听文件变更的进程,多个 worktree 同时跑,端口冲突、CPU 占用都是问题。我一般只让 Agent 做“生成代码 + 跑静态测试”这两件事,长驻服务由我在主目录或者 CI 环境里统一起。
第三,合并之前,一定要在 Agent 目录里看一眼工作区状态。Agent 经常留下一些意想不到的东西。我遇到过一次,Agent 在 worktree 的根目录生成了一个 500MB 的日志文件,要不是合并前检查,这玩意差点跟着提交进仓库。Worktrunk 的wtrk list会显示未跟踪文件数量,超过阈值就提示你留意。
第四,给每个 worktree 规定最长的“存活时间”。我个人的经验是:一个 AI 任务如果没有在 3 天内完成,中间状态很容易丢失——Agent 的上下文窗口会滚动掉,你再去 review 它干到一半的代码,往往云里雾里。所以超过 3 天还没做完的任务,宁可wtrk prune掉重来,也不要硬着头皮续。
4.3 多 Agent 工作流的前置规划
最后说下我在跑多 Agent 工作流之前会做的规划动作。打开一张纸(其实是在终端里列个清单),把任务拆好,标注依赖关系,然后才决定并行度。
一个非常实用的策略是“主 Agent 拆解,从 Agent 执行”。我先用主 Agent(通常是最聪明的那个模型)把一个大需求拆成 5 个子任务,每个子任务明确输入输出和涉及文件。然后把子任务分发给多个从 Agent,每个从 Agent 在独立 worktree 里完成。主 Agent 事后做整合 review。这其实就是把团队里“架构师 + 程序员”的分工模式复制到了 AI 工作流里。
Worktrunk 在这套模式里的角色是“任务编排与隔离层”。它管好每个 Agent 的物理工作目录和分支生命周期,让主从 Agent 的协作不被文件冲突打断。
5. 常见问题与排查技巧实录
5.1 创建 worktree 时提示目录已存在
这是最常遇到的问题。比如你执行wtrk new "修复订单金额精度丢失",但系统提示类似directory already exists。
原因通常有两种:第一种确实有一个同名目录残留;第二种是 Worktrunk 生成的分支名撞了之前的分支名,那不只是目录冲突,连 Git 分支都会冲突。
排查方式:
# 看看目标路径是否真的存在 ls -la /path/to/target # 查看所有分支,确认是否有同名分支 git branch --list "wtrk/*" # 如果只是目录残留,先删掉或用 --path 指定新目录 wtrk new "修复订单金额精度丢失" --path /tmp/wtrk-test如果确认是分支名撞了,我一般给任务名加个后缀或者日期来区分,比如“修复订单金额精度丢失-20250601”。Worktrunk 也支持显式指定分支名,wtrk new "任务名" --branch my-custom-branch。
5.2 Agent 工作到一半,worktree 里的改动不见了
这个问题最让人抓狂。Agent 明明在 worktree 里写了一大堆代码,怎么切换了一下分支,改动就消失了?
先说结论:这些改动基本没丢,只是被“藏”起来了。Git worktree 的每个工作目录都有自己的索引和 HEAD。你在 worktree A 里切换分支时,如果在切换前有未提交的改动,Git 会拒绝切换,或者根据配置自动 stash。如果你用了git reset --hard之类的危险命令,那确实会丢。
我在 Worktrunk 里做了一个防呆设计:执行任何可能影响工作区状态的操作之前,先检查是否有未提交或未跟踪文件。如果有,就暂停操作并提示用户,而不是默默执行。
如果你真的遇到了改动消失的情况,按顺序排查:
# 切回那个 worktree 目录 cd /path/to/that-worktree # 看 stash git stash list # 看 reflog git refloggit reflog是最强的兜底,它能找到近期 HEAD 的移动历史。只要改动被提交过一次(哪怕是本地提交),reflog 里都能找回来。
5.3 合并时冲突太多怎么办
多个 Agent 并行开发后,合并回主分支时经常看到“冲突文件几十个”的惨状。这里面有很多冲突其实是“伪冲突”——两个 Agent 各自格式化了一遍代码,或者都往package.json的相同位置插了内容。
我的处理策略是这样的:
首先,如果 Agent 开发的是一个整体模块(比如整块页面或整组接口),合并时冲突不会太多,因为文件交集小。如果冲突真的大面积爆发,说明任务拆分不合理,Agent 们动了同一片文件。
其次,用git merge --no-commit先合一下看规模,如果冲突文件数量超过十个,我倾向于不开git mergetool一个个手动解,太费劲。直接把分支丢回给 Agent,让它重新处理:把主分支的最新代码 merge 到它的 worktree,解决冲突后再给我。
这个流程在 Worktrunk 里对应一个函数,我称为“同步主分支”。执行后 Agent 的 worktree 分支会从主分支拉取并合并,Agent 之后的所有操作都基于合并后的状态。实验下来,这比人工解几十个冲突文件高效多了。
5.4 清理不掉 worktree 的几种特殊情况
wtrk prune的主力场景是清理已合并分支的 worktree,但有几类情况经常让它卡住。
第一类是“worktree 目录里有未跟踪文件”。Git 出于安全考虑,不允许直接删除包含未跟踪文件的工作目录。Worktrunk 的处理方式是先自动执行git clean -df,如果文件太多或想保留一些特定文件,我会用--force-keep参数把目录保留下来,只解除 Git worktree 关联。
第二类是“worktree 里还有某个 Agent 的进程跑着”。这种情况不常发生,但一旦发生,git worktree remove会报错说目录不可删除,并提示有进程占用。我一般在执行 prune 之前,先ps aux | grep worktree 路径看看有没有残留进程,kill 掉再 prune。
第三类是“分支被删了,worktree 成了孤儿”。这种情况出现在有人直接用git branch -D删掉了分支,但 worktree 没删。此时 Worktrunk 会把该 worktree 标记为orphaned,prune 时强制解除关联。
5.5 一个小技巧:用--dry-run放心试操作
如果你对 Worktrunk 还不够熟悉,或者正在处理一个很重要的仓库,我强烈建议所有破坏式操作都先跑一遍--dry-run。它不会真正执行删除或合并,只会列出来“如果执行了会有什么后果”。
比如:
wtrk prune --dry-run输出会明确告诉你,哪些 worktree 会被删除,哪些文件会被连带清理。确认没问题后再真正执行。不要嫌多这一下,但在让你避免删错目录这件事上,这半秒钟的检查非常值。
6. 几个让工作流更顺畅的进阶配置
6.1 默认路径与命名规则
Worktrunk 默认把 worktree 放在仓库的上级目录,命名规则是“仓库名-任务短名”。这个规则在大多数情况下够用,但如果你有特殊偏好,可以在.wtrkrc文件里覆盖。我自己的配置是:
main_branch: main worktree_base: ~/workspace/wtrks branch_prefix: feat/wtrk设置之后,新的 worktree 统一创建在~/workspace/wtrks下,分支名统一是feat/wtrk/xxx。这样主仓库和所有 worktree 的位置一目了然,不会散落在不同的磁盘路径里。
6.2 前置钩子:自动清理残留进程
我在设计 Worktrunk 时还加了钩子机制。比如before-prune钩子,可以在真正删 worktree 之前执行一段脚本。我的实战配置是这样的:如果检测到 worktree 里还有node进程或者python进程在跑,就自动kill掉。要是没有这层保护,经常出现“目录删不掉,Git 提示被占用”的卡壳情况。
6.3 接入 CI 检查未合并任务
前面提到过wtrk list --json,这里再说一个我实际用到的场景。我在团队的 CI 流水线里加了一步,发版之前检查有没有未合并的 worktree 任务:
wtrk list --json | jq -e '[.[] | select(.status != "merged")] | length == 0'如果还有任务没合并,这步会直接失败,提醒我们“有 Agent 干到一半的活还没收尾”。这条规则看着简单,但对保证主分支状态干净非常有效。以前经常出现发版完了才发现某个分支还在 origin 上挂着的情况,自从加了这道检查,再没发生过。
7. 最后再聊几句实际体会
Worktrunk 这个项目给我最大的启示是:AI Agent 时代,真正值钱的不只是模型有多强,而是你怎么组织这些 Agent 的工作方式。Git Worktree 是个 2015 年就有的老功能,但把它跟并行 AI 工作流结合起来之后,效率提升是肉眼可见的。
我现在已经离不开这套工作流了。开一个需求会,拆完任务,直接在终端里跑几个wtrk new,然后给每个 Agent 分配独立的目录,自己只需要在收尾时做 review 和合并。以前那种“打开一堆 IDE 标签页,手动切分支切到头晕”的日子,彻底过去了。
如果你也要在团队里或者个人项目里跑多个 AI Agent,我的建议是:先从一个小项目开始,试着把 2-3 个独立任务丢给并行 Agent,配合 Worktrunk 管理 worktree,跑顺了再逐步扩大并行规模。并行开发这件事,工具选对了,剩下的就是流程问题。