🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

主题记忆 fan56/dsh-topics-memory

DeepSeek Harnessv0.25.1
fan5615 天前有提交已提供中文文档扫描:含敏感能力

一个 dsh 插件:把「工作 topic 记忆」维护成 OKF 标准Open Knowledge Format…

34.1/100
综合分
6
GitHub 星标
523
npm 周下载
—/5
用户评分

数据截至 2026/10/21 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add @aiwayds/dsh-topics-memory
npm 源 · @aiwayds/dsh-topics-memory 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · chat
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 15 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包@aiwayds/dsh-topics-memory @ 0.25.1
✓Node 引擎要求 >=22.19.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/28 08:37:51

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 182 处,展示前 110 条)
  • 读取本地文件
    lib/index.js:230
    content: stripFrontmatter(await readFile(bodyUrl, 'utf8')),
  • 发起网络请求
    lib/index.js:580
    const run = distiller.request(sessionId, reason);
  • 发起网络请求
    lib/index.js:793
    const run = distiller.request(sessionId, 'boot-replay');
  • 发起网络请求
    lib/index.js:954
    const run = distiller.request(sessionId, 'manual');
  • 读取本地文件
    lib/store.js:24
    return access(path, FsConstants.F_OK).then(() => true, () => false);
  • 写入本地文件
    lib/store.js:27
    await mkdir(dirname(file), { recursive: true });
  • 写入本地文件
    lib/store.js:29
    await writeFile(tmp, content, 'utf8');
  • 写入本地文件
    lib/store.js:30
    await rename(tmp, file);
  • 读取本地文件
    lib/store.js:98
    const lines = (await readFile(file, 'utf8')).trimEnd().split('\n');
  • 写入本地文件
    lib/store.js:119
    await mkdir(this.metaDir(), { recursive: true });
  • 读取本地文件
    lib/store.js:124
    return JSON.parse(await readFile(this.distillStatePath(), 'utf8'));
  • 写入本地文件
    lib/store.js:135
    await mkdir(this.metaDir(), { recursive: true });
  • 读取本地文件
    lib/store.js:140
    return JSON.parse(await readFile(this.consolidateStatePath(), 'utf8'));
  • 写入本地文件
    lib/store.js:149
    await mkdir(this.topicsDir(), { recursive: true });
  • 读取本地文件
    lib/store.js:173
    files = await readdir(this.topicsDir());
  • 读取本地文件
    lib/store.js:183
    const raw = await readFile(join(this.topicsDir(), f), 'utf8');
  • 删除本地文件
    lib/store.js:350
    await rm(file);
  • 删除本地文件
    lib/store.js:374
    await rm(file);
  • 读取本地文件
    lib/service.js:25
    return readFileSync(file, 'utf8').split('\n');
  • 读取本地文件
    lib/service.js:90
    st = await stat(path);
  • 读取本地文件
    lib/service.js:99
    const doc = okf.parseTopicDoc(await readFile(path, 'utf8'));
  • 读取本地文件
    lib/service.js:157
    st = statSync(file);
  • 读取本地文件
    lib/service.js:166
    const lines = readFileSync(file, 'utf8').split('\n');
  • 读取本地文件
    lib/service.js:233
    const st = statSync(file);
  • 写入本地文件
    lib/service.js:317
    writeFileSync(file, lines.join('\n') + '\n');
  • 读取密钥环境变量
    lib/sync.js:167
    const env = process.env.GITHUB_TOKEN?.trim();
  • 执行外部命令固定命令
    lib/sync.js:175
    const child = spawn('gh', ['auth', 'token'], { stdio: ['ignore', 'pipe', 'ignore'], env: process.env });
  • 写入本地文件
    lib/commands.js:472
    await writeFile(file, html, 'utf8');
  • 执行外部命令来自配置/变量
    lib/commands.js:484
    const child = spawn(cmd, args, { detached: true, stdio: 'ignore' });
  • 读取本地文件
    lib/legacy-import.js:158
    if (existsSync(markerPath))
  • 读取本地文件
    lib/legacy-import.js:171
    const source = legacySettingsCandidates(input.home).find((p) => existsSync(p));
  • 读取本地文件
    lib/legacy-import.js:176
    const section = parseFlatSection(readFileSync(source, 'utf8'), LEGACY_SECTION);
  • 写入本地文件
    lib/legacy-import.js:237
    mkdirSync(join(path, '..'), { recursive: true });
  • 写入本地文件
    lib/legacy-import.js:238
    writeFileSync(path, `${JSON.stringify(marker, null, 2)}\n`);
  • 执行外部命令固定命令
    lib/git.js:29
    const child = spawn('git', [...extra, ...args], {
  • 读取本地文件
    lib/retrieval.js:404
    raw = readFileSync(embeddingIndexPath(bundleDir), 'utf8');
  • 写入本地文件
    lib/retrieval.js:432
    mkdirSync(join(bundleDir, 'meta'), { recursive: true });
  • 写入本地文件
    lib/retrieval.js:433
    writeFileSync(embeddingIndexPath(bundleDir), JSON.stringify(index));
  • 发起网络请求
    lib/embedding.js:79
    res = await fetch(`${host}/api/embed`, {
  • 执行外部命令固定命令
    lib/embedding.js:178
    raw = execFileSync('curl', [
  • 写入本地文件
    lib/pipeline-events.js:53
    await mkdir(dirname(file), { recursive: true });
  • 写入本地文件
    lib/pipeline-events.js:54
    await appendFile(file, lines.map((l) => `${l}\n`).join(''), 'utf8');
  • 读取本地文件
    lib/pipeline-events.js:67
    raw = await readFile(file, 'utf8');
  • 写入本地文件
    lib/pipeline-events.js:78
    await writeFile(tmp, keep.map((l) => `${l}\n`).join(''), 'utf8');
  • 写入本地文件
    lib/pipeline-events.js:79
    await rename(tmp, file);
  • 读取本地文件
    lib/jev/gate.js:52
    raw = readFileSync(file, 'utf8');
  • 执行外部命令固定命令
    lib/onboard.js:196
    const child = spawn('gh', ['api', 'user', '--jq', '.login'], { stdio: ['ignore', 'pipe', 'ignore'] });
  • 读取密钥环境变量
    lib/jev/client.js:56
    const explicit = process.env.JEV_KEYCHAIN?.trim() ?? '';
  • 发起网络请求
    lib/jev/client.js:186
    res = await fetch(backend.endpoint, {
  • 写入本地文件
    lib/jev/log.js:52
    await mkdir(dirname(file), { recursive: true });
  • 写入本地文件
    lib/jev/log.js:53
    await appendFile(file, lines.map((l) => `${l}\n`).join(''), 'utf8');
  • 读取本地文件
    lib/jev/log.js:66
    raw = await readFile(file, 'utf8');
  • 写入本地文件
    lib/jev/log.js:77
    await writeFile(tmp, keep.map((l) => `${l}\n`).join(''), 'utf8');
  • 写入本地文件
    lib/jev/log.js:78
    await rename(tmp, file);
  • 写入本地文件
    src/commands.ts:515
    await writeFile(file, html, 'utf8')
  • 执行外部命令来自配置/变量
    src/commands.ts:527
    const child = spawn(cmd, args, { detached: true, stdio: 'ignore' })
  • 发起网络请求
    src/embedding.ts:147
    res = await fetch(`${host}/api/embed`, {
  • 执行外部命令固定命令
    src/embedding.ts:262
    raw = execFileSync(
  • 执行外部命令固定命令
    src/git.ts:48
    const child = spawn('git', [...extra, ...args], {
  • 读取本地文件
    src/index.ts:358
    content: stripFrontmatter(await readFile(bodyUrl, 'utf8')),
  • 发起网络请求
    src/index.ts:718
    const run = distiller.request(sessionId, reason)
  • 发起网络请求
    src/index.ts:931
    const run = distiller.request(sessionId, 'boot-replay')
  • 发起网络请求
    src/index.ts:1090
    const run = distiller.request(sessionId, 'manual')
  • 读取密钥环境变量
    src/jev/client.ts:136
    const explicit = process.env.JEV_KEYCHAIN?.trim() ?? ''
  • 发起网络请求
    src/jev/client.ts:294
    res = await fetch(backend.endpoint, {
  • 读取本地文件
    src/jev/gate.ts:67
    raw = readFileSync(file, 'utf8')
  • 写入本地文件
    src/jev/log.ts:122
    await mkdir(dirname(file), { recursive: true })
  • 写入本地文件
    src/jev/log.ts:123
    await appendFile(file, lines.map((l) => `${l}\n`).join(''), 'utf8')
  • 读取本地文件
    src/jev/log.ts:136
    raw = await readFile(file, 'utf8')
  • 写入本地文件
    src/jev/log.ts:145
    await writeFile(tmp, keep.map((l) => `${l}\n`).join(''), 'utf8')
  • 写入本地文件
    src/jev/log.ts:146
    await rename(tmp, file)
  • 读取本地文件
    src/legacy-import.ts:210
    if (existsSync(markerPath)) return { outcome: 'marker-exists', imported, skipped }
  • 读取本地文件
    src/legacy-import.ts:224
    const source = legacySettingsCandidates(input.home).find((p) => existsSync(p))
  • 读取本地文件
    src/legacy-import.ts:230
    const section = parseFlatSection(readFileSync(source, 'utf8'), LEGACY_SECTION)
  • 写入本地文件
    src/legacy-import.ts:292
    mkdirSync(join(path, '..'), { recursive: true })
  • 写入本地文件
    src/legacy-import.ts:293
    writeFileSync(path, `${JSON.stringify(marker, null, 2)}\n`)
  • 执行外部命令固定命令
    src/onboard.ts:209
    const child = spawn('gh', ['api', 'user', '--jq', '.login'], { stdio: ['ignore', 'pipe', 'ignore'] })
  • 读取本地文件
    src/paths.ts:41
    existsSync(path: string): boolean
  • 写入本地文件
    src/paths.ts:42
    renameSync(from: string, to: string): void
  • 写入本地文件
    src/pipeline-events.ts:78
    await mkdir(dirname(file), { recursive: true })
  • 写入本地文件
    src/pipeline-events.ts:79
    await appendFile(file, lines.map((l) => `${l}\n`).join(''), 'utf8')
  • 读取本地文件
    src/pipeline-events.ts:92
    raw = await readFile(file, 'utf8')
  • 写入本地文件
    src/pipeline-events.ts:101
    await writeFile(tmp, keep.map((l) => `${l}\n`).join(''), 'utf8')
  • 写入本地文件
    src/pipeline-events.ts:102
    await rename(tmp, file)
  • 读取本地文件
    src/retrieval.ts:503
    raw = readFileSync(embeddingIndexPath(bundleDir), 'utf8')
  • 写入本地文件
    src/retrieval.ts:526
    if (mkdir) mkdirSync(join(bundleDir, 'meta'), { recursive: true })
  • 写入本地文件
    src/retrieval.ts:527
    writeFileSync(embeddingIndexPath(bundleDir), JSON.stringify(index))
  • 读取本地文件
    src/service.ts:37
    return readFileSync(file, 'utf8').split('\n')
  • 读取本地文件
    src/service.ts:179
    st = await stat(path)
  • 读取本地文件
    src/service.ts:187
    const doc = okf.parseTopicDoc(await readFile(path, 'utf8'))
  • 读取本地文件
    src/service.ts:243
    st = statSync(file)
  • 读取本地文件
    src/service.ts:251
    const lines = readFileSync(file, 'utf8').split('\n')
  • 读取本地文件
    src/service.ts:316
    const st = statSync(file)
  • 写入本地文件
    src/service.ts:402
    writeFileSync(file, lines.join('\n') + '\n')
  • 读取本地文件
    src/store.ts:84
    return access(path, FsConstants.F_OK).then(
  • 写入本地文件
    src/store.ts:91
    await mkdir(dirname(file), { recursive: true })
  • 写入本地文件
    src/store.ts:93
    await writeFile(tmp, content, 'utf8')
  • 写入本地文件
    src/store.ts:94
    await rename(tmp, file)
  • 读取本地文件
    src/store.ts:180
    const lines = (await readFile(file, 'utf8')).trimEnd().split('\n')
  • 写入本地文件
    src/store.ts:203
    await mkdir(this.metaDir(), { recursive: true })
  • 读取本地文件
    src/store.ts:209
    return JSON.parse(await readFile(this.distillStatePath(), 'utf8')) as Record<string, unknown>
  • 写入本地文件
    src/store.ts:221
    await mkdir(this.metaDir(), { recursive: true })
  • 读取本地文件
    src/store.ts:227
    return JSON.parse(await readFile(this.consolidateStatePath(), 'utf8')) as Record<string, unknown>
  • 写入本地文件
    src/store.ts:236
    await mkdir(this.topicsDir(), { recursive: true })
  • 读取本地文件
    src/store.ts:261
    files = await readdir(this.topicsDir())
  • 读取本地文件
    src/store.ts:269
    const raw = await readFile(join(this.topicsDir(), f), 'utf8')
  • 删除本地文件
    src/store.ts:430
    await rm(file)
  • 删除本地文件
    src/store.ts:454
    await rm(file)
  • 读取密钥环境变量
    src/sync.ts:181
    const env = process.env.GITHUB_TOKEN?.trim()
  • 执行外部命令固定命令
    src/sync.ts:189
    const child = spawn('gh', ['auth', 'token'], { stdio: ['ignore', 'pipe', 'ignore'], env: process.env })
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-agent@deepseek-ai/dsh-attachment@deepseek-ai/dsh-brand@deepseek-ai/dsh-commands@deepseek-ai/dsh-invariants@deepseek-ai/dsh-llm@deepseek-ai/dsh-scope@deepseek-ai/dsh-sandbox@deepseek-ai/dsh-session@deepseek-ai/dsh-settings@deepseek-ai/dsh-skill
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 维护主题记忆
  • 按OKF标准组织
  • 本地优先可追溯

面向 LLM 智能体的主题记忆——靠编辑而非堆积:主题只保留起始问题、结论、影响和依赖,过程不算记忆。dsh 的 OKF 包,本地优先、可 git 追溯、按预算注入且不调用 LLM。

v0.25.12026-10-19
修复(boot):智能体创建后 1.5 秒再跑启动链,避开首帧窗口
v0.24.12026-10-11
fix(service):影子账本追加时覆盖了 512KB 上限以下的已有记录 之前只在压缩分支里重新读取已有行,所以向小于 512KB 的账本追加时,整个文件会被重写成只剩新条目(dsh-self-improve N1 波次的账本交叉校验发现)。现在每次追加都会带上已有行;超过上限时压缩仍会裁到最新的 3/4。同时保护 package-shadow 和 lesson-shadow 两类账本(共用辅助函数)。回归测试锁定两条记录都能存活。
v0.22.02026-10-09
Merge feat/open-rate-metrics: injection-vs-open metrics and zero-open decay
原文(未汉化)
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
dsh-topics-memory 一个 dsh 插件:把「工作 topic 记忆」维护成 OKF 标准(Open Knowledge Format v0.2)的知识 bundle,持久化在本地 git 仓库(可选同步到 GitHub 私有仓库),利用 git 历史提供结论可追溯性,自动观察会话沉淀知识,并在每轮对话前向模型注入相关 Topic。 要求 dsh >= 0.2.0-rc.2 — 本插件只跟随 dsh RC/stable 线(CI 与发版在运行时解析 latest/next 中更新的 dist-tag)。不再支持 alpha 线。 演示 约 4 分钟的完整流程:topic 沉淀、蒸馏、注入在真实会话里跑起来。 https://github.com/user-attachments/assets/6e9b346d-2ec9-4148-ae62-7087797d3188 它解决什么问题 长会话会失忆,跨会话更会。本插件维护一份结构化的 topic 记忆:每个 Topic 记录一件事的名字、依赖、未决问题、目前结论、影响、建议。结论变了就改文件、打 commit——git log 直接回答「这个结论什么时候、被谁、为什么改的」。 初衷:记忆是编辑出来的,不是攒出来的 先说结论:记忆不是越多越好。
展开完整文档(README 剩余 1 节)
dsh-topics-memory

一个 dsh 插件:把「工作 topic 记忆」维护成 OKF 标准(Open Knowledge Format v0.2)的知识 bundle,持久化在本地 git 仓库(可选同步到 GitHub 私有仓库),利用 git 历史提供结论可追溯性,自动观察会话沉淀知识,并在每轮对话前向模型注入相关 Topic。

要求 dsh >= 0.2.0-rc.2 — 本插件只跟随 dsh RC/stable 线(CI 与发版在运行时解析 latest/next 中更新的 dist-tag)。不再支持 alpha 线。

演示

约 4 分钟的完整流程:topic 沉淀、蒸馏、注入在真实会话里跑起来。

https://github.com/user-attachments/assets/6e9b346d-2ec9-4148-ae62-7087797d3188

它解决什么问题

长会话会失忆,跨会话更会。本插件维护一份结构化的 topic 记忆:每个 Topic 记录一件事的名字、依赖、未决问题、目前结论、影响、建议。结论变了就改文件、打 commit——git log 直接回答「这个结论什么时候、被谁、为什么改的」。

初衷:记忆是编辑出来的,不是攒出来的

先说结论:记忆不是越多越好。

「记忆」类产品的常见思路是多多益善——能记的都记,用时全量检索。这对人也许成立,对 LLM 恰恰相反,而且反在两处:模型的注意力是有限资源,巨型记忆库意味着每一轮对话都在噪音里翻找信号;更危险的是,过程记忆里囤着大量「当时正确、事后失效」的中间判断,它们会理直气壮地把模型引向错误的决定。

所以本插件对「记什么」做了一次苛刻的取舍:一个 topic 只记四件事——它从什么问题开始、得出了什么结论、影响什么、依赖什么。至于得出结论的过程:讨论、试错、绕过的弯路,一概不记。过程属于 session,session 结束就该随它去;能留下的,只有经得起蒸馏的结论。

短时记忆交给 session,对话上下文本就是它,不需要插件再回喂一遍;长时记忆交给 topics——小、结构化、git 可追溯,注入按预算切片、零命中零注入,每轮给模型的都是最小的高价值上下文,而不是越大越好的仓库。

所以这个插件想做的不是模型的记事本,而是模型的编辑:替它决定什么值得记住,以及——更要紧的——什么应该被忘掉。

核心特性

- OKF v0.2 严格合规:每个 Topic 是 markdown + YAML frontmatter 的 concept 文档(type: Topic),可被 Obsidian、OKF 校验器等整个生态直接消费;自带 provenance(sources)、trust(generated/verified)、lifecycle(status/stale_after)三族字段。
- git 可追溯:一次结论变更 = 一个 commit(写穿);topic_history 工具和 /topics history 把变更史工具化。
- local-first:默认 local-only 模式(~/.dsh/topics/),零配置零凭据;配置 repo 后启用 GitHub 同步(单库单 Bundle 单 main,写穿 + 去抖推送,rebase 冲突标记降权等人解,不做自动智能合并)。
- 免 LLM 热路径注入:每轮输入做词法匹配(CJK bigram + 词 + tag 加权 + depends 图游走),毫秒级;无命中零注入;per-topic 摘要 ≤300 token、top-K ≤4、总预算 ≤1.5k token,全部可配。
- 注入可观测可调参:每轮落 Injection Log(命中、得分、near-miss、预算占用),/topics stats 给出 hit rate、top-N、near-miss 分布和阈值调参建议——调参看证据,不拍脑袋。
- 知识连接成图:depends(机器可读的有向依赖边)+ 正文 [[wikilink]] 与 markdown 链接(人写边)共同构成图;检索命中后沿图双向游走(每层衰减一半、深度可配),一次命中带入一个知识子图;每次写入自动重建 meta/backlinks.json 反向引用索引,/topics show 直接列出「谁引用了我、怎么引用的」——改一条结论前先看牵连面。
- 两段式观察(M2):主模型用 topic_observe 随手记原子观察,后台蒸馏 lane(session end + 每 N 轮,模型可配)把观察批量蒸馏成正式 Topic;主模型认为值得记时直接 topic_save。

Jev 决策层(System One,实验,default off)

架在异步 lane 上的可选决策层,由 System One typed-decision 模型(jev)驱动:慢车道 rerank 候选改批量 noul 打分(替换 LLM rerank)、整理 lane 的 merge 候选对在 LLM 园丁接手前先做前置过滤、快道词法门由 turn-end lane 做影子对账。每个判定都过概率门控,任何失败——超时、网络错、坏答案——一律回退到今天的纯本地行为(fail-open 硬编码,无开关键)。注入热路径永不发起远程调用(ADR 0016–0019;设计文档:docs/design/2026-09-25-system-one-integration.md)。默认关闭:jevEnabled: false 即零行为变化。

键

| 键 | 默认 | 说明 |
| jevEnabled | false | 总开关(灰度:default off → shadow → opt-in per profile → 默认开观察);false 连统计一起停 |
| jevBackend | zen | zen(免费)/ native / openrouter |
| jevModel | 空串哨兵 | 调用时按 backend 解析:jev-1.13-free(zen)/ jev-1.13.0(native)/ typesafe/jev-1.13(openrouter)——钉版本号,永不指向别名 |
| jevTimeoutMs | 3000 | 单请求超时;单发 AbortSignal.timeout 不重试(lane 的下一节拍自然重试) |
| jevSecretFile | 无 | 外部 secret 清单路径;启动加载一次,热更换路径即重读 |

key 配置

| Backend | key 来源 |
| zen(默认,免费) | JEV_ZEN_API_KEY env,或 macOS 钥匙串服务 opencode-zen-inference(默认回退——零配置) |
| native | TYPESAFE_API_KEY |
| openrouter | OPENROUTER_API_KEY;钥匙串服务 openrouter-inference 需显式 JEV_KEYCHAIN |

dsh 会清洗插件环境里匹配 KEY|PASSWORD|SECRET|TOKEN 的 ambient 变量,shell 里 export 的 key 到不了插件——请走 profile patch 的 env: 块显式透传,或用钥匙串(JEV_KEYCHAIN / zen 默认服务;都不匹配清洗规则)完全避开文件落 key:

~/.dsh/cordis.patch.yml — 并入你的 profile patch。!!js 表达式让 key 不落文件(与 dsh-jev-mcp 同款约定)。
- insert:
- id: dsh-topics-memory
name: '@aiwayds/dsh-topics-memory'
env:
JEV_ZEN_API_KEY: !!js process.env.JEV_ZEN_API_KEY ?? ''
native: TYPESAFE_API_KEY: !!js process.env.TYPESAFE_API_KEY ?? ''
openrouter: OPENROUTER_API_KEY: !!js process.env.OPENROUTER_API_KEY ?? ''
另加 JEV_KEYCHAIN: 'openrouter-inference' (zen 无需 JEV_KEYCHAIN——无 env key 时自动读默认钥匙串服务)

阈值(rerank / merge pair)

| 档 | rerank(慢车道) | merge pair(整理前置) | 动作 |
| 采纳 | noul ≥ 0.60 | ≥ 0.50 | 进 picks / 送 LLM 生成 |
| 记录带 | 0.10 – 0.60 | 0.15 – 0.50 | 只落 decisions.jsonl,不影响行为 |
| 回退 | < 0.10 | < 0.15 | 强否决,回退词法/现状行为 |

绝对线绑定批量协议(换协议必须重扫)。三缝失败回退行为见 ADR 0018。判定与调用明细落 meta/decisions.jsonl(本地 only,脱敏——绝不存 state 原文)。

延迟基准(2026-09-26 实测,Apple M5,typesafe native 后端,jev-1.13.0)

真实负载基准 + 沙箱实跑——用来选 jevTimeoutMs。各 source 形状与负载不同,除注明外均为同一 8 候选 shadow 批量:

| 来源 | 形状 | p50 | p90 | max | n |
| 空载基准(顺序发) | 8 问批量 | 742 ms | 1290 ms | 1461 ms | 10 |
| 空载基准(顺序发) | 单问 | 367 ms | — | 925 ms | 5 |
| 阈值扫描(09-25) | 20 问批量 | 均值 1760 ms | — | 8470 ms | 16 |
| 真机 headless 轮 | 8 问批量,与主模型流式并发 | 327–5698 ms | — | 10619 ms | 3 |

关键读数:空载路径远低于 3000 ms 默认线(p90 约 2× 余量);但真机轮的 shadow 调用与主模型的流式响应共享网络——上表 10.6 s 离群值正是在这个位置观测到的。超时即 fail-open:该批数据丢弃(慢车道回退旧 LLM rerank),紧超时损失的是 shadow 数据,不是正确性。默认 3000 保持不变;除非 decisions.jsonl 显示 timeout 占比持续 >5%(/topics status 的 30 天摘要会露出),弱网用户可自行上调 jevTimeoutMs。zen 与 openrouter 本次未实测——开启后你自己 decisions.jsonl 的 latency 列就是你网络的真值。

工具与命令

| 模型工具 | 用途 |
| topic_save | 沉淀/修订一个 Topic(名字/依赖/未决问题/结论/影响/建议) |
| topic_observe | 随手记一条原子观察(decision/finding/constraint/question),等蒸馏 |
| topic_search | 免 LLM 关键词检索记忆 |
| topic_history | 某 Topic 的结论变更史(git log 工具化) |

| 命令 | 用途 |
| /topics onboard | 交互式配置向导:唤起 dsh 原生 ask-user 面板逐项问答(模式 / 仓库 / 蒸馏 / 注入档位 / 自动观察),末步确认才写入;无 ask-user UI 的环境自动退化为逐条输入 |
| /topics status | bundle 健康:topic 数、观察积压、冲突、最近蒸馏结果、同步状态 |
| /topics distill | 手动触发一次蒸馏 run(复用现有 lane 与 in-flight 守卫;输出摘要与 distill-state 字段一一对应) |
| /topics consolidate | 手动触发一次整理 run:LLM 园丁合并重复/晋升 stable/废弃过时/刷新元数据,逐条 git commit 可回滚 |
| /topics stats | 注入统计:hit rate、top-N、near-miss 分布与调参建议 |
| /topics list / show / history | 浏览 Topic、反向引用与变更史 |
| /topics graph | 生成关系图网页(力导向、可拖拽缩放、悬停看结论)并自动在浏览器打开 |
| /topics sync [pull\|push] | GitHub 模式手动同步 |
| /topics config / set <key> <value> | 查看与修改配置(阈值、预算、蒸馏模型等) |

安装

dsh plugin --profile <你的profile> add @aiwayds/dsh-topics-memory

Bundle 默认在 ~/.dsh/topics/($DSH_TOPICS_HOME 可覆盖)。装好后的第一件事:跑 /topics onboard——直接弹出 dsh 原生 ask-user 交互面板逐项问答(TUI 面板 / 浏览器会话 / 飞书卡自动适配,feishu 侧装了 dsh-ask-router 还能双端竞答)。GitHub 同步:/topics set repo <owner/name>(建议仓库名 dsh-topics-data,与插件源码仓区分开),凭据走 $GITHUB_TOKEN 或已登录的 gh CLI(登录不是本插件职责)。

从 0.5.x 升级(更名)

0.6.0 起插件更名:@aiwayds/dsh-llmwiki-memory → @aiwayds/dsh-topics-memory,命令族 /wiki → /topics,settings namespace llmwiki → topics。安装新包(并把旧包从 profile 移除)即可——首次启动时插件自动完成迁移:数据目录 ~/.dsh/llmwiki 改名为 ~/.dsh/topics,旧 llmwiki namespace 里用户调过的配置值一次性拷入 topics。无需手动操作;迁移某步失败时插件回落旧路径继续工作。

快速上手

1. 安装(上方命令),重启 dsh;
2. 跑 /topics onboard,在 ask-user 面板里走完 模式 / 仓库 / 蒸馏模型 / 注入档位 / 自动观察 五个决定——末步确认才写入;
3. 正常干活:相关结论每轮自动注入;说「记住…」让模型 topic_save;/topics status 看健康,/topics stats 看注入命中。

卸载

从 profile 移除插件:

dsh plugin --profile <name> remove @aiwayds/dsh-topics-memory

宿主会自动收敛:dsh.profile.bundles 条目被移除,patch 层随包消失。

以下内容有意保留在磁盘上(这是你的记忆本体):

- ~/.dsh/topics/ —— 整个话题包:话题 markdown、meta/、以及内嵌的 .git 仓库(完整历史;可能带有 origin 远端 —— GitHub 同步随插件停止)。要异地归档就原样拷贝/克隆这个目录。
- 设置页 dsh-topics-memory 条目(profile patch)—— 用户覆盖项。注意:重装会静默恢复同步(包括已配置的 repo);想干净重来就先清空该条目。
- 0.5.x → 0.6.x 迁移留下的 llmwiki: 旧设置段不会被自动删除;确认无用后手动移除。

彻底清除:先备份 ~/.dsh/topics,再 rm -rf ~/.dsh/topics。

配置

首次配置交给 /topics onboard;日常微调用 /topics set <key> <value>——0.1.7+ 宿主上写设置页 dsh-topics-memory 条目(profile patch),全部键 volatile 免重启即时生效。从 0.1.7 之前升级:旧 settings.yaml 里的 topics: 段会在下次插件 boot 时一次性自动迁入新条目(审计档在 ~/.dsh/storages/dsh-topics-memory/legacy-import.json)。全部键与默认值:

| 键 | 默认 | 说明 |
| repo | 空(local-only) | GitHub 同步仓 owner/name;建议 dsh-topics-data;置空回 local-only |
| autoInject | true | 每轮注入总开关 |
| injectDedup | true | 会话级注入去重:本会话已实际注入过的 Topic 不重注(会话结束清空注册表;被预算丢弃的仍可注入;被去重占用的 topK 名额不回填)——见 ADR 0012 |
| suppressEcho | true | 蒸馏回声抑制:本会话蒸馏出的 Topic 不回注同会话(provenance 记入 observations 日志的 sessionId → distilledInto) |
| topK | 4 | 每轮最多注入的 Topic 数 |
| perTopicBudget | 300 | 单 Topic 摘要 token 预算 |
| totalBudget | 1500 | 每轮注入总预算 |
| matchThreshold | 0.3 | 命中阈值;按 /topics stats 的 near-miss 证据调 |
| tagBoost | 0.15 | tag 命中加成(多次命中的总上限也为此值) |
| injectMode | pointer | 注入形态:pointer(轻指针,单条 ≤80 tok,topic_open 拉全文;总预算上界锁 600)/digest(完整摘要渲染,per-topic 300 / 总 1500) |
| qualityLane | sampled | 慢道质量 lane:off/sampled(1/3 轮触发)/always;turn/end 产出、下一轮注入消费(消费即清),子代理会话写死不跑 |
| graphDepth | 2 | depends 图双向游走深度(0 关闭) |
| recencyWindowDays | 7 | 近因加分窗口(+0.2) |
| autoObserve | true | 每轮自动抓原子观察 |
| includeSubagents | false | 注入与观察是否作用于子代理会话(ADR 0011;0.7.0 起默认 off);off = 子代理整体跳过 |
| observationMaxChars | 2000 | 每侧每轮观察截断长度 |
| distillProvider / distillModel | 空(蒸馏关闭) | 蒸馏 lane 模型路由,两者都设置才启用。有 UI 时 /topics set distill-provider / distill-model 不带值会弹选择面板(provider 列表 → 该 provider 的模型目录);带值时 distill-model 支持 provider model 或 provider/model 混写自动拆成两个键 |
| distillEveryTurns | 5 | 长 session 每 N 轮触发一次蒸馏 |
| distillOnSessionEnd | true | session 结束时蒸馏一次 |
| distillBatchSize | 40 | 每次蒸馏模型调用携带的观察条数。遇输出上限(max-tokens)失败自动减半重试(下限 5),失败批次不再活锁积压;缩小状态跨 run 保持,直到插件重载或配置变更。注意:/topics set distillBatchSize 重设为同值不触发复位,需设为不同值或重载插件 |
| distillMaxModelCalls | 8 | 单次蒸馏 run 的模型调用预算,含 ops 未回显有效 observed_ids 时的纠错重试(至多一次,预算放不下即零消费停机)。预算耗尽即停,已成功的批次照常 markDistilled(部分前进优于零前进),distill state 记 partial: … |
| consolidateCadence | daily | 整理 lane 节拍:daily/3d/7d/off。会话启动时检查上次整理时间(meta/consolidate-state.json),到期即在后台自动跑 LLM 园丁(复用蒸馏模型路由):本地词面聚类只送「长得像」的候选簇,四类动作 merge/promote/deprecate/refresh,越权 op 一律丢弃;模型调用失败不推进节拍,下次启动重试 |
| deprecatedTtlDays | 15 | deprecated 条目超过 N 天在会话启动时自动删除(本地规则不依赖模型,逐条 git commit 可回溯找回);0 关闭清扫 |
| usageBoost | 0.15 | 使用加成(ADR 0015):近 30 天被注入命中/点开过的 Topic 检索加分(计入门槛分、帽 0.2、零词面相关不加、结构门不豁免);0 关闭 |
| zeroOpenDecay | false | 零打开衰减:零打开高频 slug 的命中分在阈值裁剪前减半(见下方小节) |
| pushDebounceSeconds | 45 | GitHub 模式去抖推送间隔 |
| jevEnabled | false | System One 决策层总开关(实验):false = 零行为变化——见上方「Jev 决策层」一节 |
| jevBackend | zen | 决策端点:zen(免费)/ native / openrouter |
| jevModel | 空(按 backend:jev-1.13-free / jev-1.13.0 / typesafe/jev-1.13) | 版本钉定的决策模型;升级是显式动作 |
| jevTimeoutMs | 3000 | 单次决策请求超时;单发不重试 |
| jevSecretFile | 无 | 出网 secret gate 的外部清单路径;热更换路径即重读 |
| jevLayaFallback | false | 本地 laya 陪跑:每次 jev 调用并发一路 laya 请求;laya 为纯遥测对照(不接管决策,真实负载排序一致率 0/14) |
| jevLayaUrl | http://127.0.0.1:8000/v1/systemone | 陪跑用的 laya-serve 端点 |
| jevDebug | 关 | 诊断日志开关(走宿主 logger,默认静默) |

零打开衰减(默认关)

zeroOpenDecay: true 时,命中分在 matchThreshold 裁剪前乘 0.5:滚动 30 天内被真正注入 ≥5 次、且 opens.jsonl 全期 0 次打开的 slug(即 /topics stats 的「零打开高频」清单——stats 面板与打分衰减共用同一条共享规则)不再霸榜;跌破阈值的候选自然落入 nearMisses 并带 zero-open-decay 标签,注入日志可见原因。动机:24 个「注入 ≥5 次、0 打开」的高频话题长期霸榜,而 score 与打开仅弱相关(阈值 0.3 不动)。默认 false 零行为变化;injections/opens 读取失败跳过衰减(fail-open);/topics set zero-open-decay on|off 运行时切换。

laya 本地陪跑(实验,默认关)

jevLayaFallback: true 且本地跑着 laya serve 时,每次 jev 调用并发一路本地 laya 请求:主路答案驱动决策,laya 答案并排落账——一组永久运行的 laya vs jev 同题对照。laya 是纯遥测(真实负载实测批内排序与主路一致率 0/14、绝对分不可用),主路失败照旧回退旧路径;laya 没起 = 毫秒级连接拒绝。

laya 安装配置:随包 skill dsh-topics-memory-laya 完整覆盖安装(venv / 国内镜像加速 / checkpoint 下载)、启动 laya-serve、配置 jevLayaFallback、用 decisions.jsonl 验证生效。对 agent 说 laya 即可触发,或直接读 skills/dsh-topics-memory-laya/SKILL.md。

Acknowledgements

本项目的形态直接受以下项目的启发与支撑:

- zosmaai/pi-llm-wiki — pi 上的原生 OKF v0.2 知识库扩展,本项目的直接灵感来源。其两段式观察(便宜的原子观察 + 后台蒸馏)、缓存安全注入(易变内容不进 system prompt)、分层 vault 与 ownership 模型都被本设计吸收。
- GoogleCloudPlatform/open-knowledge-format — Open Knowledge Format (OKF) v0.2 规范,本项目 Bundle 格式严格遵循的标准。
- Karpathy 的 LLM Wiki pattern — 整个 LLM 维护个人知识库方法论 的起点。
- fan56/pi-topic-memory — 同作者的前作:pi 上的工作 topic 台账与静默注入扩展,其热路径免 LLM 匹配与注入时序经验是本项目的直接技术前身。
- chancelu/dsh-llmwiki — dsh 生态的同类先例,本项目的同轮注入 seam(agent/inbox/spliced + systemPrompt.context())沿用了它在真实 dsh 上验证过的机制。

已知边界

- 子代理默认不参与记忆,可整体打开:默认(include-subagents off,0.7.0 起)delegation depth > 0 的子代理会话被整体跳过——不注入、不观察、不触发蒸馏;/topics set include-subagents on 后注入与观察同样作用于子代理会话。topic 工具始终在全局层(子代理显式 topic_save 不受开关影响)。跨进程子代理(claude-code/codex 等 provider)本就不加载本插件。
- 退出路径本地化(0.10.0):插件 disposer 只做一步本地 git commit(meta 侧车文件:observations / injections / distill state),不再等待任何网络——没有 pull、没有 push、没有模型调用,宿主退出不再支付 git 双程与有界蒸馏等待(旧的 90s 上限移除,仅保留 10s 兜底以防病理性 git 卡死)。退出蒸馏触发改为 fire-and-forget;已有 session-end run 在跑时整体跳过(否则同一全局队头批次会被双份喂给模型)。跳过不丢活:observations 本就 write-through 落盘,推迟的 push 由下次启动的 pull 补推,被跳过的蒸馏同样由下次启动回放(boot-replay)。meta/distill-state.json 记录每次 lane 的结局,/topics status 可查。
- 观察 GC(三振删除):被模型实际评估(返回了可解析应答,无论内容有无价值)却未被任何 op 消费的观察记一次 failed attempt,连续 3 次即物理删除(用户已明确授权删除 lane 确实无法处理的原始观察)。模型从未评估过的批次永不计数:基础设施失败(网络错误、蒸馏路由未配置 → 可读 no-model 短路)与输出不可解析(invalid-output)豁免;输出上限减半重试中的批次只有到达裁决(成功 / 触底 / stalled / 明确跳过)才计一次。删除立即 commit(数据销毁 git 可追溯),纯计数沿用 flush 节奏。
- 配置读取时机:/topics set 与 profile patch 修改在下次会话启动后生效最稳(dsh 0.1.7 起 settings 文档即 profile patch;旧 settings.yaml 导入一次后改名)。
- 蒸馏选模型:/topics onboard 的蒸馏一步拆成两问(先 provider 后 model,模型列表取自该 provider 的目录),选完经 resolveModelInfo 预校验——provider 无活路由会阻断重选,模型目录校验非 NO_ADAPTER 失败(目录外,可能仍可用)则警告但放行;无 ask UI 或无可用模型路由的环境自动退回文本输入。/topics set 的选择面板走同一套校验。

设计文档

- CONTEXT.md — 领域术语表
- docs/adr/ — 0001–0019:OKF 合规、Remote 形态、同步策略、两段式 Observer、Bundle 布局、注入默认值、可观测与调参、双模式持久化、配置向导、子代理隔离、include-subagents 开关、注入去重默认开、更名与迁移、双通道注入、使用加成、决策层直连 HTTP、快道零远程红线、fail-open 硬编码、secret gate 内嵌

License

MIT
预览分享卡片长什么样 ↓fan56/dsh-topics-memory
分享 · DeepSeek Harness Hub
fan56/dsh-topics-memory

一个 dsh 插件:把「工作 topic 记忆」维护成 OKF 标准Open Knowledge Format…

这是你的仓库?认领条目

回到顶部