Worktrunk:并行AI Agent工作流的Git Worktree管理CLI
先给你一个场景你手里同时跑着三五个 AI Agent 在同一个仓库里改代码Claude Code 在重构模块 ACodex CLI 在补测试另一个 Agent 在改文档。它们会共用一个工作目录踩坏彼此的未提交改动甚至因为同一个index.lock互相对峙一上午全耗在“这文件怎么又变了”上。这个问题的标准解法是 Git Worktree但裸用 Git 命令管理多个并行工作区很快又会被分支命名、路径记忆、任务回收这类琐事淹没。Worktrunk 就是我做的一个面向并行 AI Agent 工作流的 Git Worktree 管理 CLI把“给每个 Agent 分配独立工作区、跑完回收、冲突早发现”这件事封装成了一条条清晰命令。这篇文章我会从设计思路、核心命令拆解、完整实操、常见坑和进阶实践五个维度讲讲为什么需要它、怎么用好它以及我在真实项目中踩过的那些坑。1. 并行 AI Agent 工作流的最大痛点一个仓库多个大脑1.1 多智能体同时改动一个仓库有多痛我先说一个我团队里的真实事故。我们把一个中型服务仓库同时交给了四个 Agent一个负责新增接口一个负责重构鉴权逻辑一个在补单元测试还有一个在整理 README 和 API 文档。刚开始很顺利但两个小时后仓库工作目录里出现了大量互相覆盖的文件。Agent A 以为auth/service.go是自己刚改过的版本结果 Agent B 在另一个会话里把它回滚了Agent C 新建的utils/token.go又和 Agent D 生成的临时文件重名最后整个编译都过不了。这种“共享目录”模式的问题本质上有三层文件级冲突多个 Agent 改同一个工作目录下的同名文件后写覆盖先写毫无防护。Git 状态污染一个 Agent 执行git add、git reset、git stash时影响的是全部 Agent 共同的暂存区和索引。它就相当于几个程序员不拉分支全在一个 checkout 里各改各的。上下文错乱Agent 是通过当前目录里的文件内容来理解任务的目录被别的进程频繁改动模型的上下文会被“噪声”反复污染表现为答非所问、越改越偏。如果你只是一个人用 IDE 写代码这种问题还好控制但 AI Agent 的执行路径往往是非线性的它会动态创建文件、重命名、回滚。让它们共用一个工作区等于让一群不太讲卫生的实习生共用一张桌子。1.2 为什么必须用 Worktree 而不是分支切换有些人会说“让每个 Agent 用一个分支不就行了完事再合并。”这个方向是对的但具体做法有讲究。如果用git checkout -b agent-a的方式切换分支那么同一时间整个仓库还是只有一个工作目录。Agent A 切到自己的分支干活时Agent B 无法同时工作因为工作目录只有一份。你要么串行跑要么就得反复 stash、checkout、checkout、stash中间稍有不慎就会丢失未提交的改动。Git Worktree 解决的正是在“同一个仓库、多个工作目录、各自独立分支”上的并行问题。它的原理很好理解仓库的对象库、引用、配置是共享的但每个 worktree 有自己独立的 HEAD、索引和工作目录。你只需要一条命令git worktree add ../repo-agent-a -b feature/agent-a它就会为feature/agent-a分支创建一个新目录../repo-agent-a。在这个目录里你可以随意改动、提交完全不影响主工作区。多个 Agent 可以同时操作同一个仓库的不同 worktree各写各的分支最后再合并回主干。相比分支切换worktree 的优势非常实在真正并行N 个 Agent 就有 N 个独立工作目录互不等待。状态隔离每个 Agent 的未提交改动、暂存区、未跟踪文件都是独立的不会互相踩踏。不打断上下文Agent 进入某个 worktree 后它看到的目录内容是稳定的不会因为另一个 Agent 的 checkout 操作而剧变。异常恢复成本低某个 Agent 把目录搞乱了直接删掉这个 worktree 重新创建一个干净的即可主仓库毫发无损。1.3 Worktrunk 解决的问题范围裸用 Git Worktree 有一个很现实的问题它是 Git 的底层设施并不是为“多 Agent 并行作业”设计的。你需要在外面包一层任务管理逻辑每个 Agent 对应哪个 worktree 路径、哪个分支创建完成后怎么快速让 Agent 的工作目录切换过去任务跑完了谁负责提交、合并、删分支、清目录如果 Agent 中途失败worktree 变成“僵尸工作区”怎么发现和回收多个人、多个 CI、多个 Agent 同时操作怎么避免 Worktree 本身的命名冲突这些就是 Worktrunk 这个 CLI 的职责。它的核心定位不是替代git worktree而是作为“任务级”的管理层把底层命令封装成面向 Agent 生命周期的操作原语。你也可以把它理解成一个比较薄的、专门为 Agent 工作流设计的调度壳只是它管的是工作区而不是计算资源。2. Worktrunk 的设计思路与核心命令拆解2.1 核心模型任务到工作区的映射Worktrunk 的第一个设计决策是想清楚“一个 Agent 的一次作业”应该如何建模。我把它定义成一个四元组任务标识、工作区路径、跟踪分支、生命周期状态。TaskID AgentA-20250214-01 WorktreePath .wt/agent-a Branch feature/agent-a-task01 State active | commit-pending | merged | closed | orphan在这个模型里任务标识是给 Agent 和人看的建议直接用能读懂的字符串比如agent-name/task-id。工作区路径是 Agent 实际干活的地方我习惯统一放在仓库根目录下的.wt/里这样.gitignore只用加一行就能避免误提交。跟踪分支则是每个 Agent 独立工作的分支命名规则由 Worktrunk 统一生成避免你和 Agent 自己手写分支名导致撞车。为什么不用 Agent 自己的名字直接当分支名因为同一个 Agent 很可能同时接到多个任务它需要多个工作区而且 Agent 一旦崩溃重启名字相同但任务可能不同容易混淆。所以分支名里必须带上任务维度。每创建一个 worktreeWorktrunk 会在自己的状态目录里写入一条 JSON 记录{ task_id: AgentA-20250214-01, path: .wt/agent-a, branch: feature/agent-a-task01, base_branch: main, created_at: 2025-02-14T10:00:00Z, status: active }这份记录是 Worktrunk 后续所有命令的依据list列出它close更新它prune删除僵尸记录。把这个模型想清楚后面的命令设计就顺了。2.2 命令集设计哪些该做哪些不该做CLI 设计最容易犯的错是追求大而全。Worktrunk 执行的是“够用且明确”原则平常用得最多的命令不超过 6 个。命令作用对应裸 Git 操作worktrunk init在当前仓库初始化 Worktrunk 配置创建.wt/目录和状态索引无worktrunk start -n agent-a -b main为一个任务创建独立 worktree生成分支并记录状态git worktree addgit branchworktrunk list查看所有活跃/僵尸工作区含磁盘占用、脏文件数git worktree list 状态核对worktrunk use agent-a输出或切换到指定任务的工作目录cd的封装worktrunk close agent-a --commit --merge检查工作区差异提交改动合并回基线分支清理 worktreegit add/commit/mergegit worktree removeworktrunk prune扫描并清理无主/僵尸 worktreegit worktree prune增强版这些命令背后都封装了多个 Git 操作但接口层只暴露“任务”这个抽象不用让使用者关心底层分支和路径。尤其close命令它在真正合并前会先做一次git diff --name-only检查并把冲突文件列表打印出来。如果检测到两个 Agent 改了同一个文件Worktrunk 会默认停在“提交但暂不合并”的状态不会贸然执行 merge避免自动合并把 Agent 的改动静默覆盖。2.3 为什么是 CLI而不是插件或守护进程我在开发初期认真考虑过做成 VS Code 插件或后台守护进程后来全否了。原因有三个。第一AI Agent 本身跑在终端里。Claude Code、Codex CLI 这一类工具的启动方式就是在终端里执行命令它们天然能理解和调用 CLI。如果做成 GUI 插件Agent 根本没有入口去触发它。CLI 是 Agent 和操作系统之间最通用的“按键”它的标准输入输出、退出码、JSON 输出格式都可以被 Agent 或脚本直接消费。第二CLI 可编程性最好。给worktrunk list加上--json输出任何语言、任何 CI 流水线、任何 Agent 都能解析它。这比守护进程的 RPC 接口简单可靠得多也容易调试。第三生命周期短。一个 Agent 任务的典型执行时间是几分钟到几十分钟不是需要长期驻留的服务。CLI 这种“用完即走”的模式更贴合场景没有额外进程没有端口占用也没有状态同步问题。守护进程适合高频率、细粒度的调度但我们这个场景里命令调用频率很低CLI 的启动开销完全可以忽略。3. 从零实操把 Worktrunk 跑起来3.1 安装与初始化Worktrunk 对运行环境的要求很少Git 2.30 以上、任意支持 Python 3.9 的机器或者直接使用预编译的二进制我把 CLI 编译成了单一可执行文件方便塞进 CI 镜像。安装方式习惯用包管理器或者直接下载 release 二进制curl -fsSL https://platform.example.com/worktrunk/install.sh | bash worktrunk --version我实际更推荐手动下载二进制放到/usr/local/bin避免管道执行远程脚本带来的安全风险。初始化前先确认你当前仓库没有未提交的改动因为初始化本身不会动你的工作目录但后续创建的 worktree 都会基于当前分支的 HEAD。建议你先切到一个稳定的 long-lived 基线分支main或者developgit status --porcelain git checkout main worktrunk init --workroot .wt --base main执行后Worktrunk 会做三件事在仓库根目录创建.wt/目录并在.gitignore里追加忽略规则如果原先没有该目录。生成.worktrunk/config.toml记录默认基线分支、worktree 根目录、分支命名规则。验证 Git 版本和当前仓库是否具备多 worktree 条件。配置文件的初始内容类似这样[core] workroot .wt base_branch main branch_pattern agent/{task_id} [cleanup] auto_prune true stale_after_minutes 240stale_after_minutes是我很看重的一个参数如果一个 worktree 超过 4 小时没有新的提交执行prune时就会把这个工作区标记为“疑似僵尸”并提示是否清理。这个阈值根据你的 Agent 任务时长调整短任务可以压到 60 分钟长任务可以放宽到一整天。3.2 创建你的第一个 Agent 工作区初始化完成后创建一个任务工作区的命令是worktrunk start -n agent-a -b feature/agent-a-login-refactor --base main我来逐步解释这条命令背后发生了什么Worktrunk 在.wt/下创建目录agent-a-login-refactor。基于main的当前 HEAD 创建并检出分支feature/agent-a-login-refactor用的是git worktree add因此主工作区的分支不受影响。在状态索引里写入一条 active 记录。输出一段可直接执行的启动提示默认是cd .wt/agent-a-login-refactor。如果仓库比较大首次创建 worktree 会花一些时间因为 Git 需要把文件 checkout 到新目录。对于 200MB 左右的中型仓库比如我们团队的项目首次创建耗时大约 20 到 40 秒后续再创建新的 worktree 会快很多因为对象库是共享的只是重新生成工作目录文件。创建完成后用git worktree list能看到类似输出/path/to/main main /path/to/.wt/agent-a feature/agent-a-login-refactor注意主工作区和 worktree 是平级的指向不同分支完全隔离。3.3 查看、切换与状态同步当多个 Agent 并行运行时你最需要的是一个能一眼看全局的命令。worktrunk list的输出设计成表格形式$ worktrunk list TASK_ID PATH BRANCH STATUS UNCOMMITTED agent-a-login-refactor .wt/agent-a-login-refactor feature/agent-a-login-refactor active 3 agent-b-tests .wt/agent-b-tests feature/agent-b-tests active 0 agent-c-docs .wt/agent-c-docs feature/agent-c-docs commit-pending 2 agent-d-orphan .wt/agent-d-orphan feature/agent-d-orphan orphan 5UNCOMMITTED列统计的是该 worktree 里未提交的改动文件数它能帮你判断 Agent 是不是卡住了。如果一个 worktree 长时间 active 且 uncommitted 数量没有变化大概率是 Agent 已经挂掉或者失去了上下文。手动进入某个工作区很简单cd $(worktrunk use agent-a-login-refactor)如果你用的是 bash我建议在.bashrc里加一个 shell hooksource (worktrunk hook bash)之后可以直接用wtuse agent-a-login-refactor切换目录它会自动cd到对应 worktree 并把GIT_DIR环境变量修正到正确位置避免在子 shell 里 Git 命令误操作了错误的仓库。这个体验对频繁在多个 Agent 工作区之间“人肉巡检”时特别重要。3.4 任务收尾提交、合并、清理Agent 干完活之后不能直接把它所在的工作区删掉就完事。Worktrunk 的close命令把收尾流程做成了带检查的多步操作worktrunk close agent-a-login-refactor --commit --merge --delete-branch执行过程如下在对应 worktree 里执行git status --porcelain检查是否有未提交改动。如果存在未提交改动且指定了--commit用预置的提交信息默认包含 task_id创建一次提交。切回主工作区执行git merge feature/agent-a-login-refactor先尝试 fast-forward如果基线分支有新的提交则用--no-ff合并。合并发生冲突时立即停止并输出冲突文件列表绝不自动选择一方覆盖。git worktree remove移除该工作区随后删除临时分支。更新状态索引把记录标记为closed。我在一开始使用close时吃过一次亏两个 Agent 分别改了同一个模块的测试文件merge时出现冲突但第一个 Agent 的分支被--delete-branch强制删除了导致它的提交只能通过 reflog 找回。后来我在实现里加了一条硬性规则如果合并未成功任何清理动作都不执行分支必须保留。这也是为什么close命令在实际使用中会比一条git worktree remove“重”很多的原因。它真正要做的是让任务收尾变成可审计、可回滚的流程而不是直接做手术。另外prune命令可以处理那些 Agent 中途崩溃后留下的僵尸工作区。它不是简单执行git worktree prune而是先根据状态索引里的记录逐项检查worktree 目录是否还存在对应分支是否还存在最后一次提交时间是否超过了stale_after_minutes如果有未提交改动是否要备份到一个stash或直接丢弃确认这些信息后它会列出一份清理建议清单让你在真正动手前再看一眼。我强烈建议任何自动清理动作在执行前都要有“人肉确认”的台阶尤其是面对 AI Agent 产生的脏数据时它们经常会在很诡异的位置留下你没见过的文件。4. 常见坑与排查技巧实录4.1 untracked 文件和新文件互相遮挡多 Agent 并行、基于同一基线分支工作时最容易踩的第一个坑是两个 worktree 各自新建了同名文件但合并时 Git 不会报冲突因为文件本来就是新增的。比如 Agent A 在feature/agent-a创建了internal/repo/config.goAgent B 在feature/agent-b也创建了internal/repo/config.go两者内容完全不一样。合并时 Git 会把两个文件都保留但最终目录里只有一个config.go另一个分支的内容被静默丢弃。这在实际中非常危险因为 Agent 往往不会只改配置它还会引用这个配置里的字段。合并后代码可能编译失败而且失败信息跟这个文件根本不相关排查起来费时。Worktrunk 在close命令里做了一个额外检查合并前扫描两个分支相对于基线的“新增文件集合”如果存在同名文件但内容不同直接视为预冲突终止合并并打印文件路径。这比等到真正 merge 时自动决定胜负要安全得多。4.2 同一个分支被多个 worktree 检出的问题Git Worktree 有一个天然限制同一个分支只能被一个 worktree 检出。如果你尝试在两个 worktree 里分别 checkout 同一个分支Git 会报fatal: main is already checked out at /path/to/other/worktree那么什么时候会触发这个问题最常见的是你手动创建 worktree 时没有指定独立分支而是直接git worktree add ../wt/test main这样它与主工作区冲突了。另一个场景是某个 Agent 在 worktree 里执行了git checkout main把它的当前分支切回了基线分支于是和其他 agent 冲突。Worktrunk 对此做了两层防护创建时强制为每个任务生成独立分支名每条命令在执行前都会检查 worktree 当前 HEAD 是否等于预期分支如果发现 Agent 擅自切了分支会立即报警并给出修正命令git -C .wt/agent-a-login-refactor checkout feature/agent-a-login-refactor不要小看这个检查。Agent 有时候会自作聪明地“帮”你切分支、拉远程、git reset --hard把好端端的 worktree 弄到不可预期的状态。一个能随时校验 worktree 状态是否匹配预期的工具能帮你省下大量排查时间。4.3 进程内工作目录被删导致的 GC 失败有段时间我发现prune偶尔会报错提示git gc无法完成。定位后发现是某个 Agent 的进程还驻留在那个 worktree 目录里但该目录已经被worktrunk remove删掉了。这个进程持有旧的目录 inodeGit 在做对象清理时扫描到这类误操作记录就会跳过某些对象。这种问题在纯人工操作时几乎不会遇到但 AI Agent 是“跑完也不自杀”的它的 Python 进程可能会挂在那里等待输出流。所以我在这块加了顺序控制先查一下该 worktree 是否有活动进程通过lsof D或检查 shell 进程树的子进程如果有就拒绝删除警告你手动确认后再执行。如果你在真实环境中遇到类似问题最简单的排查方式是先杀掉相关进程再执行git worktree prune git gc --prunenow正常情况下能恢复干净状态。如果git worktree list里出现了prunable标记用git worktree remove --force清理即可。但要注意--force会丢弃该 worktree 里的所有未提交改动用之前一定要确认里面的改动已经要么提交、要么不需要。4.4 worktree 数量膨胀与磁盘占用失控和 Agent 并行跑时间久了最常见的现象是.wt/下面挂着几十个目录。每个目录都是仓库的一次完整 checkout对中型仓库来说每个可能占 100MB 到 1GB 不等。如果不及时清理磁盘会肉眼可见地吃紧。Worktrunk 在list里专门加了磁盘占用统计来源其实很简单对每个工作区执行du -sh。发布时很多人反馈说这不优雅但我还是保留了因为大多数人在日常操作中根本不会主动去看目录大小直到磁盘报警才想起来。关于并行度我实测下来的经验值一个 200MB 左右的仓库同时维持 4 到 6 个活跃 Agent 工作区是比较健康的超过 8 个后磁盘和 IO 压力会明显上升CI 如果还在跑构建时长会跟着膨胀。所以init时建议设置一个max_worktrees参数比如 8超出后start会直接拒绝并提示你先close掉至少一个任务。这个限制没什么高深原理它就是阻止你把资源浪费在没有收敛的任务上。4.5 常见问题速查表现象排查方向解决建议start报错分支已存在是否上次任务没清理干净git branch -D后重试或换一个新 task_idAgent 在 worktree 里执行git push失败该分支可能尚未设置 upstreamgit push -u origin branch即可Worktrunk 也可在 start 时预置list显示状态和实际不一致状态索引文件被误改或 Agent 直接操作了 worktree用worktrunk sync --from-git重建索引close --merge卡住不动合并冲突或预提交钩子阻塞先看git status处理冲突后重试主工作区也被 Agent 动了有人没走 Worktrunk 直接在主目录跑 Agent禁用主工作区写入用分支保护策略兜底5. 进阶实践把 Worktrunk 放进真实 Agent 工作流5.1 配合 Claude Code / Codex CLI 的启动模板Worktrunk 最常见的用法不是人手工跑而是让 Agent 启动器来调用。以 Claude Code 这类交互式 CLI 为例你可以写一个启动模板TASK_IDtask-$(date %s) worktrunk start -n claude-agent -b feature/claude-$TASK_ID --base main WORKDIR$(worktrunk use claude-agent) cd $WORKDIR claude --allowedTools Read,Write,Edit,ExecuteCommand --resume task context here执行完这个脚本Agent 进入的是一个干净、隔离且具有完整任务上下文的工作目录。它在这个目录里的任何操作不会影响主仓库或其它 Agent。Codex CLI 也一样关键在于Worktrunk 负责的是“给 Agent 准备好一个不被污染的战场”而不是代跑 Agent 任务。两者的职责边界非常清晰。5.2 并行度估算一个仓库撑得起多少个 Agent关于并行度我按实际经验给一个比较保守的算法。核心成本不是 CPU 或内存而是磁盘 IO 和 checkout 时间。对于大多数代码仓库一次git worktree add的耗时约等于一次git checkout全量写文件的时间。如果仓库有 5 万个小文件一个 worktree 创建就要几十秒连续创建 6 个总耗时就接近 3 分钟。我建议用下面这个公式做预估算仓库源文件总大小 * 预期并行 worktree 数 * 1.2 倍冗余 最低可用磁盘空间预留比如仓库.git对象库 2GB实际 checkout 出来 300MB跑 6 个 worktree那额外工作目录就是 1.8GB 左右。看着不多但实际上每个 worktree 里还会生成 Agent 的日志、缓存、虚拟环境所以我把冗余系数拉到 1.5 到 2 比较稳。如果确实需要跑超过 10 个独立任务我不建议都在同一仓库上做 worktree而是考虑把这批仓库拆成小块或者让 Agent 任务按队列顺序执行。并行不是越多越好IO 争抢导致的等待时间反而会让总吞吐下降。5.3 context 与任务交接每个 Agent 进来就知道该干嘛把 worktree 准备好只是解决了隔离问题让 Agent 高效工作还依赖上下文。Worktrunk 在start时支持一个--context参数它会把你指定的文件复制到 worktree 的.wt/context.md位置相当于给 Agent 留了一份“工单说明”。我通常会在 context 里写清楚这些内容任务目标用自然语言描述最终要交付什么。验收标准哪些测试要跑过哪些文件不能动。禁止事项比如不要改公共接口签名、不要删除未跟踪文件。参考文件列表相关模块的路径减少 Agent 浪费在代码搜索上的时间。任务结束后这份 context 文件会作为提交信息的一部分归档后续人肉 review 或者另一个 Agent 接手时能快速理解之前发生了什么。这一点在长时间、多轮迭代中特别有价值。Agent 没有记忆它们的记忆完全靠文件你提前把关键信息写进文件就等于给它装了一个外置记忆。5.4 后续扩展方向MCP、远程作业与调度Worktrunk 目前的核心能力还是“本地单机管理”。如果要更进一步我有两个方向上已经在做供你参考。一是接入 MCPModel Context Protocol。如果 Worktrunk 暴露一个 MCP serverAgent 就可以直接通过工具调用创建/关闭 worktree而不需要启动器去手动拼命令。对 Agent 来说这意味着它可以在工作过程中动态决定“我需要一个新的试验分支来验证方案”然后自己创建、自己清理整个流程完全自主。这在复杂任务拆解时非常有用。二是支持远程作业队列。把 worktrunk 的命令套上一层任务队列服务就可以实现“多个 Agent 候选任务按优先级排队后台调度器逐个分配 worktree 并运行”。本质上是一个轻量的 Agent 作业调度器。CLI 的设计天然适合这种外接扩展因为它所有命令都支持--json输出退出码和标准错误都定义得很明确作为上层系统的原语完全没有隔阂。我个人在实际项目里体会最深的一点是AI Agent 工作流能不能稳定十有八九取决于你能不能在“隔离”和“收敛”上做功夫。隔离靠 worktree收敛靠合并流程和清理策略。Worktrunk 把这两条线串成了一套人人都能上手的 CLI它的价值不在于实现了多么复杂的算法而在于让并行 Agent 这件事从“手忙脚乱”变成了“有章可循”。如果你也在用多个 AI Agent 同时干活不妨从worktrunk start开始给每个 Agent 一个独立的家你会发现整个世界清静很多。