skill定义
结合anthropic,microsoft的前沿定义,可以将Skill总结如下:
Agent Skill 是一种可复用、可发现、可组合的能力单元,它将领域知识、执行流程(Workflow)、工具调用策略和资源封装在一起,使 Agent 能够在特定任务场景下稳定地复现专家级行为。
写成公式可以表示为
其中Skill正在成为Agent stack中独立于tool和memory的第4层抽象
其中的resource包含的类别很多,可以是独立的python脚本,专家知识,配置及依赖环境,比如第三方api或者mcp服务配置
Skill内部处理逻辑
从具体的Hermes agent的源码看skill部分的具体逻辑
Skill文件夹的结构
在Hermes的路径/.hermes/skills中可以找到hermes的skill包
常见skill文件夹中结构如下
~/.hermes/skills/ └── <category>/ └── <skill-name>/ ├── SKILL.md ← 必须,主文件 ├── references/ ← 可选,按需加载的深度文档 ├── templates/ ← 可选,模板文件(LaTeX、YAML 等) ├── scripts/ ← 可选,辅助脚本 └── assets/ ← 可选,图片等资源这个skill的文件夹设计和一般的文件夹设计不同,其中包含的reference文件夹是一般skill不具备的,框架会自动发现references/、templates/、assets/、scripts/四个目录,并在skill_view()返回值的linked_files字段中列出,供 LLM 按需调用。
Skill文件结构
以YAML Frontmatter作为文件开头的,作为描述skill的元数据,被两个---包围,作为LLM在系统提示词中看到的skill元描述内容,复合渐进式披露的原则
通常内容如下:
正文结构
| 顺序 | 章节 | 作用 |
|---|---|---|
| 1 | # 标题 | skill 名称 |
| 2 | ## When to Use | 触发条件,最关键 |
| 3 | ## Overview / Key Concepts | 核心概念速查 |
| 4 | ## Steps / Workflow | 具体操作步骤 |
| 5 | ## Examples | 可运行的代码示例 |
| 6 | ## Common Pitfalls / Red Flags | 反面教材 |
| 7 | ## Checklist / Verification | 收尾核查清单 |
| 8 | ## References | 指向 references/ 子文件 |
示例
以市场预测的Polymarket Skill为例子,他的文件夹结构如下
skill/ ├── SKILL.md ← 2.9KB:概念、典型流程、3个API、注意事项 ├── references/ │ └── api-endpoints.md ← API 细节(按需加载) └── scripts/ └── polymarket.pyPolymarket 是一个预测市场平台,用户可以用真实金钱对未来事件的结果下注,比如"特朗普会赢得2028年大选吗?"、"比特币年底会超过10万美元吗?"。市场价格本身就代表概率——某个结果的价格是 0.65,就意味着市场认为这件事发生的概率是 65%。
其中SKILL.md内容为背景知识和操作规范
api-endpoints.md保存实际api的请求使用方式,是对SKILL.md中的补充
scripts/polymarket.py是一个可以直接运行的命令行工具,零依赖,全程用 Python 标准库。LLM可以直接进行调用
python3 polymarket.py search "bitcoin" # 搜索比特币相关市场 python3 polymarket.py trending # 看今天交易量最大的事件 python3 polymarket.py history 0xabc... # 看某个市场的价格历史 python3 polymarket.py book TOKEN_ID # 看 orderbook 深度Hermes agent中代码逻辑
1.提取skill元数据
/.hermes/hermes-agent/agent/skill_utils.py
# skill_utils.py L532-544 def iter_skill_index_files(skills_dir: Path, filename: str): """Walk skills_dir yielding sorted paths matching *filename*.""" matches = [] for root, dirs, files in os.walk(skills_dir, followlinks=True): # 剔除 .git / venv / node_modules / __pycache__ 等 dirs[:] = [d for d in dirs if d not in EXCLUDED_SKILL_DIRS] if filename in files: matches.append(Path(root) / filename) # 按相对路径字典序排列,保证结果稳定 for path in sorted(matches, key=lambda p: str(p.relative_to(skills_dir))): yield path提取每个skill的元数据,并在60字符处截断
# prompt_builder.py L990-1006 def _parse_skill_file(skill_file: Path) -> tuple[bool, dict, str]: raw = skill_file.read_text(encoding="utf-8") frontmatter, _ = parse_frontmatter(raw) # ← 解析 YAML frontmatter if not skill_matches_platform(frontmatter): return False, frontmatter, "" # ← 平台过滤(macos/linux/windows) return True, frontmatter, extract_skill_description(frontmatter) # skill_utils.py L518-526 def extract_skill_description(frontmatter: Dict[str, Any]) -> str: raw_desc = frontmatter.get("description", "") desc = str(raw_desc).strip().strip("'\"") if len(desc) > 60: return desc[:57] + "..." # ← 这就是系统提示词里看到的内容 return desc2.检查两级缓存
/.hermes/hermes-agent/agent/prompt_builder.py
第一层(进程内 LRU)
_SKILLS_PROMPT_CACHE: OrderedDict 最多 8 个 entry
cache_key = (skills_dir, external_dirs, tools, toolsets, platform, disabled_skills)
第二层(磁盘 snapshot)
~/.hermes/.skills_prompt_snapshot.json
含 manifest(每个 SKILL.md 的 mtime + size),用于校验缓存是否过期
校验snapshot逻辑,如果命中则直接复用,而非重新扫描skill
# prompt_builder.py L919-934 def _load_skills_snapshot(skills_dir: Path) -> Optional[dict]: snapshot = json.loads(snapshot_path.read_text(encoding="utf-8")) # 版本号不匹配(代码升级后)→ 缓存失效 if snapshot.get("version") != _SKILLS_SNAPSHOT_VERSION: return None # manifest 不匹配(有 SKILL.md 文件被修改)→ 缓存失效,重新扫描 if snapshot.get("manifest") != _build_skills_manifest(skills_dir): return None return snapshot # 命中:直接用预解析的 metadata,跳过全量扫描snapshot内容
3.组装系统提示词
/.hermes/hermes-agent/agent/system_prompt.py
# system_prompt.py L179-195 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, ) if skills_prompt: stable_parts.append(skills_prompt) # 加入系统提示词的 stable 层stable层属于系统提示词的一部分,并且系统提示词不参与上下文压缩,以保持kv缓存的命中率,
系统提示词被分成三层:
stable = 身份 + 工具指引 + skills 索引 ← 本会话不变,prefix cache 保持有效
context = AGENTS.md 等项目上下文 ← 按 cwd 变化
volatile = 记忆 + 时间戳 ← 每次对话都可能变
skills 索引放在stable层,意味着一旦 LLM API 缓存了这个 prefix,后续每轮对话不用重新计算这些 token,显著降低成本和延迟。这也是为什么 description 只截断到 60 字符而不是完整加载所有 SKILL.md 内容——索引要尽量短,以便 prefix cache 命中率更高。
最终注入格式
# prompt_builder.py L1235-1262 result = ( "## Skills (mandatory)\n" "Before replying, scan the skills below. If a skill matches or is even " "partially relevant to your task, you MUST load it with skill_view(name) " "and follow its instructions. ...\n" "\n" "<available_skills>\n" " research:\n" " - polymarket: Query Polymarket: markets, prices, orderbooks...\n" " - research-paper-writing: End-to-end academic paper writing ...\n" " software-development:\n" " - spike: Run a time-boxed experiment to validate a technolog...\n" " ...\n" "</available_skills>\n" "\n" "Only proceed without loading a skill if genuinely none are relevant." )4.LLM决策调用
如果LLM在这个用户输入的语句中满足某个skill的触发条件,LLM返回一个tool_call
{ "tool": "skill_view", "arguments": { "name": "polymarket" } }agent后端接受到tool_call指令,解析并执行工具
# tool_executor.py L542 / L110 def execute_tool_calls_sequential(agent, assistant_message, messages, ...): for tool_call in assistant_message.tool_calls: function_name = tool_call.function.name # = "skill_view" function_args = json.loads(tool_call.function.arguments) # 安全门:guardrail 检查、plugin block 检查 guardrail_decision = agent._tool_guardrails.before_call(function_name, function_args) if not guardrail_decision.allows_execution: ... # 拦截,不执行 # 最终分发执行 result = agent._invoke_tool(function_name, function_args, task_id, ...) messages.append(make_tool_result_message(function_name, result, tool_call.id))skill_view通过注册表连接到真实的工具函数
# skills_tool.py L1517-1524 registry.register( name="skill_view", toolset="skills", schema=SKILL_VIEW_SCHEMA, handler=_skill_view_with_bump, # ← 真正执行的函数 check_fn=check_skills_requirements, emoji="📚", )LLM收到返回示例
{ "success": true, "name": "polymarket", "content": "---\nname: polymarket\n...(SKILL.md 全文 2.9KB)...", "linked_files": { "references": ["references/api-endpoints.md"], "scripts": ["scripts/polymarket.py"] }, "usage_hint": "To view linked files, call skill_view(name, file_path=...)" }5.阅读SKIll
LLM 读完 SKILL.md 后,如果需要实际查询数据,会调用terminal工具直接运行脚本:
# LLM 的 tool_call: { "tool": "terminal", "arguments": { "command": "python3 ~/.hermes/skills/research/polymarket/scripts/polymarket.py search 'bitcoin'" } }如果需要查看api细节
# LLM 的 tool_call: { "tool": "skill_view", "arguments": { "name": "polymarket", "file_path": "references/api-endpoints.md" // ← file_path 参数 } }LLM获取足够信息后则会按照SKILL内容做下一步操作。