Ironclaw 04 - IronClaw 中的 Tools 与 Skills:登记、注入与信任模型
IronClaw 中的 Tools 与 Skills:登记、注入与信任模型
本文基于 IronClaw 源码阅读,梳理内置工具的分层注册、WASM/MCP 等动态扩展、向 LLM 暴露的 function-calling schema,以及 SKILL.md 的发现、选择、上下文注入与基于信任的工具有效上限(attenuation)。文中路径均相对于仓库根目录。
1. 两种扩展机制:Tools 与 Skills 各管什么
在 IronClaw 里可以粗略分成两层:
| 维度 | Tools | Skills |
|---|---|---|
| 本质 | 宿主可调用的可执行单元(Rust 内置、WASM、MCP 等),带 JSON Schema | YAML frontmatter + Markdown 正文的提示词扩展(SKILL.md) |
| 对模型的可见性 | 通过 ToolDefinition 列表参与 function calling |
选中后作为额外文本块注入推理上下文(XML 包裹) |
| 安全抓手 | 参数校验、超时、审批、SafetyLayer 等 |
确定性预选(不用 LLM 选技能)、信任等级决定工具列表是否收缩 |
二者可同时生效:模型既看到一组 tool definitions,也可能看到若干 <skill>...</skill> 片段;若存在「已安装(Installed)」信任等级的技能,工具列表会被硬过滤,这是防提示词劫持的关键设计。
2. Tool 抽象与 ToolRegistry
2.1 Tool trait 与审批
src/tools/tool.rs 定义了工具的统一接口(名称、schema、执行、requires_approval、超时、ToolDomain 等),以及 ApprovalRequirement / ApprovalContext。交互式会话里,需要审批的工具会走网关与 session 状态机;自主任务(job/routine)则用 ApprovalContext::Autonomous 等规则约束。
2.2 注册表:同步 vs 异步、防顶替
src/tools/registry.rs 中的 ToolRegistry 用 RwLock<HashMap<String, Arc<dyn Tool>>> 持有工具。
register_sync:启动阶段同步注册;若工具名在PROTECTED_TOOL_NAMES中,会记入builtin_names,后续动态register不能顶替同名内置工具(避免恶意 WASM/扩展冒充shell、memory_write等)。register(async):用于运行时安装 WASM、MCP 工具等;若名称已被标记为内置,会打日志并拒绝注册。
tool_definitions() / tool_definitions_for() 等从当前 map 生成发给 LLM 的 ToolDefinition(name、description、parameters)。
2.3 内置工具的分层(按能力域拆分)
同一文件里通过不同方法分批注册,便于编排器进程只暴露安全子集、容器/本地再挂上文件与 shell:
register_builtin_tools:echo、time、json、http(可选接入SharedCredentialRegistry+SecretsStore做凭据注入)。register_tool_info:tool_info通过Weak<ToolRegistry>在运行时查询其他工具的 schema(须在 registry 的Arc上调用)。register_dev_tools:shell、read_file、write_file、list_dir、apply_patch。register_memory_tools:依赖Workspace的memory_*四件套。register_job_tools:创建/列出/状态/取消 job,可选 DB 事件、PromptQueue、沙箱与注入通道等(由调用方传入依赖)。register_secrets_tools:密钥列表/删除(不向 LLM 回显密钥值)。register_extension_tools:tool_search/tool_install/tool_auth/tool_activate/tool_list/tool_remove/tool_upgrade/extension_info,对接ExtensionManager。register_skill_tools:skill_list/skill_search/skill_install/skill_remove。register_routine_tools:routine 与event_emit等(由 agent 在启用 routine 引擎时调用)。register_message_tools(async):message,并维护message_tool句柄以便每轮设置 channel/target。register_image_tools/register_vision_tools:图像生成、编辑、分析(需 API 配置)。register_builder_tool:内部会先register_dev_tools,再异步注册build_software(BuildSoftwareTool)。
PROTECTED_TOOL_NAMES 中还列出了 web_fetch、restart 等名称;其中 web_fetch 在当前树中未见对应 Rust 内置注册调用,可理解为与扩展/未来路径相关的预留保护名,避免被动态工具顶替;RestartTool 存在于 src/tools/builtin/restart.rs,实际暴露路径需结合配置与命令(例如 Docker 场景)。
Orchestrator 与 Container 的注释约定:register_orchestrator_tools 与 register_container_tools 将「仅 HTTP 类」与「开发/文件类」分开,供不同进程组合使用。
3. 启动时装配:app 与 main 的分工
3.1 AppBuilder::init_tools(src/app.rs)
大致顺序:
- 构造
ToolRegistry,若配置了secrets_store,则with_credentials(供HttpTool等与凭据注入协作)。 register_builtin_tools、register_tool_info。- 有密钥存储则
register_secrets_tools。 - 有 DB 则建
Workspace,register_memory_tools。 - 有 workspace 且能解析出 API key 时,
register_image_tools/register_vision_tools。 - 若启用 builder 且允许本地工具(或非沙箱),
register_builder_tool。
此时尚未加载 WASM 目录、MCP、扩展管理工具、技能工具、routine 工具、job 工具、message 工具——这些在后续阶段或 main 中补齐。
3.2 init_extensions
- WASM:
WasmToolLoader扫描wasm.tools_dir,register_wasm校验、编译、包一层WasmToolWrapper再register;可选把 HTTP 凭据映射合并进共享credential_registry。 - MCP:按配置创建 client,
list_tools→create_tools,每个实现Tool的实例tools.register().await。 - ExtensionManager:创建后
register_extension_tools;若 builder 未注册 dev 工具且allow_local_tools,则在此补register_dev_tools。
3.3 build_all 中的 Skills
若 config.skills.enabled:
SkillRegistry::new(local_dir).with_installed_dir(...),discover_all().await。tools.register_skill_tools(registry, catalog)。skill_registry/skill_catalog放入AppComponents供Agent使用。
注意:SkillRegistry API 支持 with_workspace_dir(工作区 skills/),但 build_all 当前未链式设置 workspace 技能目录;工作区技能若需加载,要依赖将来接线或自行把 SKILLS_DIR 指到期望目录(见 src/config/skills.rs)。
3.4 main.rs 中的后注册
register_job_tools:传入ContextManager、SchedulerSlot、可选ContainerJobManager、DB、SSE、inject、PromptQueue、secrets 等。register_message_tools:在ChannelManager就绪后异步注册message。
Routine 工具在 agent 启动且启用 routine 时由 src/agent/agent_loop.rs 调用 register_routine_tools。
4. 向 LLM「注入」Tools:Schema 从哪来、为何每轮刷新
对话路径在 src/agent/dispatcher.rs 的 run_agentic_loop:
- 首轮从
self.tools().tool_definitions().await取全量定义。 - 若本回合有激活技能,先
attenuate_tools,再reasoning.build_system_prompt_with_tools(&defs)缓存带工具/不带工具两种系统提示变体。 - 在
ChatDelegate::before_llm_call中每一轮重新tool_definitions().await并再次attenuate_tools,写回reason_ctx.available_tools。
这样,用户在会话中通过 tool_install 等新装的工具,下一轮即可出现在模型可见列表中,无需重启进程。
5. 执行管线:execute_tool_with_safety
src/tools/execute.rs 提供 chat、job、container 共用的路径:
get → prepare_tool_params → SafetyLayer 校验参数 → 按工具 execution_timeout 做 tokio::time::timeout → tool.execute → 结果再经安全与序列化(与 agent 文档中的 sanitizer / policy / leak detector 等配合)。
工具找不到时映射为 ToolError::NotFound。
6. Skills:格式、发现、gating、选择、注入
6.1 模块职责(src/skills/mod.rs)
- Trusted:用户放在
~/.ironclaw/skills/或(API 支持的)workspaceskills/。 - Installed:ClawHub 安装目录(默认
~/.ironclaw/installed_skills/),工具权限受限。
规范摘要见 .claude/rules/skills.md。
6.2 发现顺序(src/skills/registry.rs)
discover_all:工作区(若配置)→ 用户目录 → installed 目录;同名后者被跳过。加载时解析 frontmatter,做 gating(src/skills/gating.rs:二进制在 PATH、环境变量、配置文件存在性),失败则跳过并记录。
6.3 确定性预选(src/skills/selector.rs)
prefilter_skills(message, available, max_candidates, max_context_tokens):
- 无 LLM:关键词精确/子串、标签、正则模式打分,exclude_keywords 一票否决。
- 预算:按声明或估算的 token 成本在
SKILLS_MAX_TOKENS(配置项max_context_tokens)内贪心选取。
src/agent/agent_loop.rs 的 select_active_skills 在持有 skill_registry 读锁时调用上述逻辑,参数来自 AgentDeps.skills_config(SKILLS_MAX_ACTIVE、SKILLS_MAX_CONTEXT_TOKENS 等,见 src/config/skills.rs)。
6.4 注入到上下文(src/agent/dispatcher.rs)
对每个激活技能:
- 转义后包成 XML:
<skill name="..." version="..." trust="TRUSTED|INSTALLED">...</skill>。 - Installed 技能在正文后追加固定免责声明,要求模型把内容视为建议且不得与核心指令冲突。
- 通过
reasoning.with_skill_context(...)并入推理层(与 workspace 系统提示等并列)。
设计意图:预选阶段不把技能全文喂给「选技能」的逻辑,避免循环操纵。
7. 信任衰减(Tool Attenuation)
src/skills/attenuation.rs 的 attenuate_tools:
- 无激活技能:原样返回。
- 所有激活技能均为 Trusted:不过滤。
- 若最低信任为 Installed:仅保留硬编码
READ_ONLY_TOOLS白名单(当前含memory_search、memory_read、memory_tree、time、echo、json、skill_list、skill_search等)。其余工具从发给 LLM 的 definitions 中移除,模型无法调用「不存在的」工具名,从机制上限制 Installed 技能诱导高危操作。
该过滤在 dispatcher 首轮与每轮 before_llm_call 都会应用。
8. 配置与 CLI
- 环境变量:
SKILLS_ENABLED、SKILLS_DIR、SKILLS_INSTALLED_DIR、SKILLS_MAX_ACTIVE、SKILLS_MAX_CONTEXT_TOKENS(src/config/skills.rs)。 ironclaw skills(src/cli/skills.rs):list/search/info;本地发现使用SkillRegistry::new(local_dir).with_installed_dir(...),同样未默认附带 workspace 目录,与app行为一致,便于 CLI 与主进程对齐预期。
9. 小结(一张心智图)
flowchart LR
subgraph reg [ToolRegistry]
builtin[Builtins分批注册]
wasm[WASM动态register]
mcp[MCP动态register]
end
subgraph llm [LLM可见性]
defs[tool_definitions]
att[attenuate_tools]
sys[system_prompt加skill_XML]
end
subgraph skills [Skills]
disc[discover_all gating]
pre[prefilter_skills]
end
reg --> defs
skills --> pre
pre --> sys
defs --> att
att --> llm
skills --> att
一句话:Tools 负责「能做什么、怎么调用」;Skills 负责「在什么条件下多给模型读一段说明」;Installed 技能通过衰减工具列表把「能做什么」收紧到只读白名单,与 XML 里的免责声明形成纵深。
参考源码索引
| 主题 | 路径 |
|---|---|
| Tool trait、审批 | src/tools/tool.rs |
登记、内置分批、PROTECTED_TOOL_NAMES |
src/tools/registry.rs |
| 内置实现 | src/tools/builtin/ |
| 启动装配 | src/app.rs(init_tools、init_extensions、build_all) |
| job/message 后注册 | src/main.rs |
| 执行管线 | src/tools/execute.rs |
| 对话注入与衰减 | src/agent/dispatcher.rs |
| 技能选择入口 | src/agent/agent_loop.rs |
| 预选打分 | src/skills/selector.rs |
| 信任衰减 | src/skills/attenuation.rs |
| 技能发现/安装 | src/skills/registry.rs、src/skills/gating.rs、src/skills/catalog.rs |
| 规范摘要 | .claude/rules/skills.md |