dshkit

把 Claude Code 的 skill 移植到 DeepSeek Harness(其实不用改)

dsh 读的是和 Claude Code 一样的 SKILL.md 格式。目录复制进 ~/.agents/skills 就会被发现——已对运行中的实例实测。frontmatter 契约、五个发现根、以及怎么确认它真的加载了。

更新于 2026-08-143 min
一句话结论

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-invocationtrue 则从模型目录中移除
user-invocablefalse 则从人机命令中移除

所以「能不能移植」这个问题,简化成了:你的 skill 有没有 kebab-case 的 name 和一个 description 有,就能跑。

五个发现根

按 rank 顺序解析,同名时最近的作用域胜出

Rank来源路径
100project-dsh<项目根>/.dsh/skills
200project-agents<项目根>/.agents/skills
300custom你在 customSkillDirs 里配的
400user-dsh$DSH_HOME/skills(默认 ~/.dsh/skills
500user-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,先拍平。

referencesscriptsassets 及其它 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 是被刻意排除的。

接着看