Knowledge base with a corrective acquisition loop for DeepSeek Harness: when the agent keeps hitting the same failure, a background worker fetches the web under a rate limit, distills the result into a staging area, and only a two-stage commit makes it recallable — pages without sources are refused.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-learn-wiki
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:Dayi-Z/dsh-learn-wiki
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
This plugin publishes its README in Chinese only.
DSH 的「边做边学」知识库插件:工作前自动检索 → 卡住时后台限流联网补料 → 蒸馏落暂存 → 两段式 commit 升入知识库 → 下次自动命中。
它把 CRAG(Corrective RAG)的纠错回路从"单次问答"搬到 agent 循环上:知识库检索不到就自己去学,学到的东西沉淀下来,下次就不用再学。
它解决什么问题
绝大多数"记忆插件"是静态管道——检索不到就检索不到,不会去学。结果就是同一个知识缺口在项目里反复踩,每次都要重新上网查。
本插件的增量只有一个,但是关键的那个:"反复撞同一堵墙"是一个可观测的信号,会触发补料并沉淀。
★ 触发器是挣扎,不是"检索未命中"。原设计确实用未命中,但实测那个信号太廉价 ——任何新话题都会未命中,于是系统为聊到的每件新鲜事都去联网。未命中仍可用 (
gapTrigger: 'miss' | 'both'),只是默认关。
三层知识架构
| 层 | 载体 | 角色 | 谁写 |
|---|---|---|---|
| L1 | 独立 Markdown 仓库 | 精选知识页,强命中直接注入 | 本插件蒸馏 + 人工编辑 |
| L2 | Hindsight | 原始情景记忆,语义召回 | Hindsight(由既有 hindsight 插件负责) |
| L3 | 网络 | 兜底补料,限流 | 本插件经 ctx.web |
不重复实现 L2。 既有 hindsight 插件已经负责每轮情景记忆召回;本插件只管 L1、未命中判定和 L3 补料,避免两套注入互相打架。
三条设计铁律
自动的不阻塞,阻塞的必须显式。 自动注入走
agent/pre-step(每轮第一步);自动补料走turn/end之后的后台 worker; 真正"现在就要这个事实"时由模型显式调wiki_recall—— 那一次阻塞天经地义。 中途阻塞 5–30 秒联网会拖垮轮次、打断工具链,而同一个缺口通常后面几轮还会出现—— 所以"后台学、下轮用"才是"边做边学"的正确形态。staged/永不参与召回。 自动产出必须经commit才升入 L1。投毒面被限制在暂存区。无
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。 这一块就是补那个缺口。
学习触发器
| 触发器 | 回答的问题 | 默认 |
|---|---|---|
| 挣扎(连续失败 / 撞同一堵墙 / 改了还是不通) | 我卡住了 | 开(gapTrigger: 'struggle') |
| 检索未命中 | 我不知道 | 关(需显式设 'miss' 或 'both') |
| 被纠正(用户说"不对") | 你说的不对 | 开,但不联网 —— 答案来自用户,只记证据与日志 |
联网这一段怎么设计的
补料要真的"补到东西",靠的是三件事各自成立,而它们失败的样子完全不同:
| 层 | 判据 | 失败时长什么样 |
|---|---|---|
| 查询 | 先剥掉指令性尾缀("常见故障原因 解决办法"),空了再用原查询兜底一次 | 搜回一堆泛泛的页面,看起来"有结果但没用" |
| 抓取 | 宿主 ctx.web.fetch 优先(DNS 钉死 + SSRF 防护),直连兜底;结果带路径(host / direct) | 分不清是宿主拦了、站点拦了、还是要浏览器 |
| 抽取 | 配对标签树选正文容器,而不是"整页去标签" | 抓到了,但截断截在导航上,答案没进提示词 |
三条实测依据(都在 verify:web 里钉住):
- 抽取:nodejs.org 的正文与左侧 API 目录同属一个包装层 —— 朴素去标签会把一百多个 API 名字当成正文;改成配对容器后抽出的是文章本体(含 createZstdDecompress 这类答案词)。 fetchMaxChars 也因此从 4000 提到 12000:原来那个预算先被导航吃掉。
- 壳页:GitHub issue 抓回 373KB HTML,剥完只剩 874 字符的表单提示语(正文是 JS 渲染的)。 现在这种页会被标成 shell 并计入统计 —— 它和"抓取失败"是两种故障,修法相反。
- 缓存:同一批 URL 会被反复抓(一次 gap 两次尝试、相似缺口搜出同一批页面), 单页实测约 1.4 秒。现在按 URL 缓存抽取后的文本(成功 24h / 失败 10min),命中时不再发请求。
每次补料的取材统计会写进 gap 记录与页面正文,例如:
"取材: 2 usable, 3 tried, path host×2/direct×1, 9000 chars"。理由很直接:队列是
"哪些缺口学不动"的唯一台账,而没有这一列时它只能回答"被拒了",答不出为什么 ——
是搜不到、抓不到、还是蒸馏器太保守,这三种修法完全相反。
| wiki_harvest | 我们刚刚想清楚了一件事 | 显式 |
★ 默认是挣扎而不是未命中:未命中太廉价(任何新话题都会触发), 挣扎稀有、昂贵、且必须当场兑现。这个取舍有实测依据,见下面的两节。
挣扎检测修过两次,两次都是"信号在撒谎"
第一次:失败根本看不见。 判"这次失败了"原来只看 result.isError,而那是
harness 层的语义(工具没找到、参数非法)。实测 15,868 条真实工具结果:
| 条数 | 说明 | |
|---|---|---|
isError: true |
1,228 | 全部是 unknown tool "X" 这类接口误用 |
正文带 [exit code: N](N≠0) |
588 | 没有一条置了 isError |
也就是说"改了 → 跑检查 → 失败 → 再改"这条最典型的死胡同对检测器完全不可见;
两个号称零误报的低噪信号只在模型用错工具接口时响。现在 looksLikeFailure()
把两类都算上,判据刻意保守(只认证据不认语气:正文里出现 "Error:" 但退出码为 0
不算失败)。
第二次:edit-churn 把"努力"当成了"卡住"。 43 条挣扎记录里 35 条是它,
全部来自正常迭代(同一个 client.js 改了十几次、每次都跑通);它登记的两条 gap,
一条被蒸馏器拒绝、一条沉淀出关于另一个撞名项目的内容。阈值从 4 提到 8 没用——
病不在阈值上:"反复修改"是努力,"反复修改并且撞墙"才是死胡同。
现在 struggleEditChurnNeedsFailure(默认 true)要求 churn 与失败共现,
信号里带上 failCount 便于事后核账;设成 false 可退回旧行为。
连带修掉一处查询撒谎:symptomQuery() 原来对 edit-churn 硬编码
"反复修改 X 仍不成功 常见原因"——一个只是"改了很多次"的信号,跑到搜索引擎
那里声称自己"不成功"。现在只有真的有失败证据时才这么说。
前两个都是关于"我们自己失败"的信号,发现不了第三种,而它往往最值钱。
实测(2026-09-11):一次会话里用户亲口说出的设计规则("位置表达状态是个陷阱") 一条都没有被沉淀 —— 因为它不经过任何一个自动触发器。而同一次会话自动闭环 产出的,是一页关于另一个撞名项目的内容。信号选错,产出就是反的。
wiki_harvest 补的就是这个入口。它读本会话已经发生的内容(只要人说的话与
助手的 text 段,不要注入块、不要 reasoning 草稿),提炼后落 staged,仍需人工固化。
与 Hermes Agent 的
/learn同形:它也能拿"刚走完的这段对话"当输入 (/learn how I just deployed the staging server)。一个有意的差异是我们学的是 知识页(事实 + 教训),不是技能(可复用过程)—— 后者要引入一个新物种, 召回、证据账、界面全都要分叉,不在本期。
阈值标定(重要)
打分依赖语料规模,阈值必须随语料重新标定,不能跨语料复用:
node scripts/calibrate.mjs
它会用一组标注查询实测分数分布并给出建议阈值。当前默认值来自 7 正例 / 5 负例的实测: 正例 0.152–0.541,负例 0.000–0.105。
★ 对真实语料标定(合成语料不够用)
node scripts/calibrate-real.mjs
calibrate.mjs 用的是 7 页写死的合成语料。它标出来的东西在真实语料上会失效——
这不是猜测,是实测:那条「已知缺陷」负例在合成语料里得 0.0000,在真实语料里
跨过 hitThreshold 被判成 hit,把整页正文注进了提示词。
calibrate-real.mjs 跑在真实的 D:\Harness\dsh-wiki 上,用 13 条正例
(每条都指定期望命中的页)与 5 条负例,输出分数分布与两者之间的 Gap。
语料长大之后暴露的真问题:打分不可分,而不是阈值没调好
语料从 15 页涨到 19 页后,那条已知缺陷从 0.2141 涨到 0.2728。对真实语料重跑标定, 得到的第一个结论是:
(修之前)正例最低 0.2811 负例最高 0.3737 Gap -0.093
最好的负例比最差的正例还高。 一条「Rust 的 borrow checker 报错怎么绕过」 拿到 0.3737,比 13 条真实正例里的 4 条还高。单靠调阈值救不了。
根因是分词器没有停用词概念:中文走字符二元组,而「的」出现在 15/19 页 (df/n≈0.79)且 tf 很高,却和内容词被同等对待。那条 Rust 查询真正命中的是 「的」(15)、「报错」(4)、「怎么」(4)、「绕过」(2)——撑起分数的主要是「的」。
修法是标准 IR 做法:出现比例过高的词不携带区分度,不计入覆盖度(maxDfRatio)。
扫了一遍取值:
| maxDfRatio | 正例最低 | 负例最高 | Gap | 正例 top1 |
|---|---|---|---|---|
| 基线(不过滤) | 0.2811 | 0.3737 | −0.093 | 12/13 |
| 0.5 | 0.2811 | 0.3136 | −0.033 | 12/13 |
| 0.2 | 0.2446 | 0.1969 | +0.048 | 12/13 |
0.2 是第一个让 Gap 转正的取值,且正例 top1 正确率与基线完全相同—— 没有为了分离开而牺牲命中。修完之后 5 条负例没有一条判成 hit, 那条已知缺陷从 0.2728 降到 0.1394。
★ 样本只有 19 页 / 18 条查询,间隔 +0.048 是薄的。语料显著增长后必须重扫。 下限取 2 而不是 1:纯比例在小语料上会退化(n=2 时 floor(2×0.2)=0 → 所有词被滤掉 → 永远返回空),而插件冷启动时正是那个状态。
一个踩过的坑
中文没有词边界,本插件用字符二元组分词,于是"协议的分帧"会产生 议的 / 的分 / 帧和 这类跨词边界的噪声二元组。而 idf() 对 df=0 的词返回的是上界(ln(1+(N+0.5)/0.5),N=1 时约 1.386),语料内常见词只有约 0.288 —— 差 5 倍。
结果:这些永远不可能命中的噪声词反而主导了分母,把每个自然语言查询的分数压到接近 0,几乎每轮都判 miss、反复触发联网。修复是给 df=0 的词中性权重(语料内词的平均 IDF),而不是最大 IDF。实测把"Widget 协议的分帧和魔数是什么"从 0.094(误判 miss)拉回 0.32(hit)。
界面
侧边栏底部的 learn-wiki 入口打开一个面板,三个页签:
| 页签 | 回答的问题 |
|---|---|
| 能力 | 71 个工具里哪些在每轮都收费、我该裁哪个;11 个技能的常驻目录成本各是多少 |
| 知识 | 哪些知识被确认过、哪些有反证、哪些被隔离;每条可以就地展开读全文 |
| 补料 | 挣扎信号分布、缺口队列、最近的判定 |
一条贯穿界面的规矩:位置不表达状态
上一版把"裁掉的工具"顶到工具表最前面,于是你点一下方框,这一行就从指针底下 消失——连点第二下都做不到。知识页签当时也有同一个病:按"需关注度"排 (隔离 > 有嫌疑 > 未确认 > 其余),而这几档全部由证据决定,证据每 8 秒轮询刷新 一次,所以你展开一行读正文,下一轮询它就可能换位置。
现在两处都按不随证据变化的键排(工具按 族→名字,知识按 id), "我该先看哪些"改由筛选器回答(已确认 / 未确认 / 有反证 / 已隔离,都带计数)。
乐观更新
勾选工具、固化暂存页都是先动界面再发请求:失败则回滚,并明确写出"这一格已回滚"。 固化那条不会假装完成——它以「固化中…」的标记躺在表里,真值一到自动换成真实分类 (靠取差集,不靠"记得在某处删掉它")。
键盘
页签是 role="tab" 且可方向键移动;展开控件是真的 <button>(带
aria-expanded),不是"给 <tr> 挂个 onClick"——键盘用户 Tab 不到 <tr>。
面板是模态的,焦点被圈在里面,Esc 关闭并把焦点还给入口。
界面自检与预览
agent 看不到这个界面(没有浏览器运行时,而且当前模型可能不接受图像输入)。 所以有两个脚本把界面变成可读的东西:
npm run verify:render # 结构级渲染测试(真 React + 自写的原语替身)
npm run ui:probe # ★ 从真实浏览器排版量出**文本**:列宽/行高/滚动长度/截断
npm run ui:snapshot # 截成 PNG + 写出 HTML(给看得见图的模型或人)
node scripts/ui-probe.mjs --only knowledge --json .snapshots/probe.json
ui:probe是给 agent 用的那条路。 它把同一份页面交给无头 Chrome 真排版, 再用 CDP 把几何取出来翻成文本:面板/表体矩形、"内容高 vs 可视高 → 要滚几屏"、 每列真实像素宽度、哪些单元格的文字被 CSS 截断了。装不了 playwright 也能跑—— 直接用系统里的 Chrome/Edge,通过 Node 自带的 WebSocket 说 CDP。verify:render抓的是崩溃类 bug。 用真 React 把组件树渲成静态 HTML, 断言"不崩 + 分组正确 + 行数与顺序对"。它第一次跑就抓到一个真 bug: 工具表族标题的计数用的是out[out.length - 1].n++,而循环体已经 push 过行了, 于是计数加到了行对象上,每个族的标题恒显示 1——界面上一直这么显示着。
两个脚本共用
scripts/lib/ui-harness.mjs,所以"测到的"和"看到的"是同一个东西。 夹具也从那里来:想量真实数据,把/learn-wiki/api/state的响应存成 JSON, 用--state传进来。
⚠️ 原语是替身。 Pill / Button / StateDot / MarkdownText / 图标都是照
dsh-client-ui-primitives当前实现写的最小等价物(每个都带data-stub标记), 不是真原语。所以:结构、密度、列宽、行数、截断是可信的; 胶囊的圆角、按钮的填充、状态点的形状不代表真实应用。这件事有代价也有教训。替身图标一开始漏了
width/height,无尺寸的<svg>会拿到浏览器默认的 300×150,把 26px 宽的展开列撑到 105px 高——量出来的 "行太胖"完全是替身自己的问题。替身漏掉一个属性,量出来的"问题"就是替身的问题。
自检
node scripts/verify-core.mjs # 解析 / 索引 / 打分 / 三分桶
node scripts/verify-loop.mjs # 端到端闭环(含防投毒与 commit 闸门)
node scripts/verify-plugin.mjs # 插件接线(mock ctx,无需重启 DSH)
node scripts/verify-ui-api.mjs # UI 数据接口(含桌面 app:// 载体的 POST 形态)
node scripts/verify-render.mjs # 组件树结构级渲染
node scripts/verify-subagent-guard.mjs # 子代理不污染长期记忆(两个方向都测)
node scripts/verify-harvest.mjs # 会话提炼:不把自己的注入物当成人说的话
verify-plugin.mjs 用 mock ctx 跑 apply(),能在不重启 DSH 的情况下抓出事件名拼错、
工具注册缺 output 声明这类错误——本插件开发中它实际抓到了一个阈值标定 bug。
状态
Phase 1(host 半)与 Phase 2(client 半)均已完成并验证:
- host:自动注入 / 三档判定(hit·weak·miss)/ 挣扎检测 / 后台限流补料 / 使用证据与强化因子
- client:能力(工具+技能,按族折叠)/ 知识(可展开、可筛选)/ 分拣(回收站与已拒绝的恢复/删除)/ 补料 四个页签
- 另外注册了
tool.call.toolview(keyed onwiki_recall):把三档判定与命中页做成对话内卡片, 而不是让通用卡片渲染一大坨 JSON。解析不了就回落通用卡片 —— 不画半懂的卡。
- 另外注册了
- 界面可离线自检与预览(见上),不需要开浏览器、不需要重启 DSH
自检:24 个套件,全部离线可跑(npm run verify:*)。最大的那个是结构级渲染测试
——真 React + 自写的原语替身,把组件树渲染成静态 HTML 再断言结构与行序。
已完成(原来的 Phase 3 清单):
wiki_lint:死链 / 来源失效 / 来源不像指针 / 疑似重复 / 主题重叠 / 过期。只读, 且结果里会列出"这次没查什么"(URL 离线验证不了,如实说没查)wiki_merge:两页并一页。默认只出提案,apply:true才写;合并稿落staged/、 被并入的页移进.rejected/并附> REJECTED:原因- 命中率与补料收益度量(
wiki_review detail=metrics):注入命中率 + 缺口→暂存→固化→确认 的漏斗 + 知识库利用率 - 技能按 agent 粒度裁剪(默认关)
- 第三、四个触发器:被纠正 / 已是显式的
wiki_harvest - 工具表按族折叠:
ui:probe实测内容高 4093px ≈ 6.1 屏 → 672px(不需要滚动) - 宿主兼容性自检(
lib/compat.js):启动时把"插件自带 vs 宿主实际"的版本 与 8 项宿主 API 摆进日志
还没做:
- 技能表「来源」列偏窄:11 条来源路径里最长的一条被截掉 108px
- 彻底消除"两份实现":本地
link:安装下插件仍会解析到自己那份@deepseek-ai/dsh-tools(用户从 npm/GitHub 安装则不会——已改为只声明peerDependencies)。这是 dev 环境的常态,不是缺陷,但值得知道
Links
More in this category
vectorize-io/hindsight#coding-agents★ 54464
Hindsight, agent memory that learns: long-term project memory with auto recall and retain, knowledge pages, deep reflection, and per-repo memory banks.
volcengine/OpenViking#examples/dsh-memory-plugin★ 40976
OpenViking memory and context bundle for DeepSeek Harness: pre-step auto-recall and profile injection, session capture, `viking://` URI guarding, and recall/write memory tools backed by an OpenViking server.
MemTensor/MemOS#memos-local-plugin★ 12136
Local-first memory plugin for DeepSeek Harness: layered L1/L2/L3 memory with automatic capture, manual memos_* retrieval tools, and a built-in memory viewer.
agentscope-ai/ReMe#dsh★ 3702
Connects DeepSeek Harness to ReMe's local-first, self-evolving personal knowledge base: automatically captures completed main-agent conversations as user-owned Markdown memory, searches conversations and source material through reme_search with BM25, optional embeddings, and wikilink expansion, and schedules daily memory consolidation.
zilliztech/memsearch#MemSearch★ 2802
Shared Markdown memory for DSH and other coding agents, with automatic capture, pre-step context injection, searchable recall, and memory-to-skill self-evolution through a review panel.
vshulcz/deja-vu#extensions/dsh★ 1321
Reads the session files forty-two other coding agents on this machine already wrote (Claude Code, Codex, Cursor, VS Code Copilot Chat, opencode, OpenClaw, Hermes, Kimi, Cline, Zed and more), including sessions from before it was installed: six tools (deja_recall, deja_session, deja_blame, deja_fix, deja_how, deja_remember), a /deja command, and optional automatic recall added to the runtime context. Local BM25 index, no LLM, no embeddings, no network (dsh plugin --profile web add dsh-deja).
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.