02|Tool Runtime 不是工具箱,而是行动层:从 FileRead / FileEdit 看到 Agent 工程

这是「Claude Code 第一性原理拆解」的第 3 篇。

前一篇我在讲"回合怎么推进",这一篇我只讨论"动作怎么落地":也就是 Tool Runtime 为什么会成为 Agent 的行动层。

摘要

这一篇我会把 Claude Code 的工具层压成一个更稳定的理解框架:

  • 工具不是孤立函数,而是带 schema、上下文、权限和状态回流语义的运行时对象。
  • ToolUseContexttoolOrchestration.tstoolExecution.ts 一起定义了 Claude Code 的动作协议。
  • FileRead / FileEdit 这类基础工具,最能暴露一个 Agent 系统到底是不是认真。

读完这一篇你应该能回答

  • 为什么 Tool Runtime 比"加几个工具给模型"要复杂得多。
  • 为什么并发必须建立在动作语义而不是性能冲动上。
  • 为什么错误分类、结果规范化和消息回写都属于工具主路径。

如果说主循环决定了 Agent 怎么活着,那 Tool Runtime 决定的就是 Agent 怎么动手。

这也是我读 Claude Code 时最强烈的一个感受:

它并不是简单地给模型"挂了几个工具",而是认真地把"动作执行"做成了一层独立系统。

很多人第一次做 Agent,都会天然把工具理解成:

  • 一个函数
  • 一个 API
  • 一个被模型触发的外部能力

这个理解不算错,但太轻。

因为在一个真正会行动的系统里,工具从来不只是"可调用函数",它至少还意味着:

  • 输入要校验
  • 权限要判断
  • 执行要编排
  • 错误要分类
  • 结果要回到消息链里
  • 状态可能会被修改

也就是说,工具不是"插件列表",而是 Agent 的行动层。

一、从第一性原理看,为什么 Agent 一定会长出 Tool Runtime

如果我只从问题出发,而不是从 Claude Code 的代码出发,我会这样推。

模型只能产生意图,不能直接产生真实世界动作

比如模型说:

  • 读这个文件
  • 改这个函数
  • 跑一下测试

这些都只是"动作意图",还不是系统动作本身。

系统必须负责把它们翻译成:

  • 哪个工具
  • 用什么参数
  • 在什么环境里执行
  • 执行完后如何反馈

所以只要一个 Agent 不止是聊天,它几乎一定会长出一层:

intent -> action 的运行时翻译层

Claude Code 的 Tool Runtime,本质上就是这层。

二、源码主线:我为什么重点看 Tool.tstools.tstoolOrchestration.tstoolExecution.ts

如果我要抓 Claude Code 的工具主线,我会先锁定四个文件:

1. src/Tool.ts

这里是整个工具抽象的地基。

我最关心的不是某个 interface 名字,而是它到底把"工具调用"理解成什么。

里面最重要的,是 ToolUseContext 这种设计。

2. src/tools.ts

这里不是单纯列工具清单,而是在构造:

  • 当前环境下真正对模型可见的工具集合

3. src/services/tools/toolOrchestration.ts

这里处理的是:

  • 多个工具调用如何分批
  • 什么能并发
  • 什么必须串行

4. src/services/tools/toolExecution.ts

这里处理的是:

  • 单个工具调用到底怎么被执行
  • 在执行前后,权限、hook、错误分类、结果规范化如何串起来

把这四个文件连起来,我脑子里才有完整的"工具运行时"。

三、ToolUseContext 为什么是关键

很多 Agent demo 对工具的理解都很薄:

python 复制代码
def search(query: str) -> str:
    ...

这种接口当然够写 demo,但一旦系统复杂起来,马上就会出现问题:

  • 权限从哪拿
  • 当前消息从哪拿
  • 当前缓存从哪拿
  • 当前工具集和资源从哪拿
  • 工具执行后如何更新状态

Claude Code 用 ToolUseContext 的方式,把这些运行时状态聚到了一起。

我会把它翻译成这样的 Python 伪代码:

python 复制代码
class ToolUseContext:
    def __init__(self):
        self.options = {
            "tools": [],
            "commands": [],
            "mcp_clients": [],
            "mcp_resources": {},
            "main_loop_model": None,
        }
        self.abort_controller = AbortController()
        self.read_file_state = FileStateCache()
        self.messages = []

    def get_app_state(self):
        ...

    def set_app_state(self, updater):
        ...

    def set_response_length(self, updater):
        ...

    def update_file_history_state(self, updater):
        ...

这说明在 Claude Code 里,工具不是独立执行单元,而是:

  • 在会话内执行
  • 对会话负责
  • 依赖会话上下文

这是一个非常成熟的判断。

四、tools.ts 为什么不是工具列表,而是工具暴露策略

我觉得很多人看 tools.ts,第一反应会是:

  • 哦,项目里有哪些工具

但我更在意的是另一件事:

当前这一轮、当前这个环境,模型到底应该看到什么工具?

这件事很重要,因为模型能看到什么能力,直接决定了它怎么规划动作。

从 Claude Code 的做法可以看出来:

  • 工具集合不是静态的
  • 它会受 feature gate、环境能力、权限模式等因素影响

这其实是一种 runtime exposure control。

我自己以后设计 Agent,也会很重视这一点:

  • 工具不是越多越好
  • 工具要按当前场景有选择地暴露

五、toolOrchestration.ts 让我最认同的地方:并发必须以动作语义为前提

我非常认同 Claude Code 在工具调度上的一个核心判断:

并发不是默认值,并发应该建立在"动作语义安全"之上。

它大致的逻辑是:

  1. 找到具体工具
  2. 用 schema 解析输入
  3. 调工具自己的 isConcurrencySafe
  4. 连续的并发安全工具打成一批
  5. 非并发安全工具串行跑

我会把它压成下面的 Python 伪代码:

python 复制代码
def partition_tool_calls(tool_calls, context):
    batches = []
    for call in tool_calls:
        tool = find_tool(call.name)
        parsed = tool.input_schema.parse(call.input)
        safe = tool.is_concurrency_safe(parsed)
        if safe and batches and batches[-1].is_safe:
            batches[-1].calls.append(call)
        else:
            batches.append(Batch(is_safe=safe, calls=[call]))
    return batches


def run_tools(tool_calls, context):
    current_context = context
    for batch in partition_tool_calls(tool_calls, current_context):
        if batch.is_safe:
            updates = run_concurrently(batch.calls, current_context)
            current_context = apply_context_modifiers_in_logical_order(
                current_context, updates
            )
        else:
            for call in batch.calls:
                update = run_single_tool(call, current_context)
                current_context = update.new_context
        yield current_context

我特别喜欢"apply context modifiers in logical order"这一层意思。

因为在工具系统里,真正难的不只是并发执行,而是:

  • 并发执行完以后,状态怎么不乱

这恰好是很多 demo 最容易忽略的地方。

六、toolExecution.ts 才是完整的行动协议

如果说 toolOrchestration 是调度器,那 toolExecution 更像执行协议层。

它大致会负责:

  • tool 定位
  • schema 校验
  • 权限判断
  • hook 执行
  • tracing / telemetry
  • 错误分类
  • 结果标准化

这说明 Claude Code 对工具执行的理解不是:

  • 调个函数就完了

而是:

  • 每次工具调用都要走固定管道

我脑子里的版本大概是这样:

python 复制代码
def run_tool_use(tool_use, context):
    tool = registry.get(tool_use.name)
    parsed_input = validate(tool.input_schema, tool_use.input)

    pre_hooks(tool, parsed_input, context)

    permission = can_use_tool(tool, parsed_input, context)
    if permission.behavior == "deny":
        return denied_result(tool_use)
    if permission.behavior == "ask":
        return ask_user_then_resume(tool_use)

    start_trace(tool_use)
    try:
        result = tool.call(parsed_input, context)
        post_hooks(tool, result, context)
        return normalize_result(result)
    except Exception as e:
        kind = classify_tool_error(e)
        failure_hooks(tool, kind, e, context)
        return normalize_error(kind, e)
    finally:
        finish_trace(tool_use)

如果一个系统愿意把工具执行做成这样一条固定管道,我就会认为它已经不再是"工具拼装应用",而是在做真正的运行时。

七、从 FileReadToolFileEditTool 我看到的,不是两个工具,而是工程态度

我很喜欢看这种"看似基础"的工具,因为越基础,越能暴露团队的工程态度。

FileReadTool 暴露出的判断

它不只是读文本文件,还在认真处理:

  • 路径标准化
  • 设备文件阻断
  • 图片、PDF、notebook 特殊逻辑
  • token 预算
  • 权限检查

也就是说,它不是:

  • 读文件函数

而是:

  • 在 Agent 语境下受约束的文件读取能力

FileEditTool 暴露出的判断

它也不只是 replace string,而是在处理:

  • 文件是否存在
  • old/new string 是否合理
  • 文件过大怎么办
  • deny rule 怎么判
  • 并发修改冲突怎么办
  • line ending / quote style 如何保留

所以我会说:

真正的 Agent 工程,不体现在华丽能力上,而体现在基础能力是否被认真约束。

八、架构图:我心里的 Tool Runtime 是什么形状

flowchart TD A[模型输出 tool_use] --> B[Tool Registry] B --> C[Input Schema 校验] C --> D[ToolUseContext 注入] D --> E[Permission Check] E --> F[Pre Hooks] F --> G[Tool Call] G --> H[Post Hooks] G --> I[Error Classification] H --> J[Result Normalization] I --> J J --> K[写回消息流与状态]

我觉得这张图特别重要,因为它说明:

  • Tool Runtime 不是功能边角
  • 它是 Agent 从"想法"到"动作"的中枢

九、我自己的个人判断:Tool Runtime 比大多数人想象得更重要

我现在越来越觉得,一个团队有没有真正理解 Agent,往往看它怎么理解 tools。

如果它只把 tools 当成:

  • 给模型加点能力

那系统通常会很快碰到:

  • 状态混乱
  • 错误不可恢复
  • 权限四处散落
  • 观测难统一

但如果它把 Tool Runtime 当成行动层来设计,很多问题就会自然变得更清楚:

  • 什么时候可以行动
  • 怎么行动
  • 行动后如何反馈
  • 出错后如何恢复

十、这一篇我想留下的结论

如果只留一句话,我会写:

Claude Code 的 Tool Runtime 最值得学的地方,不是它托管了多少工具,而是它把"模型意图如何转成受控动作"这件事做成了一层清晰的运行时协议。

而这恰恰是我认为,大模型应用真正开始走向智能体系统时,最该认真设计的一层。

系列串联

相关推荐
AI专业测评2 小时前
2026网文圈大地震:顶配AI写作神器实测,这几款让“代练”彻底失业
人工智能·算法·aigc·ai写作
智者知已应修善业2 小时前
【数字稳压控制DAC/TLC5615驱动】2023-5-27
c++·经验分享·笔记·算法·51单片机
爱写代码的倒霉蛋2 小时前
2021年天梯赛L1-8
数据结构·算法
阿Y加油吧2 小时前
动态规划入门必刷:不同路径 & 最小路径和 详解
算法·动态规划
ximen502_2 小时前
算法面试题
java·数据结构·算法
zzzsde2 小时前
【Linux】进程信号(2)保存信号与信号处理
linux·运维·服务器·算法
QuZero2 小时前
Semaphore Principle
java·算法
ZPC82102 小时前
自定义机械臂驱动(Action Server + /joint_states 发布)
算法
啊我不会诶2 小时前
牛客练习赛151
算法·深度优先·图论