Agent Harness 源码拆解(五):工具系统——默认给几个工具,工具结果该写什么

工具是 harness 和真实世界的唯一接口,也是设计密度最高的一块。这一章的两个问题看似简单,答错了代价极大:默认给模型几个工具?工具执行完,给模型看什么?

六家的答案再次分道扬镳,但底下埋着两条共识,我们放到最后讲。

pi:默认只给四个,工具结果要给”下一步指引”

pi 有 8 个内置工具(read、bash、powershell、edit、write、grep、find、ls),但默认只激活 4 个(read、bash、edit、write),grep/find/ls 注册了但默认关着,bash 的自我描述里把自己定位成”ls、grep、find 的兜底”。

为什么默认这么少?因为每多一个工具,就是每轮请求多一份 schema + 一段提示词 + 一次模型选择歧义。bash 全能但烧 token,grep/find/ls 省 token 但不全能——权衡之后选”默认少、按需开”。

但 pi 真正出彩的是它对工具结果的理解,浓缩成一句话:工具结果是给模型的下一步指引,不是日志。

举几个它的实际做法:bash 输出超长被截断,附一句 Use offset=N to continue;输出超限落盘,附 Full output: <path>;参数校验失败,回显收到的参数让模型直接改对;edit 失败,逐条给出修复动作。道理很简单——模型遇死胡同会瞎猜,遇岔路口会前进。这条纪律的回报,高于任何提示词工程。

配套的还有一对严格分离:content(进模型的 token)和 details(给 UI 的结构化数据)。edit 工具算出了完整 diff,但 diff 进模型是烧 token、进 UI 才有价值,所以成功时给模型的只有一句”Successfully replaced N block(s)”,完整 diff 留在 details。设计任何工具返回时先问一句:模型真需要这个吗?多数 harness 在这里白烧 30% 以上的 token。

代价:默认 4 个工具的极简,意味着 bash 承担了太多,风险面集中在它一个工具上。pi 的应对是把权限门做成扩展(第 06 章),而不是靠工具本身设防。

codemode:脚本即工具,嵌套调用不进上下文

pi 最激进的一招是 codemode:让模型写一段 JavaScript、在沙箱里跑、脚本再去调其他工具,而不是逐条发 tool call。

入参只有一个字段 code——原始 JS 源码。模型对它的用法和普通工具一模一样,但一次调用能在脚本里扇出 N 个并行工具调用、Promise.allSettled 聚合、过滤掉大结果,最后只把 text() 的产物回灌给模型。核心价值在 README 一句话:”Nested tool calls never enter the LLM context; only the script’s output and return value do.“——批处理 200 个文件,模型不再需要 200 个来回,控制流下放给脚本,token 只为最终输出付费。

执行边界是全章最硬的部分:每次执行起一个 worker 线程,里面 new 一个全新 QuickJS wasm 实例,脚本碰不到宿主线程的事件循环;VM 的 importObject 只有一个 host-call 入口 + 一个 WASI shim,脚本没有 timer、没有 fetch、没有 process、没有 require——只能通过注册的工具触达外界。

这里有个容易被忽略的关键决定:codemode 不绕过工具管线。脚本里的每个 tools.x() 都落回 ctx.executeTool,走和模型直接调用完全相同的路径——参数校验、beforeToolCall 钩子、权限门。为什么?因为如果嵌套调用绕开权限门,codemode 就成了越权后门(一个脚本悄悄 bash 删库)。把嵌套调用塞回管线,权限门在一个地方成立就覆盖了所有调用路径。

代价是它诚实地标注了一条边界:调用是真的。”Tool calls are real: calls made before a failure are not undone.” codemode 不是事务,脚本失败前发出的写操作不会被回滚。

CodeWhale:每个工具进 active 集,都要算过一笔账

CodeWhale 的默认工具目录(read/write/edit/bash/agent/workflow/todo_write + goal 三件套 + load_skill)不是拍脑袋定的,而是预算决策的产物,每个名字进 active 集都有理由注释。

两个例子最有说服力:目标三件套(create_goal/get_goal/update_goal)eager 的理由是——系统提示里有继续指令,如果模型要先 tool_search 才能停掉自己启动的工作,那条指令就是假的;load_skill eager 的理由是一次显式换算——延迟化的话每次用技能要花一次发现跳 + 一次前缀重排,而让它 eager 只花约 134 字节的钉住成本,v0.9.6 落地时算过这笔账。

配套的还有一条锁死级不变量:目录头稳定性。所有非延迟工具组成的目录头,在模式切换(Plan ↔ Agent ↔ YOLO)时保持字节一致,延迟工具的激活追加到尾部、绝不重排头部——因为 DeepSeek 的 KV cache 靠这个前缀命中。

grok-build:五套工具人格,模型-工具分布匹配是质量变量

grok-build 最不寻常的设计是五套工具人格并存:grok_build(原生全套)、grok_build_concise(精简)、grok_build_hashline(锚点定位)、codex(移植 OpenAI Codex 的工具面)、opencode(移植 sst/opencode 的工具面)。

为什么?因为模型-工具分布匹配是质量变量,不是工程洁癖。一个在 codex 工具 schema 上训练过的模型,用原生 grok 工具反而发挥不出来——所以直接给它 codex 那套工具面。inject_default_tools: false 服务于”trained schema”模型。

代价:五套工具人格的维护成本是真实存在的,这只有在”要多模型适配”的定位下才划算。

opencode 和 Maka 的两笔

opencode 的工具系统框架只有 4 个文件,靠三条 Law 定死语义:单一执行器、codec 边界、存储封装。最有价值的是一条单一兜底截断点——工具返回完整 domain output,一个统一的结算边界做测量/预览/托管,截断从工具语义里剥离成基础设施,而不是”每个工具自己截断”。

Maka 的 MakaTool 接口把”工具”和”恢复契约”焊在一起——每个工具自带 recoveryMode,配合 T1/T2 副作用窗口(第 07 章),让工具执行的崩溃恢复有据可依。它的一句话:工具接口即治理接口。

两条共识

六家争得凶,但两条共识是铁的:

一是默认少、按需开。pi 默认 4 个、CodeWhale 每个 eager 都要算账、grok 按模型给工具面——没有任何一家把几十个工具全量塞进上下文。Anthropic 在工具设计指南里把这条说得更直白:“如果一个工程师都说不清某个场景该用哪个工具,就别指望 AI agent 能选对”——工具集膨胀带来的选择歧义,是比 schema token 成本更隐蔽的杀手。他们还分享过一个数据:用一个专门的 tool-testing agent 反复重写工具描述、规避掉失败用法后,后续 agent 的任务完成时间降了 40%——工具描述的质量,是被严重低估的优化点。

二是工具结果给”下一步”,不是给”日志”。pi 的 Use offset=N to continue、CodeWhale 的双形态、grok 的 output/prompt_text 分离,本质是同一件事:想清楚这条结果对模型的下一步意味着什么。

如果你要设计工具系统

  • 个人项目:学 pi,默认 4 个 + 按需开,工具结果写”下一步指引”,content/details 分离。
  • 要省钱、缓存敏感:学 CodeWhale,每个 eager 工具算一笔账,目录头字节冻结。
  • 要多模型适配:学 grok,按模型的训练分布给工具面。
  • 有副作用、要崩溃恢复:学 Maka,工具自带恢复契约。

四个最容易踩的翻车点:

一是工具结果当日志写。把完整 diff、完整输出全塞给模型,token 白烧还不给下一步方向。

二是默认开一堆工具。每轮 schema 撑爆上下文,模型选择歧义暴涨。

三是嵌套调用绕过权限门。脚本里调工具直连执行、跳过审批——越权后门。

四是每个工具自己截断。截断逻辑散在工具里,行为不一致、也无法统一托管(opencode 的单一兜底截断点)。

下一篇讲 权限与沙箱:什么能自动跑、什么要问人——CodeWhale 的九层授权管线、opencode 的 Deferred、Maka 的”权限是控制流不是对话框”。


本篇术语

  • active 工具集:默认激活、进模型上下文的工具子集;其余注册但按需开
  • exposure:工具对模型的可见程度(active/direct/deferred/hidden 四态)
  • content vs details:进模型 token 的内容 vs 给 UI 的结构化数据,严格分离
  • codemode:让模型写脚本在沙箱里跑、脚本再调工具,嵌套调用不进上下文
  • eager/deferred:工具 eager 常驻目录头,deferred 延迟激活追加到尾部
  • 工具人格(persona):针对不同模型训练分布移植的工具面
  • recoveryMode:工具自带的崩溃恢复契约,决定副作用窗口怎么解释

参考资料

参照系仓库(结论基于 2026-09 下旬至 10 月上旬各仓库 HEAD):

  1. earendil-works/pi —— packages/coding-agent/src/core/tools/ 与 packages/codemode
  2. anomalyco/opencode —— packages/core/src/tool/ 三条 Law
  3. xai-org/grok-build —— implementations/ 五套工具人格
  4. zai-org/ZCode
  5. apache/maka —— tool-runtime.ts MakaTool 恢复契约
  6. Hmbown/CodeWhale —— tool_catalog.rs eager/deferred 与目录头不变量

Harness 设计方法论文献:

  1. Anthropic,《Agent Harness Design: 3 Patterns for Harnessing Claude’s Intelligence》
  2. Anthropic,《Effective context engineering for AI agents》(2025-09)——“人都说不清用哪个工具,agent 更不行”与最小工具集论述出处
  3. Simon Willison,《Anthropic: How we built our multi-agent research system》——tool-testing agent 重写工具描述、任务完成时间降 40% 的转述