Appearance
3.10 技能 Skill
本章概览:技能(skill)能力族发现可复用的任务特定指令,并经 provider 中立的目录与加载器暴露给模型。本章拆解
ctx.skills提供方注册表、host+per-scope 分层合并、发现快照的完整性语义、调用策略(invocation policy)、模型侧skill工具与目录生命周期。
概述 / 定位
skill 是"可复用的任务特定指令",位于核心控制脊柱之外:ctx.skills 是纯提供方注册表,不关心技能来自本地文件、嵌入插件数据还是 HTTP 后端;模型可见的目录与加载工具由消费端 dsh-tool-skill 提供。能力族四包拆分:dsh-skill(Service Definition,ctx.skills)、dsh-skill-filesystem(本地提供方)、dsh-skill-badge(可选打包徽章技能)、dsh-tool-skill(Consumer,注册在 ctx.tools)。技能是可选的指令而非会话事件,因此其词汇在子系统页而非 core。
提供方注册表
SkillProvider 只有两个方法:list(options) 返回候选(候选数组是"发现完整"的简写;显式不完整发现返回 { candidates, complete: false } 观察),get(candidate, options) 加载完整技能体。注册是同步的,远程初始化、认证与发现放在 awaited list() 内。registerProvider(create) 的同步工厂收到注册作用域的控制对象:control.signal 在注册失败或释放时 abort;control.invalidate() 仅当该确切注册仍存活时清除已完成目录并通知消费者,迟到的回调不能影响同名替换者。注册即副作用,返回精确的 Cordis disposer。
出处:
packages/skill/skill/README.md(deepseek-harness-src/packages/skill/skill/README.md)
分层合并:global → scope 链
注册表是 host+per-scope 分层(tools 注册表在 dsh-scope 上建立的形状):一次注册落入调用上下文 scope 的层——宿主行与仓库插件进全局(global)层,agent preset 常驻组合挂载的插件进该 preset 层;提供方名按层唯一,而非进程全局。一次读取合并全局层与查看 scope 的链:最近层的同名技能直接获胜(遮蔽更远层),rank(数字越小越优先)只在同一层内决定重复。层内重复名按 rank → 提供方注册序 → 提供方本地序解析,摘要按名排序。发现缓存以解析出的 scope 链为键,所以 scope 重新挂父(空会话重组)无需注册表变更即可被下次读取看到。
发现快照的完整性
snapshot() 返回 SkillCatalogSnapshot { skills, complete }:complete 为 true 仅当每个注册提供方都完成发现且无并发目录修订。被拒绝的 list() 记录并从未完整观察中省略;显式不完整观察的候选仍可直接加载,但使聚合快照不完整且不可缓存;一次目录修订使在途发现重试一次,第二次修订则返回最新候选且不完整不缓存——持续失效的提供方无法独占调用者。消费端据此保留 last-good 过滤目录并在下一个请求边界重试。
失效通知:提供方或运行时贡献注册/释放、活动提供方调用 invalidate() 后发出无 diff 的 skills/change(emit)事件——消费者各自以自身 lookup options 重取 snapshot(),监听器失败被包含、不能否决注册表变更。
调用策略:四组合
SkillInvocationPolicy 是两个独立正布尔 modelInvocable 与 userInvocable。ctx.skills.list() 保留全部四种组合:
| modelInvocable | userInvocable | 语义 |
|---|---|---|
| true | true | 模型目录与人类命令都含 |
| true | false | 仅模型 |
| false | true | 仅人类(disable-model-invocation) |
| false | false | 两者皆否,仅受信任的 ctx.skills.get() 调用者可加载 |
本地提供方读取精确的 frontmatter 键 disable-model-invocation 与 user-invocable,缺省均 true;非法(非布尔、驼峰拼写)的调用值把整个技能从发现中丢弃——策略 fail closed,因为忽略无效数据可能把技能暴露到禁用表面。ctx.skills.get() 是策略中立的加载原语,每个面向模型或用户的消费方必须在自己的边界强制匹配的谓词。
本地提供方与打包提供方
dsh-skill-filesystem 按 rank 扫描根:project-dsh(<projectRoot>/.dsh/skills,rank 100)→ project-agents(<projectRoot>/.agents/skills,200)→ custom(Config.customSkillDirs,300)→ user-dsh(<dshHome>/skills,400)→ user-agents(<agentsHome>/skills,500)→ bundled(配置 bundledSkillDir 时,600)。项目根 = 最近含 .git 的祖先;有 ctx.fs 时经文件系统服务探测 .git,远程/沙箱工作区不回落宿主边界。格式:单层目录包 <name>/SKILL.md 或扁平 <name>.md(嵌套 **/SKILL.md 不发现),名字必须 kebab-case。Chokidar 监视既有根的直接增删改;write/edit 工具经 fs/observed 同步失效提供方,使下一次模型步骤无需等宿主 watcher。dsh-skill-badge 注册单个不可变 bundled 候选(dsh-badge 徽章技能,暴露打包 assets/ 为资源基),出厂 composition 声明 disabled: true,显式启用才入目录。
模型侧目录与加载工具
dsh-tool-skill 在每个 agent/pre-step 调 snapshot()(转发步骤的 abort signal),首次观察到非空完整视图时向后续 enter 决策注入持久的 user 角色 <system-reminder> 会话前缀目录:<available_skills> 内仅 name + 归一化并截断的 description(catalogDescriptionMaxLength 默认 500),不含 body、路径、source、provider 与 whenToUse。目录 digest 基于持久 entries 而非渲染文本;digest 变化时整表替换(agent.inject() 追加完整替换消息),删空时显式空替换;不完整快照保留 last-good 模型视图,compaction 隐藏全部历史目录后由下一次完整观察重建。
skill({ name }) 工具校验 kebab-case 名,拒绝非 modelInvocable 技能(userInvocable 不限制该工具),按调用 agent 的 session.header.cwd 重读完整定义并复检策略,返回含 <skill_content name> / <skill_resources> / <skill_instructions> 的文本结果。资源按 resourceBase(directory/url/opaque)只解析指令实际引用的路径或 URL,不枚举技能目录;body-only 编辑改变后续调用而不产生目录消息或改写旧工具结果。
用户显式手势是另一条加载路径:用户消息中出现命名用户可调用技能的 /name 记号时,注入该技能完整 <skill_content> 渲染(user 角色指令上下文,置于该步所有注入之后);这是 disable-model-invocation 技能的唯一入口。
出处:
packages/skill/tool-skill/README.md(deepseek-harness-src/packages/skill/tool-skill/README.md)
与工具、提示词的关系
技能目录是持久会话历史而非系统提示词:首次请求前作为 durable 消息注入,变更走整表替换;技能体作为工具结果保留(append-only,直到压缩)。技能本身不是工具、也不是提示词段:它通过 ctx.tools 上的 skill 工具按需加载,目录消息是 session history 而非 World State。
关键设计决策
- 注册表是纯提供方抽象:本地/嵌入/远程提供方可互换而不改模型侧契约;Consumer 与提供方解耦(
dsh-tool-skill是唯一模型侧消费者)。 - 分层合并"最近者胜 + 层内 rank":与 tools 注册表同形,agent preset 可遮蔽全局技能。
complete是缓存资格而非数据完整性:不完整观察宁可保留 last-good,也不发布误导性的删除。- 调用策略双布尔独立:一个发现结果同时服务模型工具、人类命令与受信任内部调用者。
- 目录与 body 生命周期分离:digest 只盯目录,body 每次
get()现读,编辑无需版本协议。
扩展点
- 新提供方:
ctx.skills.registerProvider(create)同步注册,返回 disposer;远程认证/发现放list(),按需调control.invalidate()。 - 运行时技能:
ctx.skills.register(definition)注册嵌入技能(rank 250,层内同名 first-wins),省略 invocation/provider 时补全为全可调用 +provider: 'runtime'。 - 新消费面:在自身边界强制
isModelInvocable/isUserInvocable后消费list()/snapshot();collectCacheMaxEntries(默认 128)限制注册表内存中的完成目录数。 - 打包技能:配置
bundledSkillDir或启用dsh-skill-badge行,把出厂技能带入目录。
参考资料
- packages/skill/skill/README.md — 注册表 Service Definition(
deepseek-harness-src/packages/skill/skill/README.md) - packages/skill/skill-filesystem/README.md — 本地提供方(
deepseek-harness-src/packages/skill/skill-filesystem/README.md) - packages/skill/skill-badge/README.md — 打包徽章提供方(
deepseek-harness-src/packages/skill/skill-badge/README.md) - packages/skill/tool-skill/README.md — 目录与 skill 工具(
deepseek-harness-src/packages/skill/tool-skill/README.md) - docs/subsystems/skills.md — 技能子系统参考(
deepseek-harness-src/docs/subsystems/skills.md) - skill catalog hot-refresh Agent Note(
deepseek-harness-src/.agents/notes/implemented/feature/2026-07-27-skill-catalog-hot-refresh.md) - docs/tool-catalog.md — skill 工具 schema(
deepseek-harness-src/docs/tool-catalog.md)