DeepSeek Harness 技能怎么手动装:SKILL.md 四个失效点

DeepSeek Harness 的技能可以自己装,五分钟够用:把带 SKILL.md 的目录放进被扫描的位置,新开会话就能调用。真正费时间的是两种失败,一是目录多套了一层,二是名字不合规,这两种情况客户端都不会给你明确提示,技能就是不出现。

本文核对的是桌面端 0.2.1-rc.5 的实际行为,命令都能直接粘。

五个扫描根,先放对地方

技能在磁盘上是普通文件,没有数据库,也没有导入步骤。客户端起会话时按固定顺序扫几层目录,把每个技能的 YAML 头部解析成一条目录项,模型用到时才读正文。所以装技能等于把目录摆到正确的路径上。

rank 扫描目录 生效范围 适合放什么
100 <项目根>/.dsh/skills 只在当前项目 要跟仓库一起提交、只服务这个项目的技能
200 <项目根>/.agents/skills 当前项目,跨 agent 工具共用 团队里还有别的 agent 客户端在同一个仓库干活
300 customSkillDirs 配置的目录 自定义 技能放在另一块盘或共享目录
400 ~/.dsh/skills 所有会话 自己长期用的通用技能,默认从这里起步
500 ~/.agents/skills 跨工具的用户级目录 同一批技能还要喂给别的 agent 客户端

rank 的数字越小越先被扫到,两个位置出现同名技能时靠前的那个生效。所以项目里放一份同名技能,能盖住用户目录里的旧版本,这个特性用来做单项目覆盖很方便。

项目根并不等于终端当前所在的目录。正确算法是从工作目录往上找最近的、带 .git 的那个祖先目录,一个都没有时才退回当前目录。这条最容易判断错:你在某个子目录里敲命令,但上一级才是仓库根,技能就得放到仓库根下的 .dsh/skills。

# 用户级技能根,默认就在这里
ls -la ~/.dsh/skills

# 确认仓库根,也就是项目根
cd 你的项目目录 && git rev-parse --show-toplevel

用户级目录固定是 $DSH_HOME 下的 skills,$DSH_HOME 默认等于 ~/.dsh。这个目录下面的 .system 子目录会被跳过,那是客户端放内置技能的位置,手写技能别往里塞,塞了也不会被当成你的技能。

最小可用的一个技能

目录形式是 名字/SKILL.md,中间不能再夹一层,正文以外可以再带 references、scripts、assets 等资源目录。下面这个技能不到二十行,是能跑的最小形态。

mkdir -p ~/.dsh/skills/fb-draft-check
cat > ~/.dsh/skills/fb-draft-check/SKILL.md <<'EOF'
---
name: fb-draft-check
description: 验收一篇稿件是否达标:汉字数、h2 数量、站内内链数。当需要判断稿件能不能发、或想知道差哪几项时使用。
---

# fb-draft-check

## 步骤

1. 取正文 HTML,剥掉标签后统计汉字数。
2. 统计 h2 数量与指向本站的内链数量。
3. 任意一项不达标,直接报出实际数字与差值。
EOF

头部只有 name 和 description 是必填。缺任何一个字段,或者 YAML 本身写坏(制表符缩进、冒号后面没空格、引号没闭合),整个文件被跳过,会话目录里不会留下任何痕迹。

头部字段哪些真被读

解析器认的字段不多,其余键不会报错也不会生效。真正的差别在于写错之后是整条丢弃,还是只影响一半功能。

字段 必填 取值规则 写错的后果
name 是 小写字母与数字,用连字符分段 整条技能被丢弃
description 是 一句话写清能力与触发场景 缺失则整条被丢弃,过长则在目录里被截断
whenToUse 否 字符串 无影响,属于补充说明
disable-model-invocation 否 严格布尔值 值不合法则整条被丢弃
user-invocable 否 严格布尔值 值不合法则整条被丢弃
metadata 否 自定义键值对 原样带着,客户端不解析

name 的规则很硬,等价于这条正则:^[a-z0-9]+(-[a-z0-9]+)*$。大写字母、下划线、点、中文、连续两个连字符、首尾连字符,全部不合法。合法的只有 my-skill、fb2-check 这种形态。

布尔字段同样是硬规则,只认 true 与 false、yes 与 no、on 与 off、1 与 0,大小写不敏感。写成 sometimes、maybe、空值,解析直接抛错,整条技能被丢掉。

平铺单文件与软链接

扫描根直属的一层里,除了目录包,也认单独的 名字.md 文件。技能只有一个文件、不带脚本和参考资料时,平铺最省事,省掉一层目录。

# 平铺装法,文件名就是技能名
cp my-skill.md ~/.dsh/skills/my-skill.md

软链接也认,符号链接的目录和文件都能正常打开,技能里记录的仍是链接路径。想把技能留在代码仓库里维护、又不想每次复制两份,直接链过去就行。

ln -s ~/code/my-skills/fb-draft-check ~/.dsh/skills/fb-draft-check

资源子目录怎么用

正文之外的资源放在技能目录里,正文用相对路径引用。模型加载技能时会同时拿到这个技能的资源基路径,脚本和参考文档按需读,不用一次全塞进上下文。

长文档型技能尤其要这么拆:把大表格、完整参数表、几十个报错样例放进 references/ 下的单个文件,正文只留触发条件、判断顺序和执行步骤。加载成本能降一个量级,触发准确率反而更高,因为描述里只留了场景,没有堆背景。

刷新粒度这里有个差别要记住。SKILL.md 正文每次加载都重新读文件,改完立刻是新的;references、scripts、assets 下的改动不会触发技能目录刷新,但同样是按需读取,所以也不用重启。

踩坑一:多套一层目录等于没装

扫描只到每个根的第一层,形如 skills/写作/长文/SKILL.md 的嵌套结构是故意不发现的。很多人按分类先建一层子目录,把写作、编程、运营各放一个文件夹,结果整批技能一起消失,看上去像客户端坏了。

判断方法只有一个,看实际深度。正确形态是 ~/.dsh/skills/名字/SKILL.md,两级;出现三级就是放错了,把中间那层分类目录去掉,分类交给描述里的关键词解决。

# 只应该列出两级路径
find ~/.dsh/skills -maxdepth 2 -name SKILL.md

如果确实需要分类,用文件名前缀代替目录层级,比如 fb-draft-check、fb-report-scan,排序时自然聚在一起。

踩坑二:名字带大写或中文,静默消失

名字不合法不会弹窗,客户端只在自己的日志里留一行忽略记录,模型侧的技能目录里既没有这条技能,也没有任何诊断信息。你从会话界面根本区分不出「没装」和「装错了」,这是排查时最浪费时间的一点。

所以先自检,再怀疑别的。下面这条命令用的是同一套规则,返回 ok 才说明名字能过。

python3 -c "import re,sys;n=sys.argv[1];print('ok' if re.fullmatch(r'[a-z0-9]+(-[a-z0-9]+)*',n) else 'invalid')" fb-draft-check

还要注意目录名与头部 name 的落差。目录名随便叫,发现时看的是头部 name,但用起来容易混淆,建议两者写一致,改的时候一起改。

踩坑三:布尔字段写错,连带整条被丢

disable-model-invocation 与 user-invocable 决定技能出现在哪一侧,两个都不写时两侧全开,这是默认行为,绝大多数技能也就该这么写。写反或者都关掉,技能等于装了用不上。

写法 模型能否加载 斜杠菜单能否调用
两个字段都不写 能 能
disable-model-invocation: true 不能 能
user-invocable: false 能 不能
两个都限制 不能 不能

第一行之外的三行都有明确用途:要人工兜底的分步流程关掉模型侧,避免模型自己乱调;纯内部约定、不想出现在斜杠菜单里的关掉人工侧,减少干扰。写之前想清楚这一条技能是给自己敲还是给模型挑。

两个长度上限

目录里给模型看的描述默认最多 500 个字符,这个值可以配,下限是 3。描述写成一整段背景介绍,模型看到的只是前面 500 字,触发条件被切掉,技能就调不动了。写法上把「什么时候用」放在第一句,能力说明放后面。

正文侧的上限是 8192 字符。超过这个长度,加载结果会被工具结果的裁剪机制处理,后半段可能看不到。手写长技能时,正文留判断逻辑和步骤,细节拆进 references/,这条规矩对五十行以上的技能都成立。

用命令行验证要过的第一道门

桌面端自带命令行,dsh tui 起终端界面,dsh web 起网页界面,dsh headless 跑单轮任务后退出。想不开图形界面确认技能是否被加载,最省事的做法是跑一次 headless,让它只回一句话。

实测第一次跑会直接停在凭据上,报错原文是这样的:

MISSING_CREDENTIAL: llm-deepseek: no API key for provider route "deepseek-official"

原因很直接,命令行不继承图形界面里已经登录的凭据,得显式给一把 key。两条路,导出环境变量,或者先在网页版 Models 页把这把 key 存进凭据服务。导出之后重跑同一条命令即可。

export DEEPSEEK_API_KEY=你的key
dsh headless "只输出两个字:好的"

这一步过了,才轮得到验证技能:把技能写进提示词,观察它是否按 SKILL.md 里的步骤执行。桌面端怎么装、四种会话模式分别干什么,写在桌面端安装教程里,这里不再重复。

改完怎么生效

技能目录靠文件监听维护,监听深度只到每个根的第一层。会触发目录刷新的改动有四种:新增或删除目录包、新增或删除平铺的 md 文件、新增或删除或修改 SKILL.md。资源子目录里的改动不触发刷新,因为它们本来就在加载时按需读。

正文改动不需要任何重启,下一次加载就是新内容,这点比插件宽松得多。插件要靠 profile 里的组合层生效,改动得走 dsh plugin 那条路径,具体分级见插件四档分级。

有一处容易误判:命令行的斜杠菜单会缓存目录。磁盘上增删技能之后,已经打开的会话会继续列出旧的技能表,直到切换一次预设、执行一次插件管理操作或者重置连接。遇到「装了不显示」,先切换预设再下结论,别急着改文件。

常见问题

技能放用户目录还是项目目录?

自己攒的通用技能放 ~/.dsh/skills,装一次所有会话都能用,维护成本最低。只在某个仓库生效、并且需要跟代码一起提交的放 <项目根>/.dsh/skills。两处同名时项目目录优先,所以覆盖单项目行为不用改用户目录里的原件。

装了没反应,按什么顺序排查?

四步就够。先用 find 确认真实深度是两级,再自检 name 是否全小写连字符,然后看两个布尔字段有没有把需要的那一侧关掉,最后切换一次预设清掉斜杠菜单缓存。四步都过还不行,才需要怀疑 YAML 语法,检查缩进是不是用了制表符。

技能和插件是一回事吗?

两回事,装法也完全不同。技能只是一份按需加载的说明文档,放进目录就被发现,不涉及任何代码执行。插件是装进 profile 的机制扩展,能挂工具、中间件和界面插槽,要用 dsh plugin 命令安装并重启 profile。想给模型加一套固定流程用技能,想改客户端自身行为才需要插件。

站内关于本体的其他实测还有接本地 Ollama 的三个坑,版本变化与安装方式看桌面版 0.2 发布说明。