Claude Code 的 skill 一个字都不用改。dsh 解析同一种 SKILL.md 格式——必填 frontmatter 只有 name(kebab-case)和 description——并且把 ~/.agents/skills 当作跨工具共享根来扫描。把 skill 目录复制过去、启动 dsh,它就出现在目录里。version、metadata 这类它不认识的键会被忽略而不是报错。
DeepSeek Harness 的 skill 系统真正有意思的地方,不是它存在,而是它读的是
和 Claude Code 一模一样的 SKILL.md 格式,并且会去扫描一个明确以「跨工具共享」命名的目录。
我们没有靠推测——实测了一遍:一个 Claude Code 的 skill 原样复制过去,首次启动就被发现了。
实测过程
被测对象是 docx-builder——一个为 Claude Code 写的 Word 文档生成 skill,frontmatter 里还带着
ClawHub 专有字段。
mkdir -p ~/.agents/skills
cp -R ~/.claude/skills/docx-builder ~/.agents/skills/docx-builder
dsh web全部步骤就这些。SKILL.md 一个字没改。
它的 frontmatter 里有一堆 dsh 从没听说过的字段:
---
name: docx-builder
description: 一套基于 docx npm 库的 Word 文档排版样式系统…
version: 1.0.1
user-invocable: true
metadata:
openclaw:
requires:
bins: [node, npm]
emoji: "📄"
---version 和整棵 metadata.openclaw 树对 dsh 都是陌生的。它忽略掉继续跑。
查运行中的实例:
{
"skills": [
{
"name": "docx-builder",
"description": "一套基于 docx npm 库的 Word 文档排版样式系统…",
"modelInvocable": true
}
]
}关键是 modelInvocable: true——模型可以自己决定去用它,不只是人工显式调用。
frontmatter 契约
frontmatter 被当作开放的 YAML 对象解析。dsh 只解释六个键,其余全部忽略:
| 键 | 必填 | 含义 |
|---|---|---|
name | 是 | 必须是 kebab-case |
description | 是 | 展示在面向模型的目录里 |
whenToUse | 否 | 路由提示 |
metadata | 否 | 自由字段 |
disable-model-invocation | 否 | true 则从模型目录中移除 |
user-invocable | 否 | false 则从人机命令中移除 |
所以「能不能移植」这个问题,简化成了:你的 skill 有没有 kebab-case 的 name 和一个
description? 有,就能跑。
五个发现根
按 rank 顺序解析,同名时最近的作用域胜出:
| Rank | 来源 | 路径 |
|---|---|---|
| 100 | project-dsh | <项目根>/.dsh/skills |
| 200 | project-agents | <项目根>/.agents/skills |
| 300 | custom | 你在 customSkillDirs 里配的 |
| 400 | user-dsh | $DSH_HOME/skills(默认 ~/.dsh/skills) |
| 500 | user-agents | $DSH_AGENTS_HOME/skills(默认 ~/.agents/skills) |
五个里有两个是 .agents 路径,不是 .dsh 路径。这个信号值得读懂:
harness 是刻意去扫一个共享的 agent 配置根,而不是把 skill 当成自己的独家格式。
<项目根> 是最近的含 .git 的祖先目录;没有的话就用当前 cwd。
目录规则
skill 要么是目录 bundle,要么是单个扁平文件:
~/.agents/skills/
docx-builder/
SKILL.md ← skill 本体
references/ ← 资源,不影响目录
examples/
quick-note.md ← 扁平文件形式,同样有效
发现只有一层深。 只认 <root>/<名字>/SKILL.md 和 <root>/<名字>.md——嵌套的
**/SKILL.md 是被刻意排除的。如果你的 skill 是 bundle 套 bundle,先拍平。
references、scripts、assets 及其它 bundle 资源下面的改动不会让目录失效,
所以改一个引用文件不会触发无谓的重扫。
怎么确认它加载了
两种办法。
网页端在选好 workspace 之后会列出 skill。
RPC 更快、也可脚本化。dsh 在 POST /api/<method> 上暴露了一个类 JSON-RPC 的网关:
curl -s -X POST http://127.0.0.1:3080/api/skill.list \
-H 'content-type: application/json' \
-d '{"type":"client-request","rpcId":"'"$(uuidgen)"'","method":"skill.list",
"payload":{"sessionId":"<你的会话 id>"}}'skill.list 必须带 sessionId——skill 是按会话解析的,不是全局的。先用
session.create(接受一个 cwd)建一个,这也正是项目级根得以解析的方式。
如果你已经有一批 skill
如果你为 Claude Code 写过 skill,你已经拥有 dsh 的 skill 了。迁移成本是一条 cp -R,
而且同一个文件在两边都继续能用——不用 fork,不用双份维护。
这是进入这个生态远比写插件便宜的路。插件意味着 TypeScript、Cordis seam、peerDependencies
纪律、发 npm——具体要做什么见写你的第一个插件。
而 skill 只是一个你早就写好的 markdown 文件。
已知限制
项目自己声明的,批量移植之前值得知道:
- 只有一层深 —— 嵌套 skill 树和包清单会被忽略。
- 项目作用域是最近的
.git—— 没有备选的项目根标记,也不支持 monorepo 子项目选择。 - 畸形条目带一条警告后消失 —— 模型目录拿不到逐个 skill 的诊断信息,所以从模型的角度看, 「无效的 skill」和「不存在的 skill」长得一模一样。
- 没有正文版本协议 —— 已加载的正文就是普通的保留历史。之后的编辑影响之后的调用, 但既不会重写旧结果,也不会通知谁。
常见问题
Claude Code 的 skill 需要为 dsh 重写吗?
不需要。我们原样复制了一个过去,首次启动就被发现了。dsh 只解释 name、description、whenToUse、metadata、disable-model-invocation、user-invocable 这几个键,其余 frontmatter 一律忽略。
skill 放在哪?
五个根,按 rank 顺序:<项目根>/.dsh/skills、<项目根>/.agents/skills、你配置的 customSkillDirs、$DSH_HOME/skills、以及 $DSH_AGENTS_HOME/skills(默认 ~/.agents/skills)。
为什么我的 skill 悄无声息地消失了?
调用策略是 fail closed。disable-model-invocation 或 user-invocable 写成驼峰、或者值不是布尔,会让整个 skill 从发现中掉队,只留一条警告——它不会退回宽松默认值。
支持嵌套 skill 吗?
不支持。发现只有一层深,只认 <root>/<名字>/SKILL.md 和 <root>/<名字>.md,嵌套的 **/SKILL.md 是被刻意排除的。