news 2026/9/7 19:56:56

learn-claude-code 任务系统:用磁盘上的 JSON 文件构建带依赖关系的 Agent 任务图

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
learn-claude-code 任务系统:用磁盘上的 JSON 文件构建带依赖关系的 Agent 任务图

learn-claude-code 任务系统:用磁盘上的 JSON 文件构建带依赖关系的 Agent 任务图

【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code

本篇围绕 learn-claude-code 教程中的 "Task System"(任务系统)章节展开:讲解如何将内存里的扁平待办清单升级为持久化到磁盘的任务图(Task Graph),实现"任务间依赖(blockedBy)+ 三态状态机 + 自动解除阻塞"。读完后你将掌握文件型任务图的完整设计(TaskManager 的 CRUD、依赖解析、工具注册),并能在 agents/s07_task_system.py 中逐行对应实现,为后续后台任务、多智能体协作打下基础。

1. 问题:扁平待办清单为什么不够

在前序章节中,TodoManager(TodoWrite)提供的只是一份内存中的扁平清单:没有顺序、没有依赖、状态只有"完成/未完成"。真实目标是带结构的——任务 B 依赖任务 A,任务 C 和 D 可以并行,任务 E 要等 C 和 D 都完成。

文档指出了两个致命缺陷:

  1. 关系缺失:没有显式的依赖关系时,Agent 无法判断哪些任务"已就绪"、哪些"被阻塞"、哪些"可以并发";
  2. 生命周期过短:清单只存在于内存中,一旦触发上下文压缩(context compression),所有计划信息会被清空。

2. 方案:把清单提升为持久化任务图

核心思路:将清单升级为落盘的任务图(task graph)——每个任务是一个 JSON 文件,携带状态与依赖(blockedBy)。这个图随时能回答三个问题:

  • 什么是就绪的(What's ready?)——状态为pendingblockedBy为空的任务;
  • 什么是被阻塞的(What's blocked?)——还在等待未完成依赖的任务;
  • 什么是已完成的(What's done?)——completed任务;其完成会自动解除下游依赖。

文档给出的目录结构与 DAG 示例:

.tasks/ task_1.json {"id":1, "status":"completed"} task_2.json {"id":2, "blockedBy":[1], "status":"pending"} task_3.json {"id":3, "blockedBy":[1], "status":"pending"} task_4.json {"id":4, "blockedBy":[2,3], "status":"pending"} Task graph (DAG): +----------+ +--> | task 2 | --+ | | pending | | +----------+ +----------+ +--> +----------+ | task 1 | | task 4 | | completed| --> +----------+ +--> | blocked | +----------+ | task 3 | --+ +----------+ | pending | +----------+ Ordering: task 1 must finish before 2 and 3 Parallelism: tasks 2 and 3 can run at the same time Dependencies: task 4 waits for both 2 and 3 Status: pending -> in_progress -> completed

该图同时表达了四层语义:顺序(task 1 必须先于 2、3)、并行(2 和 3 可同时执行)、依赖汇聚(4 等待 2、3 两者)、状态流转(pending → in_progress → completed)。文档还强调:从本章起,任务图成为后续所有章节(后台执行、多智能体团队、worktree 隔离)的"协调骨架"——它们读写的是同一份磁盘结构。

3. TaskManager:文件即任务记录的 CRUD 实现

agents/s07_task_system.py 中,任务系统的全部逻辑集中在TaskManager类(L47-L118)。

3.1 存储与自增 ID

每个任务对应.tasks/下的一个 JSON 文件,文件名即task_{id}.json

class TaskManager: def __init__(self, tasks_dir: Path): self.dir = tasks_dir self.dir.mkdir(exist_ok=True) self._next_id = self._max_id() + 1
  • tasks_dir在模块级被固定为WORKDIR / ".tasks"(L41),即工作目录下的隐藏目录;
  • 新实例启动时通过_max_id()扫描已有文件计算最大 ID,因此任务编号在重启后也能无缝续接,不会与旧文件冲突;
  • _save()使用json.dumps(task, indent=2, ensure_ascii=False)写盘,保证非 ASCII 字符(如中文 subject)可原样落盘。

3.2 create:任务记录的默认值约定

def create(self, subject, description=""): task = {"id": self._next_id, "subject": subject, "description": description, "status": "pending", "blockedBy": [], "owner": ""} self._save(task) self._next_id += 1 return json.dumps(task, indent=2)

任务 JSON 的字段约定:

字段类型默认值含义
idint自增(max+1)任务唯一标识,同时是文件名一部分
subjectstr必填任务标题
descriptionstr""详细描述,跨会话恢复时供 Agent 读取上下文
statusstrpending仅允许pending/in_progress/completed
blockedBylist[int][]前置依赖的任务 ID 列表(依赖边)
ownerstr""负责任务的 Agent(本阶段预留给后续多智能体章节)

3.3 依赖解析:完成即自动解除下游阻塞

def _clear_dependency(self, completed_id): for f in self.dir.glob("task_*.json"): task = json.loads(f.read_text()) if completed_id in task.get("blockedBy", []): task["blockedBy"].remove(completed_id) self._save(task)

这是整个任务图的"状态传播"机制:当任务 X 被标记为completed时,系统扫描所有任务文件,把 X 的 ID 从每个依赖方的blockedBy中移除并回写磁盘。依赖边因此是"物理删除"而非"逻辑忽略"——任何新会话重新加载文件后看到的都是最新的就绪状态。

3.4 update:状态机与依赖边的统一入口

def update(self, task_id, status=None, add_blocked_by=None, remove_blocked_by=None): task = self._load(task_id) if status: if status not in ("pending", "in_progress", "completed"): raise ValueError(f"Invalid status: {status}") task["status"] = status if status == "completed": self._clear_dependency(task_id) if add_blocked_by: task["blockedBy"] = list(set(task["blockedBy"] + add_blocked_by)) if remove_blocked_by: task["blockedBy"] = [x for x in task["blockedBy"] if x not in remove_blocked_by] self._save(task)

源码中比文档片段多了一层状态合法性校验:非法状态直接抛ValueError,异常在 agent loop 中被捕获并作为Error: ...文本回传给模型(见 L217-L220),形成"工具失败 → 模型自我纠正"的闭环。add_blocked_by通过set去重,避免重复依赖边。

3.5 list_all:一屏看清任务图

marker = {"pending": "[ ]", "in_progress": "[>]", "completed": "[x]"}.get(t["status"], "[?]") blocked = f" (blocked by: {t['blockedBy']})" if t.get("blockedBy") else "" lines.append(f"{marker} #{t['id']}: {t['subject']}{blocked}")

输出按任务 ID 排序,用[ ]/[>]" /[x]三种勾选标记对应三态,并附当前仍存在的依赖边。Agent 只需调用一次task_list` 就能回答"什么是就绪的、什么是被阻塞的"。

4. 四个任务工具进入调度表

任务能力以 4 个工具的形式注册进TOOL_HANDLERS分发映射(L173-L182),Agent 从 5 个基础工具变为 8 个:

TOOL_HANDLERS = { # ...base tools: bash, read_file, write_file, edit_file... "task_create": lambda **kw: TASKS.create(kw["subject"], kw.get("description", "")), "task_update": lambda **kw: TASKS.update(kw["task_id"], kw.get("status"), kw.get("addBlockedBy"), kw.get("removeBlockedBy")), "task_list": lambda **kw: TASKS.list_all(), "task_get": lambda **kw: TASKS.get(kw["task_id"]), }

配套的 JSON Schema(L193-L200)定义了模型可见的参数契约:

工具参数说明
task_createsubject(必填)、description创建任务,返回完整任务 JSON
task_updatetask_id(必填)、status(枚举: pending/in_progress/completed)、addBlockedBy(int 数组)、removeBlockedBy(int 数组)更新状态和/或依赖边
task_list返回全部任务的状态摘要(勾选标记 + 阻塞信息)
task_gettask_id(必填)返回单任务完整 JSON

注意task_updatestatusenum约束与源码中的ValueError校验互为呼应:前者在 API 层引导模型生成合法值,后者在运行时兜底。

5. 相对 s06 的变化

组件之前(s06)之后(s07)
工具数58(新增task_create/update/list/get
规划模型扁平清单(内存中)带依赖的任务图(磁盘上)
任务关系blockedBy依赖边
状态跟踪完成与否pendingin_progresscompleted
持久性上下文压缩即丢失压缩与重启后依然存活

文档给出的定位总结:从 s07 起,任务图成为多步骤工作的默认规划方式;s03 的 Todo 只保留给"单会话快速清单"场景。关键洞察如 agents/s07_task_system.py 头部注释所言——"State that survives compression -- because it's outside the conversation"(能活过压缩的状态,因为它在对话之外)。

6. 仓库内的后续演进:从"图"到"协作协议"

教程的后续课程目录 s10_task_system/code.py 保留了同一套.tasks/JSON 文件约定,并在此骨架上叠加了多智能体协作所需的机制,可以作为本章节方案的"下一步参考实现":

  • 任务 ID 改为随机十六进制task_{8位hex}secrets.token_hex(4)生成,L112-L129),创建时以"x"独占模式写文件,ID 冲突则重试,最多 100 次;
  • owner+ claim/complete 动作claim_taskpending → in_progress并写入负责人;complete_task校验 owner 一致才允许in_progress → completed,并在完成时扫描出"刚刚被解除阻塞"的下游任务,把Unblocked: ...直接回传给模型(L185-L228);
  • 就绪判定函数can_start:所有blockedBy前置任务必须为completed,缺失文件视为未完成依赖(L174-L186);
  • 路径安全TaskStore校验任务存储目录必须位于工作区内,拒绝符号链接逃逸(L80-L95)。

测试用例 tests/test_task_system.py 验证了这些契约:依赖未满足时claim_task返回Blocked by: [...]test_dependencies_gate_claim_and_completion_checks_owner);他人 owner 无法完成任务;../outside之类的非法 ID 与不存在的任务都会变成工具错误文本而非崩溃;创建时重复 ID 走重试而非覆盖;符号链接指向工作区外时create_task返回Error: Task store escapes the workspace且不产生任何外部文件。

7. 动手实践(Try It)

运行环境依赖见 requirements.txt(anthropicpython-dotenvpyyaml),并需要设置MODEL_ID环境变量(可选ANTHROPIC_BASE_URL指向兼容网关)。

cd learn-claude-code python agents/s07_task_system.py

交互提示符为s07 >>,输入q退出。按文档建议依次尝试这四条指令:

  1. Create 3 tasks: "Setup project", "Write code", "Write tests". Make them depend on each other in order.
  2. List all tasks and show the dependency graph
  3. Complete task 1 and then list tasks to see task 2 unblocked
  4. Create a task board for refactoring: parse -> transform -> emit -> test, where transform and emit can run in parallel after parse

观测要点:

  • 当前目录下是否生成.tasks/task_1.jsontask_2.json等文件,内容与task_get返回一致;
  • 完成 task 1 后,task 2 的blockedBy是否被自动清空并落盘(重新运行程序后依赖关系依然保留——这正是"磁盘状态活过会话"的验证);
  • 第 4 条指令会构造出parse → {transform, emit} → test的并行结构,task_list中 transform 与 emit 应同时显示为无阻塞的[ ]任务。

8. 小结

learn-claude-code 的任务系统用最朴素的手段——"每个任务一个 JSON 文件 +blockedBy数组"——实现了完整的任务图语义:顺序、并行、汇聚依赖、三态状态机、完成即解阻、跨会话持久化。它不需要数据库、不需要图引擎,Agent 通过四个原子工具(create/update/list/get)即可规划任意复杂度的多步骤工作,并天然把"什么是就绪的"这一判断暴露给模型。从源码结构看,这套.tasks/约定也是后续后台任务、多 Agent 团队与 worktree 隔离章节的公共存储层——文件即状态,状态即协作协议。

【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 19:56:33

SWAT环境建模从入门到实战:流域划分、HRU分析与模型率定全解析

SWAT这套环境仿真软件,我在流域水文方向摸爬滚打了这些年,可以说它是做非点源污染模拟、土地利用变化影响评估最绕不开的工具之一。很多人刚接触SWAT时,第一反应是“这界面也太不友好了”“数据格式怎么这么死板”,好不容易装好了…

作者头像 李华
网站建设 2026/9/7 19:54:42

2026保姆级降AI教程:10款ai降重工具真实效果对比

三月是所有大学生的渡劫期,导师催稿消息不断,知网AIGC检测率一路飘红。不同专业的论文各有特点,降AI稍不注意就会破坏内容逻辑或关键表述。 别再乱试工具踩坑。为了帮助大家选出最合适的工具,我用一篇临床医学综述,原…

作者头像 李华
网站建设 2026/9/7 19:54:36

2026论文降AI全攻略:DeepSeek调教指令+5款降AIGC工具横评

毕业季熬大夜敲出来的论文,明明每个字都是自己一点点码出来的,结果一查AIGC率却飘红,这种委屈和焦虑相信很多同学都懂。现在的系统算法越来越敏锐,只要你的逻辑连词过于工整,或者专业陈述稍显生硬,就容易被…

作者头像 李华
网站建设 2026/9/7 19:54:33

知网/维普通用:10款降AI工具红黑榜,亲测靠谱

终于把论文肝完,知网一测AIGC疑似度直接飘红,心态直接崩了!熬夜改了一遍又一遍,润色到吐,检测结果还是一片通红,眼看截止日期逼近,真的要崩溃。 别再瞎改浪费时间!我耗时三天&#…

作者头像 李华
网站建设 2026/9/7 19:54:31

2026年四月份降AI工具测评红黑榜,这篇或许能帮到你

家人们谁懂啊?一觉睡醒三月底了,2026年的毕业季又双叒叕快要到了!每天在图书馆里疯狂敲键盘,眼睛都快看瞎了,结果导师一句“你这文章是用AI润色过的吧?重写!”直接让人原地破防😭。 …

作者头像 李华
网站建设 2026/9/7 19:52:02

MySQL驱动下载与配置:Jar包与ODBC驱动权威指南

1. 别去搜索引擎乱找,官网才是驱动唯一的“官方正确来源”先聊点实际的。很多朋友一装 MySQL 驱动,第一反应就是去搜索引擎搜“mysql 驱动 jar 包下载”,然后点进那些下载站。这些站点的按钮套路多得要命,要么是假按钮、要么是捆绑…

作者头像 李华