DeepSeek Harness 学习:Skill 加载全流程

你有没有遇到过这种情况:在本地配了个技能(skill),信心满满让模型用,结果它要么压根不调、要么调到的根本不是你以为的那一版?

问题往往不在你写错了什么,而在「技能到底怎么被框架读进来」,它和工具不一样:工具登记完就全局可见,技能却不是。

技能是更大块的指令集(一段 Markdown 流程 + 配套资源),而且来源不止一个:本地目录、远程注册表、运行时贡献都可能是它的来处。同名听谁的、远程改了为何本地没反应、模型为什么调不到,这 3 个最常踩的坑,根源都在这套加载机制里。

于是框架要解的其实是一道题:怎么把多个来源的技能,并成一份不冲突、可分层、带调用策略的清单。

这篇文章顺着框架处理技能的链路往下拆:先看技能都从哪来(本地目录、远程注册表、运行时贡献都能提供);再看框架怎么把来自多处的技能并成一份清单、同名时听谁的;最后说框架怎么决定「模型能不能自己调这个技能」「用户能不能手动触发它」。一层一层讲清楚。

一、提供方只管「从哪来」,注册表只管「怎么合」

框架把职责切得很干净。每个**提供方(provider)**就是技能的一个来源(本地目录、远程注册表、运行时贡献都算),它只回答两个问题:

  • 发现(list):我能提供哪些技能? 提供方扫一遍自己的地盘,把找到的技能报成一份「候选」列表。候选只是轻量目录条目:名字、描述、来源、能否给模型或用户调用,以及决定胜负的 rank------它还不包含正文。返回时要么给完整列表,要么带一个「这次发现是否完整」的标记:远程来源可能半路超时、或正赶上刷新,这时提供方会说「我这次没看全」,注册表就不会把它当最终答案缓存。
  • 取正文(get):把胜出候选的指令正文取回来。 候选里只挂着一个只有提供方看得懂的 locator(本地目录里它指向 SKILL.md,运行时技能则直接内联)。真正用到时,注册表才拿着候选回找提供方,由它读出正文、剥掉 frontmatter(文件顶部 --- 之间的 YAML 配置块),变成一段模型可读的 Markdown 指令。正文就是 SKILL.md 里 frontmatter 以外的那段指令。

而**注册表(registry)**完全不管来源,只做三件事:合并、胜出解析、校验。所有提供方在它眼里平等,它只关心这些候选怎么拼成一份不打架的视图。同名技能最终谁留下,规则在第三节细讲。

换句话说:提供方是「情报员」,负责把技能找出来、把正文递上来;注册表是「编辑部」,负责把多方情报编成一份不打架的清单。

二、分层注册:你用的 Agent 预设决定落在哪一层

先厘清两个词,后面才好懂:作用域 是位置,层是挂在这个位置上的技能箱。

  • 作用域(scope):agent 在嵌套层级里的位置。一个 agent 往往不是孤立的,它上面还套着更外层的 scope(比如项目级、工作区级),一直到最外层全局。所谓作用域的链,就是从最外层祖先一路到当前 agent 自身的一串层级。
  • 层(layer) :每个作用域对应一个层,层里装这个作用域注册进来的技能提供方 (少数技能也可以直接注册进来)。注意进层的是提供方而不是技能本身,真正叫技能的候选,要等读取时由提供方 list 出来。

注册表就靠这两个概念运转,有两个动作:

  • 注册(把提供方挂进来) :插件挂载时,它自带的技能提供方被放进它所属作用域对应的层:host 插件、仓库插件属于全局层 (人人可见);某个 Agent 预设自带的插件,属于那个 Agent 预设的层(只在该预设视野内)。
  • 读取(查当前能用哪些技能) :要查这个 agent 此刻能看到哪些技能时,注册表把全局层 和当前查看作用域的链上的各层合并成一份清单:全局层打底,作用域链从最远的祖先到当前 agent 依次叠加在上面,越近的层越优先。

这就解释了:为什么不同项目、不同 Agent 预设看到的技能集合可以不同。它不是一份全局字典,而是全局层和当前作用域链叠加的结果。

三、同名怎么办:优先级低的赢,近层盖远层

多个提供方、多个层都可能出现同名技能。胜出规则就两条:

  • 优先级(rank)数值低的优先 :先比 rank;rank 相同,再比提供方的注册顺序;仍相同,比提供方内部的本地顺序。
  • 近层遮蔽远层 :全局层和作用域层都有同名技能时,作用域链里更近的那一层整条覆盖更远的那一层。

这点和工具完全不同。工具是注册即全局可见、靠命名空间隔离;技能是同一个名字只会留下一个胜出版本,其余被静默遮蔽(被盖掉的来源会打一条 warning 日志)。

举个例子。假设两个提供方都贡献了 commit-review 这个技能:

  • 本地的 project-dsh 提供方,rank = 100;
  • 远程注册表提供方,rank = 500。

合并时先按 rank 排序,本地那份(100)排在前,于是 commit-review 这个名字胜出的是本地版本 。哪怕远程那份内容更新,只要它的 rank 没压过本地,模型看到的就一直是本地那版,除非你主动提高远程的优先级,或者让远程落在更近的作用域层。

所以当你发现「这个技能不是我以为的那个」,第一反应应该是:有个更高优先级、或更近层的来源把它盖掉了。

四、调用策略矩阵:一份发现结果,两个面

每个技能都带一个调用策略,本质是两个独立的布尔开关:

  • 模型可调(modelInvocable):模型能不能自己决定调用它;
  • 用户可调(userInvocable):人能不能手动触发它(比如当命令执行)。

注册表保留全部四种组合。所以一次发现结果,可以同时服务「模型面」和「用户面」,而两个面的目录互不混淆:

策略 模型工具/目录 用户命令
模型可调 + 用户可调 包含 包含
只模型可调 包含 排除
只用户可调 排除 包含
都不可调 排除 排除

为什么这套设计重要?「给模型看见」和「给用户执行」是两件事。一个技能可以是「只给模型当工具用、不许用户手点」,也可以是反过来,比如一个会改写文件的危险操作,只允许模型在特定流程里调用、不允许用户随手触发;又或者一个纯手工流程技能,只出现在命令菜单里、模型不该自作主张去跑。框架用这两个开关精确区分,而不是含糊地说一句「可用」。

五、没有 TTL:过期靠来源主动失效

注册表没有自动过期时间 。提供方接口上只有一个失效信号 invalidate(),由提供方自己决定什么时候说「我变了,请作废缓存」,注册表收到后才把版本号(revision)加一、清空缓存、广播 skills/change 事件。

这意味着:一个远程来源改了内容,却没主动发失效信号,注册表是推断不出来的,会一直用旧的。这和「工具注册即全局可见」那种简单世界形成鲜明对比,也是接远程技能源时最容易踩的坑:改了远端,本地没反应,多半是失效信号没发出来。

六、串行查询的代价

发现查询是串行 执行的:注册表对每一层、每一层的每个提供方,依次 await 它的 list()。一个慢源会拖垮排在它后面的所有源。所以如果你挂了多个提供方(尤其含远程源),最慢那个决定了整体发现延迟。

这也是为什么发现结果会按「工作目录 + 作用域链 + 版本号」缓存(默认最多 128 份):省得每次都重跑这一串串行查询。注意,如果某次发现里某个提供方失败了或声明不完整,这一份结果不会被缓存,下次调用仍会重试,保证尽量拿到完整清单。

七、惰性加载:用到才取正文

注册表里的候选只是「目录条目」:名字、描述、来源、策略这些元数据。真正的指令正文(Markdown 指令、相对资源)是用到时 才通过候选里的 locator 交回给提供方的 get() 取回,再渲染成模型可读文本。技能附带的资源也由对应提供方负责解析,注册表不越界。

八、小结

  • 技能 ≠ 工具:不是注册即全局可见,而是多源发现 + 分层合并 + 分面调用策略;
  • 提供方管来源 (list 发现、get 取正文),注册表管合并(胜出解析 + 校验);
  • 分层注册:全局层 + 当前作用域链叠加;落到哪层由调用方 Agent 预设决定;
  • 同名 rank 低的赢 ,rank 相同比注册顺序,且近层遮蔽远层;
  • 调用策略两个开关 (modelInvocable / userInvocable)四种组合,模型面与用户面目录互不混淆;
  • 没有 TTL :过期全靠提供方主动调 invalidate(),注册表不会自己过期;
  • 提供方串行查询,慢源拖累后续,结果按目录/来源缓存,失败或不完整的那次不缓存;
  • 惰性加载 :候选只是目录条目,正文 get() 用到时才取、才渲染。

排查「技能不对劲」时照这三点查:

  1. 是不是被遮蔽了 :同名技能按 rank 胜出、近层盖远层,先看有没有更高优先级、或更近层的来源盖掉了你想要的那个;
  2. 远程改了没反应:检查远端来源有没有发失效信号,注册表不会自动发现远程变更;
  3. 模型调不到 :确认技能的 modelInvocable 是开的,而不是 userInvocable 单独开。

所以下次再遇到技能「不对劲」,先别怀疑自己写错了,十有八九,是这套加载机制在背后替你做了主。

本文基于 DeepSeek Harness v0.2.1-rc.2,涉及机制由 skill/*(skill、skill-filesystem、tool-skill)协同实现。

我是小金,如果这篇帮你解决了一个小问题,欢迎关注我的公众号:小金的研发日志。

相关推荐
GPUStack5 小时前
DeepInfer 能否跑赢 vLLM?GPUStack 八卡 B200 实测结果来了
llm·vllm·大模型推理·deepseek·gpustack·deepinfer
jerrywus5 小时前
Codex、Claude Code、DSH 怎么用手机管?我试了试 Agents Anywhere
前端·claude·deepseek
樊小肆1 天前
睡前丢给它三张工单,早上收代码和账单:我开源了一个 DeepSeek 驱动的 AI 编码助手
开源·ai编程·deepseek
我才是银古1 天前
让 Agent 把视频做出来:DeepSeek Harness × DeepSeek-V4.1-Flash 视频生产流水线
deepseek·dsh
坤盾科技1 天前
DeepSeek 开源模型矩阵深度拆解:不追“全能“,用架构差异重新定义能力边界
架构设计·moe·开源模型·deepseek·推理模型·engram
玫瑰互动GEO1 天前
把GeoReady九维当工程项做:储能管理官网的AI就绪度改造笔记
人工智能·ai·ai搜索·seo优化·豆包·deepseek·geo优化
大模型真好玩2 天前
初试短视频生成:豆包将我梦中的场景画出来了~
人工智能·agent·deepseek
ServBay2 天前
DeepSeek 又出王炸,用 DeepInfer 搭建自己的服务是不是很容易?
后端·ai编程·deepseek
comedate2 天前
DeepSeek API 接入教程:申请、Python 调用与常见坑
大模型·api·deepseek