构建mini Claude Code:12 - 从「文件冲突」到「分身协作」:Worktree 如何让多 Agent 安全并行

构建mini Claude Code:12 - 从「文件冲突」到「分身协作」:Worktree 如何让多 Agent 安全并行

📍 导航指南

这是「从零构建 Claude Code」系列的第十二篇。根据你的背景,选择合适的阅读路径:


目录

第一部分:理论基础 🧠

  • [多 Agent 并行的文件冲突问题](#多 Agent 并行的文件冲突问题 "#conflict-problem")
  • [git 分支给我们的启示](#git 分支给我们的启示 "#git-insight")
  • 核心洞察:隔离执行,统一协调

第二部分:机制设计 ⚙️

第三部分:代码实现 💻

  • [WorktreeManager 实现](#WorktreeManager 实现 "#worktree-impl")
  • [TaskManager 与 Worktree 绑定](#TaskManager 与 Worktree 绑定 "#task-binding")
  • 工具接口设计

第四部分:扩展方向 🔭

附录

  • [常见问题 FAQ](#常见问题 FAQ "#faq")

引言

上一篇我们让 Teammate 学会了「主动找活」:空闲时扫描任务看板、认领任务、自主执行。Agent 系统终于从「被动工具」变成了「自主团队」。

但随之而来一个新问题,而且是个根本性的问题:

ini 复制代码
场景:两个 Agent 同时处理同一个代码库

coder_1: [认领任务A:重构 auth.py 的登录逻辑]
coder_2: [认领任务B:给 auth.py 添加 OAuth 支持]

coder_1: [读取 auth.py,开始修改...]
coder_2: [读取 auth.py,开始修改...]

coder_1: [写入修改后的 auth.py]
coder_2: [写入修改后的 auth.py]  ← 覆盖了 coder_1 的修改!

谁来改这个文件?能改什么?改完怎么合并?

这不是调度问题,也不是锁的问题------锁只能让两个 Agent 串行,失去了并行的意义。这是一个隔离问题。

v11 的答案借鉴了 git 最核心的设计思想:分支

说明:v11_worktree_task_isolation.py 在任务看板的基础上,引入了 git worktree 机制,让每个任务在独立的文件系统目录中执行,完成后通过 git 合并回主分支。本文聚焦这个隔离机制的设计与实现。


第一部分:理论基础 🧠

多 Agent 并行的文件冲突问题

想象一个真实的软件团队同时处理同一个文件:

perl 复制代码
没有隔离的并行(危险):
  工作目录: /project/
    auth.py  ← 所有人都在改这个文件

  Agent A: read(auth.py) → 修改登录逻辑 → write(auth.py)
  Agent B: read(auth.py) → 添加 OAuth   → write(auth.py)

  结果: 后写入的覆盖先写入的,其中一个 Agent 的工作丢失

这个问题有几种「错误的解法」:

css 复制代码
错误解法1:加锁
  → Agent A 持有 auth.py 的锁
  → Agent B 等待...
  → 并行变串行,失去了多 Agent 的意义

错误解法2:任务分配时避免冲突
  → 需要提前知道每个任务会修改哪些文件
  → Agent 的工作是动态的,无法提前预知
  → 过于保守,大量任务被迫串行

错误解法3:事后合并(无隔离)
  → Agent A 和 B 都修改了 auth.py
  → 合并时发现冲突,但已经无法还原各自的修改意图
  → 合并质量极差

git 分支给我们的启示

git 早就解决了这个问题,答案是分支

bash 复制代码
git 的解法:
  main 分支: auth.py (原始版本)
      ↓
  ┌─────────────────────────────────┐
  │                                 │
  feature/login    feature/oauth
  auth.py (副本A)  auth.py (副本B)
      ↓                ↓
  修改登录逻辑      添加 OAuth
      ↓                ↓
  └─────────────────────────────────┘
              ↓
          git merge
              ↓
  main 分支: auth.py (合并版本)

关键洞察:每个分支都有自己的文件副本,互不干扰,最后通过 merge 合并

但普通的 git 分支有一个问题:git checkout 会切换整个工作目录,同一时刻只能在一个分支上工作。

这就是 git worktree 的用武之地。

核心洞察:隔离执行,统一协调

v11 的注释里有一句话:

vbnet 复制代码
Key insight: "Isolate by directory, coordinate by task ID."

翻译过来:用目录隔离执行,用任务 ID 协调控制

bash 复制代码
v10 的工作模型(共享目录):
  所有 Agent → 同一个工作目录 → 文件冲突

v11 的工作模型(隔离目录):
  任务A → worktree/auth-refactor/ → 独立修改
  任务B → worktree/oauth-support/ → 独立修改
  任务C → worktree/test-coverage/ → 独立修改

  完成后 → git merge → 主分支

两个平面分离:

  • 控制平面 :任务看板(.tasks/),记录任务状态、归属、绑定关系
  • 执行平面 :Worktree 目录(.worktrees/),每个任务有独立的文件系统

第二部分:机制设计 ⚙️

Worktree:文件系统的「分身」

git worktree 是 git 的一个功能,允许同一个仓库在多个目录中同时检出不同的分支:

bash 复制代码
# 创建一个新的 worktree
git worktree add -b wt/auth-refactor .worktrees/auth-refactor HEAD

# 结果:
# .worktrees/auth-refactor/  ← 独立目录,有完整的文件副本
#   auth.py                  ← 与主目录的 auth.py 完全独立
#   ...
# 分支:wt/auth-refactor     ← 独立的 git 分支

这正是我们需要的:

bash 复制代码
主工作目录 /project/
  auth.py (main 分支)

.worktrees/auth-refactor/
  auth.py (wt/auth-refactor 分支) ← Agent A 在这里工作

.worktrees/oauth-support/
  auth.py (wt/oauth-support 分支) ← Agent B 在这里工作

三个 auth.py,互不干扰。Agent A 和 B 可以真正并行,不需要任何锁。

任务看板:控制平面

任务文件(.tasks/task_12.json)记录了任务与 worktree 的绑定关系:

json 复制代码
{
  "id": 12,
  "subject": "Implement auth refactor",
  "status": "in_progress",
  "owner": "coder_1",
  "worktree": "auth-refactor",
  "blockedBy": [],
  "created_at": 1234567890,
  "updated_at": 1234567891
}

Worktree 索引(.worktrees/index.json)记录了所有 worktree 的生命周期状态:

json 复制代码
{
  "worktrees": [
    {
      "name": "auth-refactor",
      "path": "/project/.worktrees/auth-refactor",
      "branch": "wt/auth-refactor",
      "task_id": 12,
      "status": "active",
      "created_at": 1234567890
    }
  ]
}

两个文件互相引用:任务知道自己绑定了哪个 worktree,worktree 知道自己服务于哪个任务。

何时触发 Worktree,何时不触发

Worktree 不是自动触发的------它是 Agent 根据任务性质主动选择的工具。系统提示里给了明确的判断标准:

arduino 复制代码
"For parallel or risky changes: create tasks, allocate worktree lanes,
 run commands in those lanes, then choose keep/remove for closeout."

触发 Worktree 的场景

复制代码
✅ 并行修改(最典型):
  多个 Agent 同时处理同一代码库
  → 每个任务必须有独立的 worktree,否则文件互相覆盖

✅ 高风险修改:
  重构核心模块、修改数据库 schema、大规模重命名
  → 在隔离目录中实验,失败了直接 worktree_remove 丢弃
  → 主目录始终保持干净可运行状态

✅ 需要独立测试环境:
  修改依赖版本、更改配置文件
  → 在 worktree 中运行测试,不污染主目录的环境

不触发 Worktree 的场景

less 复制代码
❌ 只读操作:
  读取文件、查看日志、运行分析脚本
  → 不修改文件,不需要隔离

❌ 串行的简单修改:
  修改一个配置项、修复一个明确的 typo
  → 没有并行冲突风险,直接在主目录操作更简单

❌ 临时调试:
  加一行 print、查看变量值
  → 生命周期极短,worktree 的创建/删除开销不值得

❌ 跨任务有强依赖:
  任务 B 必须等任务 A 完成才能开始
  → 串行执行,不需要隔离

判断的核心问题只有两个:会不会并行?会不会有风险? 两个都否,直接操作主目录;任意一个是,创建 worktree。

生命周期:创建、执行、关闭

每个 worktree 经历完整的生命周期:

ini 复制代码
创建阶段:
  1. task_create → 在看板上创建任务
  2. worktree_create → 创建 git worktree + 绑定任务
     → git worktree add -b wt/{name} .worktrees/{name} HEAD
     → 更新 index.json
     → 更新 task.json (status: in_progress, worktree: name)

执行阶段:
  3. worktree_run → 在 worktree 目录中执行命令
     → 所有文件操作都在隔离目录中进行
  4. worktree_status → 查看 worktree 的 git 状态

关闭阶段(二选一):
  5a. worktree_remove (complete_task=true)
      → git worktree remove
      → task.status = "completed"
      → index.json status = "removed"

  5b. worktree_keep
      → index.json status = "kept"
      → worktree 目录保留,等待手动合并

关闭阶段的「二选一」设计很关键:

makefile 复制代码
worktree_remove: 直接删除
  适用于: 修改已经合并到主分支,或者任务失败需要丢弃

worktree_keep: 保留目录
  适用于: 需要手动审查后再合并,或者作为参考保留

EventBus:可观测性

多个 worktree 并行运行时,如何知道发生了什么?

v11 引入了 EventBus,将所有生命周期事件追加写入 .worktrees/events.jsonl

json 复制代码
{"event": "worktree.create.before", "ts": 1234567890, "task": {"id": 12}, "worktree": {"name": "auth-refactor"}}
{"event": "worktree.create.after",  "ts": 1234567891, "task": {"id": 12}, "worktree": {"name": "auth-refactor", "status": "active"}}
{"event": "worktree.remove.before", "ts": 1234567900, "task": {"id": 12}, "worktree": {"name": "auth-refactor"}}
{"event": "task.completed",         "ts": 1234567901, "task": {"id": 12, "status": "completed"}, "worktree": {"name": "auth-refactor"}}
{"event": "worktree.remove.after",  "ts": 1234567902, "task": {"id": 12}, "worktree": {"name": "auth-refactor", "status": "removed"}}

JSONL 格式(每行一个 JSON)的优点:

  • 追加写入,不需要读取整个文件再写回
  • 天然有序,按时间顺序排列
  • 并发安全,多个 worktree 同时写入不会互相覆盖(文件追加是原子的)

通过 worktree_events 工具,Agent 可以随时查看最近的事件,了解整个系统的运行状态。


第三部分:代码实现 💻

WorktreeManager 实现

WorktreeManager 是核心类,封装了所有 worktree 操作:

python 复制代码
class WorktreeManager:
    def __init__(self, repo_root: Path, tasks: TaskManager, events: EventBus):
        self.repo_root = repo_root
        self.tasks = tasks
        self.events = events
        self.dir = repo_root / ".worktrees"
        self.index_path = self.dir / "index.json"

创建 worktree

python 复制代码
def create(self, name: str, task_id: int = None, base_ref: str = "HEAD") -> str:
    self._validate_name(name)          # 名称只允许字母数字.-_
    if self._find(name):
        raise ValueError(f"Worktree '{name}' already exists")

    path = self.dir / name
    branch = f"wt/{name}"

    self.events.emit("worktree.create.before", ...)
    self._run_git(["worktree", "add", "-b", branch, str(path), base_ref])

    # 更新 index.json
    entry = {"name": name, "path": str(path), "branch": branch,
             "task_id": task_id, "status": "active", ...}
    idx = self._load_index()
    idx["worktrees"].append(entry)
    self._save_index(idx)

    # 绑定任务
    if task_id is not None:
        self.tasks.bind_worktree(task_id, name)

    self.events.emit("worktree.create.after", ...)
    return json.dumps(entry, indent=2)

名称验证是安全关键点:

python 复制代码
def _validate_name(self, name: str):
    if not re.fullmatch(r"[A-Za-z0-9._-]{1,40}", name or ""):
        raise ValueError("Invalid worktree name. Use 1-40 chars: letters, numbers, ., _, -")

这防止了路径注入攻击------如果 name 包含 ../ 或空格,git worktree add 可能产生意外行为。

在 worktree 中执行命令

python 复制代码
def run(self, name: str, command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"

    wt = self._find(name)
    path = Path(wt["path"])

    r = subprocess.run(command, shell=True, cwd=path, ...)
    return (r.stdout + r.stderr).strip()[:50000]

关键:cwd=path 确保命令在 worktree 目录中执行,而不是主工作目录。

TaskManager 与 Worktree 绑定

TaskManager 新增了两个方法处理绑定关系:

python 复制代码
def bind_worktree(self, task_id: int, worktree: str, owner: str = "") -> str:
    task = self._load(task_id)
    task["worktree"] = worktree
    if owner:
        task["owner"] = owner
    if task["status"] == "pending":
        task["status"] = "in_progress"   # 绑定时自动推进状态
    task["updated_at"] = time.time()
    self._save(task)
    return json.dumps(task, indent=2)

def unbind_worktree(self, task_id: int) -> str:
    task = self._load(task_id)
    task["worktree"] = ""               # 清空绑定
    task["updated_at"] = time.time()
    self._save(task)
    return json.dumps(task, indent=2)

worktree_remove 时的完整清理流程:

python 复制代码
def remove(self, name: str, force: bool = False, complete_task: bool = False) -> str:
    wt = self._find(name)

    # 1. 删除 git worktree
    self._run_git(["worktree", "remove", wt["path"]])

    # 2. 如果需要,完成关联任务
    if complete_task and wt.get("task_id") is not None:
        task_id = wt["task_id"]
        self.tasks.update(task_id, status="completed")
        self.tasks.unbind_worktree(task_id)
        self.events.emit("task.completed", ...)

    # 3. 更新 index.json 状态
    for item in idx["worktrees"]:
        if item["name"] == name:
            item["status"] = "removed"
            item["removed_at"] = time.time()
    self._save_index(idx)

complete_task=True 是一个便利参数:删除 worktree 的同时标记任务完成,一步完成收尾工作。

工具接口设计

v11 提供了 11 个工具,分为三组:

makefile 复制代码
基础工具(4个):
  bash, read_file, write_file, edit_file
  → 在主工作目录操作

任务工具(5个):
  task_create, task_list, task_get, task_update, task_bind_worktree
  → 管理控制平面(.tasks/)

Worktree 工具(6个):
  worktree_create   → 创建 worktree + 可选绑定任务
  worktree_list     → 列出所有 worktree
  worktree_status   → 查看某个 worktree 的 git 状态
  worktree_run      → 在 worktree 中执行命令
  worktree_keep     → 标记保留(不删除)
  worktree_remove   → 删除 worktree + 可选完成任务
  worktree_events   → 查看生命周期事件

典型的 Agent 工作流:

ini 复制代码
1. task_create(subject="重构登录逻辑")
   → 创建任务 #12

2. worktree_create(name="auth-refactor", task_id=12)
   → 创建 .worktrees/auth-refactor/
   → 创建分支 wt/auth-refactor
   → 任务 #12 绑定到 auth-refactor

3. worktree_run(name="auth-refactor", command="cat auth.py")
   → 在隔离目录中读取文件

4. worktree_run(name="auth-refactor", command="python -m pytest tests/")
   → 在隔离目录中运行测试

5. worktree_status(name="auth-refactor")
   → 查看修改了哪些文件

6. worktree_run(name="auth-refactor", command="git add -A && git commit -m 'refactor login'")
   → 提交修改到 wt/auth-refactor 分支

7. worktree_remove(name="auth-refactor", complete_task=true)
   → 删除 worktree 目录
   → 任务 #12 标记为 completed
   → (手动或自动 merge wt/auth-refactor 到 main)

第四部分:扩展方向 🔭

自动合并策略

v11 的当前实现中,合并是手动的------Agent 完成工作后,需要人工执行 git merge。更完整的实现可以自动化这个步骤:

python 复制代码
def auto_merge(self, name: str, target_branch: str = "main") -> str:
    wt = self._find(name)
    branch = wt["branch"]  # wt/auth-refactor

    # 在主仓库中执行 merge
    try:
        self._run_git(["merge", "--no-ff", branch, "-m", f"Merge {branch}"])
        return f"Merged {branch} into {target_branch}"
    except RuntimeError as e:
        # 合并冲突,需要人工介入
        return f"Merge conflict: {e}\nManual resolution required."

更智能的策略是在 worktree_remove 时提供 merge_strategy 参数:

python 复制代码
worktree_remove(name="auth-refactor",
                complete_task=True,
                merge_strategy="squash")  # squash / merge / rebase

多级隔离

当前实现是「任务级隔离」:每个任务一个 worktree。更细粒度的设计是「步骤级隔离」:

bash 复制代码
任务 #12: 重构登录逻辑
  ├─ 步骤1: 提取 LoginService → worktree/auth-step1/
  ├─ 步骤2: 添加单元测试    → worktree/auth-step2/
  └─ 步骤3: 更新 API 文档   → worktree/auth-step3/

每个步骤完成后合并到任务分支,任务完成后合并到主分支。

这种「树状合并」策略在大型重构中特别有用,可以精确控制每个步骤的影响范围。


常见问题 FAQ

Q: git worktree 和普通 git branch 有什么区别?

A: 普通 git checkout branch 会切换整个工作目录,同一时刻只能在一个分支上工作。git worktree 允许同一个仓库在多个目录中同时检出不同分支,互不干扰。

bash 复制代码
# 普通分支:切换工作目录(破坏性)
git checkout feature/auth  # 整个目录变了

# worktree:新增目录(非破坏性)
git worktree add -b feature/auth .worktrees/auth HEAD
# 主目录不变,.worktrees/auth/ 是新的独立目录

Q: worktree 目录和主目录共享 git 历史吗?

A: 是的。所有 worktree 共享同一个 .git 目录(通过 .git/worktrees/ 链接),所以它们共享完整的 git 历史、远程配置、标签等。只有工作目录和当前分支是独立的。

Q: 如果 Agent 在 worktree 中崩溃了,怎么清理?

A: worktree 目录会残留,但不影响主仓库。可以手动清理:

bash 复制代码
# 列出所有 worktree
git worktree list

# 删除残留的 worktree
git worktree remove --force .worktrees/auth-refactor

# 清理无效引用
git worktree prune

v11 的 worktree_remove(force=True) 对应 git worktree remove --force,可以强制删除有未提交修改的 worktree。

Q: 多个 Agent 可以同时操作同一个 worktree 吗?

A: 不建议。每个 worktree 应该只有一个 Agent 在操作。v11 通过任务绑定(task.owner)来保证这一点------一个任务只能被一个 Agent 认领,一个 worktree 只绑定一个任务。

Q: worktree 的数量有限制吗?

A: git 本身没有硬性限制,但每个 worktree 都是完整的文件系统副本,会占用磁盘空间。对于大型代码库,建议及时清理已完成的 worktree。


📝 结语

从 v10 到 v11,核心变化只有一个:给每个任务一个独立的文件系统目录。但这个改变背后的思想值得细品:

arduino 复制代码
v10 的问题:
  多 Agent 共享同一个工作目录
  文件修改互相覆盖
  并行执行存在根本性冲突

v11 的解决:
  git worktree → 每个任务有独立的文件副本
  任务看板    → 控制平面,记录绑定关系
  EventBus    → 可观测性,追踪生命周期
  keep/remove → 灵活的关闭策略

更深层的洞察是:并行的本质是隔离

不是「谁先抢到锁谁先改」,而是「每个人在自己的空间里改,改完再合并」。这正是 git 分支模型的精髓,也是 v11 把它引入 Agent 系统的原因。

makefile 复制代码
系列能力演进:
  上下文压缩(v5)    → Agent 能长时间运行
  持久化任务(v6)    → Agent 能跨会话追踪任务
  后台执行(v7)      → Agent 能并行处理任务
  Agent Teams(v8)   → Agent 能组建团队协作
  Team Protocols(v9)→ Agent 团队能有序协调
  Autonomous(v10)   → Agent 能主动找工作、自主执行
  Worktree(v11)     → Agent 能安全并行、隔离执行
                        ↓
              真正的「并行自主 Agent 系统」

七个能力叠加,才能处理真实世界的复杂任务:长时间、多步骤、有依赖、可并行、需协作、能协调、会自主、不冲突

系列导航

相关推荐
yuki_uix1 小时前
为什么我的 Auth Token 藏在了 Network 面板的 Doc 里?
前端·python·debug
老纪的技术唠嗑局1 小时前
OpenClaw 是怎么让 AI 变得 “像人” 的?
人工智能
算法备案代理1 小时前
深度合成算法备案:生成式AI需要备案吗?
人工智能·算法·算法备案
沪漂阿龙2 小时前
大模型选型决策全流程:从需求分析到生产上线的六步法
人工智能·数据挖掘·需求分析
琅琊榜首20202 小时前
移动端AI挂机新范式:YOLOv8+NCNN实现无Root视觉自动化
人工智能·yolo·自动化
甲枫叶2 小时前
【claude+weelinking产品经理系列16】数据可视化——用图表讲述产品数据的故事
java·人工智能·python·信息可视化·产品经理·ai编程
大模型真好玩2 小时前
LangChain DeepAgents 速通指南(二)—— Summarization中间件为Agent作记忆加减法
人工智能·langchain·agent
北辰alk2 小时前
大模型微调技术全景解析:从LoRA到RLHF的演进之路
人工智能
番茄去哪了2 小时前
Python基础入门(二)
linux·服务器·开发语言·python