
learn-claude-code 任务系统实战磁盘持久化任务图、blockedBy 依赖与多 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 教程的 s07 章Task System任务系统为主线讲清楚如何把 Agent 的执行清单升级为落盘到.tasks/目录的带依赖关系的任务图DAG每个任务一个 JSON 文件用blockedBy记录前置依赖完成任务时自动解锁下游。读完本文你可以理解这套任务系统的完整数据结构、TaskManager的源码级实现、四个任务工具的注册方式以及它在后续章节后台任务、多 Agent 团队、worktree 隔离中如何演化为整个 Harness 的协调骨架。一、问题扁平清单为什么不够s03 章引入的TodoManager见 agents/s03_todo_write.py只是一个内存中的扁平清单没有顺序、没有依赖、状态只有做完没做完。而真实目标是有结构的任务 B 依赖任务 A任务 C 和 D 可以并行任务 E 要等 C 和 D 都完成。没有显式关系Agent 分不清什么能做、什么被卡住、什么能同时跑。更致命的是——清单只活在内存里s06 的上下文压缩context compact一跑就全没了。s07 给出的答案是把扁平清单升级为持久化到磁盘的任务图让目标比任何一次对话都更长寿。二、解决方案随时回答三个问题的任务图任务图持久化在.tasks/目录每个任务是一个 JSON 文件带状态和前置依赖blockedBy。它可以随时回答三个问题引自 docs/zh/s07-task-system.md什么可以做—— 状态为pending且blockedBy为空的任务。什么被卡住—— 等待前置任务完成的任务。什么做完了—— 状态为completed的任务完成时自动解锁后续任务。目录与依赖关系长这样.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} 任务图 (DAG): ---------- -- | task 2 | -- | | pending | | ---------- ---------- -- ---------- | task 1 | | task 4 | | completed| -- ---------- -- | blocked | ---------- | task 3 | -- ---------- | pending | ---------- 顺序: task 1 必须先完成, 才能开始 2 和 3 并行: task 2 和 3 可以同时执行 依赖: task 4 要等 2 和 3 都完成 状态: pending - in_progress - completed这个任务图是 s07 之后所有机制的协调骨架后台执行s08、多 Agent 团队s09、worktree 隔离s12都读写这同一个结构。三、TaskManager每个任务一个 JSON 文件核心实现位于 agents/s07_task_system.pyTaskManager类L47-L118负责 CRUD 和依赖图维护。3.1 任务数据结构每个任务就是一个 JSON 文件task_{id}.jsoncreate方法生成的初始结构L67-L74def create(self, subject: str, description: str ) - str: task { id: self._next_id, subject: subject, description: description, status: pending, blockedBy: [], owner: , } self._save(task) self._next_id 1 return json.dumps(task, indent2, ensure_asciiFalse)字段含义字段类型说明idint自增 ID构造时通过_max_id() 1扫描目录中已有的task_*.json得出L53-L55因此重启进程后 ID 不会冲突subjectstr任务标题task_create的唯一必填参数descriptionstr详细描述默认空串statusstr状态机三值pending/in_progress/completedblockedBylist[int]前置依赖任务 ID 列表列表为空表示可开始ownerstr负责 Agent默认空串为多 Agent 分工预留读写细节_load直接读task_{id}.json不存在时抛ValueError_save用json.dumps(..., indent2, ensure_asciiFalse)落盘保证人类可读且中文不被转义L57-L65。3.2 状态变更与依赖边update 方法update是任务系统里唯一会同时触碰状态和依赖边的方法L79-L93def update(self, task_id: int, status: str None, add_blocked_by: list None, remove_blocked_by: list None) - str: task self._load(task_id) if status: if status not in (pending, in_progress, completed): raise ValueError(fInvalid 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) return json.dumps(task, indent2, ensure_asciiFalse)三个关键行为状态校验源码里对status做了白名单校验非法状态如done会直接抛ValueError由 agent loop 捕获后作为Error: ...返回给模型——状态机是pending → in_progress → completed的三态不允许随意取值。完成即解锁状态置为completed时立即调用_clear_dependency(task_id)把该 ID 从所有其他任务的blockedBy中移除。依赖边可增可删add_blocked_by用集合去重后追加list(set(...))remove_blocked_by做差集允许事后调整任务图。3.3 依赖解除_clear_dependency解锁逻辑非常朴素但足够健壮——直接扫描磁盘上的全部任务文件L95-L101def _clear_dependency(self, completed_id: int): Remove completed_id from all other tasks blockedBy lists. 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)因为每个任务的完整状态就在磁盘文件里完成任务 1 → task_2 和 task_3 的blockedBy里移除 1 → task 4 仍需等待整个过程不需要任何内存索引。这正是文档强调的State that survives compression —— because its outside the conversation状态活在对话之外压缩和重启都动不了它。3.4 列表输出list_all 的状态标记list_all把全部任务按 ID 排序后渲染成带状态标记的一行式摘要L103-L118[ ] #1: Setup project [] #2: Write code (blocked by: [1]) [x] #3: Write tests标记映射为pending → [ ]、in_progress → []、completed → [x]未知状态兜底为[?]blockedBy非空时追加(blocked by: [...])。这让模型每轮都能用极低 token 成本掌握全局进度。四、四个任务工具加入 dispatch maps07 在原 4 个基础工具bash/read_file/write_file/edit_file上注册了 4 个任务工具工具总数从 5含上下文扩充到 8。工具注册表与参数 schema 见 agents/s07_task_system.pyTOOL_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]), }各工具的完整参数约束摘自源码中的TOOLS定义L193-L200工具必填参数可选参数说明task_createsubject(string)description(string)创建pending任务返回任务 JSONtask_updatetask_id(integer)status(enum:pending/in_progress/completed)、addBlockedBy(int 数组)、removeBlockedBy(int 数组)改状态或改依赖边置completed时自动解锁下游task_list无无返回全量状态摘要task_gettask_id(integer)无返回单个任务的完整 JSON注意task_update的工具入参是 camelCaseaddBlockedBy/removeBlockedBy与 Python 方法参数add_blocked_by在 lambda 里做了转换——这是模型接口与内部 API 的边界。在 agent loopL204-L224中tool_use块按block.name查TOOL_HANDLERS执行结果以tool_result回传给模型handler 抛出的任何异常包括任务不存在、非法状态都会被捕获并转成Error: ...字符串返回让模型自行纠错而不是进程崩溃。系统提示词也随之更新为You are a coding agent at {WORKDIR}. Use task tools to plan and track work.L43引导模型在多步工作中优先使用任务图。五、相对 s06 的变更组件之前 (s06)之后 (s07)Tools58新增task_create/update/list/get规划模型扁平清单仅内存带依赖关系的任务图磁盘关系无blockedBy边状态追踪做完没做完pending → in_progress → completed持久化压缩后丢失压缩和重启后存活使用边界也由此划清从 s07 起任务图是多步工作的默认选择s03 的 Todo 仍可用于单次会话内的快速清单。六、源码级纵深s10 的演进验证与完整集成s07 的设计在后续章节中被不断加深读这三处代码可以印证任务图确实成为整个 Harness 的协调骨架6.1 s10 版本字符串 ID、ID 校验与认领/完成动作章节化目录 s10_task_system/code.py 把任务记录进一步结构化Task是 dataclassL66-L73TaskStore负责 ID 校验正则^task_[0-9a-f]{8}$ID 为task_ 8 位随机十六进制、工作区逃逸检查L76-L95依赖判断从清空列表演化为can_start——blockedBy中全部前置任务 completed才放行且前置文件缺失同样视为未就绪。生命周期变成显式动作pending --claim_task-- in_progress --complete_task-- completedclaim_task负责认领写owner、校验依赖complete_task校验 owner 后才允许置为 completed并回报刚刚被解锁的下游任务。测试用例 tests/test_task_system.py 覆盖了这些契约依赖未满足时拒绝认领Blocked by: [...]、owner 不符时拒绝完成owned by agent, not other、创建任务时随机 ID 冲突会自动重试而非覆盖、blockedBy指向不存在的任务会报错、.tasks被符号链接到工作区外时直接拒绝写入Task store escapes the workspace。这些行为在 s07 的简洁实现里尚未出现属于后续版本的加固——引用时请注意适用版本。6.2 s_full.py任务图如何驱动多 Agent完整集成版 agents/s_full.py 中保留了 SECTION: file_tasks (s07) L261-L324可以推断出两点扩展update额外支持deleted状态置为deleted时直接unlink对应 JSON 文件任务图可以真正删任务新增claim(tid, owner)方法L319-L324把owner字段填上并置in_progress——这正是 s09 多 Agent 团队里谁来做的落地机制。系统提示词同时写明分工原则Prefer task_create/task_update/task_list for multi-step work. Use TodoWrite for short checklists.L554。6.3 TodoWrite 与 Task System 的定位对比配套章节文档 s10_task_system/README.zh.md 给出了一张更完整的对照表可作为选型参考TodoWrite (s05)Task System (s07/s10)定位当前任务的执行清单可恢复的任务系统存储进程内 / 会话状态.tasks/{id}.json依赖无blockedBy依赖图生命周期当前会话 / 当前任务跨会话保留分工不负责任务认领owner/ claim更新契约整表替换对单条记录执行创建、读取、更新、列举七、试一试运行 s077.1 环境要求脚本通过load_dotenv加载.env并从环境读取模型配置agents/s07_task_system.py环境变量MODEL_ID必填os.environ[MODEL_ID]直接取值可选ANTHROPIC_BASE_URL自定义网关设置后会自动移除ANTHROPIC_AUTH_TOKENPython 依赖为anthropicSDK 与python-dotenv见 requirements.txt。7.2 运行与推荐 promptcd learn-claude-code python agents/s07_task_system.py交互提示符为s07 输入q/exit退出。推荐尝试这些 prompt英文 prompt 对 LLM 效果更好也可以用中文Create 3 tasks: Setup project, Write code, Write tests. Make them depend on each other in order.List all tasks and show the dependency graphComplete task 1 and then list tasks to see task 2 unblockedCreate a task board for refactoring: parse - transform - emit - test, where transform and emit can run in parallel after parse观察重点工作目录下是否生成了.tasks/及其中的task_N.json文件完成任务 1 后task_2.json的blockedBy是否变为空数组重启脚本后task_list是否能恢复完整进度——这三点正是任务图比对话长命的直接证据。八、小结s07 用不到两百行代码完成了三个关键动作把计划从内存搬到磁盘每任务一 JSON、把顺序隐式约定变成显式的blockedBy依赖边、把完成变成自动解锁下游的原子动作。这套结构没有引入任何数据库或额外服务纯文件即可表达 DAG、状态机与所有权也因此成为后续后台任务、Agent 团队和 worktree 隔离机制共同读写的协调骨架。如果你在设计自己的 Agent Harness状态放在对话之外这条原则值得直接借鉴。【免费下载链接】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),仅供参考