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

长期记忆库 LittleBlackTong/dsh-plugin-memory

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

DeepSeek Harness 长期记忆插件:跨会话、可迁移、带「灵魂」的 markdown 记忆库。

45.1/100
综合分
20
GitHub 星标
13,543
npm 周下载
—/5
用户评分

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

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

状态速览

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

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

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

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

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

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/31 02:18:43

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

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

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 129 处,展示前 95 条)
  • 发起网络请求
    lib/client.js:519
    fetch(INSIGHTS_URL)
  • 发起网络请求
    lib/client.js:720
    fetch(CONTROL_URL, {
  • 发起网络请求
    lib/client.js:746
    fetch(CONFIG_URL)
  • 发起网络请求
    lib/client.js:769
    fetch(CONFIG_URL, {
  • 发起网络请求
    lib/client.js:1444
    fetch(GRAPH_URL)
  • 读取本地文件
    scripts/memory.mjs:40
    if (existsSync(local)) return local
  • 读取本地文件
    scripts/memory.mjs:47
    if (!existsSync(dir)) return out
  • 读取本地文件
    scripts/memory.mjs:48
    for (const e of readdirSync(dir, { withFileTypes: true })) {
  • 读取本地文件
    scripts/memory.mjs:94
    const text = readFileSync(f, 'utf8')
  • 读取本地文件
    scripts/memory.mjs:196
    const index = readFileSync(join(store, 'index.md'), 'utf8')
  • 读取本地文件
    scripts/memory.mjs:199
    if (!existsSync(join(store, p))) problems.push(`index.md links to missing page: ${p}`)
  • 写入本地文件
    scripts/memory.mjs:393
    writeFileSync(probe, '')
  • 删除本地文件
    scripts/memory.mjs:394
    rmSync(probe)
  • 执行外部命令固定命令
    scripts/memory.mjs:444
    const git = (...a) => execFileSync('git', a, {
  • 写入本地文件
    scripts/memory.mjs:526
    mkdirSync(staging, { recursive: true })
  • 写入本地文件
    scripts/memory.mjs:530
    writeFileSync(join(staging, 'memory-manifest.json'), JSON.stringify(manifest, null, 2))
  • 执行外部命令固定命令
    scripts/memory.mjs:531
    execFileSync('tar', ['-czf', out, '-C', staging, '.'], { stdio: 'inherit' })
  • 删除本地文件递归
    scripts/memory.mjs:532
    rmSync(staging, { recursive: true, force: true })
  • 写入本地文件
    scripts/memory.mjs:544
    mkdirSync(staging, { recursive: true })
  • 执行外部命令固定命令
    scripts/memory.mjs:545
    execFileSync('tar', ['-xzf', archive, '-C', staging], { stdio: 'inherit' })
  • 删除本地文件递归
    scripts/memory.mjs:548
    rmSync(store, { recursive: true, force: true })
  • 删除本地文件递归
    scripts/memory.mjs:550
    rmSync(staging, { recursive: true, force: true })
  • 写入本地文件
    scripts/memory.mjs:567
    writeFileSync(join(store, 'user', 'profile.md'),
  • 写入本地文件
    scripts/memory.mjs:572
    writeFileSync(join(store, 'index.md'), index)
  • 删除本地文件递归
    scripts/memory.mjs:587
    rmSync(dir, { recursive: true, force: true })
  • 删除本地文件递归
    scripts/memory.mjs:590
    rmSync(dir, { recursive: true, force: true })
  • 读取本地文件
    lib/boot.js:30
    if (!existsSync(path)) return undefined
  • 读取本地文件
    lib/boot.js:33
    text = readFileSync(path, 'utf8')
  • 读取本地文件
    lib/boot.js:57
    if (!existsSync(path)) return undefined
  • 读取本地文件
    lib/boot.js:59
    const text = readFileSync(path, 'utf8')
  • 读取本地文件
    lib/boot.js:120
    chars = readFileSync(join(memoryDir, fileName), 'utf8').length
  • 读取本地文件
    lib/boot.js:229
    if (!existsSync(join(memoryDir, 'BOOTSTRAP.md'))) return false
  • 读取本地文件
    lib/pages.js:52
    if (!existsSync(dir)) return
  • 读取本地文件
    lib/pages.js:53
    for (const entry of readdirSync(dir, { withFileTypes: true })) {
  • 读取本地文件
    lib/pages.js:104
    if (!existsSync(filePath)) return false
  • 读取本地文件
    lib/pages.js:107
    text = readFileSync(filePath, 'utf8')
  • 写入本地文件
    lib/pages.js:118
    writeFileSync(filePath, next, 'utf8')
  • 读取本地文件
    lib/pages.js:173
    const lastAccess = readLastAccess(readFileSync(abs, 'utf8'))
  • 读取本地文件
    lib/pages.js:193
    return statSync(path).mtimeMs
  • 读取本地文件
    lib/scaffold.js:244
    if (existsSync(filePath)) return false
  • 写入本地文件
    lib/scaffold.js:245
    writeFileSync(filePath, template, 'utf8')
  • 写入本地文件
    lib/scaffold.js:257
    mkdirSync(memoryDir, { recursive: true })
  • 读取本地文件
    lib/scaffold.js:260
    if (!existsSync(target)) {
  • 写入本地文件
    lib/scaffold.js:261
    mkdirSync(target, { recursive: true })
  • 读取本地文件
    lib/insights.js:70
    text = readFileSync(abs, 'utf8')
  • 读取本地文件
    lib/insights.js:71
    const stat = statSync(abs)
  • 读取本地文件
    lib/insights.js:97
    if (!existsSync(path)) return []
  • 读取本地文件
    lib/insights.js:100
    text = readFileSync(path, 'utf8')
  • 读取本地文件
    lib/insights.js:108
    entries.push({ target, exists: existsSync(join(memoryDir, target)) })
  • 读取本地文件
    lib/insights.js:207
    const indexBytes = existsSync(indexPath) ? statSync(indexPath).size : 0
  • 读取本地文件
    lib/graph.js:117
    text = readFileSync(join(memoryDir, rel), 'utf8')
  • 读取本地文件
    lib/graph.js:141
    if (!existsSync(join(memoryDir, name))) continue
  • 读取本地文件
    lib/graph.js:178
    text = readFileSync(join(memoryDir, node.id), 'utf8')
  • 读取本地文件
    lib/config-store.js:84
    if (!existsSync(this.path)) return
  • 读取本地文件
    lib/config-store.js:85
    raw = JSON.parse(readFileSync(this.path, 'utf8'))
  • 写入本地文件
    lib/config-store.js:174
    mkdirSync(dirname(this.path), { recursive: true })
  • 写入本地文件
    lib/config-store.js:176
    writeFileSync(tmp, `${JSON.stringify(this.config, null, 2)}\n`)
  • 写入本地文件
    lib/config-store.js:177
    renameSync(tmp, this.path)
  • 读取本地文件
    lib/digest-guard.js:65
    if (!existsSync(path)) continue
  • 读取本地文件
    lib/digest-guard.js:67
    latest = Math.max(latest, statSync(path).mtimeMs)
  • 读取本地文件
    lib/recall-nudge.js:64
    if (!existsSync(logPath)) return []
  • 读取本地文件
    lib/recall-nudge.js:66
    const text = readFileSync(logPath, 'utf8')
  • 读取本地文件
    lib/proactive-lock.js:54
    if (typeof path !== 'string' || path.length === 0 || !existsSync(path)) return undefined
  • 读取本地文件
    lib/proactive-lock.js:57
    raw = JSON.parse(readFileSync(path, 'utf8'))
  • 写入本地文件
    lib/proactive-lock.js:112
    mkdirSync(dirname(path), { recursive: true })
  • 写入本地文件
    lib/proactive-lock.js:114
    writeFileSync(tmp, `${JSON.stringify(lock)}\n`, 'utf8')
  • 写入本地文件
    lib/proactive-lock.js:115
    renameSync(tmp, path)
  • 读取本地文件
    lib/autocommit.js:27
    if (!existsSync(dir)) return out
  • 读取本地文件
    lib/autocommit.js:30
    entries = readdirSync(dir, { withFileTypes: true })
  • 读取本地文件
    lib/autocommit.js:52
    latest = Math.max(latest, statSync(file).mtimeMs)
  • 执行外部命令固定命令
    lib/autocommit.js:63
    execFileSync('git', ['--version'], { stdio: 'ignore' })
  • 读取本地文件
    lib/autocommit.js:103
    if (!existsSync(join(this.memoryDir, '.git'))) {
  • 执行外部命令固定命令
    lib/autocommit.js:155
    execFileSync('git', ['-C', this.memoryDir, 'add', '-A'], { stdio: 'pipe' })
  • 执行外部命令固定命令
    lib/autocommit.js:163
    execFileSync('git', ['-C', this.memoryDir, 'commit', '-m', `digest: auto-commit memory store ${stamp}`], { stdio: 'pipe' })
  • 执行外部命令固定命令
    lib/autocommit.js:176
    execFileSync('git', ['-C', this.memoryDir, 'diff', '--cached', '--quiet'], { stdio: 'ignore' })
  • 执行外部命令固定命令
    lib/autocommit.js:186
    const out = execFileSync('git', ['-C', this.memoryDir, 'status', '--porcelain'], {
  • 读取本地文件
    lib/skill.js:37
    cached = readFileSync(fileURLToPath(url), 'utf8')
  • 读取本地文件
    lib/growth.js:78
    if (existsSync(logPath)) {
  • 读取本地文件
    lib/growth.js:80
    for (const line of readFileSync(logPath, 'utf8').split('\n')) {
  • 读取本地文件
    lib/growth.js:101
    stat = statSync(join(storeDir, rel))
  • 读取本地文件
    lib/growth.js:160
    if (typeof statePath !== 'string' || statePath.length === 0 || !existsSync(statePath)) return undefined
  • 读取本地文件
    lib/growth.js:163
    raw = JSON.parse(readFileSync(statePath, 'utf8'))
  • 写入本地文件
    lib/growth.js:196
    mkdirSync(dirname(statePath), { recursive: true })
  • 写入本地文件
    lib/growth.js:198
    writeFileSync(tmp, `${JSON.stringify(state)}\n`, 'utf8')
  • 写入本地文件
    lib/growth.js:199
    renameSync(tmp, statePath)
  • 读取本地文件
    lib/index-page.js:230
    const text = existsSync(indexPath) ? readFileSync(indexPath, 'utf8') : ''
  • 读取本地文件
    lib/index-page.js:230
    const text = existsSync(indexPath) ? readFileSync(indexPath, 'utf8') : ''
  • 读取本地文件
    lib/index-page.js:246
    pageText = readFileSync(join(memoryDir, item.target), 'utf8')
  • 读取本地文件
    lib/index-page.js:282
    .filter((path) => !existsSync(join(memoryDir, path)))
  • 读取本地文件
    lib/index-page.js:298
    const before = existsSync(indexPath) ? readFileSync(indexPath, 'utf8') : ''
  • 读取本地文件
    lib/index-page.js:298
    const before = existsSync(indexPath) ? readFileSync(indexPath, 'utf8') : ''
  • 写入本地文件
    lib/index-page.js:310
    if (written > 0) writeFileSync(indexPath, out, 'utf8')
  • 写入本地文件
    lib/index-page.js:353
    writeFileSync(abs, next, 'utf8')
  • 读取本地文件
    lib/checkup.js:234
    return existsSync(path) ? statSync(path).size : 0
  • 读取本地文件
    lib/checkup.js:234
    return existsSync(path) ? statSync(path).size : 0
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

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

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-skill@deepseek-ai/dsh-system-prompt@deepseek-ai/schemastery
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 跨会话记忆
  • 可迁移记忆库
  • markdown记忆库

README

由 DeepSeek 最新模型翻译生成
dsh-plugin-memory 中文 | English DeepSeek Harness 长期记忆插件:跨会话、可迁移、带「灵魂」的 markdown 记忆库。 让我们一起早日实现完整的「蓝色大肥鱼」。 English TL;DR — A Cordis plugin for DeepSeek Harness that gives agents a persistent, cross-session, migratable long-term memory: a markdown + git store (inspired by Karpathy's LLM Wiki pattern) with a SOUL.md persona file, auto-injected at every session start via the system-prompt runtime context, plus remember / recall / consolidate / forget workflows and portable CLI tooling. 快速上手 三步,大约五分钟: 1. 安装 dsh plugin --profile <profile> add dsh-plugin-memory 2. 重启应用,开个会话随便说句话。
展开完整文档(README 剩余 1 节)
dsh-plugin-memory

中文 | English

DeepSeek Harness 长期记忆插件:跨会话、可迁移、带「灵魂」的 markdown 记忆库。

让我们一起早日实现完整的「蓝色大肥鱼」。

English TL;DR — A Cordis plugin for DeepSeek Harness that gives agents a persistent, cross-session, migratable long-term memory: a markdown + git store (inspired by Karpathy's LLM Wiki pattern) with a SOUL.md persona file, auto-injected at every session start via the system-prompt runtime context, plus remember / recall / consolidate / forget workflows and portable CLI tooling.

快速上手

三步,大约五分钟:

1. 安装
dsh plugin --profile <profile> add dsh-plugin-memory

2. 重启应用,开个会话随便说句话。boot 块会自动加载——因为记忆库还没有灵魂,agent 会主动开始「铸魂」对话:它叫什么名字、怎么称呼你、你们是什么关系、该是什么性格。答多答少都行,它会逐项写回去。

3. 确认生效。dsh-memory doctor 做一次环境体检(脚手架 / 孤儿页 / git 状态 / boot 预算),最后给出修复每一条的命令。或者打开设置 → 记忆看健康看板,选项都能热改。

之后就很简单了:你干活时 agent 顺手把记忆写回去;你随时能手动整理记忆库——纯 markdown + git,没有任何锁死的东西。

特性

- 开机强制注入(带路由表保护):插件通过 ctx.systemPrompt.context() 把记忆 boot 块(SOUL.md 人格 + MEMORY.md 协议 + index.md 目录 + 最近动态)注入会话上下文。宿主按投影去重:记忆不变就不重复注入,变化时新快照自动取代旧的——这是"新会话必先加载记忆"的硬保障,不需要模型碰运气调技能。总预算是 bootMaxChars,按文件分配:每个文件先拿一份均分(受实际大小封顶),剩余额度优先给 index.md、再给尚有内容未注入的文件——所以记忆库长大时,目录的尾部不会先被截掉(详见「boot 预算分配」)。默认还带两道礼貌闸门:deferUntilUserSpeaks(用户开口后才注入)与 activeSessionOnly(只注入当前激活会话),见「配置」。
- last_access 自动戳记:MEMORY.md 的 salience 衰减规则依赖 last_access,而它以前纯粹靠自觉——模板里有字段、规则里提到它,但没有任何代码去写它,于是每个页面都原地变老、衰减表形同虚设。现在会话收到第一条真实用户消息时,插件会把 boot 块实际注入过的页面(SOUL.md / MEMORY.md / index.md + 目录里被引用到的页)戳成当天,每会话一次、按天幂等,只改 last_access 一行;dsh-memory touch <pages...> 可手动补记,trackPageAccess: false 可关闭。
- SOUL.md 铸魂:安装后首要任务是和用户对话定义灵魂(名字、性格、价值观、语气、边界)、确认身份与关系(BOOTSTRAP.md 清单驱动,complete 前优先于常规任务)。
- 铸魂自动引导:记忆库还没有灵魂(BOOTSTRAP.md 非 complete,或 SOUL.md 仍是占位模板)时,boot 块会自动前置一段第一人称引导词——「我的首要任务是确认我是谁,还有你是谁:我叫什么名字、怎么称呼你、你我是什么关系、我该是什么样的性格」——像 OpenClaw 初始化那样,由 agent 在对话里主动发起铸魂,逐项问、逐项写回,而不是等用户来喂。铸魂完成后引导词自动消失,零开销。
- 复利记忆:遵循 Karpathy 的 LLM Wiki 约定——记忆是"一次编译、持续保鲜"的持久产物,不是每次查询重新 RAG。remember / recall / consolidate / forget 四操作 + salience 三级衰减。
- 可迁移:记忆本体是纯 markdown + git + 自描述 schema,任何能读 markdown 的 agent 都能接手。dsh-memory pack/unpack 打包迁移。
- 内嵌技能:通过 ctx.skills.register() 注册 memory 技能(操作协议随插件分发);项目级 .dsh/skills/memory 文件技能仍可覆盖它。
- 防懒 digest 唤醒:每轮结束后,若 agent 空闲且记忆库超过 digestNudgeAfterMinutes 未写入,插件注入一条 digest 提醒(合成消息,走 agent.followup),把"会话收尾沉淀"从靠自觉变成有机制兜底;带冷却与每会话限次,不骚扰。独立于 dsh-plugin-heartbeat,两插件各自可装、互不依赖。
- 主动追忆(拟人化):对话空下来时,插件会以第一人称主动提起一件真实记得的、关于用户或你们之间的事(偏好、往事、未了的决定、最近的进展),把记忆从"只写回"变成"也用起来"——像老友自然想起那样,而非报状态。间隔在最短/最长之间随机取值(不固定节奏),配合每会话限次,不骚扰、不编造、不硬聊;纯对话行为,不写记忆库。同样独立于 heartbeat。
- git 自动提交:记忆库变更静默 autoCommitQuietSeconds 后自动 git add -A && git commit(无 .git 则跳过)——历史可回滚不再依赖 agent 记得 commit。
- 设置面板:在 DSH 设置页提供「记忆 / Memory」区块(跟随语言)——总开关、记忆目录、开机注入、技能注册、主动追忆(开关 + 随机间隔范围 + 每会话次数)均可热改,立即生效,无需重启。
- index 自动整理(声明式编译):索引行不再手写。页面在 frontmatter 里声明 summary:,dsh-memory index --write 把行重写为它的投影(标签 ≤32 / 摘要 ≤48 / 整行 ≤132 字符),只改行内容,分节与顺序逐字节保留,且幂等。index --check 给机器判定(有漂移退出码非 0),index --sync-frontmatter 把已写在 index 里的摘要回填进页面(老库一次性迁移)。digest 提醒会在索引漂移时附一句提示,agent 顺手就能修。
- 互链成为默认动作:index.md 是目录(谁存在),页间互链才是关系(谁和谁有关)——实测一个真实记忆库里 29 页只有 1 条真互链,而图谱与跨页综合全都建立在这层关系上。现在三处一起推:skills/memory.md 的 remember 流程、MEMORY.md 模板的工作流、以及 digest 提醒的收尾指令,都明确要求"给这次碰过的页面各补 1–3 条相关页链接"。
- 补链建议:dsh-memory graph --suggest 列出「只有索引入口、没有任何内容关系」的页面该引用谁——按共享主题 tag、标题词、同类型打分,并说明理由(共享 #dsh / 标题词 dsh / 同类型)。只报告不写入:是否连、怎么连由 agent 决定。GET /api/memory/graph?suggest=1 同源。
- 记忆图谱:设置页里一张只读关系图 —— 不只是 index.md 那种"索引连着所有页"的星形,而是把页面已经编码但没人画出来的关系画出来:索引路由(index.md 指向每一页——这是全库最大的边集,43 条;先前版本跳过了元文件,导致 index 在图上孤立无援)、页间显式 markdown 链接(含相对路径解析)、共享 frontmatter tag(通用容器 tag 如 project/skill 会被忽略,稠密 tag 走锚点链而非全连接)。坐标由服务端 lib/graph-layout.js 一次性算好(确定性、无随机、无依赖),客户端只负责画 SVG。可交互:滚轮缩放(以光标为中心,缩放范围 0.4×–2.5×,非 passive 监听所以不会连带滚动设置页)、拖空白处平移、重置视图 回到全图;按住节点即可拖走,直接邻居按距离轻微跟随,松手后带缓动滑回原布局——拖动期间暂停过渡做到 1:1 跟手,用 O(邻居数) 的局部松弛而不是逐帧全量力导向(后者在几百页时会卡)。悬停高亮邻里,首次渲染从中心绽开(尊重 prefers-reduced-motion)。图谱每 25 秒自动刷新(面板开着时记忆变更会自己出现,副标题显示「更新于 HH:MM:SS」);取数失败保留上一张图而不是清空。视图数学(zoomAt/panBy/toGraphPoint)在 lib/graph-view.js 里是纯函数并有测试——缩放中心不漂移这件事必须被钉住。GET /api/memory/graph(只读、实时,?types=1 追加同类型弱边)。三类边在图上可区分:页间互链=实线蓝、索引路由=灰色虚线、共享 tag=细线。
- dsh-memory checkup:把四个读者(lint 的完整性、status 的计数、看板的体量与新鲜度、graph 的关系)合成一份带优先级的报告 —— 体量、新鲜度条形图、连接度、一致性检查,最后给出"最该做的 N 件事"。四个数字是诊断,"做这三件事"才是产品。有活干时退出码非 0,可被脚本消费。--boot=N 用于按你的 boot 预算计算索引占比。
- 记忆健康看板:同一区块下方是一张只读看板——记忆页数 / index 路由数 / 记忆字数、访问新鲜度条形图(今天 / ≤7 / ≤30 / ≤90 / >90 天)、四项体检结论(index 链接、孤儿页、frontmatter、新鲜度)与健康分、陈旧页候选、最近 5 条动态。数据来自新增的 GET /api/memory/insights(每次请求实时统计,不缓存),体检口径与 dsh-memory lint 同源,所以看板与 CLI 不会互相打脸。
- 零构建:纯 ESM JavaScript,无编译步骤,pnpm add 即用。

人格层、礼貌与成长(v0.12.0)

四个小增量,把「带灵魂的存储」变成「会处事的伙伴」:

- A 与其他主动插件的发言权仲裁:任何主动消息——追忆、digest 提醒、成长回顾——发出前先 claim 共享文件 ${DSH_HOME:-~/.dsh}/proactive-cooldown.json({ version: 1, speaker: 'memory' | 'heartbeat', at: <epoch ms> },tmp+rename 原子写)。若 proactiveCooldownSeconds(默认 180,0 = 关闭)内有其他 speaker 先 claim,本条让位且不消耗节奏——下次触发自动重试。两个插件零 import、各自独立实现同一协议,任一方可单独装 / 升 / 卸。
- B 语气漂移:lib/tone.js 在 SOUL 之后注入一小段 ## 语气(按语境微调)(≤12 行,计入 boot 预算——先扣掉该段字符成本再按文件分配):赶工/紧急修复 → 短句、先结论、零玩笑;深夜(23:00–06:00)→ 更短更温和;报错/坏消息 → 先给事实与修复动作再谈感受;里程碑/完成 → 轻松一句然后回正事。toneGuideEnabled(默认 true)开关,toneIntensity(low / medium / off,默认 low)控制明显程度。
- C 表达层(只表达不假装):同一段语气规则画出边界——只描述观察到的处境与关心,不宣称内心情绪(禁「我很… / 我感到…」);仅允许三类场合:风险/坏消息、里程碑/完成、超过 3 天没联系后的第一次开口。
- D 每周成长回顾:growthRecapEnabled(默认 true)、growthRecapIntervalDays(默认 7)。走 recall 通道(同一节律、同一每会话上限、同一仲裁锁),内容只从记忆库已经证明的东西派生——最近的 log.md 标题与页面 mtime——不编造成长。<store>/.growth-recap.json 记录上次投递,重启不会重复同一周的回顾。

架构

插件只拥有工作流,不拥有数据格式:

dsh-plugin-memory(本插件) ├── lib/index.js # Cordis 入口:boot 注入 + 运行时技能注册 + settings 热改 ├── lib/boot.js # boot 块渲染(SOUL/MEMORY/index + 最近 log,按文件分配预算) ├── lib/pages.js # 页面访问记账:extractReferencedPages / touchPages(last_access) ├── lib/insights.js # 记忆健康看板的数据层(页数/分布/新鲜度/体检,只读) ├── lib/activity-tracker.js # 两道礼貌闸门:用户是否开口 + 当前激活会话 ├── lib/digest-guard.js # 防懒 digest 唤醒(空闲 + 记忆库久未写 → followup 提醒) ├── lib/recall-nudge.js # 主动追忆(空闲 → 第一人称提起一件真实往事,纯对话不写库) ├── lib/scaffold.js # 记忆库脚手架(模板只建不覆盖) ├── lib/client.js # 客户端半:设置面板「记忆 / Memory」区块(跟随语言)+ 记忆健康看板 ├── skills/memory.md # 内嵌技能的操作协议正文 └── scripts/memory.mjs # CLI:init/search/touch/lint/status/pack/unpack

记忆库(用户数据,默认 ~/.memory) ├── SOUL.md # 人格与灵魂(用户主导) ├── BOOTSTRAP.md # 铸魂清单(complete 前优先) ├── MEMORY.md # schema 与维护协议(自描述) ├── index.md # 页面目录 log.md # 时间线(append-only) ├── identity/ user/ skills/ decisions/ projects/{active,archive}/ concepts/ └── raw/ # 不可变源材料

安装

dsh plugin --profile <profile> add dsh-plugin-memory

(包内置 dsh.bundle manifest,dsh plugin add 会把它自动挂进 profile 的 bundles 层;dsh-market 里的一键安装同此通道。)

重启 profile(DSH Desktop 重启应用)后生效。

📌 版本要求:DSH session format v4 + 官方 DeepSeek Harness 0.2.1-rc.5 系列(0.13.x 已声明 engines.dsh;旧宿主会被闸门拒绝,见兼容性)

注入消息的 source 使用 producer-owned kind plugin:memory。v4 的原生准入明确拒绝 v3 时代的 { kind: 'plugin', plugin: 'memory' } wrapper,报 format v4 message requires a producer-owned source kind(该错误被包成 code: "UNKNOWN",所以在界面上会显示成 ... source kind UNKNOWN——UNKNOWN 是错误码,不是 kind 值)。

v4 与 v3 的白名单互斥(v3 只认 plugin),因此无法同时兼容两版。
0.8.0 及更早版本在 v4 上会触发该错误;旧版 DSH 请停留在 0.8.0。

⚠️ 不要再往 profile 的 cordis.patch.yml 里手写 - insert: {id: dsh-memory, ...}:那会与 bundle manifest 的自动挂载产生两条同名 entry,整个 profile 会以 duplicate loader entry id "dsh-memory" 启动失败(2026-08-18 实机事故)。
运行期配置(enabled / memoryDir / autoInject / registerSkill / recallEnabled)改走 <dshHome>/memory.json(设置面板热改);composition 配置见下表。
如需覆盖某个 composition 键,用不带 insert 的 id 覆盖条目(见配置一节)。

配置

| 键 | 默认值 | 含义 |
| enabled | true | 总开关:关闭后不注入 boot 块、不注册 memory 技能 |
| memoryDir | ~/.memory | 记忆库绝对路径(~ 自动展开) |
| bootFiles | [SOUL.md, MEMORY.md, index.md] | 开机注入的文件 |
| bootMaxChars | 12000 | boot 块总字符预算(防止占用过多上下文);按文件分配见下文 |
| bootFileBudgets | — | 逐文件字符上限,例如 { index.md: 3500 };未列出的文件分剩余额度(composition 层,改完需重启) |
| trackPageAccess | true | 会话首条用户消息时,把 boot 块实际注入的页面戳 last_access(关闭后衰减表需手动维护) |
| autoInject | true | 会话开始时注入 boot 块 |
| deferUntilUserSpeaks | true | 用户发出第一条真实消息后才注入(boot 块 / 追忆 / digest 提醒都遵守);面板可热改 |
| activeSessionOnly | true | 只对「当前激活会话」(最近收到用户消息的会话)注入,后台会话不打扰;面板可热改 |
| registerSkill | true | 注册内嵌 memory 技能 |
| scaffold | true | 记忆库缺失时自动创建模板(只建不覆盖) |
| configFile | <dshHome>/memory.json | 用户可改配置的 JSON 文件路径(设置面板读写它) |
| digestNudgeEnabled | true | 防懒 digest 提醒总开关(composition) |
| digestNudgeAfterMinutes | 120 | 记忆库超过多久未写入就提醒 |
| digestNudgeCooldownMinutes | 180 | 两次提醒的最小间隔 |
| digestNudgeMaxPerSession | 2 | 每个会话最多提醒次数 |
| recallEnabled | true | 主动追忆总开关(面板可热改) |
| recallIntervalMinMinutes | 30 | 随机间隔下限(分钟,面板可热改) |
| recallIntervalMaxMinutes | 240 | 随机间隔上限(分钟,面板可热改) |
| recallMaxPerSession | 3 | 每个会话最多追忆次数(面板可热改) |
| proactiveCooldownSeconds | 180 | 与其他主动插件的共享发言冷却(0 = 关闭) |
| toneGuideEnabled | true | 在 SOUL 之后注入按语境微调的语气段 |
| toneIntensity | low | low / medium / off(off = 纯工程师语气) |
| growthRecapEnabled | true | 每周成长回顾(走 recall 通道) |
| growthRecapIntervalDays | 7 | 两次成长回顾间隔天数 |
| autoCommit | true | 记忆库 git 自动提交开关(composition) |
| autoCommitQuietSeconds | 60 | 变更静默多久后提交(防抖) |
| autoCommitIntervalSeconds | 60 | 变更轮询间隔 |

设置面板(热改)

enabled / memoryDir / autoInject / deferUntilUserSpeaks / activeSessionOnly / registerSkill / recallEnabled / recallIntervalMinMinutes / recallIntervalMaxMinutes / recallMaxPerSession / proactiveCooldownSeconds / toneGuideEnabled / toneIntensity / growthRecapEnabled / growthRecapIntervalDays 十五项在 DSH 设置页的「记忆 / Memory」区块中可改(跟随语言),即时生效:boot 注入、两道礼貌闸门、技能注册、主动追忆(含随机间隔与次数)、发言权仲裁、语气段与每周成长回顾随修改立即生效;记忆目录切换时自动为新目录初始化脚手架(scaffold: true 时)。其余键(bootFiles / bootMaxChars / bootFileBudgets / trackPageAccess / scaffold / configFile / digestNudge / autoCommit)只在 composition 配置层生效,改完需重启。

旁边还有一个 「立即回顾」按钮:它是控制动作,不是配置项——让插件立刻发一次成长回顾。仍尊重发言权仲裁(别的插件在说话就让位)、本周没有新内容就不发、即使 recallEnabled 关闭也能用,并在面板里给出结果(回顾中 / 已发送 / 让位 / 会话正忙 / 没有新内容 / 已关闭 / 失败),结果提示 5 秒后自动消失。

记忆设置面板

语言 / Language

设置面板与侧栏 label 跟随 DSH 自己的语言设置(设置 → 通用设置 → 语言),切换即时生效、无需重启:中文显示「记忆」,英文显示 "Memory"。面板文案通过 ctx.locale 注册在 memory 命名空间,renderer 会给区块注入 t(key) 座位,于是表单、健康看板、图谱卡片整体重渲染为当前语言;侧栏图标的认领键取自同一个 label 解析函数,语言切换后自动重新认领正确的那一行。健康看板的四项体检 label 与 detail 由 insights 路由新增的结构化 data 在客户端按语言重建(lib/insights.js 仍保留中文 label/detail 供 CLI 使用);旧版本宿主 payload 没有 data 时回退中文串。用户内容(页面标题、log.md 动态)与 CLI 自身的中文输出属于内容而非界面文案,不在翻译范围内。

兼容性

package.json 声明 engines.dsh: "^0.2.1-rc.5",宿主用 semver.satisfies(..., { includePrerelease: true }) 强制校验——prerelease 细节很关键:^0.2.1 / >=0.2.0 匹配不上 0.2.1-rc.5,只有显式写 prerelease range 才认(它同时覆盖将来的 0.2.1 正式版)。一路走来有两个宿主服务变成「承重」的:locale 服务(0.11+:面板通过它注册语言包)与会话投影(0.12+:发言权仲裁与成长回顾要读)。旧宿主会被闸门直接拒绝加载,而不是让插件半残运行;运行期缺投影则优雅降级——不发回顾、不抢发言权,绝不崩。

boot 预算分配(为什么 index 不会先被截掉)

bootMaxChars 是总预算,按文件分配而不是简单平摊:

1. 有 bootFileBudgets 条目的文件先拿自己的额度;
2. 其余文件各拿一份均分额度,以文件实际大小封顶——短文件把用不完的额度退回池子;
3. 池子再补给「还有内容没注入」的文件(每个最多补到均分额度的两倍):index.md 优先,其余按体积降序——所以截断落在哪个文件上由体积决定,而不是由 bootFiles 的书写顺序决定。

当每个文件都小于均分额度时,结果与旧的平摊规则逐字节相同;只有记忆库长大后才会不同。这也意味着:index.md 是路由表,别把它写成摘要表——它会被完整注入,膨胀的代价是挤掉人格与其他记忆。

「当前激活会话」怎么判? DSH 宿主侧没有「浏览器当前聚焦的会话」信号(激活会话是前端概念)。插件用最近一次收到真实用户消息的 live root agent作为激活会话的代理:你在哪个会话里说话,哪个会话就激活;切到别处但不发消息时,宿主感知不到「切换」这个动作(这是代理的已知边界)。若日后需要精确到「展开/聚焦」级别,需补一小段客户端 focus 上报。

记忆健康看板

设置页「记忆 / Memory」区块下方是一张只读看板,数据来自 GET /api/memory/insights:

| 展示 | 含义 |
| 记忆页 / index 路由 / 记忆字数 | 记忆库规模(字数按字符计,中文不会按字节虚高 3 倍) |
| 健康分(0–100) | 由孤儿页、缺 salience/type、陈旧页、失效 index 链接加权得出 |
| 访问新鲜度条形图 | 今天 / ≤7 / ≤30 / ≤90 / >90 天 / 从未戳记(驱动自 last_access 自动戳记) |
| 四项体检 | index 链接完整、无孤儿页、frontmatter 完整、访问新鲜度——口径与 dsh-memory lint 同源 |
| 陈旧页候选 | 超过 90 天或从未戳记的页面,按陈旧度排序(前 8) |
| 最近动态 | log.md 最新 5 条标题 |

看板是实时的:每次打开设置页(或改完任一配置项后)重新拉取,不落盘、不缓存、不写记忆库。头less 部署没有 webServer 时该路由与看板都不存在,其余功能不受影响。

覆盖 composition 键(例如把 boot 块预算调大),在 profile 的 cordis.patch.yml 里写不带 insert 的 id 覆盖条目:

- id: dsh-memory
config:
bootMaxChars: 12000

实现说明:DSH 的 settings wire 只服务硬编码的命名空间白名单,插件命名空间写不进去,因此本插件走自建通道——配置存 <dshHome>/memory.json(schema 校验 + 原子落盘),由插件自注册的 GET/POST /api/memory/config 路由服务,客户端区块 fetch 直连。

首次使用:铸魂

插件安装后,第一次会话里 agent 的首要任务不是干活,而是与你对话定义它的灵魂:名字、性格、价值观、语气、边界,以及你的身份与你们的关系。逐项确认并写回 SOUL.md / user/profile.md,直到 BOOTSTRAP.md 的 status 变为 complete。你可以随时跳过或暂缓。

四个操作

- remember(记):把值得持久化的内容蒸馏成页面,同步更新 index.md、追加 log.md。维护 index 时守路由表纪律:一行一页、一句话摘要(≤ 80 字)+ salience。
- recall(忆):会话开始读 boot 块;查询时先查 index.md 再钻页;必要时 dsh-memory search <关键词>。
- consolidate(整理):dsh-memory lint 查矛盾、孤儿页、该归档的冷页;dsh-memory status 看陈旧页分布。
- forget(忘):显式遗忘立即执行;自动衰减按 salience + last_access(冷页优先归档)。last_access 由插件自动维护,无需手改。

CLI

dsh-memory init [dir] # 创建记忆库脚手架 dsh-memory search <query> [--touch] # 全文检索(--touch 给命中页戳 last_access) dsh-memory touch [pages...] # 手动戳 last_access(不带参数 = 全部页面) dsh-memory graph --suggest # 关系报告 + 「这页该连谁」建议(只报告) dsh-memory query --type decision --tag dsh --hot --stale [关键词] 结构化检索:先按 frontmatter 过滤,再匹配正文 dsh-memory checkup [--boot=N] # 一份报告:体量/新鲜度/连接度/一致性 + 优先级行动清单 dsh-memory lint # 完整性体检 dsh-memory doctor # 环境体检:脚手架 / 孤儿页 / git / boot 预算,并给出修复命令 dsh-memory status # 健康概览(含陈旧页统计) dsh-memory pack [out.tar.gz] # 打包导出(含 manifest) dsh-memory unpack <archive> [--force] # 从归档恢复

存储定位顺序:$MEMORY_DIR → ./.memory(存在时)→ ~/.memory。

迁移

记忆库是纯 markdown + git:拷贝即迁移。跨机器 / 跨 agent / 能力降级档位见 docs/MIGRATION.md。

常见问题

Q:和手写的 .dsh/skills/memory 文件技能(skill 版)什么关系? skill 版是"软保障"(技能目录只注入简介,正文靠模型主动加载);本插件是"硬保障"(boot 块随系统提示词运行时上下文自动注入)。两者可共存:文件技能(rank 100)会覆盖插件内嵌技能(rank 250)的协议。如果你之前为了软保障改过系统提示词 persona(如 profile 补丁里的开机指令),装上本插件后建议移除那段 persona,避免双份注入。

Q:boot 块会不会每次请求都重复注入、烧 token? 不会。运行时上下文按投影去重:内容不变只注入一次;记忆更新后新快照取代旧快照。

Q:记忆库放在哪里最合适? 默认 ~/.memory(全局、跨项目)。需要按项目隔离时,把 memoryDir 配到项目内,或让 agent 在项目里维护 .memory/。

Q:可以加密吗? 记忆含敏感内容时,可把 memoryDir 放进加密卷 / 私有仓库。格式不变,插件无感知。

开发

git clone https://github.com/LittleBlackTong/dsh-plugin-memory.git
cd dsh-plugin-memory
node scripts/memory.mjs --self-test # 冒烟测试(无需安装依赖)

零构建:lib/ 直接是运行时代码,lib/types/index.d.ts 供 TS 消费方使用。boot.js / scaffold.js 只依赖 node:* 内置模块,可独立复用。

测试用 node --test,当前共 260 个用例;CI(.github/workflows/ci.yml)会连同 node scripts/memory.mjs --self-test 一起跑。贡献与安全策略见 CONTRIBUTING.md、SECURITY.md;跨插件发言协议见 docs/PROACTIVE-PROTOCOL.md。

路线图

- [ ] TypeScript 重写(带完整类型与构建步骤)
- [ ] embedding/BM25 检索(规模超过几百页后替代 index 先行)
- [ ] MCP server(让非 DSH 的 agent 也能用同一套记忆库)
- [ ] GitHub Actions CI(跑 --self-test 与 lint)
- [ ] 记忆加密存储选项

欢迎在 Issues 里提需求、报 bug、交 PR。

License

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

DeepSeek Harness 长期记忆插件:跨会话、可迁移、带「灵魂」的 markdown 记忆库。

回到顶部