任务图:状态机、父子关系与回收
一句话理解
当你在 Claude Code 中运行一个复杂操作——比如后台跑一个 Agent,同时执行一个 Bash 命令,再加上一个定时任务——这些东西需要被统一管理。任务图就是 Claude Code 用来跟踪"谁在做什么、做到哪了、什么时候清理"的系统。
比喻:想象一个快递公司的调度中心。每个快递(任务)有状态(在途/已送达/退回),有关系(A 快递是 B 快递的附件),有超时回收机制(无人领取的快递 30 天后销毁)。
7 种任务类型
每种类型就像不同岗位的员工,虽然做的事不同,但都遵循同一套考勤规则。
状态机:一个任务的一生
所有任务都遵循同一个状态机:
判断是否终结
任务存储:一个扁平的字典
所有任务存放在一个扁平的 Map 中,没有树形结构:
为什么是扁平的? 因为父子关系是通过字段引用而非嵌套结构表达的。这样更新一个子任务时,不需要深层修改父任务对象。
Task ID 设计
为什么用 8 位随机字符?36^8 ≈ 2.8 万亿种组合,即使是恶意攻击者也几乎不可能猜到一个有效的 task ID(防止符号链接攻击等)。
父子关系:三种模式
虽然存储是扁平的,但任务之间有三种父子关系:
1. Agent → Bash(生成关系)
当 Agent 被终止时,它生成的所有 Bash 任务也会被一起清理:
2. 父 Agent → 子 Agent(中断传播)
比喻:就像一个电话树。总经理挂断电话(abort),经理的电话也会自动断掉,经理下面的员工也一样。
3. 队友 → Leader(跨会话引用)
任务注册与更新
注册(创建)
更新(状态变化)
输出收集:磁盘文件 + 增量读取
每个任务的输出写在磁盘上,而不是全放在内存里:
使用增量读取避免重复读:
通知机制:防止重复
每个任务有一个 notified 标志,确保完成通知只发一次:
回收机制:两个守门人
任务不会永远留在内存中。回收需要满足两个条件:
宽限期
Agent 任务在终止后有 30 秒的宽限期,让 UI 有时间显示最终状态:
前台 vs 后台
一个 Agent 任务可以在前台和后台之间切换:
InProcessTeammate:特殊的内存管理
团队 Agent 有一个精心设计的内存管理策略:
为什么只保留 50 条?因为实测发现:每个 Agent 在 500+ 轮后占用 ~20MB 内存。一个有 292 个 Agent 的大型会话曾经占用了 36.8GB 内存。所以 UI 层只保留最近 50 条,完整对话存在磁盘上。
任务终止的多态分发
不同类型的任务有不同的终止方式:
关键常量
小结
任务图的设计遵循几个原则:
- 扁平存储 + 引用关系:不用嵌套结构,用字段引用表达父子关系
- 统一状态机:所有类型的任务都是 pending → running → terminal
- 增量输出:磁盘文件 + offset,避免大量数据在内存中堆积
- 安全回收:notified 标志 + 宽限期,防止丢通知或 UI 闪烁
- 多态终止:每种任务知道如何优雅地终止自己
- 中断传播:父级被终止时,子级自动跟随终止

