1.2 装到哪个目录:六层优先级与选择决策

六层发现优先级

DeepSeek Harness 的本地技能提供方按 rank 顺序扫描若干根目录。官方给出的完整表如下,rank 数字越小越优先:

Rank 来源标识 根目录 典型用途
100 project-dsh <projectRoot>/.dsh/skills 只属于这个项目的技能
200 project-agents <projectRoot>/.agents/skills 项目级、跨 agent 工具通用的技能
300 custom Config.customSkillDirs 部署方指定的自定义目录
400 user-dsh <dshHome>/skills 你个人的全局技能
500 user-agents <agentsHome>/skills 个人级、跨工具通用技能
600 bundled Config.bundledSkillDir 随包分发的技能

两点说明。其一,bundled 层只有在配置了 Config.bundledSkillDir 时才参与扫描。其二,SkillSource 的完整取值里还有一个 runtime,它与本地文件扫描的六个根不是一回事,不会以上表任何一行的形式出现。

“层”到底是什么

优先级表里的 rank 只是决胜规则之一。官方的结构是“宿主 + 按 scope 的分层”:

  • 宿主行与 repository 插件落入全局层;
  • 由 agent preset 常驻组合挂载的插件落入该 preset 的层;
  • 提供方名称在每层内唯一,而非进程级唯一。

读取时把全局层与所观察 scope 的链合并。所以“层”回答的是“这次读取站在哪个 scope 上看”,rank 回答的是“同一层内谁先谁后”。两件事别混:换 scope 换的是层,同层之内才轮到 rank 说话。

项目根目录怎么定

这里的“项目根目录”有明确定义:包含 .git 的最近祖先目录,找不到时退化为当前工作目录。技能的作用域跟着 git 仓库边界走,这在多仓库工作区里非常重要。

细节上还有三条:

其一,git 根目录是向上探测的。 当 ctx.fs 可用时,查找 .git 的动作通过文件系统服务完成,因此远程或沙箱工作区不会回退到宿主机文件系统边界。你在容器里打开一个仓库,技能解析依然以仓库根为准。

其二,用户级 DSH 根目录会跳过 .system 子目录。 这是平台的保留区域,不要往里放自己的东西。

其三,本地提供方不会合成内置系统 skill。 想随包分发技能,部署方应通过 bundled 根目录或专用提供方提供,而不是指望本地目录里凭空多出系统技能。

举个多仓库工作区的例子:你在 platform/apps/web 子目录里打开 harness,而 .git 在 platform/ 根上,那么项目级技能根就是 platform/.dsh/skills,不是 platform/apps/web/.dsh/skills。想只在单个子应用范围内生效,得换能被识别的位置或走 custom 层——这是“作用域跟着 git 边界走”的直接后果。

怎么选:一张决策表

面对一个新技能,按这个顺序问自己:

你的情况 放哪 理由
这技能只对当前项目有意义 <projectRoot>/.dsh/skills 随仓库走,clone 即得
团队共享,且用多种 agent 工具 <projectRoot>/.agents/skills 跨工具通用层
我想让所有项目都能用 <dshHome>/skills 个人全局
我用多个 agent 工具,想共用一套 <agentsHome>/skills 个人跨工具层
我是部署方,要统一下发 customSkillDirs 集中管控

提醒:把技能提交进项目仓库前,确认里面没有公司敏感信息。skill 是纯文本,git log 会记住你删掉的一切。

两个项目层、两个用户层,差别在哪

同样是本地四层,分两对:

层 归属 作用范围
.dsh/skills DeepSeek Harness 专属约定 只在 harness 工作流里生效
.agents/skills 跨 agent 工具通用约定 同作用域内共享给多种工具

判断标准很简单:这技能有没有用到 harness 特有的能力。用到了,放 .dsh 层,放进 .agents 层反而可能让别的工具误解它的适用范围;没用到、就是一套通用工作方法,放 .agents 层,让团队里用不同工具的人都能受益。项目对、用户对,再按作用域选。

custom 与 bundled:部署方的两个抓手

最后两层不是给个人日常用的,是给部署方用的:

  • custom(rank 300)通过 Config.customSkillDirs 把任意目录拉进扫描范围,适合企业内网统一维护一套技能库,各团队指向同一份目录;
  • bundled(rank 600)通过 Config.bundledSkillDir 提供随包分发的技能,优先级最低,是官方技能族(如 Office 族)进入目录的正规入口。

普通使用者一般不用碰这两层,但要知道它们存在——公司统一下发的技能突然出现或消失时,先问部署方改了哪一层。

重名了怎么办:两层裁决规则

多目录共存,重名几乎必然发生。规则分两层,配三个实例。

跨层:近者胜。 读取时把全局层与观察 scope 的链合并,最近层的条目直接赢得重名 skill。项目级(rank 100)无条件覆盖用户级(rank 400)。

实例一:
~/.dsh/skills/code-review/      (rank 400,用户全局)
./.dsh/skills/code-review/      (rank 100,项目级)

→ 在仓库内工作:项目级胜出
→ 在仓库外工作:用户级生效

同层内:依次比三样。 rank → 提供方顺序 → 本地顺序;摘要按名称排序。

实例二(同层比 rank):
全局层内同时存在 project-dsh 与 project-agents 两个来源的同名技能
→ rank 100 的 project-dsh 赢,rank 200 的 project-agents 让位

实例三(rank 相同,比提供方顺序):
同一层内两个提供方给出同名技能时(一般来自部署方把多个目录配进同一来源桶)
→ 按提供方注册顺序决胜;同一提供方内部再按本地扫描顺序决胜

这个设计非常实用:你可以在用户级放一份“通用代码评审技能”,再在某个严格项目里放一份“本公司代码评审技能”覆盖它,互不干扰,且不需要任何条件判断语法。

变更会实时生效吗

会。本地提供方通过 Chokidar 监视根目录,覆盖三类变化:

监听对象 变化类型
直属 bundle 与平铺条目 添加、移除
直属 skill 条目 内容变更
面向模型的 write 与 edit 观测 目标路径与目录相关时,同步让提供方目录失效

注意“直属”二字的含金量:bundle 下的资源文件变更不属于目录变更。改 references/checklist.md 不会触发技能目录失效——它本来就在按需加载的路径上,不需要刷新目录。

对缺失的根目录,watcher 会从最近的现有祖先开始,逐个跟踪缺失的路径段,直至 Chokidar 可以附加。所以 .dsh/skills 还不存在时先把它建好,watcher 才能尽早盯上。

watcher 自身失败只会让当前观测不完整,但不会把本来可读的技能藏起来;项目作用域 watcher 使用按配置设限的 LRU。绝大多数情况下,改完 SKILL.md 保存,下一次读取就能看到新内容,不需要重启。

skills/change 事件:不带 diff

提供方和运行时变更会发出不带过滤条件的 skills/change 失效事件。两个要点:

  • 事件不携带 diff——它只广播“有变化”,不说变了什么;
  • 消费方因此要用自身的查找选项重新获取 snapshot(),而不是试图从事件里推增量。

这对写代码的人重要,对使用者只意味一件事:变更通知是广播式的,任何监听方都会重新拉取,你手动改的文件不会因为“事件被过滤掉”而看不见。

另外,发现缓存以解析后的 scope 链为键:重设 scope 父级(如空会话重组)无需注册表变更,即可被下一次读取看到——换 scope 不用重启。

一个容易踩的坑

嵌套递归的 **/SKILL.md 发现不受支持。 技能目录下再套一层含 SKILL.md 的子目录,内层那个不会被发现。

skills/
├── outer-skill/
│   ├── SKILL.md          # ✅ 会被发现
│   └── inner/
│       └── SKILL.md      # ❌ 不会被发现

需要多个技能就平铺多个目录,不要嵌套。

小结

  • 六层优先级:项目 .dsh → 项目 .agents → custom → 用户 .dsh → 用户 .agents → bundled。
  • 项目根 = 最近的含 .git 祖先目录;远程与沙箱工作区不回退宿主边界。
  • .system 是保留区;本地提供方不合成内置系统技能。
  • 跨层重名近者赢,同层比 rank → 提供方顺序 → 本地顺序。
  • 监听只看直属变化;skills/change 不带 diff,消费方重新 snapshot()。
  • 不支持 **/SKILL.md 递归发现,技能要平铺。

延伸阅读