S12 Worktree 任务隔离详解笔记

基于 s12_worktree_task_isolation.py 源码逐行分析,配合 s12-worktree-task-isolation.md 设计思路。


一、问题:多个任务共享一个工作目录,互相踩踏

前面 11 章的 agent 都在同一个工作目录下操作。当只有一个 agent 时没问题。但当 s09-s11 引入了多 agent 团队后:

  • Alice 在改 utils.py,Bob 也在改 utils.py → 冲突
  • Alice 跑了 pip install flask,Bob 跑了 pip install django → 全局环境污染
  • Alice 的测试需要 Python 3.10,Bob 需要 3.8 → 不可能同时满足

共享文件系统是多 agent 并行的最大阻碍。 每个 agent 需要一个独立的工作空间,但又能共享代码基础。

s12 的解决方案:Git worktree。 每个任务分配一个独立的 worktree 目录,agent 在里面为所欲为——修改、装包、跑测试——不影响其他人。完成后,worktree 可以被保留(keep)或删除(remove)。


二、什么是 Git Worktree?

初学者git worktree 允许你从同一个仓库创建多个工作目录,每个目录有自己的分支。它们共享同一个 .git 目录(节省磁盘空间),但文件系统完全隔离。

主仓库:    /project                    (branch: main)
worktree:  /project/.worktrees/auth    (branch: wt/auth)
worktree:  /project/.worktrees/api     (branch: wt/api)

每个 worktree 像一个独立的克隆,但不需要重新 clone 整个仓库。创建 worktree 通常只需要几秒钟。


三、和 s11 相比,多了什么?

组件 s11 s12
执行隔离 无(共享目录) Git worktree(目录级隔离)
任务绑定 task.worktree 字段 + bind_worktree()
事件追踪 EventBus:生命周期事件日志
新工具 idle, claim_task worktree_create/list/status/run/keep/remove + worktree_events
工具数量 14 16

四、EventBus:可观测性的基础设施

class EventBus:
    def __init__(self, event_log_path: Path):
        self.path = event_log_path
        self.path.parent.mkdir(parents=True, exist_ok=True)

    def emit(self, event: str, task=None, worktree=None, error=None):
        payload = {
            "event": event,
            "ts": time.time(),
            "task": task or {},
            "worktree": worktree or {},
        }
        if error:
            payload["error"] = error
        with self.path.open("a") as f:
            f.write(json.dumps(payload) + "\n")

    def list_recent(self, limit: int = 20) -> str:
        # 返回最近 N 条事件,用于调试和监控

EventBus 记录 worktree 和 task 的完整生命周期:

worktree.create.before → worktree.create.after (或 .failed)
worktree.remove.before → worktree.remove.after (或 .failed)
worktree.keep
task.completed

这不是给模型看的——是给开发者运维工具看的。当系统出现异常(worktree 创建失败、任务卡住),事件日志是回溯问题的第一手资料。s12 是系列中第一个引入面向人类的可观测性的章节。


五、WorktreeManager:Git Worktree 的生命周期

5.1 初始化

class WorktreeManager:
    def __init__(self, repo_root: Path, tasks: TaskManager, events: EventBus):
        self.git_available = self._is_git_repo()
        self.index_path = self.dir / "index.json"

index.json 是 worktree 的注册表——记录所有 worktree 的名称、路径、分支、关联任务、状态。

5.2 create() — 创建 worktree

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", ...)

    # 核心:执行 git worktree add
    self._run_git(["worktree", "add", "-b", branch, str(path), base_ref])

    # 写入 index
    entry = {"name": name, "path": str(path), "branch": branch,
             "task_id": task_id, "status": "active", "created_at": time.time()}
    idx["worktrees"].append(entry)

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

    self.events.emit("worktree.create.after", ...)

base_ref 默认为 HEAD(当前分支的最新提交)。名字校验 [A-Za-z0-9._-]{1,40} 阻止特殊字符——因为 worktree 名会变成目录名和分支名。

5.3 run() — 在 worktree 中执行命令

def run(self, name: str, command: str) -> str:
    wt = self._find(name)
    path = Path(wt["path"])

    r = subprocess.run(
        command,
        shell=True,
        cwd=path,              # ← 关键:cwd 指向 worktree 目录
        capture_output=True,
        text=True,
        timeout=300,
    )
    return (r.stdout + r.stderr).strip()[:50000]

run_bash 的唯一区别:cwd 指向 worktree 目录而非主工作目录。agent 可以在 worktree 中装包、改代码、跑测试——一切操作隔离在 worktree 内

5.4 remove() — 删除 worktree

def remove(self, name: str, force: bool = False, complete_task: bool = False):
    self._run_git(["worktree", "remove", "--force" if force else "", wt["path"]])

    if complete_task and wt.get("task_id") is not None:
        self.tasks.update(task_id, status="completed")
        self.tasks.unbind_worktree(task_id)

    idx["worktrees"][...]["status"] = "removed"

如果 complete_task: true,自动把关联任务标记为完成并解绑——一条命令完成"清理 workspace + 完成任务"。

5.5 keep() — 保留 worktree

def keep(self, name: str) -> str:
    idx["worktrees"][...]["status"] = "kept"
    idx["worktrees"][...]["kept_at"] = time.time()

不删除 worktree,只在 index 中标记为 kept。适合"代码还需要审查"或"先保留以备后续修改"的场景。


六、TaskManager 的扩展

s12 的 TaskManager 在 s07 的基础上新增了 worktree 字段和 bind_worktree / unbind_worktree 方法:

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"
    self._save(task)

def unbind_worktree(self, task_id: int) -> str:
    task = self._load(task_id)
    task["worktree"] = ""
    self._save(task)

任务和 worktree 之间是一对一的关系。task.worktree 字段记录了"这个任务在哪个隔离环境中执行"。


七、两个 Manager 的协作

Task 创建 → task.status = "pending"
    ↓
Worktree 创建 → task.bind_worktree(task_id, wt_name)
    ↓           task.status → "in_progress"
Agent 在 worktree 中工作 → worktree_run(wt_name, "pip install flask")
    ↓
工作完成 → worktree_remove(name, complete_task=True)
    ↓           task.status → "completed"
              task.worktree → ""

Tasks 是控制面(管"该做什么"),Worktrees 是执行面(管"在哪里做")。两者的关联通过 task.worktree 字段 + bind_worktree 方法桥接。


八、完整流程走读

场景

用户:“重构认证模块,在隔离环境中进行,完成后清理。”

第 1 轮

Lead 创建任务:task_create("重构认证模块") → task 12。

第 2 轮

Lead 创建 worktree:worktree_create("auth-refactor", task_id=12) → 在 .worktrees/auth-refactor/ 创建隔离目录,分支 wt/auth-refactor。Task 12 的 status 自动变为 in_progressworktree 设为 "auth-refactor"

第 3-10 轮

Lead 在 worktree 中工作:worktree_run("auth-refactor", "pip install bcrypt")worktree_run("auth-refactor", "python -m pytest tests/auth/")。所有修改在 wt/auth-refactor 分支上进行,主工作区完全不受影响。

第 11 轮

Lead 完成工作:worktree_remove("auth-refactor", complete_task=True) → worktree 被删除,Task 12 自动标记为 completed。分支可以后续合并回 main。

如果出错了

worktree_events(limit=10) 显示最近 10 条事件:

{"event": "worktree.create.after", "worktree": {"name": "auth-refactor", "status": "active"}}
{"event": "worktree.remove.failed", "worktree": {"name": "auth-refactor"}, "error": "worktree contains uncommitted changes"}

Lead 看到错误,改用 worktree_remove("auth-refactor", force=True) 强制删除,或用 worktree_keep("auth-refactor") 保留下来手动处理。


九、设计洞察

9.1 控制面与执行面的分离

Tasks(控制面)回答"做什么",Worktrees(执行面)回答"在哪里做"。两者通过 task.worktree 字段关联,各自独立演化。这种分离让系统可以:

  • 换一种执行隔离方式(容器而非 worktree)而不影响任务管理
  • 换一种任务管理方式(Kanban 而非 JSON)而不影响执行隔离

9.2 Git 作为平台

s12 大量依赖 Git(worktree addworktree removerev-parse)。在很多人眼中 Git 是"版本控制工具",但在这里 Git 是文件系统管理平台git worktree 解决了"在同一仓库中创建多个独立工作目录"的问题——这个问题自己实现可能需要上千行代码。

站在巨人的肩膀上,而不是自己造轮子。 这是工程中最重要的判断力之一。

9.3 可观测性不是后加的

EventBus 从 worktree 创建的第一步就记录事件。事件覆盖了完整的生命周期:before → after/failed,每条事件带时间戳、关联的 task、关联的 worktree。当系统变得不可预测(异步操作、多线程、外部依赖),事件日志是你理解"发生了什么"的唯一窗口。

9.4 s12 是整个系列的终章——也是新起点

回头看整个系列:

s01: 循环          ← agent 能动了
s02: 工具          ← agent 能操作文件系统了
s03: 计划          ← agent 能追踪进度了
s04: 子 agent     ← agent 能委托任务了
s05: 知识          ← agent 能按需学习领域专长了
s06: 压缩          ← agent 能无限工作了
s07: 任务图        ← 状态在对话之外存活了
s08: 后台          ← agent 不等命令了
s09: 团队          ← agent 能合作了
s10: 协议          ← agent 能握手了
s11: 自主          ← agent 自己找工作了
s12: 隔离          ← agent 能在独立空间中工作了

s12 的 worktree 隔离让多 agent 并行成为可能——这是从"单机单 agent"到"分布式 agent 网络"的桥梁。虽然这个系列的代码在 s12 结束,但它指向的方向远不止于此。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐