
1. 先讲清楚为什么我会去写一个“多余的” CLI去年我开始密集用 AI 编程 CLI 干活Codex CLI、Claude Code 这类工具轮着试。单条指令写代码确实快但真正让我头疼的不是模型能力而是同时开两个任务时仓库先乱了。一个 Agent 在改auth/service.go另一个在补测试俩人在同一个工作目录里各改各的。跑到一半一个把另一个刚写的文件覆盖了测试结果也是错的因为俩人共用同一份构建产物和依赖目录。我一开始以为这是工具的问题后来发现是隔离没做好。更准确地说并行 AI Agent 工作流的瓶颈不在模型推理而在仓库层面的工作目录隔离。你让两个 Agent 在没有隔离的目录里并行跑就像让两个人在同一张桌子上各画各的画最后肯定糊成一团。我先后试过几种方案都差点意思方案隔离效果主要问题同一个目录切分支 stash差Agent 会话有状态stash 后现场全乱它不理解改动去哪了多份 clone好多套历史、多套 remotereview 时要来回 push联调费劲手动 git worktree好命令本身没问题但目录命名、分支分配、清理、同步全靠人记时间一长一堆幽灵目录后来我决定在裸 worktree 之上做一层管理工具这就是 Worktrunk把 worktree 生命周期封装成一组简单命令专门面向 AI Agent 并行工作流。它不解决 Agent 的推理问题只解决“多个 Agent 安全地共享一个仓库”这个基础设施问题。这篇文章会把它的设计思路、命令约定、实际工作流和踩坑记录都写出来给正在用 AI 编程 CLI 做多任务并行的人参考。2. Git Worktree 机制拆解共享、隔离与分支独占要理解 Worktrunk 为什么这么设计得先把git worktree的边界摸清楚。一句话总结worktree 共享仓库的 object 库和 refs但隔离工作区文件、索引、HEAD 和未跟踪文件。这个模型恰好匹配 Agent 的工作方式——它不需要完整 clone只需要一份干净的工作区、一条独立分支改完提交后把 commit 推回同一个仓库。2.1 worktree 的共享与隔离边界内容共享隔离说明对象库objects是-commit/tree/blob 全局共享任意 worktree 里的新提交在别处都可见分支引用 refs/heads是-但同一个分支同一时刻只能被一个 worktree checkout工作区文件-是每个 worktree 有自己独立的文件树索引index-是暂存区独立互不干扰HEAD-是每个 worktree 各自指向自己的分支或 commit未跟踪文件-是各自独立另一个 worktree 看不到也管不到依赖目录node_modules 等-是默认默认不共享磁盘占用要留意后面专门讲坑“共享历史、隔离工作区”这个模型意味着Agent 在worktrees/api-server里提交代码你主仓库马上能看到这个 commit切过去就能 review。而多个独立 clone 做不到这一点——两个 clone 之间没有隐性的历史连接只能靠 push/pull 来回倒腾。2.2 .git/worktrees 元数据与幽灵目录很多人不知道的一点是worktree 的登记信息存在.git/worktrees/name/下包括HEAD、index、commondir等文件。你删掉了 worktree 的工作目录.git/worktrees/里的条目不会自动消失必须跑git worktree prune才会清掉。手工管理时这个“剪不断理还乱”的状态就是幽灵目录的来源。一段时间不清理git worktree list里会挂着一堆已经不存在的路径。Worktrunk 的做法是在内部状态文件里记录每个 worktree 的创建时间、关联分支、Agent 会话 IDclean 时先对照实际文件系统和 Git 状态把无效和已合并的一并清掉而不是依赖人肉记。2.3 分支独占规则与 Agent 会话的隐含约定worktree 有一个反直觉的约束一个分支同一时刻只能被一个 worktree checkout。如果你在主仓库里执行git checkout feature-x而 feature-x 已经被某个 worktree 检出了Git 会直接报fatal: feature-x is already checked out at /path/to/worktree这个约束是 Git 保证一致性的基础但对平时习惯随便切分支的人很迷惑。Worktrunk 在设计上顺着这个约束来创建 worktree 时固定每个 Agent 的分支命名比如ai/api-server把“分支 工作区 Agent 会话”三者绑定。这样既不会撞分支也方便事后追溯。还有一点容易被忽略worktree 可以处于 detached HEAD 状态。如果你在 worktree 里git checkout commit-hashHEAD 就脱离分支了Agent 如果在这个状态下提交commit 不落在任何分支上事后很难找回。Worktrunk 在 create 和 sync 时都会检查 HEAD 状态发现 detached 直接警告避免 Agent 的产出“悬空”。3. Worktrunk 的命令设计每个子命令都对应一个真实痛点Worktrunk 的设计原则很朴素我手动操作时觉得烦、容易错的动作就封装成一条命令。命令不多但每条都对应我在真实工作流里反复做的事。3.1 先声明后执行worktrunk.tomlWorktrunk 不是“命令行里临时指定参数”的思路而是“先声明后执行”。仓库根目录放一个worktrunk.toml声明所有要管理的 Agent 工作区[workspace api-server] branch ai/api-server # 这个工作区用的分支 base main # 从哪个分支分出来 path worktrees/api-server # 工作区相对路径 deps [go mod download] # 创建后要执行的依赖安装命令 agent codex # 绑定哪个 Agent CLI仅用于标记和日志 [workspace web-client] branch ai/web-client base main path worktrees/web-client deps [npm ci] agent claude为什么用配置文件而不是命令行参数因为 Agent 工作区通常要反复重建、清理、再同步。把参数固化到文件里重建一次就只是一行worktrunk create。而且配置本身就是团队约定的载体——哪个模块、哪个分支、用哪个 Agent打开文件一目了然。团队新成员加入时不需要问“这个分支谁的、那个目录哪来的”看配置即可。3.2 create、list、status启动期的三个高频动作命令行为解决的手动痛点worktrunk create name按配置创建 worktree检出指定分支跑 deps记不住 worktree add 的路径和分支参数worktrunk list列出所有 worktree含分支、领先/落后 base 的 commit 数一眼看到每个 Agent 干到哪了worktrunk status显示每个工作区的改动文件数、未提交改动、最近提交快速判断 Agent 是否卡死或乱改create最容易错的是路径——手一抖把 worktree 建在主仓库目录内部Git 直接报错或者产生嵌套仓库的诡异结构。Worktrunk 限制路径必须落在worktrees/前缀下超出就拒绝执行。create前还会先git fetch一次远程用最新的 base 创建分支避免 Agent 拿到一周前的代码在那瞎改。list和status的区别在于list是看 Git 的静态状态status是看 Agent 的活跃度。我实际使用中基本只看status输出大概长这样api-server branch: ai/api-server (ahead 4 commits) changed: 12 files, 3 staged last: refactor auth middleware error handling web-client branch: ai/web-client (ahead 1 commit) changed: 87 files, 0 staged last: WIP: migrate button styles看到web-client有 87 个文件改动、最近提交还是 WIP我就会停下来看一眼是不是 Agent 跑偏了。这种“尽早发现跑偏”的能力比任何命令行封装都值钱。3.3 sync、clean收口期的两个高风险动作Agent 干完活收口是关键。worktrunk sync name把 base 分支通常是最新的 main的最新改动并入 Agent 分支worktrunk clean清理已合并的 worktree 和分支。这里有个关键取舍sync 默认用 merge 而不是 rebase。原因很简单Agent 不像人类它不会在处理冲突时听懂“你先 stashrebase 完再 pop”这种指导。merge 出冲突时冲突标记留在文件里Agent 还能继续基于当前状态操作rebase 一出冲突HEAD 就散了Agent 的下一个 commit 很可能落在奇怪的位置甚至把历史改得没法看。用 merge 会牺牲一点历史线性换来 Agent 会话的稳定性。对我个人来说AI 生成的历史本来就不是给人读的linear history 的执念可以放一放。如果 merge 出现冲突我的处理方式是先让 Agent 自己尝试解决冲突解决不了就人工介入但绝不 rebase。clean不会直接强删。它会先检查目录里有没有未提交改动或未跟踪文件有就列出来确认。这个“宁慢勿快”的设计是我吃过几次亏才定下来的——以前我写过自动清理脚本结果把 Agent 正在跑的进度全删了那次之后所有 destructive 操作都默认要确认。3.4 子命令的防御性检查与退出码Worktrunk 每条命令设计时都会带上防御性检查create 前检查目标路径是否是已有仓库、是否在主仓库内、是否为空目录sync 前检查 worktree 是否存在、HEAD 是否 detached、是否有未提交改动clean 前检查进程是否还在占用目录列出将被删除的内容带--yes才执行命令行工具最容易犯的错是“太信任用户”。但用脚本和 Agent 驱动时没人会像人一样盯着错误提示。防御性检查 明确的退出码0 成功、1 参数错误、2 状态冲突才能让工具被安全地接进自动化流程。我自己在写自动化脚本时依赖的就是这些退出码。4. 并行 Agent 工作流完整走一遍光讲命令没意思我把实际使用中跑通的一条完整流程写下来从启动到收口包含真实会遇到的状况。4.1 启动30 秒让三个 Agent 各就各位假设今天我同时要开三个任务重构 API 服务权限模块、改前端按钮样式、补 E2E 测试。我会先在配置里定义三个 workspace然后执行worktrunk create api-server worktrunk create web-client worktrunk create e2e-tests三条命令分别完成拉取远程最新代码、基于 main 创建ai/api-server分支、在worktrees/api-server目录 checkout、执行go mod download。几十秒后三个隔离的工作区全部就位。接下来分别开三个终端cd worktrees/api-server codex cd worktrees/web-client claude cd worktrees/e2e-tests codex三个 Agent 各干各的互不干扰。注意启动时我会把任务背景写成一个SPEC.md放在工作区根目录Agent 会自己读。Worktrunk 不管推理部分只保证有一个稳定目录和独立分支。4.2 运行阶段Agent 提交期间你该做什么Agent 跑着的时候我最常做的是每隔一段时间执行worktrunk status观察每个工作区的活跃状态。这种“轻量巡检”很有价值因为 Agent 经常会陷入无效循环——反复改同一个文件、反复跑同一条失败的命令。我一般拿这些指标判断是否介入长时间不产生 commit文件改动数却很大可能 Agent 卡在某个大改写里改动文件数突然暴涨先看一眼有没有把锁文件、构建产物也改进去连续多个 WIP 提交但没实质进展建议停掉重新调整 prompt主仓库在 Agent 运行期间保持干净我可以在主目录里正常手写代码、做 review、甚至跑另一个 Agent 来检查某个 worktree 的改动。这是 worktree 模型最好的一点——工作区和主仓库完全解耦你的日常开发节奏不受影响。4.3 收口合并、验证、清理Agent 提交完流程是这样worktrunk sync api-server # 把 main 的最新改动并入 ai/api-server git diff main...ai/api-server # 在主仓库统一看改动 # 跑关键测试 git merge --no-ff ai/api-server worktrunk clean # 清理已合并的 worktree 和分支第 4 步很容易被忽略。手动操作时经常出现“分支合完了worktree 目录还躺在那占几个 GB”的情况尤其是包含 node_modules 或 build 目录的工作区。worktrunk clean会检查分支是否已合并到 base已合并的直接删 worktree、删分支、prune 元数据一条命令搞定。4.4 一个实际的冲突场景三个 Agent 并行代码冲突不可避免。有一次api-server改了config.go的配置结构web-client也改了同一个配置文件的读取逻辑两个分支在 main 上相遇时 merge 冲突了。处理过程先worktrunk sync web-client让它把 main 上已有的 config 改动合并进来。冲突标记出现在config.go里我把冲突文件改成“配置结构用 api-server 的读取逻辑兼容两种格式”的中间态。然后让 web-client 的 Agent 继续完成剩余兼容工作再提交。这轮跑下来我的体会是并行的收益来自隔离收口的成本来自耦合。任务划分时尽量让不同 Agent 碰不同的模块和文件能显著降低冲突概率。这也是 Worktrunk 配置里推荐写base main并在 create 前 fetch 最新代码的原因——基础越新冲突面越小。5. 我在批量管理 worktree 时踩过的坑前面是顺畅路径下面这些坑是我在真实环境里踩过的按严重程度排下来。5.1 嵌套路径与“伪仓库”错误worktree 最经典的坑是路径嵌套。如果你在主仓库目录里面执行git worktree add ../repo-fix这是安全的但如果你把路径指到了现有目录里Git 会拒绝或产生一个诡异结构。我见过最无语的情况是有人在 submodule 目录里建了 worktree最后git status直接卡死。排查时如果git status告诉你某个文件“unmerged”但又找不到冲突位置先看是不是有嵌套仓库。Worktrunk 在 create 前检查目标路径是否为已有仓库、是否在主仓库内、是否为空目录三项任一不满足就报错退出。这类检查看起来啰嗦但对自动化场景是必须的因为 Agent 不会在错误发生时停下来想一想。5.2 依赖没有真正隔离导致的假绿测试这是最容易骗过自己的一个坑。worktree 默认不共享未跟踪文件但如果你的项目是 monorepo用了 pnpm workspace 或者 symlink 链接到根目录的node_modules两个 Agent 的依赖可能实际指向同一份文件。一个典型场景Agent A 改了包 A 并触发重新构建Agent B 的测试在跑时引到了包 A 的新版本于是 B 的测试“全绿”合到 main 就炸。这属于典型的假绿。我现在的处理办法是每次 create 后检查 lockfile 是否一致不一致直接失败提示Agent 跑关键测试前要求先执行一次全量构建。如果你在用 monorepo千万别省这一步。5.3 stale worktree 与进程占用stale worktree 的清理是个低频但高破坏力的操作。如果你在 worktree 目录里还开着终端或 Agent 进程直接rm -rf会留下一堆问题Git 索引锁文件残留、文件句柄占用导致删不干净。正确姿势是先退出目录里的所有进程再用git worktree remove --force。Worktrunk 的 clean 命令不会直接强删。它会先判断目录里是否有未提交改动或未跟踪文件有就列出来给你确认。后来又加了一个进程占用检查检测到 worktree 目录里有存活进程时直接跳过提示你先处理。这个设计帮助了我很多次因为我有过删除 Agent 正在使用的工作区导致它“原地失忆”的经历。5.4 Windows 路径长度与大小写问题Windows 下 worktree 的路径长度限制是个老坑。refs/worktree/name和目录层级叠起来很容易超长。我把 worktree 路径强制放在仓库根下的worktrees/目录并且用短名字能显著压低路径长度。另外 Windows 的文件系统默认大小写不敏感如果你的配置里分支名大小写变了Git 可能认不出分支worktree 会创建到一个新名字下造成重复。团队里两种系统混用时我建议统一用小写字母和连字符命名。6. 更进一步Hook、报告与跨仓库编排Worktrunk 解决的是“单仓库内并行 Agent”的管理问题。实际用下来往上走还有几个很自然的扩展方向。6.1 Hook 机制工具做得再顺手也不可能覆盖所有团队的个性化需求。Worktrunk 预留了事件 hook在post-create、pre-sync、pre-clean等时间点执行用户脚本。比如[hooks.post-create] command scripts/notify-agent-ready.sh最常用的场景是create 完成后自动往某个 Agent 的 prompt 文件里写入当前分支名和工作区路径避免 Agent 自己猜。另一个场景是 clean 前自动收集测试报告存档后再清理。6.2 Agent 会话与分支的可追溯性并行 Agent 跑得多最大的问题是“这个改动是谁干的、为什么这么改”。Worktrunk 在内部记录每个 worktree 关联的 Agent CLI、任务描述、创建时间。收口时生成一份 Markdown 报告内容包括每个 worktree 的提交列表、改动文件数、测试结果。这份报告可以直接贴进 PR 描述队友 review 时不用自己翻历史猜动机。6.3 多仓库工作区的编排一个任务通常涉及多个仓库比如前端、后端、协议定义。目前 Worktrunk 是单仓库视角我下个阶段想扩展成 workspace 概念——一个 workspace 下关联多个 repo一条命令在多个仓库里同时创建对应名字的 worktree。这样“同时开一个全栈任务”就变成了一次worktrunk create fullstack-task的事。如果让我说最终的使用感受那就是Worktrunk 解决的是“让多个 Agent 能安全地在同一个仓库里并行工作、互不干扰”这个基础设施问题。它不替你决定哪个 Agent 的改动是好的也不提升模型本身的代码质量。基础隔离做好剩下的交给 review 和测试。这个界线我从一开始就划得很清所以工具一直很轻也一直很好用。