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 中的 ToolRegistryRwLock<HashMap<String, Arc<dyn Tool>>> 持有工具。

  • register_sync:启动阶段同步注册;若工具名在 PROTECTED_TOOL_NAMES 中,会记入 builtin_names,后续动态 register 不能顶替同名内置工具(避免恶意 WASM/扩展冒充 shellmemory_write 等)。
  • register(async):用于运行时安装 WASM、MCP 工具等;若名称已被标记为内置,会打日志并拒绝注册。

tool_definitions() / tool_definitions_for() 等从当前 map 生成发给 LLM 的 ToolDefinition(name、description、parameters)。

2.3 内置工具的分层(按能力域拆分)

同一文件里通过不同方法分批注册,便于编排器进程只暴露安全子集、容器/本地再挂上文件与 shell:

  • register_builtin_toolsechotimejsonhttp(可选接入 SharedCredentialRegistry + SecretsStore 做凭据注入)。
  • register_tool_infotool_info 通过 Weak<ToolRegistry> 在运行时查询其他工具的 schema(须在 registry 的 Arc 上调用)。
  • register_dev_toolsshellread_filewrite_filelist_dirapply_patch
  • register_memory_tools:依赖 Workspacememory_* 四件套。
  • register_job_tools:创建/列出/状态/取消 job,可选 DB 事件、PromptQueue、沙箱与注入通道等(由调用方传入依赖)。
  • register_secrets_tools:密钥列表/删除(不向 LLM 回显密钥值)。
  • register_extension_toolstool_search / tool_install / tool_auth / tool_activate / tool_list / tool_remove / tool_upgrade / extension_info,对接 ExtensionManager
  • register_skill_toolsskill_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_softwareBuildSoftwareTool)。

PROTECTED_TOOL_NAMES 中还列出了 web_fetchrestart 等名称;其中 web_fetch 在当前树中未见对应 Rust 内置注册调用,可理解为与扩展/未来路径相关的预留保护名,避免被动态工具顶替;RestartTool 存在于 src/tools/builtin/restart.rs,实际暴露路径需结合配置与命令(例如 Docker 场景)。

Orchestrator 与 Container 的注释约定register_orchestrator_toolsregister_container_tools 将「仅 HTTP 类」与「开发/文件类」分开,供不同进程组合使用。

3. 启动时装配:app 与 main 的分工

3.1 AppBuilder::init_toolssrc/app.rs

大致顺序:

  1. 构造 ToolRegistry,若配置了 secrets_store,则 with_credentials(供 HttpTool 等与凭据注入协作)。
  2. register_builtin_toolsregister_tool_info
  3. 有密钥存储则 register_secrets_tools
  4. 有 DB 则建 Workspaceregister_memory_tools
  5. 有 workspace 且能解析出 API key 时,register_image_tools / register_vision_tools
  6. 若启用 builder 且允许本地工具(或非沙箱),register_builder_tool

此时尚未加载 WASM 目录、MCP、扩展管理工具、技能工具、routine 工具、job 工具、message 工具——这些在后续阶段或 main 中补齐。

3.2 init_extensions

  • WASMWasmToolLoader 扫描 wasm.tools_dirregister_wasm 校验、编译、包一层 WasmToolWrapperregister;可选把 HTTP 凭据映射合并进共享 credential_registry
  • MCP:按配置创建 client,list_toolscreate_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 放入 AppComponentsAgent 使用。

注意:SkillRegistry API 支持 with_workspace_dir(工作区 skills/),但 build_all 当前未链式设置 workspace 技能目录;工作区技能若需加载,要依赖将来接线或自行把 SKILLS_DIR 指到期望目录(见 src/config/skills.rs)。

3.4 main.rs 中的后注册

  • register_job_tools:传入 ContextManagerSchedulerSlot、可选 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.rsrun_agentic_loop

  1. 首轮从 self.tools().tool_definitions().await全量定义。
  2. 若本回合有激活技能,先 attenuate_tools,再 reasoning.build_system_prompt_with_tools(&defs) 缓存带工具/不带工具两种系统提示变体。
  3. 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 共用的路径:

getprepare_tool_paramsSafetyLayer 校验参数 → 按工具 execution_timeouttokio::time::timeouttool.execute → 结果再经安全与序列化(与 agent 文档中的 sanitizer / policy / leak detector 等配合)。

工具找不到时映射为 ToolError::NotFound

6. Skills:格式、发现、gating、选择、注入

6.1 模块职责(src/skills/mod.rs

  • Trusted:用户放在 ~/.ironclaw/skills/ 或(API 支持的)workspace skills/
  • Installed:ClawHub 安装目录(默认 ~/.ironclaw/installed_skills/),工具权限受限

规范摘要见 .claude/rules/skills.md

6.2 发现顺序(src/skills/registry.rs

discover_all工作区(若配置)→ 用户目录installed 目录;同名后者被跳过。加载时解析 frontmatter,做 gatingsrc/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.rsselect_active_skills 在持有 skill_registry 读锁时调用上述逻辑,参数来自 AgentDeps.skills_configSKILLS_MAX_ACTIVESKILLS_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.rsattenuate_tools

  • 无激活技能:原样返回。
  • 所有激活技能均为 Trusted:不过滤。
  • 若最低信任为 Installed:仅保留硬编码 READ_ONLY_TOOLS 白名单(当前含 memory_searchmemory_readmemory_treetimeechojsonskill_listskill_search 等)。其余工具从发给 LLM 的 definitions 中移除,模型无法调用「不存在的」工具名,从机制上限制 Installed 技能诱导高危操作。

该过滤在 dispatcher 首轮与每轮 before_llm_call 都会应用。

8. 配置与 CLI

  • 环境变量SKILLS_ENABLEDSKILLS_DIRSKILLS_INSTALLED_DIRSKILLS_MAX_ACTIVESKILLS_MAX_CONTEXT_TOKENSsrc/config/skills.rs)。
  • ironclaw skillssrc/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.rsinit_toolsinit_extensionsbuild_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.rssrc/skills/gating.rssrc/skills/catalog.rs
规范摘要 .claude/rules/skills.md