给 DeepSeek Harness DSH 的渐进式长期记忆 —— 记忆以分片的纯 markdown…
32.9/100
综合分
4
GitHub 星标
216
npm 周下载
—/5
用户评分
数据截至 2026/10/27 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add @jipika/dsh-memorynpm 源 · @jipika/dsh-memory 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · chat
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 5 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包@jipika/dsh-memory @ 0.6.0
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/18 00:25:16
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 53 处,展示前 22 条)
- 读取本地文件index.js:544
return normalizeSettings(JSON.parse(readFileSync(SETTINGS_PATH, "utf8"))); - 写入本地文件index.js:558
mkdirSync(`${HOME}/.dsh/memory`, { recursive: true }); - 写入本地文件index.js:559
writeFileSync(SETTINGS_PATH, `${JSON.stringify(next, null, 2)}\n`); - 读取本地文件index.js:606
return existsSync(path) ? readFileSync(path, "utf8") : ""; - 读取本地文件index.js:606
return existsSync(path) ? readFileSync(path, "utf8") : ""; - 读取本地文件index.js:818
for (const name of readdirSync(shardDir).sort()) { - 读取本地文件index.js:822
if (statSync(path).isFile()) files.push({ name: name.replace(/ md$/, ""), path }); - 读取本地文件index.js:833
if (existsSync(singleFile)) { - 写入本地文件index.js:1843
mkdirSync(dirname(normalizeToolPath(path)), { recursive: true }); - 写入本地文件index.js:1846
copyFileSync(path, `${path}.bak`); - 写入本地文件index.js:1853
writeFileSync(tmp, text, { mode }); - 写入本地文件index.js:1854
chmodSync(tmp, mode); - 删除本地文件index.js:1859
rmSync(tmp, { force: true }); - 执行外部命令来自配置/变量index.js:2398
const child = execFile(shell.file, shell.args, { timeout: timeoutMs, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => { - 删除本地文件index.js:2416
rmSync(`${path}.1`, { force: true }); - 执行外部命令来自配置/变量index.js:2794
const r = spawnSync(shell.file, shell.args, { - 删除本地文件index.js:3026
rmSync(resolved); - 发起网络请求client.js:161
return fetch("/dsh-memory/settings", { headers: { accept: "application/json" } }) - 发起网络请求client.js:177
return fetch("/dsh-memory/settings", { - 发起网络请求client.js:310
fetch("/dsh-memory/content?target=" + encodeURIComponent(target)) - 发起网络请求client.js:340
fetch("/dsh-memory/write", { - 发起网络请求client.js:897
fetch("/dsh-memory/activity?turn=" + number + sid, { headers: { accept: "application/json" } })
扫描于 2026/10/31(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 两层长期记忆
- 全局与项目级记忆
- 实时重读记忆
给 DeepSeek Harness(DSH)加两层长期记忆:全局和按项目各一份 markdown,实时重读,不额外消耗 LLM 成本,还带设置面板。
README
由 DeepSeek 最新模型翻译生成dsh-memory
给 DeepSeek Harness (DSH) 的渐进式长期记忆 —— 记忆以分片的纯 markdown 形式存放,只有一张实时生成的地图(片 · 关键词 · 条数 · 最新日期)会进入系统提示词,所以无论记忆长到多大,提示词开销都保持恒定;条目按需取回,先用 memory_search 再 read(grep 只用于精确正则)。两层结构(全局层 + 项目层)外加全局规则文件,在 DSH 设置里就能查看和编辑,零额外 LLM 成本,写完即生效。
给 DeepSeek Harness 的渐进式长期记忆:正文是分片的纯 markdown,进系统提示词的只有实时生成的片级地图(片 · 关键词 · 条数 · 最新日期),条目先调 memory_search 取线索再 read 取回(grep 只留给精确正则) —— 记忆再长、提示词开销也恒定。两层结构(全局层 + 项目层)外加全局规则文件,在设置里就能查看并编辑、不产生任何额外 LLM 调用、写完即生效。
拥有:~/.dsh/memory/* 的读写、每轮提示词注入与索引生成;tools/pre-execute / tools/post-execute 上的记忆写入守卫与审计日志。
展开完整文档(README 剩余 1 节)
dsh-memory
给 DeepSeek Harness (DSH) 的渐进式长期记忆 —— 记忆以分片的纯 markdown 形式存放,只有一张实时生成的地图(片 · 关键词 · 条数 · 最新日期)会进入系统提示词,所以无论记忆长到多大,提示词开销都保持恒定;条目按需取回,先用 memory_search 再 read(grep 只用于精确正则)。两层结构(全局层 + 项目层)外加全局规则文件,在 DSH 设置里就能查看和编辑,零额外 LLM 成本,写完即生效。
给 DeepSeek Harness 的渐进式长期记忆:正文是分片的纯 markdown,进系统提示词的只有实时生成的片级地图(片 · 关键词 · 条数 · 最新日期),条目先调 memory_search 取线索再 read 取回(grep 只留给精确正则) —— 记忆再长、提示词开销也恒定。两层结构(全局层 + 项目层)外加全局规则文件,在设置里就能查看并编辑、不产生任何额外 LLM 调用、写完即生效。
拥有:~/.dsh/memory/* 的读写、每轮提示词注入与索引生成;tools/pre-execute / tools/post-execute 上的记忆写入守卫与审计日志。
冲突时:与 trinity-hooks(dsh-hooks-claude-code 桥)同为 tools/pre-execute 监听者 —— 本插件只审记忆路径,两者互不翻案;与官方 compaction 无交集。
回滚:从 dsh.profile.bundles 去掉 @jipika/dsh-memory + 重启应用。
它解决什么
智能体每次会话都是失忆的。让记忆活下来通常只有两条路:
| 做法 | 代价 |
| 把记忆塞进系统提示词 | 需要「谁来写、写到哪、怎么注入」的一整套机制 |
| 用 LLM 从会话里提炼事实 | 每轮会话都在后台烧钱,且内容进了别人的向量库 |
本插件的取舍是:记忆就是几个 markdown 文件,注入走 DSH 原生的提示词 seam,写入由当前会话的智能体顺手完成 —— 于是它既不花钱,也不出本机,还能被 git 跟踪、被人手编辑。
特性
- 两层:~/.dsh/memory/topics/.md(全局,按主题分片:用户偏好 / 环境事实 / 通用坑……)
+ 按工作区分级的项目层,切换项目自动切换内容。
- 零 LLM 成本:插件本身不调用任何模型,只读文件;写记忆用的是会话已经在跑的那个模型。
- 写完即生效:注册的是函数式 text,每次提示词组装都重读文件 —— 不需要重启、不需要刷新。
(对比:cordis.patch.yml 里 personaPrefix 的 !!js 只在 boot 求值一次。)
- 渐进式注入,记忆再长也不炸上下文:默认地图模式 —— 提示词里只有片级地图 「片名 · 条数 · 体量 · 最新日期 · 关键词」,条目标题与正文都不进提示词;智能体先调 memory_search 取线索,命中行自带行号,再按行 read 取那一条(无命中 / 精确正则才用 grep)。条目 ≤ 12 条的层(典型是项目层单文件)仍逐条内联 —— 这类层只有几百字符,内联出来比多跑一次检索更值。
- 注入头不放动态统计(KV cache 友好):每段的头行只写档位名(… · 地图模式。),片数 / 条数 / 字符数一律挪到该段最后一行()。
因为注入段位于 system prompt 很靠前的位置,而这三个数字每写一条记忆就会变 —— 放在头行会让 整个请求前缀失去服务商的 KV cache(实测:写一条记忆后,同款子代理委派的首轮 cacheRead 从 85% 掉回 0%)。挪到段尾后,一次写入只让「被改的那一片之后」失效(同条件实测公共前缀 0 → 3875 字符)。片级统计行本身仍是动态的 —— 那是路由信息,刻意保留。
实测:58k 字符的单文件拆成 10 片后,每轮注入从 29931 → 约 6.6k 字符; 35 片 / 333 条 / 233k 字符的真实库:地图模式 7.3k 字符,索引模式 12.9k 字符。
- 四档注入方式(设置 → 记忆 →「注入方式」):
- 地图模式(默认):只给片级地图,条目靠 memory_search + 按行 read 按需取 —— 注入最省,且路由线索(片名 + 关键词)一条不少:没有它,智能体不知道自己缺什么,也就不会去查。
代价是地图随片数线性增长(实测本机 188 片 = 38885 字符 / 每轮;即便只留最新 10% 的片也仍要 4059 字符),片一多就是每轮的固定开销。
- 无地图(mapless,v0.9.4):赌注换到「检索时现算线索」—— 全局层的常驻地图完全不进 提示词,只留一段固定 416 字符的指针块;片级线索改由 memory_search 按 query 现算,落在返回的「相关记忆片」段(top8:片名 + 关键词 + 沾词 + 最新日期 + 超建议标记)。实测本机 10 组真实任务(同一批对照):条目级检索 6/10 找对片,桥 10/10,且条目漏掉的那 4 组桥全部在 #1 捞回。机制差别在命中门槛 —— 条目级要求正文字面命中,片级只要求主题*命中,所以中文口语的任务措辞(「外网连回」)对上英文专名片名(dsh-remote-access)只有后者跨得过。
⚠ 但这 10/10 是补过词之后的数:有一组任务的落点在那片正文里、而首行关键词没写「重启 / 生效」,桥就到不了(桥只读片名 + 首行关键词)。所以本档把一条旧纪律抬成了硬要求 —— 首行 里的关键词必须覆盖「用户会怎么问这件事」的中文口语说法,只堆英文专名和文件名不够。地图模式下漏一个词只是少一行线索,本档下它等于那片不可路由。
收益:注入量与记忆规模无关(本机整段 40416 → 1976 字符,省 95%),也不再因写一条记忆就作废 KV 前缀。代价:桥平均 1378 字符,只在真检索那一轮付,且多一次 round-trip。
项目层不受本档影响(照旧注入 —— 它只有几条到几十条,砍它不划算)。
⚠ 本档的前提是「模型会主动搜」。现有证据只是间接的(map 档下、有搜索行为的会话 91% 在 turn 1 就搜),去掉地图后的触发率没有实测。所以默认档仍是 map;切到 mapless 后要自己回看(~/.dsh/sessions 里数 memory_search 的 tool/call 事件)。
- 索引模式:地图 + 每片最新 N 条「标题 · 行号」,超「索引预算」(默认 12000 字符,面板可调 6K/12K/18K/24K/不限制)时沿 [8,5,3,2,1] 逐档收缩为每片最新 N 条,每片都保留线索(关键词 + 最新日期 + 「更早的 M 条在第 X–Y 行」),而不是把整层压成一行「N 条;索引预算已满」。旧行为在 27 片规模下会让所有分片同时失去标题 —— 智能体因此没有任何理由去 read,表现就是「插件明明在注入,却不读记忆」。
- 全文注入:分片正文整篇进提示词,总量超过字符上限时自动回退索引模式并说明原因(分片规模下通常都会回退)。
- 跨片检索交给子代理,并优先用低成本的专用检索子代理:注入的纪律块写明分级 —— 1–2 条自己 read;要同时读 ≥3 条 / 跨 ≥2 片 / 某片正文 ≥3k 字符时派检索子代理,只让它回「事实 + 文件与行号」,原文留在子代理的上下文里。工具表里存在 subagent_memory 时优先用它 —— 那是给检索挂的专属委派工具(固定低档思考强度、工具面白名单只留 read/grep/glob),没有该工具时退回通用 subagent。
- 片级关键词 = 最便宜的检索线索:分片首行注释 里 ·(或 :)之后那段会进地图/索引,对齐 Claude Skills 用 description 决定「要不要加载」的思路。
- memory_search 词法检索工具(v0.8):注册进 Agent 工具面 —— 中文自动 bigram 切分(「插件开发」→ 插件/件开/开发)、token 覆盖度门槛(query 足够聚焦后才要求更广覆盖)、相关度排序 + 日期并列。纯同步文件扫描(百片毫秒级),零依赖无索引;命中行号即纪律里的 L 号。词法层思路移植自 dsh-mnemon(MIT)。
- 检索排序修复(v0.9.1):记忆长到 150+ 片 / 3000+ 条后暴露出三个结构性缺陷,都已改: ① 片级加分不再抬条目分 —— 旧实现把片名 +12 / 关键词 +7 / 每 token 片名 +4 写在条目循环内部,与条目内容无关,于是同一片所有命中条目分数完全相同,排序退化成「日期 → 行号」;现在片级只作同分 tie-break(SHARD_BONUS_PHRASE / SHARD_BONUS_KEYWORD),覆盖度门槛也只由条目正文贡献(旧片段名里的 token 能单独满足门槛,架空条目级判定)。
② 每片限额(PER_SHARD_MAX = 2)—— 旧 slice(0, limit) 让一片吃掉全部名额(实测 8/8 同片);名额铺不满时放宽回填,所以 dropped.capped / dropped.backfilled 要一起看,只看 capped 会误判丢失量。
③ 作用域收窄(scope,默认 current)—— 旧实现扫 projects/ 下全部 slug,其它项目的记忆占池 60% 却不在地图里、模型无从路由,实测 top8 能被别的项目 8/8 占满。现在默认只检全局层 + 当前 cwd 所属项目层;cwd 取不到(探针直调、无会话上下文)自动退回全库,scope:"all" 显式跨项目。少给的结果必须可见:跳过的层/片数进 scanned 与 dropped,并由 render 文案写明出路。
- 检索打分修复(v0.9.2 · P1):片级偏置去掉后暴露出条目分只有「整数计数」的问题(同分块反而从 4.38 涨到 5.13),四项改动一起把区分度找回来。实测本机 8 个 query(limit=8):覆盖片数 2.88 → 5.75、最大同分块 4.38 → 1.13、预览含命中词 67% → 100%、单次耗时 30ms → 19ms。
① IDF 加权 —— 条目分 = Σ ln(1 + N/df(t)),稀有词命中比常见词命中更值钱(N=池内条目数)。
② df=0 的碎片剔出门槛分母 —— 中文 bigram 的跨词碎片(「磁盘空间」→「盘空」)语料里不存在,旧版把它计入 token 数会让门槛虚高、误毙只命中 1 个真词的条目。lexicalRequiredMatchCount 本体不改(既有单测仍成立),只改它的输入;query 全是碎片时门槛显式取 1。
③ 长度归一 score / (1 + SEARCH_LENGTH_B·(len/avgLen − 1)) —— 命中数是「免费的」,条目越长分数越高(实测一条 808 字符的诊断条目几乎霸榜所有 query)。b=0.5 实测最优。
④ 锚点预览窗口 —— 旧版一律从条目开头截 240 字符,实测 48% 的预览一个命中词都没有 (90% 条目超 240 字符),模型看不出「这条为什么命中」只能整片 read。现在以命中的最稀有 token 为锚点取 ±80 窗口,窗口不在开头时用 … 标记;锚点在原文 indexOf 失配则降级到次稀有者。
打分改为两遍扫描(pass1 统计 df/avgLen,pass2 打分),池内驻留约 1.4MB 瞬态文本,无缓存层。
已知边界:严格门槛下不做渐进松弛兜底(本机 8 query 实测空手数为 0,未触发就不加复杂度)。
- 回合记忆栏(v0.8):每条 AI 回复底部出现「本回合记忆 · 读取 N · 写入 M」折叠条(挂 DSH 公开的 conversation.chat.turnTail 插槽),展开列出这一轮实际读取/写入了哪些记忆文件,点击用 DSH 自带预览直达原文。数据来自 session 持久日志的增量投影 —— 不挂事件监听,对插件激活前就存在的会话同样生效;每会话环形保留最近 50 回合。设置 → 记忆里可关(关掉不渲染,数据照记)。
- 设置面板可读可写:DSH 设置里多一个「记忆」分栏 —— 每层一个开关,点标题即可展开正文,还能就地把改动保存回文件(保存前自动留一份 .bak)。全局层按片一个页签;同一个分栏里可以查看并编辑全局规则文件 ~/.dsh/AGENTS.md。
- 明文、可移植:就是 markdown。可以 git、可以 diff、可以手改;删掉文件即停用(段渲染为空串,被 renderPrompt() 过滤,零残留)。
安装
从 npm(推荐)
已发布为 @jipika/dsh-memory,也收录在 DSH 1024Store 目录里:
dsh plugin --profile web add @jipika/dsh-memory
本插件自带 cordis.patch.yml(package.json 里声明了 dsh.bundle.patch),安装时 DSH 会自动把它挂进 profile 的组合树,不需要手工编辑任何 profile 文件。
从源码(想改代码时)
git clone https://github.com/jipika/dsh-memory.git ~/.dsh/local-plugins/dsh-memory
然后以 link: 方式挂进 profile(以 desktop 为例):
1) 依赖(~/.dsh/profiles/desktop/package.json) "@jipika/dsh-memory": "link:../../local-plugins/dsh-memory"
2) 挂载(~/.dsh/profiles/desktop/cordis.patch.yml 末尾追加)
- insert:
- id: dsh-memory
name: '@jipika/dsh-memory'
cd ~/.dsh/profiles/desktop && pnpm install
装完后若是 link: 方式,pnpm install 会重建 node_modules 链接并把 HMR 的基线路径换掉 —— 建议重启一次 DSH(host 半在 boot 时加载)。之后改记忆、翻开关都不再需要重启。
记忆文件布局
~/.dsh/memory/topics/ ← 全局层:按主题分片(新记忆写这里) ├── preferences.md ← 用户偏好 ├── dsh-platform.md ← 环境 / 平台事实 └── … ← 想加几片就加几片,索引自动收录 ~/.dsh/memory.md ← 导航索引(人读用;注入不读它) ~/.dsh/AGENTS.md ← 全局规则(新会话注入,面板里也能编辑) ~/.dsh/memory/projects/ ├── --Users-me-code-repo--/ ← 项目层:目录形式 │ ├── MEMORY.md ← 索引(同样只注入索引 + 行号) │ ├── some-topic.md ← 主题正文,按需读 │ └── another-topic.md └── --Users-me-other-repo--.md ← 项目层:单文件形式(同样支持)
两种形态都支持:有分片目录就按分片走,没有就回落到单文件 —— 所以从单文件迁移到分片(或反过来)都不需要改配置。分片的片名优先取文件里的 H1 标题。
项目层两种形态的注入方式刻意不同(这点踩过坑):单文件形式的内容本身就是条目集合
(- [日期] 事实),所以走索引化、只给标题与行号;目录形式里 MEMORY.md 已经是人写的
索引、其余 .md 是带 frontmatter 的正文段落,所以直接注入 MEMORY.md 正文(标题 + 链接 + 摘要,超长时仍走 clamp),另外只列出没被索引登记的正文文件 —— 顺带当索引一致性检查用。
反过来对正文再套一遍条目索引,只会退化成一张纯路径清单(实测某项目 74 篇正文被压成 14340 字符的文件列表,而 MEMORY.md 的摘要一条都没进来)。
从单文件迁移过来:tools/migrate-to-shards.mjs 是当时用的迁移脚本(人工语义分类 + 生成导航文件 + 先备份),可以按自己的分类改写它。
项目层目录名由会话的 cwd 推出,规则与 DSH 自己的会话目录一致——路径里的 / 换成 -,两端各加 --:
/Users/me/code/repo → --Users-me-code-repo--
所以 ~/.dsh/sessions/--Users-me-code-repo--/ 与 ~/.dsh/memory/projects/--Users-me-code-repo--/ 是同一个项目的两份数据,一眼能对上。
记忆怎么写
由智能体顺手写(这也是「零 LLM 成本」的来源:写入搭的是本来就跑着的会话)。建议在 ~/.dsh/AGENTS.md 里放几条纪律,例如:
Memory upkeep (long-term memory):
- TWO layers, both injected on every assembly (no restart needed):
· GLOBAL ~/.dsh/memory/topics/.md — facts that would bite in ANY repo, sharded by topic (each file starts with a comment saying what belongs there). ~/.dsh/memory.md is only a navigation index — never append memory to it.
· PROJECT ~/.dsh/memory/projects/--<cwd-with-slashes-as-dashes>--.md — facts that only hold in this workspace.
- What the prompt shows is an INDEX (shard · count · path), not the full text: read that shard when you need the detail. Index lines carry line numbers.
- Append one line - [YYYY-MM-DD] fact under the matching section, in the SAME
turn you learn it. Do not batch for "later". Keep each shard small (~12k chars)
— when one outgrows that, split it by sub-topic in the same directory.
- Do NOT log narration or anything re-derivable from the repo.
- NEVER put secrets in it — both layers ride every request to the model provider.
- Record what IS true, never a removal: "X was uninstalled" is stale the moment
it is written and taxes every future session.
设置面板
设置里会出现一个「记忆」分栏,三组内容:
| 分组 | 条目 | 可编辑的文件 |
| 全局规则文件 | AGENTS.md(无开关) | ~/.dsh/AGENTS.md |
| 全局层(跨项目) | 全局记忆(带开关) | ~/.dsh/memory/topics/.md(一片一个页签) |
| 项目层(按工作区) | 每个项目一行(带开关) | 该项目目录下的全部 .md(MEMORY.md + 主题文件) |
- 点任意一行展开:先看到只读正文,点「编辑」变成可写的编辑器,⌘/Ctrl + S 或「保存」写回文件。
- 一层里有多个文件时(项目记忆的主题文件),用页签切换;未保存的页签带 ●。
- 保存是原子写(先写临时文件再 rename),改动前的内容自动留一份 <文件名>.bak;新建的文件用 0600,已有文件沿用原权限。
- AGENTS.md 的生效时机(面板底部也写了):它在每个新会话开头注入,改完对新会话生效。
- 记忆两层是每次提示词组装时实时重读的:保存后下一条消息就带上新内容,不需要重启。
- 关掉的那一层不再注入;再次开启立即重新读取文件。
- 开关存在 ~/.dsh/memory/settings.json(与记忆正文同根,面板改、手改文件都即刻生效):
{
"globalEnabled": true,
"projectEnabled": { "--Users-me-code-repo--": false },
"injectMode": "map",
"indexBudget": 12000,
"maxChars": 24000
}
- injectMode:map(默认,片级地图 + memory_search 按需取条目)/ mapless(全局地图不注入,片级线索改由 memory_search 返回的「相关记忆片」桥现算,见上)/ index(地图 + 逐条标题)/ full(正文整篇注入)。
- indexBudget:索引模式每轮注入的字符预算(0 = 不限制 → 始终全量逐条)。
超预算时按 [8,5,3,2,1] 逐档收缩为「每片最新 N 条」,不会整层归零。
- maxChars:正文上限,管两件事 —— full 模式的分片总量闸门、项目层 MEMORY.md 的 clamp。
钩子(v0.5.0)
「该不该写记忆 / 有没有好好读记忆」不再只靠提示词劝 —— 钩子直接挂在 DSH 原生工具管线(tools/pre-execute / tools/post-execute,与官方 dsh-hooks-claude-code 桥同一批事件)上,在动作发生时硬性判定:
写入钩子(write-guard,tools/pre-execute)
智能体用 write / edit 触碰记忆管辖路径(~/.dsh/memory/、~/.dsh/memory.md、~/.dsh/AGENTS.md)时,先跑内置规则引擎,deny 的原因会成为这次工具调用的 Error 结果、直接回到模型面前:
| 规则 | 判定 |
| 碰插件内部资产(.bak / audit.log / 记忆目录里的非 md) | deny |
| 往 memory.md(现役纯导航索引)写正文条目 | deny + 指路 topics/ |
| 新条目格式不是 - [YYYY-MM-DD] 事实(或日期非法/在未来) | deny + 给出正确格式 |
| 新增内容疑似凭据(sk- / AKIA / ghp_ / Bearer / password= 等 8 类) | deny(只报行号与模式名,不回显值) |
| write 整文件覆写导致既有条目变少 | deny + 要求改追加/edit |
| bash 重定向/tee/sed -i 改记忆文件 | ask(转人工确认);只认真正落盘的目标 —— 2>/dev/null、>&2 这类 fd 复制与丢向 /dev/null 不算,只读命令不会被误拦 |
| 过时陈述(「已卸载/不再使用」式)、单条超长 | allow + 审计 note |
writeGuard: "full" 时在内置规则之上再跑一个外部判定命令(writeHookCommand): stdin 收到完整 payload JSON(tool/path/layer/oldText/newText/added/removed…), exit 2 = 拒绝(stderr 为原因)、exit 0 + stdout JSON {decision, reason} 出决策(兼容 hookSpecificOutput.permissionDecision)—— 所以「这条值不值得记」的语义判断可以接任意 LLM 脚本,不再受提示词约束力上限的限制。决策合并取最严(deny > ask > allow),钩子故障放行不误伤。
审计与读取钩子(tools/post-execute)
- 写入审计:每次记忆写入落盘后追加一行 JSONL 到 ~/.dsh/memory/audit.log (时间/工具/路径/增删行数/判定),超 2MB 自动轮转 .1;落盘内容再校验一遍,条目格式有问题时附加提醒让模型立即自修。
- 读取提醒:read 命中记忆文件时附加一条上下文:条目数/字符数、按行号引用、禁止凭索引臆测、发现过时就地修正。
注入层约束(读取侧)
- 记忆纪律块:注入正文末尾强制附加一段「纪律」(值不值得记才写、格式、钩子会拒绝什么、按行号读)——提示词约束还在,但背后有钩子兜底。
- 注入过滤命令(可选,injectHookCommand):组装好的注入文本过一遍外部命令(stdin 进、stdout 出,失败/空输出回落原文),可做脱敏或按需裁剪。
开关
设置 → 记忆 → 「写入 / 读取钩子」区块(或手改 ~/.dsh/memory/settings.json):
{
"writeGuard": "rules", // rules(默认,内置规则)| full(规则+外部命令)| off "writeHookCommand": "", // writeGuard: full 时的外部判定命令 "hookTimeoutMs": 10000, // 外部命令超时 "discipline": true, // 注入层记忆纪律块 "readReminder": true, // 读取记忆文件时的附加提醒 "audit": true, // 写入审计 audit.log "injectHookCommand": "" // 注入文本过滤命令(留空不过滤) }
设计参考
「只注入索引、正文按需读」不是本插件发明的,主流做法高度一致 —— 下面是逐条对照:
| 来源 | 采用的部分 |
| Claude Code 记忆文档 | 「只放每次会话都该带的事实」;单文件 200 行以内(本插件用 SHARD_WARN_CHARS 12000 字符的同量级阈值);渐进式披露 = 给地图而不是倾倒正文 |
| 渐进式披露 / .claude/rules/ 目录 | 主题分片 + 每片一个语义边界;优先给指针而不是副本 —— 注入的是路径 + 行号,正文永远只有一份 |
| Claude Skills 的 description 路由 | 片级关键词进索引,把「要不要读这片」变成可判断的决策,而不是靠猜片名 |
| Cline Memory Bank | 把「何时读记忆」写进强制指令(本插件纪律块的【开工先查】),而不是指望模型自觉 |
| Anthropic memory tool | 写入判据与去重纪律 —— 本插件把它从「提示词建议」升级成 write-guard 钩子的 deny/ask |
与它们的差异:不做向量检索、不调用任何模型、不落第二份数据。记忆就是 markdown,检索 = 索引 + 行号 + read,所以零额外成本、可 git、可手改、可离线迁移。
工作原理
ctx.systemPrompt.section({
name: "dsh-memory:long-term",
order: 1, // 紧跟 DEPLOYMENT_PERSONA_PREFIX(order 0),先于工具规范 text: compose, // ← 函数:每次 assemble() 重新求值 });
compose() 按当前 cwd 决定注入哪些层,并遵守开关;任一层不可用(文件缺失 / 开关关闭)就返回空串,
而空段会被 dsh-system-prompt 的 renderPrompt() 过滤掉。
插件同时注册:
- 开关存在插件自持的 ~/.dsh/memory/settings.json:注入与钩子每次现读,面板改完即刻生效,也不经过会换代的宿主设置 API;已知项目清单由 host 直接扫盘,不落第二份状态。
- 三条 HTTP 路由(读写共用同一个白名单解析函数,越不出记忆目录与规则文件): GET /dsh-memory/content?target=global|rules|<slug>[&file=<名>][&format=text] POST /dsh-memory/write { target, file, text } # 需请求头 x-dsh-memory: 1 GET /dsh-memory/settings # 开关当前值 + 已知项目 POST /dsh-memory/settings { patch } # 合并写开关,需请求头 x-dsh-memory: 1
target 只认三种值:global(~/.dsh/memory/topics/.md,没有分片时回落到 ~/.dsh/memory.md)、 rules(~/.dsh/ 下的两个规则文件)、或形如 --Users-me-code-repo-- 的项目 slug;file 只认该层里真实存在的 .md(或新建 MEMORY.md)。
写路由要求带 x-dsh-memory: 1 —— 浏览器跨站简单请求带不上自定义头,省掉一整类 CSRF。
卡片记忆层(v0.9.0)
角色卡召唤会话(dsh-card-summon)里的剧情/世界观事实,不是「用户与仓库的事实」。混进长期记忆,会在后续所有会话里常驻注入 —— 这一层的职责是把召唤会话与常规记忆隔离:
- 注入:当前会话在 ~/.dsh/card-summon/summons.json 有记录(含阵容 cast)→ compose 不注入全局层 / 项目层 / 纪律块,改注入「屏蔽说明 + 本卡卡片记忆正文」;阵容会话展示各成员卡的记忆并分摊预算。开关 cardsEnabled(默认 true)。
- 写入:writeGuard 对召唤会话的全局 topics / 项目层 / 规则文件一律 deny(bash 命中同样 deny), deny 理由里带本卡记忆路径;只有 ~/.dsh/memory/cards/<cardSlug>.md 照常走完整规则(条目格式、凭据拦截、破坏性覆盖保护都在)。召唤状态判定读 dsh-card-summon 的落盘 store,只读不改;DSH_CARD_SUMMON_STATE 与其 state.js 同名同语义(测试隔离)。
- 清除:GET /dsh-memory/cards(列表,name 从文件头注释 解析,缺失时兜底用 slug)与 POST /dsh-memory/cards/delete { slug }(需要 x-dsh-memory: 1 头; slug 白名单 + resolve 前缀 + 拒 symlink;删除前全文进 audit.log —— 唯一回滚点)。
dsh-card-summon 召唤面板的「清除卡片记忆」按钮走的就是这两条路由。
验证
node tests/probe.mjs
断言(条数以 node tests/probe.mjs 的实际输出为准,这里不写死 —— 会随功能增长漂移),全部在临时 HOME 里跑(自造记忆文件,不碰你的真实数据),覆盖:两层注入、 cwd→slug 推导、无 agent 时只注入全局、两个开关的开/关/重开、地图模式(默认值 = map、片级地图不含条目标题、头注释给出 memory_search 检索指引、层内 ≤ 12 条仍逐条内联且头注释仍标「地图模式」、目录形态的项目层不受档位影响、纪律块按档位切换:地图档教 memory_search、索引档同样先教 memory_search、full 档说明正文已注入、检索分级写明优先 subagent_memory)、无地图模式(指针块不含片名、长度与记忆规模无关、纪律块仍在、否定句必须限定到全局层 —— 项目层自带「地图模式」时全称否定会自相矛盾、桥按主题捞回条目级漏掉的片、桥只收至少沾词的片、超建议标记随桥一起给、桥不满 top8 时明示「换词再搜」、host 与 client 的档位表必须一致)、索引模式(片名取文件 H1、条目带行号、分片目录优先于单文件、片级关键词与最新日期、超预算按「每片最新 N 条」配额收缩而非整层归零、 indexBudget=0 时全量逐条、injectMode=full 的多片注入与超上限自动回退索引、单文件回落时仍走 clamp 溢出索引)、注入前拆开 {{(否则整段组装会被 dsh-system-prompt 当未知变量抛错)、读路由(层内文件清单、AGENTS.md、单文件形式、路径穿越被拒)、写路由(原子写、.bak 回滚点、权限保留、首次建 MEMORY.md、内容未变不重复写、白名单外写不进去、缺 x-dsh-memory 头 → 403)、钩子规则引擎(格式/凭据/覆写历史/bash 改写)与审计,以及设置面板的渲染、「注入方式」四档切换(地图 / 无地图 / 索引 / 全文,含档位联动的预算档位显隐)与「展开 → 编辑 → 保存」全流程。v0.9.0 增补:卡片记忆层(召唤会话屏蔽全局/项目/纪律块、卡片正文注入、cardsEnabled 开关、阵容分摊、writeGuard 对三层 deny 并给卡片定向、bash 全卡片目标放行/其余 deny、列表与删除路由的头部 403 / 坏 slug 400 / 前缀逃逸拒绝 / 删除进审计、清除后恢复原状)、Windows 路径回归(HOME 为空串时仍解析出 home、盘符/反斜杠路径归位、盘符 cwd 的 slug 不含非法字符、bash 守卫识别引号内的空格路径)。
兼容性
依赖 DSH 的四个 seam:systemPrompt.section()(函数式 text)、webServer.register({kind:"prefix"})、 tools/pre-execute / tools/post-execute,以及 client 的 settings.section 槽位。
开关走插件自持的 settings.json 与自己的路由,不经过宿主设置服务 —— 0.1.7 把 settings 换成 cordis Config 表单那次换代不影响它。
Windows 路径(v0.9.0 修复 issue #1):home 取 os.homedir()(与 DSH 官方 @deepseek-ai/dsh-home-paths 的 defaultDshHome() 同源):Windows 走 USERPROFILE, HOME 未定义或为空串时再回落 passwd 记录 —— 不再出现旧实现 process.env.HOME ?? "" 把路径落到 C:\.dsh\(盘根)的情况。工具路径、bash 命令与面板写入在比较前统一反斜杠 → /,Windows / macOS 按大小写不敏感匹配;项目 slug 把 C:\Users\me\repo 折成 --C-Users-me-repo--(合法目录名,不再是带冒号/反斜杠的路径)。
写入守卫、审计、读取提醒因此与注入/面板看到的是同一个 ~/.dsh。
在 DSH 0.1.7-rc.2(Desktop 应用)上验证通过:node tests/probe.mjs 283 项全绿。
License
MIT
☕ 赞赏支持
如果 dsh-memory 对你的研究与测试有所帮助,欢迎请作者喝杯咖啡 ☕
给项目点个 Star ⭐ 同样是对开发者最大的鼓励!预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
jipika/dsh-memory
给 DeepSeek Harness DSH 的渐进式长期记忆 —— 记忆以分片的纯 markdown…
相关插件 · 对话 / 记忆
上下文中枢oceanbase/powercontext
上下文跟着工作走 —— 跨会话、跨智能体、跨人。
★ 1,462已汉化DSH
73
智能体记忆库plastic-labs/honcho
Honcho 是记忆基础设施,用来构建有状态的智能体。这些智能体能理解不断变化的人、智能体、群组、项目和想法。
★ 7,848已汉化DSH
71.7
长程智能体控制面loopx-project/loopx
给 Agent 一个目标,让工作持续向前。
★ 6,358已汉化DSH
71.3
智能体上下文数据库volcengine/OpenViking
给 AI 智能体用的上下文数据库
★ 41,246已汉化DSH
71.3
长效机器人团队Fei2411/DS-Bot
DS Bot 是一个基于 DeepSeek Harness简称 dsh的 bot 插件,插件将 dsh 改造为 bot…
★ 4,717已汉化DSH
71.1
可携带记忆层EverMind-AI/EverOS
为什么选择 EverOS
★ 13,711已汉化DSH
71.1