s01 → ... → s16 → s17 → s18 → s19
"各干各的目录, 互不干扰" — 任务管目标, worktree 管目录, 按 ID 绑定。
Harness 层: 隔离 — 永不碰撞的并行执行通道。
s17 中,Alice 和 Bob 都在同一个目录下工作。Alice 的任务是"重构认证模块",Bob 的任务是"重构 UI 登录页"。
Alice write_file("config.py", ...)。Bob 也 write_file("config.py", ...)。两个人改同一个文件,互相覆盖。而且无法干净地回滚——分不清哪些改动是谁的。
共享文件系统是多 Agent 协作的灾难。 s15-s17 解决了"谁干什么"(任务系统)和"怎么通信"(消息总线),但没解决"在哪干"。
Git worktree 让你在同一仓库中创建多个独立的工作目录,每个有自己的分支。Alice 在 .worktrees/auth-refactor/ 下工作,Bob 在 .worktrees/ui-login/ 下工作——互不干扰。
s17 的全部能力保留(自主认领、空闲轮询、消息总线)。新增三样:
| 能力 | 作用 |
|---|---|
| create_worktree | 为任务创建独立目录 + 独立分支 |
| bind_task_to_worktree | 把任务和工作目录绑定 |
| remove_worktree / keep_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——任务有了自己的工作空间,等于开工了。
任务完成后,两个选择:
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) | 之后 (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 字符。
ExitWorktree:action: '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 状态通过 PersistedWorktreeSession(utils/sessionStorage.ts)管理,字段包括 originalCwd、worktreePath、worktreeName、worktreeBranch、originalBranch、originalHeadCommit、sessionId、tmuxSessionName、hookBased——没有 taskId。状态通过 saveWorktreeState() 写入当前 session 的 transcript 文件(type: 'worktree-state'),不是单独的文件。教学版用 events.jsonl 记录生命周期事件;CC 用 session transcript + sessionStorage。