该系列文章基于
github.com/shareAI-lab/learn-claude-code写就,该仓库以大道至简的风格剖析了Claude Code的核心原理,值得大家学习。由于该仓库是基于Python语言,为方便.NET开发者学习,我已经将代码基于.NET 10的dotnet file重写,源码已上传至github,源码地址见文末。
本文是 Learn Claude Code (C# 版) 系列的第十二篇,对应代码文件
v11_worktree_task_isolation.cs。
v10 让队友能自治地认领任务,但所有智能体仍然共享同一个工作目录:
fe-dev 正在修改 src/App.tsx 的第42行...
be-dev 同时修改 src/App.tsx 的第42行...
→ 冲突!
→ 一个人的改动覆盖另一个人的改动
→ 文件系统没有并发控制
这就像多个开发者直接在同一台机器的同一个文件夹里写代码,不用 Git 分支,不用隔离——灾难是必然的。
目录隔离,任务 ID 协调——用"任务板(控制面)+ worktree(执行面)"把并行改动从互相污染变成可追踪、可恢复、可收尾。
控制面 (主仓库) 执行面 (worktrees)
+------------------+ create +------------------+
| .worktrees/ | ────────→ | ../repo-wt-fe/ |
| index.json | | (独立目录) |
| events.jsonl | | branch: wt/fe |
| | +------------------+
| .tasks/ | create +------------------+
| task_001.json | ────────→ | ../repo-wt-be/ |
| task_002.json | | (独立目录) |
+------------------+ | branch: wt/be |
+------------------+
任务看板调度 隔离的代码修改
替代 JSONL 收件箱,使用全局事件日志:
class EventBus
{
string eventsFile; // .worktrees/events.jsonl
public void Emit(string type, string source, Dictionary<string, object> data)
{
var ev = new
{
id = Guid.NewGuid().ToString("N")[..8],
type, source, data,
timestamp = DateTime.UtcNow.ToString("o")
};
// Append-only, 线程安全
lock (writeLock)
{
File.AppendAllText(eventsFile, JsonSerializer.Serialize(ev) + "\n");
}
}
public List<...> Read(int sinceIndex = 0)
{
// 从指定位置开始读, 支持增量读取
return allEvents.Skip(sinceIndex).ToList();
}
}在 v10 基础上增加 worktree 绑定:
class TaskManager
{
public void BindWorktree(string taskId, string worktreeName)
{
var task = Load(taskId);
task["worktree"] = worktreeName;
Save(taskId, task);
eventBus.Emit("task.worktree_bound", "system",
new() { ["task_id"] = taskId, ["worktree"] = worktreeName });
}
public void UnbindWorktree(string taskId)
{
var task = Load(taskId);
task.Remove("worktree");
Save(taskId, task);
}
}class WorktreeManager
{
string repoRoot; // git rev-parse --show-toplevel
string worktreeBaseDir; // .worktrees/
Dictionary<string, WtInfo> index; // index.json 注册表
public string Create(string name)
{
var branch = $"wt/{name}";
var absPath = Path.Combine(repoRoot, "..", $"{repoName}-wt-{name}");
// git worktree add -b wt/<name> <path>
RunGit($"worktree add -b {branch} {absPath}");
index[name] = new WtInfo(name, absPath, branch);
SaveIndex();
eventBus.Emit("worktree.created", "lead",
new() { ["name"] = name, ["path"] = absPath });
return $"Created worktree '{name}' at {absPath}";
}
}| 操作 | 说明 |
|---|---|
worktree_create |
创建新的 git worktree(独立分支 + 独立目录) |
worktree_list |
列出所有已创建的 worktree |
worktree_status |
查看指定 worktree 的 git 状态 |
worktree_run |
在指定 worktree 目录中执行命令 |
worktree_remove |
删除 worktree 及其分支 |
worktree_keep |
标记 worktree 为"保留"(合并回主分支) |
public string RunInWorktree(string name, string command)
{
var wt = index[name];
var proc = Process.Start(new ProcessStartInfo
{
FileName = IsWindows ? "cmd.exe" : "/bin/bash",
Arguments = IsWindows ? $"/c {command}" : $"-c \"{command}\"",
WorkingDirectory = wt.AbsPath, // 关键: 切换到 worktree 目录
RedirectStandardOutput = true
});
// ...
}public string KeepWorktree(string name, string? mergeMessage)
{
var wt = index[name];
// git checkout main && git merge wt/<name>
RunGit($"checkout main");
RunGit($"merge {wt.Branch} -m \"{mergeMessage ?? $"Merge {name}"}\"");
wt.Status = "kept";
SaveIndex();
return $"Merged {name} into main.";
}
public string RemoveWorktree(string name)
{
var wt = index[name];
// git worktree remove <path> && git branch -D wt/<name>
RunGit($"worktree remove {wt.AbsPath} --force");
RunGit($"branch -D {wt.Branch}");
index.Remove(name);
SaveIndex();
return $"Removed worktree '{name}'.";
}v11 包含 17 个工具,是系列中最丰富的:
| 类别 | 工具 |
|---|---|
| 基础 (4) | bash, read_file, write_file, edit_file |
| 任务 (5) | task_create, task_update, task_list, task_get, task_bind_worktree |
| Worktree (7) | worktree_create, worktree_list, worktree_status, worktree_run, worktree_remove, worktree_keep, worktree_diff |
| 事件 (1) | read_events |
1. Lead 创建任务:
task_create("实现登录页面") → task_001
2. 创建隔离 worktree:
worktree_create("login") → ../repo-wt-login/
3. 绑定任务到 worktree:
task_bind_worktree("001", "login")
4. 在隔离目录中工作:
worktree_run("login", "echo 'working...'")
5. 完成后合并:
worktree_keep("login", "feat: add login page")
6. 或者丢弃:
worktree_remove("login")
| 组件 | 之前 (v10) | 之后 (v11) |
|---|---|---|
| 通信模型 | JSONL 收件箱 | EventBus 全局事件日志 |
| 工作隔离 | 共享目录 | git worktree 独立目录 |
| 任务绑定 | 无 | task ↔ worktree 绑定 |
| 分支管理 | 无 | 自动 wt/ 前缀分支 |
| 工具数 | 14 (Lead) | 17 |
| 架构 | 平铺结构 | 控制面/执行面分离 |
v11 的关键架构洞察是控制面与执行面分离:
- 控制面(主仓库):任务看板、事件总线、worktree 注册表——管理"做什么"
- 执行面(worktree 目录):实际代码修改——执行"怎么做"
Git worktree 是天然的隔离机制:
- 共享
.git:所有 worktree 共享 object store,不浪费磁盘 - 独立工作树:每个 worktree 有自己的 HEAD、index、工作目录
- 分支粒度:每个任务一个分支,天然的代码审查单元
这个模式让并行开发从"互相污染"变成"可追踪、可恢复、可收尾"。
dotnet run v11_worktree_task_isolation.cs可以尝试的提示:
Create a task "Build login page" and a worktree for itRun "ls" in the login worktreeCreate another worktree "api" for backend workShow me all worktrees and their statusKeep the login worktree (merge back to main)
从 v0 到 v11,我们构建了一个完整的智能体系统:
| 版本 | 核心概念 | 一句话总结 |
|---|---|---|
| v0 | Bash 工具 | 给 LLM 一个终端 |
| v1 | Agent Loop | while(true) + 工具调用 |
| v2 | TODO 工具 | 工具就是函数 |
| v3 | Sub-agent | 智能体可以创建智能体 |
| v4 | Skills | 按需加载领域知识 |
| v5 | Context Compact | 三层压缩管道 |
| v6 | Task System | 文件系统持久化任务板 |
| v7 | Background Tasks | 后台执行不阻塞 |
| v8 | Agent Teams | JSONL 收件箱通信 |
| v9 | Team Protocols | request_id 握手 FSM |
| v10 | Autonomous Agents | WORK/IDLE 生命周期 |
| v11 | Worktree Isolation | 控制面/执行面分离 |
核心循环始终不变:
while (true)
{
var response = await client.Messages.Create(params);
if (response.StopReason != StopReason.ToolUse) break;
// 执行工具, 收集结果, 继续
}每一步只增加一个概念。这就是 Learn Claude Code 的全部哲学。