中文 | English(语义增强)
dsh-mneme
给 DeepSeek Harness 的跨会话记忆插件:让 Agent 记住你、记住项目、自动整理记忆。Mneme(Μνήμη)——希腊记忆女神 Mnemosyne 之名,掌管记忆与梦境,正如 autoDream 在后台巩固记忆。
dsh-mneme 是一个 DeepSeek Harness (DSH) 插件,为 Agent 提供持久的跨会话记忆能力。它借鉴了 Claude 的 Dream 机制 与 cc-haha / Claude Code 的 autoDream 记忆巩固功能——不仅存储记忆,还会自动巩固(去重、合并、冲突裁决、摘要生成),让记忆库越用越精炼。完整溯源与致谢见文末「🙏 致谢」。
⏱️ 30 秒理解
一句话:给 Agent 装上跨会话记忆——记住你、记住项目,并在后台自动整理,越用越懂你;v0.8.0 起记忆可按 agent 与工作区隔离(scopeEnabled),多 Agent / 多项目互不串台;v0.9.0 起「这件事怎么跑」也能落进项目仓库(M.O.,actionPipelineEnabled,默认关)。
| ① 写入 | ② 存储 | ③ 进化 |
|---|
对话中模型主动记录(memory_save,可带敏感度与事件时间);会话结束自动提炼(autoSummarize) | SQLite 主库 + 人类可编辑 Markdown 镜像;实体 / 属性 / 时间轴三层结构化;记忆带 agent / workspace / 敏感度 / 发生时间标注 | 新会话自动注入相关记忆(按当前作用域加权,strictScope 开启后显式收窄的归属硬隔离);autoDream 后台去重 / 合并 / 归档,冲突冻结待人工裁决 |
# 30 秒上手
dsh plugin --profile web add @modusensus/dsh-mneme
dsh web
它不是什么(边界声明):
- 不是向量数据库——语义搜索是可选增强,默认零额外依赖
- 不替代会话日志——它存的是「值得跨会话记住的」精炼知识
- 不改变模型本身——进化的是记忆库与每次注入的上下文
- 删对话 ≠ 删记忆——记忆是跨会话的全局知识(v0.7.11 起不再与会话绑定),删除会话不影响已存入的记忆
✨ 功能
记忆存储(SQLite + Markdown 镜像)
- SQLite 主存储:
~/.dsh/memory/memory.db,node:sqlite 内置,零原生依赖
- Markdown 镜像:按类型分文件(
preferences.md / projects.md / decisions.md / history.md / summary.md / patterns.md / pitfalls.md / constraints.md / rejected-solutions.md,外加只读视图 documents.md),文件头是 YAML frontmatter(type / generated.by / generated.at / covered / coverage / tags)。人类可读,条目标题与正文可手工编辑(人工修改优先合并回库);documents.md 例外:它只有 document 指针行(id、标题、摘要首句、文件路径),不含正文也不参与人工回填。文件头与条目元数据行由机器维护
- 10 种记忆类型:
preference 偏好 / project 项目 / decision 决策 / history 历史 / summary 会话总览 / pattern 模式 + 编码记忆三型 rejected_solution 被否方案 / pitfall 踩坑 / constraint 约束(v0.7.13 起,codingRetrospect 默认关;v0.7.11 近重写已收窄删除 user/fact 两型)+ document 文档指针(#230,只存摘要与 doc_path,正文留在磁盘由 agent 自管)
- 镜像同步状态机(v0.3.6+):mirror 与主库强一致,用
generation(期望轮次)/ applied_generation(已应用轮次)建模同步债务
- 业务写操作在自身事务内原子递增 desired generation——崩溃在 COMMIT 后、渲染前,重启也能凭 durable 债务恢复,绝不静默跳过(v0.3.8)
generation 用 SQLite 原子语句递增,多进程并发零丢失;带 CHECK 上界,负数/溢出拒绝
- 逐 type 记录
committed / failed / pending 回执,健康端点区分 ok / degraded / unknown
- 状态写失败不静默:同步失败落日志并留债务,重启自动收敛
模型工具(默认注册 11 个;开 organizeEnabled 后 12 个)
| 工具 | 功能 |
|---|
memory_save | 记录一条记忆(自动按标题去重合并;v0.8.0 起去重键含 agent/workspace/scope 作用域,可选 sensitivity / occurred_at 参数) |
memory_search | 全文搜索(中文子串友好,可启用向量语义搜索;可选 occurred_from / occurred_to 按事件发生时间过滤) |
memory_list | 按类型分页列出(include_archived=true 可查看已归档;可选 occurred_from / occurred_to 时间过滤) |
memory_get | 读取单条记忆完整正文(v0.7.25,按 id;memory_search / memory_list 命中后读全文) |
memory_update | 修改已有记忆 |
memory_delete | 删除记忆(按记忆 ID 精确删除) |
memory_forget | 抑制注入(降权不删除,可恢复) |
memory_archive | 归档/恢复记忆(v0.2.5;归档后隐藏于列表/搜索/注入/整理,archived=false 可恢复) |
set_scope_state | 按下/松开某个作用域的可逆闸门(#471:政策轴。暂停期间该作用域的记忆照旧存着,只是不注入、不参与巩固,松开即原样回来;不改任何一条记忆的标注,与上面逐条操作的 memory_forget / memory_archive 是两根不同的轴)。两级开关 scopeState.enabled / .enforce 默认都关,没开时按下不产生拦截效果 |
memory_register_document | 登记 agent 产长文档的指针行(#230:摘要 + doc_path + evidence 落 document 类型;正文留在磁盘、由 agent 自管,注册口唯一,旁路写入被拒) |
memory_organize | 主动整理(#231,要 organizeEnabled 打开才注册):mode=dryRun 先出比对报告(每条候选 exact / near / new + 命中行,不写一行库、只留审计回执),agent 判断后 mode=apply 引用那份报告的 run_id 落地。两种「不要它」要分清——discard = 这条候选不写,什么都不动(只进回执);archive = 把报告标为被取代的那条既有行归档(可恢复,绝不物理删除)。没有比对过的候选写不进库,一份报告也只许落地一次 |
memory_runtime | 本地推理运行时的状态 / 供给 / 校验(v0.7.32,自管运行时三档取件) |
工作流(M.O.,v0.9 beta)
记忆库答的是「这件事为什么这么办」,答不了「这件事到底怎么跑」。v0.9 把后者从记忆库里的一行文字变成项目里可提交、可 review、可被 CI 读的一份文件——一条工作流一个 JSON:
<项目>/.mneme/pipelines/<id>.json ← 项目层(默认;跟着项目提交)
<插件包>/pipelines/<id>.json ← 全局层(兜底;今天包里没有这个目录,有意为空)
- 两层来源,顺序即优先级:同 id 两处都有,项目层赢,被挡住的那份进
shadowed 报出来(不静默丢弃);项目层的坏文件进 unreadable 而不拖死全局层——一层写坏不该让人连兜底那份也看不见
- 校验很硬:
id / name / project / version / status / done 与每一步的 phase / title / when / do / done / why 缺一不可,risk 限 R0–R3、needs 必须指向存在的步骤、图里有环直接报错——任何一处不合规整条读不出来,不做「跳过坏文件继续」:半个工作流比没有更危险
- 护栏,不是轨道:绕行要留出口。自报 R0/R1 且不跨闸门 且兜底清单(
IRREVERSIBLE_PATTERNS,机械匹配、不看自报)没命中 → 记账先走;其余 → 停下问人
- 候选分层:草稿住
<memoryDir>/candidates/<项目键>/,只在本地。升格要同时满足「走过两次(至少一次成功)」且「写得出反事实」;够格之后系统只提议,promoteCandidate() 只返回内容不写盘,提交与推送是人握着的那道门
- 两个指标分开数:跳过(这次不用这一步,≥3 次提议删这一步)与绕开(这条路整个不对,≥3 次提议重看这条路径)各自累加,混在一起两种提议都发不出来
- 只读路由
GET /api/dsh-mneme/pipeline[?id=&project=](project 非绝对路径回 400 project-not-absolute,不回退 process.cwd());答复契约 mneme.pipeline/v1,面板渲染与 agent 读的是同一份数据
- 默认关(
actionPipelineEnabled),设计取舍见 docs/WORKFLOWS.md
自动注入 + 会话摘要
- 自动注入:新会话开局注入记忆摘要(
summary 优先 + 少量高重要性条目)
- 会话摘要:
turn/end 时用 LLM 提炼本次会话的偏好/决策/教训,自动入库(过滤 plugin 注入上下文,避免污染)
autoDream 自动记忆整理 🧠
溯源:autoDream 的理念源自 Claude Code 的 Auto Dream 记忆巩固功能(Anthropic,Memory 2.0 的后台整理子代理),其「离线巩固」思路与 UC Berkeley & Letta 的 Sleep-time Compute 论文(arXiv:2504.13171)一脉相承。dsh-mneme 将其引入 DSH 插件生态,并发展为自己的实现——K-Means++ 聚类预分组、类型化决策清单与可回放的 sha256 摘要审计链,详见文末「🙏 致谢」。
- 触发:记忆数 > 10 或总字符 > 5000 时,异步自动触发(不阻塞写入)
- 决策清单式整理:LLM 输出
keep / merge / archive / conflict / update 决策清单,服务端校验后逐条应用
merge:合并主题相近的条目,保留信息最完整者
archive:归档过时/冗余条目(可恢复,不物理删除)
conflict:裁决矛盾信息,胜者保留、败者归档并追加溯源注释;conflictFreezeEnabled(默认关)开启后矛盾对不自动裁决,改为冻结进冲突队列(状态页并排对比,人工选保留方,#166)
update(v0.2.1):直接修正单条记忆的过时/错误内容(单 id / 必须实际变化 / 非 summary / 24h 保护 / 每次 ≤2)
- 失败追踪(v0.2.1):用户纠正记忆时写入
failure_memories 表(旧值/新值),为后续自进化积累数据
- 摘要生成:整理后生成"记忆库总览"(单一实例),作为下次会话的优先注入
- Fail-safe:非法 LLM 输出(未知 id / 非法 action / 越界 importance / 跨类型合并等"单条非法"决策,Issue #26)默认跳过该条并应用合法子集(run 记为
degraded),绝不破坏记忆库
- 裁决审计:每次运行写入
dream_runs 审计表(输入快照 sha256 digest + 完整输入快照 + 决策清单 + 逐 id 去向 + receipt),可离线回放;merge / conflict / update 幂等应用,重放/并发重复执行无累积副作用;update 记录 _before 快照
dreamMaxTokens 调优指南
默认 131072(即上限,#135)已为思考型模型预留推理+正文的双重预算(部分思考型模型仅推理就可能消耗 8k+ token),常规记忆库无需调整。若记忆量大(数万字符以上)且正文仍被截断,优先按下方「巩固模型分类声明」换非思考模型 / 配 dreamReasoningEffort 压低思考,再考虑调大输入侧上限(如 distillMaxChars)——dreamMaxTokens 本身已无上调空间。
不要为了省 token 把它调小(#135 成本审计实测):这个键是上限而非计费口径,流式按实际用量计费,压上限只在真撞顶时生效;而撞顶的后果是整轮失败(截断 → 无完整 JSON → no json array in llm output),且调度器只有 ok 轮才刷新基线,下一次写入会用同一批输入重跑。按 133 个有真实用量的成功轮折算,cap=16384 撞顶 78.9%、净总量 +54.1%;cap=32768 +15.9%。真正的大头(dream_consolidate 输出 token 里约 88% 是推理与 JSON 骨架,只有约 11.8% 是决策正文)由 dreamReasoningEffort 与模型选择决定,与本键无关。相关的数量护栏见 dreamMaxMergePerRun。
若使用思考型模型(如 deepseek-v4-flash / DeepSeek-R1 类),模型可能把全部预算花在 reasoning 上导致正文为空(日志出现 no json array in llm output)。#135 修复后:未配置 dreamReasoningEffort 时会自动取模型支持的最低档发流(不再省略字段让模型自带默认档——v4-flash 系默认 high——顶上烧光预算),默认配置即生效;显式 none = 不发送字段用服务商默认,off/low/medium/high 原样传递,模型不支持的档位自动换用(拒绝原因记入 llm_audit)。正文仍为空时配置 dreamProvider/dreamModel 指向非思考模型(dreamMaxTokens 默认已是上限 131072,无上调空间)。sleep 侧对应 sleepReasoningEffort。
巩固模型分类声明(settings panel「巩固模型」= dreamProvider/dreamModel,睡眠侧对应 sleepProvider/sleepModel):
| 模型类别 | 例子 | 说明 |
|---|
| 非思考模型(推荐) | glm-5-2 类等 | 无 reasoning 声明;即使配了 effort 被 harness 拒绝,fallback 去掉字段重试即成功。空体风险最低 |
| 思考模型(需实测) | deepseek-v4-flash-ga 等 v4-flash-ga 系 | 默认开推理,可能烧光 token 预算返回空体;部分型号(如 v4-flash-ga)在 harness 侧被声明为不接受任何 reasoning effort(去掉 effort 时 harness 的 defaultEffort 会顶上来再次拒绝)。v0.7.26+ 已根治:resolveDreamEffort 发流前探测模型支持的档位,不支持的配置档位自动换用模型默认/首个支持档位,声明无 reasoning 能力的型号则省略字段。选用时建议配 dreamReasoningEffort 实测(未配置时自动取最低档),不行就换非思考模型 |
Sleep Mode 系统级睡眠 💤(v0.4.0,opt-in)
从 autoDream 的"被动阈值触发"升级为"主动定时维护 + 分层压缩"。系统空闲 sleepIdleMinutes 分钟自动执行深度维护,默认关闭(sleepModeEnabled: false),开启后行为:
- 可中断:AbortController 实现,用户恢复活动即中止当前周期(
noteWrite 重置空闲计时 + 中断信号)
- 串行安全:睡眠周期走
service.enqueue 串行队列,与 autoDream 严格不重叠;minRefTimeMs 防止快照后被召回的记忆被误降级
- 四阶段深度维护:
conflict_resolution:全库冲突消解,strictness 三级可配(gentle 0.92 / normal 0.85 / aggressive 0.75)
archival_demotion:按 last_accessed_at 分层——30 天未召回压成摘要(原文进 _full_content,可无损恢复)、90 天完全归档
pattern_discovery:LLM 扫描近期记忆提炼规律,产出 type=pattern 记忆,evidence 强校验防伪造
relation_completion:检测孤立实体并补全隐含关系(共现 related_to / 项目 part_of / 技术 depends_on)
- Fail-safe:每阶段独立 try/catch,LLM 故障只跳过对应阶段;无 LLM 路由时纯规则降级(demotion/relations)照常执行
- 审计延续:睡眠周期写入
dream_runs,run_type='sleep',与 autoDream 共用审计表可追溯
配置详见 docs/SLEEP.md。
官方设置面板 → 「记忆库设置」→「记忆」标签:按类型浏览、全文搜索;启用向量搜索后可用「语义」切换做向量召回。
用户设置(画像 / 规则)与自定义指令 ⚙️
官方设置面板 → 「记忆库设置」标签:
- 用户画像:一段自由文本描述用户自己(角色、背景、偏好),每轮注入到系统提示,让 Agent 始终遵循
- 规则:Agent 必须遵守的行为规则列表(如"回答先给结论"),同样每轮注入
- 自定义指令:注册斜杠命令(
/名称 [补充说明]),触发时把用户定义的指令内容作为一条用户消息提交给当前 Agent,命令后缀会作为补充说明一并提交;界面仅显示提交回执(宿主不支持 followup 提交时退回旧行为,指令原文会显示在界面,便于复制后手动发送)。命令持久化到 SQLite,启动时自动注册到 DSH 命令表,增删实时生效
画像与规则通过独立的 [用户设置] 注入区块(优先级高于记忆库),即使记忆为空也会注入。
向量搜索(语义搜索)🔎
可选能力:接入 OpenAI 兼容的 embeddings API,让搜索能命中字面不同但语义相近的记忆。
配置:官方设置 → 「记忆库设置」→ 滚动到底部「向量搜索」区块:
| 字段 | 说明 |
|---|
启用向量搜索 | 总开关;开启后记忆面板出现「语义」切换 |
API 地址 (Base URL) | OpenAI 兼容端点,如 https://api.openai.com/v1;也支持 SiliconFlow、智谱、本地 Ollama 等 |
API Key | 对应服务的密钥 |
模型名 | embedding 模型,如 text-embedding-3-small、text-embedding-v3、bge-m3 等 |
保存配置后点「重建索引」,为已有记忆批量补建向量(新写入的记忆会自动嵌入)。之后在记忆面板输入查询并点「语义」,即可用向量召回语义相关结果;向量服务不可用时自动回退全文搜索。
⚠️ 密钥仅保存在本机 ~/.dsh/memory/memory.db 的 user_settings 表,不会上传,也不会写入代码仓库。
需要 embedding 而非 rerank 模型:如阿里云 text-embedding-v3 可用,qwen3-vl-rerank 是 rerank 模型(不走 /embeddings)。
语义增强(Semantic)🧠
v0.2 起新增可以完全离线运行的语义记忆引擎(本地模型 + 精排 + 聚类):
- 本地 Embedding:三后端可选——ONNX(
Xenova/bge-small-zh-v1.5,离线)/ Ollama / OpenAI 兼容,失败自动逐级降级,最差回退关键词搜索
- Rerank 精排:
Xenova/bge-reranker-base 对召回候选交叉编码精排,提升 Top-K 准确率
- autoDream 语义增强:对记忆向量聚类(
clusterMemories),自动发现主题相近 / 疑似矛盾的记忆,巩固更精准
- 搜索流水线:混合召回(关键词 + 向量)→ Rerank → Top-K
- 自管运行时:本地推理的依赖闭包(
@huggingface/transformers + onnxruntime-node + sharp,从宿主收编时本机实测 49 个包 / 约 393MB)可收编到 ~/.dsh/mneme/runtime/,与 profile 的依赖图解耦——由于 profile 是所有插件共用的依赖图,这份闭包留在此前的位置会让「安装任何插件」都替它重走一遍整条依赖链;收编优先硬链接,同盘时几乎不额外占盘。@huggingface/transformers 已从 dependencies 降为可选 peer,因此安装插件不再携带它;正在使用本地嵌入的用户请先按文档收编,否则升级后本地嵌入不可用(读写信道不受影响)
- 取回运行时的三条来源:① 收编本机已有(零网络、同盘硬链接)→ ② 本地
.tgz 目录(runtimeTarballDir,某个包网络下不到时用)→ ③ npm registry(runtimeMirror 可换镜像),按随包发布的 runtime-manifest.json 逐个取并先校验 sha512 再落盘(win32-x64 实测约 33 个包 / 2142 个文件 / 数百 MB;清单平台无关,一份覆盖 win32/darwin/linux × x64/arm64;剔除 onnxruntime-web——Node 构建从不 import 它)。面板「向量索引」卡片在不就绪时会给出这段代价说明并提供一个按钮;不想开面板也可以让 agent 用 memory_runtime 工具(status / provision / verify)代做
配置只需在 cordis.patch.yml 里设置 embedProvider(默认 openai,保持 v0.1 行为;改为 local 即离线)。升级无需迁移数据。
node scripts/mneme-runtime.mjs status # 运行时在哪、来源、结构是否完整(不加载模型)
node scripts/mneme-runtime.mjs adopt --from ~/.dsh/profiles/<profile>/node_modules
node scripts/mneme-runtime.mjs verify # 真加载运行时并跑一次推理
三个命令的退出码为 0 健康 / 1 不健康 / 2 用法错误,便于脚本 gate;--json 输出机器可读结果。运行时不可用时不会崩——检索降级为关键词/BM25,读写不受影响。详见 本地模型部署指南 §2.5。
实体结构化记忆(Entity Gene)🧬
v0.3.0 起新增记忆基因层:从记忆里抽取命名实体、带时间轴的属性、实体间关系,让搜索从"字面关键词"升级为"按实体/属性精确召回"。
- 三表:
entities / entity_attrs(valid_until 快照式时间轴)/ entity_relations,旧库打开自动建表,幂等无迁移成本
- 自动抽取:
entityExtractionEnabled=true 后,新写入的记忆 fire-and-forget 触发 LLM 抽取(同名实体去重、属性存时间轴、关系追加;失败绝不阻塞写入)
- 实体搜索(
searchMemories 前缀路由,entitySearchEnabled 默认开):
entity:阿尔托 → 属性精确关联的记忆(_score 1.0)排在关键词提及(_score 0.7)之前
attr:国籍=芬兰 → 精确匹配该属性值的记忆
attr:国籍 → 该属性键的全部当前有效记忆(value 为空契约)
- autoDream 联动:update 决策写
supersedes 自引用(属性版本被替代);merge 决策把 loser 的属性归属迁移到 keeper(keeper 已有同键当前值则失效)
📖 详见 实体结构化记忆设计 · 语义增强架构 · 本地模型部署指南
记忆质量过滤 🧼(v0.4.6,默认开)
写库前对每条记忆做启发式质量打分(纯函数,无 I/O、无共享状态):元记忆词汇(谈论记忆系统本身)、自指类型标签、内容过短、重复度高、与近期记忆近似重复都会扣分(0-100):
score ≥ 60:正常存储
30 ≤ score < 60:quality_score 落库,注入排序改为按 importance × quality/100 降权(degraded)
score < 30:归档并标记 low_quality——仍可显式搜索召回,只是永不自动注入。例外(#135):importance ≥ memoryQualityFilter.exemptImportance(默认 4)的记忆只降权不归档——评分与信号标签照常落库,归档决定不再静默越过用户标注的重要性
memoryQualityFilter.enabled 可整体关闭,archiveThreshold / degradeThreshold / minContentLength / exemptImportance(1=全部豁免,5=仅最重要豁免)可调。此外更新记忆(memory_update 等)不会抹掉系统信号标签(low_quality / duplicate / meta / self_referential / repetitive / short_content)——它们是「为什么被降权/归档」的审计线索,与用户自传标签并集保留(#135)。
LLM 消耗审计 📊(v0.4.6,默认开)
每次后台 LLM 调用都会写入 llm_audit_logs 表:tokens / duration / status / source(由哪个触发产生)。失败调用记为 status=error,绝不阻塞功能本体;retentionDays(默认 90)在启动时清理超期行。
巩固那一轮还有一层事后判决:调用成功不等于决策可用,而校验发生在调用之后。判决以 metadata.decision_validation(verdict / error_count / skipped_count / survivor_count / invalid_actions)浅合并回同一行——status、tokens、时间戳一律不改,它们是调用层的事实。verdict 四档:unparsed(没解析出 JSON 数组)/ rejected(整单拒绝)/ degraded(只应用了合法子集)/ ok。有了这一层,「调用成功率」与「整轮有效率」才对得上账。
覆盖的后台调用(source 即触发源,operation_type 区分同一源下的不同调用):
source | operation_type | 说明 |
|---|
autoDream | dream_consolidate / dream_summarize | 巩固的整理裁决与总览摘要,各一行 |
autoDream | dream_narrative | 叙述条合成,需 dreamNarrativeEnabled(默认关)才跑 |
autoSummarize | summarize_compress | 空闲蒸馏的压缩 |
sleep | sleep_conflict / sleep_pattern | Sleep Mode 的冲突裁决与模式挖掘,两阶段各一行 |
entityExtract | entity_extract | 实体抽取,每次写入记忆都会跑 |
后两条链路见 #250——它们是结构上漏接的后台路径(sleep 的 streamText 副本没有 onUsage、实体抽取适配器拿不到 service),不是刻意收窄口径。
新增两个只读 API:
GET /api/dsh-mneme/semantic/llm-audit?page=&pageSize=&source= — 分页查询 + 按 source 过滤
GET /api/dsh-mneme/semantic/llm-audit/stats?days= — 近 N 天按 source 汇总预算(tokens / 次数 / 失败数)
/llm-audit/stats 的 by_source 是服务端 GROUP BY trigger_source,不做任何白名单过滤,因此新链路自动进入汇总。
面板侧现状(#250 之后,未改面板):
- 「LLM 消耗」卡读
/llm-audit/stats,但只渲染 total_calls / total_tokens 两个总数。新链路的 token 与次数已经计入这两个总数,所以「mneme 总共花了多少」现在是对的;
- 按链路的拆分(
by_source)目前只能从 API 读,面板不展示——「哪条链路最费」在面板上还答不了;
- 状态页活动流仍只显示
autoDream / autoSummarize 两个硬编码来源(lib/client.js),新链路不会出现在活动流里。
这三处要动面板(活动流还有一处来源白名单加一处二元标签兜底,且 pageSize=12 会被每次写入都跑的实体抽取占满),留到面板批次,不在 #250 内。
实体抽取是每次写入记忆都会跑的链路,计入审计后 llm_audit_logs 的行数会随写入量线性增长(retentionDays 只约束上界、不约束增速)。若实测增速过快,再考虑按 operation_type 采样或单独保留期。
只读端点,与 list/search/semantic 一样在设置 apiToken 后仍保持开放。
记忆库主区视图与记忆图谱 🕸️(v0.5.0)
记忆功能从侧边栏抽屉收进主内容区全宽 tab(conversation.view 插槽,与「对话 / Trajectory」并列),侧边栏「记忆」入口点击后直接激活该 tab。页面顶部子 tab 行切换三个子视图:
- 记忆(三栏浏览):左栏分类树(类型 + 计数)/ 中栏时间树(月 → 日两级倒序、可折叠)/ 右栏详情(全文不截断 + 复制全文);语义搜索内嵌工具栏开关(防抖 250ms),
entity: 前缀可「在图谱中查看」
- 图谱:输入实体名,加载以该实体为中心的关联网络——
- 服务端只读 ego-graph API:
GET /api/dsh-mneme/semantic/graph/ego?entity=<name>&depth=1|2(BFS 层级遍历,limit 防大图失控,实体不存在 404;配套 /semantic/graph/entity-attrs 查实体属性)
- 前端零依赖手写 SVG 力导向布局(插件运行时无法 require vis-network 等第三方库):斥力 + 弹簧 + 向心引力物理模拟,节点按类型着色、按提及次数定半径,支持拖拽、点节点看属性、点边跳回来源记忆
- 设置:画像 / 规则 / 指令 / 向量配置,限宽居中
图谱 ↔ 记忆双向互跳:图谱详情侧的关联记忆可点击,记忆边的「来源记忆」按 memory_id 直跳三栏视图并自动定位。
- 分享包三步(面板):「⋯」菜单里的分享记忆包…(报告先于下载:进包条数、同键折叠、六个跳过原因、可展开的跳过清单,超水位回 413 同屏)与导入分享包…(选文件 → 干跑预览将新建几条 / 撞名几条 → 确认才写库),外来行带
from-<别名> 徽标、工具条「外来」chip 按 source=imported 服务端筛、抽屉「收起这个包」两段式整包退出(先清点后物删)。口径与判据一条都不在面板里复述——出包在 src/share.js、入包闸门在 src/share-import.js、协议层在 src/share-http.js,细则与实测见 docs/SHARE.md
四路召回融合与会话热记忆 🔎(v0.5.0,v0.8.4 扩四路)
- BM25 稀疏第三路召回(
src/search/bm25.js):与向量召回、FTS5/LIKE 关键词并列——ASCII 词元 + CJK bigram 分词、IDF 加权(归一化 [0,1]),专有名词 / ID / 代码片段等散词查询不再依赖子串命中。融合规则:未召回行按 0.3×BM25分 回填;仅向量召回行获得词法加分;LIKE 已命中行不叠分。bm25SearchEnabled 可关
- 实体图召回轴(第四路,v0.8.4)(
entityRecallEnabled 默认关):开启后检索融合池三源扩四源——store 新增 findEntitiesMentionedIn / getLinkedMemoryIds 反查原语 + idx_relations_memory 索引,命中实体所在记忆按边入池,fuseRecall 的 blend / rrf / minmax 全配方参与;实体轴与 BM25 同为确认/回填信号(we 并入 wb 共用 0.3 回填权重),永不主导语义排序,失败降级空数组;keyword 模式纯文本路径不吃实体轴
- 自适应阈值(
src/search/adaptive.js):取代固定 0.65 截断——entity:/attr: 前缀放宽 0.5,短查询(<5 字符)收紧 0.7,长查询(>50)放宽 0.6,Top1/Top5 分差 > 0.3 时放宽让尾部进 Rerank;显式传 threshold 或 adaptiveThresholdEnabled=false 走旧行为
- 会话级短期热记忆(
src/hot-memory.js):最近 N 轮对话(默认 5 轮,hotMemoryRounds)按 token 预算(默认 2000,hotMemoryMaxTokens)滚动截断,从会话事件日志无状态重建、不落库;注入顺序为「短期上下文 → 长期记忆召回 → 摘要」
- 选择性注入:query 向量可用时注入候选按主题相似度重排(
selectiveInjectEnabled 可关);搜索时语义去重为激进选项(searchSemanticDedup=true 显式开启,近重复行 Rerank 前丢弃)
- 召回基准(
scripts/benchmark-recall.js):标准查询集驱动,计算 Recall@5 与 MRR,legacy(三特性全关)vs fused(默认配置)双跑对比
📜 版本历史与路线图
完整版本说明见 CHANGELOG 与各版本 GitHub Release;当前特性以本 README 正文与配置表为准。早期版本中的实验性功能(Wiki-Link、tag 系统等)已在 v0.7.11 移除,详见 CHANGELOG 对应条目。
🧬 Gene → 🛡️ 审计加固 → 💤 Sleep Mode → 🕸️ 召回融合与图谱 → ✨ 面板体验 → 🌡️ 自进化记忆 → 🔐 作用域隔离 → 🌐 MCP 生态 → 🧭 M.O.(项目级工作流,v0.9 beta)
| 版本 | 主题 |
|---|
| v0.9.0(2026-10-17) | M.O.(Modus Operandi)beta:工作流本体住 <项目>/.mneme/pipelines/、候选分层与升格判据、面板清单与图同屏(actionPipelineEnabled 默认关);设置面板折叠线挪到「新手之外」并按人话重写 22 个条目名(#427);安全与供应链加固(39 条路由声明式鉴权、OSV 有漏洞即失败、发布带 provenance) |
| v0.9.1(2026-10-18) | M.O. 从「看得见」跑到「跑得出账」:执行窗口由 /mo-run start / /mo-run stop 显式划开、运行账落 <项目>/.mneme/pipelines/<id>.runs.jsonl(账本坏了与从没跑过严格分开)、归属只出候选、观察旁挂与脱节检出、步骤 assert / optional、R1/R2 提议;沉淀链从真实会话日志挖 draft 工作流(判据恒 null,答案只能由人补);人格定义页 v1 → v1.2(按 agent 寻址多卡,默认关);面板、向量与检查更新的一圈修复(#448 / #450 / #460);Release 正文改分层渲染 |
| v0.9.2(2026-10-19) | 让「看不见的失败」显形:功能开关的第三态「待重启」把「面板显示已改 ≠ 运行时已改」摆到界面上(#481);作用域级可逆闸门让一路记忆能被单独按下与松开、不必改配置重启(#471,默认关);巩固用的 dreamMaxTokens 默认值撞服务商上限、八天 180 次全失败,面板现在能改、超上限还会自动降级重试(#478);CLI 把 -- 开头的值当新旗标的真实缺陷(一次 1/4096 概率的 CI 红揪出来的)、人格卡删除 × 永远不可见、自动蒸馏写入的行 100% 拿不到向量(#477) |
| v0.9.3(2026-10-20) | M.O. 从「挖得出来」到「会被叫去挖」:沉淀链算法自 v0.9.1 起就完整实现(多帧 zstd → 重复序列 → 缺席对照 → draft 草稿),运行时却没有任何调用点——机器挖得出来、却永远不会被叫去挖。这一版把归属与触发接上:按会话头 cwd 反查项目桶(桶名是路径的有损变形,查不到整跑跳过而不是降级成全局普查)、挂 turn/end、冷却 10 分钟、单次预算 4 秒、延到宏任务、一次一个项目、抛错只 warn,只产提议不产草稿(actionNudgeEnabled,beta,默认关);面板加「现在沉淀一次」,与自动跑同一条入口,区别只有它不改仓库;另修作用域反查自 v0.8.0 起每次都抛错走降级(#487)、/nudge 三处「没看全」被报成「确实没有」(#489 评审)、多帧 zstd 反证断言绑了 Node 整块解语义(#490),并把整个 M.O. 的「手法」改回「工作流」 |
| v0.9.4(2026-10-21) | 三条链各自补上「最后一环」——东西早就造好了,只差没人能叫它:agent 主动整理(memory_organize,先 dryRun 出比对报告 → agent 判断 → apply 落库,筛除只归档不删、全程留审计回执;organizeEnabled 默认关,关时工具根本不注册);画像提取草稿(面板按钮读活跃记忆 + 一次模型调用,产一段「助手眼中的用户」草稿,profileExtractEnabled 默认关)——只出草稿,采用是用户再点一次,手写的画像原文不会被模型改写,空库与解析失败都直接报错、绝不回空草稿冒充「没提炼出东西」;人格寻址收口(default 卡以前无条件落给所有没建卡的身份,宿主里自带身份的 bot 每帧收到两句「你是谁」;personaStrictAddressing 默认关,把「没建卡」与「卡被显式关掉」都收回为不注入)。同版补蒸馏的转录围栏 + 解析失败护栏(#494:一条会话被同一窗口卡死、重试 20 次越试越贵的根因;失败行现在恒带原始输出形状),把面板自称「注入生效文本」的只读区改成后端单源渲染(并修它从不显示人格行),评审另收掉两处「坏了却不报」:整理报告侧的存在性探测口(strictScope 下按调用方可见性过滤落点并重算计数)、画像保存不判 res.ok 却提示已保存 |
| v0.9.5(2026-10-30) | 记忆能安全地递给别人,也能整包收回来:分享包 ( / / ,面板「⋯」里同一批三件事)只含活跃行,scope 与 home 前缀不进包,命中密钥 / PII 的行整条不进;出包先给报告再给文件,超 3 MiB 水位回 413、按 / 分批;入包一条不合格整包拒、干跑为默认、按 幂等,整包退出走物删。「本机巩固不许改写别人导入的原文」由单一谓词 守在汇流口与九个行池上。同版给记忆来源加了读出口 (#421,@z2Ace0107 实现;409 = 用户关了、503 = 装配坏了),修掉同标题重复入库(#505)、扫描谎报「没东西可扫」(#515)、五处未围栏的巩固 prompt(#501)与 dream 合并护栏整类误拦(#521),面板新增设置页分组目录(#518) |
| v0.8.x | 作用域隔离(agent / workspace 双维 + opt-in 硬过滤)、冲突队列人工裁决、生态化(stdio MCP server / 图召回轴 / 冷启动 / 注入截断) |
📦 安装
前置条件
- DeepSeek Harness(DSH)— 兼容 DSH 0.1.x 与 0.2.x(三段式 peer 范围见
package.json);开发与 CI 已对齐 0.2 线,0.2.0-rc.1 / 0.2.0-rc.2 实测全绿。旧版兼容垫片(Session.events → snapshotEvents(),v0.7.9 起确保进入发布产物 lib/)保留
- Node 24+(
node:sqlite)
安装步骤
方式一:npm 安装(推荐)
dsh-mneme 是一个 bundle(声明了 dsh.bundle manifest),安装即自动激活,无需手动写配置:
# 1. 安装插件(自动注册 bundle 层)
dsh plugin --profile web add @modusensus/dsh-mneme
# 2. 重启
dsh web
如需自定义配置(阈值、延迟等),可在 ~/.dsh/profiles/web/cordis.patch.yml 中按 id: dsh-mneme 覆盖默认值(见下方配置表)。
方式二:从源码安装(本地目录)
git clone https://github.com/slow-stack/mneme.git
cd mneme/dsh-mneme
dsh plugin --profile web add "$(pwd)"
dsh web
路径必须绝对:宿主对 dsh plugin add 的本地路径有一条硬校验(parseInstallSpec 里的 a local path must be absolute),相对路径(含 .、..)一律拒掉。上面用 "$(pwd)" 就地展开,就是为了不给它相对路径。
方式三:直接从 Git 仓库安装
本仓库是单仓多包:仓库根那份 package.json 是仓库壳(private、不带 dsh 字段),插件本体在 dsh-mneme/。所以 Git 地址必须带 #path: 指到子目录:
dsh plugin --profile web add 'git+https://github.com/slow-stack/mneme.git#path:/dsh-mneme'
dsh web
| 写法 | 结果 |
|---|
git+https://github.com/slow-stack/mneme.git#path:/dsh-mneme | ✅ 装出 @modusensus/dsh-mneme(实测 0.9.0,38 个包) |
https://github.com/slow-stack/mneme#path:/dsh-mneme | ✅ 同上;lockfile 会归一成 github:slow-stack/mneme#path:/dsh-mneme |
git+https://github.com/slow-stack/mneme.git | ❌ 装到仓库根那份壳(0.0.0、无 dsh 字段),宿主不认它是插件 |
#path: 是 pnpm 的 git 参数(parseGitParams 里按 path: 前缀解析),不是本仓库自定义的语法;pnpm 官方文档没写这一条,所以这里给出实测结果。
方式四:tarball
cd dsh-mneme
npm pack # 产出 modusensus-dsh-mneme-<版本>.tgz
dsh plugin --profile web add "C:/path/to/modusensus-dsh-mneme-0.9.0.tgz"
必须用 npm pack,别用 pnpm pack:bin/cli.mjs 在工作区里是 CRLF,pnpm 会以 ERR_PNPM_BIN_CRLF 直接失败。
自定义配置(可选)
默认配置即可用。如需调整,在 ~/.dsh/profiles/web/cordis.patch.yml 中覆盖:
- id: dsh-mneme
name: '@modusensus/dsh-mneme'
config:
memoryDir: ~/.dsh/memory
language: zh
autoInject: true
autoSummarize: true
maxInjectedItems: 5
importanceThreshold: 3
autoDream: true
dreamThresholdCount: 10
dreamThresholdChars: 5000
dreamDelayMs: 2000
⚙️ 配置
| 键 | 默认值 | 说明 |
|---|
memoryDir | ~/.dsh/memory | 记忆存储目录(SQLite + Markdown) |
language | zh | 记忆语言:生成的记忆条目、注入标题与后台 LLM 提示词所用语言(zh / en) |
autoInject | true | 会话启动自动注入记忆 |
autoSummarize | true | 会话结束自动提炼摘要 |
summarizeProvider / summarizeModel | 空 | 摘要的 LLM 路由覆盖(空=使用当前会话模型);推荐轻量模型节省主模型 token |
summarizeMinIntervalMinutes | 0 | autoSummarize 最小触发间隔(0-10080,0=不限):两次蒸馏之间最短间隔,失败/degraded run 也占用(#127) |
summarizeMaxEntriesPerRun | 0 | 单次蒸馏产出记忆条数上限(0-50,0=不限):节流防单会话大量重复条目(#127) |
summarizeMinWindowChars | 0 | 蒸馏前零 LLM 预判:窗口可蒸馏文本不足此字符数直接跳过调用(0-100000,0=关);skip 原因写入审计(#239) |
summarizeMaxRunsPerSession | 0 | 每会话最多发起多少次蒸馏 LLM 调用(0-1000,0=不限);只计真实调用,被预判拦下的不占额度(#239) |
summarizeParseFailureMaxRetries | 0 | 连续几次解析失败后放弃当前窗口(0-10,0=关):确定性失败(模型把转录当成该自己回话,finish_reason=stop)重试收益为 0,而窗口只会越攒越大;设 2-3 可止血,达到阈值强制消费游标跳过,失败行 metadata 带 raw_head/raw_has_bracket/streak(#493) |
summarizePeakHours | 空 | 高峰时段(本地时间,逗号分隔、可带星期前缀,支持跨零点):空=关;命中时蒸馏顺延到非高峰、窗口累积后一次蒸、skip 原因入审计。例:按高峰计费的供应商可写 mon-fri 08:00-12:00,14:00-18:00(如 DeepSeek,以其官方定价页为准)(#239) |
summarizePeakMaxDeferMinutes | 120 | 高峰顺延上限(0-1440 分钟,0=不设上限):到点仍处高峰就照常跑,避免长高峰把蒸馏饿死(#239) |
summarizeDedupeMode | off | 落库前去重档位:off(默认=现状)/ title(零成本,仅拦完全同名)/ vector(复用 embedding 列做同会话语义近邻,无 LLM 调用,#127) |
summarizeDedupeMinSim | 0.92 | vector 去重档的相似度阈值(0.5-0.99) |
🔐 API 安全:DSH 无内置鉴权且默认仅监听 127.0.0.1。插件 API 默认开放(便于 Web 面板即装即用)——未配 apiToken 时读和写都开放,真正的围栏是回环 + 宿主监听器的 Host/Origin 校验(它防 DNS rebinding 与跨站浏览器请求,但不建立身份)。如需防护(如局域网暴露),在配置中设置 apiToken:此时写操作(新增/更新/删除/导入/收包(/receive、/receive/undo——写的是本机自己的库,归 token-write,所以免 token 的 GET 在那两条路径上只会拿到 405)/画像/规则/命令/开关/开关计费类)与敏感读(/export、/external-api、/vector-config、/vector-reindex、/maintenance/*、/runtime/provision、/share、/memory/provenance(蒸馏窗口的逐条会话原文,另受 distillRecords.enabled 门控))需携带 Authorization: Bearer <token>(前端设置面板填同一个 token);只读的 list / search / semantic / inject-preview 等仍然开放,不要把它们当成被 token 保护的面。/api/dsh-mneme/vector-config 返回的 apiKey 已掩码(sk-***…),存储仍保留明文供调用;前端回传空或掩码值表示"不改 key"。
安全模型全表(哪些路由免鉴权、为什么)见 SECURITY.md。
⚠️ 已知边界:极简模式(minimal agent preset)下不注入(宿主设计,非插件缺陷):minimal 预设的组合文件显式 complete: true + includeRuntimeContext: false——整条系统提示词被 persona 钉死、全部 runtime context 快照被压制,记忆注入 / 用户画像 / hot memory 在极简模式会话中一律不送达模型(宿主内置 context 同样消失)。判断与解法:会话头部显示「极简模式」即为该形态;需要记忆注入请改用标准模式(会话级切换,或 ~/.dsh/settings.yaml 设 agent-presets.default: standard——注意 --dump-config 显示的是 bundle 默认值,会被用户层覆盖,不能反映实际生效值);极简模式下的过渡方案是把画像/规则写入 AGENTS.md(agent-instructions 走 section 路径,不受该压制影响)。
外部 API 与 CLI
除 DSH 内部端口外,插件还可以开启一个独立的 HTTP 外部 API(默认 http://127.0.0.1:8790,Bearer token 鉴权),供其他插件、CLI 脚本或桌面工具读写记忆,不依赖 DSH 内部端口。
启用与鉴权
- 在插件设置中开启外部 API(默认监听
127.0.0.1:8790,仅本机可访问);
- 访问 token 在插件设置 / 面板「设置 → 外部访问」中查看;
- 除
GET /health(免鉴权)外,所有路由需携带 Authorization: Bearer <token>,无效 token 返回 401 {"error":"unauthorized"}。
主要路由:
| 方法 | 路由 | 说明 |
|---|
GET | /health | 健康检查(免鉴权),返回 {ok:true} |
GET | /status | 版本、记忆统计、实体数、运行时长 |
GET | /profile | 用户画像,返回 {profile:"..."};未设置时为空字符串 |
GET | /rules | 行为规则列表,返回 {rules:[...]};未设置时为空数组 |
GET | /memories?limit&offset&type&minImportance&source&order=chrono&include_archived&occurred_from&occurred_to | 分页列出记忆(include_archived=true 含归档;occurred 时间窗按 occurred_at 回退 created_at) |
GET | /memory/provenance?id=<memoryId> 或 ?sessionId=<sid>(可选 &limit=,默认 20、上限 100) | 溯源读出口,两个方向共用一条路径(#421 拆出两条:方向 1 记忆→窗口由 #509 交付,方向 2 会话→窗口由 #524 补上;宿主内面板路由 /api/dsh-mneme/memory/provenance 同一口径)。两个主键互斥:都缺 400 {"error":"missing-id"}、都给 400 {"error":"ambiguous-id"}(刻意不静默择一——两个方向的结果形状不同,猜一个只会在出事之后才发现)。方向 1 是一条溯源链接一个窗口 {session_id, seq_start, seq_end, created_at, distill_run_id, run_type, trigger_source, run_status, records[]}(#512 补运行归因:run 是归因主体,窗口只是它的一批输入;读不到整轮归因时整组 null,不从区间或时间戳反推),答复级另带 mints[](行级归因:这条记忆出生自哪一轮 dream/sleep,只填 create);方向 2 把同一个 (session_id, seq_start, seq_end) 合并成一项(GROUP BY),带 link_count=这个窗口派生出几条记忆,按 seq_start 排——逐链接返回会让「这个会话蒸了几次」被重复行淹没。两个方向都带 truncated(撞上 limit 时说「还有更多」;少了它,被截断的答复与「本来就这么少」逐字节相同)。records 是那段蒸馏窗口的逐条会话原文(按 seq 升序、不越出区间)。没有链接返回空 windows——「不是蒸馏来的」是有效答案,不是错误。状态码阶梯:401 无 token → 400 缺参/多参/invalid-limit → 409 {"error":"distill-records-disabled"}(distillRecords.enabled 关着;该开关是启动快照,面板改完要到下次启动生效,面板会显示「待重启」)→ 503 {"error":"provenance-unavailable"}(该方向要用的方法没接线,按方向判——只缺 session 侧方法时方向 1 仍然 200)→ 200。409 与 503 必须分开读数:前者是「用户关了」,后者是「装配坏了」。——只是不再经 HTTP 提供,盘上的行要 (或重新开启后的启动裁剪)才走,详见 |
| GET | /memories/:id | 单条记忆 |
| POST | /memories | 新增记忆 {type,title,content,importance?,tags?,source?,sensitivity?,occurred_at?,agent_scope?,workspace_scope?};响应含 action: created\|merged |
| PUT | /memories/:id | 局部更新 {title?,content?,type?,importance?,tags?,reason?,agent_scope?,workspace_scope?};content 改写按 human_override 入档 |
| DELETE | /memories/:id | 删除记忆 |
| GET | /search?q&mode=keyword\|vector\|hybrid\|auto&topK&occurred_from&occurred_to&agent_scope&workspace_scope | 搜索(关键词 / 向量 / hybrid=向量领位关键词补位 / 自动)。scope 两参声明调用方身份(不传 = 不解析,行为与旧版一致;任一给出则 strictScope 硬过滤与 A2 软加权随之生效) |
| GET | /context?q&topK&threshold&agent_scope&workspace_scope | 注入候选一站式聚合(issue #370,#363 承诺):宿主注入管线 injectCandidates 全语义(优先级分层 / 编码门控 / heat / scope 软加权 / hybrid 语义路 / pin 前置)+ 画像 + 规则,返回 {profile, rules, items, pinnedCount, tier} 结构化条目,不含渲染文本。threshold 是 importance 阈值(默认 3,与 /search 的相似度阈值同名不同义);topK 默认 5(对齐宿主 maxInjectedItems);scope 语义与 /search 同口径;跨轮轮换 / 热记忆 / 时间前缀不含(调用方自管);recall_runs 注入分账与宿主注入同口径(#217 mode='inject') |
| POST | /bootstrap | 冷启动(v0.8.4):从仓库目录反向构建初始记忆,body {dir} 必填;幂等((type,title,scope) 去重 + _overwrite 原地刷新),零 LLM、必有产出 |
| POST | /maintenance/reclaim | 无损回收(#275 / #411,与面板同口径):只有 POST(本路径不提供 GET 预估分支)。字段只从 JSON body 读;dryRun 默认 true,只有 dryRun:false 且 confirm:true 才真删;vacuum 只允许显式指定 |
| POST | /share | 记忆分享包:把活跃记忆按口径打成可外发的 JSON 包,纯读不写库;取行与跳过口径、脱敏规则与 3 MiB 体积水位见 docs/SHARE.md。本路径的 GET 一律 405 |
| POST | /receive | 吃进一份分享包(body {pack, confirm?},包内容整个送上来、服务端不读路径):默认干跑,只有 confirm: true 才写库;整包一个事务、按 share_external_id 幂等(同一个包重跑 created = 0),外来行标 source=imported,不参与本机巩固/降级。包里一条不合格就整包拒(收侧不做逐条修复,也不跑敏感扫描)。入站上限 4 MiB;GET 一律 405。细则与实测见 docs/SHARE.md |
| | | 整包退出(body ,同样只清点):按 (不是归档——归档行会占住 UNIQUE 位,撤了反而导不回来), 是全量、 只给前 20 条预览;什么都没导入时答 0,不抛。GET 一律 405 |
/context 的 tier 档位信号(#383)
GET /context 的响应多一个 tier 字段,回答「这次注入跑在哪一档」。嵌入降级在调用方视角原本完全静默——items 看着正常,只是召回质量悄悄变差,外部集成方无从感知自己长期跑在降级档上。
tier | 含义 |
|---|
full | 语义档:查询嵌入成功,且与库里的向量维度可比 |
degraded | 规则档:语义路本次不可用(原因见日志) |
unknown | 语义路未启用(q 为空),或判不出来(维度探针没跑成 / 一条向量都读不出)——那不是降级,但也不宣称 full |
两套消费者的分工: tier 是响应读数,给调用方——三态、fail-closed(判不出来报 unknown,不报 full),用于在集成侧告警或切换策略。降级的具体原因是日志读数,给运维——走 daemon 的 stderr(dsh-mneme: /context degraded to the rule tier (<reason>)),不进响应体。原因枚举六值,互不吞并(成例是「修法不同就分开」):embed-unavailable(无可用嵌入接口:embedder 对象在,上面没有 embedSingle / embed)/ embed-empty(返回了空或非数组向量)/ embed-timeout(接口挂起,被超时护栏按缺失处理)/ embed-error(接口抛错)/ embed-dimension(接口成功了,但库里没有一条维度相符的活跃向量)/ no-embedder(压根没装配 embedder——daemon --embed off、lightMode、宿主未装配;这是预期态,不是故障)。六值的唯一真源是 src/api-standalone.js 导出的 CONTEXT_DEGRADE_REASONS,契约测试从它派生断言集合。
embed-dimension 值得单说:它是「拿到了向量却不可比」——searchVector 对维度不符的行直接跳过,不抛错、只是命中为零,于是召回静默退回关键词/规则档,而嵌入侧一切正常。修法是重建索引,不是查端点,所以它必须和前四个分开。no-embedder 与前四个也分开:前者(embed-unavailable)是适配器坏了、要修代码,后者是语义路没开、要把嵌装扮上或开起来,主体与动作都不同;并成一个值会让按 embed-unavailable 告警的面板对每个轻量模式安装报警。用词则与宿主面 /api/dsh-mneme/semantic 的 reason 对齐——同一个物理条件在两个面叫同一个名字(那边是另一套枚举、另一个消费者,别顺手统一)。
unknown 之下的判档故障另有一条 warn: unknown 有三个生产者(空 q / 维度探针没跑成 / 有行却一条向量都读不出来),响应体只有 tier 一个键、分不出它们,而调用方对 unknown 的策略常常是「不告警」——所以判不出来的情形会另打一行 dsh-mneme: /context tier left unknown: <what>(探针抛错 / 没有可读的向量可比)。它不是上面那六值的一员(tier 不是 degraded,没有降级原因可记),运维程序不要按原因枚举解析它。
预算: 判档不是免费的。q 非空且装配了 embedder 时,一次 /context 真的会起一次查询嵌入(一次网络往返 + embedTimeoutMs 的超时窗口)——档位要回答的正是「这次嵌入成不成」。这里没有复用 /health 的数据源:standalone 的 /health 只回 {ok:true},而 embeddingDimensionBreakdown 是宿主面的一次全表扫(每行 JSON.parse 一个向量),按请求跑会把语义路成本翻倍。维度探针因此走 service.vectorDimensionState()——按匹配短路的存在性判据(有没有一条活跃向量的维度等于查询维度),不是「读一行的维度」:混合维度库里 LIMIT 1 会抽错维(明明有可比的向量却报 degraded),首条恰好解析失败时又会漏报成 full。成本是一次无 LIMIT 的全 id 扫描 + 按匹配短路的解析:全库同维度(常态)时循环第一行就返回,只解析 1 个向量;只有一条都不匹配时才走完整个循环,而那正是要报 degraded 的情形,值得。解析复用 getParsedEmbedding 的缓存,缓存热不热取决于 injectCandidates 是否真的走了 vectorIndex 分支。
curl 示例
# 服务状态
curl -s -H "Authorization: Bearer $DSH_MNEME_TOKEN" http://127.0.0.1:8790/status
# 列出最近 5 条记忆
curl -s -H "Authorization: Bearer $DSH_MNEME_TOKEN" \
"http://127.0.0.1:8790/memories?limit=5"
# 新增一条决策记忆
curl -s -X POST http://127.0.0.1:8790/memories \
-H "Authorization: Bearer $DSH_MNEME_TOKEN" \
-H "Content-Type: application/json" \
-d '{"type":"decision","title":"采用 SQLite","content":"存储层使用 node:sqlite","importance":4,"tags":["存储"]}'
CLI 安装
插件自带零依赖 CLI(随 npm 包一起发布):
npm i -g @modusensus/dsh-mneme
dsh-mneme --help
首次使用先配置服务地址与 token(也可用环境变量 DSH_MNEME_URL / DSH_MNEME_TOKEN,或 --url / --token 参数临时覆盖):
dsh-mneme config set http://127.0.0.1:8790 <你的token>
CLI 常用命令
dsh-mneme status # 服务状态
dsh-mneme list --type project --limit 10 # 列出记忆
dsh-mneme search "部署流程" --mode vector --topk 5 # 语义搜索
dsh-mneme add --type decision --title "采用 SQLite" \
--content "存储层使用 node:sqlite" --importance 4 --tags 存储,决策
dsh-mneme get 42 # 查看单条
dsh-mneme delete 42 # 删除
dsh-mneme config show # 查看当前配置(token 打码)
dsh-mneme share --dry-run # 分享包:只看会进几条、跳过几条,不落盘
dsh-mneme share --out pack.json # 写一个可外发的脱敏包(3 MiB 水位在源头拒)
dsh-mneme receive pack.json # 吃包:默认干跑,一个字都不写
dsh-mneme receive pack.json --apply # 确认后真的吃进自己的库(整包一个事务,可重跑)
dsh-mneme undo-share pack.json --apply # 按 pack_id 整包收回(物删,不是归档)
所有读取/写入命令支持 --json 输出原始 JSON;config path 打印配置文件路径(~/.dsh-mneme/cli.json)。
人类可读档只转达服务端文案里的 hint / bytes / limitBytes;闸门的 detail(坏在哪一格)与
413 的 limit 要 --json 才看得见——排查收包失败时先加它。
独立服务(daemon,dsh-mneme-serve)
不启动 DSH 也能让记忆库对外服务:dsh-mneme-serve 在宿主之外组装同一套 lib,把数据面跑成常驻进程(路由与鉴权与「独立外部 API」完全同源)。适用场景:第三方集成(如 Mneme-Bridge 这类网页端桥接,见下方社区适配器)需要长期挂载记忆库,而 DSH 不必一直开着。
dsh-mneme-serve # 默认 ~/.dsh/memory + 8790
dsh-mneme-serve --memory-dir "D:\my mem" --port 8790 --host 127.0.0.1
dsh-mneme-serve --embed off # 纯关键词 + BM25(不取件模型)
- 鉴权与端口:Bearer token 与 DSH 面板 / CLI 共用同一份(kv
external_api,首次启动自动生成并持久化到 memory.db);端口/主机解析链与「外部访问」一致(显式参数 > 持久值 > 默认 8790/127.0.0.1)。配置端口被占会直接报错退出(不做端口顺延)——第三方把 URL 写死,静默换端口等于坏。因此 daemon 与 DSH 的「外部访问」二选一,不要同端口同开。
- 安全:daemon 使用明文 HTTP,不提供原生 TLS。指定非回环
--host 时,请勿直接把服务暴露给不可信网络;远程访问请走 TLS 终止代理或 SSH 隧道。
- 能力边界(第一期,无 LLM):存储 / 检索(关键词 + BM25 + 向量)/ 镜像同步与人改合并 /
POST /maintenance/reclaim / POST /receive(吃包入库与整包退出:结构落库 + 随提交排空嵌入,不建实体——外来行本来就不做抽取,见 docs/SHARE.md)/ /bootstrap 全可用;巩固(autoDream)与蒸馏不在 daemon 内——巩固只属于 DSH 宿主进程,这是与宿主「单写者」的机械保证。--embed 默认 local(自管 runtime 与嵌入模型缺失时自动取件,约 200MB;失败降级关键词并打日志),也可选 ollama / openai(读宿主面板的 vector-config)/ off。
- 检索回执:daemon 的
/search 同样落 recall_runs,第三方检索的复用统计不缺数。
- 生命周期:stdout 仅就绪时打一行
dsh-mneme-serve listening on http://host:port (pid N)(供脚本解析实际端口),日志走 stderr;SIGINT/SIGTERM 优雅收库,Windows 强杀由 WAL 回放兜底。
- 已知限制:与 DSH 同时运行属设计内场景(WAL 多进程并发),但去重是先查后写、库层无 UNIQUE 约束,双进程并发写同一
(type, title, scope) 有极小概率产生重复;daemon 与插件请同版本升级。细节与坑清单见 docs/DAEMON.md。
MCP Server(任意 MCP 客户端接入)
Claude Code / Codex / Hermes / OpenCode / OpenClaw 等各客户端的最小挂载配置速查表见根 README;本节是完整配置与安全说明。独立分发包 mneme-memory(bin mneme-mcp)已发布,与插件内置的 dsh-mneme-mcp 并存、挂载命令互相兼容。
插件自带 stdio MCP server(bin/dsh-mneme-mcp.mjs,零依赖,随 npm 包发布,bin 名 dsh-mneme-mcp)。任何支持 Model Context Protocol 的客户端(Claude Code、Cursor 等)挂载后即可获得与 DSH 内一致的记忆工具六件套:memory_save / memory_search / memory_list / memory_get / memory_update / memory_delete——工具名、参数与去重合并、重要性等语义与 DSH 内工具对齐(测试锁漂移)。
数据面走上面的独立外部 API(8790,Bearer):写入并发由 DSH 单点负责;DSH 未运行(外部 API 未启动)时 MCP 侧调用会报连接失败。scope 语义注意:外部 API 无会话上下文,memory_save 不做自动标注,只认显式 agent_scope / workspace_scope 声明。
配置优先级沿用 CLI 约定:环境变量 DSH_MNEME_URL / DSH_MNEME_TOKEN > ~/.dsh-mneme/cli.json > 默认 http://127.0.0.1:8790。安全注意:服务默认只绑 127.0.0.1;若把 DSH_MNEME_URL 指向非回环的明文 HTTP 地址,Bearer token 将明文过网(启动时会有 stderr 警告)——远程场景建议走 SSH 隧道。
Claude Code 挂载示例(项目根 .mcp.json;token 在面板「设置 → 外部访问 API」查看):
{
"mcpServers": {
"dsh-mneme": {
"command": "dsh-mneme-mcp",
"env": { "DSH_MNEME_TOKEN": "<你的token>" }
}
}
}
未全局安装 npm 包时,把 command 换成 npx、加 args: ["-p", "@modusensus/dsh-mneme", "dsh-mneme-mcp"] 即可。
社区适配器
以下项目由社区维护,把 mneme 接入第三方平台或宿主。它们不是本仓库的一部分,兼容性由各自维护者跟进——升级 mneme 后如遇接口变更,请优先查看对应项目的 release note。
| 项目 | 平台 | 说明 |
|---|
| Mneme-Bridge | DeepSeek 网页端 | 浏览器扩展(MV3)+ 本地 Node 服务:网页端会话蒸馏进 mneme 记忆库,DSH 侧记忆按话题回注网页对话,两侧共享同一份记忆。embedded(直接挂载 mneme lib)与 remote(standalone API,推荐配合 dsh-mneme-serve)双模式 |
想被收录?在 Discussions 开帖介绍你的项目,或直接提 PR 补充本表。
🏗️ 架构
┌─────────────────────────────────────────────────┐
│ 存储层:SQLite (archived/forgotten 状态) │
│ + Markdown 镜像(人工可编辑,双向同步) │
├─────────────────────────────────────────────────┤
│ 服务层:saveWithDedupe / injectCandidates │
│ / mergeHumanEdits / onWrite 钩子 │
├─────────────────────────────────────────────────┤
│ 模型接口:11 个工具 + 自动注入 + 会话摘要 │
├─────────────────────────────────────────────────┤
│ autoDream:阈值调度 → LLM 决策清单 │
│ → 校验(fail-safe)→ 应用 → 摘要 │
├─────────────────────────────────────────────────┤
│ Web 面板:设置面板内嵌 + 浏览/搜索(含向量) │
└─────────────────────────────────────────────────┘
源码结构:
src/
├── store.js # SQLite 存储(CRUD、搜索、归档/遗忘、schema 迁移、scope 四列)
├── mirror.js # Markdown 镜像(渲染/解析,人工优先)
├── service.js # 领域逻辑(去重合并、注入筛选、写入钩子、scope 加权/硬过滤、冲突队列)
├── scope.js # 作用域解析(agentPreset + workspace registry 反查,issue #17)
├── config.js # schemastery 配置 schema
├── tools.js # 11 个模型工具(defineTool;memory_organize 要 organizeEnabled 打开才注册,开了是 12 个)
├── user-settings.js # 「用户设置」注入块(人格 → 画像 → 规则)的唯一渲染处,注入与面板预览共用
├── profile-extract.js # 从活跃记忆提取「助手眼中的用户」画像草稿(只出草稿,不写 user_settings)
├── organize.js # agent 主动整理:dryRun 比对报告 → agent 判断 → apply 落库(筛除=归档不删,留 dream_runs 回执)
├── inject.js # systemPrompt.context 动态注入(含花括号转义)
├── summarize.js # 会话结束 LLM 摘要
├── dream.js # autoDream 调度 + runDream(LLM 决策 + 摘要)
├── dream/decisions.js# 决策校验(fail-safe)+ 决策应用
├── dream/clustering.js # 候选集聚类(k-means++ seeding)
├── dream/sleep.js # Sleep Mode 分层压缩
├── entities/extractor.js # 实体抽取器(v0.3.0:LLM JSON 抽取 + 去重 + fail-safe)
├── search/bm25.js # BM25 稀疏召回(v0.5.0:分词 + IDF 索引)
├── search/adaptive.js# 自适应相似度阈值(v0.5.0)
├── hot-memory.js # 会话级短期热记忆(v0.5.0:滚动轮次 + token 预算;注入剥离 reasoning)
├── runtime/ # 自管本地推理运行时(收编 / 本地 .tgz / registry 三档取件 + 校验)
├── embedding.js # OpenAI 兼容 embeddings 客户端 + 向量检索
├── api.js # HTTP 路由(Web 面板数据通道,含 /conflicts 冲突队列)
└── index.js # 插件接线
lib/ # src 的同步分发产物(npm run sync,prepack 钩子在发布时自动跑;一致性由 scripts/check-sync.js 在 CI 与 release.yml 校验——它只查不同步;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
test/ # 2681 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步 · check-sync.js 发布闸门 · benchmark-recall.js / benchmark-embed.js / benchmark-rerank.js 基准 · sync-test-badge.mjs 测试徽章 · build-runtime-manifest.mjs 运行时清单
🧪 开发
cd dsh-mneme
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
npm test # 运行 2681 个测试
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
压测(npm run stress)三条轴线:长会话检索(Recall@k、陈旧残留率)、冲突裁决(可重放仲裁集:审计快照 hash + receipt + 幂等回放)、多 Agent 并发(丢更新、重复合并、事务/崩溃恢复)。每次 autoDream 运行都会写入审计表 dream_runs(输入快照 digest + 决策清单 + 逐 id 去向 + receipt),让高通过率下也能定位静默错误。
lib/ 是 src/ 的同步分发产物(npm run sync):除 lib/client.js(Web 面板 bundle)为 lib 独有的手写文件、sync 与 check-sync 均不触碰外,其余全部由 src 复制而来。改 src 必 npm run sync 并提交 lib,否则发布产物静默失效(issue #65 教训)。
📄 设计文档
设计文档在 dsh-mneme/docs/(本目录内,直接相对链接);只有能力对比表在仓库根 docs/,用 ../docs/ 指过去。GitHub 上从本目录打开均可正常跳转。
🙏 致谢
autoDream 的理念溯源(理念借鉴、实现原创):
在上述工作之上,dsh-mneme 做了自己的工程发展:K-Means++ 聚类预分组(src/dream/clustering.js)、类型化决策清单(keep / merge / archive / conflict / update,及 sleep 侧 supersede / differentiate)与可回放的 sha256 摘要审计链(dream_runs / receipt_chain)。如有遗漏的灵感来源,欢迎提 issue 指出。
📚 相关研究
dsh-mneme 的机制设计与下列公开研究相互印证(以下引用均逐一核对过 arXiv 收录页;机制对应关系为我们自己的映射,非原论文观点):
📜 License
MIT
🤝 参与之前
行为守则:CODE_OF_CONDUCT.md。它管三件事——署名归人(模型不进作者行)、责任落在签下作者行的人身上、发言权按证据分配,不按身份。参与 issue / PR / 评审即受其约束;使用本插件不受约束,MIT 已经授权。