> TL;DR:Skill 不是静态的文档——它是 Agent 的"程序性记忆"系统。SKILL.md 采用 YAML frontmatter + Markdown 的元数据结构,各文件通过 AST 扫描自动被 System Prompt 的 Skills Index 加载。这篇从 skills_tool.py(1545 行)和 skill_commands.py(527 行)源码拆 Skill 的完整链路。
前六篇拆了外围子系统,这篇拆 Hermes Agent 最核心的特性——技能进化(Skills)。 你可能从各种地方听过 Hermes 的"自进化"能力:Agent 用着用着自己长出新技能。这是怎么实现的?不是魔法,是一套完整的"经验→模板→复用"管道。
1. Skill 是什么
Skill 的本质是带元数据的可执行工作流文档。格式是组合式:
---
name: axolotl
description: Axolotl: YAML LLM fine-tuning (LoRA, DPO, GRPO).
version: 1.0.0
category: mlops
metadata:
hermes:
tags: [fine-tuning, lora, dpo]
related_skills: [unsloth, trl]
---
# Axolotl Fine-tuning Skill
使用 Axolotl 进行 LLM 微调的完整工作流。核心操作:
```bash
# 安装
pip install axolotl
# 训练
accelerate launch -m axolotl.cli.train config.yml
每个 Skill 对应 `~/.hermes/skills/<category>/<skill-name>/` 下的一个目录:
~/.hermes/skills/ ├── mlops/ │ ├── axolotl/ │ │ ├── SKILL.md # 主文件(必需) │ │ ├── references/ # 参考文档 │ │ │ └── dataset-formats.md │ │ └── templates/ # 输出模板 │ │ └── config.yaml │ └── unsloth/ │ └── SKILL.md ├── devops/ │ └── csdn-publish/ │ └── SKILL.md └── ...
---
## 2. SKILL.md 元数据结构
SKILL.md 的头部 YAML frontmatter 定义了 Agent 和工具系统需要的全部结构化信息。`skills_tool.py` 中的 `_parse_frontmatter()` 负责解析它,但不重复实现——它委托给 `agent.skill_utils.parse_frontmatter()`:
```python
def _parse_frontmatter(content: str) -> Tuple[Dict[str, Any], str]:
from agent.skill_utils import parse_frontmatter
return parse_frontmatter(content)
Frontmatter 支持 agentskills.io 兼容格式。关键字段:
| 字段 | 必需 | 说明 | 上限 |
|------|------|------|------|
| name | ✅ | Skill 唯一标识 | 64 字符 |
| description | ✅ | 列表展示摘要 | 1024 字符 |
| version | ❌ | 语义化版本 | — |
| category | ❌ | 分类路径 | — |
| platforms | ❌ | 运行平台约束(macos/linux/windows) | — |
| required_environment_variables | ❌ | 环境变量需求 | — |
| metadata.hermes | ❌ | Hermes 专属元数据(tags/related_skills) | — |
Prerequisites(旧版)和 required_environment_variables(新版)的解析:
def _get_required_environment_variables(frontmatter, legacy_env_vars=None):
required_raw = frontmatter.get("required_environment_variables")
# 支持三种输入格式:
# 1. 字符串列表: ["OPENAI_API_KEY"]
# 2. 带描述的 dict: {"name": "OPENAI_API_KEY", "prompt": "输入 key"}
# 3. 旧版 prerequisites.env_vars
for item in required_raw:
if isinstance(item, str):
_append_required({"name": item})
if isinstance(item, dict):
_append_required(item)
# 旧版兼容
if legacy_env_vars is None:
legacy_env_vars, _ = _collect_prerequisite_values(frontmatter)
3. 加载机制:Skills Index——System Prompt 的循环注入
Skills Index 是连接 Skill 系统和 System Prompt 的桥梁。它不是每次从文件系统扫描,而是用两层缓存。
在 prompt_builder.py(System Prompt 组装层)中:
has_skills_tools = any(name in agent.valid_tool_names for name in ['skills_list', 'skill_view', 'skill_manage'])
if has_skills_tools:
skills_prompt = _r.build_skills_system_prompt(
available_tools=agent.valid_tool_names,
available_toolsets=avail_toolsets,
compact_categories=_compact_cats or None,
)
build_skills_system_prompt() 返回的是一个索引——不是全部 Skill 的内容,只是名称和描述。每一行是一条记录,像这样:
tools/skill_name: 简短描述(10-30字)
这个索引通过两层缓存减少 I/O 和 Token:
1. 进程内 LRU 缓存:在内存中缓存 index 的构建结果
2. 磁盘快照:~/.hermes/.skills_prompt_snapshot.json——跨进程重启也能恢复
当 Agent 启动时,先按"编码模式"(compact_categories)决定是否要压缩非编码类 Skill 的描述。如果当前目录和平台被识别为 coding 场景,非编码类 Skill 在索引中只显示名称,不显示描述——节省约 60-80% 的 Index Token。
4. 自注册:工具系统中的 Skills 工具
Skill 的操作通过三个工具暴露给 Agent:
# tools.registry 中的注册(在 skills_tool.py 的模块顶层)
registry.register(
name="skills_list",
toolset="skills",
schema={
"description": "按类别列出所有可用的技能,返回元数据",
"parameters": {
"type": "object",
"properties": {
"category": {"type": "string", "description": "按类别筛选(可选)"}
}
}
},
handler=skills_list,
check_fn=check_skills_requirements,
)
registry.register(
name="skill_view",
toolset="skills",
# ... 读取完整内容或引用文件
)
registry.register(
name="skill_manage",
toolset="skills",
# ... 增删改
)
注册表的 check_fn 是一个常量 check_skills_requirements()——它始终返回 True:
def check_skills_requirements() -> bool:
"""Skills are always available — the directory is created on first use if needed."""
return True
这意味着 Skills 工具集会一直可用,永远不会被"环境不满足"而禁用。
5. 斜杠命令绑定
skill_commands.py 把 Skill 加载和 CLI 斜杠命令绑定在一起。当用户在终端敲 /my-skill 时:
def _load_skill_payload(skill_identifier, task_id=None):
raw_identifier = (skill_identifier or "").strip()
if not raw_identifier:
return None
try:
from tools.skills_tool import SKILLS_DIR, skill_view
# 支持绝对路径和相对路径
identifier_path = Path(raw_identifier).expanduser()
if identifier_path.is_absolute():
# 安全检测:只允许在 SKILLS_DIR 和 external_skills_dirs 内的路径
for root in trusted_roots:
try:
normalized = str(identifier_path.relative_to(root))
break
except ValueError:
continue
else:
normalized = raw_identifier.lstrip("/")
loaded_skill = json.loads(
skill_view(normalized, task_id=task_id, preprocess=False)
)
except Exception:
return None
路径安全检测是一个值得关注的设计:skill_view() 在处理绝对路径时,只允许在 SKILLS_DIR 和 external skills dirs 范围内的路径。这意味着你不能用 skill_view("/etc/passwd") 来读取系统文件——这是一个安全边界。
6. 注入检测
skills_tool.py 硬编码了一个 prompt injection 检测模式列表:
_INJECTION_PATTERNS: list = [
"ignore previous instructions",
"ignore all previous",
"you are now",
"disregard your",
"forget your instructions",
"new instructions:",
"system prompt:",
"<system>",
"]]>",
]
这些模式在 skill_view() 加载内容时检查。如果你从某个不可信源复制了 SKILL.md,其中包含"ignore previous instructions",Hermes 会探测到并发出警告。
7. 退化机制:什么时候 Skill 不会加载
Hermes 不会无差别加载所有 Skill。以下情况 Skill 会被跳过或标记为不可用:
7.1 平台不匹配
_PLATFORM_MAP = {
"macos": "darwin",
"linux": "linux",
"windows": "win32",
}
def skill_matches_platform(frontmatter) -> bool:
from agent.skill_utils import skill_matches_platform as _impl
return _impl(frontmatter)
如果 SKILL.md 中声明了 platforms: [macos],这台 Linux 机器就不会加载它。
7.2 环境不匹配
def skill_matches_environment(frontmatter) -> bool:
from agent.skill_utils import skill_matches_environment as _impl
return _impl(frontmatter)
这个检查是"offer-time"级别的——只在工具发现阶段生效。如果你显式调用 skill_view("my-skill"),它会强制加载,不受环境检查影响。
7.3 所需环境变量缺失
Skill 可以声明 required_environment_variables。如果某个必需的环境变量不存在:
if _is_gateway_surface() and not env_var_enabled("HERMES_INTERACTIVE"):
return {
"missing_names": missing_names,
"setup_skipped": False,
"gateway_setup_hint": _gateway_setup_hint(),
}
Gateway 上(Telegram/Discord 等非交互界面)会返回一条提示,告诉用户去 CLI 配置这个环境变量。CLI 上则会直接弹窗让用户输入。
8. 一条完整的 Skill 链路
从零到使用一个 Skill 的完整路径:
用户创建 SKILL.md
↓
文件写入 ~/.hermes/skills/<category>/<name>/
↓
Agent 启动 → System Prompt 构建
↓
prompt_builder 调用 build_skills_system_prompt()
→ 扫描 skills/ 目录
→ 读取每个 SKILL.md 的 frontmatter
→ 两层缓存(LRU + disk snapshot)
→ 生成 Skills Index(名称+描述列表)
↓
System Prompt 注入 → Agent 看到可用工具
↓
用户:"/check-server"
↓
skill_commands._load_skill_payload("check-server")
→ skill_view() 读取 SKILL.md
→ 注入检测扫描
→ 平台/环境检查
→ 环境变量检查
↓
Agent 执行 Skill 中定义的工作流
↓
完成后 → Hindsight 自动 retain 总结
→ 如果够复杂,新建/更新 SKILL.md(技能进化)
本系列基于 Hermes Agent v0.15.2 源码。技能系统文件:tools/skills_tool.py(1545 行)、agent/skill_commands.py(527 行)、agent/skill_preprocessing.py、agent/skill_utils.py。
本文由 admin 原创,转载请注明出处。
评论
0