
说实话我一开始没怎么把 Git Worktree 当回事。用了好多年 Gitbranch、rebase、cherry-pick 这些常用命令都玩得滚瓜烂熟worktree 偶尔在改文档的时候用一下感觉也就是个补充功能。直到我开始用 AI Agent 并行开发才第一次真正意识到——并行 AI Agent 工作流的最大瓶颈根本不是模型能力或者上下文窗口而是 Git 仓库的隔离问题。当我在同一个仓库里同时跑三四个 Agent 时每个 Agent 都在改文件、跑测试、提交 commit很快它们就会互相踩脚。传统的解决方案是手动开多个分支来回切但 Agent 进程并不像人那样懂得切分支前先 stash 一下它们只管按指令一个劲儿地改文件。改到一半切分支丢改动或者两个 Agent 改了同一个文件导致互相覆盖这类事故我几乎每周都能碰上。Worktrunk 就是在这种背景下产生的工具它把一个 Git 仓库要在多个 Agent 之间划成多个互不干扰的工作区这件事固化成了 CLI 能力。你只需要告诉它为 Agent-A 建一个基于 main 的独立工作区它就把路径、分支、任务状态全部给你管起来。这篇文章我从头讲一遍这个工具的设计思路、实操流程和我在日常使用中踩过的坑希望能帮到同样在跟并行 Agent 工作流搏斗的人。1. 并行 Agent 工作流里共享目录这个矛盾怎么解1.1 两个 Agent 同时跑在一个目录里会发生什么我最早尝试多 Agent 并行开发时用的是一个非常朴素的方案把任务拆开让两个 Agent 在同一台机器、同一个仓库目录下各自干活。听起来很合理对吧任务不同改的文件不同理论上谁也不会碰到谁。但实际跑起来根本不是这么回事。第一个问题出现在共享配置文件上。两个 Agent 开工后没多久都要装依赖于是它们几乎同时去读写package.json和package-lock.json。Agent A 刚加上一个依赖Agent B 紧接着把文件覆盖了。最终的结果是A 装的依赖丢了B 装的依赖也被 A 的版本冲掉了两边跑测试都报错。第二个问题是构建产物和缓存目录的互相污染。现代前端项目几乎都有dist、.next、node_modules这些目录Agent A 构建出来的产物会被 Agent B 的构建覆盖测试结果变得不可复现。更隐蔽的是有些工具会往node_modules/.cache里写缓存A 的缓存可能会让 B 拿到错误的编译结果反过来也一样。第三个问题是 Agent 进程的工作状态和文件系统状态脱节。Agent 不是人它不会在执行到一半时抬头看一眼咦文件怎么变了它读取文件、生成代码、执行命令完全基于它启动时看到的状态。一旦另一个 Agent 改了同一个文件它的认知就永久失真了后续的所有操作都可能基于一个已经过期的文件内容展开。1.2 分支切换不是答案为什么既然在一个目录里并行会乱很多人第一反应是开多条分支来回切不就行了这个思路本身没错但它在 Agent 场景下有一个致命的问题——切换分支的操作会打断正在运行的工作流。假设 Agent A 正在改登录页的样式改到一半还没提交这时候你想把分支切到 Agent B 的工作分支Git 会拒绝切换因为工作区有未提交的变更。你不能简单地把一个 Agent 正在写了一半的代码 stash 掉再让另一个 Agent 继续干因为被 stash 的上下文很可能再也回不来了。就算你能通过自动化脚本强制 stash 和切换Agent 在切走再切回之后它的运行时状态也已经变了。比如它之前在缓存里加载了某个模块的版本切分支之后这个模块的版本变了它再跑测试就会得到和之前完全不同的结果。它不会意识到这一点只会觉得代码逻辑没问题怎么测试挂了然后开始做出一堆无意义的修复把问题越搞越复杂。另一个现实问题是切换成本。每次切分支构建缓存、热更新、测试环境状态全部要重建一个中型项目的完整预热可能要花几分钟。三四个 Agent 并行跑的时候频繁切换会让整个机器一直处于预热—打断—再预热的循环里实际产出效率低得离谱。1.3 worktree 是正确的基本工具Git Worktree 解决的就是这个根本矛盾它允许同一个仓库同时拥有多个工作目录每个工作目录对应一条独立的分支共享同一个.git对象库。也就是说Agent A 可以在repo/agent-a目录里操作自己的分支Agent B 可以在repo/agent-b目录里操作自己的分支两边同时改文件、同时提交互不干扰。用 worktree 之后构建缓存的问题也自然解决了因为每个工作目录是物理隔离的node_modules、dist、缓存目录完全独立。两个 Agent 改同一个文件也没有关系它们各改各的副本只有在最后合并的时候才需要处理冲突。Worktrunk 做的就是把这套多工作目录并行的能力系统化地封装起来。它不是要发明一个全新的 Git 概念而是把它变成适合 Agent 工作流直接调用的工具。2. 原生 git worktree 够用但裸命令支撑不起 Agent 工作流2.1 原生语法和基础用法如果你还没用过 Git Worktree这里快速过一遍最基础的命令。假设当前仓库的主目录在~/repo你想给一个叫agent-a的分支单独开一个工作目录git worktree add ../repo-agent-a -b agent-a这个命令会在../repo-agent-a位置创建一个新的工作目录并基于当前 HEAD 拉出一条新分支agent-a。之后你可以直接cd到那个目录里正常写代码、提交、推送所有操作都是独立进行的。查看当前仓库挂了哪些 worktreegit worktree list输出大概是这样的~/repo main ../repo-agent-a agent-a清理一个不再需要的 worktreegit worktree remove ../repo-agent-a如果那个目录里还有未提交的变更需要强制删除git worktree remove --force ../repo-agent-a这套语法本身很清晰单个用起来也没什么问题。但当你把它放到一个真实的并行 Agent 工作流里就会发现它缺少几层关键的能力。2.2 原生命令在 Agent 工作流里的断点第一个断点是路径和分支的映射关系需要手动记忆。git worktree list输出的信息很原始它告诉你这个 worktree 在哪个路径对应哪条分支但不会告诉你这个 worktree 是给哪个 Agent 用的在跑什么任务负责人是谁。当你有五个 worktree 挂在同一个仓库下面时你打开 list 输出往往要愣一下才能想起来这个repo-feature-login是干嘛的。第二个断点是生命周期管理完全靠自觉。worktree 建起来之后Git 不会自动告诉你这个分支已经合入 main 了可以清理了。你得自己手动判断每个 worktree 是否还在使用、分支是否已经合并、目录是否可以删除。Agent 跑完任务之后如果没有人去清理这些 worktree它们就会像一堆僵尸目录一样一直占着磁盘空间。第三个断点是缺少任务上下文。原生 worktree 只关联目录和分支不承载任何任务描述、创建时间、最后活跃时间这类元数据。可是在 Agent 工作流里你恰恰最需要知道这个工作区是哪天晚上创建的那个分支合并了没有哪个工作区已经三天没有提交了。这些信息直接决定你要不要清理它、要不要合入它。第四个断点是状态展示碎片化。要判断一个 worktree 当前是否干净、有没有未推送的提交、分支领先或落后基分支多少你得手动执行一堆git status、git log、git rev-list命令然后自己在脑子里拼出全貌。并行 Agent 数量一多这个操作就变得不可维护。2.3 从命令工具到工作流工具的差距所以我说原生git worktree是一个很好的命令工具但它离工作流工具还有一段距离。它提供了最核心的隔离机制却没有把一个 Agent 在仓库里工作所需要的完整生命周期这件事抽象出来。Worktrunk 的目标就是补上这段距离。就是在命令应有的、原生 Git 没有做的那一层抽象把 worktree、分支、任务元数据打包成一个统一的工作区让创建、查看、同步、清理这个工作区变成一套完整的可编程流程。后面我会具体讲它的设计。3. Worktrunk 的设计Agent Workspace 的创建、追踪与清理3.1 核心抽象Workspace 对应一个 Agent 会话Worktrunk 的第一设计原则是一次 Agent 会话 一个 Workspace。所谓 Workspace就是以下三样东西的组合一个基于指定基点分支创建出来的独立 Git 分支一个物理隔离的工作目录一段记录任务上下文和会话状态的元数据当你在一个仓库里执行worktrunk init之后它会在仓库根目录初始化一个.worktrunk/目录里面存放所有 Workspace 的元数据记录。之后再创建新的 Workspace目录结构大致是这样~/repo/ ├── .git/ ├── .worktrunk/ │ ├── config.toml │ └── workspaces.json ├── main/ # 主工作目录 ├── workspace-login/ # 登录页任务的工作目录 └── workspace-payment/ # 支付模块任务的工作目录每个 Workspace 目录和普通的git worktree在底层没有任何区别但 Worktrunk 会额外维护一份结构化的元数据把路径、分支名、基点分支、任务描述、创建时间、最后活跃时间、状态全部记录在workspaces.json里。这样任何时候你想了解这个 Agent 干到哪了只需要执行一行命令而不需要自己去跑一串 Git 命令然后拼信息。3.2 命令模型一览Worktrunk 的命令设计刻意保持精简核心就六条# 在当前仓库初始化 Worktrunk 元数据 worktrunk init # 创建一个新的 Agent Workspace worktrunk add --name login-fix --base main --task 修复登录页样式异常 # 列出当前仓库的全部 Workspace worktrunk list # 查看所有 Workspace 的详细状态 worktrunk status # 打开某个 Workspace 的目录打印路径方便切换 worktrunk open login-fix # 清理一个不再需要的 Workspace worktrunk rm login-fixadd命令是最核心的操作。它的逻辑是这样先检查--base指定的基点分支存在然后基于这个分支创建新分支再调用底层git worktree add新建目录最后把元数据写进workspaces.json。全程只需要一个参数对所有底层细节都被封装掉了。3.3 元数据与状态追踪的具体设计我特别喜欢worktrunk status的输出。它把原来需要手动拼凑的信息统一汇总成一张表格Workspace Branch Base Status Ahead/Behind Last Commit login-fix wtr/login-fix main clean ahead 2 2 hours ago payment-callback wtr/payment-callback main uncommitted ahead 1 5 minutes ago log-format wtr/log-format main merged - 3 days ago每个字段都直接服务于决策Status告诉你这个工作区是否还有未提交的变更Ahead/Behind告诉你它领先或落后基点分支多少提交Last Commit让你一眼看出哪个 Workspace 已经很久没有活干了。基于这张表你要决定清理哪个、合并哪个、哪个还在跑任务三秒钟就能判断完。在实现层面workspaces.json的结构很简单类似这样{ workspaces: [ { name: login-fix, branch: wtr/login-fix, base: main, path: workspace-login-fix, task: 修复登录页样式异常, created_at: 2025-01-12T10:30:0008:00, last_active: 2025-01-12T12:45:0008:00 } ] }它不是一个复杂的数据库就是一个 JSON 文件。好处是透明、可检查、可被其他脚本读取不会被锁在某个私有格式里。Agent 自动化脚本完全可以自己解析这个文件做更进一步的处理。3.4 为什么是 CLI 而不是一坨脚本可能有人会问这套逻辑我自己写个 shell 脚本也能实现为什么要单独做一个 CLI我的看法是脚本和 CLI 的差别不在功能而在可预期的边界。自己写脚本的时候你会倾向于往里面加各种只适合当前项目的硬编码逻辑比如目录名写死、分支前缀写死、清理策略写死。等仓库一变、Agent 一变脚本就废了还得重新改。而 Worktrunk 作为 CLI把交互边界和使用协议固定下来你只需要关心五六个子命令剩下的交给它处理。另外一个实际的考量是Agent 工具链调用 CLI 比调用脚本更自然。在给 Agent 写 prompt 的时候你让它执行worktrunk add --name xxx --base main比让它去执行一串git worktree add git branch echo ...的 shell 脚本要可靠得多。CLI 有确定的参数格式和返回值Agent 不会在执行过程中跑偏。4. 实操三个并行 Agent 的合入全流程4.1 场景设定用一个具体的例子把流程走一遍。假设你有一个 Web 项目仓库里同时放着后端 API 代码和前端页面代码你现在要派三个 Agent 并行干三件事Task A修复登录页在移动端的样式异常顺带把表单交互的错误提示补上Task B修复支付回调接口的幂等性问题防止重复回调导致重复扣款Task C把全站日志从纯文本格式改成 JSON Line 格式方便日志系统采集这三个任务之间基本没有代码依赖非常适合并行。4.2 创建三个 Workspace先进入仓库根目录初始化 Worktrunkcd ~/repo worktrunk init接着为三个任务分别创建 Workspaceworktrunk add --name login-fix --base main --task 修复登录页移动端样式和表单错误提示 worktrunk add --name payment-idempotency --base main --task 修复支付回调接口幂等性 worktrunk add --name json-logging --base main --task 全站日志改造为 JSON Line 格式每条命令执行后Worktrunk 会在.worktrunk/workspaces.json里写入对应记录并创建独立的目录比如workspace-login-fix、workspace-payment-idempotency、workspace-json-logging。现在三个 Agent 的工作环境已经完全隔离了。4.3 Agent 写入与自检接下来你要做的是把每个 Agent 的指令指到对应的工作目录。假设你在用某个支持文件系统访问的 Agent 工具prompt 大致可以这样写你的工作目录是 /path/to/repo/workspace-login-fix。 任务修复登录页在移动端的样式异常并补全表单错误提示。 要求所有改动只发生在该目录内完成后运行前端测试确认通过后提交 commit。Agent 收到指令后切到对应目录改代码、跑测试、提交 commit所有操作都被限制在那个 Workspace 里不会影响到别的 Agent。在它干活的过程中你可以随时看一眼整体状态worktrunk status输出会告诉你哪个 Agent 已经开始提交了哪个还没有动静哪个目录里有未提交的变更。4.4 合并前的检查三个 Agent 都提交完成后先别急着合并。合并之前我习惯逐一检查每个 Workspace 的分支状态确认它确实领先于 main并且没有意外地落后worktrunk status如果某个 Workspace 显示Status: uncommitted说明 Agent 留下了一些没提交的文件。这时候我一般先手动看一下这些变更是什么确认为什么没有提交再决定是补一个提交还是直接丢弃。确认没有问题之后切到主工作目录逐个合并cd ~/repo/src git checkout main git merge --no-ff wtr/login-fix -m Merge login-fix: 修复登录页样式 git merge --no-ff wtr/payment-idempotency -m Merge payment-idempotency: 修复支付回调幂等性 git merge --no-ff wtr/json-logging -m Merge json-logging: 日志改造为 JSON Line如果某个分支合入时产生冲突最好先处理掉再继续下一个。三个合入完成之后跑一遍全量测试确认集成没有问题。4.5 清理 Workspace合入 main 并且测试通过之后这些 Workspace 的历史使命就结束了。逐个清理worktrunk rm login-fix worktrunk rm payment-idempotency worktrunk rm json-loggingrm命令会做两件事强制删除对应的工作目录同时删除分支并且把workspaces.json里的记录同步移除。执行后再跑一次worktrunk list应该只看到主工作区和空的记录列表。这套流程走下来三个 Agent 的并行开发从创建到合入再到清理全程没有产生任何互相干扰。这是 Worktrunk 最核心的价值把底层 Git 操作的复杂度全部吃掉让你只需要关注任务级别的调度。5. 接入 CI 和编排器把并行流程变成自动化流水线5.1 让状态可被脚本读取Worktrunk 在设计上特别强调可编程性其中一个关键能力就是支持结构化输出。worktrunk list和worktrunk status都支持--json参数worktrunk list --json输出是标准 JSON{ workspaces: [ { name: login-fix, branch: wtr/login-fix, base: main, path: workspace-login-fix, status: ahead, ahead_count: 2, behind_count: 0 } ] }这就给 CI 和编排器留了很大的接入空间。脚本可以解析 JSON判断哪些 Workspace 已经可以合入、哪些还需要人工介入然后自动执行后续动作。5.2 在 CI 里做自动合入策略一个常见的接入方式是在 CI 里增加一个合并所有已合入 Workspace的任务。思路是这样的每当有 Agent 提交代码到自己的分支后CI 触发检查解析worktrunk list --json找出所有指向 main 分支且状态为干净、领先的 Workspace然后自动执行 merge。这里有一个非常值得注意的点自动合入之前必须检查这个 Workspace 是否领先于基点分支。如果某个 Workspace 落后了说明它有可能是基于旧的 main 开发的直接合并可能会带来冲突。好的做法是自动合入之前先做一次 rebase 或 merge base 同步git checkout wtr/login-fix git rebase main然后再合并回 main。把这套逻辑写进 CI 脚本之后并行 Agent 的产出就能形成一个半自动化的流水线Agent 提交 - CI 检查 - 自动同步 - 自动合入 - 清理 Workspace。5.3 在 Agent 编排器里使用如果你正在搭建自己的 Agent 编排系统Worktrunk 可以承担任务运行环境管理那一层。编排器的逻辑大致是收到一个任务解析出任务名和依赖仓库调用worktrunk add创建独立 Workspace启动 Agent 进程把 Workspace 路径注入到 Agent 的 prompt 里监听 Agent 的完成信号调用worktrunk status检查状态合并分支或者如果失败则调用worktrunk rm清理环境重新分配任务。这套模式里最有价值的一点是所有工作区的生命周期都由 Worktrunk 统一管理编排器不需要自己设计一套复杂的目录和分支命名规范。Agent 运行失败也好、中途退出也好下一次任务启动时编排器只需要查一下worktrunk list就能知道哪些旧环境还没有清理。5.4 自动化接入时的注意事项自动化接入时我踩过最大的坑是不要让多个进程同时对同一个 Workspace 做操作。比如 CI 脚本和本地终端同时对一个 Workspace 执行worktrunk rm会导致元数据文件写冲突甚至可能误删还没合并的分支。更安全的做法是在编排器层面做一次互斥控制。比如用锁文件或者分布式锁保证同一时间只有一个进程在对某个 Workspace 执行合并或清理操作。Worktrunk 本身不会帮你解决并发问题它的设计假设是每个 Workspace 同一时间被一个主人操作这个主人可能是人也可能是编排器进程但不能既是人又是编排器。6. 我踩过的坑和沉淀下来的使用习惯6.1 坑一在一个 Workspace 里手动切分支刚开始用的时候我犯过一个低级错误在一个由 Worktrunk 创建的 Workspace 目录里手动执行了git checkout main想看看 main 分支的代码长什么样。结果等 Agent 回来继续干活的时候它发现当前分支已经不是它提交的那个分支了工作目录里出现了大量它不认识的变更整个状态彻底混乱。后来我明白了Worktrunk 管理的 Workspace它的分支应该是锁死的。不要手动去切换 Workspace 里的分支想看别的分支的代码去主工作目录或者其他 Workspace 里看。这仿佛是一句废话但在 Agent 和人类协作的环境里这种误操作真的很容易发生。6.2 坑二每个 Agent 都重新安装一遍依赖Worktree 目录是物理隔离的这意味着每个 Workspace 都要有自己独立的node_modules或虚拟环境。三个 Agent 并行跑的时候每个 Agent 都要重新安装一遍全部依赖磁盘占用和安装时间都会成倍上升。一个前端项目如果依赖有 500MB三个工作区就是 1.5GB再加构建缓存很快就吃掉十几 GB 磁盘。我的应对策略是接到任务后先看一眼依赖规模如果项目依赖太重就尽量不要同时开太多 Agent 跑同一个仓库或者把 Agent 数量限制在两到三个。另外尽量给每个 Workspace 设置独立的构建缓存目录避免由于缓存目录互相指向而出现诡异的问题。6.3 坑三两个 Agent 改了同一个文件导致的合并冲突这是并行开发绕不开的问题。即使你在任务分配时已经尽力避免了重叠两个 Agent 在实现交叉功能时仍然有可能碰到同一个文件。比如 Task A 改了后端 API 的返回结构Task B 正好在前端页面里消费这个 API两边都可能改到类型定义文件。合并冲突本身不可怕可怕的是冲突发生在你没预期到的地方。我现在的做法是合入前先用git diff main...wtr/分支名快速看一遍变更范围如果发现两个 Workspace 的 diff 有交叉就先把其中一个分支合入 main再让另一个 rebase 到最新的 main 上这样冲突会提前暴露而不是等到最后一起炸。6.4 习惯一任务完成后立即跑一次 syncWorktrunk 提供了一个sync操作但它的意义不在于自动解决冲突而是帮你把本地状态和远端状态同步到一致。任务完成后合入之前我强烈建议先跑一遍 sync确保没有遗漏的提交也避免合入一个落后的分支。几个 Agent 跑得久了分支之间会逐渐产生漂移。合入前跑 sync 可以让你清楚地看到每个分支领先多少、落后多少减少合入之后发现代码少了一大段这种事故。6.5 习惯二把 Workspace 路径写进 Agent 的 prompt这点可能是所有经验里最容易被忽视的一条。Agent 工具并不是天生就知道自己该在哪个目录干活如果你不在 prompt 里显式指定工作目录它可能会默认在仓库根目录或者它自己所在的目录操作那就完全失去了 Worktrunk 隔离的意义。我的做法是在每个 Agent 的 prompt 第一行就写明你的工作区路径是 /path/to/repo/workspace-payment-idempotency所有文件操作只能发生在该目录下。实测下来这样写之后 Agent 跑偏的概率大大降低几乎不会再出现Agent 跑到别的目录里改代码的情况。6.6 习惯三定期清理僵尸 Workspaceworktrunk status表格里Last Commit超过两天的 Workspace我基本都会主动检查并清理掉。Agent 跑完任务之后如果没有人去清理Workspace 就会一直挂在仓库下面占地方不说还会干扰下次worktrunk list的视觉判断。我现在固定在每周五下午做一次大扫除worktrunk status把所有 Workspace 过一遍已经合入 main 的直接rm还没合入的确认一下是否需要继续保留不再需要的马上清掉。这个习惯坚持下来之后我的仓库里永远只有正在活跃使用的工作区没有被一堆僵尸目录淹没的感觉。Worktrunk 这类工具的本质是把 Git 原生的能力包装成适合现代 AI 协作工作流的形态。它没有发明新概念只是把一个 Agent 一个工作区这个模式变成了随手可用的基础设施。如果你也在用多个 Agent 并行开发同一个仓库我建议你试试这套工作区管理方式大概率会重新找回干净仓库的掌控感。