最近这半年,我是彻底被 AI Agent 编程助手给惯坏了。Codex 帮我写接口,Claude Code 帮我修 bug,Trae CLI 帮我跑重构,有时候同一时间能开两三个会话。代码产出确实上来了,但问题也跟着来了:多个 Agent 同时在一个仓库里干活,工作目录是同一个,分支是同一根,改着改着就互相踩脚。你改了 A 文件,它以为 A 文件还是旧的;你跑测试跑挂了,发现是另一个 Agent 的中间状态导致的。
这个思路说穿了就一句话:每个 Agent 任务一个独立工作树、独立分支,互不干扰,最后合并回主干。Git Worktree 本身具备这个能力,但直接用 git worktree 命令管理一堆任务,状态维护、目录命名、清理、上下文注入都很痛苦。Worktrunk 把这些细节封装起来,让“为任务开工作区、跑到一半看状态、跑完合并清理”变成几条简单命令。这篇文章会聊清楚它解决什么问题、底层原理怎么配合、我实际是怎么用的,以及踩过的坑。
1. 项目概述与核心需求解析
1.1 并行 AI Agent 工作流到底卡在哪
我现在的工作方式基本是:给 Codex 或 Claude Code 派一个任务,让它自己读代码、改文件、跑测试。单 Agent 的模式很流畅,一旦并行起来就全变味了。问题通常出在几个地方。
第一,多个 Agent 共用一个工作目录时,文件状态是错乱的。Agent A 正在改src/api/user.ts,Agent B 同时也在看这个文件,它读到的是 A 改到一半的中间状态。轻则理解错误,重则直接把 A 的修改覆盖掉。AI 模型没有能力区分“这个改动是我做的”和“这个改动是别人刚留下的”,因为它本身就活在当前文件系统里。
第二,分支是同一根,语义全乱。如果两个 Agent 都从 main 拉出去改,最后都往同一个分支上提交,你根本不知道这个分支代表哪个任务。更常见的情况是:Agent A 在分支 feature/login 上干活,Agent B 因为git checkout操作把整个仓库切到了另一个分支,A 的文件瞬间“消失”,随后 A 再创建新文件时,实际落在了 B 的分支上。这种问题排查起来极其痛苦。
第三,构建产物和缓存互相污染。Node 项目的node_modules、Python 的__pycache__、编译生成的target/、以及各类日志文件,多个 Agent 频繁读写同一套内容,经常出现“我这边明明跑过了,怎么换个人就挂了”的假性失败。实际上不是代码逻辑问题,而是缓存目录被另一个任务改坏了。
这三个痛点本质上是同一个根源:工作目录没有隔离。人工开发的时候,开发者之间有 commit 节奏、有 code review、有权限边界,能靠约定和流程避让。AI Agent 的协作能力还没到这个水平,它不会主动说“你先改完我再动”,所以只能从工程层面强制隔离。
1.2 Worktrunk 是什么,解决什么问题
Worktrunk 就是一个面向并行 AI Agent 工作流的 Git Worktree 管理 CLI。核心思路是给每个 Agent 任务创建一个独立的 worktree 目录和独立分支,让所有 Agent 在物理隔离的工作区里干活。
它对外暴露的命令围绕“任务”展开,而不是围绕 Git 内部概念展开。你在 Worktrunk 里看到的不是“工作树”“引用”“HEAD”,而是“创建任务”“查看任务”“进入任务目录”“完成任务”。它把 Git Worktree 的底层操作全部转化成了任务生命周期管理。
举一个典型使用过程。你想修一个登录 bug,同时加一个导出功能,还希望 Agent 并行去做,不需要互相等待。传统做法是手动开两个目录、切两个分支、记住每个目录对应哪个任务,然后分别把 Codex 或 Claude Code 扔进去。任务一多就乱,尤其是过了两天再回头看,完全分不清某个 worktree 是为了哪个需求创建的,改动是否已经合并。
Worktrunk 的做法是:
worktrunk init worktrunk task create fix-login-bug worktrunk task create add-export worktrunk task list这样就有了两个隔离的工作区。接下来你可以让 Agent A 在 fix-login-bug 目录里修登录,让 Agent B 在 add-export 目录里做导出。它们看到的代码基线一模一样,但改动互不影响,测试互不干扰。跑完之后,各自合回主干。
所以这个工具解决的核心问题是:多 Agent 并行工作时的工作区和分支分配问题。它不改变 Agent 本身的业务能力,也不改变 Git 的底层逻辑,只是把 Git 已有的能力编排成适合 AI 工作流的样子。
1.3 适用场景与目标用户
这个工具最适用的场景是那些“任务之间基本不重叠”的并行开发。比如同时修多个独立 bug、同时给几个模块加独立功能、同时跑几个实验性方案对比效果。任务之间的代码交集越小,worktree 隔离策略的效果就越明显。
不适合的场景也要说清楚:如果你的任务本身高度耦合,改登录 bug 的时候必须同步改导出模块的依赖,那强行拆到两个 worktree 只会增加最后合并的冲突量。这种场景还是让一个 Agent 串行做完更合适。
目标用户有两类。第一类是重度 AI 编程用户,日常靠 Codex、Claude Code、Trae CLI 这类终端工具写代码,并且经常同时开多个会话。第二类是开始搭建“Agent 多机编排”的团队,希望把多个 Agent 接入同一个代码仓库做自动化改动,但需要一个清晰的隔离和合并机制。
2. Git Worktree 原理与选型考量
2.1 Git Worktree 是什么,和普通分支有什么区别
很多人对 Git Worktree 的理解停留在“一个仓库可以开多个分支”这个层面,这其实是不准确的。分支只是引用,默认情况下一个仓库只有一个工作目录,同一时间只能检出一个分支的内容。
Git Worktree 是 Git 2.5 引入的功能。它的做法是:在一个主仓库的基础上,额外创建多个“工作树”。每个工作树有自己独立的目录、独立的索引文件、独立的 HEAD 引用,但对象数据库和 refs 引用空间是共享的。
用大白话解释:主仓库是一棵树的主干,每一个 worktree 是独立生长出来的枝条。枝条里的树叶(文件)可以随便改,不影响主干,也不影响其他枝条。但所有枝条共享同一个根系(对象数据库),所以分支、提交记录这些内容是互通的。
关键命令就三个:
git worktree add -b feat-export ../repo-export git worktree list git worktree remove ../repo-exportgit worktree add -b feat-export ../repo-export做的事情是:基于当前 HEAD 创建一个新分支 feat-export,同时在新路径../repo-export下初始化一份完整的工作目录,然后切换到新分支。这个目录里的.git是一个文件,内容指向主仓库的.git/worktrees/repo-export目录。所以它不是一个独立仓库,而是一个“主仓库的另一个视图”。
2.2 为什么用 Git Worktree 而不是 clone 多份仓库
最直观的替代方案是git clone多份仓库。但实际对比下来,worktree 优势非常明显。
| 对比项 | Git Worktree | 多次 Clone |
|---|---|---|
| 磁盘占用 | 只复制工作文件,共享 .git 对象库 | 每个仓库都含完整 .git,体积成倍上涨 |
| 拉取新提交 | 主仓库 fetch 一次,所有 worktree 同步可见 | 每个 clone 都要分别 fetch |
| 分支切换 | 每个 worktree 独立固定在一个分支 | 需要手动维护多个 remote 和分支同步 |
| 最终合并 | 直接在同一个仓库内 merge,简单直接 | 需要加 remote、fetch、再 merge,流程冗长 |
| 资源开销 | 极低,几十个 worktree 也没压力 | 每多一个 clone 都是完整仓库复制 |
很早以前我用 clone 方案,管理路径和 remote 的心智负担特别重。每个 clone 都要添加 remote、设置 upstream、定期同步 main,操作错了还会出现“为什么我改了 A,但 B 的代码还是旧的”这种困惑。换成 worktree 以后,所有任务共享同一个 fetch 状态,新版代码一拉,所有 worktree 都能基于最新主分支创建任务。
对 AI Agent 工作流来说,还有一点很关键:worktree 是“一个仓库内的隔离”,最终合并时改动粒度很小,不需要跨仓库搬运提交。Agent 在各自目录里完成修改后,所有提交都在同一个 repo 的引用空间里,合并只是普通的git merge。
2.3 为什么做成 CLI 而不是编辑器插件
有人在设计时问过我:为什么不直接做成 VS Code 插件,或者 IDE 面板?我的判断是:AI Agent 工作流的入口在终端,不在 IDE。
现在的 AI 编程工具,比如 Codex CLI、Claude Code、Trae CLI,本质都是终端进程。它们天然适合在终端环境里被调度、被脚本化、被环境变量配置。Worktrunk 做成 CLI,可以直接作为 Agent 启动命令的前置封装,也能被 CI 脚本调用,还能被用户手动在任意 shell 里执行。
如果做成 IDE 插件,功能边界会受限——插件必须依附于 IDE 进程,无法单独在终端里驱动 Agent;而且用户如果同时用 VS Code 和终端,插件就覆盖不全。CLI 是各种工具的公共交集,思路最通用,维护成本也最低。
3. 核心功能设计与实操过程
3.1 安装与初始化
这个项目我用 Go 写,发布方式很简单,提供一个静态编译的二进制文件。如果你本地有 Go 环境,也可以直接安装:
go install github.com/yourname/worktrunk@latest或者下载对应平台的 release 包,解压后把可执行文件放到 PATH 里。Windows 用户注意一下,Git for Windows 自带一个git.exe,Worktrunk 会通过 PATH 查找它,所以 Git 的安装目录必须已经在 PATH 里。之前有用户反馈,Codex CLI 安装完之后在 CMD 里codex --version正常,但换到 Windows Terminal 就提示找不到,原因就是两个终端的 PATH 环境变量不一致,同步解决就好。
初始化非常简单,在已有的 Git 仓库根目录执行:
worktrunk init运行后会有以下效果:
- 检查当前目录是否是一个 Git 仓库;
- 创建
.worktrunk/目录,用于存放状态配置和任务索引; - 默认把 worktree 统一放在
.worktrunk/worktrees/下,保证所有工作区集中管理,不乱撒; - 生成
config.yaml,里面记录主仓库路径、worktree 根目录、默认主分支名等信息。
config.yaml内容类似这样:
repo: /home/user/projects/myapp worktree_dir: .worktrunk/worktrees main_branch: main完成 init 后,这个仓库就具备并行任务管理能力了。
3.2 创建和管理并行任务工作区
创建任务的命令是:
worktrunk task create fix-login-bug这个命令背后做的事情比看起来复杂。Worktrunk 会先检查任务名是否冲突,然后在.worktrunk/worktrees/fix-login-bug路径下执行git worktree add -b fix-login-bug,最后把任务信息写入状态文件,记录“任务名、分支名、worktree 路径、创建时间、当前状态”。
如果你想指定其他目录,也可以加参数。但我平时建议直接用默认结构,因为 Worktrunk 的所有命令都是按默认结构设计的,强行自定义路径会增加认知负担。
创建完两个任务后,目录结构大概长这样:
myapp/ ├── .worktrunk/ │ ├── config.yaml │ ├── state.yaml │ └── worktrees/ │ ├── fix-login-bug/ │ └── add-export/ ├── src/ ├── tests/ └── README.md主仓库根目录仍然保持干净,两个任务工作区都在.worktrunk/worktrees/下面。git status不会显示这些 worktree 的改动,因为它们是独立工作目录,对主仓库来说只是普通的未跟踪目录。
任务列表一眼就能看全:
worktrunk task list输出类似:
NAME BRANCH PATH STATUS fix-login-bug fix-login-bug .worktrunk/worktrees/fix-login-bug active add-export add-export .worktrunk/worktrees/add-export active切到某个任务的工作目录:
worktrunk task go fix-login-bug这个命令本质上是在子 shell 里切目录,但它同时做了另一件事:写入环境变量,标识当前处于哪个任务。这样后续在目录里执行任何命令,都可以感知到任务上下文。
3.3 与 AI Agent 的上下文联动
Worktrunk 和一个普通的工作目录管理工具最大的区别就在这里:它能和 Agent 启动流程打通。
我通常这样启动 Agent:
worktrunk task run fix-login-bug -- codex这条命令会完成三件事:
- 把当前工作目录切换到 fix-login-bug 的 worktree;
- 设置环境变量
WORKTRUNK_TASK_ID=fix-login-bug、WORKTRUNK_BRANCH=fix-login-bug; - 在设置好的环境里执行
codex命令。
为什么多这一步很重要?因为 AI Agent 是无状态的程序,它只能感知到当前进程的工作目录和环境变量。如果你直接手动cd到某个目录再启动 Codex,Agent 并不知道自己属于哪个任务、要往哪个分支提交。Worktrunk 把任务标识和分支信息注入环境,Agent 就能在初始化时读取这些信息,更精准地理解自己的“工作边界”。
我在配置 Agent 时,会把提示词模板写成这样:
你现在正在执行任务 $WORKTRUNK_TASK_ID。 工作分支是 $WORKTRUNK_BRANCH。 请只修改当前工作目录下的文件,不要尝试切换分支。 完成修改后,优先提交到当前分支。这种做法有几个直接好处。第一,Agent 知道自己的目标分支,不会乱 checkout;第二,如果工作区里同时有多个目录,Agent 也能定位到自己负责的位置;第三,提交信息可以自动带上任务名,方便后续合并和回溯。
如果你用的是 Claude Code 或 Trae CLI,也是一样的套路。因为终端 AI 工具都遵循“当前目录即上下文”的原则,只要目录隔离得干净,Agent 的行为就会收敛到自己的任务范围内。
3.4 任务收尾与清理
Agent 完成任务后,收尾流程是:
worktrunk task finish fix-login-bug这条命令内部会依次做几件事:
- 进入任务 worktree,检查是否有未提交的改动;
- 如果有,会提示你是否先提交,或者用
--force跳过; - 把任务分支切回主分支(比如 main);
- 在保证没有未提交内容的情况下,删除 worktree 目录;
- 清理任务状态记录。
如果你希望把改动合并进主分支,可以加参数:
worktrunk task finish fix-login-bug --merge它会额外执行一次git merge fix-login-bug,把任务分支合并到主分支,然后再清理。这个方式比较适合“任务完成且验证通过”的场景。
还有一条命令值得说:
worktrunk task status它会遍历所有活跃任务,列出每个任务里是否存在未提交改动、落后主分支多少提交、领先主分支多少提交。这个视图在并行多 Agent 的时候特别重要——你能快速看出哪些任务还在跑,哪些已经安静下来可以收尾了。
4. 常见问题与排查技巧实录
4.1 我踩过的几个坑
Worktrunk 本身是薄封装,踩的坑大多来自 Git Worktree 的特性。第一个坑是删除 worktree 时,如果目录里有未提交改动或未跟踪文件,git worktree remove会直接拒绝。这个保护机制很合理,但对 Agent 工作流来说很烦,因为 Agent 经常会在工作目录里生成日志、临时文件、未跟踪的产物。解决办法是使用--force参数,或者先手动清理生成物目录。
第二个坑是主仓库目录绝对不能误删。Worktrunk 的所有 worktree 都依赖主仓库的.git/worktrees目录。如果主仓库被删了,所有 worktree 全部失效,里面的改动虽然文件还在,但所有 Git 历史都会脱离仓库上下文。这个风险在多人协作时尤其要注意。我目前的做法是把主仓库目录放到一个不太容易被误操作的位置,并且用 Worktrunk 的配置明确标注主仓库路径。
第三个坑和构建缓存有关。Worktrunk 隔离了代码目录,但依赖目录如果被每个 worktree 单独安装一遍,磁盘开销会很大;如果共享同一个依赖目录,又会有两个 Agent 同时写node_modules的风险。我目前的方案是:独立的高频构建目录放 worktree 内,比如node_modules、target、build这些各自维护;低频的大件产物放外部共享目录,只读挂载。具体取舍看项目规模,但一定要让每个 worktree 的构建环境保持自洽。
第四个坑是 Agent 的配置文件分散在各处。Codex 的配置可能在~/.codex/或项目根目录,Claude Code 的权限配置也可能放在项目级。如果 Agent 在 worktree 里读不到项目根目录的.claude/配置,行为会跟预期不符。Worktrunk 目前会把主仓库根目录下的 Agent 配置文件做个软链接到 worktree 里,确保 Agent 启动时能拿到一致的配置,又不会互相污染。
4.2 常见问题速查表
| 现象 | 原因 | 解决办法 |
|---|---|---|
git worktree add报“already exists” | 分支名或路径已被占用 | git worktree list查看现有 worktree,删除旧记录 |
| 删除 worktree 时提示有未提交更改 | Agent 留下的临时文件/改动 | 先人工确认内容,再用worktrunk task finish --force |
主仓库git status看不到 worktree 改动 | 这是正常现象,worktree 是独立工作树 | 用worktrunk task status查看每个任务状态 |
执行codex时报 “unable to locate the codex cli binary” | Codex CLI 未加入 PATH 或缺少 runtime 组件 | 检查二进制路径、环境变量,确保 shell 和终端使用同一套 PATH |
| 两个任务都改了同一个文件,合并时冲突 | 任务分工没有划清边界 | 用冲突标记手工解决,后续分配任务时按模块拆分 |
git worktree prune误删了目录 | worktree 元数据与目录失联 | 尽量使用 Worktrunk 的task destroy,不要手动 prune |
4.3 排查思路与建议
如果任务状态乱了,第一件事永远是查状态文件,而不是乱猜。Worktrunk 的所有状态都记录在.worktrunk/state.yaml里,打开它就能看到每个 worktree 对应的任务名、分支名、路径和状态。即使某个命令出错、目录失联,状态文件也能把线索摆出来。
如果是分支合并冲突,建议先看冲突文件涉及哪些模块。如果冲突集中在一两个文件,直接手工解决;如果十几个文件全是冲突,说明任务边界划分有问题,不要硬 merge,应该找业务负责人重新拆分。
另外给并行 Agent 工作流一句忠告:worktree 隔离解决的是“工作目录互相踩踏”的问题,解决不了“业务逻辑互相依赖”的问题。任务拆分越独立,worktree 方案的效果越好。我见过不少人把 Worktrunk 当成万能隔离工具,用之后冲突比之前还多——核心原因就是任务本身就是高度耦合的,拆到多个目录只是把即时冲突延后到了 merge 阶段。
5. 内部实现与设计取舍
5.1 状态存储与命令流程
Worktrunk 的内部设计比较朴素,核心是状态文件和 Git 命令的调度。状态文件用 YAML 记录,每次创建任务都会更新:
tasks: - name: fix-login-bug branch: fix-login-bug path: .worktrunk/worktrees/fix-login-bug status: active created_at: "2025-01-10T10:00:00Z"所有命令的执行流程都是:读状态 -> 解析参数 -> 调用 Git 命令 -> 更新状态。用 Go 实现的好处是编译成单个二进制,不依赖 Python 或 Node 运行时,交给 Agent 或 CI 使用都干净。
对于并发场景,我还给状态文件加了一个文件锁。因为有可能两个 Agent 同时发起任务创建或状态更新,如果没有锁机制,状态文件可能会被写坏。锁实现用的是操作系统的文件锁机制,简单的 flock 就能搞定,没必要引入外部服务。
5.2 为什么不用纯 Shell 脚本
有人提过,这套功能用 Shell 脚本也能实现,为什么要专门写一个 Go 项目?原因有三层。
第一层是跨平台。Shell 脚本在 macOS 和 Linux 上表现良好,但在 Windows 上会很痛苦,路径分隔符、命令行为差异、Git bash 和 CMD 的问题都不好处理。Go 可以交叉编译出三个平台的二进制,维护成本更低。
第二层是状态管理。Shell 脚本处理 YAML 读写和并发加锁要依赖外部工具,做得再深也没有原生语言方便。而且 Shell 脚本报错信息很简陋,Agent 在终端里看到错误往往不知道怎么处理。Go 的错误处理更清晰,能给出带上下文提示的报错信息。
第三层是可测试性和扩展性。Worktrunk 后续要接入任务调度、配额管理、Agent 日志收集这些能力,Shell 脚本会变得越来越难维护。用 Go 写,能针对命令流程做单元测试和集成测试,也更容易扩展。
5.3 后续值得扩展的方向
目前 Worktrunk 已经能支撑我日常的并行 Agent 工作流,但我认为还有几个方向值得继续做。
第一个是任务配额管理。现在所有任务都允许任意创建,但 Agent 并行太多会导致机器资源被吃满。可以给每个项目设置最大并行数,任务创建时检查当前活跃任务数量,超过上限就排队或拒绝。
第二个是 Agent 日志收集。每个 worktree 里的 Agent 命令输出可以自动归档到.worktrunk/logs/<task>.log,这样即使任务结束后,也能回溯这个任务到底发生了什么。
第三个是自动合并策略。对“改动小、测试通过、冲突概率低”的任务,可以自动执行 merge 并删除 worktree。当然这个功能要先做严格的安全检查,不能把没有验证过的改动直接合进主干。
回到开头那个问题:并行 AI Agent 工作流最大的敌人不是 Agent 能力不够,而是多个进程在同一个物理环境里互相干扰。Git Worktree 提供了隔离的物理环境,Worktrunk 则在上面加了一层任务语义,让“开工作区、跑任务、收尾合并”变得可以管理。
最后分享一个我自己的切身体会。这套工具用了两个多月,最大的收获不是命令多好用,而是它倒逼我把任务拆得更细了。以前我给 Agent 派活经常是一大坨需求丢过去,现在因为要创建独立 worktree,必须先想清楚这个任务的边界在哪里、改哪些模块、和谁可能有交集。想不清楚就拆不开,拆不开就并行不起来。这个思路本身,可能比任何工具都更值钱。