Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

并行、取消与终止

一条 assistant 响应可以同时包含多个工具调用。如果所有工具都完全串行,读三个独立文件也要等三次;如果所有工具都盲目并行,两个写操作又可能相互覆盖。

因此 Claude Code 不是只有“是否并行”一个开关,而是为每种工具定义并发安全性、中断语义和上下文更新边界。

两种工具调度路径

路径启动时机并发原则
流式工具执行一个 tool_use 内容块完成后立即尝试可并发工具可以边采样边运行;非安全工具必须独占
响应后批量执行整条模型响应完成后连续安全调用成批并行,非安全调用分开串行

流式路径是条件启用的能力,而且在一次查询开始时快照。这避免流正在运行时,功能开关突然变化,导致前半批与后半批使用不同调度语义。

并发安全是工具协议的一部分

可并发工具通常是相互独立的读取或搜索。非安全工具通常可能修改文件、工作目录或共享运行时状态。

并发分类必须来自工具本身,不能靠模型猜测。模型可以在一条响应里提出多个意图,但本地调度器才知道哪些可以同时运行。

工具还可以带来上下文修改,例如更新工作目录或读取缓存。这类修改必须有可序列化语义,否则并发完成顺序会让最终状态不可预测。

进度通道和结果通道分开

流式工具执行器会分开管理:

  • progress:可随时唤醒消费者,提供实时可观测性;
  • final result:只在工具进入成功、错误或取消终态时产生,用于下一次模型请求。

分开后,UI 不需要等所有工具完成才更新,模型历史又不会被短暂进度刷屏。

取消是一棵有明确边界的信号树

一个主回合的父取消信号会向下传递到 API 流、工具执行、Hook 和压缩。条件启用的流式工具执行路径还拥有一层兄弟信号,再为各工具建立执行信号:

查询父信号 → 流式批次的兄弟信号 → 每个工具的执行信号

当前可观察语义包括:取消整个用户回合;流式路径中的 Bash 失败取消同批兄弟工具;以及工具自身声明的允许取消或阻塞中断。响应结束后的普通批量执行路径没有 Bash 专属的兄弟取消策略。

这棵树并不提供一个通用的“任意点名单独取消某个工具、其他工具照常继续”控制接口。每工具信号主要用于传播正确的生命周期与清理语义,不能把内部信号层级误读成对外调度能力。

用户中断的两个阶段

模型流阶段

中断后优先停止响应流,但必须先为已经产生的工具意图补齐结果,避免 transcript 留下半个工具回合。

工具阶段

中断后工具执行器会尝试收齐已完成结果,并为未完成调用生成取消结果,然后主循环才以“工具阶段被中断”结束。

工具默认可以声明“阻塞中断”或“允许取消”。这个声明很重要:某些修改了外部状态的动作,在不可预知的中间点强制终止可能比让它完成更危险。

interrupt 与普通取消的区别

当中断原因是新用户消息接管时,运行时可以不再追加一条冗余的“已中断”可见消息,因为下一条用户输入已经表达了接管。

这是一个小但精妙的协议设计:取消不仅是布尔值,还携带意图,从而决定会话应该如何呈现。

从流转为非流的重复副作用风险

条件启用时,流式请求失败可以用同一模型的非流式请求重试。但如果部分流已经启动了工具,新的非流请求可能再次产生同一意图。

运行时可以用墓石清理旧 assistant 尝试、丢弃旧执行器的后续结果,但已经发生的外部副作用无法靠消息撤回。

因此流转非流是一项带明确取舍的恢复策略,并提供关闭边界。这也说明为什么外部副作用最好具有幂等键或重复检测。

终止前的最后检查

工具批次完成不代表回合必然继续。主循环还要检查:

  • 取消信号是否已触发;
  • Hook 是否要求停止继续;
  • Agent 是否达到最大回合数;
  • 工具结果是否保持协议配对;
  • 是否还有需要在下一迭代注入的附件或队列消息。

并发、取消和终止不是三个独立功能,而是同一调度状态机的三个维度。

源码定位

  • 流式工具调度:src/services/tools/StreamingToolExecutor.ts
  • 响应后批量执行:src/services/tools/toolOrchestration.ts
  • 查询级取消与终止:src/query.ts
  • 流式恢复与非流 fallback:src/services/api/claude.ts
  • 查询入口功能快照:src/query/config.ts