第一次让两个 Agent 同时干活,是我想省时间。
一个改前端表单校验,一个补后端接口。两个任务互不相干,我觉得并行肯定更快。
二十分钟后我回去看,前端的 Agent 在抱怨「文件里出现了不属于我的代码」,后端的 Agent 把一个组件文件整个重写了。两个人都没错,它们只是同时在一份工作区里动了手。
那次之后我才认真去看隔离这件事。十月的 Google Antigravity 2 把「原生 Git worktree 隔离」直接做成了核心特性——每个 Agent 拿一个一次性分支,在自己的 worktree 里跑、编译、把代码改坏,都不会碰到主干。
方向是对的。但隔离只解决了第一层。
冲突一:同一份工作区
这是最直观的一种,也是最容易被发现的。
两个 Agent 共用一份工作目录,就会出现三种情况:读到了对方刚写下但还没成型的内容;写的时候互相覆盖;以及最阴的——A 在读文件,B 恰好删了那个文件,A 直接报错退出。
第三种最难查,因为报错信息长这样:No such file or directory。你会以为是路径写错了,其实是另一个进程干的。
传统的解法是复制一份目录。但复制有代价:node_modules动辄几百兆,复制一次几十秒,两个 Agent 并行复制两次,你的磁盘开始尖叫。
worktree 解决的就是这件事。它不复制工作文件,只给你一个新的工作目录,.git对象库是共享的。所以:
| 对比项 | 复制目录 | git worktree |
|---|---|---|
| 磁盘占用 | 每个副本一份完整文件 | 只增加工作文件 |
| 创建耗时 | 十几秒到几分钟 | 一般一秒以内 |
| 依赖安装 | 要重装一遍 | 可以软链或复用缓存 |
| 清理 | 手动删,容易漏 | git worktree remove |
| 分支关系 | 无,只是文件副本 | 天然一个分支一个目录 |
对 Agent 来说,最后一行才是关键:一个 worktree 对应一个分支,这件事在 Git 层面是可解释的。它不是一个「临时拷贝」,而是一个有身份的工作区。合并的时候,你在合并分支,不是在对比两个文件夹。
冲突二:构建产物和依赖
这个冲突不会让你报错,只会让你怀疑人生。
两个 worktree 里装同一份依赖,占双倍磁盘;更麻烦的是编译缓存。有些构建工具会把缓存写在仓库内的目录里(比如.next、dist、__pycache__),两个 Agent 同时编译,缓存互相污染,你会看到「明明改了代码但页面没变」这种灵异现象。
我的处理是三件事:
- 缓存目录统一指到仓库外面,用环境变量覆盖,两个 worktree 各用一个子目录
- 依赖用软链接共享一份,只在依赖变更时才重新装
dist之类的产物目录加进.gitignore,避免 Agent 误把它当源码读
第 3 条听起来很基础,但真有 Agent 会去读dist里的打包结果,然后告诉你「你的代码是压缩过的」。它不知道那是产物。
冲突三:端口、数据库、外部状态
这是三种里最隐蔽的,也是最容易在「隔离得很好」之后翻车的。
工作区隔离了,但下面这些东西没隔离:
- 本地开发端口。两个服务同时起,第二个必然绑定失败,Agent 会开始疯狂重试或者改配置
- 本地数据库。A 的 Agent 跑了迁移脚本,B 的 Agent 突然发现少了一列
- 缓存和队列。两个 Agent 抢同一个 Redis key,谁也说不清是谁写坏的
- 还有那些需要登录态的外部服务,两个 Agent 用同一个 token,可能互相把会话踢掉
我吃过最难受的一次是数据库迁移。前端 Agent 为了让页面能跑,顺手执行了一个迁移;后端 Agent 的单元测试跑在同一个库上,于是测试数据被污染,它开始怀疑自己的 SQL 写错了。
隔离工作区只是第一层,第二层是隔离「运行时占用的资源」。端口号按 worktree 编号偏移、数据库按 worktree 建独立 schema、缓存加前缀,这三件事做完才算真隔离。
Antigravity 2 的做法,和它的取舍
公开报道里,Antigravity 2 的定位是「不用终端的 Agent 工作区」,围绕异步对话组织,核心结构特性就是原生 worktree 隔离:Agent 自己开一次性分支,在里面跑、编译、改坏代码,不污染上游。
这个设计我觉得抓得挺准。它承认了一件事:Agent 出错是常态,所以隔离必须是默认行为,而不是可选项。
但它也有明显的取舍。报道里提到,它目前只支持商业模型(Gemini 和 Claude),本地开源权重完全不支持。
这个限制意味着,如果你的团队在用自托管模型——比如为了数据不出内网——这套工作流你用不了。而「数据不出内网」这件事,恰恰是很多企业选开源权重的主要原因。
所以我的判断是:这类工具的思路值得抄,工具本身未必能用。worktree 隔离这件事,自己写脚本做也就几十行。
我实际怎么搭的
方案不复杂,几十行脚本。
任务进来的时候,脚本先做三件事:
# 1. 从主干拉一个任务分支 git worktree add ../wt-$TASK -b agent/$TASK main # 2. 依赖共享,不重复装 ln -s ../../main-repo/node_modules ../wt-$TASK/node_modules # 3. 三个资源按任务编号隔开 export PORT=$((3000 + TASK_ID)) export DB_SCHEMA=agent_$TASK export CACHE_PREFIX=agent:$TASK:任务结束,脚本做两件事:git worktree remove ../wt-$TASK,顺手删掉对应的 schema。
第二行的软链接有个前提:两个分支的依赖版本必须一致。如果 Agent 改了package.json,软链就会骗你。所以我在合并前加了一步校验——依赖文件有变更的,重新装一遍再跑测试。
第三条最容易漏。我曾经因为忘了给端口偏移,两个 Agent 抢 3000 端口,后面的那个一直报EADDRINUSE,Agent 开始自己改配置找端口,越改越乱。
隔离不只是文件的事,是「这个任务独占哪些资源」的事。把资源列出来,一样一样分,别漏。
合并的时候才是真考验
隔离做得再好,合并那一刻都会疼。
我遇到最典型的一次:两个任务都要改同一个工具函数。A 给它加了一个可选参数,B 把它拆成了两个函数。两边各自测试全绿。
合并的时候,Git 把两边的改动都留下了:函数拆了,但调用方还在传那个可选参数。编译能过,运行时报TypeError。
这种冲突 Git 帮不了你,因为它不理解语义。
我能想到的办法只有三个:一是把「可能被多个任务碰到的公共模块」提前标出来,碰到它的任务不并行;二是合并后强制跑一遍集成测试,不只看单测;三是合并只由一个人做,因为两个 Agent 各自解冲突,会解出两个风格。
第二条我执行得最严。单测绿了不代表没问题,这句话在 Agent 时代更成立——因为它写的单测和它写的实现,可能有一模一样的误解。
隔离之后,新问题才刚开始
隔离做完了,问题一点没少,只是换了个位置。
第一个,冲突从「运行时」挪到了「合并时」。
以前两个 Agent 共用工作区,改到一半就报错,你会马上发现;现在各自在干净的分支里跑得很开心,所有问题一起攒到合并那天。三个 Agent 并行,合并时要处理的分歧大概是一个的三倍——不是三倍以内,就是三倍。
第二个,评审变成瓶颈。
Agent 产出代码的速度上来了,人看代码的速度没变。三个 Agent 一小时给你三百行,你光看懂就要一下午。瓶颈从「写」变成了「读」。
第三个,回归没人负责。
每个 worktree 里测试都是绿的,合在一起挂了。因为你测的是局部,问题出在交互。
我的做法是把并行的规模压住:同时最多两个 Agent,第三个任务排队。不是因为机器跑不动,是因为我一个人最多同时评审两份改动。超过这个数,质量一定掉。
一套最小可用的约定
如果你们也想试并行 Agent,我给三条约定,比工具选型重要:
第一,一个 worktree 一个任务,任务要能用一个句子说清。「改表单校验」可以,「优化一下体验」不行——后者会让 Agent 顺手改十个文件,合并时你根本不知道哪些该留。
第二,分支命名带任务前缀。agent/form-validation比agent-2026-10-21-1有用得多,因为一周后你只记得任务,不记得日期。
第三,合并由人做,而且只从一个人手里过。不要让 Agent 之间互相合并,它们对「冲突该怎么解」的判断非常自信,但经常是错的。
最后
并行这件事,我的态度从「越快越好」变成了「快到自己能审的边界为止」。
工具会越来越好用,worktree 会被做成默认选项,端口和数据库的隔离迟早也会有人封装成一行命令。
但有一件事不会变:合并的责任在人身上。Agent 能帮你写十个分支,没法帮你判断哪一个该进主干。
所以现在我看到「让一百个 Agent 并行工作」这种宣传,第一反应不是兴奋,是想问一句:谁来评审。