Claude Code 的任务列表是怎么实现的

1. CLI 看到的任务列表是怎么样的

Claude Code CLI 里的任务列表,是当前请求的进度视图。它通常由多条任务项组成,每条任务项对应一个可以被跟踪的步骤。

一个典型任务列表可以理解成这样:

text 复制代码
□ 修改登录错误提示
□ 补充登录错误提示测试

当模型开始处理第一项时,状态会变化:

text 复制代码
◐ 修改登录错误提示
□ 补充登录错误提示测试

第一项完成后,又会变成:

text 复制代码
✓ 修改登录错误提示
□ 补充登录错误提示测试

所以在单 Agent CLI 会话里,用户看到的不是一个后台任务调度队列,而是一份当前请求的结构化进度记录。它表达三个核心信息:

信息 含义
任务标题 当前请求被拆成了哪些步骤
任务状态 每一步是待处理、进行中还是已完成
当前进度 模型现在推进到哪一步

任务状态一般可以抽象成三类:

text 复制代码
pending       待处理
in_progress   进行中
completed     已完成

这份任务列表的作用是让复杂请求变得可跟踪。它不直接修改代码、不运行命令,也不自动推进下一步;它只是保存"模型认为当前工作应该如何完成"的进度状态。

这篇文章只讨论单个 Claude Code CLI 会话里,模型如何通过任务工具维护用户看到的任务列表。团队协作、swarm、external tasks mode 里的共享任务队列、任务认领和跨进程调度不在本文范围内;那些场景会复用同一套任务数据结构,但职责比这里讲的单 Agent 进度视图更宽。

2. 任务列表是如何生成和更新的

任务列表由模型通过任务工具维护。模型不是在脑子里默默记住进度,而是通过工具把进度写成结构化状态。

这里的任务工具不是用户在终端里手动输入的 CLI 命令,而是模型在一次请求执行过程中可以调用的内部工具。CLI 里看到的列表,是这些工具读写后的任务状态再被界面渲染出来的结果。

这套工具可以分成四类。

工具 作用
TaskCreate 创建一个新的任务项,写入标题、描述、状态等字段
TaskUpdate 更新任务项,例如标记进行中、标记完成、修改描述、设置依赖
TaskList 查看当前任务列表摘要
TaskGet 查看某个任务的完整详情

一个复杂请求进来后,模型会先判断是否需要拆分。如果需要,它会调用 TaskCreate 创建任务项。创建出来的任务默认是待处理状态。

例如用户说:

text 复制代码
帮我修改登录错误提示,并补一个测试。

模型可能创建两个任务:

text 复制代码
#1 修改登录错误提示
#2 补充登录错误提示测试

接下来模型开始处理第一个任务,就调用 TaskUpdate 把它标记为 in_progress。修改完成并确认结果后,再调用 TaskUpdate 把它标记为 completed

如果模型需要重新确认当前还有哪些任务,可以调用 TaskList。如果某个任务的摘要不够,需要读取完整描述,可以调用 TaskGet

这几个工具共同完成一件事:让模型能够生成、读取和更新任务列表状态。

但这些工具本身不负责判断"代码是否真的改好了",也不负责判断"现在是否该继续下一项"。这些判断仍然由模型根据工具返回结果来完成。

更细一点看,TaskCreate 会把新任务写成 pending 状态,并保存 subjectdescriptionactiveFormmetadata 等字段;TaskUpdate 会按任务 ID 读取现有任务,再合并要修改的字段,比如 statusownerblocksblockedByTaskList 只返回列表页需要的摘要信息;TaskGet 才返回单个任务的完整描述和依赖关系。

任务列表的维护过程可以概括成:

text 复制代码
创建任务项
  -> 标记当前任务进行中
  -> 执行实际工作
  -> 根据结果更新任务状态
  -> 必要时读取任务列表决定下一步

2.1 任务状态如何持久化

在实现层,任务列表不是只存在于模型上下文里的自然语言文本,而是落到本地 JSON 文件里的结构化状态。

任务会写到配置目录下的 tasks 子目录中,路径为:

text 复制代码
<claude-config-home>/tasks/<taskListId>/<taskId>.json

每个任务对应一个 JSON 文件。一个任务对象大致可以理解成这样:

json 复制代码
{
  "id": "1",
  "subject": "修改登录错误提示",
  "description": "把登录失败时的提示改得更清楚",
  "activeForm": "修改登录错误提示",
  "status": "in_progress",
  "blocks": [],
  "blockedBy": [],
  "metadata": {}
}

创建任务时,系统会在任务列表级别加锁,读取已有任务文件和 .highwatermark,计算下一个任务 ID,然后写入新的 <taskId>.json.highwatermark 用来记住曾经分配过的最大 ID,避免任务被删除或列表 reset 后复用旧 ID。

更新任务时,系统会先确认任务文件存在,再对单个任务文件加锁,读取 JSON、合并字段、重新写回。这样做的目的不是让任务工具替模型决策,而是让任务状态成为一份模型可以反复读写、CLI 可以稳定渲染、长流程中也可以重新取回的外部进度记录。

3. 从用户输入到任务完成,发生了什么

用一个具体例子来看完整流程。

用户输入:

text 复制代码
帮我修改登录错误提示,并补一个测试。

模型会把这个请求拆成两个任务:

text 复制代码
#1 修改登录错误提示
#2 补充登录错误提示测试

然后模型按顺序推进:先修改实现,再补测试,最后汇总结果。

这张图里有几个关键点:

  1. 模型先理解请求,再决定是否拆成任务;不是所有请求都会生成任务列表。
  2. TaskCreate 只负责新增单个任务,所以两个任务对应两次创建。
  3. TaskUpdate 负责推进状态:设为进行中、设为已完成。

3.1 任务列表和 Agent loop 的关系

任务列表经常让人误以为:只要里面还有未完成任务,Claude Code 就会自动继续执行。

要解释这个误解,先要看 Agent loop 是什么。一次请求不会只经历"模型回答一次"这么简单;更接近真实情况的是:模型先根据当前上下文决定下一步,如果它需要动作,就发出工具调用;CLI 执行工具,把结果收集回来,再和已有上下文一起交给模型,让模型继续判断下一步。

在这个循环里,任务列表只是可被模型调用和读取的一类工具状态,和读文件、改文件、运行命令一样,都属于模型可以借助的外部能力。它能影响模型判断,但不是 Agent loop 的驱动器。

Agent loop 的基本关系可以理解为:

text 复制代码
模型决定下一步
  -> 如果需要动作,就发出工具调用
  -> Claude Code CLI 执行对应工具
  -> 工具结果被收集起来
  -> 组装成下一轮模型上下文
  -> 模型再次决定下一步

对应到源码里的核心分支,可以抽象成下面这段伪代码:

ts 复制代码
while (true) {
  const response = await llm.chat(messages)  // 想:让模型决定下一步

  if (response.toolCalls.length === 0) {
    break  // 模型认为任务完成了,没有工具要调
  }

  for (const toolCall of response.toolCalls) {
    const result = await executeTool(toolCall)  // 做:执行工具
    messages.push(result)                        // 看:把结果加入上下文
  }
}

这段伪代码省略了错误恢复、上下文压缩、stop hook 等旁支,只保留和任务列表最相关的主线。

在这个循环里,任务列表工具只负责一件事:维护进度状态。

所以,在单 Agent 主循环里,任务列表不是 Agent loop 的驱动器,也不是后台调度器。它不会因为还有 pending 任务就自动触发下一步。

真正决定 Agent loop 是否继续的是:模型下一次输出的是工具调用,还是最终回复

如果模型看到任务列表里还有未完成项,并决定继续处理,它会继续发出工具调用;如果模型认为事情已经完成,就会输出最终回复,Agent loop 也就结束。

3.2 如何判断是否有未完成的任务

任务列表里是否还有未完成任务,可以通过任务状态判断。

只要还有 pendingin_progress,就说明任务列表里仍然存在未完成项:

text 复制代码
#1 completed
#2 in_progress

这表示第一项完成了,第二项还在进行。

但这个判断本身有两种用途:

  1. 用于展示层。CLI 可以根据任务状态决定任务列表是否还需要展示、是否还需要继续监听变化、是否可以在全部完成后隐藏。
  2. 间接辅助模型决策

需要强调的是,执行层并不是这样工作的:

text 复制代码
if 还有未完成任务:
    自动继续 Agent loop

真实情况并不是这样。更准确地说,任务列表状态会进入模型上下文,影响模型下一步输出;但 Agent loop 真正检查的是模型下一次响应里有没有工具调用。

也就是说,"是否继续"不是任务列表自己决定的。如果模型看完上下文后继续输出工具调用,Agent loop 就继续;如果模型没有输出工具调用,而是输出最终回复,Agent loop 就结束。

长上下文下模型忘记进度或产生幻觉怎么办

极端情况下,上下文很长,模型可能不再清楚当前任务列表的最新状态。比如它创建过两个任务,完成了第一个,但后续经过很多轮工具调用后,模型可能误以为第二个也已经处理完,或者忘记把第二个任务标记为进行中。

Claude Code 对这种情况有一个补偿机制:任务状态提醒。它的目的不是判断上下文够不够长,而是在长流程里定期把结构化任务状态重新放回模型视野,降低模型凭记忆猜进度的概率。

当系统发现模型已经有一段时间没有维护任务列表时,会把当前任务列表重新放回上下文,提醒模型检查进度。这里的"一段时间"不是按现实时间计算,也不是按上下文长度直接触发,而是按模型响应轮次计算:如果距离上次 TaskCreate / TaskUpdate 已经经过足够多轮,同时距离上次提醒也经过足够多轮,就可以生成一次 task_reminder

这个补偿发生在 Agent loop 的中间位置:工具执行完成之后、下一轮模型调用之前

更具体地说,一轮模型输出工具调用后,Claude Code 会先执行这些工具,并把工具结果收集起来。随后系统会检查是否需要补充上下文附件;如果满足任务状态提醒条件,就生成一条任务提醒附件,把当前任务列表放进去。最后,Claude Code 再把工具结果和这些附件一起组装进下一轮上下文,发起下一次模型调用。

继续用上一节的伪代码看,它不是在任务刚创建时立即提醒,也不是在最终回复之后提醒,而是在这里补偿:

ts 复制代码
while (true) {
  const response = await llm.chat(messages)  // 想:让模型决定下一步

  if (response.toolCalls.length === 0) {
    break  // 模型认为任务完成了,没有工具要调
  }

  for (const toolCall of response.toolCalls) {
    const result = await executeTool(toolCall)  // 做:执行工具
    messages.push(result)                        // 看:把结果加入上下文
  }

  // 补偿点:工具调用完成后、下一轮模型调用前。
  // 系统会在这里检查是否需要补充 task_reminder,
  // 再把提醒一起放进下一轮上下文。
}

这个提醒大致起到这样的作用:

text 复制代码
当前还有任务列表状态。
如果这些任务仍然和当前工作相关,请考虑更新它们。

它不会替模型完成判断,也不会强制模型继续执行。它只是把任务进度重新放回模型视野,让模型少依赖模糊记忆,多依赖刚刚注入上下文的结构化状态,从而降低长上下文中忘记进度或凭空补全进度的概率。

所以长上下文下的处理方式可以概括为:

text 复制代码
任务列表保存状态
工具执行完成后,系统在进入下一轮模型调用前重新提醒
模型重新看到状态后决定是否继续或更新

4. 总结

Claude Code 的任务列表实现,关键不在于"列出几个待办",而在于把任务进度从模型脑海里的临时判断,变成一份可以被创建、更新、读取的结构化状态。

这样做有三个直接结果。

第一,任务进度变得可见。CLI 能把当前有哪些任务、做到哪一步、哪一项正在进行中,稳定地展示给用户,而不是完全依赖模型临时生成一段自然语言说明。

第二,任务进度变得可操作。模型不是只能"记得自己做到哪",而是可以通过 TaskCreateTaskUpdateTaskListTaskGet 明确地创建任务、推进状态、重新读取现状。这样任务列表就不只是显示层,而是 Agent loop 里可被模型反复使用的一份外部状态。

第三,任务进度变得可恢复。随着上下文变长,模型未必始终清楚当前还剩哪些任务;但因为任务列表已经被工具写成了结构化记录,Claude Code 就可以在合适的时候把它重新注入上下文,提醒模型恢复对当前进度的视野。

因此,这套机制的分工其实很清楚:

  • 任务列表:保存结构化进度
  • 任务工具:负责创建、更新、读取这份进度
  • 模型:基于当前上下文的进度决定下一步
  • Agent loop:负责执行工具调用,并把结果推进到下一轮上下文

用一句话收束就是:

text 复制代码
任务列表把进度状态显式保存起来;长流程中多轮未维护任务工具时,系统还会把当前任务列表重新注入上下文;模型结合整体上下文决定下一步,Agent loop 负责执行模型产出的工具调用。

更多 AI 工程、Agent 和技术实践相关内容,欢迎关注公众号。

相关推荐
IOT.FIVE.NO.13 小时前
别再只盯 AGENTS.md 了,Codex 和 Claude Code 真正重要的是这几层配置
人工智能
Mike_6663 小时前
PaddleOCR v4模型转onnx踩坑记
人工智能
小雨青年3 小时前
GitHub Copilot CLI 完全指南:把终端里的 AI 助手真正用起来
人工智能·github·copilot
黎阳之光3 小时前
黎阳之光:深耕视频孪生核心领域 构筑数字孪生全域数智新标杆
大数据·人工智能·算法·安全·数字孪生
郭龙_Jack3 小时前
自有广告系统设计与实践
大数据·人工智能
自小吃多3 小时前
AI本地部署快速步骤
人工智能
漫游的渔夫3 小时前
前端开发者做 AI 工程:别停在脚本阶段,用 2 个 API 把 Agent 交给前端调用
前端·人工智能·typescript
AustinXu3 小时前
构建 AI Agent 系统:从复杂 Agent Skills到企业级 AI Agent
人工智能
ZGi.ai3 小时前
业务负责人的AI焦虑:花了钱、用了工具,但什么都没留下来
人工智能·chatgpt·企业ai·ai焦虑·ai资产