dsh-memory
DeepSeek Harness 的三层凝练记忆插件(Cordis 插件,bundle-declarative,零 npm 运行时依赖)。它以工作 / 情景 / 语义三层存储组织记忆,用双信号热度(指数衰减 + 召回频率)衡量"这条还活跃吗",以三级阶梯主动遗忘做磁盘与上下文的熵管理——核心存、查、写、热度、遗忘全程零 LLM,纯函数 + 规则,稳定可靠。
一、设计思路
1.1 为什么做三层(演进动机)
早期方案只有"语义事实"一张表,存在四个缺口:
- 缺情景层——只记录"事实",不记录"发生过什么"。会话级摘要缺失,无法回答"当时是怎么讨论的"。
- 库只增不减——归档是软删,无物理删除,磁盘无限增长。
- 热度只看 recency——
1/(1+λ·Δt)^α只依赖最后访问时间,区分不出"过去 30 天召回 50 次"与"昨天召回 1 次"。 - 凝练未显式化——记忆应沿时间熵减(对话 → 事实 → 规则),早期只有去重/合并,缺"情景 → 语义"的抽取管道。
v3 对应补齐:情景层、主动遗忘、双信号热度、显式凝练管道(L0 记录落地,L1/L2 周期性运行 + 即时触发)。
1.2 三层记忆架构
| 层 | 存储 | 载体 | 注入策略 | 遗忘 |
|---|---|---|---|---|
| 工作记忆 | 不落盘 | dsh 上下文窗口(宿主能力) | 实时 | — |
| 情景记忆 | episodes 表 |
会话级摘要(第一级压缩) | 显式召回,不常驻 | 时间驱动归档 |
| 语义记忆 | memories 表 |
稳定事实 / 偏好 / 教训 | Tier0 注入 + Tier1 召回 | 三级阶梯 |
工作记忆(宿主) ──会话结束──▶ 情景记忆(episodes) ──L1抽取(周期/即时)──▶ 语义记忆(memories) ──L2去重/合并──▶ 技能(扩展预留)
三个关键决策:
- 情景层存"会话摘要"而非原始全文——原文由 dsh 宿主 session 管理,插件不重复存。既解决"存什么",又天然完成第一级压缩,还控制磁盘增长。
- 上限只约束"注入",不约束"存储"——海量记忆照存,进上下文必须过预算闸门。
- 注入现算而非冻结——
systemPrompt.section()每次装配重算,天然实时 + 抗 compaction;工作记忆由宿主上下文窗口实时承担,插件不引入冻结。
1.3 双信号热度(为什么这个模型)
heat = recency_weight × frequency_boost
recency_weight = e^(-λ·Δt) # 指数衰减,Δt = 距 last_accessed 的天数
frequency_boost = 1 + ln(1 + window_freq) # 近 windowDays 天召回次数的对数加成
λ 由期望遗忘时间反推(λ = ln20 / forgetDays,即降到 heat≈0.05 所需天数):
| kind | forgetDays(默认) | 半衰期 | 降到 heat<0.01 |
|---|---|---|---|
| env(环境) | 365 天 | 84.5 天 | 561 天 |
| lesson(教训) | 180 天 | 41.6 天 | 277 天 |
| decision(决策) | 90 天 | 20.8 天 | 138 天 |
| general(一般) | 60 天 | 13.9 天 | 92 天 |
| user(用户层) | ∞(免疫) | 0 | 永不 |
要点:
- 双信号分离是防误删核心:热度回答"这条现在还活跃吗"(负责排序、降级、进入遗忘候选),重要性回答"这条删得起吗"(负责遗忘的最终闸门)。
- 召回频率 ≠ 价值:
用户房贷还款日在每月 15 日这类低频高价值事实,绝不因冷而被删。 - 诚实标注:
window_freq是"召回命中"的代理信号,不是"被模型真正采用";固定窗口有边界效应,后续可换 EWMA 平滑。
1.4 主动遗忘(三级阶梯 + 双遗忘面)
遗忘是降级 → 软归档 → 真删,每步可回滚,双向都可审计。两个遗忘面:memories(语义层,按热度 + 重要性)+ episodes(情景层,按时间)。
语义事实真删门槛(缺一不可):
heat < 0.05(冷,约一个 forgetDays 未访问)且archived = 1且超过观察期(默认 30 天)importance < 3、quality < 60(低价值、低质量)layer != 'user'且无未决纠错痕迹
免疫规则:layer=user 与 importance=5 永远免疫真删(importance=5 最多降级)。真删前落快照(forget_deleted 表),内容 + 原因可查、可回滚。
1.5 零 LLM 主循环 & Live 设置
- 存、查、写、热度、遗忘全零 LLM(纯函数 + 规则)。核心存查循环绝不因 LLM 挂掉而退化。
- LLM 只在凝练增强使用:L0 会话摘要(默认纯规则,可切 LLM)、L1/L2 决策式整理(周期性 + 即时触发)、情景召回摘要增强。
- 宿主通过
ctx.settings注册memory命名空间,设置面板的开关改动经scope.watchlive 热生效,无需重启。
二、功能特性
2.1 记忆工具(Agent 可调用)
memory —— 写入全局语义库(跨会话即刻可见):
{ action: list|add|replace|remove, layer, kind, tier, topic, id,
content, importance, force }
memory_recall —— 三种范围召回:
{ query, topK?, scope? } # scope: semantic | episodic | all(默认 all)
- 语义层:FTS5 + CJK 子串 +
epistemic × heat加权 - 情景层:FTS5 + 时间近因
memory_drafts(2026-09-08,事件驱动沉淀兜底)——处理 turn-end 纯规则(零 LLM)捕获的"待沉淀技术经验草稿":
{ action: list|promote|discard, id?, content?, kind?, layer?, topic?, importance?, epistemic? }
- 背景:记忆写入触发依赖主会话(LLM)临场想起
memory add,LLM 忙(构建/调试/查证)时会忽略阶段收尾而丢失稳定技术事实。插件在每次完成的回合用纯规则探测"用户明确确认/查证根因/确定技术决策"等强信号,命中即自动留存为memory_drafts草稿(零 LLM、零旁路,不占模型);memory:draftssystem-prompt 节在有待沉淀草稿时提示主会话。 list查看待沉淀草稿;promote对认可的草稿先查重(memory_recall)再memory add(写入语义层,kind/layer/importance 由本调用把关)并标记promoted;discard丢弃草稿。- 草稿本身不直接入语义层——promote 是唯一转成 memory 的通道,守"写入必须闸门"。开关:设置面板「事件驱动沉淀兜底」(
draftCaptureEnabled,默认开)。
2.2 设置面板(dsh 设置页「记忆」项)
设置面板注册在官方 settings.section slot,左侧导航自带神经元图标(插件自声明,无需侵入 harness shell)。可实时切换(经 scope.watch live 生效,免重启,写入 dsh 设置文档持久化):
| 开关 / 控件 | 字段 | 作用 |
|---|---|---|
| 记忆总开关 | enabled |
关 → 清洁会话(不注入任何记忆),后台整理/遗忘全停 |
| 系统提示注入当前日期 | timeInjection |
关 → 系统提示不注入真实世界日期节 |
| 自定义系统提示词 | customPromptEnabled / customSystemPrompt |
开关置于该段之前(见 §2.4b):开 → customSystemPrompt 用户自编指令逐字注入系统提示词、每个会话生效;关 → 整段不注入(即使有内容);留空亦不注入 |
| 主动遗忘 | forgetEnabled |
关 → 暂停降级/归档/硬删,不清理已有记忆 |
| 事件驱动沉淀兜底 | draftCaptureEnabled |
关 → 回合结束不再纯规则捕获待沉淀草稿(memory_drafts 工具仍可显式用) |
| 忙闲时段抑制扫描 | peakHourSuppress |
关 → 任何时段都跑后台 LLM 凝练(费 API 钱) |
| 凝练整理时间间隔(小时) | refineIntervalMs |
自定义 L1/L2 抽取与去重的周期扫描间隔(默认 1h,0.1h 起);改小更及时更费 API、改大更省。新会话后 10 秒内仍会即时凝练一次(不受此间隔影响) |
| 凝练模型(R10) | refineModelMode / refineModelProvider / refineModel |
记忆整理(L1 抽取/L2 合并/教训升格/会话收口)所用 LLM 的路由策略。自动 = 跟随会话所用模型 → dsh 默认模型(含 cordis 显式 l1/l2/l0 路由);手动 = 固定用一个模型——下拉从 dsh 已配置模型(GET /memory/models,与 dsh 模型选择器同源的 LLM registry)里选,或选「自定义…」手填 provider/model。手动值填完整则最高优先于其余路由来源;未填完整自动回落,绝不硬降级 |
| soul.md / user.md 编辑器 | — | 行内 textarea 编辑 + 保存;旁有 打开编辑 按钮——经 /memory/identity/open 用系统默认编辑器打开磁盘上的真实文件(文件仍为真相源) |
| 教训沉淀开关 | lessonDraftEnabled |
关 → 纠错仍记审计、草案滞留表内,但不做升格沉淀 |
| 纠正即时判定 | lessonInstantJudge |
关 → 仅周期升格(默认 1h + 会话后 10s 触发),不做 replace 现场即时判定 |
| 教训用 LLM | lessonUseLlm |
关 → 纯规则模板升格(降级兜底,不调 LLM) |
面板另有两个操作按钮:
- 立即整理记忆 —— 点按即触发
POST /memory/trigger,不等定时扫描,立即执行 L1/L2 凝练 + 主动遗忘(绕过忙闲时段抑制,因为是你主动要求),并回显本次结果(凝练是否执行、遗忘降级/归档/删除各多少)。 - 查看记忆 —— 打开一个弹窗(
GET /memory/view),展示当前有效记忆摘要:有效记忆/会话摘要/主题计数 + 表格(层级、类型、主题、内容、重要性)。弹窗顶部可切换视图 tab(2026-09-08):全部与常驻区 T0——常驻区 T0专看常驻核心(tier-0)记忆;后端保证常驻区记忆在 digest 中完整返回(此前统一slice(0,400)按 updated 排序可能把常驻区挤出首 400 行导致"看不到常驻区")。每行带「编辑 / 删除」按钮(人工记忆编辑,2026-09-06):编辑经POST /memory/memories/edit就地改内容/主题/重要度/类型(按 id 精确定位,不触发模型去重/合并启发式,只改该行);删除经POST /memory/memories/delete,memory层硬删并快照留痕(可回滚),user层不可摧毁、退化为归档。
面板底部的备份区(/memory/backup/*,同受 loopback 信任模型约束):
- 导出备份 —— 下载一个「整库」一致快照
.db(VACUUM INTO,含记忆、会话摘要、FTS 与全部审计轨,WAL 无关),可离线保存/迁移。 - 导入备份 —— 选择一个
.db快照替换全部现有数据;导入前先读-only 校验(非 SQLite 或缺memories/episodes表直接拒绝、不清空现有数据),成功后热切换连接——既有路由/工具/后台 pass 无需重启即针对恢复后数据继续工作,同时把导入前状态VACUUM INTO到memory.db.pre-import.bak供回滚。 - 重置记忆(2026-09-06)——
POST /memory/reset,清空全部语义记忆与会话摘要(含归档/低质量)与整理/遗忘/纠错审计轨,把库回到空白态;保留 soul.md / user.md(身份文件是绑定在磁盘的独立文件,重置记忆不碰它们,语义即「重置记忆、保留我的画像」)。破坏性操作走两步确认弹窗;执行前自动把当前状态VACUUM INTO到memory.db.pre-reset.bak供误操作回滚(备份失败会在回显里显式警告「无法回滚」)。
面板另有 导出 Markdown(/memory/export/markdown,MD-EXPORT,2026-09-06,同受 loopback 信任模型约束)——分层导出全部记忆为 3 个 Markdown 档案:01-memories.md(全部语义记忆,按 状态→layer→kind→importance 分节,含已归档与低质量)、02-episodes.md(全部会话摘要,含已归档,时间倒序)、03-identity.md(soul.md / user.md 原文)。只读、零 LLM:任何时点导出一致,不触发凝练/遗忘、不改库;数据源与「查看记忆」弹窗同口径,UI 所见即所得。注册为前端 fetch 后逐个浏览器下载(不打 zip、零新增依赖)。
2.3 身份文件(soul.md / user.md)
- soul.md:AI 人格 / 行为准则,只由人写,插件永不自动改写。
- user.md:用户长期画像,由人手动维护(2026-08-31 权威化 → 2026-09-02 彻底取消自动维护)——与 soul.md 完全相同的人写机制。
layer=user稳定记忆继续在库中积累、供memory_recall召回,但不注入 Tier0(画像只经 user.md 呈现,避免双份 + KV-prefix 抖动)。
两者通过设置面板的 保存 / 打开编辑 或直接手改文件维护(<memoryHome>/memory/*.md),作为恒定 section 注入(mtime 变化才重读,KV 缓存友好),不与 memories 表混管(不该被热度/遗忘管)。Windows 写入无 BOM UTF-8。
2.4 当前日期注入(time-injection)
解决"大模型不知道自己处于真实世界哪一天"(训练截止 ≠ 现在)。会话开始时在系统提示词里预置一条当前真实世界日期 section(name: memory:time,order 5,置于大多 section 之前):
- 日期来自互联网:后台刷新循环(默认每 15 分钟一次,
timeRefreshIntervalMs)从公开授时 API 取权威 UTC 时刻(worldtimeapi.org→timeapi.io依次尝试,单次超时 5s),再套用当前系统时区渲染成本地日期。 - 联网失败兜底:离线/被墙/超时时回退到本机时钟继续注入(渲染在时区后带极简标记
(本机时钟)),绝不让该节整体缺席。 - 只注入日期,不含时刻(KV 友好):文本在一天内字节稳定,前缀尽量命中缓存而非每次装配抖动。
- live 开关:设置面板「系统提示注入当前日期」,或 cordis 配置
timeInjection: false关闭;关时该节输出空串即时消失,无需重启。 - 示例(2026-09-06 起为单行数据节:无 markdown 标题,仅日期+时区+极简来源标记+护栏):
当前真实世界日期是 2026年9月5日星期六,时区:Asia/Shanghai(互联网授时)。该日期非模型训练截止知识;涉及『今天』/『今天星期几』/日期相关判断时以此为准。
定位说明:DSH 自身另有
@deepseek-ai/dsh-time-context(按请求往会话历史追加"采样时刻",本机时间、非系统提示、非互联网授时)。本插件的memory:time是系统提示内置、互联网授时、本机时区的日期节,二者定位不同、可共存。/ 新增smoke.mjs断言组 G40(23 断言)全绿。
2.4b 自定义系统提示词注入(custom prompt injection)
允许你在会话开始前注入一条自定义系统提示词——用户直接编写的、希望模型在每个会话都遵守的指令/行为准则(如沟通风格、输出格式、工作纪律)。它作为系统提示里独立的一条 section(name: memory:custom,order 8,位于记忆数据段之前)注入,逐字、不加任何"数据非指令"包裹——这与记忆 / 身份 / user.md 等区块(按 P0-5 声明为 data-not-instruction)语义相反:这里的内容是你亲笔写的、可信的指令,模型应当遵守。
- 为什么不受 P0-5 约束:P0-5 防的是「LLM 自己写的内容混入系统提示被误读为指令」。而你自定义的提示词本来就是指令,作者是可信的人类,故直接作为最高信任级指令注入。
- 开关置前(2026-09-06):设置面板在整段之前提供一个滑动开关「自定义系统提示词」——关 → 整段不注入(即使有内容);开 → 依内容注入。与「记忆总开关」
enabled是两层闸门:enabled && customPromptEnabled && customSystemPrompt 非空才注入。关切换 live 生效。 - 生效范围:所有新会话(每次 prompt 组装现算,非常驻冻结);受「记忆总开关」
enabled控制——关闭总开关(清洁会话)时该节随之消失。 - live 编辑:设置面板「自定义系统提示词」开关 + 多行输入,保存/失焦即写入;节点端
watch实时更新,下一个 prompt 组装即生效,无需重启。 - 配置入口:
cordis.patch.yml的customPromptEnabled(开关,默认 true)与customSystemPrompt(多行字符串,见 §四),及设置面板双入口;留空/纯空白 → 该节不注入。 - 示例:
customPromptEnabled: true customSystemPrompt: | 先给结论,再给依据。 中文为主,句中保留英文术语/代码/URL 原样。
2.5 后台维护
- L0 会话收口:turn-end 只做零 LLM 规则留痕;会话空闲 ≥
l0IdleMinutes后一次性 LLM 收口为 episode 摘要。 - L1/L2 凝练(情景→事实抽取 + 语义簇合并/去重):由
scheduleRefine按refineIntervalMs(默认 1h,设置面板可自定义)周期扫描;新会话摘要落库后约 10 秒即时触发一次(M6 kick,不受间隔影响)。可随时用面板「立即整理记忆」手动触发(绕过忙闲时段)。 - 每日主动遗忘:
runForget按热度/重要性/时间执行三级阶梯,受enabled与forgetEnabled双闸门实时控制。 - 峰时抑制:默认北京 09–12 / 14–18 点(含前 15 分钟)跳过后台 LLM 凝练,省 API 费用。
2.6 关于 KV Cache(配置注意事项)
Tier0 section 每次装配现算。写入路径会按"从第一个变动的 token 起复用失效"影响前缀缓存命中:append-only 代价最小;replace/remove 位置之后的 token 全量重算。因此建议:跨会话稳定的记忆尽量在会话早期写入;频繁检索走 memory_recall(不落盘、不动 section);不要在会话中途反复改预算/开关。
2026-08-31 去重改造对 KV 的影响(正向):layer=user 记忆已移出 Tier0 注入(画像只经 user.md 恒定 section 呈现),Tier0 段落在会话间趋于稳定;新增"写时近重复合并"(findCanonical,严格门 SIM_DUP=0.85)与"跨 topic 分组"(L2 周期用 LLM 判断歧义),重复事实被合并/收敛而不是反复 INSERT——两者都让 Tier0 内容更少变动,前缀命中更稳。
三、安装方法
运行时要求:Node ≥ 22.5(依赖
node:sqlite;Node 24 才稳定)。lib/已提交,消费者无需任何工具链。
3.1 从远端安装(推荐)
dsh plugin --profile web add https://gitcode.com/foqiang/dsh-memory.git
# 或 GitHub 源:
dsh plugin --profile web add git@github.com:masquerator-coder/dsh-memory.git
3.2 本地开发安装
dsh plugin --profile web add file:C:/abs/path/to/dsh-memory
3.3 验证
重启 dsh 后:
# 1. 存储库初始化:
ls ~/.dsh/memory/memory.db
# 2. 新会话 system prompt 出现记忆 section
# 3. 设置 → 「记忆」面板出现(含神经元图标、记忆总开关、主动遗忘等)
bundle 安装时
cordis.patch.yml自动作为 loader patch 应用,注入id: memory的实例(enableInjection: true默认开启 Tier0 注入)。
/memory/* 路由的信任模型(安全,2026-09-02):设置面板依赖的路由——/memory/identity(GET/POST 读写 soul/user)、/memory/identity/open(打开本地编辑器)、/memory/trigger(立即整理)、/memory/view(查看记忆)、/memory/memories/edit(人工编辑单条记忆,2026-09-06)、/memory/memories/delete(人工删除单条记忆,2026-09-06)、/memory/reset(重置全部记忆,2026-09-06)、/memory/models(R10:读取 dsh 已配置模型清单)、/memory/backup/export(导出整库快照)、/memory/backup/import(导入替换全部数据)、/memory/export/markdown(MD-EXPORT:分层导出 Markdown 档案)——全部仅接受 loopback 来源,校验基于 socket.remoteAddress(传输层事实,不可被 Host/Origin 头伪造),可挡局域网客户端与 DNS-rebinding 页面,即使 webServer 绑到非 loopback 地址。面板的 soul/user 编辑器或按钮在跨源时将得到 403。请保持绑定 loopback,或在宿主侧为该组路由前置你自己的鉴权。
四、配置说明
dsh-memory 开箱即用(全默认即可)。按需在 dsh 配置中覆盖:
# --- 存储 & 注入 ---
memoryHome: <path> # 存储目录(默认 ~/.dsh)——注意:库文件在 <memoryHome>/memory/memory.db,
# store 会再拼一层 memory/。想让库落在 ~/.dsh/memory/memory.db 就设 ~/.dsh,
# 不要设 ~/.dsh/memory(否则得到 ~/.dsh/memory/memory/memory.db)
enableInjection: true # Tier0 常驻 section
budgetTier0: 900 # 常驻核心字符预算
budgetUser: 400
budgetMemory: 500
importanceThreshold: 3
epistemicWeighting: true
# --- 主动遗忘 ---
forgetEnabled: true # false → 暂停降级/归档/硬删(不清理已有记忆)
forgetDays: # 期望遗忘时间(天);0 = 立即过冷,仍受重要性/观察期门槛保护
env: 365
lesson: 180
decision: 90
general: 60
windowDays: 30 # frequency 滑动窗口(天)
episodeRetentionDays: 180 # episodes 归档时间(天)
forgetObserveDays: 30 # 归档 → 真删观察期(天)
# --- 后台凝练 L0 / L1 / L2 ---
# 路由跟随 dsh 模型选择,勿写死:L0 缺省用会话 request-header;L1/L2 缺省按
# 会话路由 → 宿主导航模型 回退。写死而缺凭证会导致 refine 全程 degraded。留空=跟随。
l0Summarize: 'llm' # 'llm' | 'rules'(L0 会话 → 情景摘要)
l0Provider: '' # 显式路由(建议留空跟随)
l0Model: ''
l0MaxTokens: 400
l0TimeoutMs: 8000
l1Enabled: true # L1 情景→稳定事实抽取(LLM 决策)
l2Enabled: true # L2 语义簇合并/仲裁(LLM 决策)
refineIntervalMs: 3600000 # 后台整理扫描间隔(面板可自定义,live 生效)
l2MinCluster: 2
# L1 遇非 JSON/空输出先做一次纠错重试(R8)再降级;手动"立即整理"始终复活
# degraded 的 episode(R9),不受本开关影响。true=后台周期也重试 degraded。
l1RetryDegraded: false
# --- M5 会话收口 ---
l0IdleMinutes: 30 # 会话空闲 ≥ 此分钟数才一次性 LLM 收口
checkMinutes: 5 # idle 收口判定周期(分钟)
# --- M8 峰时抑制(省 API 钱)---
suppressWindows: # 这些时段 L1/L2 后台 LLM 不跑(按 timeZone 计算)
- start: '09:00'
end: '12:00'
- start: '14:00'
end: '18:00'
suppressLeadMinutes: 15
timeZone: 'Asia/Shanghai'
peakHourSuppress: true # false → 任何时段都跑后台 LLM
# --- M9 身份块 ---
enableIdentity: true # 注入恒定 soul.md / user.md 身份 section
# --- time-injection:系统提示注入当前日期 ---
timeInjection: true # 注入真实世界日期(互联网授时 / 本机时区)
timeRefreshIntervalMs: 900000 # 互联网日期重校间隔 ms(默认 15 分钟)
# --- 自定义系统提示词注入 ---
customPromptEnabled: true # 总开关(默认 true);false → 整段不注入
customSystemPrompt: | # 用户自编指令,逐字注入每个会话的系统提示(§2.4b)
先给结论,再给依据。
中文为主,句中保留英文术语/代码/URL 原样。
# 留空/不写 → 不注入
# --- R3-total:记忆总开关 ---
enabled: true # false → 清洁会话,后台全部停;memory 工具保留
哪些可在设置面板实时切换(免重启):enabled / forgetEnabled / refineIntervalMs / peakHourSuppress / lessonDraftEnabled / lessonInstantJudge / lessonUseLlm / timeInjection / customPromptEnabled / customSystemPrompt / refineModelMode + refineModelProvider + refineModel(R10 凝练模型)——这些经 dsh 设置页「记忆」面板读写,settings 用户层覆盖 cordis config,改动 live 生效、写入设置文档持久化。
五、存储布局
~/.dsh/memory/
└── memory.db # SQLite (node:sqlite), WAL
├── memories # 语义事实(含 window_freq/window_start/archived_at)
├── mem_fts # FTS5 语义索引
├── episodes # 情景会话摘要
├── ep_fts # FTS5 情景索引
├── failure_memories # 纠错留痕
├── forget_runs # 遗忘审计
└── forget_deleted # 真删快照(内容+原因,可回滚/可查)
身份文件(不进 memories 表):<memoryHome>/memory/soul.md、<memoryHome>/memory/user.md。
六、使用示例
# Agent 记入一条用户偏好(layer=user → 永生;不自动写 user.md,user.md 由人维护)
memory action=add layer=user topic="用户偏好" importance=5 content="用户偏爱简洁、结构化的中文回答"
# 记一条一般教训
memory action=add layer=memory topic="部署教训" importance=4 content="harness 推送 master:main 到 gitcode"
# 跨层召回
memory_recall query="用户的回答风格偏好" scope=semantic
memory_recall query="上周关于部署的讨论" scope=episodic
七、开发
# 一键构建:自动定位 deepseek-harness(DSH_HARNESS_ROOT 优先),从其 pnpm store
# 自动探测 esbuild / @types/react / typescript 最高语义版本(不硬编码),
# 随后 tsc 类型检查(Node + client)+ esbuild 打包 client。
node build.mjs
# 冒烟(无 dsh 也可跑,全量断言组 G1–G35)
npm run smoke # 等价于 node smoke.mjs
提交的 tsconfig.json / tsconfig.client.json 仅为开发机 IDE 默认;构建不用其中的硬编码路径。前端 client 入口为 exports["./client"](dsh.client.platform: "web"),esbuild 打包成 window.__ModuleLoader__.load 格式(react 走 shell 单例,不打进 bundle)。
八、状态与兼容性
事件驱动沉淀兜底(2026-09-08,MEMORY-TRIGGER):插件补上"写入前触发"这一记忆盲区——turn-end 用纯规则(零 LLM、零旁路)从当前回合的 user/agent 文本 + 工具集探测稳定技术事实强信号(用户明确确认/查证根因/确定技术决策),命中即自动留存为
memory_drafts草稿(新表),即使主会话 LLM 忙到忘了memory add也不丢。memory:draftssystem-prompt 节(仅有待草稿时非空、KV 友好)提示主会话;新工具memory_drafts(list / promote / discard)让主会话闲时查重(memory_recall)后沉淀(promote→memory add,守写入闸门)或丢弃。草稿不直接入语义层。新开关draftCaptureEnabled(settings 面板「事件驱动沉淀兜底」,默认开,live-toggle)。不改memories/episodes存储检索热度遗忘语义(与设计文档正交);不每轮 spawn LLM 旁路、无常驻进程。新增smoke.mjs断言组 G44(23 断言)全绿。设计定稿见 Obsidiandsh-memory/MEMORY-TRIGGER-DESIGN.md。核心闭环完成:三层存储、全局直写、跨会话召回、双信号热度、主动遗忘(三级阶梯 + 双遗忘面 + 审计 + 免疫 + 真删快照)、教训沉淀管道、整库备份导出/导入——
smoke.mjs全部断言组(219 项,G1–G35)全绿、稳定连跑,tsc零错误。真机联调通过(2026-08-31):「记忆」设置项出现、面板控件渲染正常;
curl /memory/identity路由通;设置页关「记忆总开关」→ 新会话 agent 不再记得(live 生效铁证)。后台凝练 L1/L2:周期性运行(默认 1h,面板可自定义间隔),情景→事实抽取 + 语义簇合并/去重,受忙闲时段抑制;新会话落库 10 秒内即时触发一次;亦可面板「立即整理记忆」手动触发。LLM 降级时不退化为纯规则硬抽(标记 degraded)。
KV Cache:Tier0 现算,写路径会按变动位置影响前缀缓存命中(见 §2.6)。
去重 + 身份权威化(2026-08-31):① 写时近重复合并
findCanonical(严格门)+ L1 同通道受益;②isNearDupCandidatetoken 宽松门支撑跨 topic 分组;③crossTopicNearDupGroups接入 L2 周期,破除按 topic 聚类边界;④layer=user移出 Tier0 注入 —— user.md 转人写权威(画像只经 user.md 呈现)。user.md 按需加载(2026-09-02):完整画像不再内联注入系统提示(省上下文、稳 KV 前缀),系统提示仅留一条指引;模型确需了解用户画像时调用
memory_read_user工具读取 user.md。设置面板控制面(2026-09-02):soul/user 编辑器新增「打开编辑」(
/memory/identity/open默认编辑器打开磁盘文件);新增「立即整理记忆」(/memory/trigger,绕过忙闲时段立即执行凝练+遗忘并回显结果)与「查看记忆」(/memory/view只读弹窗);「凝练整理时间间隔」可在面板自定义(refineIntervalMs),改动 live 生效。新增路由全部 loopback 校验。教训沉淀管道(2026-09-02,DESIGN docs/lesson-pipeline.md):
replace/合并冲突触发recordFailure时,零 LLM 即时双写lesson_drafts草案(同 memory 纠正聚合 draft_count);后台周期(默认 1h)+ replace 现场即时(lessonInstantJudge)两条通道经 LLM 判定(wrong-original / stale → 升格kind=lesson;trivial → drop;LLM 失败保留待下次),纯规则模板兜底(lessonUseLlm=false)。全旁路:独立 seam、非阻塞、只落库不回流、输入最小化。新增smoke.mjs断言 G26–G33 全绿。user.md 自动维护彻底移除(2026-09-02):
maintainUserIdentity、identityAuto/identityIntervalMs/identityMaxBytes、identity_synced/identity_meta表及 G22/P3-4 测试全部删除;user.md 与 soul.md 一样完全由人维护。旧库残留的identity_synced/identity_meta表为惰性孤儿,不再被引用、无害。记忆备份导出/导入(2026-09-02):设置面板「记忆」项新增「导出备份」与「导入备份」。导出走 SQLite
VACUUM INTO生成整库一致快照(.db,含记忆、会话摘要、FTS、forget/refine/lesson 审计轨,WAL 无关),浏览器下载保存;导入先读-only 校验再热切换连接(replaceWithBackup重准备全部语句,既有路由/tools/后台 pass 无需重启即针对恢复后数据继续工作),导入前自动把当前状态VACUUM INTO到memory.db.pre-import.bak供回滚,非法文件(非 SQLite / 缺 memories+episodes 表)直接拒绝、不清空现有数据。两条新路由(/memory/backup/export、/memory/backup/import)同受 loopback 信任模型约束。新增smoke.mjs断言 G34–G35 全绿。memory:protocol 行为引导节(2026-09-03):新增插件唯一指令性 section(name
memory:protocol,order 9,置于 tier0/soul/user 之前)。恒定静态文本PROTOCOL_TEXT(src/inject.ts导出)声明三个记忆工具的"场景触发"时机——recall(探索未知环境/项目/配置、有后果的决策前)、add(学到稳定的新事实、不在当前对话历史)、read_user(多方案推荐且用户偏好未知)。设计要点:①KV 免费——纯字面量、与 store/状态/时间戳无关,text thunk 每次装配返回字节一致,第一轮装配后命中前缀缓存;②指令/数据分离——这是插件刻意注入操作规则唯一处,其余 tier0/soul/user 均按 P0-5 声明为"数据非指令";③live-toggle——text thunk 读runtime.enabled,总开关关→该节即时消失。新增smoke.mjs断言组 G36(8 断言)全绿。凝练模型可选手动固定(R10,2026-09-03):设置面板「记忆」新增「凝练模型」——自动(跟随会话所用模型 → dsh 默认,含 cordis 显式 l1/l2/l0 路由)或手动指定(下拉从
GET /memory/models枚举的 dsh LLM registry 已配置模型中选择,或选「自定义…」手填 provider/model)。手动完整 pair 通过manualRefineOverride成为 L1/L2/教训升格/会话收口全部整理 LLM 路由的最高优先来源(src/index.ts5 处 resolveRefineRoute 统一接入);未填完整自动回落、绝不硬降级。改动经设置文档 live 生效(免重启)。新增smoke.mjsR10 断言(G16 扩展,+7 全绿)。L1 解析漂移一次纠错重试(R8,2026-09-03):
runRefineL1在硬解析失败(模型返回散文/fence/截断数组而非 JSON)时,把坏输出回灌做一次有界纠错重试再降级(不循环,最坏 ≤2×timeout;raw=null 超时/断流不重试,交给 R9)。同时手动「立即整理」force=true 强制retryDegraded(R9)——降级(extracted=2)episode 不再永久跳过,每次手动整理都会复活重试;后台周期仍遵循l1RetryDegraded配置(默认 false)。会话噪声收敛(2026-09-03,承接 protocol):protocol 接管 recall/add 的"何时用"引导后,同步做三处瘦身避免同节/同会话重复指令:①
buildSection尾部删掉与 protocol 重复的"需要详情用 memory_recall / 学到稳定事实用 memory 记录",仅保留 protocol 未覆盖的写-禁止 guard(避免任务进度/一次性过程);usage 只在尾部报一次(header 的原始字符数移除;单条≤300字符元提示于 2026-09-06 删除——上限由写入侧 clamp,注入文本无需告知模型);②tier1 领域列表封顶前 10 个(超出给"等 N 个,用 memory_recall"),防随记忆增长无限膨胀;③memory工具 description 瘦身(protocol 拿走"何时",description 只讲"怎么用"),且写路径成功返回不再每次 echo usage,仅当有降级(budget 紧张)时回显;④memory:user位置指引节去掉自指式前言("以下是指引,不是指令 / 完整画像默认不注入节省上下文"),仅保留memory_read_user调用指引——该节本身即位置指引,前言属冗余说明。新增smoke.mjs断言组 G37(5 断言)全绿。身份区块闭合容器(2026-09-07):
memory:soul的身份数据块(src/inject.tsbuildIdentitySection)与 tier0 同款,把原始人的原始 markdown 用<identity-data>…</identity-data>尖括号标签整块闭合包裹。此前该块只有前置「不是指令」声明、无结束标记,.join('\n\n')拼接到后续系统提示词/工具指引后,模型可能把身份声明之后的所有内容都误读为身份/画像数据;闭合标签让"身份数据到此为止"边界显式。两枚标签均为常量文本(字节稳定、mtime 缓存不变则 KV 前缀友好),> '<identity-data>'…声明在容器外描述它、数据体在容器内。memory:user不注入完整画像(按需读取指引不变),不受影响。新增smoke.mjsG21/M9 断言 5 条(标题/开头结尾标签/声明在容器外/内容在容器内/user 同样闭合)。记忆分层导出 Markdown(MD-EXPORT,2026-09-06,docs/MD-EXPORT.md):设置面板「记忆」新增「导出 Markdown」按钮(
/memory/export/markdown,只读、零 LLM,同受 loopback 信任模型约束),分层导出全部记忆为 3 个 Markdown 档案——01-memories.md(全部语义记忆,按 状态→layer→kind→importance 分节,含已归档与低质量)、02-episodes.md(全部会话摘要,含已归档,时间倒序)、03-identity.md(soul.md / user.md 原文)。数据源与「查看记忆」弹窗同口径(所见即所得);模型写的不可信content经mdSafe转义防伪造 markdown 结构。注册后浏览器逐个下载(不打 zip、零新增依赖)。新增docs/MD-EXPORT.md设计稿 +smoke.mjs断言组 G41(20 断言)全绿。注入默认值收敛 + FAIL 文案 + 三项审计修复(2026-09-07):①「自定义系统提示词」(
customPromptEnabled)与「系统提示注入当前日期」(timeInjection)默认值改为关闭(三层:settings floor / Config schema default / runtime 回退统一false,用户仍可在设置面板手动开启);②memory工具溢出拒绝文案改为指向真实触发原因(仅常驻核心 importance≥5 占满、demote 无法腾出时才拒绝,普通 <5 记忆本会降级保存)并给可操作引导;③M1 给 user-authoredmemory:custom注入加CUSTOM_CAP(8000)硬上限(新增纯函数clampCustomPrompt,其余注入皆有预算闸门、唯此缺失);④M2validateBackup除表存在外增加 memories 关键列结构校验(PRAGMA table_info),拒收「同名表但缺列」的畸形/异构备份,防热切换后rowToEntry强转读到垃圾值;⑤L1 抽出单点sqlPathLiteral复用三处VACUUM INTO路径转义(SQLite 不支持参数绑定,转义必须内联)。新增smoke.mjsG43 断言 9 条全绿。审计三项真缺陷修复(2026-09-07):对一份 13 项问题清单逐条核对代码后,落地其中 3 个确凿缺陷—— ①过期 window_freq 不再抬高热度(
src/heat.tsheatOf):此前频率信号只看存储的window_freq,而touchAccess只在下一次召回命中时重置窗口,窗口过期本身不归零——窗口内高频、此后长期冷的条目 freq 卡高位,抬高 heat、挡住降级(general 60d:100 次召回后 90d 安静 → heat 0.063,本应 0.0112)。修复:heatOf增可选windowMs参数,当window_start>0且窗口已过时按 freq=0 计;shouldDemote/shouldArchive同步透传,store 各处经windowMs()(=windowDays*DAY_MS)显式启用。无window_start/未传windowMs时保持旧行为(现有 3 参调用不受影响)。 ②预算闸门与注入闸门口径对齐「方向 A」(src/store.tsenforceBudget+src/inject.tsbuildSection+src/types.ts):此前enforceBudget把所有 kind 计入 memory 预算桶,而buildSection只注入kind∈{preference,env}——不受注入的受保护 lesson/decision/general(importance≥5)占预算、甚至能把本该注入的 preference/env 挤出导致溢出拒绝。修复:抽共享谓词isInjectableKind(types.ts 单一来源),buildSection与enforceBudget同用;memory 预算桶改核算 injectable(isInjectableKind && importance>=injectThreshold,阈值经构造器从 index.ts 传入),其 squeeze 只降级可注入冷条目。不受注入的 tier0 仍计入总 tier0 存储上限、由 forgetRun 按热度处理,但不再挤占注入配额、不再触发注入溢出。 ③identity 注入无上限无转义(src/inject.tsbuildIdentitySection):tier0 有 sanitize+escHtml+SECTION_CAP,而 soul.md/user.md 原文直接包进<identity-data>——大 user.md 撑爆 KV 前缀、字面量</identity-data>可破坏容器(memory-entry 同类问题在 identity 路径漏修)。修复:新增保留换行的sanitizeIdentity(只清控制符+归一化 LF+截断到IDENTITY_CAP,不复用 tier0sanitizeText以免折叠 markdown 行结构)+escHtml转义&<>"。新增 smoke 断言 7 条(热函数窗口归零/保留、identity 闭合标签转义/截断、② 预算口径)全绿,累计 350 断言 / 0 失败。人工记忆编辑 / 删除 / 重置(2026-09-06):设置面板「查看记忆」弹窗每行新增「编辑 / 删除」按钮——编辑经
POST /memory/memories/edit(store 新增updateMemory)就地改 内容/主题/重要度/类型,按 id 精确定位、不触发模型去重/合并启发式,只改该行且保留档案身份(id/创建时间/热度);删除经POST /memory/memories/delete(deleteMemory),memory层硬删并快照进forget_deleted留痕(可回滚),user层不可摧毁自动退化为归档。备份区新增「重置记忆」按钮(POST /memory/reset,resetStore)——两步确认弹窗后清空全部记忆/会话摘要/审计轨、保留 soul.md / user.md,执行前VACUUM INTO到memory.db.pre-reset.bak供回滚(备份失败显式警告)。三条新路由同受 loopback 信任模型约束。新增smoke.mjs断言组 G42(23 断言)全绿。记忆数据块闭合容器(2026-09-06):
memory:tier0的Persistent memory记忆数据块再用<memory-data>…</memory-data>尖括号标签整块闭合包裹(src/inject.tsbuildSection)。此前该块只有前置「数据非指令」声明、无结束标记,.join('\n\n')拼接到后续 soul.md / user.md / 系统提示词后,模型可能把记忆声明之后的所有内容都误读为记忆记录数据;闭合标签让"记忆数据到此为止"边界显式。两枚标签均为常量文本(字节稳定,KV 前缀友好),结束标签恒定置于 SECTION_CAP 截断文本之后保证容器闭环。新增smoke.mjsG37 断言 3 条(开头/结尾标签 + 声明在容器内)全绿。
设计文档
docs/DESIGN.md—— v3 设计稿(设计哲学、双信号热度推导、主动遗忘门槛、凝练管道)。docs/REFINE-REDESIGN.md—— 凝练管道重构方案(L0/L1/L2 触发与降级)。docs/MD-EXPORT.md—— 记忆分层导出为 Markdown 的设计方案(分层文件布局、格式、路由、测试)。
No comments yet. Be the first to write one.