资讯动态

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

发布时间:2026/9/20 14:52:10 来源:尧图企业网站定制
上个月我在同一个仓库里同时跑了三个编码 agent 会话一个让 Codex CLI 改权限校验一个让 Claude Code 重构数据库访问层还有一个用 Gemini CLI 在补测试用例。第一轮执行完我就发现事情不对——agent A 的提交里混进了 agent B 改过的文件agent C 想切换到自己那个分支时被工作区状态卡住整个目录乱成一锅粥。那天我被迫干了一下午手动 rebase 和分支打捞也正是从那时候开始我决定写一个专门管理并行 AI Agent 工作流的 Git Worktree CLI名字叫 Worktrunk。简单说Worktrunk 做的是这么一件事把创建独立工作区、拉出独立分支、启动 agent 任务、暂停恢复、清理回收这一整套操作压缩成几条稳定的命令让每个 agent 都在自己的目录和分支里跑互不污染、互相不阻塞。这个工具最适合已经用 Codex CLI、Claude Code、Gemini CLI 这类终端型 AI 编程工具的开发者尤其是那种一个仓库同时推进多个任务的高并发场景。文章里我会把它为什么值得做、内部怎么设计的、实际跑起来有哪些坑全部摊开讲一遍。1. 三个 Agent 同时开工之后我才决定写这个工具1.1 并行 Agent 的真实工作场景先说场景。我手上有一个中型的服务端仓库日常需求大概分成三类改业务逻辑、重构基础设施、补测试和修 lint。以前我都是自己手动切分支一个个来但自从习惯了终端型 AI 编程工具之后我发现这些 agent 单线程处理一个任务的时候效果很好可一旦我开了多个会话、想并行推进多个任务麻烦立刻出现。典型操作是这样的我打开终端 A进入项目目录跑codex让它修复用户登录接口里越权校验的问题再打开终端 B同一份目录跑claude让它把数据访问层从直接 JDBC 迁移到 MyBatis终端 C 我可能还会开一个gemini去补充订单模块的单元测试。三个会话都眼巴巴等着操作同一个工作区、同一个 main 分支。这听起来像是给自己找麻烦但它确实是效率驱动的本能行为——既然一个 agent 独立干一个任务没问题那三个 agent 并行干三个任务理论上效率就是三倍。问题在于这个理想要成立需要扎实的隔离机制做前提。1.2 没有管理工作区的乱象不隔离的直接后果我逐条列给你看。首先是工作区污染。Agent B 在改数据库访问层的时候会顺手格式化文件、给公共类加 import、甚至调整某个工具方法。它这些改动和 agent A 在权限校验上的改动全部落在同一个工作目录、同一个分支里。等 A 提交的时候git diff里混进去一堆 B 的改动更麻烦的是如果 B 先提交了A 再想提交就会面临大量冲突。其次是分支混乱。三个 agent 如果我都让它们先建自己的分支再干活那 B、C 都从 main 拉分支倒是能独立提交了。但底层还是有共享工作区的问题——git 同一时刻只允许一个分支 checkout 到工作区B 切到自己的分支时A 正在修改的文件会被强制覆盖拦截git 会报Your local changes would be overwritten然后整个流程卡住。那时候我得在不同终端之间反复确认谁改了哪个文件、谁还没提交消耗的时间比我手动写代码还多。第三是上下文污染。终端型 agent 的一个特点就是会读取当前目录及其子目录里的代码文件作为上下文分析当前改了哪些文件、git 状态是什么。当多个任务挤在同一个目录时agent A 会错误地看到 agent B 改到一半的文件它可能据此做出这块已经有人在改了的判断从而放弃对同一段代码的处理或者更糟——直接沿用 B 的半成品设计。这情况下 agent 的推理质量会显著下降因为它根本不知道该相信哪份状态。1.3 为什么 Git Worktree 是正确答案我第一反应是想办法干跑多个仓库副本也就是git clone好几份。但 clone 的问题也很明显每个副本都要从远端把对象库拉一遍磁盘占用大本地想提交然后测试跨模块集成时还得手动把几个副本 push 到各自远端分支再互相 merge非常绕。后来我意识到git worktree才是正宗解法。Worktree 允许你在同一个仓库里挂载出多个工作目录每个目录都能 checkout 不同的分支彼此共享 .git 对象库底层提交对象只有一份不会重复占磁盘。它天生是给一个仓库多用并行准备的。举个例子你可以在仓库根目录执行git worktree add ../myapp-task-auth -b task/auth-refactor git worktree add ../myapp-task-dal -b task/dal-mybatis执行完之后你会有两个新目录一个跑在 task/auth-refactor 分支上一个跑在 task/dal-mybatis 分支上两边都指向同样的对象库。然后我就可以开三个终端分别进三个目录各跑各的 agent。提交、分支、工作区状态彼此完全隔离没有一个操作能串到其他目录去。这里有个细节值得展开在附加 worktree 里.git不是一个目录而是一个普通文本文件内容指向仓库主工作区的.git/worktrees/name目录那里记录了 HEAD、索引路径等元信息。这个机制保证了系统层面就能区分哪个 worktree 对应哪个分支比起自己用环境变量和目录名做软隔离要可靠得多。所以方向定了工作隔离这件事由 Git Worktree 原生解决但围绕 worktree 的繁琐管理需要一层封装。这层封装就是 Worktrunk。2. Worktrunk 解决的问题把零散 Git 操作收敛成一条命令2.1 工作区生命周期从创建到回收Git Worktree 本身不复杂复杂的是使用流程里的决策点分支名怎么起目录放哪里任务做完怎么把分支 merge 回 main任务做一半想切换到另一个任务原先那个目录留着还是清掉这些琐碎的判断叠加起来就会让并行工作迟迟无法落地。Worktrunk 把整个生命周期压缩成四个核心动作我用一张表给你看命令行为背后做了什么事worktrunk create task为某个任务创建工作区创建独立目录、拉新分支、写入任务元信息、生成 agent 上下文文件worktrunk list查看所有任务状态汇总每个任务的分支、目录、最近活跃时间、提交数、未提交改动数worktrunk enter task进入某个任务工作区打印目录路径、把当前 shell 切换到对应上下文如果是支持的 shell 环境worktrunk drop task完成或放弃任务检查脏文件并提示处理、把分支 merge 回主干可选、删除 worktree 并对元信息进行清理具体的创建命令长这样worktrunk create auth-refactor它会自动在约定的根目录下建一个worktrees/auth-refactor文件夹从 main 拉出名为task/auth-refactor的分支并且在目录里生成一份.agent-context.md文件里面写了任务名、创建时间、当前分支、可以执行的命令摘要。这样 agent 一进目录就知道自己要干什么不用我额外在提示里反复带上下文。2.2 目录即上下文让每个 Agent 只看见自己的世界我在设计 Worktrunk 时最坚持的一个理念就是目录即上下文。对终端型 AI 编程工具来说当前工作目录是一个强上下文来源——它决定了 agent 能读到哪些文件、git 状态指代的是哪套流水线、文件变更影响范围是多大。假如同一个 agent session 能看见两个任务的文件变动它的推理就已经被污染了。所以 Worktrunk 的所有设计都围绕一个目标让每个 agent 终于只能看见自己那份世界。创建任务工作区之后agent 就在这个目录里看到干净的分支和干净的文件状态它 commit 也是提交到自己的 task 分支它的 diff 只需要面向自己负责的那块代码。这和在 IDE 里打开多个项目窗口是类似的体验但不同的是目录内部是同一个仓库的同一套对象库所以你在任意工作区里提交的 commit其他 worktree 立刻能看到。合并的时候也不会出现跨仓库找不到对象的情况。还有一个实际体验上的细节大多数 agent CLI 工具会在启动时扫描目录里的说明文件。Worktrunk 创建的.agent-context.md本质上就是给 agent 看的任务书。你可以在 create 之后追加细节比如只允许改动 service 包、完成任务后运行mvn test -DtestAuthTest等等。这样即便开了五个会话每个会话读到的都是自己那份任务书不会串味。2.3 状态可见性并行不再是一个黑盒并行任务最怕的就是不知道现在谁干到哪一步了。之前我纯手动管理的时候想搞清楚每个分支的状态要反复执行git branch -vv、git log --oneline、git worktree list再自己脑内关联哪个分支对应哪个任务哪个分支已经落后于 main 多少 commit。Worktrunk 的 list 命令把这种脑内台帐变成了一屏输出TASK BRANCH DIR AGE COMMITS DIRTY auth-refactor task/auth-refactor worktrees/auth-refactor 2h 3 1 dal-mybatis task/dal-mybatis worktrees/dal-mybatis 5h 7 0 order-tests task/order-tests worktrees/order-tests 1d 4 2Dirty 列会提醒你哪个 agent 的工作区还有未提交改动AGE 列让你一眼看出哪个任务已经挂太久该催一下了。这个表的意义不在于省了敲命令的时间而在于它把并行推进多个任务这个抽象状态具象化了让我能快速决定下一个 agent 会话要开在哪个任务上。状态数据来自 Git 元信息、worktree 的 HEAD 指针和git status --porcelain的输出统计没有额外引入数据库后面我会讲为什么这样设计。3. 核心设计里的几个关键取舍3.1 为什么不用 clone、也不用纯 branch 切换我在第一节已经提到过当时在 clone 和 worktree 之间做过比较。这里再补充一个关于纯分支切换的对比很多没有接触过 worktree 的开发者第一反应是那我分别在 main 上建两个分支不就行了切换过去再干活。问题在于当你只有一个工作目录时切换分支是一个独占操作。A agent 在终端 A 里改到一半你不可能跑到终端 B 里git checkout task/dal-mybatis否则终端 A 里那个还没提交的 agent 会当场崩溃。这本质上和你在同一块白板上同时让两个人画画没有区别一个人画的时候另一个人必须停笔。所以分支切换只适合串行做任务不适合并行做任务。Clone 则走向另一个极端隔离彻底但共享缺失。每个 clone 都是独立对象库你在 clone B 里基于某个新提交修改clone A 根本看不到跨副本合并要经过 push/pull 甚至要配置 remote。对多 agent 并行来说每个任务的工作流应该是独立提交、但对象可被主仓库直接复用这个需求只有 worktree 能满足。另外还有磁盘和 clone 时间的考量。Worktree add 一个分支基本是毫秒级clone 一个大仓库可能要拉几百 MB 对象。AI Agent 任务通常以小时计但每次创建任务都重新 clone 仓库完全没必要。3.2 分支与目录的命名约定约定优于配置很多个人工具会走到另一个极端为了灵活全部做成可配置项。分支前缀、目录名、放置位置都让你在 yaml 里填。我的看法是CLI 工具在够用的情况下应该强约定把用户的决策负担降到最低。我在 Worktrunk 里定的约定是所有任务工作区统一放在仓库根目录下的worktrees/文件夹里分支名统一为task/task-nametask-name 里的小写、连字符形态就是目录名每个任务工作区都包含一份.agent-context.md这套约定的好处是无论你过了三天还是三个月回来worktrunk list、git worktree list、ls worktrees/的输出永远是可以互相对照的不会出现目录叫 a分支却叫 b真身还不一定在哪这种玄学状态。为什么这个约定对 agent 工作流尤其重要因为 agent 的行为就是基于观察到的目录结构和 git 状态来推断的。如果它在一个分支叫task/dal-mybatis、目录名也是worktrees/dal-mybatis的上下文里工作那么它的后续命令自动会围绕 dal 迁移展开不会跑偏。如果你是目录 a、分支 b、worktree 元信息 c这种随意组织agent 很容易把某个错误路径当真。刚开始我确实设计过允许自定义 worktree 存放目录的配置项后来删掉了。理由是这个工具有一个默认假设——所有任务工作区都存在于单个仓库的同一棵目录树下这个假设才是并行管理系统可靠工作的基础。配置项越多假设越容易被破坏。3.3 与 Agent CLI 的衔接方式Worktrunk 本身不是一个 agent 工具它不负责和 LLM 对话它只负责给 agent 提供一个干净的工位。所以与 Codex CLI、Claude Code 这类工具的衔接主要靠三个层面。第一层面是工作目录。创建任务后worktrunk enter task会输出并指向该任务的目录路径。正常情况下你在新终端里 cd 进去再启动 agent 就行。我最近版本里加入了 output 里直接打印建议命令比如cd worktrees/auth-refactor codex减少记忆成本。第二层面是上下文文件。上面提到的.agent-context.md会让很多 agent 自动读取。Codex CLI 有类似文件机制Claude Code 也支持 CLAUDE.mdGemini CLI 同样有一套说明文件约定。我做的是一份抽象文件由 Worktrunk 在 create 时生成你在文件里写清楚任务目标和约束不管哪家 agent 进来都能看懂。第三层面是环境变量。Worktrunk 在 create 时会向当前 shell 导出几个变量比如WORKTRUNK_TASK、WORKTRUNK_DIR、WORKTRUNK_BRANCH。这样你可以写一些自定义 wrapper 脚本在启动 agent 时把这些变量注入 prompt或者把任务名写入日志、监控系统。它不强制你用这些变量但留了扩展口。4. 实现中真正要处理好的边界问题4.1 任务元信息存放在哪里Worktrunk 需要长期跟踪每个任务的状态这个信息放哪我纠结过一段时间。最初版本我把元信息直接塞在 worktree 目录下一个.worktrunk/meta.json文件里。它的好处是跟着任务目录走不会被 git 影响天然隔离。坏处是一旦这个文件被 agent 顺手改坏或者被 IDE 格式化整个任务的状态就断链了而 agent 恰好是很喜欢顺手写东西的执行者这个风险很大。后来我改成把元信息放在 git config 里。对就是仓库本地.git/config里的一组自定义配置段git config worktrunk.task.auth-refactor.dir worktrees/auth-refactor git config worktrunk.task.auth-refactor.branch task/auth-refactor为什么用 git config因为它和仓库强绑定且不会被目录内文件操作误删同一份对象库在多个 worktree 间切换时配置文件是共享的天然支持全局状态读取想导出备份也很容易直接复制这一段即可。list命令的输出就是扫描 git config 里的 worktrunk 段再加上对每个工作区的实时 git 查询生成的。读取时不会因为某条数据缺失就让整个命令失败缺一条就显示 UNKNOWN把容错往前置。4.2 未提交改动与 drop 保护给 agent 用的时候最大的不确定性就是它改了一半就走了。你可能早上让它开始写一个功能到晚上才发现它停在某个中间态工作区里一堆未提交的文件。这时候如果手滑执行 drop 指令把 worktree 删了后果是灾难性的——未提交的工作可能找不回来。我在设计 drop 时加了几层保护执行 drop 前先做git status --porcelain检查有改动就直接中断把文件列表打印出来提供worktrunk drop --force参数但仍要求在 force 之外再带--yes确认避免肌肉记忆式误操作提供一把留底机制drop 前可以自动打一个 WIP commit 推到本地一个wip/task分支这样文件不会丢后续想恢复还能从分支里找。这些保护对普通项目而言是贴心对 agent 驱动的工作流而言就是保命。因为你根本不知道 agent 中途做了哪些你没在盯的操作自己也未必记得每个任务改过什么不留底的删除和赌博没区别。4.3 Worktree 清理与 Git 的 GC 行为Git 的 worktree 删除有个特点它要求对应的工作目录必须是干净的否则拒绝 remove。而 agent 项目恰恰经常不干净于是会出现明明想清理一个任务但一直清不掉的烦恼。我之前踩过一次坑使用git worktree prune之后以为 worktree 就没了结果只是清掉了那些元数据指向不存在的目录的残留信息。整个分支和提交对象还留在对象库里。对这种场景Worktrunk 在 drop 流程里先尝试常规的git worktree remove path失败则提示用户是否残留对真正废弃的任务强制 remove 后还要顺手跑一次git worktree prune和引用清理避免.git/worktrees目录里堆积僵尸文件。另外提一句与 GC 的关系一个任务分支如果长期没有合并回 main它引用的所有提交对象都会被保留如果你的仓库管理松散worktree 数量一多对象库膨胀几乎是必然的。Worktrunk 目前的策略是提示为主——drop 时默认建议把分支 merge 回 main或者给出 push 远端分支的命令。真正做对象仓库压缩的工具应该是git gc层面的事情CLI 管到这个深度反而容易误伤。4.4 多终端并发执行时的锁问题这个坑我是后来才意识到的。Worktrunk 自己也是终端命令如果我在终端 A 和终端 B 同时执行worktrunk create两个进程可能同时读 git config、同时写文件轻则其中一个任务创建失败重则把 config 文件写坏。解决方式比较朴素在仓库根目录放一个.worktrunk.lock文件创建时用O_EXCL原子创建标志拿到锁之后才允许继续执行写操作执行完再释放。这样虽然不能解决 agent 自身对文件的抢占问题那是代码仓库层面的问题但至少保证 Worktrunk 自己的元数据不会因为并发执行而损坏。这个锁很小但它是把 Worktrunk 从玩具脚本推向可依赖工具的重要一步。因为并行工作流的第一前提就是管理工具自身必须能安全并发。5. 和手动命令、自写脚本放在一起比一比5.1 手动操作到底多费劲我统计过我日常手动管理三个 agent 任务时每个任务周期大概要执行这些 Git 操作环节手动操作需要的命令数创建任务git worktree add -b ...、cd、写上下文文件3查看状态git worktree list、git branch -vv、分别进目录git status至少 3暂停/切换确认干净、cd、启动另一个 agent2完成清理合并分支、删除 worktree、push 远端、手动清理 config4算下来三个任务并行一个周期我少说要敲 30 条命令其中有一半是机械性的确认动作。这在串行时无所谓一旦思绪被打断切到任务 D 就忘了任务 A 进行到哪又要重新开始看状态。用 Worktrunk 之后操作收敛成一条命令而且 list 的输出就是总状态不会因为漏看某个目录得出错误结论。5.2 为什么纯 Shell 脚本不够如果你懂一点 Shell很容易想到这些东西不就是一个 50 行的 bash 脚本吗我承认核心逻辑确实不复杂但纯脚本在三个地方撑不住一是错误处理。Git 命令的失败原因千奇百怪分支已存在、worktree 路径冲突、工作区不干净、远端拒绝推送。脚本里如果每个分支都做完整错误分支代码量翻好几倍不做出现错误时留着半坏状态更难收拾。Worktrunk 里每条写操作命令都做了输出异常码、回滚和提示这在一个一次性脚本里基本不会有人认真做。二是跨平台。git worktree后端是一致的但路径分隔符、shell 输出格式、颜色语义在 Windows 和 macOS 下都有差异。Go 编译成单二进制后这些问题在语言层面统一处理了。三是可扩展性。脚本的 x 功能写好后你想加 list 表格输出、想加 json 输出、想接 IDE 插件都得从头重构。CLI 命令之间通过标准化和稳定的元数据沟通扩展只是加一个子命令的事。5.3 已有 Git 原语和 Worktrunk 的分工需要强调一点Worktrunk 不是重新实现 Git也不可能替代git worktree。它做的是封装和策略层Git 提供可以创建多个工作树的原语Worktrunk 提供何时创建、放在哪、叫什么名、何时回收、怎么和 agent 衔接的编排逻辑。这和 Git 本身的设计哲学不冲突。Git 希望被当作一个可编程的、稳定的底层工具用而 Worktrunk 面向的是消费 Git 更多工作流语义、但不想被原生命令打断思路的 AI 编程使用者。6. 实际使用过程中的注意事项与边界6.1 每个 Agent 会话要独立维护自己的记忆这里有一个很容易被忽略的点虽然 worktree 目录隔离了代码文件但 agent 工具本身的会话历史、配置文件通常是存放在用户主目录下的和项目目录无关。比如 Codex CLI 的会话历史、Claude Code 的记忆文件默认都不在仓库内。这意味着即便代码层面完全隔离agent 的记忆如果共享它依然可能把任务 A 的上下文带到任务 B 里。我的建议是在启动不同任务的 agent 前要么用工具自带的 profile/project 机制区分要么干脆用不同的配置目录。Worktrunk 目前提供了一个小 hookworktrunk run --task ... -- command可以在启动命令时自动绑定当前任务的环境变量但这只起提示作用真正的隔离还得靠 agent 工具自己的会话管理。6.2 多 Worktree 的磁盘与性能影响虽然 worktree 共享对象库不会每个目录都存一份完整 .git但要注意每个 worktree 的 working tree 文件是实打实的独立副本。如果仓库里有几千个大文件创建 10 个 worktree 就会产生 10 份工作目录副本磁盘占用仍是可观的。另外索引文件也是每个 worktree 一份执行大量文件操作比如 agent 全局替换时每个 worktree 的git status扫描都会有自己的成本。实测下来20 个左右 worktree 以内对日常操作影响不大但超过这个量之后建议用git worktree list检查有没有已经合并完、但一直没清理的僵尸任务及时回收。通常我会给自己定一条纪律最多同时保持 4-5 个活跃任务剩下都尽快合并回 main。这样避免管理成本超过收益。6.3 什么时候不该用这个工作流Worktrunk 不是所有并行场景的银弹。有这么些情况我建议你别硬套改动只涉及一两个文件的小任务比如修个配置、改个文案。这种任务用一个目录开个分支就够建 worktree 反而增加切换成本。多个 agent 之间的任务强耦合、必须同步修改同一批文件。例如你让 agent A 重构类结构、agent B 同时改调用方的参数这两个任务底层改的是同一块代码再多的 worktree 隔离也解决不了语义冲突。这种场景应该串行或合成一个任务。团队协作里如果大家共用远端分支并且强制要求所有提交必须直接推到同一个长期分支上那么 worktree 的隔离优势会被流程规则抵消反而因为合并成本增高而变慢。我会把这些边界写进工具的使用文档里不是为了自我设限而是因为用错工作流造成的损耗通常比工具本身带来的收益更大。工具应该服务场景而不是制造新场景。7. 还可以往哪些方向扩展7.1 与任务看板、Issue 联动目前 Worktrunk 的任务元信息是本地 git config没有和 GitHub Issues、Jira 这类系统打通。我接下来的计划是让它支持从某个 Issue/工单 ID 直接创建工作区同时把 worktree 分支的提交记录回传到任务里这样人在看板上的状态和分支实际进度就不会脱节。可以想象一个场景worktrunk create --issue 472自动读取 issue 标题作为任务名在.agent-context.md里写清楚需求描述和验收标准agent 一进来就有明确目标。任务完成后worktrunk finish还会自动把提交信息关联到 issue 时间线。这对多 agent 流水线式开发会很有价值。7.2 与 IDE 和编译缓存的集成另一个我观察到的问题是每个 worktree 的构建缓存是独立的。前端项目开几个 worktree每个都要重新 npm install、重新跑 dev serverJava 项目更是每个 worktree 一份 target 目录。能不能在创建任务时复用主工作区的依赖缓存、或者至少给出合理的指向这个方向在体验上提升会非常明显。Worktrunk 同样可以在 create 时帮你在每个 agent 工作区里生成 IDE 的 workspace 配置比如 VS Code 的 multi-root workspace把每个 worktree 目录作为独立 folder 加入这样在 IDE 里也能一眼看到所有任务的状态。7.3 并行验证流水线还有个方向是集成验证即合并。每个 worktree 任务完成后可以让 Worktrunk 自动在隔离工作区里跑测试、lint、类型检查全部通过才允许 merge 回 main。这比merge 到 main 之后 CI 挂掉再回头修的反馈循环要短得多尤其适合 agent 生成的代码——你大概率不想让 agent 把半成品直接推到主干上让全组人看到。垂直上这只是把命令串起来的自动化但横向价值不小它让并行 agent 工作流的完整性从代码隔离延伸到质量准入。我在实际使用中最满意的一个设计是列表一屏看全所有任务被朋友拿去当成了他并行开发流水线的入口。这个项目的早期版本就是一堆随手 repository script后来才滚动成现在这个有命令、有约定、有边界判断的 CLI。如果你也碰过多 agent 并行写同一仓库导致焦头烂额的场面我的建议是先别急着上复杂平台用git worktree把目录隔离好再在这个基础上做一套顺手的管理命令大概率就能解决八九成问题。最后分享一个我在踩坑之后沉淀下来的小习惯每次给 agent 建任务之前我会先在 Worktrunk 的任务描述里写明验收命令比如完成后执行npm test -- --filterauth并确认 0 失败。这比在 prompt 里反复强调请仔细一点要有效得多。当工作区、分支、验收命令全部就位并行 agent 的产出质量会稳定很多也不会再有人在深夜里慌慌张张地修 merge 冲突了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价