说实话,第一次在项目里同时跑三个 AI Agent 的时候,我觉得自己马上就要解放了——Codex 改一个模块、Claude Code 修一个 bug、Gemini CLI 再补一个测试,大家各干各的,多好。结果一个上午过去,代码没合并多少,整个仓库倒是乱成一锅粥:有人把依赖版本改了,有人把公共函数签名动了,还有人 push 之前忘了 pull,直接把我本地没提交的工作区冲了。那时候我才真正意识到,并行 AI Agent 工作流里,最缺的不是更强的模型,而是一套能把执行环境互相隔离开的机制。Git Worktree 就是为这个问题存在的,而我基于它做了个管理 CLI,叫 Worktrunk。
这个工具解决的核心问题一句话就能说清:当你有多个 Agent(或者你自己开多个终端会话)要同时在一个仓库里干活时,Worktrunk 能让每个任务都拥有独立的工作目录、独立的分支、独立的构建缓存,互不踩踏。它不是重新发明一套版本管理,而是把 Git 原生的 worktree 能力包装成一套适合并行任务场景的 CLI 工作流。这篇文章我会从底层讲清楚它为什么能解决并行开发的冲突,再把安装、配置、实战命令和踩坑记录全部摊开给你看。无论你是在密集使用 Codex、Claude Code 这类 AI 编程工具,还是单纯想优化自己多任务并行开发的流程,这篇文章都值得你花十分钟读完。
1. 并行 Agent 开发的真实痛点:为什么会把仓库搞得一团糟
1.1 多个 AI Agent 同时改代码,到底乱在哪里
很多刚接触 AI Agent 编程的人会有一个错觉:既然每个 Agent 有自己的对话上下文,它们各自生成的代码就应该天然是隔离的。实际上完全不是这样。Agent 最终都要落盘到同一个文件系统、同一个工作目录、同一套分支关系里。当两个 Agent 同时在工作目录里跑测试、装依赖、改配置文件,冲突几乎是必然的。
我遇到过几个典型场景。第一个是依赖锁文件冲突,Agent A 给某个模块加了新依赖,Agent B 在另一个模块里删了旧依赖,两者都修改了package-lock.json或者go.sum,等我把两边的改动合并时,冲突内容是一大串哈希值,人工根本没法判断谁对谁错。第二个是构建缓存互相污染,前端项目里的.next或dist目录,后端项目的__pycache__和编译产物,多个 Agent 在同一目录里反复构建时,经常出现"我明明改了这个函数,但构建出来还是旧代码"的诡异现象,其实就是构建缓存被另一个 Agent 的中间产物污染了。第三个是测试基础设施的冲突,两个 Agent 同时跑监听模式的测试,端口被占用、本地数据库被重建,这类问题最让人崩溃,因为你抓不住是哪个 Agent 干的。
我自己最惨的一次经历,是让一个 Agent 去重构某个公共模块的导出方式,同时让另一个 Agent 给这个模块加新功能。两个 Agent 都不知道对方的存在,结果其中一个在保存文件时基于的是旧的内存快照,直接把对方刚改好的代码整体覆盖了。这种问题靠"小心一点"是解决不了的,需要从工作环境层面做物理隔离。
1.2 Git Worktree 到底解决了什么
Git Worktree 不是新东西,它从 Git 2.5 开始就是官方功能。简单说,它允许你在同一个仓库下创建多个工作目录,每个目录可以检出不同的分支,而这些目录共享同一个.git对象数据库。
你可以把它理解成给同一个房子开了好几扇独立的大门,门里面的房间是分开的,但储物间(.git目录里的对象)是共用的。这样做的直接好处是:一个仓库可以同时有好几个 checkout,每个 checkout 里代码状态完全不同,但它们的提交历史、对象仓储是同一套。你在 worktree A 里提交代码,worktree B 里马上可以git fetch到,不需要通过远程仓库中转。
这个特性对并行开发的帮助是革命性的。每个 Agent 分配到一个独立的 worktree 之后,它拥有完整的工作目录、独立的 HEAD、独立的索引、独立的暂存区。它可以在自己的目录里随意安装依赖、生成构建产物、修改文件,这些操作都是物理隔离的,影响不到其他 Agent。更关键的是,每个 worktree 天然对应一个分支,任务之间的代码隔离从"逻辑层面"升级到了"路径层面"。
Git 还会强制保证一个分支只能在一个 worktree 里被检出。如果你试图在第二个 worktree 里 checkout 一个已经被其他 worktree 使用的分支,Git 会直接报错,从根本上杜绝了两个 Agent 同时基于同一分支改代码、然后互相覆盖的情况。
1.3 原生 git worktree 命令为什么不够用
既然 Git 原生提供了 worktree 能力,那直接用命令不就行了?理论上可以,实际操作中你会遇到一堆烦人的事情。首先,原生命令需要你手动给每个 worktree 起名字、记路径。比如你要为修复登录 bug 的任务建一个 worktree,你得自己敲git worktree add ../fix-login-bug fix/login-bug,然后还得记住这个目录现在对应哪条分支、任务进行到什么阶段。任务一多,目录和分支的对应关系就全凭记忆了。
其次,原生命令没有任务状态的概念。worktree 建好了,任务做完了,你应该删除它、合并分支,但这一步经常被遗忘。时间一长,仓库里堆了一堆没人用的 worktree 目录,既占用磁盘空间,又拖慢 Git 操作。你可能想问,git worktree list不是能看吗?能看,但看完之后你还是要手动去清理。当你有五六个并行任务、每个任务又分好几天完成时,这个维护成本是实打实的。
还有一个问题更隐蔽:原生命令是面向"人"的,不是面向"Agent"的。人的记忆力和判断力可以弥补命令的繁琐,但 Agent 不知道你的项目规范,它不会主动为每个新任务创建 worktree,也不会在任务结束时清理现场。你需要一个更结构化的方式,把 worktree、分支、任务目标、Agent 会话绑定在一起,让整个生命周期可追踪、可自动化。这就是 Flow 里缺的那一环,也是我动手写 Worktrunk 的原因。
2. Worktrunk 的核心设计:把 Worktree 变成 Agent 的隔离沙箱
2.1 一个任务对应一个独立的 Worktree
Worktrunk 的核心设计理念非常朴素:一个任务 = 一个 worktree = 一个分支 = 一个 Agent 会话。我在设计这个 CLI 的时候,没有往里面塞复杂的抽象概念,就是围绕"任务"来组织一切。
你创建一个新任务时,Worktrunk 会帮你生成一个命名规范的 worktree,默认路径类似worktrunks/task-issue-123-fix-login,同时帮你基于指定的基线分支(比如main或develop)创建新分支,分支名和目录名自动关联。这样你不需要花任何心思去操心分支和目录的对应关系,看一眼路径就知道这是哪个任务的工位。
这套设计最大的价值在于:每个 Agent 不需要理解整个项目的分支策略,它只需要知道"我的工作目录是哪里"。你把 Agent 启动时的当前目录指向对应的 worktree,它就在自己的沙箱里自由发挥。任务结束后,你执行 Worktrunk 的完成命令,它会自动帮你做三件事:检查分支有没有未提交的改动、提醒或自动合并回基线、最后清理 worktree 目录。整个过程脚本化,不需要人肉记忆状态。
2.2 核心命令一览与设计意图
Worktrunk 的命令集设计得很克制,没有为了功能多而堆砌命令。我按实际使用频率把命令分成三类:生命周期类、状态查询类、清理维护类。
# 生命周期类 worktrunk init # 在当前仓库初始化 worktrunk 配置 worktrunk start <task-name> # 创建并切换到一个新任务 worktree worktrunk finish <task-name> # 完成一个任务:合并分支、清理 worktree worktrunk stop <task-name> # 暂停任务:保留现场但不占用当前焦点 # 状态查询类 worktrunk list # 列出所有 worktree 及对应任务状态 worktrunk current # 显示当前所在 worktree 和分支信息 # 清理维护类 worktrunk prune # 清理已合并但未删除的 worktree worktrunk reset <task-name> # 丢弃某个任务的本地改动并重新开始命令命名的逻辑是顺着任务生命周期的直觉来的:开始、结束、暂停、查询、清理。start之后你会被放在一个全新的目录里,list能看清全局任务分布,finish把任务收尾的所有脏活干完。这套命令集对人不算惊艳,但对 Agent 非常友好——Agent 只需要记住两三个命令(start、list、finish),就能配合完成整套流程。
2.3 与原生 Git Worktree 命令的关系
必须说清楚,Worktrunk 不是要取代git worktree,它是在原生命令之上加了一层任务编排层。底层实际执行的还是git worktree add、git worktree prune这些原生操作,Worktrunk 负责的是更上层的状态管理和命名约定。
这种分层设计有几个务实的好处。第一,它保证了底层行为的稳定,Git 的 worktree 机制已经成熟稳定,我们没必要重复造轮子。第二,它让工具的学习成本低,懂 Git 的人迁移过来基本零门槛,你依然可以随时用原生命令查看 worktree 情况,Worktrunk 的文件和 Git 原生的 worktree 系统完全兼容。第三,它留出了扩展空间,如果 Git 未来更新了 worktree 能力,Worktrunk 只需要适配更上层的逻辑。
对这种"封装"的设计思路,我的理解是:底层能力越通用越好,上层体验越贴近场景越好。Git 提供的是通用能力,Worktrunk 提供的是任务场景的编排,各司其职。
3. 安装配置与快速上手:从一个仓库开始
3.1 安装与环境准备
Worktrunk 的安装方式比较常规,我用过的版本支持三种渠道:如果你有 Rust 工具链,可以直接通过cargo install worktrunk安装;在 macOS 环境里可以用 Homebrew,brew install worktrunk;Linux 环境下可以直接从发布页下载预编译的二进制扔到PATH里。
安装之前要确认系统 Git 版本在 2.30 以上。因为 worktree 相关的若干增强功能(特别是git worktree list --porcelain输出格式稳定化)是在这个版本附近完善的,版本太低会导致 Worktrunk 在解析 worktree 状态时出问题。运行下面这条命令验证环境:
git --version worktrunk --version我实际踩过一个坑:在公司的老服务器上 Git 版本还是 2.17,安装完 worktrunk 之后worktrunk list输出一直异常,排查半天发现是 Git 版本太老,--porcelain输出里的字段和工具预期不匹配。所以如果你的环境比较老,先把 Git 升上去再装 Worktrunk。
3.2 在项目里初始化 Worktrunk
进入你的项目根目录,执行初始化命令:
cd /path/to/your-project worktrunk init这个命令会在项目根目录生成一个.worktrunk/config.toml文件,同时创建worktrunks/目录,后续所有任务 worktree 都会在这个目录下按任务名区分。初始化时它会自动探测当前所在分支,把它作为默认的基线分支。
配置文件是 TOML 格式,初始化生成的推荐配置大致长这样:
[project] name = "my-service" base_branch = "main" [worktree] root_dir = "worktrunks" branch_prefix = "task" keep_on_finish = false [agent] default_command = "claude"我建议在初始化之后先检查一下base_branch是否正确,如果你通常基于develop分支做开发,就手动改掉。root_dir是 worktree 的根目录,默认在项目下的worktrunks/目录,如果你有特殊的目录规范可以改到项目外,比如../my-service-worktrees,这样项目目录更干净。
3.3 几个值得认真配置的选项
branch_prefix是一个看起来不重要、实际影响很大的参数。默认值是task,创建任务时最终分支名会是task/login-fix、task/payment-fix这样。如果你的团队已经有分支命名规范,比如要求使用feature/或fix/前缀,最好在这里配置好,否则后面所有 Agent 创建的分支都长一个样,Code Review 的时候没法一眼判断分支类型。
keep_on_finish决定了任务完成后 worktree 是否保留。项目前期我建议设为true,Graph 上保留现场,发现问题还能切回去复现;跑顺之后可以改成false,让finish自动清理干净。我的个人习惯是设为true,手动确认无问题后再用worktrunk prune批量清理,这样多一层安全缓冲。
default_command是 Worktrunk 比较特别的配置项。它允许你为任务配置默认的 Agent 启动命令。当你执行worktrunk start时,工具会在输出的任务信息里附带一条可直接执行的 Agent 启动命令,省去每次手动拼装的麻烦。比如你配置了default_command = "codex",你还可以补一个default_prompt = "请阅读 README 后开始实现任务需求",让 Agent 启动时有一个统一的行为基线。
3.4 用一条命令跑通全流程
配置完成之后,新开一个任务只需要一条命令:
worktrunk start fix-login-error执行完,这个命令会做四件事:基于main创建并检出新的分支task/fix-login-error;在worktrunks/fix-login-error/目录创建 worktree;把该任务的元信息(创建时间、分支名、当前状态、关联 Agent 命令)记录到.worktrunk/state文件里;最后在终端输出这个任务的启动信息。
启动信息里包括 worktree 路径、分支名、建议执行的 Agent 命令。你可以直接cd到输出的目录,然后执行codex或claude,Agent 就会在那个隔离环境里干活。整个过程从零到准备好,不到三秒钟,而且每一步的状态都是可查的。
4. 实战:让三个 AI Agent 并行开发互不干扰
4.1 任务拆解与 Worktree 规划
工具装好了,理论说完了,我们来实际跑一个典型场景。假设我有一个后端项目,目前有三个任务要做:
- 修复登录接口在 token 过期时返回错误码不统一的问题(bug)
- 给订单模块增加导出 CSV 的功能(feature)
- 把项目里所有数据库查询迁移到新的查询构造器(refactor)
这三个任务涉及不同模块,但都会改动公共的基础代码(尤其是第三个任务,迁移查询构造器几乎会碰所有文件)。如果用同一个工作目录并行跑三个 Agent,我觉得可以肯定地说,三个任务一个都完不成。但用 worktrunk 跑就很干净。
我先初始化并规划好三个任务:
worktrunk start fix-login-token-expire worktrunk start add-order-csv-export worktrunk start refactor-query-builder三条命令执行完,项目目录下会多出三个隔离的 worktree:
your-project/ ├── worktrunks/ │ ├── fix-login-token-expire/ │ ├── add-order-csv-export/ │ └── refactor-query-builder/ ├── src/ └── .worktrunk/每个目录对应一个独立分支、一个独立工作区。三个 Agent 在各自的目录里改代码,文件系统层面的操作互不可见,这是我想要的效果。
4.2 为每个 Agent 启动独立的会话
接下来就是给每个任务分配 Agent。我一般会在不同的终端窗口启动,这样日志和输出隔离得更干净:
# 终端 1:修 bug 的 Agent cd worktrunks/fix-login-token-expire codex --dangerously-bypass-permissions # 终端 2:做 CSV 导出功能的 Agent cd worktrunks/add-order-csv-export claude --dangerously-skip-permissions # 终端 3:做查询构造器重构的 Agent cd worktrunks/refactor-query-builder gemini-cli启动之后,每个 Agent 都以为自己在唯一的项目里干活,它看不到其他分支的改动,不会因为另一个 Agent 改了同名文件而困惑。它安装依赖也是装到自己 worktree 的node_modules里(如果项目依赖安装在项目根目录的话),构建产物也只会出现在自己的目录里。
这个环节有两个细节我想特别提醒。第一个是 Agent 的权限参数:现在几个主流 AI 编程 CLI 默认都会要求用户确认文件操作,并行跑的时候你根本不可能守在三个终端前面不停点确认。所以批量跑 Agent 时要按工具的要求,显式关闭确认机制,但要注意这确实有风险,建议在一次性、非核心的环境里使用。第二个是安装依赖的策略,如果项目依赖比较大,每个 worktree 都装一套依赖会浪费磁盘。我的做法是先用一个基础 worktree 把依赖装好,其他 worktree 在启动 Agent 之前先执行软链接或者复制缓存,具体看项目语言,Node 项目可以直接把node_modules链接过去,Python 项目则建议直接使用同一套虚拟环境但要求 Agent 不修改依赖列表。
4.3 跑完后的合并与收尾流程
三个 Agent 干完活后,代码分散在三个分支和一个公共基线上,需要在 Worktrunk 的协助下统一收口。我的通常做法是"逐分支 Review、逐分支合并",先处理风险最低的 bug 修复,再处理独立的功能分支,最后处理影响面最大的重构分支。
# 先看 task 更新了哪些文件 git -C worktrunks/fix-login-token-expire diff main...HEAD # 确认没问题后,切回主仓库并合并 git checkout main git merge task/fix-login-token-expire # 合并完成且测试通过后,执行 finish worktrunk finish fix-login-token-expirefinish命令默认不会立即删除 worktree,只会把任务状态标记为 completed 并建议你确认删除。当你决定清理时,worktrunk prune会遍历所有已完成任务,把对应 worktree 目录删掉。对于重构这种跨模块的大改动,我建议在finish之前多做一轮全量测试,因为它在重构分支上看着正常,合到基线上很可能会和其他分支的改动产生集成问题。
4.4 并行度不是越高越好
我在实际使用中逐渐得出一个结论:并行 Agent 的数量,要按"公共代码的耦合度"来定,而不是按任务的多少来定。三个任务都集中在同一模块的内部逻辑,并行效果可能很差,因为最终合并时你会面对来自三个方向的激烈冲突。反过来,一个前端、一个后端、一个独立的 CLI 工具,这种天然模块化强的项目,并行度可以拉得很高。
如果多个任务注定要改同一批文件,我现在的做法是:让其中一个任务作为"主干任务",预留足够的时间,其他任务严格基于最新的主干任务分支派生。比如重构查询构造器会大面积改文件,那我就先把这个任务的 worktree 建好,等它完成大半,再让修 bug 的 Agent 从重构分支的最新状态派生一个新的 worktree,这样最终的合并冲突会少很多。虽然牺牲了一点并行度,但省下了大量解决冲突的时间,怎么算都划得来。
5. 常见问题与排查技巧实录
5.1 分支已存在于其他 Worktree,无法创建任务
这是新用户最容易遇到的问题。你可能手动用git worktree add创建过同名分支,或者上一个任务没有正确结束,分支还被别的 worktree 占用。Worktrunk 创建任务失败时,先不要急着删目录,先跑worktrunk list看分支被哪个 worktree 持有。
worktrunk list # - fix-login-token-expire status=active branch=task/fix-login-token-expire path=worktrunks/fix-login-token-expire确认确实是陈旧任务占用时,如果你确定改动都不需要了,可以执行worktrunk reset重置这个任务,然后再执行git worktree prune -expire做一次彻底的元数据清理。这里要特别说明:不要直接rm -rf删 worktree 目录,因为 Git 的 worktree 注册表里会留下脏数据,后面所有 worktree 操作都可能被影响。
5.2 Agent 在错误的目录里启动了
我有一个同事犯过低级错误:他在项目根目录启动了 Agent,然后告诉 Agent"去 worktrunks/fix-login-token-expire 目录下工作"。听起来没问题,但 Agent 有时会"自作聪明",为了执行 git 操作直接回到工作根目录,导致在错误的 worktree 里改了一堆文件。AI 编程 CLI 工具通常是以"当前工作目录"为根,它不理解你口头指定的子目录边界。
对策很简单:启动 Agent 之前,先把工作目录切到目标 worktree 内部,然后再执行 Agent 命令,不要在根目录间接引导。如果 Agent 自己做了cd ..,你要在工作开始前明确要求它不要离开当前目录。
5.3 构建缓存和依赖在多个 Worktree 之间互相干扰
常见问题的重灾区是依赖目录和缓存目录。虽然每个 worktree 的代码是独立的,但很多项目会把依赖安装到项目根目录外,或者使用全局缓存目录,导致 worktree 之间依然共享了某些状态。前端项目 pnpm 默认的全局 store 是共享的,如果 Agent 更新 lockfile 后重新 install,全局 store 的缓存可能让另一个 worktree 的构建结果异常。
我的处理方式比较务实:在 Agent 开工前,给每个 worktree 设置独立的环境变量,把缓存目录指向 worktree 内部。以 Node 项目为例:
export NODE_ENV=development export NPM_CONFIG_CACHE="$PWD/.npm-cache" export NEXT_DIST_DIR="$PWD/.next"Agent 在这个环境里启动后,它生成的缓存和产物都锁定在 worktree 内部,和其他任务物理隔离。配置好这些之后,模块构建出现"改了代码没生效"的诡异问题的概率暴跌。
5.4 多个 Agent 同时操作同一仓库的远端分支
还有一个很容易忽略的问题:虽然 Worktrunk 让每个 Agent 用独立本地分支,但如果配置了自动 push,多个 Agent 同时向远程仓库 push 时仍然可能产生远端竞争。比如 Agent A 和 Agent B 都基于main派生分支并开启自动 push,当它们都尝试更新远端记录时,可能因为远端main的变动而互相影响。
我的团队现在的约定是:Agent 默认只提交到本地分支,不做自动 push,只允许在人工 Review 之后统一 push。这样远端仓库的状态始终是可控的,不会出现多个 Agent 创建的远端分支满天飞、没人清理的混乱状态。
5.5 常见问题速查表
| 现象 | 原因 | 处理方式 |
|---|---|---|
| start 报错"branch already exists" | 分支被其他 worktree 占用 | 执行worktrunk list找到占用者,或 reset 陈旧任务 |
| worktrunk list 显示状态异常 | .worktrunk/state 文件损坏或手动改过 | 备份后删除 state 文件,执行worktrunk init -f重建状态文件 |
| 合并时大量冲突 | Agent 基于过期的基线分支工作 | 用 worktrunk 重建基于最新main的任务,减少任务并行时间窗口 |
| worktree 删不掉 | 目录里有未提交改动或未跟踪文件 | 先查看git status,处理完全部改动后再执行 prune |
| Agent 找不到依赖 | worktree 里没有安装依赖 | 先用 Base Worktree 安装依赖,再通过软链接或复制共享给其他 worktree |
5.6 几条我自己攒下来的经验
用久了之后,我把几条"常识"总结成了团队的固定流程。第一,永远先执行worktrunk current确认自己所在的位置再动手写代码。这听起来像废话,但我在多个 worktree 之间切换时犯过不止一次错误。第二,每个 Agent 开工前,先把该 worktree 的基线分支 pull 到最新状态,避免 Agent 基于过期的代码写出一堆不可用的修改。第三,任务描述里一定要带上"你只能在当前目录下修改文件",这句话成本极低但能规避大量跑错地方的问题。
6. 这套工作流的扩展思考
写完 Worktrunk 并在几个项目里稳定使用之后,我越来越确信一件事:AI Agent 编程要进入真正的工程化阶段,工具链一定会沿着"环境隔离"和"任务编排"两个方向进化。Worktrunk 只是开了个头。
环境隔离方面,目前 worktree 隔离的是 Git 工作目录,但依赖缓存、构建缓存、环境变量这些还依赖使用者自己规划。我在自己的配置里已经通过脚本把.npm-cache、.venv这类目录全部改到 worktree 内,未来的工具应该默认做到这一步,甚至可以在创建 worktree 时按项目类型自动生成对应的.gitignore和缓存策略。
任务编排方面,我现在是手动把 Agent 会话分配到 worktree,但如果 Agent 本身支持启动时读取任务描述文件,整个流程可以更进一步自动化。比如在 worktree 创建时自动生成一个TASK.md,里面包含任务目标、验收标准、相关文件列表,Agent 启动时自动读取这个文件并理解自己的任务边界。再把"完成标准"定义成可验证的测试命令,Agent 跑完测试后确认无误,才能被标记为 complete。这套机制结合 CI,就可以实现真正的"提交即验证、验证即合并"的自动流水线。
另外一个值得探索的方向是 MCP 扩展。现在主流 AI 编程工具基本都支持 MCP 协议,如果你把 Worktrunk 暴露成一个 MCP Server,Agent 就能直接通过自然语言来管理自己的 worktree,不需要记忆任何 CLI 命令。那样的话,Agent 可以在任务开始前自己创建环境、在任务进行中自己查询状态、在任务完成后自己清理现场。这才是 Agent 原生工作流的完整形态。
不过这些都属于"未来可以继续打磨的东西"。就现阶段而言,Worktrunk 已经把最疼的并行冲突问题解决掉了,这是我个人最大的感受。之前每次并行开发,心里的弦都绷着,担心某个 Agent 改动破坏了另一个 Agent 的现场;现在每个 Agent 都有一间独立的小房间,我再也不用半夜接到"代码被覆盖了"的紧急电话。如果你也在用 AI Agent 编程、并且被多任务并行搞到头大,我真心建议你尝试一下这个工具,花十分钟配好之后,它大概率会改变你和 Agent 协作的方式。最后说一句个人心得:任何工具的价值都不在工具本身,而在它能否让你从重复的协调劳动里解放出来,把精力真正放到设计和决策上。Worktrunk 这次算是做到了。