1.1 skill 是什么:按需加载的指令包

一句话定义

在 DeepSeek Harness 里,skill 是一份可复用的 agent 指令:它以 Markdown 形式存在,平时只贡献一条“名称 + 描述”给模型看,等任务匹配上时才被完整加载进上下文。它不新增进程、不打开端口,却能把某个领域的专业做法一次性注入会话。

官方子系统文档对它的定位很直接:skill 是可选的指令而非会话事件。这句话有两层含义,值得拆开。

第一,它是指令,不是事件。会话事件是运行时产生的结构化消息,会自行流动;skill 不会在后台自行触发,它只在你或模型选中它时,把一段文本送进上下文。第二,它是可选的——一个技能都不装,harness 照常工作;技能增减改变的是模型的能力面,不是会话的基本形态。

它不是插件,也不是工具

初学者最容易把三个概念混在一起。先用一张表把它们分开:

概念 本质 谁来实现 什么时候加载 例子
插件 代码模块,导出 apply 你或第三方开发者 启动时随组合装载 @deepseek-ai/dsh-skill-office
工具 模型可调用的函数 插件注册到 ctx.tools 会话全程可用 read、bash、skill
skill 一段指令文本 任何人写 Markdown 按需加载 office-docx、dsh-code-review

容易含糊的维度再对照一遍:

维度 插件 工具 skill
载体 代码包 注册表里的函数 Markdown 文件
常驻开销 随组合常驻 占一条目录条目 只占一条摘要
完整内容何时进上下文 不进去(它是机制) 调用时进参数与结果 选中时才进正文
能否被 git 管理 能 随插件走 能,且就是纯文本
编写门槛 会写代码 会写代码 会写 Markdown

关键差别在加载时机。工具是常驻的,注册了多少模型就看到多少;skill 是候诊式的——注册表里躺着几十个,模型目录里只显示几十条摘要,真正读正文的只有被选中的那一两个。

这就是官方所说的渐进加载(progressive loading):先花极小的代价让模型知道有这么一个技能、什么时候该用,等真用上了再付出完整正文的上下文成本。

三类接口:提供方、观测与控制

技能注册表 ctx.skills 不是一个扁平列表,它把本地、内嵌、远程或其他提供方组合在一起。理解这三个接口,后面讲优先级、失效与排查都用得到:

接口 职责
SkillProvider 技能的来源抽象。本地目录、内嵌集合、远程服务都可以包装成提供方。注册动作是同步的;远程提供方的初始化与发现发生在 list() 的 await 阶段
SkillProviderObservation 一次读取得到的观测快照。观测可能不完整:某个提供方 list() 被拒时记录日志,该次观测会省略它的结果
SkillProviderControl 注册作用域内的控制能力,含 signal(注册失败或 dispose 时中止)与 invalidate()(仅在精确注册仍活动时清除已完成目录)

这些接口意味着:技能出现与否不是一次性的启动动作,而是每次读取时由各提供方现场汇报的结果。这也解释了为什么改文件、换 scope 会影响下一刻的目录。

摘要里有什么:SkillSummary 字段表

模型看到的目录条目来自 SkillSummary,字段不多,各有用途:

字段 含义
name 技能名,kebab-case,同时是目录名或文件名
description 一句话说明做什么、何时用;模型唯一必读字段
whenToUse 可选的触发条件补充
invocation 调用策略,含 modelInvocable 与 userInvocable 两个独立布尔
source 来源桶,如 project-dsh、user-dsh、bundled
provider 拥有该技能正文的提供方名
path 指令文件的绝对路径;虚拟技能没有此字段

注意官方的明确边界:模型可调用的会话目录只使用 name 与 description,从不使用正文或绝对文件路径。path 是给人排查用的,不是给模型用的。

一个 skill 长什么样

skill 的物理形态只有两种。

形式一:目录包(推荐,可带附件)

my-skill/
├── SKILL.md            # 必需:指令正文,开头是 frontmatter
├── references/         # 可选:按需展开的参考资料
│   └── checklist.md
└── scripts/            # 可选:配套脚本
    └── verify.py

形式二:扁平 Markdown 文件

skills/
└── my-skill.md         # 文件名即技能名

两者的加载结果完全一致,区别只在于目录包能带参考资料与脚本,适合内容较多的技能。

SKILL.md 的骨架

一份合格的 SKILL.md 由 frontmatter 加正文构成。frontmatter 声明元数据,正文写指令:

---
name: my-skill
description: 一句话说明这个技能做什么、什么时候该用它。这是模型唯一必读的字段,务必写清触发条件。
---

# 我的技能

## 适用场景

什么时候该启用本技能。

## 执行步骤

1. 第一步做什么
2. 第二步做什么
3. 交付前检查什么

## 注意事项

容易踩的坑与红线。

其中 description 是整个 skill 里最值钱的一行。模型的技能目录只展示名称与描述,正文和绝对路径都不给模型看。描述写得含糊,技能就永远不会被选中——这是技能“装了没反应”的头号原因。

名称有硬性格式

skill 名称必须是 kebab-case,也就是小写字母、数字与连字符的组合:

^[a-z0-9]+(?:-[a-z0-9]+)*$
✅  office-docx        dsh-code-review        webapp-testing
❌  Office_Docx        dshCodeReview          webapp.testing

名字同时是目录名(或文件名),改名字等于换一个技能。

模型怎么用它

加载路径有两条,互补而非二选一:

路径 触发者 形式 适用
模型自选 模型 调用 skill 工具,按名称加载 任务匹配上时自动发生
用户直调 人 输入 /技能名 你明确知道要用哪个

每个技能还带一对布尔控制,官方称之为调用策略(SkillInvocationPolicy):

字段 含义
modelInvocable 是否出现在面向模型的目录与加载器里
userInvocable 是否出现在面向人的命令目录与加载器里

把 modelInvocable 关掉、userInvocable 打开,就得到一个只给你自己用的私有技能——模型看不到,也不会擅自执行。适合放带有个人偏好或敏感内部信息的指令。日常两种都可以用:让模型自己挑,需要强制走某套流程时(比如必须按公司规范写周报)直接斜杠调用。

渐进加载的价值,数量上去后才显出来

只装一个技能时,渐进加载的优势看不出来——就一个,全量加载也无所谓。价值出现在技能数量上去之后:

  • 常驻成本:每条技能只贡献 name 与 description 两行,几十个技能也不过几十条摘要;
  • 完整成本:只有被选中的技能付出正文代价,一次通常是一两个;
  • 若换成全量注入:每份正文动辄数百行,开销随技能数量线性膨胀,很快挤占任务本身需要的空间。

这也是官方把 description 定为模型唯一必读字段的原因:摘要就是全部广告位,广告写砸了,商品再好也卖不出去。

description 写废的三种典型

按踩坑频率排:

  1. 只写功能,不写触发条件。 例如“Excel 处理技能”——模型知道它能干什么,却不知道什么时候该找它。对照官方技能的写法:“电子表格文件是主要输入或输出时使用”,触发条件写在明面上;需要时也可以用 whenToUse 显式补充。
  2. 触发条件过于宽泛。 “处理文档时使用”几乎匹配一切任务,等于没有;候选一多,模型反而难以取舍。触发条件越具体,命中越稳。
  3. 把正文抄进描述。 唯一必读字段被复述内容占满,触发信息被稀释。描述要回答的是“什么时候用”,不是“里面有什么”。

为什么它值得你花时间

把 skill 和另外两种“教 agent 做事”的方式对比,优势就出来了:

方式 成本 复用性 版本管理
每次在对话里重复交代 低(单次) 无 无
写进系统提示词 中 全局生效、无法按需 难
做成 skill 一次性写 任意项目可复用 可随 git 走

第三条是重点:skill 就是仓库里的普通文件,意味着它可以被 review、被 diff、被回滚。团队可以把“我们公司怎么写迁移脚本”沉淀成技能提交进仓库,新人 clone 下来即用——这是把个人经验变成组织资产的最短路径。

小结

  • skill = 按需加载的指令包,形态是 SKILL.md 目录或扁平 .md 文件。
  • 名称必须 kebab-case;description 决定模型能不能选中它。
  • 模型通过 skill 工具自选,人通过 /名称 直调;两个调用开关可做仅供本人使用的技能。
  • 提供方、观测、控制三类接口支撑起注册表;进模型目录的只有 name 与 description。
  • 它是文件,所以天然可复用、可评审、可版本化。

下一节解决第一个实操问题:这么多层目录,skill 到底该放哪一层。

延伸阅读