dsh-skill-sediment
DSH 的技能沉淀插件:确定性闸门决定要不要沉淀,LLM 只负责怎么写。向量索引必装,LLM 可选。
它解决什么
现有的会话提取是「定时扫会话日志 → 发一次 LLM → 产出候选」,没有任何东西判断「这条经验值不值得留」。结果是:要么攒一堆一次性闲聊,要么淹没在待审列表里没人看。
这个插件反过来:先用四道确定性闸门把 95% 的东西挡掉,只有过关的才值得花一次 LLM 调用。
四层结构
采集(进程内事件订阅,零 IO)
→ 索引(IndexStore,三后端,必装)
→ 闸门(G1 白名单 / G2 频次 / G3 优化证据 / G4 覆盖,全确定性)
→ 审查(LLM fork,只读,只输出 JSON,落盘由宿主做)
关键设计约束
| 约束 | 为什么 |
|---|---|
| LLM 从不判断「要不要沉淀」 | 判定要么可复现要么不可信;LLM 只写正文 |
| 索引必装,LLM 可无 | 没有模型时经验以结构化片段入库,仍可召回 |
| 默认不沉淀 | 白名单剔除基础操作,bundled 技能不进流水线 |
| 下载的技能默认不可改 | mutable=false,解锁要留痕,锁在最末端写入路径而不是文件权限 |
| 归档不删除 | 误淘汰的代价是一次恢复,不是一次重写 |
| 「没人用过」不算证据 | 生命周期时钟只从第一次真实命中开始走 |
模块
| 文件 | 职责 |
|---|---|
lib/paths.js |
技能真源探测(不假设路径,不自动 mkdir) |
lib/signals.js |
从 session 事件读技能命中 / 工具失败 / 真实用户轮 |
lib/collector.js |
每会话滚动计数,满窗口才产出 |
lib/classify.js |
六类轮次分类、三段式经验记录 |
lib/fingerprint.js |
错误签名归一化(把路径/时间/数字抹平) |
lib/gates.js |
G1–G4 闸门 |
lib/health.js |
六轮体检:取窗口、分类、判纠正重复、评估 |
lib/pipeline.js |
装配审查输入(8K 硬顶)、构建候选、验证 |
lib/complexity.js |
六维复杂度打分(全部机械可数) |
lib/store.js |
IndexStore + 三后端 + 能力位 |
lib/validate.js |
六条验证门 |
lib/redact.js |
熵检测(限定字符集)、敏感路径阻断、<SKILL_ROOT> |
lib/writer.js |
整目录快照、原子写、归档 |
lib/registry.js |
skills-meta.json:来源、可改性、用量、首次迁移 |
lib/lifecycle.js |
active → stale → archived 状态机(可逆) |
lib/locks.js |
三域锁,固定获取顺序 |
lib/settings.js |
五组参数 + 全量校验 |
lib/queue.js |
候选队列 |
lib/state.js |
状态目录总装 |
lib/index.js |
事件订阅、工具、路由、/sediment 指令 |
用法
dsh plugin --profile web add link:/path/to/dsh-skill-sediment
指令:
/sediment pending 列出待审候选
/sediment show <id> 看正文与证据
/sediment apply <id> 通过并落盘
/sediment reject <id> [理由] 拒绝
/sediment edit <id> [正文] 一次提交改正文
/sediment history <name> 版本快照
/sediment undo <name> <stamp> 回滚
/sediment lock|unlock <name> 改可改性
/sediment stats 计数
工具:sediment_pending、sediment_show、sediment_stats。
Web 路由:/api/dsh-skill-sediment/{status,list,settings,pending,decide,unlock}(仅 loopback)。
测试
node scripts/link-deps.mjs # 首次:把宿主自带的 @deepseek-ai/* 链进 node_modules
node --test test/*.test.js
47 项测试,五组:
| 文件 | 覆盖 |
|---|---|
test/core.test.js |
分类、闸门、脱敏回归(含中文误报)、bm25 分数符号、快照回滚、生命周期、并发锁、设置校验 |
test/pipeline.test.js |
窗口装配、候选生成、6 条验证门 |
test/fork.test.js |
审查 fork:JSON 抽取、坏片段丢弃、restrict + guard 双层限制、超时不抛错 |
test/mount.test.js |
挂载面:3 工具 / 6 路由 / 1 指令 / prompt 段、loopback 守卫、headless 无路由宿主仍可用 |
test/load.test.js |
清单可加载性:dsh.bundle.patch 存在且指向本包、入口导出 apply |
scripts/link-deps.mjs 链接的是宿主正在运行的那一份 harness,不是从 npm 另装一份——否则测试会对着一个线上不存在的 API 通过。
实测(scripts/,跑在本机真实会话上)
node scripts/a1-sigs.mjs --sessions /root/.dsh/sessions
node scripts/a3-replay.mjs --sessions /root/.dsh/sessions --window 6
node scripts/gate-test.mjs
a1-sigs:41 个会话 → 53 个失败工具结果 → 27 个不同签名 → 8 个跨 ≥2 会话复现(29.6%)。
头部三条都是"文件未读/被改后再编辑"这类可避免错误,"重复错误 → fork 审查"这条触发路径成立。
a3-replay:108 个真实轮次里 chat 54.6% / question 38.0% / correct 4.6% / failed 1.9% / instruct 0.9%。
全语料 176 轮只有 13 条含指令线索。这是刻意的:默认不沉淀,绝大多数会话一条候选都不产。
已真机验证 / 未验证
已验证:
dsh plugin --profile web add link:...装进真实 profile,--dump-config能看到插件行。- web profile 真机启动:6 条路由全部 200,
status返回provider: sqlite-bm25、pending: 0。 - headless profile 真机启动:无
webServer时插件仍激活,工具与/sediment可用,状态目录正确创建。
这轮真机启动抓到三个只有启动才看得见的错(都已在代码里修掉并有测试守着):
webServer放进顶层inject→ headless 上永远pending,工具和指令一起死。改成ctx.inject子 fiber。- 子 fiber 没有纳入 disposer → 设置变更重挂时路由泄漏。
defineTool的嵌套 object schema 必须显式写additionalProperties,否则注册直接抛错。
已知未验证项
- 向量阈值
store.thresholdVector的分数量纲未实测,阶段 1 标定前不参与判定。 instruct的准确率(不是数量)还需要人工标注集;脚本支持--labels labels.json并在低于 0.8 时退出非零。- fork 已接上
subagents.startActivation(lib/fork.js),但"在真实会话里跑一次审查"尚未端到端验证:本机 headless 缺 LLM 凭据,web profile 的 fork 需要一个跑满 6 轮且触发判决的会话。restrict+guard两层限制有单测覆盖,没有真机覆盖。
No comments yet. Be the first to write one.