首页 > 基础资料 博客日记
【OpenClaw】通过 Nanobot 源码学习架构---(6)Skills
2026-04-09 21:30:02基础资料围观1次
【OpenClaw】通过 Nanobot 源码学习架构---(6)Skills
0x00 概要
OpenClaw 应该有40万行代码,阅读理解起来难度过大,因此,本系列通过Nanobot来学习 OpenClaw 的特色。
Nanobot是由香港大学数据科学实验室(HKUDS)开源的超轻量级个人 AI 助手框架,定位为"Ultra-Lightweight OpenClaw"。非常适合学习Agent架构。
Skill(技能)是将特定领域的专业知识、工作流程和最佳实践打包成可复用的指令模块,嵌入到 AI 的上下文中,使 AI 在处理相关任务时能够自动激活并运用这些专业能力。
简单来说,Skill 就是一个教 AI 怎么干活的 "技能手册。通过将领域知识、标准流程与执行工具封装为可插拔的模块,Skill让智能体首次实现了专业能力的沉淀、共享与持续优化,从而真正从“自动化执行”迈向“专业化思考与行动”。
Skill 的本质,是把「隐性知识」变成「显性规程」。
注:本系列借鉴的文章过多,可能在参考文献中有遗漏的文章,如果有,还请大家指出。
0x01 原理
本部分很多内容参考于:AI Agent系列|深入解析Function Calling、MCP和Skills的本质差异与最佳实践
1.1 Function Calling
Function Calling的本质:LLM要实现工具调用,实际上最需要的内容只有:函数名、参数要求,以及一个description。然而,在Function Calling的框架下,用户面临几个问题:
- 如何融入到现有的系统中。
- 如何在尽可能的准确的前提下,能让用户能用文字(而非代码)指导模型按照特定的流程和规则执行任务?
- 所有知识都写入prompt,会造成上下文爆炸。
1.2 解决方案
上述问题的解决方案就是MCP和Skills。
MCP和Skills都是基于Function Calling的,它们只是在Function Calling这个基础能力之上的不同应用方式。
-
MCP:让AI“能做到”,执行工具,读写数据。
-
Skill:教AI“怎么做”,是给AI的操作手册(告诉模型一步一步 How to do),包括流程、标准、策略,脚本,代码,Skills 不是 “训练进模型”,而是写进 System Prompt,构建上下文时自动读进去,LLM 靠阅读理解就会用。
1.2.1 如何融入到现有的系统中?
MCP是一种用于规范大模型与外部能力交互方式的协议。如果说 Tools 解决的是“模型如何调用一个函数”,那么 MCP 解决的是“模型如何与一个长期存在、可复用的能力服务交互”。
MCP的核心是解决与既有系统的接驳问题,MCP的价值在于它提供了一套标准化的接驳协议,让不同的工具和数据源能够以统一的方式被LLM使用。本质上,MCP更偏重是一套接驳标准(只是在Function Calling的基础上,增加了一层JSON-RPC协议转换),而不是唯一的接驳方式。
或者说,MCP 更像是 API,Agent 只关心该 API 提交什么「参数」、得到什么「结果」。
1.2.2 如何指导模型按照特定的流程和规则执行任务?
Skills提供一个方式,让用户可以用文字定义指令、脚本和资源,形成可复用的任务流程。
Skills则实际上是一个sub-agent的包装:
- Skills只是在Function Calling之上的一个巧妙应用:把"加载文档"这个操作封装成一个函数,让模型在需要时自动去查找和加载固定的skills文档。
- Skills 让用户可以用文字来写流程,替代了过去在MCP调用的函数里用代码写的一套串接各种API的逻辑流程。这种方式可以增强流程的环境适应性——因为模型可以根据实际情况动态调整策略,处理不确定性,理解自然语言意图。Skills能支持更复杂的流程定义——通过SKILL.md文档告知LLM如何组合多个接口调用,所以长流程任务的成功率会更高。
1.2.3 如何解决上下文爆炸
可以使用双层加载范式。第一层: 系统提示中放技能名称 (低成本)。第二层: tool_result 中按需放完整内容。
System prompt (Layer 1 -- always present):
+--------------------------------------+
| You are a coding agent. |
| Skills available: |
| - git: Git workflow helpers | ~100 tokens/skill
| - test: Testing best practices |
+--------------------------------------+
When model calls load_skill("git"):
+--------------------------------------+
| tool_result (Layer 2 -- on demand): |
| <skill name="git"> |
| Full git workflow instructions... | ~2000 tokens
| Step 1: ... |
| </skill> |
+--------------------------------------+
1.3 Skills:大模型的经验库
Skill = 可复用的工作方法,就是把专家经验固化下来,让每次执行都按最佳实践来,即把人类经验性的任务,变成 AI 可复用的模块。
Skills是项目保持精简的扩展机制。不是把所有可能的功能都塞进核心代码库,而是把新能力定义为 SKILL.md 文件——用 Markdown 写的指令文档,教智能体如何使用特定工具或执行特定工作流。
Skill 的本质是 Prompt + Tools。
- Prompt: 告诉 LLM “我是谁”、“我能做什么”、“什么时候用我”。
- Tools: 具体的函数实现(通常是 CLI 命令或 API 调用)。
tool 是能力,skill 是方法:
- Tools 是能力的低层接口(API),tool 更像“手和脚”,是 agent 真正可以直接调用的操作能力,tool 解决的是“能不能做”。
- Skills 是任务的高级模板(SOP + Tools),skill 更像“作战手册”或者“标准流程”。所以,skill 解决的是:应该怎么做,按什么步骤做,什么时候用这套流程最合适。
| 特性 | Function Calling | Skills |
|---|---|---|
| 定位 | 原子能力 | 任务模块 |
| 构成 | 仅包含函数定义(JSON Schema) | 包含 Prompt + 代码 + 流程定义 |
| 关注点 | 我可以调用这个 API | 我知道如何完成这项工作 |
| 上下文 | 无状态,通常一次性调用 | 有状态,通过 Prompt 引导多步推理 |
| 复用性 | 代码级复用 | 业务逻辑级复用 |
1.3.1 Skill的核心原理
Skill的核心在于“模块化封装”与“渐进式披露”。它的出现本质上是工程化手段的伟大创新,其核心目标是更高效地释放和驾驭大模型已有的潜力。它并非因为大模型突然变得“更聪明”,而是它将完成特定专业任务所需的所有要素(知识、流程、工具)打包成一个独立模块,助力大模型从漫无边际的“流淌”,转向目标明确、步骤清晰的“专业执行”。
Skill的关键创新在于:
- 运行机制:智能体在初始时仅预载轻量的技能索引目录。当识别到具体任务需求后,才动态加载相应技能的详细指令与资源。这种“按需加载”模式,实现了对上下文窗口的极致利用。
- 累积能力:Skill 把复杂任务沉淀成可调用、可复用、可组合的“技能模块,Agent 的落地,正在从单次问答,转向可复用能力的积累。
- 攻克专业化匮乏:它将隐性的领域专家知识(如财务分析SOP、代码审查清单)显性化、结构化,成为模型可理解和执行的具体指令,让通用大模型获得了深度专业能力。它不是在给 AI 增加新的「能力」(AI 本身已经很强),而是在给 AI 增加领域知识和操作规程——告诉它:在这个特定场景下,应该怎么做,用什么工具,遵守什么约束。
- 化解上下文爆炸:相比传统上将全部知识塞进提示词或固化在冗长workflow中的方式,Skill的“按需加载”机制在复杂任务中可降低40%-60%的上下文消耗。
1.3.2 Skills 与架构选型
我们思考下:当 Single Agent 遇到知识瓶颈时,除了拆分成 Multi-Agent,还有没有更轻量的路径?
Skill 机制给出的答案是——回归 Single Agent 架构本体,但赋予其极强的动态扩展能力。这种模式让单个 Agent 具备了"局部专业化"的能力:它在宏观上保持统一的记忆和状态,微观上却能像调用工具一样灵活掌握成千上万种垂直领域的专业知识。
因此,架构选择的原则可以明确为:
能用 Single Agent 解决的,绝不上复杂架构。 遇到知识瓶颈,优先引入 Agent Skills 机制,通过动态渐进式加载 Skills 来扩展能力边界。
为什么 Skills 能保持上下文一致性?
我们从 Prompt 工程的角度拆解其机制:
| 层级 | 特征 | 作用 |
|---|---|---|
| System Prompt | 恒定不变 | 核心系统指令(人设身份、基础要求)保持稳定,确保模型认知的统一性 |
| User Prompt | 动态注入 | Skills 内容以"用户输入"或"工具返回结果"的形式渐进式披露 |
这对模型而言意味着什么?就像是用户在对话过程中不断提供新的参考资料(Reference Material),而不是强行改变它的"人设"。打个比方:Multi-Agent 是让模型"精神分裂"成多个人格轮流登场;而 Skills 是让同一个专家在需要时查阅不同的专业手册——人格不变,知识动态扩展。
Agent Skills 架构的三重收益
1. 低成本的知识注入
真正实现了将海量领域知识"说明书化"——模型按需阅读,无需全量预加载。这比 Multi-Agent 更轻量,也比 RAG 更精准(RAG 是检索后被动接收,Skills 是主动调用并明确用途)。
2. 全局上下文一致性
由于始终由同一个主 Agent 执行(类似 Multi-Agent 里的 Orchestrator,但没有路由层),它完整知晓:
- 已执行的步骤
- 已读取的 Agent Skills
- 当前的任务状态
这彻底消除了 Multi-Agent 中的信息割裂和重复劳动问题——没有路由错误的风险,也没有子 Agent 间的上下文盲区。
3. 规避 Context 爆炸
通过"读一点、做一点、再读一点"的流式处理,有效控制了瞬时上下文长度。相比 Multi-Agent 每个子节点都要维护独立 System Prompt 的冗余,Skills 只在需要时加载,用完即释放。
本质洞察
Skill 用文件系统的结构化能力替代了复杂的网络通信协议,用渐进式的信息披露替代了暴力的全量注入。这不是简单的"功能替代",而是架构哲学的转变:从"用复杂度换能力"走向"用编排精度换效率"。
1.3.3 Skill的结构
Skill 不是“一个统一目录里的插件列表”,而是一套分层发现、按 workspace 和共享范围组织起来的工作流系统。Skill 通常包含三层结构(三级加载机制):
- 元数据(始终加载):
- 技能的描述、功能与接口定义。
- 这些信息永远被加载在上下文,约 100 词。
- AI 用这个判断是否触发 Skill。
- 指令(触发时加载):
- 结构化的专业操作步骤与判断逻辑。
- Skill 被触发后才加载,建议 < 500 行。
- 资源(按需加载):
- 执行所需的数据、模板或工具链接。
- AI 判断需要时才加载,无大小限制,因为脚本可以直接执行。比如,
references/负责放详细说明,scripts/负责放真正复用的脚本。
所以 skill 不是“只有一个 markdown 文件”,而是一组围绕某种任务组织起来的资源,或者说,能力即文件(Capability as Files)。复用 + 资源需求 = Skill。
下面展示了一个典型的Skill的目录结构,通过 元数据 → 指令 → 代码与资源 这三层结构(Skills 本质上只是一种文件结构约定),一个 skill 不仅能被 Claude 正确识别和触发,还能真正完成从“理解需求”到“执行任务”的完整闭环。
some-repository/
├── AGENTS.md # Permanent repository guidelines (recommended)
└── .agents/
└── skills/
├── rot13-encryption/ # AgentSkills standard (progressive disclosure)
│ ├── SKILL.md
│ ├── scripts/
│ │ └── rot13.sh
│ └── references/
│ └── README.md
├── another-agentskill/ # AgentSkills standard (progressive disclosure)
│ ├── SKILL.md
│ └── scripts/
│ └── placeholder.sh
└── legacy_trigger_this.md # Legacy/OpenHands format (keyword-triggered)
1.3.4 Skill vs Workflow vs Prompt
与Workflow相比,Skill代表了一种根本性的能力升级。其核心区别在于:
- Workflow是预先编排好的、线性的、固定路径的执行流程图,确保复杂任务能“稳定地做对”;
- Skill是模块化的、可组合的、按需调用的专业化能力包,旨在让智能体能够“专业地做对”。Skill以前者难以实现的知识封装与灵活调度,开启了智能体专业化与规模化应用的新阶段。Skill所代表的“能力模块化”与“知识工程化”方向,无疑是构建下一代专业化、高可靠智能体的关键路径。
Skill 与 Prompt 对比如下:
| 维度 | Prompt | Skill |
|---|---|---|
| 触发 | 手动粘贴 | 自动匹配 |
| 能力 | 仅文本 | 文档+脚本+模板 |
| 复用 | 低 | 高 |
| 上下文 | 每次完整加载 | 渐进式加载 |
1.3.5 场景
Skill 适合解决的三类问题:
- AI 没有的领域知识
- 重复性工作流
- 需要确定性执行的操作(与直接提示词相比,SKill强在可复用性和一致性)
不适合用 Skill 的情况
- 一次性任务:你只用这个工作流一次,写 Skill 的时间比直接做还长
- 高度个性化:每次需求都完全不同,没有可以固化的模式
- AI 已经很擅长:通用编码、写作、翻译等,AI 本身就有很强的默认行为,加了 Skill 反而可能画蛇添足
未来很多软件的核心资产可能是:一组高质量、可调用、可审计、可复用的 Agent Skills。Agent 的竞争,未来很可能会从“谁的模型分数更高”,逐渐转向:谁更擅长把能力沉淀成技能,谁更擅长把技能组织成工作流,谁更擅长让这些流程在真实业务里稳定运行。
0x02 SkillsLoader
SkillsLoader 是 Nanobot 框架中负责智能体技能(Skills)管理的核心组件,核心职责是加载、管理、校验智能体的技能文件(以 SKILL.md 为载体),为智能体提供可用技能的检索、加载、校验能力,是 Agent 具备工具使用 / 任务执行能力的基础支撑模块。
- SkillsLoader:扫描插件
- ContextBuilder:把插件文档塞进上下文
- LLM:照着插件文档做任务
2.1 业界范式
我们引用 openhands 的文档来看看Skills管理系统的范式。
The Skill system has four primary responsibilities:
- Context Injection - Add specialized prompts to agent context based on triggers
- Trigger Evaluation - Determine when skills should activate (always, keyword, task)
- MCP Integration - Load MCP tools associated with repository skills
- Third-Party Support - Parse
.cursorrules,agents.md, and other skill formats
其架构图如下:

2.2 通用工作流程
我们来看看 Skill 的通用工作流程。
LLM 使用 Skills 的方式 = 阅读文档 + 调用工具。即,人负责说清目标和约束,Skill 封装实现标准(代码/流程),Agent 负责理解意图并调度执行。LLM不需要理解具体有哪些工具以及工具的用法是什么,只需要在需要使用某个工具时查看工具说明书,再把工具拿出来用。
Agent 的行为是Progressive Disclosure,渐进式批露:
- 初始化,这样只看 Skill 的 name 和 description, 扫一眼这个工具能干啥
- Agent 看技能摘要,确定 skill 和任务相匹配
- 发现需要用 → 调用
read_file读SKILL.md - 按文档里的步骤执行
具体工作流程是这样的:
- 初始化阶段:用户用文字定义指令、脚本和资源,打包成Skills(包含SKILL.md和可选的脚本、参考资料等)。LLM 在启动时会读取所有Skills的元数据(名称和描述),这些元数据被加载到模型的上下文中(系统提示词),Agent 在每次对话开始时就"知道"自己拥有哪些 Skill,但不知道具体内容。用于后续的意图匹配和技能触发判断。
- 发现阶段:当用户发起请求时,LLM 会根据请求内容,对比已加载的Skills元数据,判断是否需要使用某个Skill。这个判断过程本质上就是LLM根据上下文做决策,跟Function Calling中判断是否需要调用工具是一样的。需要注意两个要点:
- Agent 只在觉得自己搞不定时才找 Skill。简单的一步操作(比如"读一下这个文件"),即使 Description 完美匹配也可能不触发,因为 Agent 判断自己直接就能完成。
- Agent 天生偏向欠触发(under-triggering)。Description 要写得主动一点,把边界往外推。
- 加载阶段(Function Calling):如果 LLM 判断需要某个Skill(当用户的请求与某个 Skill 的 description 匹配),它会通过Function Calling机制调用一个专门的加载工具(类似
load_skill(skill_name)),将对应的SKILL.md文档内容读取并加载到当前上下文中。这一步完全依赖Function Calling的能力。 - 执行阶段(继续使用Function Calling):SKILL.md的内容(包含指令、流程、示例等)被加入到上下文后,LLM 按照文档中定义的指令执行任务。如果SKILL.md中定义了需要执行脚本(比如
scripts/rotate_pdf.py),LLM 还是会通过Function Calling调用执行脚本的函数。如果需要加载参考资料,同样是通过Function Calling调用读取文件的函数。
可以看到,整个Skills的运行过程,从加载文档、执行脚本到读取资源,每一步都离不开Function Calling。Skills并没有创造新的能力,它只是把Function Calling这个基础能力组织成了一个更易用的形式:让用户可以用文字定义流程,让LLM 自动发现和加载相关知识。 这也意味着 Skill 的激活本身会消耗 1-2 步工具调用。所以 description 写得准不准,直接影响 Token 消耗和响应速度,误触发意味着浪费,漏触发意味着能力缺失。
用户消息
↓
Agent 判断是否需调用某 Skill(基于请求内容匹配已加载的 skill_name 与 description)
↓
若需要,则通过 Function Calling 调用 load_skill(skill_name)
↓
将对应 SKILL.md 的内容注入当前上下文,作为执行指令依据(SOP)
↓
Agent 依照 SKILL.md 中定义的流程执行任务
↓
在执行过程中,按需通过 Function Calling:
• 调用 bash 执行附带脚本
• 调用 read_file 读取所需资源文件
↓
整合执行结果
2.3 Nanobot Skill 管理系统核心特色
Nanobot Skill 管理系统的核心特色如下:
- 双源优先级加载:支持「工作区技能(workspace)」和「内置技能(builtin)」双来源,工作区技能优先级更高(避免内置技能被覆盖);
- 需求校验机制:自动校验技能依赖(CLI 工具、环境变量),可过滤掉依赖不满足的不可用技能;
- 结构化技能管理:提供技能列表查询、单技能加载、技能摘要构建(XML 格式)、常驻技能筛选等能力,适配 Agent 上下文加载、渐进式技能使用的需求;
2.4 总体流程
SkillsLoader 相关的总体流程如下。
用户输入:“每小时检查一次Github星数”
↓
LLM识别上下文
↓
SkillsLoader.get_always_skills() → 获取激活的技能
↓
SkillsLoader.load_skills_for_context() → 加载cron技能上下文
↓
SkillsLoader.bulid_system_prompt() → 构建系统提示
↓
SkillsLoader._run_agent_loop() → 运行代理循环
↓
LLMProvider.chat() → LL处理请求
↓
LLM识别为cron工具调用
↓
ToolRegistry.execute() → 执行工具
↓
CronTool.execute() → Cron工具执行
↓
CronService.add_job() → 添加定时任务
↓
任务存储到cron.json文件
2.5 Skills.md
Skills 本质 = 可插拔的插件文档。
以nanobot\skills\memory\SKILL.md 为例。
上面的三根短横线部分相当于 Skill 的「身份证」:
- name 是它的唯一标识,起个简单好记的英文名字就行
- description 则决定什么时候会触发这个 Skill,描述这个 Skills 是做什么的、遇到什么样的用户请求应该用它、提醒读者:描述越具体,越容易在正确场景被调用。
- description 包含三层信息:这个 Skill 干什么用(这个 Skill 的核心价值是什么)、核心能力是什么(具体包含哪些能力)、什么时候触发(用户说什么话、做什么操作时应该触发)。
- description 始终存在于 Agent 的系统提示词中,作用类似索引,当用户输入到来时,Agent 拿请求和所有 Skill 的 description 做匹配,命中了才加载对应 Skill 的完整内容。这个设计意味着你可以同时挂载几十个 Skill,而激活判断的成本只是几十行短文本的比对,不需要把所有 Skill 的完整指令都塞进上下文。
下面就是 Skill 的正文部分:
---
name: memory
description: Two-layer memory system with grep-based recall.
always: true
---
# Memory
## Structure
- `memory/MEMORY.md` — Long-term facts (preferences, project context, relationships). Always loaded into your context.
- `memory/HISTORY.md` — Append-only event log. NOT loaded into context. Search it with grep.
## Search Past Events
```bash
grep -i "keyword" memory/HISTORY.md
```
Use the `exec` tool to run grep. Combine patterns: `grep -iE "meeting|deadline" memory/HISTORY.md`
## When to Update MEMORY.md
Write important facts immediately using `edit_file` or `write_file`:
- User preferences ("I prefer dark mode")
- Project context ("The API uses OAuth2")
- Relationships ("Alice is the project lead")
## Auto-consolidation
Old conversations are automatically summarized and appended to HISTORY.md when the session grows large. Long-term facts are extracted to MEMORY.md. You don't need to manage this.
2.6 图例
2.6.1 Skills 核心逻辑关系图

2.6.2 Skills 核心流程流程图
核心依赖关系:
- ContextBuilder 依赖 SkillsLoader 提供技能相关内容,是「技能消费方」;
- SkillsLoader 是「技能管理核心」:负责加载、校验、格式化技能,对接 workspace/builtin 两类技能存储;
- Skill 实体由 SKILL.md 文件定义,包含内容 + 元数据(frontmatter),元数据决定技能的可用性 / 常驻属性。
核心流程闭环:
- 初始化:路径配置 → 目录校验;
- 技能加载:清单遍历 → 依赖校验 → 内容加载 → 格式化;
- 上下文构建:Identity → Bootstrap → Memory → 常驻技能 → 技能摘要 → 最终 Prompt。
关键设计:
- 优先级:workspace 技能 > builtin 技能,避免同名冲突;
- 可用性:通过 bins/env 依赖校验过滤不可用技能,摘要中明确标注缺失依赖;
- 轻量化:技能摘要仅返回元数据,完整内容需 agent 调用 read_file 工具加载,符合渐进式设计;
- 常驻技能:标记 always=true 的技能直接嵌入上下文,提升高频技能调用效率。

2.6.3 SkillsLoader 技能元数据解析逻辑

2.7 代码
class SkillsLoader:
"""
Loader for agent skills.
Skills are markdown files (SKILL.md) that teach the agent how to use
specific tools or perform certain tasks.
"""
def __init__(self, workspace: Path, builtin_skills_dir: Path | None = None):
# 初始化工作区路径(用户自定义技能的根目录)
self.workspace = workspace
# 定义工作区技能目录(workspace/skills)
self.workspace_skills = workspace / "skills"
# 定义内置技能目录:优先使用传入值,无则使用默认内置目录
self.builtin_skills = builtin_skills_dir or BUILTIN_SKILLS_DIR
def list_skills(self, filter_unavailable: bool = True) -> list[dict[str, str]]:
"""
List all available skills.
Args:
filter_unavailable: If True, filter out skills with unmet requirements.
Returns:
List of skill info dicts with 'name', 'path', 'source'.
"""
# 初始化技能列表,用于存储所有符合条件的技能信息
skills = []
# 第一步:加载工作区技能(优先级最高)
if self.workspace_skills.exists():
# 遍历工作区技能目录下的所有子目录(每个子目录对应一个技能)
for skill_dir in self.workspace_skills.iterdir():
# 仅处理目录类型(排除文件)
if skill_dir.is_dir():
# 拼接技能文件路径(每个技能目录下必须有SKILL.md)
skill_file = skill_dir / "SKILL.md"
# 仅添加存在SKILL.md文件的技能
if skill_file.exists():
skills.append({
"name": skill_dir.name, # 技能名称(目录名)
"path": str(skill_file), # 技能文件的绝对路径
"source": "workspace" # 技能来源(工作区)
})
# 第二步:加载内置技能(仅添加未被工作区技能覆盖的)
if self.builtin_skills and self.builtin_skills.exists():
# 遍历内置技能目录下的所有子目录
for skill_dir in self.builtin_skills.iterdir():
if skill_dir.is_dir():
skill_file = skill_dir / "SKILL.md"
# 条件:1. SKILL.md存在 2. 技能名称未在工作区技能中出现(避免覆盖)
if skill_file.exists() and not any(s["name"] == skill_dir.name for s in skills):
skills.append({
"name": skill_dir.name,
"path": str(skill_file),
"source": "builtin" # 技能来源(内置)
})
# 第三步:根据需求过滤不可用技能
if filter_unavailable:
# 仅保留满足依赖要求的技能(_check_requirements校验依赖)
return [s for s in skills if self._check_requirements(self._get_skill_meta(s["name"]))]
# 不过滤时返回所有技能(包含依赖不满足的)
return skills
def load_skill(self, name: str) -> str | None:
"""
Load a skill by name.
Args:
name: Skill name (directory name).
Returns:
Skill content or None if not found.
"""
# 第一步:优先从工作区加载技能(优先级更高)
workspace_skill = self.workspace_skills / name / "SKILL.md"
if workspace_skill.exists():
# 读取技能文件内容(UTF-8编码保证中文等字符正常)
return workspace_skill.read_text(encoding="utf-8")
# 第二步:工作区不存在时,从内置目录加载
if self.builtin_skills:
builtin_skill = self.builtin_skills / name / "SKILL.md"
if builtin_skill.exists():
return builtin_skill.read_text(encoding="utf-8")
# 技能不存在时返回None
return None
def load_skills_for_context(self, skill_names: list[str]) -> str:
"""
Load specific skills for inclusion in agent context.
Args:
skill_names: List of skill names to load.
Returns:
Formatted skills content.
"""
# 初始化技能内容片段列表,用于拼接最终上下文
parts = []
# 遍历需要加载的技能名称列表
for name in skill_names:
# 加载单个技能的内容
content = self.load_skill(name)
# 仅处理存在的技能
if content:
# 剥离技能文件的YAML前置元数据(仅保留核心描述)
content = self._strip_frontmatter(content)
# 按固定格式拼接技能内容(便于Agent识别)
parts.append(f"### Skill: {name}\n\n{content}")
# 拼接所有技能内容:用分隔线(---)分隔不同技能,无技能时返回空字符串
return "\n\n---\n\n".join(parts) if parts else ""
def build_skills_summary(self) -> str:
"""
Build a summary of all skills (name, description, path, availability).
This is used for progressive loading - the agent can read the full
skill content using read_file when needed.
Returns:
XML-formatted skills summary.
"""
# 获取所有技能(不过滤不可用的)
all_skills = self.list_skills(filter_unavailable=False)
# 无技能时返回空字符串
if not all_skills:
return ""
# 定义XML转义函数:避免特殊字符(&/</>)破坏XML格式
def escape_xml(s: str) -> str:
return s.replace("&", "&").replace("<", "<").replace(">", ">")
# 初始化XML内容行列表,构建标准XML格式的技能摘要
lines = ["<skills>"]
# 遍历每个技能,生成XML节点
for s in all_skills:
# 转义技能名称(避免特殊字符)
name = escape_xml(s["name"])
# 技能文件路径
path = s["path"]
# 转义技能描述
desc = escape_xml(self._get_skill_description(s["name"]))
# 获取技能元数据(包含依赖要求)
skill_meta = self._get_skill_meta(s["name"])
# 校验技能是否可用(依赖是否满足)
available = self._check_requirements(skill_meta)
# 添加单个技能的XML节点(available属性标记是否可用)
lines.append(f" <skill available=\"{str(available).lower()}\">")
lines.append(f" <name>{name}</name>") # 技能名称
lines.append(f" <description>{desc}</description>") # 技能描述
lines.append(f" <location>{path}</location>") # 技能文件路径
# 对不可用的技能,添加缺失的依赖信息
if not available:
missing = self._get_missing_requirements(skill_meta)
if missing:
lines.append(f" <requires>{escape_xml(missing)}</requires>")
# 闭合单个技能节点
lines.append(f" </skill>")
# 闭合根节点
lines.append("</skills>")
# 拼接所有行,生成完整的XML字符串
return "\n".join(lines)
def _get_missing_requirements(self, skill_meta: dict) -> str:
"""Get a description of missing requirements."""
# 初始化缺失依赖列表
missing = []
# 获取技能元数据中的依赖要求(requires字段)
requires = skill_meta.get("requires", {})
# 检查CLI工具依赖:遍历需要的CLI工具,判断是否存在
for b in requires.get("bins", []):
if not shutil.which(b): # shutil.which检查系统是否安装该CLI工具
missing.append(f"CLI: {b}")
# 检查环境变量依赖:遍历需要的环境变量,判断是否存在
for env in requires.get("env", []):
if not os.environ.get(env): # os.environ.get检查环境变量是否定义
missing.append(f"ENV: {env}")
# 拼接缺失依赖为字符串(逗号分隔)
return ", ".join(missing)
def _get_skill_description(self, name: str) -> str:
"""Get the description of a skill from its frontmatter."""
# 获取技能的元数据(包含description字段)
meta = self.get_skill_metadata(name)
# 有元数据且包含description时返回描述,否则返回技能名称(兜底)
if meta and meta.get("description"):
return meta["description"]
return name # Fallback to skill name
def _strip_frontmatter(self, content: str) -> str:
"""Remove YAML frontmatter from markdown content."""
# 检查内容是否以YAML前置分隔符(---)开头
if content.startswith("---"):
# 正则匹配:从开头的---开始,到下一个---结束(包含换行)
# re.DOTALL使.匹配换行符,确保完整匹配多行YAML
match = re.match(r"^---\n.*?\n---\n", content, re.DOTALL)
if match:
# 截取分隔符之后的内容,并去除首尾空白
return content[match.end():].strip()
# 无YAML前置时直接返回原内容
return content
def _parse_nanobot_metadata(self, raw: str) -> dict:
"""Parse skill metadata JSON from frontmatter (supports nanobot and openclaw keys)."""
try:
# 将原始字符串解析为JSON字典
data = json.loads(raw)
# 优先取nanobot字段,无则取openclaw字段,均无则返回空字典
return data.get("nanobot", data.get("openclaw", {})) if isinstance(data, dict) else {}
except (json.JSONDecodeError, TypeError):
# 解析失败(非JSON/类型错误)时返回空字典
return {}
def _check_requirements(self, skill_meta: dict) -> bool:
"""Check if skill requirements are met (bins, env vars)."""
# 获取技能的依赖要求
requires = skill_meta.get("requires", {})
# 检查所有CLI工具依赖:只要有一个不存在,返回False
for b in requires.get("bins", []):
if not shutil.which(b):
return False
# 检查所有环境变量依赖:只要有一个未定义,返回False
for env in requires.get("env", []):
if not os.environ.get(env):
return False
# 所有依赖都满足时返回True
return True
def _get_skill_meta(self, name: str) -> dict:
"""Get nanobot metadata for a skill (cached in frontmatter)."""
# 获取技能的原始元数据(YAML前置)
meta = self.get_skill_metadata(name) or {}
# 解析并返回nanobot/openclaw格式的元数据
return self._parse_nanobot_metadata(meta.get("metadata", ""))
def get_always_skills(self) -> list[str]:
"""Get skills marked as always=true that meet requirements."""
# 初始化常驻技能列表(always=true的技能会默认加载到Agent上下文)
result = []
# 遍历所有可用技能(已过滤依赖不满足的)
for s in self.list_skills(filter_unavailable=True):
# 获取技能的原始元数据
meta = self.get_skill_metadata(s["name"]) or {}
# 解析nanobot/openclaw格式的元数据
skill_meta = self._parse_nanobot_metadata(meta.get("metadata", ""))
# 判断是否标记为常驻:1. skill_meta中的always 2. 原始meta中的always(兼容两种格式)
if skill_meta.get("always") or meta.get("always"):
result.append(s["name"])
# 返回所有常驻技能名称
return result
def get_skill_metadata(self, name: str) -> dict | None:
"""
Get metadata from a skill's frontmatter.
Args:
name: Skill name.
Returns:
Metadata dict or None.
"""
# 加载技能的完整内容
content = self.load_skill(name)
# 技能不存在时返回None
if not content:
return None
# 检查是否有YAML前置元数据(以---开头)
if content.startswith("---"):
# 正则匹配YAML前置内容(---之间的部分)
match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
if match:
# 初始化元数据字典
metadata = {}
# 按行解析YAML内容(简易解析:仅处理key: value格式)
for line in match.group(1).split("\n"):
if ":" in line:
# 分割键值对(仅分割第一个冒号,避免值中包含冒号)
key, value = line.split(":", 1)
# 清理键和值的空白、引号,存入字典
metadata[key.strip()] = value.strip().strip('"\'')
# 返回解析后的元数据
return metadata
# 无YAML前置时返回None
return None
0xFF 参考
3500 行代码打造轻量级AI Agent:Nanobot 架构深度解析
从被动唤醒到主动守望:基于AI Agent的智能任务架构实践
AI Agent系列|深入解析Function Calling、MCP和Skills的本质差异与最佳实践
OpenClaw 源码漫游指南(七):Skill 架构与插件生态 —— 给 AI 装上“机械臂”
春节加餐:Anthropic首个公开的Skills构建指南来了!
【翻译】Anthropic工程博客:长运行Agent的有效利用框架
OpenClaw 源码漫游指南(九):大脑的解剖学 —— Prompt 动态组装与 Context 压缩算法
https://docs.openhands.dev/overview/skills
Agent/Skills/Teams 架构演进过程及技术选型之道
深度剖析|Claude Agent 是如何一步步加载 Skill 的?
一文讲透:OpenClaw多agent模式下Skills的分层调用机制
Claude Code中的Commands→Skills→Agents是进阶路径?你可能理解错了
如何评测Agent Skills?Anthropic给出了解决方案
写给所有想让 AI 助手「学新技能」的人:智能体技能(Skills)开发完全指南
一文讲透:OpenClaw多agent模式下Skills的分层调用机制
万字】带你实现一个Agent(上),从Tools、MCP到Skills
3500 行代码打造轻量级AI Agent:Nanobot 架构深度解析
https://github.com/shareAI-lab/learn-claude-code
OpenClaw架构-Agent Runtime 运行时深度拆解
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签:
相关文章
最新发布
- 【手把手教学】RoboSense E1R 固态激光雷达 Windows 可视化连接全攻略
- Tailwind CSS 4.2 的真正变化:它正在把一部分前端基础设施直接做进框架
- C#/.NET/.NET Core优秀项目和框架2026年3月简报
- 算法分享01——埃拉托斯特尼算法(埃氏筛)【简单】
- 从“词元”到“符元”:Token 中文名背后的 AI 底层认知之争
- 团结引擎发布抖音小游戏(十万个坑已踩完)
- 【OpenClaw】通过 Nanobot 源码学习架构---(6)Skills
- AScript - C#轻量级动态脚本引擎
- Mem0 源码解析系列(一):记忆是如何被添加的
- 春秋云境 Initial 靶场 WP:ThinkPHP RCE → 内网横向 → 域控沦陷

