如果你手里同时跑着好几个 AI Agent——比如 Codex CLI 在改接口,Claude Code 在写前端,还有一个自动化脚本在补测试——大概率会遇到同一个头疼的问题:它们全挤在同一个工作区里,互相覆盖文件、抢分支、把对方刚写好的代码“恢复”掉。git worktree 本来能解决目录隔离,但裸用又有一堆命名、清理、任务关联的麻烦。我做了个名为 Worktrunk 的 CLI 工具,把 Git Worktree、分支管理和 Agent 任务绑定成一组直观的命令。这篇文章聊聊这个工具的来龙去脉、核心设计和实际踩坑记录,适合正在搞多 Agent 并行开发的团队或个人,也适合刚接触 AI Agent 编程、想给工作流加一层“秩序”的朋友。
1. 为什么并行 AI Agent 工作流需要一个专用 CLI
1.1 多个 Agent 抢同一个工作区的混乱
先说痛点。AI 编程 Agent 和传统开发者不一样,它们没有“我在改这个文件,你别动”的自觉。你给它一句 prompt,它就会自己去读代码、写文件、跑测试、提交 commit。听起来很爽,但一旦你开了两个终端,一个跑 Codex,一个跑 Claude,让它们同时改同一个仓库,问题就来了。
最典型的场景是:Agent A 花了十分钟改完auth_service.go,刚提交。Agent B 在另一个终端里并不知道这件事,它读到的还是旧代码,然后基于旧代码又改了一遍。等你把两个 Agent 的成果合并到一起,要么全是冲突,要么后提交的 Agent 直接把前面的改动覆盖了。更烦的是,Agent B 在 checkout 分支时如果看到工作区有未提交修改,它会停下来问“怎么办”,或者干脆拒绝执行。
我最早也试图通过“人工排队”的方式解决:先让 Agent A 干活,等它提交完,再让 Agent B 上。但这样完全失去了并行能力,任务一多就变成串行流水线。而且很多 Agent 任务并不依赖同一个文件,比如“改登录接口”和“改支付页面样式”,这两件事本来可以同时做。
1.2 Git Worktree 刚好吃下这个需求
Git 本身其实已经给出了标准答案:git worktree。这个命令允许你在同一个仓库里创建多个工作目录,每个目录可以 checkout 不同的分支,所有目录共享同一套对象数据库和远端配置。也就是说,你可以在一个仓库里同时开出两个目录,一个在main分支,一个在feature/login分支,两边互不干扰,各自提交各自推。
实现原理其实不复杂。普通仓库的.git是一个目录,而 worktree 里的.git是一个纯文本文件,里面写着一行路径,指向主仓库的.git/worktrees/<name>。这个<name>目录里存放着该 worktree 自己的 HEAD、index 和 pending refs。多个 worktree 共享对象库和 refs,所以你在 worktree 里创建的 commit,另一个 worktree 用git log能立刻看到。但工作区文件彼此完全独立。
这正好解决了多 Agent 场景下的核心矛盾:目录隔离、分支隔离、对象共享。
1.3 Worktrunk 的设计目标与定位
裸用git worktree能解决目录问题,但离“好用”还有距离。你得自己记哪个目录对应哪个任务,分支名要手动起,任务完成后还得记得清理,不然用久了满屏都是wt-xxxx这种看不懂的目录。更不用说多个 Agent 同时跑的时候,每个 Agent 还要有自己独立的上下文文件(比如 AGENTS.md、CLAUDE.md),这些文件如果都放在主工作区,Agent 之间又会互相污染。
Worktrunk 的定位就是把这一层“日常管理”自动化。它是构建在 Git Worktree 之上的一个封装 CLI,让开发者用 Agent 的语义来操作 worktree,而不是用 Git 的底层命令。你可以这样理解:git worktree 是发动机,而 Worktrunk 是方向盘和仪表盘,它帮你把任务、分支、目录、上下文、清理这些杂事变成几条好记的命令。
2. Worktrunk 的核心设计:从 Git 命令到 Agent 语义
2.1 命令体系与命名规则
Worktrunk 的命令我尽量设计成“一看就知道在干嘛”的风格。核心命令如下:
worktrunk init # 初始化项目配置 worktrunk status # 列出所有 Agent 工作区状态 worktrunk agent create --name codex --task "登录页改造" --base main worktrunk agent run --name codex --task "登录页改造" -- "请实现响应式登录页" worktrunk status --verbose # 查看每个工作区修改了哪些文件 worktrunk merge --name codex --task "登录页改造" --target main worktrunk destroy --name codex --task "登录页改造" --prune worktrunk doctor # 检查环境,排查常见问题每条命令背后都有一串固定的 Git 操作。比如agent create实际上会经历这几步:
- 校验 base 分支存在,并且与远端同步;
- 把任务描述转换成目录名和分支名,默认的目录格式是
wt/<agent>-<task-slug>,分支格式是agent/<agent>/<task-slug>; - 执行
git worktree add <目录> -b <分支> <base分支>; - 在 worktree 根目录写入一个
.worktrunk-agent.yml文件,记录 Agent 名称、任务描述、创建时间、关联 CLI; - 根据配置,为 Agent 生成上下文文件(比如拼接 AGENTS.md 和任务说明)。
这些默认值都写死在代码里,如果你不喜欢,可以通过配置文件覆盖。
2.2 一个 Agent 工作区的完整生命周期
Worktrunk 把每个 Agent 任务看成一次“有开始、有结束”的会话。创建 worktree 只是第一步,后续所有操作都围绕这个会话展开。
任务进行中,你可以随时用worktrunk status查看所有 Agent 工作区的状态。它的输出大概是这样的:
Repository: payment-service (branch: main) Base: main (ahead 0, behind 0) Agent worktrees: codex/feature-login-page ../payment-service-wt/codex-feature-login-page branch: agent/codex/feature-login-page state: 2 modified files, 1 new file, CI pending claude/fix-payment-timeout ../payment-service-wt/claude-fix-payment-timeout branch: agent/claude/fix-payment-timeout state: clean, ahead 3 commits这个状态面板是我日常用得最多的东西。它把“哪些 Agent 还在改、哪些已经提交完、哪些还在等 CI”一次性摊开,不用再挨个目录去git status。
等任务完成后,worktrunk merge会按顺序执行:检查是否有未提交修改(有的话提示先提交或自动提交)、切到目标分支、拉取远端、合并 Agent 分支、推送、调用 gh 或 glab 创建 MR/PR。这一条链路上每一步都可能出错,所以命令会一步步打印执行日志,出问题时能定位到具体是哪个环节。
2.3 配置文件:声明式定义 Agent 工作区
Worktrunk 的配置采用 YAML 格式,放在仓库根目录的.worktrunk.yml。下面是一个实际可用的示例:
version: 1 project: payment-service default_base: main worktree_dir: ../payment-service-wt # worktree 放项目外,避免 Agent 扫描时互相干扰 context_files: - AGENTS.md - docs/ARCHITECTURE.md agents: codex: cli: codex entrypoint: exec auto_commit: true claude: cli: claude entrypoint: -p auto_commit: false重点说下worktree_dir。我强烈建议把 worktree 放到项目目录外面。原因很实际:如果 worktree 建在项目内部(比如.wt/目录),Agent 扫描代码的时候很容易把其他 Agent 的工作区当成普通源码读进去,导致它看到一堆重复代码,产生错误判断。放到项目外,Agent 眼里只有自己这一个干净的目录。
context_files的作用是给每个 Agent 准备“背景材料”。每次创建 worktree 时,Worktrunk 会把这些文件的内容复制到 worktree 的根目录,并追加一段任务说明。这样每个 Agent 都有独立的任务上下文,不会出现两个 Agent 同时改写同一个 AGENTS.md 的情况。
3. 从零搭建:安装、初始化和第一轮并行任务
3.1 安装方式
Worktrunk 是 Go 写的单二进制文件,安装很简单。目前支持两种方式:
# 方式一:Homebrew(macOS / Linux) brew install worktrunk/tap/worktrunk # 方式二:Go 直接安装 go install github.com/worktrunk/worktrunk@latest安装完先跑一下worktrunk --version确认没问题,然后worktrunk doctor帮你检查环境。这个命令会看几样东西:Git 版本是否支持 worktree(2.5 以上才支持)、远端认证是否配置、当前目录是否在一个 Git 仓库里、操作系统是否有路径长度限制。我第一次在 Windows 上跑时就靠它发现路径太长的问题。
3.2 初始化一个项目
假设你有一个名为payment-service的仓库,主分支是main,当前工作区是干净的。初始化命令只需要一行:
cd ~/dev/payment-service worktrunk init这个命令会做三件事:检查当前 Git 仓库状态、生成一个默认的.worktrunk.yml、打印一份“默认约定”说明。默认约定包括:worktree 目录命名规则、分支命名规则、上下文文件的注入方式。如果你是第一次用,建议先花五分钟看看这些默认值,再决定要不要在配置文件里改。
3.3 创建并运行并行任务
现在模拟一个真实场景。今天有两个任务:一个由 Codex 负责“实现登录/注册接口”,另一个由 Claude Code 负责“实现登录页面 UI”。两个任务之间没有代码依赖,可以并行。
# 为 Codex 创建工作区 worktrunk agent create --name codex --task "实现登录注册接口" --base main # 为 Claude 创建工作区 worktrunk agent create --name claude --task "实现登录页面UI" --base main执行完后,项目外会出现两个目录:../payment-service-wt/codex-实现登录注册接口和../payment-service-wt/claude-实现登录页面UI。我刚才的目录名里带了中文,实际使用时 Worktrunk 默认会把中文转成拼音或英文字段,避免某些工具链在非 ASCII 路径上出问题。
接下来分别让两个 Agent 进入自己的目录干活。你可以手动 cd 进去跑,也可以让 Worktrunk 代劳:
worktrunk agent run --name codex --task "实现登录注册接口" \ -- "请实现登录接口 /api/login,包含参数校验和 token 签发,并补上单元测试"这条命令会在对应的 worktree 目录里执行 Agent CLI,比如 Codex:
cd ../payment-service-wt/codex-实现登录注册接口 codex exec --skip-git-repo-check "请实现登录接口..."加--skip-git-repo-check是因为 Codex 在某些情况下会嫌弃 worktree 里的 .git 不是标准目录结构,跳过检查能避免它拒绝执行。
两个 Agent 并行跑的时候,你可以在第三个终端里用worktrunk status观察它们的进度。哪个 Agent 改了哪些文件、是否已经产生提交,一眼就能看到。
3.4 合并与清理
任务完成后,合并是一个高频操作。Worktrunk 的merge命令会把分支合并回目标分支,并生成 MR/PR:
worktrunk merge --name codex --task "实现登录注册接口" --target main这背后做的事情是:
- 进入 worktree,检查是否有未提交修改,有的话按照
auto_commit配置决定是自动提交还是停下来询问; - 切回主工作区,
git checkout main; git pull拉取最新远端;git merge --no-ff agent/codex/实现登录注册接口,生成一个明确的合并提交;git push推送;- 如果配置了
gh或glab,自动创建 MR 草稿,并把任务描述填进去。
清理工作区对应destroy命令:
worktrunk destroy --name codex --task "实现登录注册接口" --prune它会执行git worktree remove,删除 Agent 分支,清理工作区元数据文件,最后跑一次git worktree prune把残留引用清掉。这样整个任务生命周期结束,不留垃圾。
4. 实操中的典型问题与排查技巧
4.1 常见问题速查表
用了一段时间后,我把遇到的典型问题整理成了下面这个表,方便遇到问题时快速定位。
| 现象 | 原因 | 解决方式 |
|---|---|---|
报错'xx' is already checked out at '...' | 同一个分支不能同时被两个 worktree 使用 | 用git worktree list找到已占用的 worktree,给它换分支,或者选择另一个新分支 |
报错fatal: 'worktrees/xxx' already exists | 目录或元数据残留 | 检查目录是否真的存在,然后git worktree prune --expire now清理索引 |
| 某些脚本/工具读不到 Git 配置 | worktree 里的.git是文件不是目录,脚本按目录方式访问会失败 | 脚本里应该用git rev-parse --git-common-dir,而不是直接拼.git/config路径 |
Agent 提交时提示缺少user.name | 全局 Git 配置没配好 | 先配全局或仓库级配置:git config --global user.name "your name" |
| Windows 下 Agent 执行失败 | worktree 目录路径太长,超过 MAX_PATH | 把worktree_dir配置到尽量短的外部路径,或开启系统长路径支持 |
| 工作区里有来自其他分支的旧文件 | 任务依赖关系没声明,两个 Agent 改到了重叠文件 | 使用worktrunk run的depends_on声明依赖,强制串行执行冲突任务 |
| 合并时新的提交被反复弹回 | 两个 Agent 同时推了同一个目标分支,远端处于中间状态 | 先git pull --rebase再合入,或者调整并行度,避免同时推同一分支 |
| 同一个 Agent CLI 同时跑两个任务导致串话 | Codex/Claude 这类 CLI 多数不支持并发执行 | 同一 Agent 的任务在调度上排队,worktrunk run默认不并行同一 Agent |
4.2 我踩过的几个坑
第一个坑是 worktree 目录放项目内部。我最早把 worktree 建在.wt/下面,结果 Agent 扫描目录时,把其他 Agent 的工作区当成项目源码的一部分,读了一堆重复代码进来,给出的建议全都乱了。后来我把worktree_dir改成项目外,这个问题彻底消失。如果你接手的项目已经把 worktree 建在了仓库内部,至少要把这些目录加到.gitignore里,并且在 Agent 的上下文文件里明确“不要扫描这些目录”。
第二个坑是 pre-commit 钩子。很多 Python/Node 项目会在钩子里跑 lint,钩子脚本里如果用pwd来定位仓库根目录,在 worktree 里会定位到 worktree 自己的目录,这其实是符合预期的,但有些脚本会继续往上找.git目录,找到的却是主仓库目录,导致路径错乱。写钩子脚本时,根目录定位应该统一用git rev-parse --show-toplevel,这个命令在 worktree 里会返回正确的顶层目录。
第三个坑和 Agent 上下文长度有关。如果一个 Agent 长时间待在同一个 worktree 里,它会在文件末尾反复追加 helper 函数,越写越长,甚至出现重复定义。我的经验是:一个任务一个 worktree,任务结束立即 destroy,不要让 Agent 在一个 worktree 里“续命”很久,这样每个 Agent 的上下文都是干净的,它也更专注。
4.3 几个让我少走弯路的设计决定
第一个决定是把上下文文件做成了“注入”而不是“共享”。最开始的版本里,所有 Agent 共用一个 AGENTS.md,结果 Codex 往里面加了“登录模块的说明”,Claude 干 UI 任务时也读到了这段,然后被带偏去做接口逻辑。现在每个 worktree 都会生成一份独立的任务说明文件,相当于给每个 Agent 单独发了一份“项目背景 + 本次任务”的简报,效果好了很多。
第二个决定是默认禁用同一 Agent 的并行执行。虽然 Worktrunk 面向的是并行工作流,但同一个 Agent CLI(比如 codex)同时跑两个任务,大概率会在同一个缓存目录或者配置目录上打架。所以我在run的调度器里加了规则:不同 Agent 可以并行,同一 Agent 的任务必须排队。这是妥协,但换来了稳定。
第三个决定是提供doctor命令。很多 Git worktree 的兼容问题不是马上暴露的,而是某个工具链在某个时刻突然爆出来。worktrunk doctor会把 Git 版本、远端认证、路径长度、hook 脚本、子模块状态都扫一遍,并给出具体的修复建议。我从一开始就把这个命令当成一等公民,因为并行 Agent 工作流一旦跑起来,排查问题的成本非常高,不如提前把基础检查好。
5. 后续还可以怎么扩展
我自己目前用的是命令行版本,但心里已经有几个想做的扩展方向。第一个是按 Agent 维度的审计和计费——每个 Agent 任务改了多少文件、跑了多少次命令、花了多长时间,这些数据如果能自动汇总到一张表格里,对团队管理很有价值。Worktrunk 的 worktree 元数据文件已经记录了任务创建和完成时间,后续只要在destroy时把统计信息输出到.worktrunk/audit/目录,再配一条命令按项目汇总就行。
第二个方向是把 DAG 任务调度做成可视化。现在worktrunk run plan.yml会在控制台输出执行顺序,但并行的任务多了以后,一张图比文字直观得多。我倾向于输出一个静态的 HTML 报告,或者直接在终端里用树形结构展示依赖关系,而不是引入额外的前端依赖。
第三个方向是和 MCP(Model Context Protocol)集成。现在各种 Agent 都支持 MCP 工具,如果能把 Worktrunk 封装成一个 MCP server,让 Agent 自己在需要时创建 worktree、查看其他 worktree 的状态,就能实现 Agent 之间的“互相感知”,而不是仅靠外部调度。这个想法还在实验阶段,但我觉得方向是对的。
最后分享一个实际操作中的体会:不要一上来就追求“全自动并行”。先从手工命令跑通两个 Agent 的完整生命周期,再慢慢引入 task graph 和自动合并。多 Agent 并行的最大风险不是技术,而是你没想清楚哪些任务可以真正并行,哪些任务其实在读写同一批文件。Worktrunk 只能帮你解决目录和分支层面的隔离,任务之间的数据依赖,归根结底还是要靠人先想清楚。