该系列文章基于
github.com/shareAI-lab/learn-claude-code写就,该仓库以大道至简的风格剖析了Claude Code的核心原理,值得大家学习。由于该仓库是基于Python语言,为方便.NET开发者学习,我已经将代码基于.NET 10的dotnet file重写,源码已上传至github,源码地址见文末。
本文是 Learn Claude Code (C# 版) 系列的第七篇,对应代码文件
v6_task_system_agent.cs。
v5 的上下文压缩解决了窗口溢出问题,但带来了副作用:
当对话被压缩时,所有的任务状态都丢失了。
想象你在管理一个 10 个任务的项目:
压缩前:
Task 1: done ✓
Task 2: in-progress 🔄
Task 3: blocked by Task 2 ⏳
... (全在上下文中)
压缩后:
[Summary]: "用户在处理一个多任务项目"
...具体任务? 不知道了
你需要一种方式让状态存活于上下文压缩之外。
任务以 JSON 文件持久化在 .tasks/ 目录中,每个任务一个文件:
.tasks/
├── task_001.json ← { "id": "001", "title": "...", "status": "done" }
├── task_002.json ← { "id": "002", "title": "...", "status": "in-progress" }
└── task_003.json ← { "id": "003", "blockedBy": ["002"], ... }
Agent Context File System (.tasks/)
+------------------+ +------------------+
| Messages[] | | task_001.json |
| (can be cleared) | ←--读取--→ | task_002.json |
| | | task_003.json |
+------------------+ +------------------+
↑ volatile ↑ persistent
class TaskManager
{
string taskDir;
int nextId;
public TaskManager()
{
taskDir = ".tasks";
Directory.CreateDirectory(taskDir);
// ... 从已有文件推断 nextId ...
} public string Create(string title, string description, List<string>? blockedBy)
{
var id = $"{nextId++:D3}";
var task = new Dictionary<string, object>
{
["id"] = id,
["title"] = title,
["description"] = description,
["status"] = "pending",
["blockedBy"] = blockedBy ?? [],
["blocks"] = new List<string>()
};
// 双向依赖连接
if (blockedBy != null)
foreach (var depId in blockedBy)
{
var dep = Load(depId);
((List<string>)dep["blocks"]).Add(id);
Save(depId, dep);
}
Save(id, task);
return id;
} // 完成任务时, 自动从下游任务的 blockedBy 中移除自己
public void ClearDependency(string finishedId)
{
var finished = Load(finishedId);
foreach (var downId in (List<string>)finished["blocks"])
{
var down = Load(downId);
((List<string>)down["blockedBy"]).Remove(finishedId);
Save(downId, down);
}
}当更新任务状态为 done 时,自动调用 ClearDependency:
public void Update(string id, string? status, string? title)
{
var task = Load(id);
if (title != null) task["title"] = title;
if (status != null)
{
task["status"] = status;
if (status == "done") ClearDependency(id);
}
Save(id, task);
}除了原有的 4 个基础工具外,新增 4 个任务工具:
| 工具 | 参数 | 说明 |
|---|---|---|
task_create |
title, description, blocked_by | 创建任务,自动建立双向依赖 |
task_update |
task_id, status, title | 更新任务状态,完成时自动清除依赖 |
task_list |
- | 列出所有任务概览 |
task_get |
task_id | 获取单个任务详情 |
在工具执行的 switch 中增加任务相关分支:
var result = toolName switch
{
"bash" => RunBash(input),
"read_file" => File.ReadAllText(input["path"]),
"write_file" => WriteFile(input),
"edit_file" => EditFile(input),
"task_create" => taskManager.Create(
input["title"], input["description"],
input.TryGetProperty("blocked_by", out var bb)
? bb.EnumerateArray().Select(x => x.GetString()!).ToList()
: null),
"task_update" => taskManager.Update(...),
"task_list" => taskManager.ListAll(),
"task_get" => taskManager.Get(input["task_id"]),
_ => $"Unknown tool: {toolName}"
};| 组件 | 之前 (v5) | 之后 (v6) |
|---|---|---|
| Tools | 5 (基础 + compact) | 8 (+ 4 task 工具) |
| 上下文管理 | 三层压缩 | 三层压缩 (继承) |
| 持久化状态 | 无 (仅 transcript) | .tasks/ JSON 文件 |
| 依赖管理 | 无 | 双向 blockedBy/blocks |
| 状态恢复 | 无法恢复 | 压缩后仍可读取 |
文件系统是最简单的持久化方案:无需数据库,无需序列化框架,JSON + 文件目录就是一个功能完整的任务看板。
关键设计选择:
- 每个任务一个文件:避免并发写入冲突,方便单任务读写
- 双向依赖链接:
blockedBy记录上游,blocks记录下游,完成时反向清理 - 状态转换触发器:
done状态自动级联ClearDependency
任务系统的价值在于让状态外部化——它不再关心上下文窗口发生了什么。
dotnet run v6_task_system_agent.cs可以尝试的提示:
Create a plan with 5 tasks for building a REST API, with proper dependenciesShow me all tasks(观察.tasks/目录中的 JSON 文件)Mark task 001 as done(观察依赖自动清理)