Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

README.md

s18: Worktree Isolation — 各干各的,互不干扰

中文 · English · 日本語

s01 → ... → s16 → s17 → s18s19

"各干各的目录, 互不干扰" — 任务管目标, worktree 管目录, 按 ID 绑定。

Harness 层: 隔离 — 永不碰撞的并行执行通道。


问题

s17 中,Alice 和 Bob 都在同一个目录下工作。Alice 的任务是"重构认证模块",Bob 的任务是"重构 UI 登录页"。

Alice write_file("config.py", ...)。Bob 也 write_file("config.py", ...)。两个人改同一个文件,互相覆盖。而且无法干净地回滚——分不清哪些改动是谁的。

共享文件系统是多 Agent 协作的灾难。 s15-s17 解决了"谁干什么"(任务系统)和"怎么通信"(消息总线),但没解决"在哪干"。


解决方案

Worktree Overview

Git worktree 让你在同一仓库中创建多个独立的工作目录,每个有自己的分支。Alice 在 .worktrees/auth-refactor/ 下工作,Bob 在 .worktrees/ui-login/ 下工作——互不干扰。

s17 的全部能力保留(自主认领、空闲轮询、消息总线)。新增三样:

能力 作用
create_worktree 为任务创建独立目录 + 独立分支
bind_task_to_worktree 把任务和工作目录绑定
remove_worktree / keep_worktree 完成后清理或保留

工作原理

创建:任务-Worktree 绑定

WORKTREES_DIR = WORKDIR / ".worktrees"

def create_worktree(name: str, task_id: int = None) -> str:
    """创建 worktree,可选绑定到任务。"""
    path = WORKTREES_DIR / name
    path.mkdir(parents=True, exist_ok=True)
    run_git(["worktree", "add", str(path), "-b", f"wt/{name}", "HEAD"])
    if task_id:
        bind_task_to_worktree(task_id, name)
    return str(path)

def bind_task_to_worktree(task_id: int, worktree_name: str):
    task = load_task(task_id)
    task["worktree"] = worktree_name
    if task["status"] == "pending":
        task["status"] = "in_progress"
    save_task(task)

绑定规则:一个任务绑定一个 worktree。绑定后任务自动推进到 in_progress——任务有了自己的工作空间,等于开工了。

收尾:Keep 还是 Remove

任务完成后,两个选择:

def keep_worktree(name: str):
    """保留 worktree,后续手动合并。"""

def remove_worktree(name: str, complete_task: bool = True):
    """删除 worktree,可选标记任务完成。"""
    wt = load_worktree(name)
    run_git(["worktree", "remove", wt["path"], "--force"])
    if complete_task and wt.get("task_id"):
        update_task(wt["task_id"], status="completed")

Keep = 留着分支,等人工 review 后合并到主分支。Remove = 删目录 + 删分支 + 标记任务完成。一次收尾。

事件流

每次生命周期操作写入日志,崩溃后可重建现场:

def log_event(event_type: str, worktree_name: str, task_id: int = None):
    event = {"type": event_type, "worktree": worktree_name,
             "task_id": task_id, "ts": time.time()}
    events_file = WORKTREES_DIR / "events.jsonl"
    with open(events_file, "a") as f:
        f.write(json.dumps(event) + "\n")

事件类型:create(创建)、remove(删除)、keep(保留)。重建逻辑:逐行读 events.jsonl,每个 worktree 取最后一条事件——create 说明还活着,remove 说明已清理。


相对 s17 的变更

组件 之前 (s17) 之后 (s18)
工作目录 所有 Agent 共享 WORKDIR 每个任务独立 git worktree
Task 数据 id/subject/status/owner/blockedBy + worktree 字段(绑定目录)
新函数 create_worktree, bind_task_to_worktree, remove_worktree, keep_worktree, log_event
隔离方式 git worktree add + 独立分支 wt/{name}
收尾 任务完成 keep(保留分支)/ remove(删除+标记完成)
崩溃恢复 events.jsonl 生命周期日志
Lead 工具 13 (s17) + create_worktree, remove_worktree, keep_worktree (16)
队友工具 7 (s17) 7(不变,auto-claimed 消息附带 worktree 路径)

试一下

cd learn-claude-code
python s18_worktree_isolation/code.py

试试这个 prompt:

Create two tasks, then create isolated worktrees for each. Run git status in each worktree to confirm isolation.

观察重点:两个 worktree 的 git status 输出是否显示不同的分支?在一个 worktree 里修改文件,另一个 worktree 是否不受影响?


接下来

现在 Agent 团队能在隔离的工作空间中自组织了。但 Agent 的能力受限于我们给它写的工具——bash、read、write、glob、task...

如果用户已经有了自己的工具怎么办?比如一个公司内部的 Jira API、一个自建的部署系统?难道要重写一遍?

s19 MCP Plugin → 给 Agent 装一个插件系统。外部工具通过标准协议接入,Agent 不需要知道它们是谁写的。

深入 CC 源码

CC 的 worktree 系统由两个工具驱动(EnterWorktreeTool.ts 127 行、ExitWorktreeTool.ts 329 行):

EnterWorktree:接受可选的 name 参数创建新 worktree。省略 name 时自动生成随机名。新 worktree 的 branch 名基于 HEAD,创建在 .claude/worktrees/ 下。每个 worktree 的 / 分隔段只能包含字母、数字、点、下划线、短横线,最大 64 字符。

ExitWorktreeaction: 'keep'(保留目录和分支)或 'remove'(删除两者)。Remove 时如果有未提交的改动,除非设置 discard_changes: true,否则工具会拒绝执行——这是安全机制,防止丢失工作。

isolation:AgentTool 的 isolation: 'worktree' 参数让子 Agent 在 worktree 中运行。这是 fork subagent(s06 分析过)的一种隔离模式。

isDestructive:ExitWorktree 的 isDestructive(input) 返回 input.action === 'remove'——只在真正删除时才标记为 destructive(这是 CC 中极少数覆写了 isDestructive 的工具之一,见 s03 分析)。

教学版 vs CC 的关键差异:教学版做了 task-worktree 双向绑定(Task 数据加 worktree 字段),CC 没有这种机制。CC 的 worktree 状态通过 PersistedWorktreeSessionutils/sessionStorage.ts)管理,字段包括 originalCwdworktreePathworktreeNameworktreeBranchoriginalBranchoriginalHeadCommitsessionIdtmuxSessionNamehookBased——没有 taskId。状态通过 saveWorktreeState() 写入当前 session 的 transcript 文件(type: 'worktree-state'),不是单独的文件。教学版用 events.jsonl 记录生命周期事件;CC 用 session transcript + sessionStorage。