dsh-skill-folder
Fold the DSH skill catalog prompt surface: static KV-cache-stable catalog + BM25/bge-m3 hybrid skill_search + autoRoute hints. v0.3.0. npm: dsh-skill-folder
- Stars
- 0
- Language
- JavaScript
- Created
- Aug 30, 2026
- Updated
- Aug 30, 2026
Introduction
dsh-skill-folder
根治「DSH 每轮把全部技能 description 平铺进 prompt 吃 token」问题。
v0.3.2(2026-08-30,否定词过滤 + frontmatter 检索 + 配置持久化):
- routeHint 否定词过滤:消息含「不需要/不用/无需/不要/别用/别再/别管/不必」时视为反意图,直接不路由(「不需要验证这个方案」不再错误路由到 verifier)。只用多字组合,单字「别/不」不误伤「告别/不错」;「别忘了记住这个」仍正常命中 viking。
- 技能 frontmatter 元数据进检索:新增
lib/frontmatter.js(extractMeta解析 SKILL.md frontmatter YAML 子集 —— category/tags/whenToUse,含metadata.*嵌套与扁平tags:),filterPool保留metadata字段,searchSkills/searchSkillsHybrid把 frontmatter 词并入 BM25 索引(加权:出现 2 次 > description 的 1 次)。无 frontmatter 的技能检索路径完全不变(poolDocs 逻辑保持等价)。审查接线:宿主toSummary不输出 metadata,故检索前经skills.get(name).path读 SKILL.md 原文解析合并(enrichPoolWithFrontmatter,进程级缓存,fail-safe)。 - 配置三级持久化:新增
lib/storage.js(readState/writeState按~/.dsh/settings/→~/.dsh/state/→ 进程内内存 逐级降级,任何 fs/JSON 失败不抛错返回默认)。index.js 用之持久化真实 autoRoute 命中统计{routes: {skillName: count}, lastTs},上限 200 技能名,超出清最旧。 - 77 条测试全绿(新增 25 条:否定词 3 + frontmatter 12 + storage 6 + routeSkillName 1 + 接线 3)。
v0.3.1(2026-08-30,路由收紧 + 稳定性):
- routeHint 误路由修复:多技能同时命中 → 不路由(「验证这个方案安全吗」不再同时猜 verifier+injection-guard);短消息(<5 字)不路由(「验证一下」「安全吗」是闲聊不是意图);名 token 改词边界匹配(
remem不再误命中 memory,完整 part 才命中)。普通对话的<skill-route>噪声大幅减少。 - 52 条测试全绿(新增 3 条误路由回归)。
v0.3.0(2026-08-30,语义混合 + 自动路由):
- 语义检索腿:
skill_search升级为 BM25 + 本地 bge-m3(Ollama)RRF 混合检索——中文意图可直接命中英文技能(BM25 词面鸿沟补上)。语义索引按内容指纹懒构建 + 落盘缓存(~/.dsh/state/semantic-cache.json),Ollama 离线/超时自动降级纯 BM25,绝不比旧版慢或差。 - 自动路由提示(
autoRoute,默认开):用户消息明显指向某技能时(aliases 命中或技能名 token 命中),在用户消息尾部追加一行<skill-route>提示——用户区本就是动态区,catalog 前缀字节不变,KV 缓存零破坏。无关消息不路由,幂等,fail-safe。 - 新配置:
semanticEnabled/ollamaBase/embedModel/autoRoute。
v0.2.0(KV-cache-stable):静态稳定 catalog(前缀永不变化)+ skill_search 检索工具(按需精准发现)。这是 Deferred loading 模式(Anthropic Tool Search / SkillRouter 同款)——动态裁剪目录文本会破坏 prompt cache 前缀(每轮数万 token 重算,净收益为负),静态 catalog + 检索工具则两者兼得:token 省 + 缓存命中 + 选择精准。
架构评审版规格:
docs/system_design.md(权威,含宿主契约行号 A-F 全章节)
核心机制(一句话)
catalog 静态渲染(保缓存前缀)+ skill_search 工具(保选择质量)。
- 宿主
@deepseek-ai/dsh-tool-skill负责:skill工具注册、/name手势注入(L1)、catalog 发布/更新/digest/历史(L2)。 - 本插件以
ctx.on("agent/pre-step", fn, true)(prepend → 最外层) 挂在瀑布最外层:等 L1/L2 全部完成拿到含全量 catalog 的最终 decision 后,只替换message.content[0].text(模型看到的渲染),绝不动message.source.entries(digest 输入)。 - 静态渲染:目录文本只依赖技能集合(core 全量 + 其余截断 + deny 剔除),永不随 query 变 → 每轮字节相同 → DeepSeek 自动前缀缓存 100% 命中。
- skill_search:注册
skill_search(intent)工具(静态前缀),按意图 BM25 检索(name+description+aliases,中文可命中英文技能),结果追加消息尾部 → 不碰前缀。
安装
- 把本目录放进 DSH 插件搜索路径(或 bundle 依赖),
cordis.patch.yml会把skill-folder插入 profile 组合。 package.json的dsh.bundle.patch指向./cordis.patch.yml;也可在 profile 自己的cordis.patch.yml里按id: skill-folder覆盖配置。- (可选)语义检索腿需要本地 Ollama(
http://127.0.0.1:11434)+bge-m3模型:ollama pull bge-m3。不装也能用——自动降级纯 BM25,功能与 v0.2.0 完全一致,只是少了中英跨语言语义命中。
cd dsh-skill-folder
npm install # 只需 @deepseek-ai/schemastery(宿主已内置 cordis/dsh-tools 作 peer)
npm test # node:test,49 条测试,零外部测试依赖
⚠️ 不要禁用
@deepseek-ai/dsh-tool-skill:会导致skill工具消失、/name注入消失,模型无法按名加载技能。本插件与其共存,最小干预 = 最小风险。
配置(schemastery,profile 可按 id: skill-folder 覆盖)
| 键 | 默认 | 说明 |
|---|---|---|
enabled | true | 总开关:关闭则完全不注册监听器 |
core | ["dsh-injection-guard", "dsh-verifier"] | P0 常驻:每轮全量描述可见(安全底线,不截断;deny 不能删除 core) |
deny | ["autotelic-evolution", "dsh-team-orchestra"] | P3 剔除:精确名或 prefix*;core 技能豁免 |
aliases | 12 技能中文映射 | 意图词→技能:skill_search 检索索引(BM25 加分),中文意图可命中英文技能 |
maxDescLength | 100 | 非 core 技能描述最大长度(core 不受限) |
maxFoldMs | 5 | 裁剪耗时上限(ms),超时仅告警,结果仍应用 |
toolSearchEnabled | true | 注册 skill_search 检索工具(静态前缀,结果追加尾部,不破坏缓存) |
maxDescLength | 100 | 动态条目描述最大长度;core 不受限 |
maxFoldMs | 5 | 裁剪耗时上限(ms),超时仅告警,结果仍放行 |
默认 aliases:
{
"viking-memory-guide": ["记忆", "回忆", "记住", "memory", "remember"],
"dsh-grilling": ["访谈", "对齐", "先问我", "grilling", "问清楚", "开工前"],
"dsh-delegation-checklist": ["委派", "子智能体", "subagent", "delegate", "openhands"],
"dsh-context-language": ["术语", "词汇表", "领域语言", "context", "语言"],
"dsh-injection-guard": ["注入", "安全", "不可信", "injection", "外部内容"],
"dsh-verifier": ["验证", "检查完成", "防假完成", "verify", "验证器"],
}
core / deny / aliases 的键(技能名)均支持精确名或 prefix* 前缀匹配。
文件结构
dsh-skill-folder/
├─ package.json # name: dsh-skill-folder, type: module, main: lib/index.js
├─ cordis.patch.yml # profile patch: insert {id: skill-folder, name: 'dsh-skill-folder'}
├─ README.md
├─ lib/
│ ├─ index.js # 插件入口:name/inject/Config/apply + pre-step 监听器(prepend 最外层)+ 注册 skill_search
│ ├─ bm25.js # 原样 vendor dsh-tool-folder/lib/bm25.js(零依赖,CJK bigram)
│ ├─ pattern.js # matchesAnyPattern(精确名或 prefix*,deny/core 共用)
│ ├─ select.js # selectEntries(entries, cfg) -> 静态有序选择(core 豁免 deny)
│ ├─ catalog.js # findCatalogMessage / trimDecision(静态渲染:只改 content,不动 entries)
│ ├─ render.js # renderCatalogText(selected, [], opts):保留宿主 framing
│ ├─ skill-search.js # 纯函数检索(BM25 over name+description+aliases)
│ └─ tool-skill-search.js # defineTool 包装 skill_search(依赖 ctx.skills snapshot)
├─ test/
│ ├─ fixtures.js # 10 技能 fixture(含 cordis 技能)+ 宿主渲染/digest 复刻
│ ├─ apply.test.js # 插件入口回归:prepend/disabled/fail-safe/next 传播/慢告警/disposer/tool 注册
│ ├─ catalog.test.js # T1-T6 裁剪 + T5b KV 稳定性 + T5c 全列 + T5d 安全底线 + digest 一致性 + 放行
│ ├─ skill-search.test.js # S1-S10 检索质量(中文→英文技能命中/deny/确定性/纯函数)
│ └─ waterfall.test.js # T13-T14 瀑布集成 + digest 一致性回归
├─ node_modules/@deepseek-ai/ # 测试专用轻量 stub(schemastery/dsh-tools,npm install 会被真实包覆盖)
└─ docs/ # system_design.md + class/sequence mermaid(架构评审版)
红线(规格 Shared Knowledge)
- catalog 消息
source.entries是全量快照,永不裁剪——只替换content[0].text(digest 一致性红线,否则每轮重发全量 token 更爆炸)。 - 注册必须
prepend:true(最外层)——普通ctx.on是最内层,在 L2 之前运行,改不到 catalog。 - 所有改写返回新对象(spread),绝不 mutate 冻结消息;失败一律原样放行,绝不 throw。
- catalog 渲染必须静态(只依赖技能集合,不随 query 变)——否则 KV cache 前缀失效,净收益为负。
- 渲染保留宿主 framing(
<system-reminder>/<available_skills>/加载指引/直呼指引)。 - 全零运行时依赖(除 schemastery + cordis/dsh-tools peer)。
测试
node --test "test/*.test.js"
49 条测试(node:test + node:assert,零外部依赖;经 node_modules/@deepseek-ai/ 下的轻量测试 stub 直接 import lib/index.js):
- apply.test.js:插件入口回归——exports 契约(name/inject/Config);disabled 不注册监听器;
agent/pre-step以prepend=true(最外层)注册;skill_search工具注册 / toolSearchEnabled=false 不注册;trim 抛错 → catch → 原样放行(fail-safe);next()抛错 → 传播不吞错;maxFoldMs 超时 → 告警但结果仍放行;disposer 幂等。 - catalog.test.js:T1 全量裁剪显著变短 + entries deep-equal + kind/id 保留;T2 无 catalog 同一引用;T3 reject 放行;T4 entries 畸形放行;T5 全选中文本不变同一引用;T5b KV 稳定性(不同 query 字节相同);T5c 全列(cordis 技能可见,绝不丢技能);T5d 安全底线(deny 不能删 core);T6 多条 catalog 全部裁剪。
- skill-search.test.js:S1 deny 剔除;S2-S7 中文意图命中英文技能(cordis 插件/composition/委派/审查/记忆/规划);S8 空意图空结果;S9 确定性;S10 纯函数无副作用。
- hybrid-route.test.js(v0.3.0):routeHint 中文/英文 alias 命中、无关消息不误路由、deny 技能不提示;searchSkillsHybrid 无语义降级 BM25、语义命中、双命中 RRF 排序、语义抛错降级;appendRouteHint 尾部追加 / catalog 引用不变 / 幂等 / fail-safe。
- waterfall.test.js:T13 模拟 L2 注入全量 catalog + 外层监听器裁剪,其它消息顺序/引用不变;T14 下轮 digest 判定「无变化」不追加(防 republish 死循环)。
已知边界(v1 接受)
- 技能集变更时宿主会追加一条裁剪版 catalog(~600-1000 字符),远小于全量;v2 可研究 session surface 重写。
maxDescLength=100为初始值,落地后按真实 token 收益调参。- deny 只影响目录,不影响用户
/name直呼(L1 绕过目录,仍能注入)。