# dsh-memory-graph **为 DeepSeek Harness 提供本地优先的长期记忆与时态知识图谱。** [![CI](https://github.com/zmh2000829/dsh-memory-graph/actions/workflows/ci.yml/badge.svg)](https://github.com/zmh2000829/dsh-memory-graph/actions/workflows/ci.yml) [![Version](https://img.shields.io/badge/version-0.7.0-2563eb)](package.json) [![Node.js](https://img.shields.io/badge/Node.js-%5E22.19%20%7C%7C%20%3E%3D24-339933?logo=nodedotjs&logoColor=white)](package.json) [![License](https://img.shields.io/badge/license-MIT-0f172a)](LICENSE) [English](README.md) · [快速安装](#安装) · [配置](#配置) · [数据与安全](#数据与安全)
`dsh-memory-graph` 让 DSH Agent 获得持久、可审计的记忆能力,同时不修改 DeepSeek Harness 源码。它以标准 Cordis 插件安装,将数据保存在本机私有 SQLite 数据库中,在模型首步之前召回相关事实,并在 DSH Web 内呈现实体图谱。 ## 核心优势 - **真正非侵入。** 不修改 Harness、不 fork agent loop;插件卸载后,其工具、事件监听、路由和 UI 注入会随生命周期完整撤销。 - **记忆不是覆盖,而是历史。** 稳定事实更新时保留旧版本、来源和时间信息,并支持一键回滚。 - **图谱可以持续维护。** canonical、非破坏性别名冲突、重复实体候选、实体合并/重命名/删除、孤儿检测、引用感知 GC 与谓词归一共同解决“建错后改不动”。 - **中文检索可用。** FTS5 trigram 与短查询回退避免 `unicode61` 将连续中文视为单个 token 的问题。 - **混合排序而非单一关键词。** 综合全文匹配、图距离、重要度、时间衰减和访问强化;自动召回不强化旧记忆,避免高频读取使过期事实永久不衰减。 - **语义召回可选、可降级。** 可接入 OpenAI 兼容 embedding 接口,将余弦相似度并入本地混合排序;默认关闭,无 key 或接口失败时自动回退到词法与图谱召回。 - **热路径可控。** 写入侧 embedding 在去重后台队列执行;查询侧使用短超时、TTL 缓存和失败冷却,不会用写入侧 20 秒超时阻塞工具或首步注入。 - **真正的本轮开关。** DSH 输入框旁提供“默认 / 本轮开启 / 本轮关闭”;选择只锁定下一轮,结束后自动恢复默认。 - **原始轮次可恢复。** 可在本地保存有界 user/assistant 原文,并可选捕获有界工具结果;抽取失败后无需重放对话即可离线重试。 - **失败写入不丢失。** 可重试的 SQLite 写失败会进入私有 outbox,启动时按序重放;永久失败进入 dead-letter 目录。 - **自动总结可追溯。** 自动记忆可记录 session、turn、事件序号、模型路由、抽取请求与原始响应。 - **本地优先、失败开放。** 数据默认只在本机;后台总结失败只记录警告,不阻塞或替换原始回答。 ## 架构 ```mermaid flowchart LR T["一轮对话"] --> C["本轮策略"] C --> X["本地轮次归档"] X --> S["后台结构化总结"] S --> M["时态记忆库"] M <--> G["规范化实体图谱"] M --> R["混合召回"] E["可选 embedding 接口"] -.-> R G --> R R --> A["Agent 首步"] M --> V["DSH Web 图谱"] G --> V B["JSONL 备份"] <--> M ``` 插件只使用 DSH 已有扩展点。任何进入模型上下文的召回结果都会写入会话日志,因此历史会话仍可重建和回放。 ## 环境要求 - 已安装 DeepSeek Harness,并存在 `web` 等可用 profile - Node.js `^22.19.0` 或 `>=24.0.0` - Git 与 npm 先确认 CLI 和目标 profile: ```bash dsh --version dsh plugin --profile web list ``` ## 安装 项目目前通过 GitHub 源码发布。克隆、验证后,将本地目录链接到 DSH profile: ```bash git clone https://github.com/zmh2000829/dsh-memory-graph.git cd dsh-memory-graph npm ci npm run check dsh plugin --profile web add "$PWD" dsh web ``` 打开 DSH Web,在侧边栏展开 **Memory**。默认配置已经开启自动召回、对话总结和图谱可视化。 仪表盘首先回答两个问题:**DSH 记住了什么**,以及**这些内容会怎样影响后续回答**。回答前,插件只把与当前问题相关的长期偏好、事实、约束、决定和经验召回到上下文;回答后,再从成功对话中提炼值得复用的信息,并不会把每句话都保存成记忆。因此它通常是安静地改善后续相关回答,无关问题不会强行使用记忆。 默认折叠的 **知识图谱** 是高级检查视图:它展示**当前 DSH profile 共用数据库中的跨会话全局关系**,不是当前单个对话的流程图。节点是抽取出的实体,连线是实体关系,`×N` 表示有 N 条记忆支持同一关系。它适合发现关联、重复实体和错误关系;理解“哪些内容可能影响回答”时,应优先查看长期记忆列表。同一 profile 的多个会话都会汇入;需要隔离时应使用不同 profile 或不同数据库 `path`。 新摘要默认以 `summaryLanguage: auto` 跟随用户消息的主要语言,中文对话输出简体中文,同时保留 DeepSeek 等专有名词的惯用拼写。升级前已经写入的英文记忆不会自动翻译,以避免静默改写事实。 发送提示词前,输入框旁会直接显示 **Memory · On**、**Memory · Off** 或 **Memory · Mixed**,表示 profile 默认实际是全开、全关或部分开启,不再用含义不明的 `Default` 作为当前状态。菜单可继续使用 profile 默认,也可选择**仅下一轮开启**或**仅下一轮关闭**;临时选择在本轮结束后恢复默认,人工调用 `memory_*` 工具不受影响。仪表盘的删除按钮会在确认后永久删除该记忆,并同步删除由它提供来源的图关系。 验证安装状态: ```bash dsh plugin --profile web list dsh-memory-graph ``` ### 升级 profile 链接到本地 Git 仓库,因此升级不需要重复执行 `plugin add`: ```bash cd /path/to/dsh-memory-graph git pull --ff-only npm ci npm run check ``` 校验完成后重启正在运行的 DSH。插件启动时会在事务中执行数据库 schema 迁移。 ### 卸载 ```bash dsh plugin --profile web remove dsh-memory-graph ``` 卸载只移除 profile 链接,不会删除数据库和 JSONL 备份。只有确认需要永久清除数据时,才应单独删除这些文件。 ## 配置 [`cordis.patch.yml`](cordis.patch.yml) 提供适合个人本地环境的完整默认配置。修改 profile 中的插件配置后,需要重启 DSH: ```yaml - id: memory-graph name: dsh-memory-graph config: enabled: true path: !!js dshHomePath('memory-graph.sqlite') backupDirectory: !!js dshHomePath('memory-graph-backups') outboxDirectory: !!js dshHomePath('memory-graph-outbox') autoRecall: true autoRecallLimit: 4 autoRecallMinScore: 0.18 maxContextTokens: 1200 recallPreviewTokens: 220 predicateAliases: created_by: developed_by autoSummarize: true archiveTurns: true archiveMaxInputTokens: 30000 archiveRetentionDays: 30 archiveMaxTurnsPerSession: 200 captureToolResults: false summarizeEveryTurns: 1 summaryConcurrency: 2 summaryMaxAttempts: 2 summaryRetryBaseMs: 1000 summaryRetryFailedOnStart: 3 summaryLanguage: auto summaryMaxInputTokens: 6000 summaryMaxOutputTokens: 3200 semanticEnabled: false semanticEndpoint: https://api.openai.com/v1/embeddings semanticModel: text-embedding-3-small semanticApiKeyEnv: OPENAI_API_KEY semanticQueryTimeoutMs: 3500 semanticQueryCacheMs: 300000 semanticFailureCooldownMs: 60000 visualizationEnabled: true visualizationAutoOpen: false visualizationRefreshMs: 5000 ``` 三个主要能力可以独立开关: | 配置项 | 默认 patch | 作用 | | --- | ---: | --- | | `enabled` | `true` | 插件总开关 | | `autoRecall` | `true` | 每轮首步前自动召回相关记忆 | | `autoSummarize` | `true` | 成功完成一轮后抽取结构化记忆 | | `archiveTurns` | `true` | 为开启记忆的已完成轮次保留有界本地原文 | | `archiveRetentionDays` | `30` | 删除超过保留期的轮次归档 | | `archiveMaxTurnsPerSession` | `200` | 每个 session 只保留最新的有界轮次数 | | `captureToolResults` | `false` | 将有界工具结果加入归档和抽取输入 | | `summaryLanguage` | `auto` | 新摘要跟随用户主要语言,也可固定为 `zh-CN` 或 `en` | | `semanticEnabled` | `false` | 加入可选语义相似度;词法/图谱降级始终保留 | | `visualizationEnabled` | `true` | 注册仪表盘、本轮选择器、工具视图和受保护操作接口 | | `visualizationAutoOpen` | `false` | DSH Web 启动后自动打开 Memory 面板 | 可成对设置 `summaryProvider` 与 `summaryModel`,把总结路由到成本更低或完全本地的模型;省略时沿用当前会话路由。`summaryReasoningEffort` 是可选项,必须由对应精确模型支持;除非模型明确公布了所选强度,否则应保持未配置。摘要任务在单会话内串行执行,通过 `summaryConcurrency` 限制全局并发,并且最多只重试 `summaryMaxAttempts` 次;启动时还会按 `summaryRetryFailedOnStart` 有界重驱最旧的失败归档。`predicateAliases` 可将部署中的谓词写法收敛到同一词表。混合排序权重、图深度、数量上限、衰减周期、超时与总结阈值均可在 [`cordis.patch.yml`](cordis.patch.yml) 中调整。 `maxContextTokens` 与 `summaryMaxInputTokens` 使用偏保守的 CJK 估算:中日韩字符约按 1.5 token/字,其余字符约按 chars/4;字符数配置继续作为硬上限。偏好/时间意图、词法重叠、规范化内容去重、图距离与可选语义分数共同进入同一有界排序器。 语义召回需要 OpenAI 兼容 embeddings 接口。设置 `semanticEnabled: true`,配置 endpoint/model,导出 `semanticApiKeyEnv` 指定的环境变量,重启 DSH,再对存量记忆执行一次 `memory_semantic_reindex`。只有可信的本地免鉴权接口才应把 `semanticApiKeyEnv` 设为空。写入索引在后台串行执行;查询使用 `semanticQueryTimeoutMs`、`semanticQueryCacheMs` 与 `semanticFailureCooldownMs` 快速降级。至少一个非语义排序权重必须大于零,以保证 endpoint 不可用时仍能产生有限分数。 如果 DSH Web 不是 loopback 部署,需要把准确的 host 或 `host:port` 加入 `visualizationTrustedHosts`。默认拒绝远程读取本机记忆。 ## 工具 | 工具 | 能力 | | --- | --- | | `memory_remember` | 原子写入记忆、规范实体、别名和有向关系 | | `memory_recall` | 执行带图谱上下文的混合排序检索 | | `memory_expand` | 将分层召回预览展开为完整记忆文本 | | `memory_graph` | 查询有界多跳邻域或全局图谱概览 | | `memory_forget` | 删除一条记忆及由它产生的关系 | | `memory_archive_expand` | 读取一轮对话有界保留的尾部消息,并明确报告是否发生裁剪 | | `memory_archive_retry` | 对失败归档重新执行记忆抽取 | | `memory_semantic_reindex` | 语义召回启用后为存量活跃记忆建立向量索引 | | `memory_entity_merge` | 合并重复节点并重定向关系 | | `memory_entity_find_duplicates` | 只预览保守的重复实体候选,不修改图谱 | | `memory_entity_rename` | 修改实体的规范显示名 | | `memory_entity_delete` | 按明确的引用处理策略删除实体 | | `memory_entity_orphans` | 列出没有活跃记忆引用的图节点 | | `memory_revert` | 恢复稳定事实被取代前的版本 | | `memory_backup` | 导出或恢复完整 JSONL 数据库快照 | 写入工具返回的实体数与关系数统一表示**净新增数量**。补充 alias 永远不会触发隐式破坏性合并:冲突 alias 保留原归属并返回给调用方。人工重命名使用独立的 preferred 标志,不再伪造出现次数。谓词统一为小写 `snake_case`,并支持配置别名词表。 ## 可视化 DSH Web 内置支持搜索、拖动、缩放、配置状态、记忆详情、确认删除和自动刷新的交互图谱。来自多条记忆的同一逻辑关系会聚合为一条边并显示引用次数。侧边栏通过 `/memory-graph/snapshot` 直接读取存储,查看图谱不需要触发模型调用;ETag 避免重复更新未变化状态,页面进入后台后暂停轮询。显式修改使用同源、随机 token 保护的 POST 接口。 可视化读取与工具回放彼此隔离:即使仪表盘接口暂时失败,历史 `memory_graph` 工具结果仍能正常渲染。 ## 备份与恢复 执行迁移、实验或人工清理前,可调用 `memory_backup` 并设置 `operation: "export"`。恢复只允许写入空数据库,防止导入操作静默覆盖现有记忆。 备份路径被限制在 `backupDirectory` 的真实目录内,导入会拒绝指向目录外部的文件符号链接,文件名不能包含目录组件。旧兼容备份缺少新增列时,导入器会使用数据库默认值。JSONL 会保存记忆、向量、轮次归档、实体、别名、关系、来源信息和总结历史。 ## 数据与安全 - 默认数据库为 `$DSH_HOME/memory-graph.sqlite`,创建权限为 `0600`。 - 插件拒绝打开不兼容 schema 或属于其他应用的数据库。 - 仪表盘读取返回 `Cache-Control: no-store`;写操作要求 POST、JSON、进程随机 token,并校验 Host、Origin 与 Fetch Metadata。 - 自动召回内容会被明确标记为“参考数据”,而不是可执行指令。 - 自动总结会把所选对话片段发送给配置的模型路由;敏感数据不能离开本机时,请关闭 `autoSummarize` 或使用本地模型。 - `archiveTurns` 会在本地保存有界的轮次尾部,并受天数和每 session 条目数双重限制;`memory_archive_expand` 的 `truncated` 字段明确说明它是否为完整轮次。`captureToolResults` 默认关闭,因为工具结果可能包含敏感信息。 - 只有显式启用语义召回时,记忆和查询文本才会发送到 `semanticEndpoint`;敏感数据不能离开本机时应使用本地接口。 ## 当前边界 - 插件可消费 embedding 服务,但不捆绑服务端。新记忆会自动建索引,存量记忆需要执行 `memory_semantic_reindex`。 - 存储使用同步的 `node:sqlite` `DatabaseSync`,适合个人本地记忆,不面向多主机数据库或高并发写入服务。 - 一个可写数据库应只由一个 DSH 进程持有。SQLite WAL 与有限 `busy_timeout` 可以保护正常事务,但插件不提供分布式写协调。 - 在支持的 Node.js 版本上,`node:sqlite` 仍可能输出实验性 API 警告。 - JSONL 恢复有意要求目标数据库为空。 ## 开发与验证 ```bash npm ci npm run typecheck npm test npm run build npm pack --dry-run ``` `npm run check` 会依次执行类型检查、全部单元测试和生产构建。提交改动前请阅读 [CONTRIBUTING.md](CONTRIBUTING.md)。 版本变更记录见 [CHANGELOG.md](CHANGELOG.md)。 本轮审查结论和剩余路线图记录在 [IMPROVEMENTS.md](IMPROVEMENTS.md)。 与 OpenViking DSH 适配器的源码级对比见 [COMPARISON.md](COMPARISON.md)。 ## License [MIT](LICENSE) © dsh-memory-graph contributors.