Skip to content

技能(skill)是可复用的、针对特定任务的指令包,智能体可以按需加载,这样模型就不必把每套流程都常驻在上下文窗口里。技能既不是代码、会话事件,也不是 World State 事实——它是一段 Markdown 指令正文,初始为空,只在被调用时注入到存活智能体的某一轮 turn 中。这条链路全部位于 packages/skill/*,并有意识地排除在核心 agent-loop 主链之外,因此提供方可以是本地的、内嵌的或远程的,而不会改变模型所看到的内容。

角色ctx key
packages/skill/skill服务定义:分层提供方注册表ctx.skillsctx.skills
packages/skill/skill-filesystem本地提供方:扫描磁盘上的项目/用户根目录注册到 ctx.skills
packages/skill/skill-badge可选的 dsh-badge 内置提供方注册到 ctx.skills
packages/skill/tool-skill消费方:会话目录 + 面向模型的 skill 工具注册到 ctx.tools
packages/client/ui-skillWeb UI:技能加载的紧凑转录行仅浏览器端

官方子系统参考见 docs/subsystems/skills.md;源码见 packages/skill/{skill,skill-filesystem,skill-badge,tool-skill}/src/index.ts

技能是什么,如何声明

技能是一个带 YAML frontmatter 的 Markdown 文件。文件系统提供方接受两种磁盘形态(packages/skill/skill-filesystem/src/index.ts):

  • 目录包(directory bundle)——<skill-name>/SKILL.md
  • 扁平文件——<skill-name>.md(单个 Markdown 文件)。

技能名必须为 kebab-case(^[a-z0-9]+(?:-[a-z0-9]+)*$)。不支持递归 **/SKILL.md 发现——提供方只扫描一层。

frontmatter 契约极其精简(缺少 YAML、或缺少 name/description,该文件会被跳过并给出警告):

markdown
---
name: my-skill
description: 在目录中展示的简短路由描述。
# 可选:
whenToUse: 额外的路由指引(在渲染它的消费方中展示)。
disable-model-invocation: false   # modelInvocable
user-invocable: true              # 用户可用 /<name> 触发
metadata-key: any-extra-field     # 作为不透明 metadata 保留
---

实际的指令正文写在这里(Markdown)。

两个 frontmatter 键映射到调用策略(invocation policy)SkillInvocationPolicy):disable-model-invocation(默认 false → 模型可调用)与 user-invocable(默认 true)。两者都以精确的 kebab-case 布尔值读取;旧的 camelCase 键(disableModelInvocationmodelInvocableuserInvocable)会被拒绝,并提示应使用的规范键。把 disable-model-invocation 设为 trueuser-invocable 设为 false,则该技能只能通过受信任的 ctx.skills.get() 调用方触达。

注册表:ctx.skills

SkillRegistrypackages/skill/skill/src/index.ts)是中立于提供方的目录。提供方在 apply() 期间同步注册;发现逻辑在 list() 内被 await。注册表是分层的 host + per-scope:注册会落入其调用上下文所处 scope 的那一层,因此全局行与仓库插件落在全局层,而由智能体 preset 挂载的插件落在该 preset 的层。一次读取会合并全局层与查看智能体的链——最近的层的条目直接赢得重名。

在同一层内,重名的技能按rank、然后提供方顺序、再本地顺序解析。注册表的公开 ID:

registerProvider(create)         register(skill)
list(options) -> Promise<SkillSummary[]>
snapshot(options) -> Promise<SkillCatalogSnapshot>
get(name, options) -> Promise<SkillDefinition | undefined>

三个结果形态从 summary → candidate → definition 逐级扩展:

  • SkillSummary——与调用无关的元数据(namedescription、可选 whenToUseinvocationsourceproviderresourceBase)。目录渲染的就是它。
  • SkillCandidate——summary 加上 rank 与透明的 locator(提供方所有),注册表存储它,并在 get() 时交还给胜出的提供方。
  • SkillDefinition——candidate 加上 content(Markdown 正文),由 get() 返回。

Snapshot 区分权威缺失瞬时失败:只有当每个注册的提供方都在没有并发目录修订的情况下完成时 complete 才为 true,且不完整的快照绝不缓存。查找对 cwd 敏感(工作区本地技能),并可通过 signal 取消。任何提供方/运行时变更后,注册表都会发出不带 diff 的 skills/change 失效事件——消费方据此重新 snapshot()

本地发现优先级

dsh-skill-filesystem 按 rank 顺序扫描根目录。项目根是含有 .git 的最近祖先(当存在时通过 ctx.fs 探测,使沙箱/远程工作区保持一致),否则用 cwd:

Rank来源
100project-dsh<projectRoot>/.dsh/skills
200project-agents<projectRoot>/.agents/skills
300customConfig.customSkillDirs
400user-dsh<dshHome>/skills(跳过 .system 子目录)
500user-agents<agentsHome>/skills
600bundled配置时使用 Config.bundledSkillDir

dshHome 解析为 $DSH_HOME~/.dshagentsHome 解析为 $DSH_AGENTS_HOME~/.agents。Chokidar 监视现有根目录的扁平/包条目新增、移除与直接条目变更;项目级监视器使用有界 LRU(watchMaxProjects,默认 128)。面向模型的 write/edit 观察会同步使提供方失效,而 host 监视器则覆盖 IDE/Git/外部变更。监视失败会把观察标记为不完整,但不会对直接加载隐藏可读候选。

skill-badge

dsh-skill-badge 是一个提供方,而非带行为的注册表插件——它注册一个固定不变的、名为 dsh-badgebundled 候选(为文档、PR/MR 描述及其他产出内容添加官方 "powered by dsh" 署名徽章),rank 为 BUNDLED_SKILL_RANK = 600,并把打包的 assets/ 目录作为 resourceBase 暴露。随附 CLI 声明该插件禁用,因此启用其组合行是显式 opt-in。get()../assets/dsh-badge.md 读取正文。

会话目录与 skill 工具

dsh-tool-skill 在存活会话首次 agent/pre-step 观察到非空、完整视图时,把初始目录作为一条持久的 <system-reminder> 用户消息注入:

text
<system-reminder>
A skill is a reusable set of task-specific instructions. The following skills are available in this session:

<available_skills>
- `skill-name`: description
</available_skills>

If the user names a skill ... call the `skill` tool with the exact skill name before taking task actions...
</system-reminder>

这些目录是会话历史,而非 World State——它们以 source.kind === 'skill-catalog' 消息进入(一条 catalog 形式的消息,同时记录其精确 entries 供非模型消费方使用)。在后续每一步之前,插件对渲染条目做摘要(对每个 [name, description] 的 JSON 做 SHA-256);摘要变化时向该 step 的 enter 决定追加一条全量替换目录(另一条 skill-catalog 消息),删除全部技能则追加一条显式空替换。目录只携带模型可调用的 name + 限长的 description——绝无正文、路径或提供方。这部分就是喂给系统提示组装的地方:技能作为可调用指令与系统提示并列浮现,而非混入其中。

面向模型的 skill 工具name: 'skill',一个参数 name)校验 kebab-case 名称,在与调用无关的目录中查找,并在 isModelInvocable(skill) 不允许时加以拒绝。随后针对调用智能体的 cwd 重读当前定义并返回:

json
{ "name": "...", "provider": "...",
  "resourceBase": {"kind": "directory", "path": "..."},
  "content": "完整的 markdown 正文" }

每次调用都会加载——注册表从不缓存定义,因此仅正文的修改会改变之后的调用,而不会产生目录消息。resourceBase 只按需解析相对脚本/资源。还有第二个入口点:文本任意位置出现一个以空白为界的 /<skill-name> token 的用户消息(匹配用户可调用技能)是一个确定性的加载手势——正文在所有其他注入之后、最接近答案处注入。目录与 skill 工具永远看不到 disable-model-invocation 技能;用户手势是它们的唯一路径。

配置摘要

默认 / 说明
dsh-skillcollectCacheMaxEntries缓存的已完成的 cwd/提供方目录最大数
dsh-skill-filesystemproviderNameincludeDefaultRootsdshHomeagentsHomecustomSkillDirs提供方名默认 filesystem;默认开启根目录
dsh-skill-filesystemwatch*watchUsePollingwatchStabilityThresholdMs (200)、watchPollIntervalMs (100)、watchMaxProjects (128)、watchFollowSymlinks监视器调优
dsh-skill-filesystembundledSkillDir默认 $DSH_BUNDLED_SKILL_DIR
dsh-skill-badge(无——固定候选)随附 CLI 中禁用
dsh-tool-skillcatalogDescriptionMaxLength默认 500,最小 3

SkillConfig 包(由配置目录转发而来)把 enabledregistryfilesystemtool 子配置合并到一个开关里。

UI:packages/client/ui-skill

该 client 包是纯 UI 插件——其 Node apply() 为空;浏览器端经包的 exports["./client"] 输送。SkillRow.tsx 在 toolview 插槽上注册一条紧凑的转录行:它仅从持久化的调用切片(绝不查询实时目录)派生显示状态(running/ok/error/stopped),从调用参数取技能名,并把确切加载的输出留在有界展开卡中。本地化字符串位于 skill 命名空间下(client/locales.ts)。

延伸阅读

  • 系统提示组装——目录与 reminder 如何编入模型上下文
  • 智能体循环与作用域——agent/pre-step 与分层注册表
  • 工具注册表与执行流水线——ctx.tools 定义与 post-execute 钩子如何运行
  • docs/subsystems/skills.md——官方子系统参考与完整 API
  • packages/skill/skill/src/index.ts——SkillRegistry 服务定义
  • packages/skill/tool-skill/src/index.ts——目录注入与 skill 工具