ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用 Git Worktree 管理并行 AI Agent 工作流:Worktrunk 实战指南

用 Git Worktree 管理并行 AI Agent 工作流:Worktrunk 实战指南 这个月我用 Codex CLI、Claude Code 在同一台机器上并行跑了十几个修复任务Git 历史干净得像单线程写出来的一样。如果不是提前给每个 Agent 配了独立的 worktree仓库早就乱成一锅粥了。今天这篇就把 Worktrunk 这套方案完整摊开讲它解决什么问题、核心设计怎么来的、以及我实际接入并行 AI Agent 工作流时踩过的坑和补救办法。无论你是在做 AI 编程工具链的工程化还是单纯被多个 Agent 同时改一个仓库搞到崩溃这篇文章都值得看完。1. 为什么并行 Agent 会踩到同一块工作区1.1 你真正的痛点不是跑得慢是互相拆台先说一个最常见的场景你用 Codex CLI 起了一个会话让它修登录接口的超时 bug又用 Claude Code 起了一个会话让它重构工具函数库。两个 Agent 默认都在同一个 git checkout 目录里工作。这时候问题就来了。Agent 不像人它不会跟另一个 Agent 说我正要动 utils/request.ts你别碰。它只会基于自己上下文里看到的文件内容去改改完还可能直接git commit --all。你想想它执行--all的时候另一个 Agent 刚写的未提交改动会怎样全被它带进自己的 commit 里了。更别说两边同时改同一个文件的时候后写入的一方会把前面的改动整个覆盖掉。我在最初跑并行 Agent 的时候最崩溃的一次是这样的A Agent 改完了 6 个文件并提交B Agent 正在改第 3 个文件时报错文件已被外部修改。我去看发现 A Agent 提交的 6 个文件里有 3 个是 B Agent 刚写的。那次我花了将近一个小时才把提交拆开因为 A Agent 用--all把 B 的东西全吸收进去了git 层面根本分不清哪些改动属于谁。这就是共享工作区最致命的点你的隔离性等于零。1.2 clone 一份新仓库听起来可行实际很痛我最初试过的方案是给每个 Agent 单独git clone一份仓库。这个方案确实解决了工作区冲突但带来了新的问题磁盘占用直接翻倍如果仓库里有大文件或者构建缓存几个并行任务跑下来几百 MB 就没了。每个 clone 都是一个独立的 .git 对象库你在主仓库里未推送的提交、游离分支、本地标签Agent 那边全都看不到。修改完以后要把结果合回来得先 push 到远端再 pull或者用 patch 文件手工搬运。一旦涉及 merge 冲突来回折腾的沟通成本比写代码还高。在 monorepo 场景下更难受。一个包含十几个包的仓库每个包都 clone 一份光是安装依赖就能把机器跑死。那有没有一种方案是共用同一份 .git 对象库但每个任务有独立的工作区和分支有就是git worktree。1.3 git worktree 是官方解法但原生命令不够顺手git worktree是一个很老但很多人不熟的功能。它的核心机制是同一个.git对象库里可以挂多个工作目录每个工作目录对应一个独立分支互不干扰。底层原理是每个 worktree 有自己独立的 HEAD、index 和 per-worktree refsrefs/worktree/命名空间所以切换分支、提交代码、改文件都不会串。举一个简单例子# 在主仓库 git worktree add -b fix/login-timeout ../app-fix-login main cd ../app-fix-login # 在这里可以安全地改代码完全不影响主目录 codex exec 修复登录超时问题这个机制天然适合并行 Agent共享对象库隔离工作区。但我实际用下来发现原生git worktree命令在 Agent 并行场景还是太原始了。第一你要手动记忆哪个 worktree 目录对应哪个任务任务一多就乱。第二git worktree list的输出没有任务状态、没有最近提交时间、没有当前进度Agent 跑到哪一步你根本不知道。第三清理的时候很容易误删——git worktree prune会把元数据不一致的 worktree 标记为过期如果你没 lock正在跑的任务目录也可能被清掉。这些痛点累积起来我决定做一个专门的 CLI 工具把 worktree 的创建、命名、状态查询、合并、清理全部收口成以任务为单位的操作。这就是 Worktrunk 的由来。2. Worktrunk 核心设计以任务为单位的 Worktree 生命周期管理2.1 为什么不继续写 shell 脚本很多人看到这里会想这玩意儿用 shell 脚本不就几十行吗我一开始也是这么想的但真正深入以后发现 shell 脚本在几个方面扛不住。跨平台。macOS 的sed -i和 Linux 不一样Windows 的路径分隔符、执行权限、符号链接更是折磨。Agent 工作流往往要在多种环境下跑脚本一多就变成能用但只有我能用。状态持久化。我需要在多个命令之间记住某个任务处于什么状态、跟哪个分支绑定、Agent 跑到哪一步。每次都去解析git worktree list --porcelain不是不行但逻辑会越来越复杂。安全保护。清理 worktree 之前要确认 Agent 是否还在目录里跑、分支有没有未推送的提交、有没有 uncommitted changes。这种安全检查用脚本写异常繁琐而且很容易漏。所以 Worktrunk 选择了 Go 来写。一来编译成单个二进制分发简单二来跨平台支持好三来标准库对 JSON、文件锁、子进程管理支持都不错适合做这类开发工具。2.2 核心命令设计围绕任务而不是分支Worktrunk 的命令设计原则是用户和 Agent 关注的是任务不是分支或路径。所以所有操作都以任务名为中心。# 创建一个新任务自动创建分支和 worktree worktrunk create fix-login-timeout --base main # 列出所有任务及其状态 worktrunk list # 进入某个任务的工作目录 worktrunk cd fix-login-timeout # 把某个任务的分支合并回 base worktrunk merge fix-login-timeout --rebase --delete # 清理已完成/已合并的任务 worktrunk prune # 显示每个任务占用的磁盘空间 worktrunk disk创建任务的时候Worktrunk 会做这么几件事校验任务名是否合法、是否已存在。基于--base指定的分支默认 main创建新分支命名规范是worktrunk/task-name。在约定的子目录默认.worktrees/task-name执行git worktree add -b ...。往状态文件里写入任务元数据。2.3 状态文件与命名约定Worktrunk 会在仓库根目录的.worktrunk/state.json里维护一份任务状态。大概长这样{ version: 1, baseBranch: main, tasks: { fix-login-timeout: { worktreePath: .worktrees/fix-login-timeout, branch: worktrunk/fix-login-timeout, status: active, createdAt: 2025-01-06T10:30:0008:00, lastActiveAt: 2025-01-06T11:20:0008:00 } } }有人可能问为什么不直接解析git worktree list --porcelain来获取状态我觉得有两个原因。worktree list 能告诉我们 worktree 存在但无法轻松关联这个 worktree 是给谁用的、什么时候创建的、Agent 任务到哪一步了。per-worktree 的状态信息分散在多个 git 内部文件中自己维护一份 JSON 更直观也方便以后扩展任务依赖Agent 类型等字段。这就是为什么 Worktrunk 以任务为核心——它把 git 层面的分支、目录、状态粘合成一个更高层的概念让并行工作流变得可管理、可观察。3. 实操把 Codex CLI、Claude Code 塞进独立 Worktree3.1 初始化与创建任务假设你有一个仓库名字叫my-service你想并行处理三个任务修复登录超时重构 HTTP 客户端升级日志库第一步初始化 Worktrunkcd my-service worktrunk init --base main第二步为每个任务创建独立 worktreeworktrunk create fix-login-timeout --base main worktrunk create refactor-http-client --base main worktrunk create upgrade-logging-lib --base main执行完以后目录结构大概是my-service/ ├── .worktrunk/ │ └── state.json ├── .worktrees/ │ ├── fix-login-timeout/ │ ├── refactor-http-client/ │ └── upgrade-logging-lib/ └── src/每个.worktrees/下的目录就是一个独立 checkout它们共享同一个.git对象库所以不会重复占用太多磁盘空间。3.2 并行启动多个 Agent接下来就是关键一步让不同 Agent 在不同 worktree 里工作。以 Codex CLI 和 Claude Code 为例# 终端 1跑 Codex 修复登录超时 cd my-service/.worktrees/fix-login-timeout codex exec 修复登录超时问题确保请求在 3 秒内完成 # 终端 2跑 Claude Code 重构 HTTP 客户端 cd my-service/.worktrees/refactor-http-client claude -p 重构 http client统一错误处理结构 # 终端 3跑 Codex 升级日志库 cd my-service/.worktrees/upgrade-logging-lib codex exec 将日志库从 log4j 1.x 升级到 2.x保持接口兼容你可以用tmux或screen开多个窗格也可以写一个简单的并行启动脚本#!/usr/bin/env bash set -e declare -A TASKS( [fix-login-timeout]codex exec \修复登录超时问题\ [refactor-http-client]claude -p \重构 http client\ [upgrade-logging-lib]codex exec \升级日志库\ ) for task in ${!TASKS[]}; do ( cd my-service/.worktrees/$task eval ${TASKS[$task]} ) done wait这个脚本的核心逻辑是每个 Agent 都在自己的 worktree 目录里启动彼此之间的文件改动、git 提交、依赖安装完全隔离。你只需要在主目录用worktrunk list观察所有任务的进度即可。3.3 让 Agent 只看到自己的上下文还要防止它越界光有独立目录还不够Agent 在自动模式下可能做出一些越界行为。这里有几个实用技巧。限制文件访问范围。在任务目录下放一个.worktrunk/AGENTS.md明确告诉 Agent你只被允许修改 src/ 下的文件不要动 go.mod之类。虽然这靠 Agent 自律但实际约束效果明显。隔离 git 身份。每个任务的提交应该用不同的身份这样git log一眼能看出来哪些提交来自哪个 Agent。Worktrunk 可以在创建任务时自动写入 per-worktree 的 git 配置git config --worktree user.name agent-codex-fix-login git config --worktree user.email codexlocal关于 per-worktree 配置Git 2.20 支持git config --worktree它会把配置写到$GIT_DIR/config.worktree只对当前 worktree 生效完美适配这个场景。统一约束 commit 行为。有些 Agent 默认不带--no-verify跑 pre-commit hook 会花很长时间。我在创建任务时会默认生成一个.worktrunk/pre-commit.disable标记agent 工具调用前会先判断要不要绕过 hook。不过这属于工具链适配后面可以再展开。3.4 合并、冲突处理与清理任务跑完后合并回主分支是重头戏。worktrunk merge会按固定流程处理先检查任务 worktree 是否有未提交的改动。如果有自动帮你 commit 或者警告。把任务所在分支 rebase 到最新的 main 上提前暴露冲突。若有冲突停下来让你手工解决或者交给另一个 Agent 处理没有冲突则merge --no-ff回 main。实际操作worktrunk merge fix-login-timeout如果 rebase 出现冲突Worktrunk 会提示冲突文件列表并且提供两种选择手工解决或者启动一个新 Agent 在对应 worktree 里自动解决冲突。# 在 fix-login-timeout 的 worktree 里自动解冲突 cd my-service/.worktrees/fix-login-timeout codex exec 解决当前 rebase 冲突保留两个分支的语义 git rebase --continue所有任务合并并验证通过后一次性清理worktrunk pruneprune 会删除已合并分支对应的 worktree 和分支同时更新状态文件。这里最需要注意的一点是prune 之前 Worktrunk 会强制检查目标 worktree 是否有未提交改动、是否有活动中的 Agent 进程。这一点我在下面踩坑部分会详细说。4. 目录规划与命名策略并行任务的边界怎么划4.1 任务拆分粒度依赖密集的任务不要并行我一开始踩过的坑是对任务拆分粒度毫无概念。比如重构 HTTP 客户端和升级日志库两个任务它们改动的文件可能有大量重叠都在src/common/下结果两边同时改了同一个文件合并时冲突多到怀疑人生。并行任务的安全边界其实是文件依赖边界而不是语义边界。如果两个任务要改的文件集合交集很大它们就不适合并行——无论用不用 worktree最终合并都会是一场灾难。实际经验是两个任务改动文件交集为空或极小是最佳并行候选。有共享公共文件但改动逻辑独立可以接受但合并时要留意。改动同一个核心模块且无法拆分建议串行执行或者先把模块接口抽出来任务 A 改接口任务 B 在接口之上做实现这样两边才真正解耦。Worktrunk 在list里会展示每个任务最近改动过的文件列表我习惯拿这个来判断是否适合并行。4.2 依赖目录的共享与隔离并行 worktree 之间最容易被忽略的是依赖目录。举个典型例子一个 Node.js 项目每个 worktree 都跑一遍npm installnode_modules 会重复安装磁盘和安装时间都成倍增长。我采用的是共享依赖目录的方案。对于 Node.js 项目推荐用 pnpm。pnpm 本身有全局 content-addressable store多个 worktree 里跑pnpm install不会重复下载硬链接直接指向 store非常高效。不需要额外 symlink。如果你用的是 npm 或 yarn可以用 symlink 指向共享目录# 首次创建共享依赖目录 mkdir -p /shared/deps/my-service/node_modules # 每个 worktree 里 ln -s /shared/deps/my-service/node_modules my-service/node_modules注意Windows 上创建符号链接需要开发者模式或管理员权限。如果你在 CI 或容器里跑记得提前开启。对于 Rust 项目用的是CARGO_TARGET_DIR环境变量export CARGO_TARGET_DIR/shared/target/my-service这样所有 worktree 的编译产物都走同一个缓存目录不会重复编译。对于 Python 项目venv 环境可以复制一份.venv也可以直接共用。共用的话用--system-site-packages或者设置PYTHONPATH指向共享目录都行但要注意依赖冲突时不好排查。我一般选择每个任务保留独立 venv但用同一个 pip cache速度也很快。4.3 Monorepo 场景下的特殊规划monorepo 用 worktree 并行是最舒服的场景因为包与包之间天然隔离。但要注意跨包改动极容易冲突。比如你在包 A 里改了接口包 B 也调用了这个接口两个 Agent 分别改 A 和 B合并后包 B 可能因为缺少 A 的提交而无法编译。我的建议是跨包的任务尽量用一个 worktree 串行完成全部跨包改动。如果一个任务必须同时改 A 和 B就不要同时起另一个也动 A/B 的 Agent。Worktrunk 的命名里我习惯带上模块前缀比如fix-login-timeout只动 auth 模块refactor-http-client只动 client 模块一眼就能看出边界。5. 踩坑实录并发安全、垃圾回收与身份隔离5.1 worktree prune 误删活跃目录这是我最开始用裸git worktree时的惨痛教训。某次我手动执行git worktree prune正在跑 Agent 的任务目录差一点被清理掉。prune 的判断是基于.git/worktrees/name/gitdir文件是否指向有效路径。某些情况下比如目录被其他进程临时锁定、路径暂时不可达prune 会误判为失效然后把元数据清掉。Worktrunk 的处理方式是维护自己的状态文件作为权威来源prune 之前必须检查状态文件里标记为 active 的任务。Active 的任务绝不能清理除非显式用--force。这是一个工作流工具最核心的安全兜底。# 强制清理某个 active 任务不推荐但需要时存在 worktrunk prune --force fix-login-timeout5.2 Agent 提交身份混在一起并行 Agent 跑得多了git log里全是同一个人名比如你本机的 user.name提交你根本分不清哪个提交是哪个 Agent 干的。更尴尬的是如果某个 Agent 出错你想回滚它的全部改动靠人名根本过滤不出来。解决方案就是前面说的 per-worktree git 配置。在创建每个任务时Worktrunk 自动写入独立的 user.name / user.emailgit config --worktree user.name agent/codex/fix-login-timeout git config --worktree user.email agentcodexfix-login-timeoutlocal这样git log --authoragent/codex/fix-login-timeout就能精准拉出这个任务的所有提交。万一 Agent 把整个分支搞砸了你可以快速识别并回滚而不影响其它并行任务。5.3 未提交改动跨任务漂移很多人会问worktree 都隔离了未提交改动怎么还会漂移问题出现在你手动在主目录切分支的时候。假如你有个 Agent 在主目录跑着你自己也在主目录手工改了文件然后你执行了git checkout feature-x你未提交的改动会被一并带到新分支——如果两边文件不冲突的话。这个行为在单 worktree 下是 git 的特性但对并行 Agent 工作流来说就是一个坑。Worktrunk 明确要求所有 Agent 只能在各自的 worktree 目录里启动主目录不跑任何 Agent。这样主目录始终对应 main 分支干净、可控。我建议你也养成这个习惯主目录只是用来创建任务、查看状态、合并分支的地方不要让任何 Agent 在主目录执行代码生成。5.4 git gc 卡顿并行工作流的另一个实际问题是垃圾回收。worktree 共享同一个对象库如果你有十几个 worktree 同时跑了很久git gc或git maintenance run可能带来明显的卡顿因为对象数暴涨重新打包耗时很长。解决方案有几个定期跑git maintenance start让 git 在后台自动做增量式维护。Git 2.31 用增量机制卡顿会明显降低。大量的任务分支合并回 main 后及时用worktrunk prune清理。如果仓库特别大可以设置稀疏检出git sparse-checkout让 worktree 只检出需要的目录减少 IO 压力。5.5 Windows 路径与权限问题如果你在 Windows 上跑 Worktrunk有几个坑一定会遇到。路径长度。Windows 默认路径上限 260 字符worktree 嵌套多层后很容易超。在 git 里开启core.longpaths true可以缓解但根治还是要尽量用短路径。符号链接权限。前面提到共享 node_modules 时要用符号链接Windows 需要开发者模式或管理员权限。如果你在受限的 CI 机器上跑建议改用 pnpm 的全局 store或者直接把 node_modules 设为每任务独立安装。Agent 命令的调用方式。Windows 下的进程启动和路径解析和 Unix 不一样Worktrunk 在启动 Agent 时会优先用cmd /c同时把路径转成/风格避免反斜杠转义问题。5.6 LSP/语言服务缓存串台最后一个是很多人没注意到的坑VS Code 或 JetBrains 同时打开多个 worktree 目录时语言服务器LSP可能缓存旧的编译单元。我在并行跑 TypeScript 项目时遇到过非常诡异的情况——某个文件在任务 A 的 worktree 里修改了任务 B 的窗口里报错提示类型不匹配但文件内容其实已经是最新的。原因是 LSP 服务进程复用了旧的 source 缓存。我的解决办法是尽量不要在同一个 IDE 实例里同时打开多个 worktree用独立窗口打开。给每个 IDE 窗口单独启动一个语言服务进程。VS Code 里每个窗口默认就是独立的 TypeScript 服务但要注意不要让插件跨窗口共享缓存。如果出现诡异的类型报错重启语言服务往往比继续查代码更高效。6. 后续还能怎么扩展6.1 自动同步 main让任务从一开始就不落后我目前的做法是任务创建时基于 main 拉分支但如果主分支更新很快任务跑着跑着就落后了。下一步我想在 Worktrunk 里加入自动 rebase策略每个任务达到某个里程碑时自动把 main 的最新提交 rebase 到任务分支上。这样冲突不会积累到最后一刻爆发。实现上不复杂在worktrunk merge之前先做一次git fetch origin main git rebase origin/main如果冲突可以自动调用之前提到的Agent 解冲突能力。6.2 多个 Agent 方案对比与自动挑选并行工作流的一个好处是可以用不同 Agent 跑同一个任务最后对比结果。比如让 Codex CLI 和 Claude Code 同时实现同一个功能然后在各自的 worktree 里跑测试、看指标选出更优的一版合并。Worktrunk 的模型很容易扩展这个场景一个任务可以有多个候选方案每个候选方案对应一个 worktree 分支最后合入的只是其中一版。这个功能我已经在原型阶段了关键是对比的维度要定义清楚——测试覆盖率、复杂风险、性能指标等。6.3 任务归档与知识沉淀并行 Agent 跑完之后worktree 被清理了但这些任务的经验值得沉淀。我计划把worktrunk merge生成一个简短的合并报告包含任务原始描述实际上改动的文件列表测试结果Agent 的最终自述agent 在提交信息里写入的总结合并时间与由谁合并把这些信息归档到docs/decisions/下相当于给 AI 编程工作流留下了可追溯的决策记录。以后要用 AI Agent 继续改相关模块时直接把这份报告喂给它上下文质量和开工效率都会有明显提升。6.4 与 CI/CD 无缝衔接并行 worktree 的终极形态是每个任务创建后自动在 CI 里开一个 preview 环境跑完整测试把结果反馈到任务状态。这样 Agent 在 worktree 里跑CI 也在对应的分支上验证两边进度一目了然。Worktrunk 的create命令目前已经支持回调 hook任务创建、进入 ready、合并成功这些事件都会触发脚本。你可以挂一个 Webhook把任务状态同步到内部看板或 IM 通知里。这是我实际使用中最满意的一部分整个并行工作流的可观测性和可管理性都上了一个台阶。最后分享一个我个人的使用体会并行 AI Agent 工作流能不能真正提效工具只占一半另一半是你的任务拆分能力和工作区纪律。Worktrunk 这类工具的价值是把给每个 Agent 独立工作区这个正确但繁琐的底层操作变成一条命令让并行从听起来可行变成踩实了能用。如果你也在跑多个 Agent 改同一个仓库强烈建议今天就把 worktree 玩起来越早建立这个习惯后面收拾烂摊子的时间就越少。
返回列表