DSH 插件与 Skill:两种扩展,一条边界
在 DeepSeek Harness 里,skill 和插件看起来都在扩展 agent 的能力,实际一个改的是模型的决策,一个改的是运行时本身。这篇讲清两者的分工、五条判据,以及一个真实案例。
最近在给 DeepSeek Harness(下称 DSH)做扩展,反复被同一个问题绊住:同样是"让 agent 多会一件事",什么时候该写 skill,什么时候该写插件?把源码翻完之后我发现,答案不在"复杂度"上,而在两句话里。
skill 是给模型看的说明书,插件是给运行时装上的器官。
前者改变模型怎么想,后者改变 harness 能做什么。
一、本质:数据还是代码
| 维度 | skill | 插件 |
|---|---|---|
| 形态 | 磁盘上的 Markdown | TypeScript 模块 |
| 进入方式 | 文本进入对话上下文 | 代码被加载进进程 |
| 生效对象 | 模型的决策 | 运行时本身 |
| 能力边界 | 不超过会话已有的工具 | 可以新增工具,甚至替换实现 |
| 生命周期 | 无,纯文本无副作用 | 有状态机,可加载可卸载 |
| 失败模式 | 模型忽略或误用 | 加载抛错即 FAILED |
| 可移植性 | 跨 harness 通用 | 绑定具体运行时 API |
看这张表容易得出"插件更强"的结论,这是第一个陷阱。强不等于合适。
二、skill 到底怎么起作用
机制是两段式,理解了这两段,边界就清楚了。
第一段是目录。会话开始时会注入一份技能清单,每条只带名字和一句话描述,并明确告诉模型:这只是摘要,没加载正文之前不要照着做。
第二段是按需加载。模型判断这次任务命中某条描述,调用加载工具把正文取回来,正文作为一次工具结果进入对话历史。到这里,skill 的"执行"就结束了。
所以 skill 的能力上限可以写成一个乘法:已有工具能力 × 模型的理解力。它自己不能凭空造出新动作,只能重新组织已有的动作。
这不是缺陷,是设计。正因为 skill 只是文本,它零构建、零审批、跨工具可复用——同一份技能文件在 Claude Code 和 DSH 里都能跑。
三、插件是什么
一个导出 apply(ctx) 的模块,由插件框架在启动时装载。它拿到的是一个服务仓库式的上下文,能做三件 skill 做不到的基础动作:
- 声明依赖:在
inject里写上需要的服务,依赖就绪才执行;服务下线,插件自动卸载,服务回来再自动挂载。启动顺序不用手写。 - 注册即副作用:所有注册都通过上下文安装,插件卸载时自动回滚,不需要手写清理。
- 双半结构:同一个插件可以同时有宿主半和浏览器半,浏览器半被编译进前端包,于是插件能加界面。
四、插件能做什么 skill 结构上做不到的事
按侵入性从浅到深:
造新能力。 注册工具、注册斜杠命令、注册全新服务供别的插件依赖。
改流程。 这是最本质的一条。框架暴露了工具执行前后、每一步之前、提示词组装、模型流式返回这些事件,插件可以监听并改写、短路、注入。用瀑布模式时它就是标准的中间件。skill 最多写"你应该先做 A 再做 B",插件是真的在 A 和 B 中间插了一层。
造界面。 槽位、侧边栏、浏览器端资源。
造地基。 HTTP 路由、持久化存储域、后台任务、定时任务、外部回调入口。
换实现。 模型适配器、沙箱、shell、搜索、子 agent、MCP、压缩策略,这些都是可替换的接缝,插件能整块换掉。
运行时自造插件。 框架甚至提供了让模型自己写插件并热装的工具集:给一段纯 JS 函数体,运行时立即加载,分宿主和浏览器两半,不可变版本,浏览器半首次运行需要审批,宿主半在沙箱里跑,定义只存在内存里、重启即消失。这是 skill 在原理上够不着的地方——skill 只能说"你去写个插件",动态插件是当场长出新的工具和面板。
五、能力阶梯
扩展手段不止两种,按侵入性排下来是这样:
| 手段 | 写成什么 | 能改什么 | 成本 |
|---|---|---|---|
| skill | Markdown | 模型的思路 | 零构建零审批 |
| 钩子配置 | 配置文件 + 命令 | 拦截、放行、补上下文 | 低 |
| MCP | 外部进程 | 加工具 | 中,跨进程 |
| 插件 | 模块 | 进程内一切 | 高,绑 API |
| 动态插件 | 运行时片段 | 同上,模型当场生成 | 审批 + 沙箱 |
选型的正确姿势不是"哪个强",而是"最左边那个能解决问题的手段是哪个"。
六、五条判据
| 判据 | 偏 skill | 偏插件 |
|---|---|---|
| 触发时机 | 用户开口才做 | 每轮自动、定时、命令直呼 |
| 凭证 | 无需鉴权,或已有 CLI 封装 | 需要令牌且不能进上下文 |
| 幂等 | 低频,发错能手动改 | 可能重复执行,必须去重 |
| 产物 | 自由文本 | 固定字段供下游消费 |
| 失败 | 重来一次即可 | 需要重试、超时、回滚 |
其中"凭证"这条最容易被低估,下面用一个真实案例说明。
七、案例:把会话整理成文章并发布
需求是:读会话、整理成文、发布到远端站点。它天然是两半。
整理是判断型工作:取舍哪些内容、什么算敏感、标题怎么起。模型此刻就在上下文里,让它写最便宜也最好。写进插件意味着插件里要再调一次模型,多花一次 token,多一层失败点,还丢了当下的上下文温度。
发布是副作用型工作:要令牌、要幂等、要重试、要固定地址。这四个东西一旦交给模型用命令行手搓,就全都不可控。
所以正确形态是:skill 定义结构、取舍和脱敏规则,但不直接对外发请求;插件只负责"怎么发出去且不出事",用凭证接缝取令牌、用存储记住哪次会话发过哪篇、用工具或命令暴露一个稳定入口。
顺手说一个我在自己环境里踩到的坑,它说明了 skill 的物理天花板:
沙箱的可写根只有工作目录和临时目录。也就是说,skill 里跑的脚本写不了主目录下的任何东西——想把"哪次会话已经发过哪篇文章"记下来,只能记在工作目录里,或者退化成每次重新搜索。插件用宿主的存储服务没有这个限制。这不是设计品味问题,是权限边界的直接后果。
判断什么时候该从 skill 升级成插件,我现在用这几条:想要自动触发、想让令牌不进上下文、想要幂等去重、要发多个远端、或者要分发给别人安装。出现任意一条,再动手。
在出现之前,把规则写进 skill 就够了,而且能一直够。
最后一句
skill 影响一次对话,插件影响之后所有对话。
需要传的是知识,就写 skill;需要改的是行为,就写插件。两者不冲突:skill 正文里写"用某个工具做某件事",插件把那个工具做出来,这是最常见的搭配,而不是二选一。