claudecode学习 第 3 章 · Agent 循环与工具协议

目标:把第 0 章的简化伪代码展开成真实工程细节------消息格式、工具调用协议、streaming、错误处理与中断恢复。 受众:专业程序员。本机版本 2.1.220


3.1 消息格式

回顾第 0 章伪代码

python 复制代码
messages = [system_prompt, user_message]
while True:
    a = llm.complete(messages)
    if not a.tool_calls: break
    ...

里面的 system_promptuser_messageatool_result 到底长什么样?本章拆开。

四种消息角色

role 谁产生的 内容
system runtime 组装 系统提示词(人格、规则、可用工具说明)
user 你(或 runtime 注入) 你的话、工具执行结果、文件内容
assistant LLM LLM 的回复(文字 + 工具调用)

注意 :Anthropic API 没有独立的 tool role 。工具结果(tool_result)是塞在 user 消息里的------role: "user" 的消息 content 数组里放 {"type": "tool_result", ...} 块。这是 Anthropic 的设计选择(不同于 OpenAI 的 tool role)。所以严格说只有三种 role,但消息块类型有四种(text / tool_use / tool_result 等)。

反直觉点 :工具结果不是单独的 role,而是塞在 user 消息里。Anthropic API 的设计:工具结果作为一种特殊的 user 消息回传给 LLM。

本机实证:真实会话转录的 role 分布

makefile 复制代码
文件: ~/.claude/projects/-Users-jsl-Desktop-thesis/a0bebaca-....jsonl

assistant: 89    ← LLM 的回复
user: 44         ← 你的话 + 工具结果
attachment: 11   ← 附件
system: 7        ← 系统提示
mode / permission-mode / file-history-snapshot / last-prompt / ai-title ← runtime 内部记录

关键观察

  1. 工具结果全记在 user role 下。本样本中 assistant: 89,user: 44------但注意 user = 你的话 + 所有工具结果。在工具调用密集的会话里 user 数量通常会反超 assistant,因为每调一次工具就多一条 user(tool_result)。这段样本工具调用少所以 user < assistant,不代表常态。

  2. 除四种标准 role,还有一堆 runtime 内部记录attachment/mode/permission-mode/file-history-snapshot/last-prompt/ai-title。这些不是发给 LLM 的,是 Claude Code 自己存的会话元数据。

两层消息:发给 LLM 的 vs 存在本地的

sql 复制代码
会话转录 jsonl 文件
├── 发给 LLM 的消息(system / user / assistant / tool_result)
│     └── 这些进 messages 数组,每轮 API 调用都发
└── runtime 内部记录(mode / permission-mode / file-history-snapshot / ...)
      └── 这些不发给 LLM,只用于本地状态管理

消息流转图(一轮工具调用完整过程)

scss 复制代码
┌──────────┐                              ┌──────────┐
│  runtime │                              │   LLM    │
│ (本地)   │                              │ (云端API)│
└────┬─────┘                              └────┬─────┘
     │                                         │
     │  ① messages 数组 + 所有工具 schema       │
     │  ────────────────────────────────────▶  │
     │                                         │ (LLM 思考)
     │                                         │
     │  ② assistant 消息 (含 tool_use 块)       │
     │  ◀────────────────────────────────────  │
     │                                         │
     │  ③ runtime 解析 tool_use                │
     │     查注册表 → 找到实现                 │
     │  ④ 执行工具 (副作用+权限+hook)          │
     │     ↓                                   │
     │  ⑤ 结果包成 tool_result                 │
     │     塞进 user 消息                      │
     │                                         │
     │  ⑥ 新的 messages (含 tool_result)       │
     │  ────────────────────────────────────▶  │
     │                                         │ (LLM 继续想)
     │  ... 循环 ...                           │

--resume 恢复会话时 runtime 要做:

  1. 读 jsonl
  2. 过滤出发给 LLM 的那部分(system/user/assistant/tool_result)
  3. 重建 messages 数组
  4. 同时恢复本地状态(mode、permission-mode、file-history 等)

这就是为什么 --resume 后能看到之前对话、还在同一个权限模式、文件改动历史也在------转录里这两层都存了。

四种角色的具体结构

system 消息

json 复制代码
{
  "type": "system",
  "content": "你是 Claude Code...(几千字的系统提示)"
}

runtime 组装,整个会话只发一次(作为 system 参数传,不在 messages 数组里重复)。

user 消息(你的话)

json 复制代码
{
  "type": "user",
  "message": {
    "role": "user",
    "content": "帮我重构这个函数"
  }
}

assistant 消息(LLM 回复,含工具调用)

json 复制代码
{
  "type": "assistant",
  "message": {
    "role": "assistant",
    "content": [
      {"type": "text", "text": "我先读一下这个文件"},
      {"type": "tool_use", "id": "toolu_xxx", "name": "Read", "input": {"file_path": "/abs/foo.js"}}
    ]
  }
}

重点 :assistant 消息的 content 是个数组,可以同时含文字和多个工具调用。LLM 一轮能调多个工具。

user 消息(工具结果)

json 复制代码
{
  "type": "user",
  "message": {
    "role": "user",
    "content": [
      {"type": "tool_result", "tool_use_id": "toolu_xxx", "content": "文件内容..."}
    ]
  }
}

"工具结果塞在 user 里"的实证------role: "user",但 content 里是 tool_result 类型,用 tool_use_id 关联回之前的工具调用。


3.2 工具调用协议

一个工具调用的完整生命周期(五步)

注意:Ch0 将循环概括为 4 步("LLM 输出 → runtime 解析 → 执行 → 结果回传"),本章展开为 5 步------将 Ch0 的"执行"一步拆成"查注册表 → 执行实现"两步,以便展开讲解权限和 hook 的切入位置。两处编号不同但描述的是同一过程。

rust 复制代码
┌─────────────────────────────────────────────────────────────────┐
│ ① LLM 输出 tool_use 块                                          │
│    {"type":"tool_use","id":"toolu_xxx","name":"Read",           │
│     "input":{"file_path":"/abs/foo.js"}}                        │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│ ② runtime 解析 tool_use 块                                      │
│    遍历 assistant.content,收集所有 tool_use                     │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│ ③ runtime 查工具注册表                                          │
│    TOOL_REGISTRY["Read"] → {schema, impl}                       │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│ ④ runtime 执行工具实现  ← 副作用+权限+hook 都在这层              │
│    ┌──────────────────────────────────────────┐                │
│    │ PreToolUse hook → 权限检查 → 执行 impl    │                │
│    │                            ↓              │                │
│    │                    PostToolUse hook       │                │
│    └──────────────────────────────────────────┘                │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│ ⑤ runtime 包成 tool_result 喂回                                 │
│    {"type":"tool_result","tool_use_id":"toolu_xxx",             │
│     "content":"文件内容..."}                                    │
│    塞进 user 消息,回到循环顶部                                  │
└─────────────────────────────────────────────────────────────────┘

① LLM 输出 tool_use 块

json 复制代码
{
  "type": "tool_use",
  "id": "toolu_01ABCdefGH",
  "name": "Read",
  "input": {"file_path": "/abs/path/foo.js"}
}

四个字段:

  • type: 固定 "tool_use"
  • id: 全局唯一 ID(toolu_ 开头),tool_result 用它关联回来
  • name: 工具名
  • input: 参数对象,schema 由工具定义决定

关键 :LLM 输出符合 schema 的结构化 JSON,不是自然语言。runtime 不需解析自然语言,直接拿结构化数据分发。

② runtime 解析 tool_use 块

python 复制代码
for block in assistant_msg.content:
    if block.type == "tool_use":
        pending_tool_calls.append(block)   # 收集所有工具调用

一个 assistant 消息可能含多个 tool_use 块------LLM 一轮能调多个工具(如同时读三个文件)。runtime 全收集,逐个执行。

③ runtime 查工具注册表

runtime 维护工具注册表:工具名 → 工具实现(含 input schema、执行函数)。

python 复制代码
TOOL_REGISTRY = {
    "Read":     {"schema": FileReadInput,  "impl": read_file},
    "Edit":     {"schema": FileEditInput,  "impl": edit_file},
    "Bash":     {"schema": BashInput,      "impl": run_bash},
    "Glob":     {"schema": GlobInput,      "impl": glob_search},
    # ...
}

注册表是动态的------这也是为什么 MCP 服务器、插件能"加新工具":注册时往表里加条目,LLM 下轮就能调到。第 12 章(MCP)细讲。

④ runtime 执行工具实现

python 复制代码
result = TOOL_REGISTRY["Read"]["impl"](input={"file_path": "/abs/path/foo.js"})

这一步产生真实副作用------读文件、改文件、跑命令、发网络请求。权限、沙箱、hook 拦截全插在这一层:

复制代码
执行工具前 → PreToolUse hook 检查(第 11 章)
执行工具 → 真实副作用
执行工具后 → PostToolUse hook(第 11 章)

权限检查也在这一层------runtime 看 settings.jsonpermissions,决定要不要问(第 5 章)。

⑤ runtime 包成 tool_result 喂回

json 复制代码
{
  "type": "user",
  "message": {
    "role": "user",
    "content": [
      {
        "type": "tool_result",
        "tool_use_id": "toolu_01ABCdefGH",
        "content": "文件内容..."
      }
    ]
  }
}

tool_use_id 把结果关联回第①步的 id。然后加进 messages,回到循环顶部。

工具的 input schema 从哪来

每个工具自带 input schema。这就是 sdk-tools.d.ts(3807 行)的来历------所有内置工具的 schema 定义。

本机实证:Read 工具 schemasdk-tools.d.ts:602):

typescript 复制代码
export interface FileReadInput {
  file_path: string;      // 必填:绝对路径
  offset?: number;        // 可选:从第几行开始(大文件分块读)
  limit?: number;         // 可选:读几行
  pages?: string;         // 可选:PDF 页码范围
}

工程细节:

  • file_path 必填(不带 ?),其他可选
  • 路径必须绝对路径(注释明说)------防止 LLM 用相对路径搞错工作目录
  • 大文件支持 offset/limit 分块读------防止一次性塞爆 context
  • PDF 单独有 pages 参数------特殊文件类型特殊处理

工具注册表的真实形态

typescript 复制代码
{
  name: "Read",                    // LLM 看到的名字
  inputSchema: FileReadInput,      // 参数 schema(发给 LLM 让它知道怎么调)
  impl: readFileFunction,          // 实际执行函数
  // 可能还有:权限规则、hook 配置、输出格式化器等
}

inputSchema 也会发给 LLM。每次 API 调用时 runtime 把所有可用工具的 schema 打包发给 LLM,LLM 才知道"我能调哪些工具、每个工具接受什么参数"。这就是为什么加新工具(MCP/插件)后 LLM 下一轮就能用------schema 已经发过去了。

本机实证:真实 tool_use 块

从会话转录里看到的 tool_use 块:

vbnet 复制代码
id:    call_00_kkvDgUBGb2VkBDDNUIcG4327
name:  Bash
input: {"command": "ls -la /etc/resolver/ && echo \"---\" && cat ...", "description": "..."}

id:    call_01_KXTdgIyl0BLvheyIS1fk0569
name:  Bash
input: {"command": "dig @10.8.8.8 v.nuaa.edu.cn +short +time=3 2>&1", ...}

细节印证:

  1. id 格式call_00_xxxcall_01_xxx------同一轮里多个工具调用按序编号
  2. 混合工具类型 :同一段会话里有 Bash/Read/Write/Edit------LLM 灵活组合
  3. input 符合 schemaRead{"file_path": "..."}Edit{"file_path": "...", "old_string": "...", "new_string": "...", "replace_all": false}
  4. 每个 tool_use 是完整 JSON:转录里存的是流式生成完毕后的完整块,delta 片段没存

3.3 streaming 与流式输出

物理事实:LLM 生成是逐 token 的

LLM 生成文本的本质是自回归:每次预测下一个 token,基于已生成的内容接着预测,一个一个吐出来。

arduino 复制代码
输入: "中国的首都是"
LLM 逐 token 生成:
  → "北"     (基于"中国的首都是")
  → "京"     (基于"中国的首都是北")
  → "。"     (基于"中国的首都是北京")

生成一个 token 大概几十到几百毫秒。200 字回复可能要 5-10 秒。

两种 API 模式

非流式(non-streaming)

markdown 复制代码
客户端 ──请求──→ LLM
                  (生成 5-10 秒,全部完成)
客户端 ←──完整响应── LLM

用户看到转圈圈,突然蹦出整段。

流式(streaming)

scss 复制代码
客户端 ──请求──→ LLM
客户端 ←─token1─ LLM  (0.1秒)
客户端 ←─token2─ LLM  (0.2秒)
...边生成边传...
客户端 ←─tokenN─ LLM  (10秒)

用户看到文字边生成边显示,"打字机效果"。

Claude Code 用的是流式

为什么必须用流式

不是为了打字机好看,是两个硬需求:

需求一:长任务的可观测性

Agent 循环一轮可能跑 30 秒、调十几个工具。非流式时用户看到"转圈 30 秒然后突然一切完成"------中间发生了什么完全不知道。流式让你实时看到 AI 在干什么:读文件、搜索、改文件......出问题能立刻 Esc 打断。

需求二:提前打断

非流式要等整个响应生成完才能处理。但 Agent 循环里 LLM 可能生成到一半你就发现方向错了。流式允许在生成过程中就接收已生成的部分,runtime 可以随时中断。按 Esc 时 runtime 关闭流,已生成部分保留,未生成的不等了。

streaming 的数据格式:SSE

Claude API 用 SSE(Server-Sent Events) 推送流。每个事件是一行 event: xxx + data: {...}。关键事件:

csharp 复制代码
event: message_start        ← 消息开始
event: content_block_start  ← 一个内容块开始(文字块 or 工具调用块)
event: content_block_delta  ← 内容块增量(一个 token)
event: content_block_stop   ← 内容块结束
event: message_stop         ← 整个消息结束

SSE 事件流时序图(一轮含文字+工具调用的生成)

arduino 复制代码
LLM 端                  网络                    runtime 端
  │                      │                         │
  │  message_start       │                         │
  │─────────────────────▶│─────────────────────▶   │  开始接收
  │                      │                         │
  │  content_block_start │                         │
  │  (text 块)           │                         │
  │─────────────────────▶│─────────────────────▶   │  文字块开始
  │                      │                         │
  │  content_block_delta │                         │
  │  ("我")              │                         │  显示 "我"
  │─────────────────────▶│─────────────────────▶   │
  │  content_block_delta │                         │  显示 "先"
  │  ("先")              │                         │
  │─────────────────────▶│─────────────────────▶   │
  │  ...更多 delta...    │                         │
  │                      │                         │
  │  content_block_stop  │                         │
  │─────────────────────▶│─────────────────────▶   │  文字块结束
  │                      │                         │
  │  content_block_start │                         │
  │  (tool_use 块)       │                         │  工具块开始
  │─────────────────────▶│─────────────────────▶   │
  │  content_block_delta │                         │  JSON 片段
  │  ('{"type":"tool_u') │                         │  逐步显现
  │─────────────────────▶│─────────────────────▶   │
  │  ...更多 delta...    │                         │
  │                      │                         │
  │  content_block_stop  │                         │
  │─────────────────────▶│─────────────────────▶   │  工具块结束
  │                      │                         │  → 解析+执行
  │  message_stop        │                         │
  │─────────────────────▶│─────────────────────▶   │  整条消息完

runtime 收到这些事件流,边收边解析、边显示。关键在 content_block_delta------每收到一个 delta,就把新内容追加到屏幕上。

工具调用也是流式的

LLM 输出工具调用(tool_use 块)时,也是逐 token 流式生成的

比如 LLM 要输出:

json 复制代码
{"type":"tool_use","name":"Read","input":{"file_path":"/abs/foo.js"}}

流式过程:

json 复制代码
delta 1: {"type":"tool_u
delta 2: se","name":"Re
delta 3: ad","input":{"
delta 4: file_path":"/ab
delta 5: s/foo.js"}}

runtime 要做的事:边收 delta 边拼接,等到 content_block 完整了才解析成 tool_use 块去执行

这就解释了一个现象:工具调用时工具名和参数也是"逐步显现"的------因为它们就是流式生成的 JSON 片段。

多个内容块的流式

assistant 消息的 content 是数组、可含多个块(文字 + 多个工具调用)。流式时这些块顺序生成

java 复制代码
content_block_start (text 块)       ← "我先读一下文件"
content_block_delta × N             ← 文字逐 token
content_block_stop

content_block_start (tool_use 块)   ← Read 工具调用
content_block_delta × N             ← JSON 逐 token
content_block_stop

content_block_start (tool_use 块)   ← 另一个工具调用
content_block_delta × N
content_block_stop

message_stop

runtime 收完所有 content_block,才把整个 assistant 消息组装好,进入工具执行阶段。

Extended Thinking(推理令牌)

Claude 的 extended thinking 功能在 streaming 中引入额外的内容块类型:

scss 复制代码
content_block_start (thinking 块)      ← 模型开始内部推理
content_block_delta × N                ← 推理 token 逐步输出
content_block_start (redacted_thinking) ← 被安全过滤的推理内容(不显示原文)
content_block_stop

content_block_start (text 块)          ← 正式回复开始

signature 字段伴随推理块出现,用于验证推理完整性。这些块不计入常规 output token(单独计费),也不在普通 streaming 解析中显示------除非开启了推理可见性。

对 runtime 的影响:解析 content_block 时需多处理 thinkingredacted_thinking 两种类型,不能假设只有 texttool_use

实证:转录里存的是完整块

会话转录 jsonl 里存的是最终完整消息(不是 delta 流)。runtime 收完一个 content_block 才组装成完整 tool_use 存下来------所以你在转录里看到的每个 tool_use 都是完整 JSON,看不到 delta 片段。


3.4 错误处理与中断恢复

Agent 循环会出三类问题,每种有专门恢复机制。

错误处理决策图

sql 复制代码
                  Agent 循环运行中
                        │
          ┌─────────────┼─────────────┐
          ▼             ▼             ▼
     工具执行失败    用户打断      API 调用报错
          │             │             │
          ▼             ▼             ▼
   ┌──────────────┐ ┌─────────┐ ┌─────────────────┐
   │ 包成 tool_   │ │ Esc:    │ │ 可重试?          │
   │ result with  │ │ 停输出  │ │ (网络/503/limit) │
   │ is_error:true│ │ 保留已  │ └────┬───────┬────┘
   │              │ │ 生成内  │      │ 是    │ 否
   │ 喂回 LLM     │ │ 容      │      ▼       ▼
   │ 让它自愈     │ │         │  指数退避  报错引导
   │              │ │ Ctrl+C: │  自动重试  (context超限
   │ (换路径/换   │ │ 彻底中  │           → /compact
   │  命令/重试)  │ │ 断循环  │           鉴权失败
   └──────────────┘ └─────────┘           → 重新认证)

   第四类: 会话恢复 (--resume)
   ┌──────────────────────────────────────────────┐
   │ 1. 找 jsonl 转录                              │
   │ 2. 过滤出发给 LLM 的消息(system/user/assistant│
   │    /tool_result)重建 messages                │
   │ 3. 恢复本地状态(mode/permission-mode 等)     │
   │ 4. 清理残缺块(半截 tool_use 无对应 result 等)│
   │ 5. 进入 REPL 等下一句                         │
   └──────────────────────────────────────────────┘

第一类:工具执行失败

工具执行(循环 step 3)可能失败------读不存在的文件、命令返回非零、网络超时。

关键认知:工具失败不终止循环,而是把错误当成 tool_result 喂回 LLM。

失败 tool_result 长这样:

json 复制代码
{
  "type": "user",
  "message": {
    "role": "user",
    "content": [
      {
        "type": "tool_result",
        "tool_use_id": "call_00_xxx",
        "content": "Error: ENOENT: no such file or directory, open '/abs/foo.js'",
        "is_error": true                          ← 标记为错误
      }
    ]
  }
}

is_error: true 告诉 LLM "这次调用失败了"。LLM 收到错误后自己决定怎么办:

  • 文件不存在 → 改用 Glob 搜正确路径
  • 命令失败 → 看错误信息,换命令试
  • 网络超时 → 重试或换方案

这就是 Agent 的"自愈"能力------失败不是终点,是新的输入。LLM 基于错误信息调整策略继续循环。

实证:会话里 LLM 自己加 2>/dev/null 吞掉错误输出------它知道文件可能不存在,预先处理。如果文件真不存在,命令返回非零,LLM 收到 tool_result 后判断"哦文件不在,换方案"。

第二类:用户中途打断

Esc:只停输出,循环状态保留

arduino 复制代码
LLM 正在流式生成第 N 个 token
  ↓
你按 Esc
  ↓
runtime 关闭 SSE 流,停止接收后续 token
  ↓
已生成的部分保留在 messages 里
  ↓
循环停在"等待下一轮用户输入"状态

关键:已生成的内容没丢。你打断了 AI 的废话,但前面说的话、调的工具都还在。可以接着说"别说了,直接给我结果",循环继续。

CHANGELOG 2.1.219 修过相关 bug:

Fixed spurious "Request interrupted by user" messages after interrupted tool calls, and an unpaired tool_use block left in the transcript when a tool aborted mid-response

工具调用到一半被打断,可能留下"半截 tool_use 块"(没有对应 tool_result)。runtime 要清理这种残缺状态,否则下一轮 LLM 会困惑"我有个工具调用没收到结果"。

Ctrl+C:彻底中断

比 Esc 更暴力。Agent 循环本身被打断,不只是当前这一轮。通常退出 REPL 或回到空提示状态。

第三类:API 调用报错

可重试错误(网络抖动、503、rate limit) :runtime 自动重试,带指数退避:

复制代码
第 1 次调用 → 503
等 1 秒
第 2 次调用 → 503
等 2 秒
第 3 次调用 → 成功

用户看不到重试,只看到最终结果。

不可重试错误

  • Context 超限 :messages 太长超过模型 context window。runtime 提示用 /compact 压缩历史。
  • 鉴权失败:API key 无效。runtime 报错让你重新认证。

CHANGELOG 2.1.219 修过:

Fixed a retry loop that re-sent identical doomed requests after a context-overflow error with a large thinking budget

以前 context 超限后 runtime 会傻乎乎重试同样请求(注定失败)。修复后不再重试这种不可恢复的错误。

第四类:会话恢复(--resume

这不是"出错",但是中断的一种------昨天聊到一半关了终端,今天想接着聊。

--resume 做的事:

  1. 找到对应的 jsonl 转录文件
  2. 逐行解析
  3. 过滤出"发给 LLM 的消息"(system/user/assistant/tool_result)
  4. 重建 messages 数组
  5. 恢复本地状态(mode、permission-mode、file-history 等)
  6. 进入 REPL,等你下一句

工程难点:转录里可能有残缺状态

  • 最后一条是 assistant 消息含 tool_use,但没有对应 tool_result(上次中断时工具没执行完)
  • 中间被打断的 content_block

CHANGELOG 2.1.219:

Fixed a resumed session failing every turn, or crashing on resume, when its history held a malformed delta attachment

resume 要处理"转录里的畸形数据"------runtime 要能识别并跳过残缺块,否则恢复后每一轮都崩。

错误处理的总体设计哲学

Agent 循环的容错原则:能自愈的就自愈(工具失败喂回 LLM),能恢复的就恢复(resume 清理残缺),不能自愈的明确报错(不可重试错误),用户打断要保留已做的工作(Esc 不丢内容)。

这跟传统脚本编程的"出错即停"完全不同。Agent 循环把错误当成信息源,让 LLM 自己消化------这是 Agent 模式相对传统自动化的核心优势之一。


本章核心带走

  1. 四种消息角色:system(人格/规则)、user(你的话 + 工具结果)、assistant(LLM 回复 + 工具调用)、tool_result(塞在 user 里)。转录里还有 runtime 内部记录(mode/permission-mode 等)不发给 LLM。
  2. 工具调用五步 :LLM 输出 tool_use(结构化 JSON)→ runtime 解析 → 查注册表找实现 → 执行(副作用+权限+hook)→ 包成 tool_result 喂回。每个工具的 input schema 来自 sdk-tools.d.ts,既指导 LLM 怎么调,也指导 runtime 怎么验。
  3. streaming 用 SSE:LLM 逐 token 生成,runtime 边收 delta 边显示,收完一个 content_block 才组装成完整块执行。流式是为了长任务可观测性和可中断,不是为了好看。
  4. 错误处理四类 :工具失败喂回 LLM 自愈;Esc 停输出保留内容,Ctrl+C 彻底中断;API 错误分可重试(退避)和不可重试(报错);--resume 要清理转录残缺状态。
  5. 容错哲学:把错误当信息源,让 LLM 消化------与传统"出错即停"根本不同。

本机实证命令汇总

bash 复制代码
# 1. 找会话转录
find ~/.claude/projects -name "*.jsonl" -type f

# 2. 统计 role 分布
cat <转录文件> | python3 -c "
import sys, json
roles = {}
for line in sys.stdin:
    try:
        obj = json.loads(line)
        r = obj.get('type') or obj.get('role') or 'unknown'
        roles[r] = roles.get(r, 0) + 1
    except: pass
for r, c in sorted(roles.items(), key=lambda x:-x[1]):
    print(f'  {r}: {c}')
"

# 3. 找 tool_use 块
cat <转录文件> | python3 -c "
import sys, json
for line in sys.stdin:
    try:
        obj = json.loads(line)
        if obj.get('type') == 'assistant':
            for block in obj.get('message',{}).get('content',[]):
                if isinstance(block, dict) and block.get('type') == 'tool_use':
                    print(block['id'], block['name'], json.dumps(block['input'], ensure_ascii=False)[:80])
    except: pass
"

# 4. 看工具 schema
grep -A 15 "export interface FileReadInput" .../sdk-tools.d.ts
相关推荐
TrisighT2 小时前
一张路由表少写一行,装好的 tri-loop 就成了死 skill:我把 tri-intent 的 27 个落点扒了一遍
aigc·agent·ai编程
5pat54OfCg2 小时前
OpenCode 源码拆解(一):Agent 怎么活下来?——进程生命周期的 3 个设计模式
agent
树状图设计者2 小时前
MCP2026-07-28:无状态核心拆解
agent·mcp
新知图书2 小时前
2.3 开发工作流
人工智能·agent·ai agent·智能体
安逸sgr2 小时前
如何减少 Prompt 引起的幻觉和答非所问?
人工智能·ai·大模型·prompt·agent·智能体
城管不管2 小时前
重生——第五次面试2026.8.1一面
java·数据库·后端·ai·面试·职场和发展·agent
小马9263 小时前
GPT-6 Swarm 架构深度解析:从单体模型到 Agent 集群协同的范式跃迁
gpt·架构·大模型·openai·agent·分布式ai·swarm架构
utmhikari4 小时前
【AI原生】用AI-Native的方式编写SRE告警诊断Agent和Skill
人工智能·agent·稳定性·ai-native·sre·skill·rca
chaors10 小时前
DeepResearchSystem 0x06:LLM as Judge
llm·agent·ai编程