claude agent sdk 指南

05-Agent 进度跟踪

读完这篇你能:区分 subagent 消息的归属,把它的进度转发到前端。

前置:工具调用过程。约 25 分钟。

需求:subagent 作为一个 可以独立执行任务的工具,其执行过程我们也是很关心的。

Agent 调用

效果

20260817090717_rec_

在这个 subagent 的调用中, 该subagent 进行了5次工具调用,工具调用过程穿插了输出块。最后是工具调用结果。

然后,主agent 进行了一个总结。

image-20260817164723388

subagent 消息归属

主agent 可以输出一条消息,subagent 也可以输出一条消息。那么如何知道一条消息是属于谁的呢?

主agent在调用时会设置工具调用的id: toolu_bdrk_01SyBa1yBBu8cSfwY1UX8rwV

{
  "type": "stream_event",
  "event": {
    "type": "content_block_start",
    "index": 2,
    "content_block": {
      "type": "tool_use",
      "id": "toolu_bdrk_01SyBa1yBBu8cSfwY1UX8rwV",
      "name": "Agent",
      "input": {}
    }
  },
  "session_id": "9b2a5366-741f-47a1-9661-b1be8e19357b",
  "parent_tool_use_id": null,
  "uuid": "8159a50a-3ea6-4725-bece-a07c02d7d471"
}

Subagent 输出消息时会携带这个id, 字段是 parent_tool_use_id

而且只有subagent 消息的parent_tool_use_id字段会有值

{
  "type": "assistant",
  "message": {
    "model": "claude-haiku-4-5-20251001",
    "id": "msg_bdrk_01AsHke7WfJZrbn4ZRtXvBUW",
    "type": "message",
    "role": "assistant",
    "content": [
      {
        "type": "text",
        "text": "我来帮你完成这个任务。首先检查目录是否存在,然后写入文件。"
      }
    ],
    "stop_reason": null,
    "stop_sequence": null,
    "usage": {
      "input_tokens": 3,
      "cache_creation_input_tokens": 3021,
      "cache_read_input_tokens": 19054,
      "cache_creation": {
        "ephemeral_5m_input_tokens": 3021,
        "ephemeral_1h_input_tokens": 0
      },
      "output_tokens": 1
    },
    "context_management": null
  },
  "parent_tool_use_id": "toolu_bdrk_01SyBa1yBBu8cSfwY1UX8rwV",
  "session_id": "9b2a5366-741f-47a1-9661-b1be8e19357b",
  "uuid": "18f03c53-44fd-411c-a789-f3bac8e844d7",
  "subagent_type": "general-purpose",
  "task_description": "向文件写入静夜思"
}

转发Subagent消息

默认情况下,仅从子代理发出 tool_use 和 tool_result 块。思考块和文本块不会吐出。 可以在 query conds 参数设置 forwardSubagentText = true,子代理的文本和思考块就可以被正常吐出,同时消息中会设置 parent_tool_use_id,以便消费者可以呈现嵌套记录。

Subagent 进度摘要

Subagent 在执行过程中,有时很长一段时间都不调用工具,也没有消息吐出,在前端的表现就像是静默了一样。

可以在 query conds 参数设置 agentProgressSummaries = true, 主agent 会定时发送subagent 的任务进度摘要消息。

Clipboard_Screenshot_1787099305

Clipboard_Screenshot_1787099351

消息

打开 agentProgressSummaries: true 后,

  subagent 消息 AI 摘要
触发 子代理每次 tool_use,立刻发出 默认约 30s 的定时器
有没有模型调用 没有,本地拼文案 有,fork 子代理对话打一轮
特征字段 last_tool_name + description,无 summary 有 summary,通常没有 last_tool_name
文案从哪来 工具自己的 getActivityDescription,例如 Bash → Running Count from 1 to 100... 模型按「3–5 个词、现在进行时」生成,例如 Checking script existence
成本 免费 复用子代理 prompt cache,通常很低

判断规则很简单:有 summary 就是 AI 摘要,否则就是 subagent 消息。

如何处理消息

后端不要把两类帧揉成同一个字段。有 summary 可以标 source: 'summary',否则标 source: 'activity'。 这里是为了前端好区分,单独设置了一个source字段。

    if (msg.type === 'system' && msg.subtype === 'task_progress') {
      return [
        {
          type: 'task_progress',
          toolUseId: msg.tool_use_id,
          description: msg.description,
          summary: msg.summary,
          lastToolName: msg.last_tool_name,
          subagentType: msg.subagent_type,
          source: msg.summary ? 'summary' : 'activity',
          durationMs: msg.usage.duration_ms,
          toolUses: msg.usage.tool_uses,
          totalTokens: msg.usage.total_tokens,
        },
      ];
    }

前端按 tool_use_id 挂到对应的 Agent/Task 卡片,不要用进度文案覆盖最初的任务标题:

if (event.summary) {
  tool.progress = event.summary;       // AI 摘要
  tool.progressSource = 'summary';
} else if (event.description) {
  tool.progress = event.description;   // 活动脉冲
  tool.progressSource = 'activity';
}

主 Agent 心跳

主Agent中某个工具跑太久时,大约每 30 秒发一条会发送一条心跳,心跳没有内容,纯为了给前端保活。

    if (msg.type === 'tool_progress' && msg.heartbeat) {
      return [
        {
          type: 'tool_heartbeat',
          id: msg.tool_use_id,
          name: msg.tool_name,
          elapsedSeconds: msg.elapsed_time_seconds,
          parentToolUseId: msg.parent_tool_use_id,
        },
      ];
    }

下一篇:权限配置——让这些工具该放行的放行、该拦的拦住。