第四章 工具系统——四块积木,无限可能
核心问题:为什么只给 Agent 四个工具,反而比给一百个工具更强大?
你有没有遇到过这种场景:问 ChatGPT "帮我找出项目里所有的 TODO 注释",它认真地回答你——"你可以用 grep -r TODO . 来查找",然后就停住了。
它知道怎么做,但它做不到。它被关在纯文本的牢笼里,只能说话,不能行动——一个博学的空想家,有无限的知识,却没有一双手。
工具系统解决的,正是这个问题。它让 Agent 从"告诉你怎么做"变成"替你去做"。但工具系统有一道设计上的难题:应该给 Agent 多少工具?
OpenClaw 的答案出人意料——四个。
一、工具设计的两难困境
直觉上,工具越多越好。多一个工具,多一种能力,Agent 不就更强大了吗?
事实恰好相反。LLM 选择工具,靠的是工具的描述文本,而不是工具的代码。工具越多,描述空间越拥挤,边界越容易模糊。一旦两个工具功能相近,LLM 在选择时就开始犹豫——犹豫意味着错误率上升。
这里还有一个反直觉的推论:工具数量少,每个工具的描述空间反而更大,可以写得更精确。对比一下同一个 exec 工具在"四工具集"和"百工具集"中的描述质量——前者可以详细说明参数格式、适用场景、安全边界;后者不得不压缩描述,结果 LLM 理解不够准确,选错概率更高。工具数量的克制,直接带来了描述质量的提升,进而带来了决策准确性的提升。
工具数量与系统健康度之间,存在一个非线性关系:
| 工具集状态 | 典型问题 | 结果 |
|---|---|---|
| 工具太少 | 能力不足,很多任务根本完成不了 | Agent 无能为力 |
| 工具适中 | 边界清晰,各司其职 | 准确率高,组合灵活 |
| 工具太多 | 功能重叠,描述边界模糊 | 选择混乱,准确率下降 |
更深的问题在于认知负担。工具集的健康度,不由工具数量衡量,而由工具间的正交性衡量——每个工具只做自己的事,不和别人重叠。
这就是 Unix 哲学的精髓:做一件事,把它做好;让程序之间能够协作。
二、四块积木:read / write / edit / exec
OpenClaw 继承了这个哲学,提炼出四个工具。
它们的分工来自一个简单的洞察:感知需要一个统一通道,行动有三种根本不同的语义——创造、修改、触发。如果用人类的感官和肢体来类比:
| 工具 | 基础能力 | 人类感官类比 | 核心作用 |
|---|---|---|---|
read | 获取信息 | 眼睛(感知世界) | 读取文件,理解现状 |
write | 创造内容 | 笔(书写记录) | 创建新文件 |
edit | 修改内容 | 橡皮+笔(编辑整理) | 精确修改已有文件 |
exec | 执行操作 | 双手(触达世界) | 运行命令,触达外部 |
┌──────────────────────────────────────────────────┐
│ │
│ 感知世界 ──→ read(读取信息,理解现状) │
│ ↓ │
│ 思考推理 ←── LLM(理解情况,制定计划) │
│ ↓ │
│ 改变世界 ──→ write(创建新文件) │
│ edit (修改现有文件) │
│ exec (执行命令,触达外部) │
│ ↓ │
│ 观察结果 ─────────────────────→ 回到感知 │
│ │
└──────────────────────────────────────────────────┘一个感知通道,三个行动通道——不是凑数,是刻意的分工设计。
你可能会问:既然 exec 能运行任何命令,为什么还要单独设计 read、write、edit?直接用 cat、echo >、sed 不就行了?
这里有一道精妙的区别。exec 是万能工具,正因为万能,LLM 面对它时需要在脑海中构建完整的 Shell 命令,出错概率高。专用工具的价值在于:
| 场景 | exec 方式 | 专用工具方式 | 专用工具的额外保障 |
|---|---|---|---|
| 读文件 | cat file.txt | read("file.txt") | 三层上下文保护,不会读爆窗口 |
| 写文件 | echo "content" > file | write("file.txt", content) | 原子写入,语义明确是"全新创建" |
| 改文件 | sed -i 's/old/new/g' | edit("file.txt", old, new) | 精确匹配,找不到就报错而非误改 |
| 执行命令 | — | exec(command) | 命名清晰,可观测,可审计 |
约束即自由:工具的边界越清晰,LLM 的选择越准确。面对明确的 read 工具,LLM 做出正确决策的概率,远高于面对万能的 exec 后再自行组合命令。
read:感知世界,保护上下文
LLM 的上下文窗口是有限资源。把一个 10MB 的日志文件直接读进来,上下文就满了,Agent 会"忘记"自己在做什么。
read 用三层机制来保护上下文:
第一层:预算感知
计算当前上下文剩余空间
文件超出预算 → 触发截断
第二层:智能截断(按文件类型差异化)
代码文件 → 保留头部(import + 函数签名)
日志文件 → 保留尾部(最新的错误最有价值)
配置文件 → 尽量完整(配置项相互依赖)
第三层:分页支持
明确告知还有多少内容未读
Agent 可发起第二次调用继续读取分页机制里有一个容易被忽视的细节:它不是"能读大文件",而是"主动告知还有多少没读"。这种透明度让 Agent 能做出知情决策,而不是基于被截断的信息过度自信。
write:原子性创建
write 只做一件事:创建新文件。
最重要的特性是原子写入——先写入临时文件,成功后再重命名为目标文件。重命名在操作系统层面是原子操作,要么完成,要么不完成,不存在中间损坏状态。
这条"只创建新文件"的限制是刻意的保护。它防止了因文件名拼写错误意外覆盖已有内容的场景,也让工具调用历史里每一次 write 都保持清晰的语义——全新创造,而非替换。
edit:精确修改,失败安全
edit 的设计哲学可以用一句话概括:宁可报错,不可误改。
它要求同时提供旧内容和新内容,在文件中搜索精确匹配,找到才替换,找不到就返回错误。这与 sed 截然不同——sed 会替换所有匹配的字符串,可能误改多处;edit 找不到唯一匹配时直接报错。
报错不是坏事。当 edit 报告"找不到指定内容",这条错误本身传递了两个高价值信息:文件当前内容与 Agent 的记忆不一致,或者提供的旧内容片段不够精确。这两个信息都直接指向下一步——重新 read 文件,再重试。
好的错误信息不是障碍,是导向下一步的线索。
exec:通向外部世界的桥
exec 是四个工具中权力最大的一个。能运行任何 Shell 命令,触达几乎任何外部系统——测试套件、数据库、构建脚本、服务管理。凡是命令行能做的,exec 都能做。
两个值得关注的技术特性:
PTY(伪终端)支持——模拟真实终端环境,处理颜色代码、实时输出、交互式提示。没有 PTY,许多程序会检测到非终端环境并改变行为,比如缓冲所有输出直到结束,或拒绝启动交互模式。
一个具体场景:Agent 运行 npm init 创建新项目。这个命令会弹出交互式问答——"Package name: (my-project)"、"Version: (1.0.0)"……。如果没有 PTY,Agent 的 LLM 看不到这些提示,程序永远卡住。PTY 让 Agent 能看到终端的实时输出,读取提示内容,然后输入正确的回答继续流程。
后台进程模式——像开发服务器这样持续运行的命令,可以在后台启动后立即返回,Agent 随时可以查看输出或终止它。这让 Agent 能在服务运行的同时继续处理其他任务。
后台进程的完整生命周期是:启动 → 后台运行 → 随时查看输出 → 随时终止。实际场景:Agent 用 npm run dev 启动开发服务器后立即拿到进程 ID,随即继续处理用户的下一个请求;需要时再通过进程 ID 检查服务器日志或将其终止——服务器一直在跑,Agent 的主线程从未被占用。
| 工具 | 核心能力 | 关键设计 |
|---|---|---|
read | 感知世界,获取信息 | 三层防御,保护上下文 |
write | 创造新文件 | 原子写入,语义清晰 |
edit | 精确修改已有文件 | 失败安全,错误有导向 |
exec | 执行命令,触达外部 | PTY 支持,后台进程 |
三、组合的力量:四个工具如何完成复杂任务
四个工具各自能做的事情,看起来都很简单。它们真正的价值在于组合。
这就像编程语言里的基本类型——整数、字符串、布尔值本身都极其简单,但由它们组合出的数据结构和算法可以表达任意复杂的计算。工具的原子性不是局限,而是组合的前提。
来看一个具体例子:「找出所有 TODO 并生成可提交的清单」。
步骤 1 exec(grep -r "TODO" . --include="*.ts")
→ 搜索所有 TypeScript 文件中的 TODO
步骤 2 read(src/complex-module.ts)
→ 读取包含 TODO 的文件,确认上下文
步骤 3 write(TODO-report.md)
→ 生成按模块分类的 TODO 清单更复杂的任务同样只用这四个工具。重构一个函数:
exec → 搜索函数的所有引用位置
read → 读取函数定义和调用上下文
edit → 修改函数签名和实现
edit → 逐处更新调用方
exec → 运行测试套件
read → 确认测试结果这个工具链不是预先编程的,而是由 LLM 根据任务目标在运行时动态生成。每一步完成后,返回值进入对话历史,成为下一步推理的依据——这正是 ReAct 循环在工具层面的直接体现。
组合能力的层次是递进的:
原子操作 → 单个工具调用(read / write / edit / exec)
↓
工具链 → 多工具顺序调用,完成有内在逻辑的任务片段
↓
单 Agent → 工具链动态编排,完成完整任务
↓
多 Agent → 多个 Agent 分工协作,各自调用工具集四个积木,叠出无限高度。
四、exec 的安全护栏
能力越大,风险越高。exec 能运行任何命令,这意味着它也能运行 rm -rf /。
OpenClaw 的设计哲学是:不限制工具的能力,而是在外层定义边界。工具专注功能,安全专注边界,两者解耦、各司其职。这样在开发阶段可以放宽限制快速迭代,生产部署时再收紧到最小权限,工具本身的代码不需要变动。
三层安全架构,从粗到细:
| 层级 | 配置项 | 可选值 | 作用 |
|---|---|---|---|
| Security 模式 | tools.exec.security | deny / allowlist / full | 定义基本权限边界 |
| Ask 模式 | tools.exec.ask | off / on-miss / always | 何时需要人工确认 |
| 安全命令白名单 | tools.exec.safeBins | jq, head, tail… | 只读安全命令,无需额外审批 |
Security 模式决定"能做什么"——deny 全部拒绝,allowlist 只允许白名单命令,full 不限制。
Ask 模式决定"做之前问不问"——off 直接执行,on-miss 命令不在白名单时询问,always 每次都问。
safeBins 是一个细粒度的补充——对于 jq、head、tail 这类纯读取的工具,可以单独放行,不需要为每次查看文件内容都触发一次确认提示。
建议从保守配置起步,随着对 Agent 行为建立信任,再逐步开放——这与 chapter2 中讲的"渐进式建立信任"是同一个原则。
五、Skills:按需加载的上层扩展
四个工具解决了"能做什么",但还有另一个维度:知道怎么做好。
一个懂代码审查的 Agent 和不懂的 Agent,使用的工具集完全相同——区别在于它是否知道:应该优先检查安全漏洞,其次是性能问题;某种写法是反模式;某个框架有特定最佳实践。这不是工具问题,是知识问题。
这就是 Skills(技能)机制的用途。
技能是一个 Markdown 文件,包含某领域的专业知识、检查清单、决策指引。它不添加新工具,而是告诉 Agent 如何用已有工具把特定领域的任务做得更专业。
技能不预先加载进上下文,而是 Agent 在任务执行中判断需要时,通过 read 主动读取——上下文里只有当前用得到的知识:
工具扩展 技能扩展
──────────────────── ────────────────────
增加 Agent 能做的事 增加 Agent 知道的事
通用性高 专域性强
内置于运行时 按需从文件动态加载
改变执行能力 改变决策质量
更新需要修改代码 更新只需编辑 Markdown这种设计有一个直接好处:技能更新不需要重启系统。编辑 Markdown,保存,Agent 下次读取时自然获得最新版本。知识与运行时解耦,维护成本极低。
小结
四个原语工具,通过 LLM 的动态编排,能够完成任意复杂的本地计算任务。秘密不在工具本身,而在于每次调用的返回值都会流回 ReAct 循环,成为下一步推理的依据——工具既是 Agent 的双手,也是它的感知系统。
| 工具/机制 | 解决的问题 | 关键设计 |
|---|---|---|
read | 感知世界,保护上下文 | 三层防御(预算/截断/分页) |
write | 创造新内容 | 原子写入,语义清晰 |
edit | 精确修改已有内容 | 宁可报错,不可误改 |
exec | 触达外部世界 | 三层安全护栏,PTY 支持 |
| Skills | 扩展领域知识 | 按需加载,维护零成本 |
少即是多——简单性保证正交性,正交性保证组合性,组合性才是真正的超能力。
四个积木之所以够,不是因为它们功能强大,而是因为它们足够正交——每一块只做自己的事,不和别人重叠,所以可以自由组合,组合出的能力边界没有天花板。
→ 第五章 消息循环