claude code 拆解 分步执行的艺术-task的实现

10 阅读10分钟

使用claude code的过程中,如果遇到较长和较复杂的任务输入,系统会主动唤起task管理。将原始目标拆解为多个可追踪任务来执行。那么作为行业大佬,anthropic是如何来设计这个多步任务规划的呢?有什么诀窍和重点值得我们学习?

我们通过以下线索来追踪:

  1. task致力于解决什么问题?
  2. 从模型的视角来看,task的原理是什么?
  3. 其实现细节有哪些可关注的点?

为什么要有task(任务追踪)?

我们把模型当成一个真实的人的来理解这个问题。

模型是可以像人一样执行任务的。但是当任务目标较远时,模型有时也会范糊涂。跟人一样,有时它并不能不知道该如何高效的达到这个目标。同理,人是如何解决这个问题的呢?答案是列一个待办事项,办一项勾一项,这样就能把短期的注意力集中在一个具体的更容易的目标上。

此外,虽然有plan模式,但是即便是给了执行计划, 但是执行的过程如何跟踪、执行如何不漂移,也是一个问题。

在agent设计模式上,我们可以称之为,任务管理器模式(跟plan模式还是有点不一样,或者也可以叫目标监控-goal模式)。

task的原理是什么?

task模式的实现实践可能有很多种,最简单直接方案例如,

  • 先走一次模型进行一次拆解生成task列表,然后将task设定为目标不断循环让模型执行并检查(可以走工程化强制执行)。
  • 或者走意图识别,分类到task模式再执行,等等。

claude的实现其实让我有点意外的-坚持单agent循环来完成。这可能是他们对自己的模型能力有较强的信心。不过这种方案,应该是工程上实现最简单的,不需要有太多的额外的harness。

来看大致的执行过程:

image.png 解释一下:

  • 用户输入一个任务
  • 模型接收到任务,对这个任务作出判断。根据工具定义,如果这是一个多步的、复杂的任务,那么需要启动task来进行管理
  • 模型发起工具调用,使用TaskCreate来创建任务列表。在这个过程中,由模型来判定需要创建多少任务,任务间有没有依赖关系等。
  • 然后模型开始执行任务。在每个任务开始前将其设为progress,执行完成后设为complete。注意,在这个过程中,模型其实并没有一个稳定且独立的关于任务的上下文的(例如放在system prompt里的一个task 列表),除非它是在team模式中执行任务(本文不讨论这种情况)。模型是通过自己的短期记忆对任务进行管理的。
  • 在每个任务的中间,模型就跟正常模式一样,搜索上下文,执行工具,完成代码编辑。
  • 最后,模型输出所有任务的总结。

当我看到这个流程的时候,其实是有点意外的:我本会以为,有一个额外的管理过程,例如task列表会被不断的注入到模型上下文,然后让模型不断按照任务列表去执行。毕竟这是这个问题最自然的想解法。

anthropic实际的实现非常简洁:task 列表实际仅作为目标检查点的功能。实际是由模型自身来进行管理任务的状态和记忆。

理论上讲,这样的方式其实是有一定风险的:模型很容易忘记正在执行的任务和下一下个目标。或许是a社对自己的模型性能有足够的信心。也可能是他们经过多次测试,发现这样是最简洁且有效的方案。当然,稍后我们会看到,实际实现中,还是有一些补丁来弥补上下文失效的问题。

task的具体工程化细节

哪些具体的工程化细节值得关注和借鉴呢?我们逐步拆解。

提示设计

task并没有在系统提示词中被专门被提及。事实上,system prompt中并没有任何关于task的内容。这与memory和skill有明显差别。

这可能就是a社的提示设计哲学:核心系统提示词只有通用任务执行规则。也有另一种可能:anthroipic并不认为tasks对于claude code是很关键的基础设施。

task的提示词主要是在各个工具中:

TaskCreate:

#这段提示词整体说明了工具(或者说task系统)的目标和作用

Use this tool to create a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user. It also helps the user understand the progress of the task and overall progress of their requests.

#使用场景的说明,从这里明显可以看出工具定认的偏移:更像系统,而不是单个场景工具

When to Use This Tool

Use this tool proactively in these scenarios:

  • Complex multi-step tasks - When a task requires 3 or more distinct…..

When NOT to Use This Tool

……..

tips….

从提示的定义我感觉,这不仅仅是一个工具。更像是一个skills级别的提示定义。

而且拉通所有相关的工具定义来看:TaskList,TaskUpdate,TaskGet,你会发现Create是这套体系的核心。在Create工具里,定义了整体的目标和使用场景,而其他工具更像是在这个体系下的特定工具。

这里我甚至会感觉到,可能有一些设计上不合理的地方:这个Create工具似乎太重了,并不应该在这里这样子定义。当然,也有可能是a社在故意弱化task。task的工具本身被定义为deffered,也就是说它并不是直接可用的核心工具。

一个设计上的选择思考

做过agent应用的都知道,为模型设置工具并不是越多越好。工具设置的越多,模型就会面临选择困难,意图识别的时候也可能会发生偏移;工具之间的界限也就更加难以把握。

那么问题来了,这里claude为什么会选择设计4个task工具而不是一个呢?每个工具,仅负责一个很小的功能。每个工具都有对应的提示和参数说明,这对上下文也是一个挑战。

这就启发了我们在设计工具上的一个权衡。要么选择多工具,每个工具schema都较窄,意图更明确;还是选择意图更宽,单工具的复杂度上升,但工具系统的整体复杂度下降?

从这个场景来看,如果选择单工具,那么这个工具大概会变成这样:

name: TaskManage action: “create” | “list” | “get”…. other parameters….

这样做的话,整体工具系统的复杂度会下降。 模型一旦识别到task相关就会定位到这个工具上。但是这对于模型处理工具的能力则是一个更大的挑战。有兴趣的朋友可以在这两种情况下做一个评测,看看哪种更好。不过按照模型的原理来推演,大概率a社这种做法要好一些:注意力会更集中,调用会更精准。

同时,a社在这里处理的时候,其实也做了一层优化:按需加载。所有task的工具都不是即时加载的,在设置上,他们的defer_loading为true,也就是说这些工具初始阶段实际上并不会进入上下文,详见a社关于高级工具用法的说明。

remind机制

前面讲过,在正常模式下,其实task状态并没有什么特别的机制来改变模型的行为:agent仍然是单循环执行。task更像是仅仅是一个分步执行的检查点:没有task list作为全局执引,没有不断的从工程化去压制模型执行路径。

换句话说,全靠模型自觉!

对task list的记忆,也主要靠模型本身的短期记忆(message列表)。 那么不可避免的我们会想到,是否会有飘移会发生?这是很显而易见的风险。而且如果我们自己去开发这个,这种情况我认为很可能会生。

实际上a社其实有打一个补丁:在多轮对话的过程中,会不间断的将task信息注入到对话中,让模型再次唤回记忆。

流程大概是:

  • 先记录一个时间点,如果遇到了create、update或者进行了一次remind
  • 每轮对话就往前扫,看看离上一个时间点是否超过了10轮消息
  • 如果达到了,就插入一个system-reminder,用来提醒模型:你当前正在执行一个task list哦,记得跟踪任务

remind提示如下:

“ The task tools haven't been used recently. If you're working on tasks that would benefit from tracking progress, consider using TaskCreate to add new tasks and TaskUpdate to update task status (set to in_progress when starting, completed when done). Also consider cleaning up the task list if it has become stale. Only use these if relevant to the current work. This is just a gentle reminder - ignore if not applicable. Make sure that you NEVER mention this reminder to the user

Here are the existing tasks:

#1. [pending] Fix rounded-full badge violations #2. [in_progress] Run verification

task模型定义

claude code的老用户应该记得,其实最开始并不叫task,而是叫todo。在源码中也可以看到遗留的上一代代码。 改为task,是为了将任务执行层和定义进一步抽象:task不仅能在主agent中能用,而且还会支持team模式去抢单。

这是对模型执行任务的进一步抽象:不仅仅是做什么,还包括了状态、依赖、所有者等。task是目前整体的基础性设施。

z.object({
    id: z.string(),
    subject: z.string(),
    description: z.string(),
    activeForm: z.string().optional(), // present continuous form for spinner (e.g., "Running tests")
    owner: z.string().optional(), // agent ID
    status: TaskStatusSchema(),
    blocks: z.array(z.string()), // task IDs this task blocks
    blockedBy: z.array(z.string()), // task IDs that block this task
    metadata: z.record(z.string(), z.unknown()).optional(), // arbitrary metadata
  })

其中比较重要的是:

  • subject:名子,根据提示,这是A brief, actionable title
  • description:这是具体需要完成的内容和目标
  • status:任务的状态,pending-progress-completed
  • blocks和blockedBy,描述依赖关系

整体这个模型设计的比较常规化,没什么特别的。但是非常的简洁。description在提示中会包含较多的目标信息。owner的设计确保可以跨agent协同。

存储

claude code并没有选用数据库(sqlite)来存储task,而是直接使用本地文件存储。感觉有点简单和粗糙,但是我认为够用而且简洁。并且能充分满足跨进程协同。

task列表被存储在.claude/tasks/tasksListId下,结构大概是:

~/.claude/tasks/{taskListId}/ .lock .highwatermark 1.json 2.json 3.json

每个json单独存一个task。并且且锁文件对整体加锁,确保跨进程安全性。

总结一下

claude code的实现中, 使用了tasks机制来解决长任务飘移问题。作为一个基础设施层,这套机制不仅可以用在普通任务上,也可以用在plan mode和team mode上。

从模型角度来看,其核心思路就是分步执行,建立检查点。

从实现来看,采用较简洁的实现方案,使用懒加载工具触发,利用message上下文保留对任务列表的记忆,多轮对话使用remind来校正飘移。