A Claude Code skill needs no changes. dsh parses the same SKILL.md format — required frontmatter is name (kebab-case) and description — and scans ~/.agents/skills as a shared cross-tool root. Copy the skill directory there, boot dsh, and it appears in the catalog. Unknown frontmatter keys such as version or metadata are ignored rather than rejected.
The interesting thing about DeepSeek Harness's skill system is not that it exists. It is that it
reads the same SKILL.md format Claude Code does, and scans a root explicitly named for sharing
between tools.
We tested it rather than assuming. A Claude Code skill, copied across unchanged, was discovered on first boot.
The test
The skill was docx-builder — a Word-document authoring skill written for Claude Code, with
ClawHub-specific frontmatter in it.
mkdir -p ~/.agents/skills
cp -R ~/.claude/skills/docx-builder ~/.agents/skills/docx-builder
dsh webThat is the whole procedure. No edits to SKILL.md.
Its frontmatter carries fields dsh has never heard of:
---
name: docx-builder
description: 一套基于 docx npm 库的 Word 文档排版样式系统…
version: 1.0.1
user-invocable: true
metadata:
openclaw:
requires:
bins: [node, npm]
emoji: "📄"
---version and the whole metadata.openclaw tree are foreign to dsh. It ignored them and kept going.
Querying the running instance:
{
"skills": [
{
"name": "docx-builder",
"description": "一套基于 docx npm 库的 Word 文档排版样式系统…",
"modelInvocable": true
}
]
}modelInvocable: true is the part that matters — the model can reach for it on its own, not just a
human invoking it explicitly.
The frontmatter contract
Frontmatter is parsed as an open YAML object. dsh interprets six keys and ignores the rest:
| Key | Required | Meaning |
|---|---|---|
name | yes | Must be kebab-case |
description | yes | Shown in the model-facing catalog |
whenToUse | no | Routing hint |
metadata | no | Free-form |
disable-model-invocation | no | true removes it from model catalogs |
user-invocable | no | false removes it from human-facing commands |
So the portability question reduces to: does your skill have a kebab-case name and a
description? If yes, it runs.
The five discovery roots
Resolved in this rank order — nearest scope wins a duplicate name:
| Rank | Source | Path |
|---|---|---|
| 100 | project-dsh | <projectRoot>/.dsh/skills |
| 200 | project-agents | <projectRoot>/.agents/skills |
| 300 | custom | whatever you put in customSkillDirs |
| 400 | user-dsh | $DSH_HOME/skills (default ~/.dsh/skills) |
| 500 | user-agents | $DSH_AGENTS_HOME/skills (default ~/.agents/skills) |
Two of those five are .agents paths, not .dsh paths. That is the signal worth reading: the
harness is deliberately scanning a shared agent config root rather than claiming skills as its
own format.
<projectRoot> is the nearest ancestor containing .git; without one, the current cwd is used.
Layout rules
Skills are either a directory bundle or a flat file:
~/.agents/skills/
docx-builder/
SKILL.md ← the skill
references/ ← resources, do not affect the catalog
examples/
quick-note.md ← flat file form, also valid
Discovery is one level deep. Only <root>/<name>/SKILL.md and <root>/<name>.md are
recognised — nested **/SKILL.md is deliberately excluded. If your skill nests bundles inside
bundles, flatten it.
Changes below references, scripts, assets and other bundle resources do not invalidate the
catalog, so editing a reference file will not trigger a needless re-scan.
Verifying it loaded
Two ways.
The web UI lists skills once a workspace is selected.
The RPC, which is faster and scriptable. dsh exposes a JSON-RPC-ish gateway at
POST /api/<method>:
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":"<your-session-id>"}}'skill.list requires a sessionId — skills resolve per session, not globally. Create one first
with session.create (it takes a cwd), which is also how the project-scoped roots get resolved.
What this means if you have a skill library
If you have built skills for Claude Code, you already have dsh skills. The migration cost is a
cp -R, and the same file keeps working in both tools — there is no fork, no dual-maintenance.
That is a much cheaper way into this ecosystem than writing plugins. A plugin means TypeScript, a
Cordis seam, peerDependencies discipline and an npm release — see
write your first plugin for what that involves. A skill means a
markdown file you already wrote.
Known limits
Stated by the project itself, worth knowing before you port in bulk:
- One level deep — nested skill trees and package manifests are ignored.
- Project scope is the nearest
.git— no alternate project-root marker, no monorepo subproject selection. - Malformed entries disappear with a warning — the model catalog gets no per-skill diagnostic, so an invalid skill and an absent one look identical from the model's side.
- No body revision protocol — a loaded body is ordinary retained history. Later edits affect later calls but do not rewrite or announce anything.
Frequently asked
Do I have to rewrite a Claude Code skill for dsh?
No. We copied one across unchanged and it was discovered on first boot. dsh interprets name, description, whenToUse, metadata, disable-model-invocation and user-invocable; any other frontmatter key is ignored.
Where do skills go?
Five roots, in rank order: <projectRoot>/.dsh/skills, <projectRoot>/.agents/skills, any customSkillDirs you configure, $DSH_HOME/skills, and $DSH_AGENTS_HOME/skills — which defaults to ~/.agents/skills.
Why does my skill silently disappear?
Invocation policy fails closed. A camel-case spelling of disable-model-invocation or user-invocable, or a non-boolean value, drops the entire skill from discovery with a warning rather than ignoring just that field.
Are nested skills supported?
No. Discovery is one level deep — only <root>/<name>/SKILL.md and <root>/<name>.md. Nested **/SKILL.md is deliberately excluded.