乐于分享
好东西不私藏

Claude Code 源码拆解:流式工具执行 + 并发安全标记

Claude Code 源码拆解:流式工具执行 + 并发安全标记

深入 Anthropic Claude Code 源码,拆解一个让 AI Agent 快一倍的工程设计。


前言

2026 年 3 月 31 日,Anthropic 的 Claude Code CLI 工具因 npm 包中的 source map 暴露,其完整 TypeScript 源码被公开。这套代码体量惊人——1,900 个文件、51 万行代码——堪称目前公开可见的最复杂的 AI Agent 工程实现。

我花了一些时间研读,发现其中有一个设计堪称”教科书级别”——流式工具执行(Streaming Tool Execution)+ 并发安全标记(Concurrency Safety Marking)

这个设计解决了一个所有 Agent 框架都面临的核心性能问题:模型在思考时,CPU 在空等。


一、先搞清楚问题:传统 Agent 的执行时序

大多数 Agent 框架(包括 LangChain、AutoGPT 等)的工具调用流程是这样的:

┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  调用 LLM   │───▶│ 等待输出完成 │───▶│  执行工具    │───▶│  再调 LLM   │
│  (2-10s)    │    │  (空等)      │    │  (0.5-5s)   │    │  (2-10s)    │
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘

这里有两个浪费:

  1. 1. LLM 输出是流式的,第一个 tool_use 块可能在第 2 秒就完整了,但你得等到第 8 秒所有输出结束才开始执行
  2. 2. 多个工具是串行的,即使”读文件 A”和”读文件 B”完全独立,也是一个做完再做下一个

Claude Code 对这两个问题的回答是:不等,边收边执行;能并行的就并行,不能的就串行。


二、核心设计一:流式工具执行

关键代码:模型还在说话,工具已经在跑了

在 Claude Code 的核心查询循环 query.ts 中,当 LLM 的流式响应到达时:

// query.ts — 模型流式输出的处理循环
if (message.type === 'assistant') {
  assistantMessages.push(message)

const msgToolUseBlocks = message.message.content.filter(
content => content.type === 'tool_use',
  ) asToolUseBlock[]

if (msgToolUseBlocks.length > 0) {
    toolUseBlocks.push(...msgToolUseBlocks)
    needsFollowUp = true
  }

// 关键:模型还在流式输出,工具已经开始执行
if (streamingToolExecutor) {
for (const toolBlock of msgToolUseBlocks) {
      streamingToolExecutor.addTool(toolBlock, message)  // 立即调度!
    }
  }
}

// 同时收割已完成的结果
if (streamingToolExecutor) {
for (const result of streamingToolExecutor.getCompletedResults()) {
yield result.message// 工具结果立即输出
  }
}

注意这里的时序:

传统方式:
  LLM输出: ████████████████████████████████
  工具执行:                                  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓
  总耗时:   ════════════════════════════════════════════════

Claude Code:
  LLM输出: ████████████████████████████████
  工具1:        ▓▓▓▓▓▓                        ← 第一个 tool_use 解析完就开跑
  工具2:              ▓▓▓▓                    ← 第二个紧跟着
  工具3:                  ▓▓▓▓▓▓▓▓            ← 第三个也是
  总耗时:   ════════════════════════════════    ← 节省了整个工具执行时间!

StreamingToolExecutor 的状态机

每个工具在执行器中经历四个状态:

typeToolStatus = 'queued' | 'executing' | 'completed' | 'yielded'
  queued → executing → completed → yielded
    │                      │
    └── 被取消(abort)────┘  → 生成合成错误消息

addTool() 将工具入队后立即触发 processQueue(),调度逻辑检查当前并发状态后决定是立刻执行还是等待。


三、核心设计二:并发安全标记

流式执行引入了一个新问题:哪些工具可以并行跑?

“读文件”和”搜索代码”可以同时跑,但”写文件 A”和”删除文件 A”绝不能同时跑。Claude Code 的方案是让每个工具自己声明是否并发安全。

每个工具的自我声明

// GlobTool — 文件搜索,纯读取,天然并发安全
isConcurrencySafe() {
returntrue
},

// GrepTool — 内容搜索,纯读取,并发安全
isConcurrencySafe() {
returntrue
},

// FileReadTool — 读文件,并发安全
isConcurrencySafe() {
returntrue
},

// WebFetchTool — 抓网页,并发安全
isConcurrencySafe() {
returntrue
},

// AgentTool — 子代理,并发安全(子代理内部自己管理)
isConcurrencySafe() {
returntrue
},

// BashTool — 关键!根据命令内容动态判断
isConcurrencySafe(input) {
returnthis.isReadOnly?.(input) ?? false
// ls, cat, grep → true(只读命令可并行)
// rm, mv, pip install → false(写命令必须独占)
},

// McpAuthTool — 认证操作,不能并发
isConcurrencySafe() =>false,

注意 BashTool 的设计——它不是简单地返回 true/false,而是解析命令内容后动态判定。ls && cat foo.txt 是只读的,可以并行;mkdir build && npm run build 有写操作,必须独占。

下面我们深入展开这个判定过程。


三(续)、深入:BashTool 如何根据命令内容判断并发安全性

BashTool 的 isConcurrencySafe 只有一行代码,但它背后是一个五层纵深防御的只读判定体系。

第一层:入口——isConcurrencySafe 委托给 isReadOnly

// BashTool.tsx
isConcurrencySafe(input) {
returnthis.isReadOnly?.(input) ?? false// 并发安全 = 只读
},
isReadOnly(input) {
const compoundCommandHasCd = commandHasAnyCd(input.command)
const result = checkReadOnlyConstraints(input, compoundCommandHasCd)
return result.behavior === 'allow'
},

核心逻辑是:如果命令是只读的,那它就是并发安全的。这个等式很直觉——只读操作不修改状态,多个同时跑也不会互相干扰。

第二层:复合命令拆分与安全预检

checkReadOnlyConstraints 是整个判定的总调度器。它拿到原始命令后,先做一系列安全预检

exportfunctioncheckReadOnlyConstraints(input, compoundCommandHasCd) {
// 1. 命令能不能被解析?不能解析 → 无法判断 → 不允许并发
const result = tryParseShellCommand(command)
if (!result.successreturn { behavior'passthrough' }

// 2. 通用安全检查(命令替换、变量注入等)
if (bashCommandIsSafe_DEPRECATED(command).behavior !== 'passthrough')
return { behavior'passthrough' }

// 3. Windows UNC 路径检查(防止 WebDAV 凭证泄露)
if (containsVulnerableUncPath(command))
return { behavior'ask' }

// 4. cd + git 组合检查(防止沙箱逃逸)
if (compoundCommandHasCd && hasGitCommand)
return { behavior'passthrough' }

// 5. 裸 git 仓库检查(防止恶意 hooks 执行)
if (hasGitCommand && isCurrentDirectoryBareGitRepo())
return { behavior'passthrough' }

// 6. 写 git 内部路径 + 执行 git(防止构造恶意仓库后触发 hooks)
if (hasGitCommand && commandWritesToGitInternalPaths(command))
return { behavior'passthrough' }

// 7. 最终:拆分复合命令,逐个子命令验证
const allSubcommandsReadOnly = splitCommand(command)
    .every(subcmd =>isCommandReadOnly(subcmd))

if (allSubcommandsReadOnly) return { behavior'allow' }
return { behavior'passthrough' }
}

注意第 4-6 步的 git 特殊防御:攻击者可以构造一个命令如 mkdir -p hooks && echo 'malicious' > hooks/pre-commit && git status,先创建恶意 git hooks 文件,再触发 git 来执行它们。这种组合攻击会被专门拦截。

第三层:单命令只读判定——双轨验证

每个子命令要过两道关卡:

关卡 A:基于 flag 解析的命令白名单(isCommandSafeViaFlagParsing

这是最精密的一层。Claude Code 维护了一个巨大的命令安全配置表,精确到每个 flag 级别:

constCOMMAND_ALLOWLIST = {
grep: {
safeFlags: {
'-i''none',     // 忽略大小写,安全
'-r''none',     // 递归搜索,安全
'-n''none',     // 显示行号,安全
'-c''none',     // 只显示计数,安全
'-A''number',   // 后文行数,参数必须是数字
'-B''number',   // 前文行数,参数必须是数字
'-e''string',   // 指定模式,安全
// ... 50+ 个 flag 逐一声明
    },
  },
sed: {
safeFlags: {
'-n''none',     // 静默模式,安全
'-E''none',     // 扩展正则,安全
// 注意:-i(原地修改)不在列表里 → 被排除 → 不是只读
    },
// 额外的自定义校验回调
additionalCommandIsDangerousCallback(rawCommand, args) =>
      !sedCommandIsAllowedByAllowlist(rawCommand),
  },
'git diff': {  // 支持多词命令
safeFlags: { /* ... */ },
  },
'git log': {
safeFlags: { /* ... */ },
  },
// ... 几十个命令的完整配置
}

Flag 的参数类型也有严格约束:

typeFlagArgType =
  | 'none'// 无参数(--color, -n)
  | 'number'// 必须是整数(--context=3)
  | 'string'// 任意字符串
  | 'char'// 单字符(分隔符)
  | '{}'// 只能是字面量 "{}"
  | 'EOF'// 只能是字面量 "EOF"

不在白名单里的 flag 一律拒绝。比如 sed -i 的 -i 不在 safeFlags 中,因为它会原地修改文件 → 不是只读 → 不允许并发。

关卡 B:正则匹配的简单只读命令

对于不需要复杂 flag 分析的简单命令,用正则快速匹配:

constREADONLY_COMMANDS = [
'cat''head''tail''wc''stat''strings',  // 文件查看
'id''uname''free''df''du',                // 系统信息
'basename''dirname''realpath',                 // 路径操作
'cut''paste''tr''column''sort',            // 文本处理
'diff''comm''cmp',                             // 文件比较
'which''type''sleep''true''false',         // 杂项安全命令
// ... 50+ 个命令
]

每个命令被转化为安全正则(makeRegexForSafeCommand),确保不含 Shell 元字符:

functionmakeRegexForSafeCommand(commandstring): RegExp {
// 匹配:命令名 + 不含危险字符的参数
// 阻止:管道 |、重定向 <>、命令替换 $()``、花括号展开 {} 等
returnnewRegExp(`^${command}(?:\\s|$)[^<>()$\`|{}&;\\n\\r]*$`)
}

第四层:变量展开和 Glob 防护

在进入命令白名单匹配之前,还有一道关键检查——阻止运行时才能确定的内容

// 阻止未引用的 $ 变量展开
// 攻击:uniq --skip-chars=0$_ → $_ 运行时展开为上一个命令的参数
// 通过 IFS 分词,可以偷渡额外的位置参数,绕过"只有 flag"的正则

// 阻止未引用的 Glob 字符
// 攻击:python * → 如果目录下有个叫 --help 的文件
// * 会展开为 python --help,改变命令行为

functioncontainsUnquotedExpansion(commandstring): boolean {
let inSingleQuote = false
let inDoubleQuote = false
for (let i = 0; i < command.length; i++) {
// 追踪引号状态...

// 单引号内一切都是字面量,跳过
if (inSingleQuote) continue

// $ 在双引号和非引用位置都会展开
if (currentChar === '$') {
const next = command[i + 1]
if (next && /[A-Za-z_@*#?!$0-9-]/.test(next)) returntrue
    }

// Glob 只在非引用位置展开
if (!inDoubleQuote && /[?*[\]]/.test(currentChar)) returntrue
  }
returnfalse
}

精彩之处在于引号状态追踪——grep '$HOME'(单引号)中的 $ 是安全的字面量,但 grep "$HOME"(双引号)中的 $ 会被 Shell 展开,不安全。

第五层:特定命令的自定义校验回调

有些命令的危险性不能只靠 flag 判断,需要语义级别的检查:

// ps 命令:BSD 风格的 'e' 参数会泄露环境变量
ps: {
safeFlags: { '-e''none''-f''none'/* ... */ },
additionalCommandIsDangerousCallback(rawCommand, args) => {
// 检测 BSD 风格选项中是否包含 'e'
// "ps aux" 安全,但 "ps auxe" 不安全(e 泄露所有进程的环境变量)
return args.some(
a => !a.startsWith('-') && /^[a-zA-Z]*e[a-zA-Z]*$/.test(a),
    )
  },
},

// date 命令:位置参数可能设置系统时间
date: {
safeFlags: { '-d''string''-u''none'/* ... */ },
additionalCommandIsDangerousCallback(rawCommand, args) => {
// 位置参数必须以 + 开头(格式字符串如 +"%Y-%m-%d")
// 不以 + 开头的位置参数(如 MMDDhhmm)是在设置系统时间!
// ... 逐 token 分析 ...
  },
},

// tput 命令:某些终端能力值可以执行代码
tput: {
safeFlags: { '-T''string''-V''none' },
additionalCommandIsDangerousCallback(rawCommand, args) => {
constDANGEROUS_CAPABILITIES = newSet([
'init''reset',  // 执行 iprog(terminfo 中的任意代码)
'clear',          // 清除回滚缓冲(证据销毁)
'mc5''mc5p',    // 激活媒体复制(重定向到打印机)
'pfloc',          // 本地执行字符串
// ...
    ])
return args.some(token =>DANGEROUS_CAPABILITIES.has(token))
  },
},

完整判定流程图

输入: "git diff HEAD~3 && grep -r 'TODO' src/"
  │
  ▼
┌──────────────────────────────┐
│  Shell 解析器能解析吗?        │ ── 不能 → 不是只读 → 不可并发
└──────────┬───────────────────┘
           │ 能
           ▼
┌──────────────────────────────┐
│  通用安全检查                  │
│  (命令替换? 变量注入? UNC?)    │ ── 有风险 → 不是只读
└──────────┬───────────────────┘
           │ 通过
           ▼
┌──────────────────────────────┐
│  Git 特殊防御                 │
│  (cd+git? 裸仓库? 写hooks?)   │ ── 命中 → 不是只读
└──────────┬───────────────────┘
           │ 通过
           ▼
┌──────────────────────────────┐
│  拆分复合命令                  │
│  "git diff HEAD~3"           │
│  "grep -r 'TODO' src/"      │
└──────────┬───────────────────┘
           │
     ┌─────┴─────┐
     ▼           ▼
  子命令1      子命令2
     │           │
     ▼           ▼
┌─────────┐  ┌─────────┐
│ flag解析 │  │ flag解析 │
│白名单校验│  │白名单校验│   ← 每个 flag 逐一比对安全配置表
└────┬────┘  └────┬────┘
     │ 通过       │ 通过
     ▼           ▼
┌─────────┐  ┌─────────┐
│自定义回调│  │自定义回调│   ← 语义级别的额外检查
└────┬────┘  └────┬────┘
     │ 通过       │ 通过
     ▼           ▼
  ✅ 只读       ✅ 只读

  全部子命令都是只读 → ✅ isConcurrencySafe = true → 可以并行!

为什么这个设计值得学?

1. 白名单 > 黑名单

不是”列出哪些命令危险”,而是”列出哪些命令安全”。不在白名单里的一律当作不安全——这意味着新出现的未知命令自动被保护

2. flag 级别的精度

sed -n 'p'(只打印)vs sed -i 's/foo/bar/'(原地修改),同一个命令因为 flag 不同,安全性完全不同。精确到 flag + 参数类型的白名单,是这个系统能在保证安全的同时最大化并发的关键。

3. 防御纵深(Defense in Depth)

不是只有一道关卡,而是五层层层递进。即使某一层被绕过(比如 Shell 解析的边界 case),后面的层还能拦住。tput 的 -S flag 就是一个典型——validateFlags 会拦它,additionalCommandIsDangerousCallback 也会拦它,双保险。

4. 解析器一致性防护

Claude Code 发现了一个微妙的安全问题:Shell 解析库(shell-quote)和真实的 Bash 对同一个命令的解析可能不一致。比如 $VAR 在解析库中是字面量,但在 Bash 中会展开。所以它统一拒绝所有包含 $ 的 token,从根源消除解析差异攻击面。

代码中甚至有这样的注释:

// SECURITY: `$Z--output=/tmp/pwned` 在解析器中以 `$` 开头 → 被当作位置参数
// 但 Bash 运行时 $Z 为空 → 变成 `--output=/tmp/pwned` → 任意文件写入

这种对”解析器差异 = 攻击面”的深刻理解,是真正在生产环境被打过之后才会有的防御意识。


默认值:fail-closed

// Tool.ts — buildTool 的默认值
constTOOL_DEFAULTS = {
isConcurrencySafe(_input?: unknown) =>false,  // 默认不允许并发
// ...
}

如果工具开发者忘了声明,默认就是不允许并发。这是安全工程中的经典原则——默认关闭(fail-closed),宁可慢一点,也不出并发 bug。


四、调度算法:分区批处理

有了每个工具的并发安全标记,调度器如何编排执行顺序?

partitionToolCalls:把工具调用分成批次

functionpartitionToolCalls(toolUseMessages, toolUseContext): Batch[] {
return toolUseMessages.reduce((acc, toolUse) => {
const tool = findToolByName(toolUseContext.options.tools, toolUse.name)
const isConcurrencySafe = tool?.isConcurrencySafe(parsedInput.data)

// 连续的并发安全工具合并到同一个批次
if (isConcurrencySafe && acc[acc.length - 1]?.isConcurrencySafe) {
      acc[acc.length - 1].blocks.push(toolUse)
    } else {
// 非安全工具或新的批次
      acc.push({ isConcurrencySafe, blocks: [toolUse] })
    }
return acc
  }, [])
}

假设模型输出了这些工具调用:

1. GrepTool("搜索 API 定义")          ← 并发安全
2. FileReadTool("读 config.ts")       ← 并发安全
3. FileReadTool("读 types.ts")        ← 并发安全
4. BashTool("npm install lodash")     ← 非并发安全
5. FileEditTool("修改 config.ts")     ← 非并发安全
6. GrepTool("验证修改结果")            ← 并发安全

分区结果:

批次 1 [并行]: GrepTool + FileReadTool + FileReadTool  ← 三个同时跑
批次 2 [串行]: BashTool("npm install")                 ← 独占执行
批次 3 [串行]: FileEditTool("修改文件")                 ← 独占执行
批次 4 [并行]: GrepTool("验证")                         ← 独占(只有一个)

StreamingToolExecutor 的并发控制

流式执行器用更精细的逻辑控制准入:

privatecanExecuteTool(isConcurrencySafeboolean): boolean {
const executingTools = this.tools.filter(t => t.status === 'executing')
return (
    executingTools.length === 0 ||  // 没有正在执行的 → 直接跑
    (isConcurrencySafe && executingTools.every(t => t.isConcurrencySafe))
// 自己安全 && 正在跑的也都安全 → 可以一起跑
  )
}

用一句话概括这个规则:安全的工具可以和其他安全的工具并行;不安全的工具必须独占。


五、错误传播:兄弟取消机制

并行执行引入了另一个问题:如果三个 Bash 命令并行跑,第一个失败了,剩下两个怎么办?

Claude Code 的答案是兄弟取消(Sibling Cancellation),但只对 Bash 工具生效

// StreamingToolExecutor.ts — 执行过程中检测错误
if (isErrorResult) {
  thisToolErrored = true
// 只有 Bash 错误才取消兄弟。
// Bash 命令常有隐式依赖链(mkdir 失败 → 后续命令没意义)。
// Read/WebFetch 等是独立的——一个失败不应该影响其他。
if (tool.block.name === BASH_TOOL_NAME) {
this.hasErrored = true
this.erroredToolDescription = this.getToolDescription(tool)
this.siblingAbortController.abort('sibling_error')
  }
}

为什么只对 Bash 做兄弟取消?因为 Shell 命令之间常有隐式依赖mkdir build 失败了,后面的 cp file build/ 必然也会失败),而 FileReadTool 读两个不同文件是完全独立的,一个 404 不影响另一个。

被取消的工具收到的是合成错误消息,不是静默吞掉:

// 生成给模型看的取消原因
const msg = desc
  ? `Cancelled: parallel tool call ${desc} errored`
  : 'Cancelled: parallel tool call errored'

这样模型知道”不是工具本身出错,而是因为兄弟命令失败被连带取消了”,可以做出更好的错误恢复决策。


六、有序输出:Results 按接收顺序发送

并行执行还有一个隐含挑战:工具 A 先发起但后完成,工具 B 后发起但先完成,结果该按什么顺序给模型?

Claude Code 选择了**按接收顺序(FIFO)**输出,但有一个巧妙的”跳过”机制:

*getCompletedResults(): Generator<MessageUpdatevoid> {
for (const tool ofthis.tools) {  // 按加入顺序遍历
// 进度消息随时输出,不等排队
while (tool.pendingProgress.length > 0) {
yield { message: tool.pendingProgress.shift()! }
    }

if (tool.status === 'yielded'continue

if (tool.status === 'completed' && tool.results) {
      tool.status = 'yielded'
for (const message of tool.results) {
yield { message }
      }
    } elseif (tool.status === 'executing' && !tool.isConcurrencySafe) {
break// 非安全工具未完成时,阻塞后续输出(保持因果序)
    }
// 安全工具未完成时,跳过继续检查后面的(不阻塞)
  }
}

规则是:

  • • 并发安全工具:谁先完成谁先输出,不阻塞后面的
  • • 非并发安全工具:它没完成时,后面所有结果都等着(维护因果顺序)
  • • 进度消息:随时输出,不参与排序

七、Fallback 与中断处理

流式执行还要处理两个边缘场景:

模型降级(Fallback)

如果 LLM 请求触发了降级(比如某个模型不可用,切换到备用模型),已经在跑的工具怎么办?

// query.ts — 降级时丢弃所有进行中的工具
if (streamingToolExecutor) {
  streamingToolExecutor.discard()  // 标记为废弃
  streamingToolExecutor = newStreamingToolExecutor(...)  // 创建新的
}

discard() 设置一个标志位,所有排队中和执行中的工具都会收到 streaming_fallback 合成错误,不会把过期的 tool_result 送给新模型。

用户中断(ESC 键)

privategetAbortReason(tool) {
if (this.discardedreturn'streaming_fallback'
if (this.hasErroredreturn'sibling_error'
if (this.toolUseContext.abortController.signal.aborted) {
if (signal.reason === 'interrupt') {
// 用户输入了新消息,检查工具的中断策略
returnthis.getToolInterruptBehavior(tool) === 'cancel'
        ? 'user_interrupted'
        : null// 'block' 类工具不取消(如正在写的文件要写完)
    }
return'user_interrupted'
  }
returnnull
}

每个工具还可以声明自己的中断策略interruptBehavior):cancel(立即取消)或 block(写完再停)。正在写文件的工具不会因为用户按了 ESC 就写一半。


八、总结:这个设计为什么值得学

用一张图概括整个架构:

                     ┌─────────────────────────────────┐
                     │         LLM 流式输出             │
                     │   tool_use_1  tool_use_2  ...   │
                     └──────┬────────────┬─────────────┘
                            │            │
                   ┌────────▼────────────▼──────────┐
                   │   StreamingToolExecutor         │
                   │                                 │
                   │   addTool() → processQueue()    │
                   │        │                        │
                   │   ┌────▼─────┐                  │
                   │   │canExecute│                   │
                   │   │Tool()?   │                   │
                   │   └──┬───┬──┘                   │
                   │      │   │                      │
                   │  ┌───▼┐ ┌▼───┐                  │
                   │  │并行│ │串行│                    │
                   │  │批次│ │批次│                    │
                   │  └───┘ └────┘                   │
                   │      │                          │
                   │   getCompletedResults()          │
                   │   (按序输出 + 进度实时推送)        │
                   └─────────────────────────────────┘

五个值得搬进你的 Agent 的设计原则

1. 让工具自描述安全属性,而非在调度器里硬编码

每个工具通过 isConcurrencySafe(input) 自己声明。注意参数是 input——同一个工具,不同输入可能有不同的并发安全性(BashTool 根据命令是否只读来判断)。

2. 默认关闭(Fail-Closed)

buildTool 的默认值 isConcurrencySafe: () => false。开发者不声明 = 不允许并发。安全第一。

3. 不等模型说完就开始干活

LLM 的流式输出是一个 token 一个 token 到达的。当一个完整的 tool_use JSON 块解析完毕时,不需要等后面的 token,立即执行。

4. 分区批处理保持因果序

连续的安全工具合并为一个并行批次,遇到不安全工具就切分。这既最大化了并行度,又保证了有依赖关系的操作不会乱序。

5. 错误传播要分类

不是所有错误都该取消兄弟。Bash 命令有隐式依赖链,一个失败就全停;但文件读取是独立的,一个 404 不影响其他。针对工具语义定制错误传播策略。


后记

这个设计看起来不复杂,但要做到正确非常难。中断处理、降级回滚、有序输出、错误传播的每一个边缘 case 都可能导致状态不一致。Claude Code 的 StreamingToolExecutor 只有 530 行代码,但处理了全部这些 case,是一个非常精良的工程实现。

如果你正在构建自己的 Agent 框架,这个模式值得反复研读。核心思想只有一句话:

把等待变成执行,把串行变成并行,但在安全边界处绝不妥协。


本文基于 Claude Code 公开源码快照的安全研究分析。源码通过 npm 包 source map 暴露事件公开获取,本分析仅用于技术学习目的。