Appearance
第10章 工具系统
"工具安全的最难问题不是'如何阻止恶意调用',而是'如何区分一次危险但正确的操作和一次安全但错误的操作'——前者应该放行,后者应该拦截,而 LLM 给你的信号往往不足以可靠区分两者。"
本章要点
- 理解 Agent 工具的核心悖论:能力越强,风险越大
- 掌握三层工具抽象与七层策略防御管线
- 深入浏览器自动化、进程执行、Web 搜索等核心工具实现
- 理解工具系统与上下文引擎的协同关系
前九章,我们构建了 OpenClaw 的完整骨架:Gateway 调度全局、Provider 连接模型、Session 管理记忆、Agent 编排决策、通道对接用户、插件开放生态。骨架已成,但 Agent 仍然只是一个"能说会道的嘴"——它能生成文字,却无法触碰真实世界。
工具系统,就是给 Agent 装上的双手。
10.1 Agent 工具的核心悖论
什么区分了 AI Agent 和聊天机器人?答案看似简单:行动的能力。聊天机器人生成"我可以帮你搜索"的文本;Agent 真正打开浏览器、输入查询、返回结果。一个是纸上谈兵,一个是真刀真枪。
但这种简单性掩盖了一个困扰所有 Agent 框架的设计难题:如何让 AI 安全地、可扩展地、智能地调用系统能力?
考虑最朴素的方案:将每个操作系统能力暴露为 LLM 可调用的函数。这在演示中行得通,但在生产环境中必然崩溃。没有权限控制,Agent 可能因误解指令而执行 rm -rf /;没有路由智能,用户要搜索它却去生成图片;没有并发管理,并行工具调用耗尽系统资源。能力越大,责任越大——也越需要精密的管控。
好的工具系统不是给 Agent 更多的刀——而是一套完整的刀具管理体系:哪些刀放在台面上,哪些锁在柜子里,谁有钥匙,什么情况下可以用。
🔥 深度洞察:工具是 Agent 的"宪法修正案"
从政治学的视角看工具系统,你会获得一个全新的理解。LLM 的基础能力(文本生成)就像宪法原文——庄严但抽象。每一个工具的添加,就像一条宪法修正案——它赋予 Agent 新的权力(执行命令、浏览网页、操作设备),但同时必须有对应的约束条款。美国宪法第二修正案赋予公民持枪权,但配套了无数的联邦和州法律来规范这一权力的行使。OpenClaw 的工具系统遵循完全相同的逻辑:
exec工具赋予 Agent 执行 Shell 命令的能力(强大的"武器"),但七层策略管线确保这一能力在严格约束下行使。没有约束的能力不是能力,是隐患。
10.1.1 如果没有工具系统会怎样?
让我们做一个思想实验。假设 OpenClaw 没有专门的工具系统,而是像早期的 LangChain 那样,让开发者自己将函数注册到 LLM 的工具列表中。会发生什么?
第一个问题是安全真空。 没有统一的策略层,每个工具自行决定"谁能调用我"。浏览器工具可能有 URL 白名单,但 exec 工具完全不设防。安全变成了一个个孤岛——有的工具很安全,有的工具裸奔,整体安全性取决于最弱的那一环。
第二个问题是配置爆炸。 运营者需要为每个工具单独配置权限、超时、资源限制。10 个工具需要 10 套配置。50 个工具需要 50 套。配置的复杂度与工具数量线性增长,维护成本很快超过工具本身的价值。
第三个问题是上下文浪费。 没有统一的输出格式控制,每个工具返回自己认为合理的格式——浏览器返回完整 HTML(50K token),搜索返回原始 JSON(30K token),一次工具调用就可能耗尽整个上下文窗口。Agent 变成了只能使用一次工具的"一次性工人"。
OpenClaw 的工具系统正是对这三个问题的系统性回应。它不是简单的函数注册表,而是一个完整的工具治理框架——在安全、配置和效率三个维度上提供统一的解决方案。
10.1.2 工具系统的设计目标
在深入实现之前,让我们明确工具系统要解决的核心需求:
- 安全隔离:不同利益相关者(平台运营者、Agent 开发者、模型提供商)能独立控制工具访问,且安全策略可组合。
- 上下文效率:工具的输出必须经过压缩和格式化,使之适合有限的上下文窗口。
- 配置简洁:运营者能用一行配置(如
tools.profile: coding)获得一组合理的工具默认值。 - 扩展透明:添加新工具不需要修改安全系统;修改安全策略不需要理解工具实现。
- 优雅降级:工具不可用时(如浏览器未安装),系统平滑降级而非崩溃。
这五个目标之间存在张力。例如,安全隔离要求细粒度控制,但配置简洁要求粗粒度抽象。工具系统的架构设计本质上是在这些张力之间寻找平衡。
关键概念:工具策略管线(Tool Policy Pipeline) 工具策略管线是 OpenClaw 工具安全的核心机制——在任何工具进入 LLM 的可用工具列表之前,它必须通过多达七个独立的过滤阶段(Agent 级、Owner-Only、通道级、Provider 级、沙箱级等)。每个阶段可以独立允许或拒绝工具,且修改安全策略不需要修改工具实现,反之亦然。
10.2 工具架构:三层抽象
工具系统不是一个扁平的函数列表。它在三个层次上运作,每层解决不同类别的问题。
第一层:工具目录(src/agents/tool-catalog.ts)。所有工具的主注册表——文件操作(read、write、edit)、运行时操作(exec、process)、Web 操作(browser、web_search、web_fetch)、媒体操作(image、tts、pdf)和会话操作(sessions_spawn、sessions_yield)。每个工具属于一个分区(section),并声明参与哪些配置文件(profile)。
第二层:工具策略管线(src/agents/tool-policy-pipeline.ts)。安全性的核心。在任何工具进入 LLM 的意识之前,它通过多达七个独立过滤阶段——配置文件策略、提供商策略、全局策略、Agent 级策略和群组策略(策略管线的详细架构如图 6-3 所示,安全模型详见第13章)。每个阶段可以允许、拒绝或对任何工具保持沉默。管线遵循"最后一个明确意见胜出"的语义。
第三层:工具执行(各 src/ 模块)。每个工具的实际实现——用于浏览器自动化的 Playwright、用于命令执行的 Node.js child_process、用于网页搜索的 HTTP 客户端等。

这种分离至关重要。目录是静态且全面的。管线是动态且安全感知的。执行层是模块化且可独立测试的。改变安全策略永远不需要修改工具实现,添加新工具永远不需要理解策略系统。
⚠️ 注意:
exec工具的默认安全模式为"allowlist"——只有在白名单中的命令才会被自动执行。如果你需要 Agent 执行任意命令(如开发环境),可以将安全模式设置为"full",但务必确保 Agent 仅面向受信任的用户。在面向公众的 Agent 上使用"full"模式是严重的安全风险。
10.2.1 为什么是三层而不是两层?
一个自然的疑问:为什么不把目录和策略合并为一层?答案在于关注点的独立演化速度。
工具目录的变更频率较低——当 OpenClaw 添加新工具(如 canvas、feishu_doc)时才变化,这可能是几周一次。策略管线的变更频率中等——运营者调整安全配置时变化,可能是每天。工具执行的变更频率最高——Bug 修复、性能优化、API 适配,几乎每次提交都涉及。
如果目录和策略耦合,每次添加新工具都要重新审视安全策略的组合逻辑。如果策略和执行耦合,每次修改浏览器自动化的细节都可能意外改变安全行为。三层分离让每一层按自己的节奏演化,互不干扰。
10.2.2 工具目录的数据模型
让我们看看工具目录的核心数据结构(src/agents/tool-catalog.ts):
typescript
// 工具的核心定义——每个工具是一个静态声明
type CoreToolDefinition = {
id: string; // 工具唯一标识
label: string; // 显示名称
description: string; // 功能描述
sectionId: string; // 所属分区(fs/runtime/web/media/...)
profiles: ToolProfileId[]; // 属于哪些配置文件
includeInOpenClawGroup?: boolean; // 是否归入 OpenClaw 工具组
};这个定义有几个微妙的设计决策:
sectionId 的分区设计。11 个分区(fs、runtime、web、memory、sessions、ui、messaging、automation、nodes、agents、media)不是按技术实现分的,而是按用户心理模型分的。运营者不会想"我要允许使用 Playwright 的工具",而是"我要允许 Web 相关的工具"。分区对齐了运营者的思维方式,让配置更直觉。
profiles 的多重归属。一个工具可以属于多个配置文件。例如 web_search 同时属于 coding 和 full 配置文件。这意味着切换配置文件不需要手动调整个别工具——配置文件是工具子集的有名字的快捷方式。
includeInOpenClawGroup 的插件边界标识。这个布尔值标记哪些工具在策略过滤中应该被视为"核心 OpenClaw 工具"而非"插件提供的工具"。这影响当 allowlist 中出现未知条目时的降级行为——如果 allowlist 仅包含插件工具名,系统会剥离该 allowlist(让所有核心工具保持可用),而非错误地禁用所有核心工具。
10.3 策略管线:七层防御
如果你只能理解整个工具系统的一个机制,那应该是策略管线。这是 OpenClaw 安全态势的执行点——它的设计揭示了关于 Agent 安全的深刻洞察。
10.3.1 问题的本质
不同利益相关者需要不同级别的工具控制。平台运营者想全局禁止 sessions_spawn(防止远程代码执行)。Agent 配置想将某个 Agent 限制为只读工具(数据分析场景)。模型提供商策略想根据模型能力限制工具(某些模型不支持工具调用)。群组管理员想在特定 Discord 群里禁用 exec(安全考虑)。
传统的做法是把所有这些规则塞进一个巨大的 if-else 树。但这种方案有致命缺陷:规则之间会产生意外的交互效应。当运营者的全局禁止和 Agent 的局部允许冲突时,谁赢?当模型提供商策略和群组策略都对同一个工具有意见时,优先级怎么定?
10.3.2 管线方案
OpenClaw 的回答是:独立过滤阶段的有序管线,每个阶段有自己的作用域和真实来源:
typescript
// src/agents/tool-policy-pipeline.ts(简化)
const stages = [
{ policy: profilePolicy, label: "tools.profile" },
{ policy: providerProfilePolicy, label: "tools.byProvider.profile" },
{ policy: globalPolicy, label: "tools.allow" },
{ policy: globalProviderPolicy, label: "tools.byProvider.allow" },
{ policy: agentPolicy, label: "agents.X.tools.allow" },
{ policy: agentProviderPolicy, label: "agents.X.tools.byProvider.allow" },
{ policy: groupPolicy, label: "group tools.allow" },
];管线的执行语义简洁但强大:每个阶段独立决定允许或拒绝某些工具。阶段按顺序执行,后面的阶段可以覆盖前面的决策。通过所有七个阶段的工具对 LLM 可用。在任何阶段被阻止的工具从 LLM 的工具目录中移除——模型甚至不知道它的存在。
这最后一点至关重要。被策略拒绝的工具不是"显示为不可用",而是从 LLM 的世界中彻底消失。LLM 不能请求使用它不知道存在的工具,这从根本上消除了模型试图绕过工具限制的可能性。
10.3.3 为什么是七层而不是三层?
七层看起来过度设计了?让我们通过一个具体场景理解为什么每一层都不可或缺。
想象这个场景:运营者部署了一个 OpenClaw 实例,同时服务于三个 Agent——coder(编码助手)、researcher(研究助手)和 support(客户支持)。
- 配置文件层:
coder使用coding配置文件(包含exec、browser),support使用messaging配置文件(只有message、tts)。这一层决定了每个 Agent 的"能力天花板"。 - 提供商配置文件层:
researcher使用 DeepSeek,它不支持工具调用。这一层确保不向不支持的模型发送工具定义。 - 全局策略层:运营者全局禁止
sessions_spawn,因为这是远程代码执行向量。这一层实施"绝对不可逾越的红线"。 - 全局提供商策略层:对所有使用 OpenAI 的 Agent,禁止
browser(因为 OpenAI 的使用条款限制)。 - Agent 策略层:
coder只允许在~/projects/下操作文件。这一层实施每个 Agent 的最小权限原则。 - Agent 提供商策略层:
coder在使用低端模型时禁止process(防止长时间运行的命令浪费 token)。 - 群组策略层:在 Discord 的
#general频道中禁用exec(公共频道安全考虑),但在#dev频道中允许。
七层,每一层回答一个独立的问题,没有任何两层的关注点完全重叠。这就是为什么不能简化到三层——那样必然会把不同关注点混在一起,导致配置变得混乱。
10.3.4 未知 Allowlist 条目的智能处理
管线中有一个容易被忽视但极其重要的细节:当 allowlist 包含未知的工具名时会怎样?
typescript
// src/agents/tool-policy-pipeline.ts — 未知 allowlist 处理
const resolved = stripPluginOnlyAllowlist(policy, pluginGroups, coreToolNames);
if (resolved.unknownAllowlist.length > 0) {
// 区分两种情况:
// 1. 已知核心工具但当前不可用(受限于运行时/提供商/模型/配置)
// 2. 完全未知的条目(可能是拼写错误或未启用的插件)
const gatedCoreEntries = resolved.unknownAllowlist.filter(isKnownCoreToolId);
const otherEntries = resolved.unknownAllowlist.filter(e => !isKnownCoreToolId(e));
// ...
}