跳到主要内容

Automatic Worktree:让 Agent 不再在你的 main 分支上裸奔

· 阅读需 14 分钟
AI Agent Team
Core Development Team

Automatic Worktree —— Agent 在隔离分支上工作,main 保持干净

任何编码 Agent 最终都要往你的仓库里写代码。
问题是:它写到哪个分支?

大多数 AI 编码工具把这个问题甩给了用户。Helix 在 Agent 触碰任何一个文件之前,就在系统层面给出回答。

这是 Helix 多 Agent 架构 的第三条边界 —— 在 Manager Mode 守住「目标范围」、HelixVM 守住「主机边界」之外,Automatic Worktree 守住的是仓库分支的边界


1. 直接写 main:Agent 演示里没人提的那些事

当 AI Agent 编辑文件时,它需要把改动放在某个地方。阻力最小的路径,就是用户当前打开的工作目录 —— 通常正是 main 分支本身。

这就引出一类大家在 Agent 演示里很少提的问题:

  • 任务半途死掉,main 就脏了。 Agent 开始一个重构,改了三个文件,遇到错误就停了。git status 一片狼藉,用户也搞不清哪些可以放心提交、哪些应该回滚。
  • 并发任务互相打架。 同时跑多个 Agent 或多个会话,它们都在写同一份 checkout。文件冲突难以预测,也难以调试 —— 出了问题,你甚至不知道是哪个会话写脏的。
  • 回滚极其痛苦。 Agent 改了你不想要的东西,但同时也改了你想要的东西。两者纠缠在同一份工作副本里,想 revert 都没有干净的边界。
  • 没有「合并前评审」这件事。 代码已经在 main 上了 —— review 变成了事后追认,而不是事前关卡。

worktree 问题并不是 AI Agent 才有的。它是任何并行开发流程都会遇到的老问题。Git 自己给出的答案就是 worktree:仓库的另一份 checkout,处在另一个分支、另一个目录,改动在被显式合并之前都是隔离的。

真正的问题是:谁来创建并管理这个 worktree?


2. 其他工具的答案:opt-in worktree + 手动合并

Opt-in 模式与系统约束模式的对比

业内一些编码 Agent 工具已经加了 worktree 支持。模式大同小异:

用户:打开 worktree 模式
工具:好,worktree 已启用
用户:开始任务
工具:创建 worktree……完成
用户:审查输出
用户:手动合并分支 ← 手动一步

这条路径里有两个关键问题:

worktree 是可选的。 你得知道有这个东西,并主动打开它。如果忘了 —— 或者只是想跑一个小活,觉得不值得为此费心 —— Agent 还是会直接写进你当前的工作副本。

合并永远是手动的。 工具帮你建 worktree、帮 Agent 干活,但要把改动带回 main 是用户自己的事。一个任务可以接受,五个并发任务、每天十几次 Agent 协作的工作流里,这种手动开销就会迅速堆积成新的负担。

方向是对的。但「opt-in worktree + 手动合并」意味着:默认路径 —— 也就是不去配置、不假思索跑一个小任务的那条路径 —— 仍然把 main 暴露在外。

而绝大多数事故,正是从「我就跑个小活,不用 worktree 吧」开始的。


3. Helix 的做法:把 worktree 做成系统约束

在 Helix 中,worktree 隔离不是一个用户可以「启用」的功能,而是 Agent 与仓库交互方式中的一项架构约束。规则在系统层面强制执行,而不是写在 prompt 里靠 Agent 自觉。

Helix 在 worktree 上立了三条硬规则:

  1. 没有 worktree 绑定,Agent 就不能写。 无论是 Execution Agent 还是 SubAgent,在还没有为某个仓库建立 worktree 绑定之前,系统都会拒绝对当前分支上受 git 管理文件的写入。这不是「会被警告」,而是「直接写不进去」。

  2. 绑定由 Agent 主动声明,不需要用户配置。 当 Agent 判断当前任务需要改某个仓库的代码时,它会先调用一次 create_worktree_binding。系统创建 worktree、生成一个隔离分支、把路径交还给 Agent,这一切发生在 Agent 动任何一个文件之前

  3. 合并是自动的。 当会话完成、Code Review 通过、改动提交之后,系统会自动把 worktree 分支合并回 base 分支,移除 worktree,删除临时分支。用户不需要操心这一切。

整条流程对用户来说大致是这样:

Agent:我需要写 /projects/my-repo
Agent:create_worktree_binding(project="/projects/my-repo", task="add auth middleware")
系统:已创建 worktree
分支:aiagent/{session}/add-auth-middleware-a3f7c91d
base:main
Agent:[在 worktree 里完成全部工作]
Agent:[任务完成,Code Review 通过]
系统:stage → commit → 切回 main → 拉取最新 → 非快进合并 → 移除 worktree → 删除临时分支

整个过程 Agent 从来没有访问过 main。任务进行中 main 一刻也不会变脏。合并完成时,用户拿到的是一次干净的合并提交。

隔离不是一个开关,而是系统的默认形状。


4. 会话生命周期:从绑定到清理

Automatic Worktree 的会话生命周期

绑定建立之后,每一次写入都会被系统路由到 worktree 的路径下,原始 checkout 保持完全不动。

具体来说,create_worktree_binding 会做几件事:

  • 从给定路径向上回溯,找到 Git 仓库根(即用户当前打开的仓库)
  • 读取当前分支,作为后续合并的目标
  • 根据会话 ID 和任务描述生成分支名,形如 aiagent/{session}/{task}-{hash} —— 让任何一处改动都能溯源回产生它的会话
  • 在仓库之外的专用目录里建好 worktree
  • 在会话状态里登记一份「项目根 → worktree 路径、分支、base 分支」的映射

会话进入收尾阶段时,系统会按一个固定的次序处理合并和清理:

  1. 把 worktree 里所有未提交的改动 stage 并 commit
  2. 切回 base 分支,先 pull --ff-only 把远端的新提交拉下来
  3. 非快进合并把 worktree 分支并入 base —— 这一步会保留一个独立的 merge commit
  4. 移除 worktree 目录
  5. 删除临时分支

最后那一次非快进合并是有意为之:分支历史会被完整保留在 git log 里,每一段 Agent 工作都呈现为一次独立的合并节点。事后想 revert、想审计、想看「Agent 那次到底改了什么」,都有清晰的边界。

整段时间里,用户的 main 拿到的是一次合并提交;会话留下的中间状态全部消失。没有半成品文件,没有遗留分支,没有 worktree 目录残留。


5. 跨多仓库的会话:每个仓库一份独立绑定

一次会话,跨多个仓库的隔离工作

真实工程任务很少局限在一个仓库里。一次 gRPC 改造可能同时牵涉 backend、frontend、共享库三个仓库。一次行为埋点的变更可能要改业务代码也要改 SDK。

Helix 在 worktree 这一层就考虑了这一点:一个会话可以同时拥有多个 worktree 绑定,每个仓库一份。

会话状态里保存的是一份「项目根 → 仓库信息」的映射。每个仓库都有:

  • 它自己的隔离分支
  • 它自己的 worktree 目录
  • 它自己的 base 分支(每个仓库的主分支命名可能都不一样)

会话完成时,系统按顺序处理每一个绑定:依次合并、依次清理。任何一个仓库合并失败,错误会被显式抛出,那个仓库的 worktree 会被保留下来以便人工介入 —— 但已经成功合并的仓库不会被这一次失败牵连

这一点让跨仓库任务变得真正可用。一个会话可以同时跨五个仓库工作,期间没有任何一个仓库变脏;会话结束时,五个仓库各自拿到一次干净的合并提交。


6. 与 Manager Mode 协同:并行 SubAgent 共享一个安全边界

Execution Agent 与 SubAgent 共享同一个 worktree 边界

Worktree 系统在和 Manager Mode 的并行 SubAgent 执行结合起来之后,威力会显著放大。

在 Manager Mode 下,Execution Agent 可以并发派发多个 SubAgent。每个 SubAgent 是独立的上下文,有独立的工具调用、独立的 LLM 交互、独立的工作目标。如果没有 worktree 隔离,多个并行 SubAgent 同时写同一个仓库会立刻冲突

有了 worktree 隔离,事情变得不一样了:

  • worktree 由最上层的 Execution Agent 在派发 SubAgent 之前建立好
  • 所有 SubAgent 在同一个 worktree 路径下工作 —— 这个路径就是它们共享的隔离边界
  • SubAgent 自己不能创建新的 worktree 绑定 —— create_worktree_binding 这个工具在系统层面就被从 SubAgent 的工具列表中过滤掉了
  • 它们继承 Execution Agent 已经建立好的 worktree 上下文,但无权改变它

换句话说,无论并行跑十个还是二十个 SubAgent,仓库状态的唯一协调点始终是 Execution Agent。Manager Mode 在「目标范围」上守住边界,Automatic Worktree 在「物理写入」上守住边界。两者叠加之后,「让一群 Agent 在一个仓库里并发工作而不互相伤害」这件事,第一次变得不需要用户去操心。


7. Code Review 闸门:跳过 review,就跳过合并

worktree 的收尾 —— 合并加清理 —— 受 Code Review 的强制约束。会话不能完成并合并,除非 review 已通过。

这不是写在 prompt 里、靠 Agent「记得自我审查」的提示语。它是一个状态机检查:会话内部维护一个「review 是否通过」的标志位,没有置位的话,finalize 调用会直接拒绝执行,合并不会发生。

实际工作流被强制成这个顺序:

  1. Agent 判断自己已经完成任务
  2. 触发 Code Review
  3. Review 通过后,标志位被置位;review 不通过,会话需要继续改
  4. 总结改动
  5. 合并 worktree 分支到 main

跳过 Code Review 就意味着同时跳过合并。两者在系统层面是绑定在一起的 —— 你想绕过 review,就只能放弃合并。worktree 仍然安静地待在隔离目录里,等待人工处理。

这一条把「合并前评审」从一个良好实践,变成了一条 Agent 没法回避的路径。


8. 失败与清理:明确的破坏性边界

worktree 的创建和合并都可能部分失败 —— 比如目录已经存在但分支创建失败,或者会话在 finalize 之前被异常打断。Helix 用两种明确分开的方式处理这些情况:

收尾阶段显式失败: 错误会被原样暴露出来,会话不会被标记为完成。worktree 和分支都被保留 —— 用户可以查看现场、手动修复、再触发一次合并。这是「东西没坏,只是没合上」的路径。

未合并即丢弃: 当一个会话需要被放弃 —— 比如任务被用户取消、或者中途的错误让所有改动都不再需要 —— 系统会调用一个明确的「best-effort 清理」流程:直接移除 worktree 目录、强制删除还没合并的分支。

之所以是强制删除而不是普通删除,是因为这条清理路径本来就是处理未合并工作的。普通的 git branch -d 会拒绝删除一个未合并的分支,这在正常开发流程里是个保护机制 —— 而在「丢弃 Agent 半成品」这条路径上,它会反过来挡住清理。所以 Helix 在这一路径上明确选择了破坏性删除。

这条路径明确地承认了一件事:它代表的是被丢弃的工作。

边界清晰之后,用户的预期就稳定了:合并失败时东西还在、可以手动处理;放弃任务时东西干干净净没了,不留残留。两条路径不会互相干扰。


9. 为什么自动化优于 opt-in

支持 opt-in worktree 的理由通常是「把控制权留给用户」。用户可以在跑小任务时省去 worktree 的创建和清理开销。

反过来想这一点:「小任务」恰恰是绝大多数意外污染 main 的起点。

任务看起来很小,用户没有去配置 worktree。Agent 做了九件预期之内的事,外加一件没预期的事。然后用户就开始处理一份混杂的工作副本 —— 不知道哪些是想要的、哪些不是,回滚的时候找不到干净的边界。

Helix 的立场是:worktree 隔离的开销已经足够低 —— 创建一个 worktree 是 git 自身就支持的轻量操作,清理是自动的,分支命名是自动的 —— 这种取舍值得无条件做出。这一条约束,把「仓库状态管理」这一整类问题,从用户每天需要操心的事项里彻底移走了。

用户不需要去打开 worktree 隔离,也不需要记得在哪些任务上打开它。它就是 Helix 的工作方式。


10. 用户实际拿到的是什么

落到使用层面,Helix 的 Automatic Worktree 系统带来的体验是:

  • main 永远不会被 Agent 工作弄脏。 进行中的任务一直待在隔离分支和独立目录里。
  • 并行会话不会互相干扰。 每个会话有自己的 worktree、自己的分支。五个并发会话,五份完全干净的工作副本。
  • 合并不再是一道手动工序。 任务完成并通过 review 之后,改动会以一次规范的合并提交自动落到 main。
  • 历史干净、可追溯。 每一处 Agent 驱动的改动都呈现为一次独立的合并提交。分支名编码了会话 ID 和任务描述 —— 任何一段改动都能溯源回产生它的会话。
  • 回滚清晰。 如果某个会话最后产出了不想要的改动,那次合并提交本身就是一个干净的 revert 目标。不必再去拆解半成品的文件改动。
  • 跨仓库任务是一等公民。 一个会话能同时优雅地处理多个仓库的改动,每个仓库都拿到自己干净的合并提交。

11. 怎么用上它

worktree 隔离在每一个 Helix 会话里默认启用,不需要任何配置。

打开一个会话、跑一个会写入仓库的任务,Agent 在写入之前会自动处理 worktree 创建;任务完成时,合并和清理自动发生。用户的体验就是「跟一个 AI 同事说要做什么、看到结果落进 main」,中间所有隔离工作对用户完全透明。

想要直接观察这一行为:

  1. 在一个带 git 仓库的工作区里打开 Helix 会话
  2. 让 Agent 跑一个会修改仓库文件的任务
  3. 任务执行期间,查看 ~/.aiagent/worktree/ —— 你会看到隔离的工作副本
  4. 任务完成后,worktree 已经消失,改动以合并提交的形式落在了 main 上

对于跨多个仓库或者需要并行的复杂任务,把 Automatic Worktree 和 Manager Mode 搭配使用,可以让多个并行 SubAgent 在同一条隔离边界内安全协作,发挥全部能力。


12. 接下来的计划

Helix 在 worktree 工作流上还有几件正在打磨的事:

  • Worktree 检视 UI:直接从会话面板查看活跃 worktree、它们的分支以及待处理改动,不必再开终端去看 ~/.aiagent/worktree/
  • Selective merge:在改动落到 main 之前,对会话中的单个 commit 逐一批准或拒绝。
  • 跨会话 worktree 共享:让相关会话共享同一个 worktree 边界,进行协调一致的多会话协作。
  • 冲突解决工具:为那些自动合并失败、确实需要人工介入的情况提供更友好的 UI。

但有一条核心原则不会变:

Agent 在隔离分支上工作。main 只接收经过审慎评审的、规范的合并。

这是 Helix 把 Agent 当作工程系统、而不是当作聊天框来设计的必然结论之一。和 Manager Mode 的目标守护HelixVM 的执行边界 一起,它们共同构成了 Helix 对一个核心问题的回答:当 Agent 真的开始替你干活的时候,谁来守住它的边界?


想试试看?