← 返回文章

SOURCE NOTE · SKILL SYSTEM

Lesson 6: Skills 子系统

Skill 是 Markdown + YAML frontmatter。系统 prompt 只放清单;正文按需加载——模型走 skill 工具,用户走 /skill-name。

学完本课,你能回答:Skill 从哪几个来源加载、谁覆盖谁?frontmatter 有哪些字段、各控制什么?模型和用户分别通过哪两条路径触发 skill?想给项目注入领域知识时文件该放哪?

一句话概括

Skill 是一个 Markdown 文件 + YAML frontmatter。系统 prompt 里只放 name + description 的清单(省 token);模型判断匹配时才用 skill 工具把全文拉进上下文——这就是「按需加载」。

启动 / 触发时:load_skill_registry()
  bundled → user → extra dirs → project → plugin
  (后注册的同名片段覆盖先注册的)
            |
            v
系统 prompt 注入清单:name + description(不含正文)
            |
   ┌────────┴────────┐
   v                 v
模型路径           用户路径
skill 工具         /skill-name
返回全文           全文渲染成 prompt 提交

下面的 Skill Lab 不是逐步 trace,而是一张可调实验台:选定覆盖来源 → 拨两扇门禁 → 分别用模型路径或用户路径触发,直接看清单是否可见、正文是否进入上下文。

SKILL LAB · LOCAL SIMULATION

清单常驻,正文按需

先选定生效来源与两扇门禁,再分别用「模型」或「用户」触发。组件不读真实磁盘,也不调用模型。

LOAD ORDERbundled → user → extra → project → plugin同名后者覆盖前者
01

谁覆盖谁

点击最晚注册、实际生效的那一层。项目领域知识优先放 project。

02

两扇门禁

frontmatter 把「广告位」和「谁能拉正文」拆开。description 只影响模型是否想点名。

03

模型默认只看到这些

系统 prompt 注入 name + description,不含正文。

deploy-checklist

发布前检查清单,用户说「准备发布」时使用

≈ 一行广告位 · 低 token
04

两条路径,两种回灌方式

CONTEXT OUTCOME正文未进入上下文

尚未触发

默认上下文只有清单(若允许模型看见)。点下方按钮,看正文何时进入。

system prompt catalog
- deploy-checklist · 发布前检查清单,用户说「准备发布」时使用
  • 生效来源project
  • 家目录.openharness/skills/deploy-checklist
  • 清单visible

核心文件

加载来源与覆盖顺序

load_skill_registry() 严格按此顺序注册,同名 key 后者覆盖前者(dict 赋值语义):

  1. bundledskills/bundled/content/*.md,平铺 xx.md
  2. user~/.openharness/skills,兼容 ~/.claude/skills~/.agents/skills;布局为 dir/skill/SKILL.md
  3. extra dirs:运行时传入(如 ohmo workspace),布局同上。
  4. project.openharness/skills.agents/skills.claude/skills,布局同上。
  5. plugin:已启用插件携带的 skills,插件内部路径。

frontmatter:五个控制字段

SKILL.md · frontmatteryaml
---
name: deploy-checklist
description: 发布前检查清单,用户说"准备发布"时使用
user-invocable: true
disable-model-invocation: false
model: claude-opus-5
argument-hint: "<env>"
---

正文:模型/用户触发后拿到的完整指令……

没有 frontmatter 也能加载:fallback 链是 ① YAML frontmatter → ② 第一个 # 标题 + 首个正文段落(description 截断 200 字符)→ ③ "Skill: {name}" 模板。但缺 description 的 skill 模型几乎不会主动调用——description 就是 skill 的广告位

Registry:一个 skill,多个键

register() 把同一个 skill 挂到多个键上:namecommand_namedisplay_namealiases。所以用户敲 /plan 和模型查 "plan" 命中的是同一份定义。list_skills() 再按 (source, path) 去重,保证清单里每个文件只出现一次。

两条触发路径的分工

模型路径

用户路径

用户路径的渲染逻辑(_render_skill_command_prompt):

render-skill-command.py · simplifiedpython
prompt = f"Base directory for this skill: {base_dir}\n\n" + content
prompt = prompt.replace("${CLAUDE_SKILL_DIR}", base_dir)
prompt = prompt.replace("${ARGUMENTS}", args)
# 内容里没写 ${ARGUMENTS} 但用户传了参数 → 末尾自动追加 "Arguments: ..."
return CommandResult(submit_prompt=prompt, submit_model=skill.model)

为什么用户路径要 prepend base_dir?skill 正文常引用同目录的辅助文件(脚本、模板)。告诉模型「这个 skill 的家在哪」,它才能用 read_file 等工具去取——你在 Claude Code 里看到的 Base directory for this skill: ... 就是这么来的。

懒加载 + 热加载

SkillTool.execute 和斜杠命令 handler 每次调用都重新 load_skill_registry()——没有缓存。代价是每次重新扫目录、解析 YAML;收益是改完 SKILL.md 立刻生效,不用重启会话。这和 Hooks 热重载(每条消息前重建 registry)是同一哲学:配置即改即生效,用微小的 IO 换迭代速度

最小 SKILL.md 模板

project skill · minimalmarkdown
---
name: my-check
description: 一句话说清做什么 + 什么时机该调用我
---

# my-check

## When to use
(触发条件,写给模型看的)

## Workflow
1. 步骤一
2. 步骤二

## Rules
- 硬约束

落地建议:

  1. 项目知识放 project 目录,不要只放在个人 ~/.openharness/skills
  2. 把触发条件写进 description,否则清单再全,模型也难主动点名。
  3. 需要用户手搓启动、不希望模型抢跑时,设 disable-model-invocation: true
  4. 需要模型内化流程、不希望污染 / 补全时,设 user-invocable: false
  5. 辅助脚本与模板和 SKILL.md 同目录,依赖用户路径注入的 base_dir,或在正文里写清相对路径。

和前几课的边界

一句话:Tools 给手,Skills 给剧本;Permissions 与 Hooks 决定剧本里的动作能不能真的落地。

推荐精读(一手源码)

主读:src/openharness/skills/loader.py —— 来源顺序、项目发现与路径安全。

接着看: