中文EN

dsh-learn-wiki

memory装不上npmMIT

DSH 的纠错式补料知识库:agent 反复撞同一堵墙时,后台 worker 在限流下联网抓取、蒸馏结果先落暂存区,只有两段式 commit 才能进入召回——无来源的页面一律拒绝。

下载量 / Downloads
753
测试时间 / Tested
2026-11-07
DSH 版本 / Version
0.2.2-rc.1

安装

dsh plugin --profile web add dsh-learn-wiki

沙箱测试结果

阶段结果
1 安装✗
2 列装—
3 挂载—
4 启动—
第一条错误
dsh: initialized profile web at /home/node/.dsh/profiles/web

四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。

README

dsh-learn-wiki

DSH 的「边做边学」知识库插件:工作前自动检索 → 卡住时后台限流联网补料 → 蒸馏落暂存 → 两段式 commit 升入知识库 → 下次自动命中。

它把 CRAG(Corrective RAG)的纠错回路从"单次问答"搬到 agent 循环上:知识库检索不到就自己去学,学到的东西沉淀下来,下次就不用再学。

它解决什么问题

绝大多数"记忆插件"是静态管道——检索不到就检索不到,不会去学。结果就是同一个知识缺口在项目里反复踩,每次都要重新上网查。

本插件的增量只有一个,但是关键的那个:"反复撞同一堵墙"是一个可观测的信号,会触发补料并沉淀。

★ 触发器是挣扎,不是"检索未命中"。原设计确实用未命中,但实测那个信号太廉价
——任何新话题都会未命中,于是系统为聊到的每件新鲜事都去联网。未命中仍可用
(gapTrigger: 'miss' | 'both'),只是默认关。

三层知识架构

层 · 载体 · 角色 · 谁写

L1 · 独立 Markdown 仓库 · 精选知识页,强命中直接注入 · 本插件蒸馏 + 人工编辑

L2 · Hindsight · 原始情景记忆,语义召回 · Hindsight(由既有 hindsight 插件负责)

L3 · 网络 · 兜底补料,限流 · 本插件经 ctx.web

不重复实现 L2。 既有 hindsight 插件已经负责每轮情景记忆召回;本插件只管 L1、未命中判定和 L3 补料,避免两套注入互相打架。

三条设计铁律

1. 自动的不阻塞,阻塞的必须显式。

自动注入走 agent/pre-step(每轮第一步);自动补料走 turn/end 之后的后台 worker;

真正"现在就要这个事实"时由模型显式调 wiki_recall —— 那一次阻塞天经地义。

中途阻塞 5–30 秒联网会拖垮轮次、打断工具链,而同一个缺口通常后面几轮还会出现——

所以"后台学、下轮用"才是"边做边学"的正确形态。

2. staged/ 永不参与召回。

自动产出必须经 commit 才升入 L1。投毒面被限制在暂存区。

3. 无 sources 不 commit。

每条知识必须可溯源到 URL / 文件。commitReadiness() 强制这条。

安装

# 从 GitHub 安装
dsh plugin --profile web add github:Dayi-Z/dsh-learn-wiki

# 本地开发(直接链接到源码目录)
dsh plugin --profile web add link:D:/Harness/dsh-learn-wiki

# 重启 DSH 生效

零构建。 插件是纯 JS:host 半是 ESM,client 半是手写 CJS(由 window.__ModuleLoader__ 装载)。

没有打包步骤,所以从源码安装不需要 allowBuilds 授权,也不需要预先构建产物。

依赖只声明为 peerDependencies(@deepseek-ai/dsh-tools / dsh-llm)——

它们由宿主提供,不要装第二份:同一进程里存在两份实现会让 import 解析到插件自带的那份。

配置

配置来源(后者覆盖前者):代码内默认值 → <wikiRoot>/wiki.config.json → apply(ctx, config) 第二参数。

刻意不使用 Cordis 的 Config schema:rc 阶段要能容忍未知键,严格 schema 会把用户多写的一个键变成加载失败。

关键项(完整列表见 lib/config.js):

键 · 默认 · 说明

wikiRoot · D:\\Harness\\dsh-wiki · L1 仓库根目录

autoContext · true · 旋钮 A:每轮第一步自动注入

autoAcquire · true · 旋钮 B2:后台补料(非阻塞)。触发条件由 gapTrigger 决定

gapTrigger · 'struggle' · 什么触发补料:struggle(默认)\ · miss \ · both \ · off

gapTriggerSignals · ['repeat-failure','recurring-error'] · 哪些挣扎信号适合联网(只认带可搜错误文本的那些)

hitThreshold · 0.20 · score ≥ 此值 → hit(注入正文)

weakThreshold · 0.15 · score ≥ 此值 → weak(只注入标题索引);低于 → miss。见下方"阈值是量出来的"

injectOncePerSession · true · 内容不变则只注入一次(KV cache 友好)

maxAcquisitionsPerRun · 2 · 单次后台补料最多处理的缺口数

webMaxResults · 5 · 每次联网取多少条结果

阈值是量出来的,不是拍的

打分依赖语料规模,所以语料显著增长后必须重跑标定。这条规则有可执行的形状:

node scripts/calibrate-real.mjs        # 整体:正例/负例分布与 gap
node scripts/calibrate-thresholds.mjs  # 逐条 + 扫阈值,回答"该不该调"

最近一次重标定(2026-09-16,66 页语料 / 13 正例 / 5 负例)抓到的不是阈值问题:

负例 · 修前 · 修后 · 根因

「Rust 的 borrow checker 报错怎么绕过」 · 0.8364 · 0.0524 · 检索到了自己 —— 命中的那一页正文里逐字引用了这条标定查询

「如何配置 kubernetes sidecar 注入策略」 · 0.223 · 0.1493 · kubernetes 等词整个语料里都不存在,而 sidecar+注入+策略 恰好同页

两条修法都不是调阈值:前者给"关于本工具自己"的页打 meta 标签、不参与自动注入

(显式 wiki_recall 仍查得到);后者给"查询里有语料根本没有的标识符"加折扣。

修完 Gap = +0.013(正例最低 0.1621 / 负例最高 0.1493)—— 薄,所以 verify-core 里

留了哨兵断言:超线就红,逼着重标定而不是把上限改高。

给这个知识库写页面时:如果一页讲的是"本插件自己的打分/检索/标定",请打上 meta 标签。
否则它会跟标定查询共享同一批词,把标定本身污染掉。

工具

工具 · 作用

wiki_recall · 显式检索 L1,返回三分桶判定与页面正文

wiki_learn · 显式沉淀一条知识(默认落 staged)

wiki_harvest · 从一段对话里提炼(当前会话,或带 session 提历史会话;默认落 staged,无 commit 参数)

wiki_sessions · 读历史会话:list / brief 接手简报 / walls 跨会话反复撞的墙 / show 单个会话详情

wiki_review · 查看 staged 暂存队列与 gaps 缺口队列

wiki_commit · staged → pages(唯一升入 L1 的闸门)

历史会话:这个仓库最被低估的资产

本地躺着几十个会话、完整的对话与工具调用记录。在此之前没有任何路径读它——

预注入、挣扎检测、wiki_harvest 全都只看得到"现在"。于是"上次我是怎么解决的"

这个问题在整个系统里没有位置,只能靠人去记。

wiki_sessions 补上这一块。它的 brief 是接手简报:

node -e "..."   # 或直接让 agent 调 wiki_sessions { action: "brief" }

实测输出(本仓库真实数据):

=== 跨会话反复撞的墙(前 8)===
  [8 会话 / 46 次]  Error: edit requires reading "<path>" first — read the file, then retry
  [5 会话 / 18 次]  Error: old_string was not found in "<path>"
  [4 会话 / 20 次]  Error: platform github unavailable (tried: github): GitHub returned no results
  [2 会话 / 18 次]  Error: invalid arguments: missing required property "file_path"

"这个项目反复卡在哪"以前没有任何地方看得见,现在一句话就出来了。

它怎么工作(lib/session-store.js + lib/session-digest.js + lib/session-index.js):

  • 会话文件是多帧 zstd 拼接(每次追加写一帧)。★ zlib.createZstdDecompress()

不能用——它和 gzip 不一样,遇到第二帧就停(实测只解出第一帧的 170 字符)。

必须自己按魔数 28 B5 2F FD 切帧,逐个解。

  • ★ 文件名里带格式版本:session.jsonl.zstd(v0)或 session.v3.jsonl.zstd(v3,

宿主 0.1.5-rc.2 起)。判定用整名锚定的正则,不靠 includes——目录里躺着

session.jsonl.zstd.bak-20260915、...frame-broken-bak 这类备份,用 includes

会把已经判定损坏的文件重新塞回历史。版本本身从事件流头部的 version 读,

文件名只是副本;副本与真源不一致时信真源,读不到就给 null(0 是 v0,"不知道"不是 v0)。

  • 列会话只读第一帧(头部里有 id / cwd / 创建时间 / 委托深度)。全量解码一个会话

实测要解 11M 字符,列表动作用不起。

  • 索引带缓存与预算:摘要按文件 (size, mtime) 缓存进 <wikiRoot>/.index/sessions.json,

每次调用最多新摘 8 个,没摘完的如实报在 notYetIndexed 里——假装全都索引了,

等于让人以为历史就这么多。

  • 按会话头里的 cwd 字段过滤,不靠目录名反推(目录名是被 mangle 过的)。

而且过滤成空时会退回全部并说明——"查不到"和"没有"必须分得开

(见知识页 scoped-registry-empty-result:这个项目已经栽过一次)。

摘要有三条判据,每条都有实测依据

判据 · 为什么

内层派发有两个事件名(tool/code-dispatch / tool/ptc-dispatch)都要认 · 宿主 0.1.5-rc.2 起 Code Mode 的派发改叫 ptc-dispatch,载荷结构一样。漏掉之后 toolsUsed 只剩 run_code、filesTouched 全空、failures 为 0——摘要会反过来说话("这次没改过文件也没踩过坑"),而 v3 会话不再写流式 chunk,这些派发事件是唯一线索

外层 run_code 的失败是内层派发的复述,不重复计数 · 实测 124 次内层失败对应 122 次外层失败,几乎 1:1。两个都记 = 同一堵墙数两次,阈值全部失真。关联是精确的:dispatch.rootCallId === tool/call.callId

退出码标记只对 shell 工具算数 · 759 条带非零退出标记的结果里 51 条来自非 shell 工具——最多的是 job_output(35 条,它返回的就是另一个进程的 stdout)。那些是回显,不是自己失败

没有可描述症状的失败不成"墙",但计入 weakFailures · 一个命令以非零退出、输出却全是 PASS 行时,"指纹"就是那堆无关日志(真实出现过的例子:指纹是 ui: /learn-wiki 已注册 … PASS)。既搜不出来也无从推理。但不能静默丢,所以计数照记

与 Hermes 生态的 headroom learn 同形:它挖历史会话的失败模式并与"最终成功的那次修正"
做关联,写回 agent 原生的记忆文件。**而它内置的 adapter 只有 ClaudeCode / Codex / Gemini
—— 没有 DSH。** 这一块就是补那个缺口。

学习

…(truncated)

以上内容来自 npm 上的 README,我们只做格式化,不改内容。

版本

npm 上共发布:2,最后更新 2026-09-17

链接

GitHub · Homepage · npm

同类插件