# dsh-memoir

> 为 DSH Agent 提供本地项目记忆层：把工作结论、经验教训、后续行动沉淀跨会话，并通过有界 Hot Memory 自动注入 + BM25 排序召回 + Web 面板管理。

## Metadata

- Author: [@Qinling-Melon-Farmers](https://github.com/Qinling-Melon-Farmers)
- Repo: <https://github.com/Qinling-Melon-Farmers/dsh-memoir.git>
- GitHub: [Qinling-Melon-Farmers/dsh-memoir](https://github.com/Qinling-Melon-Farmers/dsh-memoir)
- Stars: 17
- Language: TypeScript
- License: [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)
- Topics: `agent`, `deepseek-harness`, `dsh-plugin`, `memory`
- Forks: 2
- Open Issues: 1
- Last push: 2026-08-20T11:20:50.000Z
- Added: 2026-08-16T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:Qinling-Melon-Farmers/dsh-memoir
```

## Wiki

## 一句话定位
dsh-memoir 是 DSH 的本地项目记忆层：把 Agent 的工作结论、经验教训、后续行动沉淀到本机文件，让下一个会话自动继承这份「项目经验包」，解决「每次新会话都要重复交代项目背景」这个痛点。

## 核心能力
- 写入三类记忆：让 Agent 通过 `memoir_record` 沉淀工作记录、经验教训、行动指南或备注
- 编辑已有记忆：通过 `memoir_update` 改标题/正文/分类/标签，或标记为 superseded / archived，旧条目不会被删除
- 检索历史：通过 `memoir_read` 按当前项目、跨项目或全局范围，配合 BM25 排序召回，支持中英文短语、代码标识符与路径关键词
- 自动注入上下文：每个会话开始时把 token 预算内的 Hot Memory 自动塞进 system prompt（默认 900/1200 token）
- 自动收尾提示：每个有实际工具调用的回合结束时，自动让 Agent 顺手沉淀本轮结论，可一键关闭
- Web 面板管理：在 DSH Web 侧边栏新增「记忆」面板，支持项目/全局浏览、排序搜索、增删改、置顶、归档、Hot Memory 预览与检索诊断

## 技术实现
- **语言**: TypeScript（host 端 src/host/*.ts，client 端 src/client/*.ts(x)）
- **关键依赖**: `@deepseek-ai/dsh-tools`（`defineTool`）、`@deepseek-ai/dsh-system-prompt`（注入段）、`@deepseek-ai/dsh-host-webserver`（路由）、`@deepseek-ai/dsh-llm`（UserMessage）、`@deepseek-ai/dsh-client-runtime`（client 端 bundle 注入）
- **架构模式**: Cordis 双面插件，含 9 个可插拔配置项；host 端注册工具、system-prompt 段、web 路由、`agent/turn-stopping` 事件监听；client 端通过 esbuild 打包为单文件闭包注入 DSH Web 渲染侧栏入口和面板。检索用本地倒排索引 + BM25（中文 2/3-gram + 英文单词 + 代码标识符），零 embedding、零外部服务
- **入口文件**: `src/host/index.ts`（host 端 `apply(ctx, config)`）+ `src/client/index.tsx`（client 端 `apply(ctx)`），由 `package.json` 的 `exports["."]` 与 `exports["./client"]` 分别暴露

## 适用场景
经常在 DSH 里做长期项目、跨多次会话的人：第一次踩过的坑、形成的项目红线、复用的部署步骤，记一次就再也不用在新会话里反复交代；中大型项目里接手别人（或几个月前的自己）写的代码时，项目记忆面板可以直接给接手者一个工作背景摘要。

## 前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DSH | 0.1.0-rc.8+ | `package.json` 的 peerDependencies 声明 `@deepseek-ai/dsh-llm ^0.1.0-rc.8` 与 `@deepseek-ai/dsh-tools ^0.1.0-rc.8`；cordis.patch.yml 通过 `dsh.bundle.patch` 注入 web profile |
| Node.js | ^22.19.0 或 >=24.0.0 | `package.json` 的 `engines.node` 声明 |
| 平台 | macOS / Windows / Linux | 源码统一用 `node:fs`、`node:http`、`node:crypto`、`node:os` 标准模块；Windows 路径在 store 层做了全小写归一化 |
| 原生模块 | 无 | 全部依赖 Node.js 内置模块，无 node-gyp / 第三方原生模块 |

## 安装方式
```bash
dsh plugin --profile web add github:Qinling-Melon-Farmers/dsh-memoir
```

## 配置项
放到 `cordis.patch.yml` 中对应 `id: memoir` 的行下的 `config` 块（全部可选，缺省走源码默认值）。

| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| `enabled` | 布尔 | 总开关；关闭后不注册工具、路由、注入段 | `true` |
| `announceToAgent` | 布尔 | 是否在 system prompt 公告段宣告本插件存在 | `true` |
| `autoDistill` | 布尔 | 每轮有实际工具调用的回合结束时，是否自动追一句归纳提示 | `true` |
| `hotMemoryTokens` | 数字 | Hot Memory 注入的软目标 token 数（超出则停止追加） | `900` |
| `hotMemoryMaxTokens` | 数字 | Hot Memory 注入的硬上限 token 数（永远不越过） | `1200` |
| `readDefaultLimit` | 数字 | `memoir_read` 默认返回条数 | `8` |
| `readMaxLimit` | 数字 | `memoir_read` 单次最大返回条数 | `30` |
| `sessionSnapshotMax` | 数字 | 内存里冻结的会话快照 LRU 上限 | `128` |
| `queryCacheSize` | 数字 | 排序召回查询的 LRU 缓存大小 | `128` |

## 常见问题

**Q: 不做任何配置能直接用吗？**

A: 可以。cordis.patch.yml 把所有配置项都标为可选默认值（enabled/announceToAgent/autoDistill 默认 true，hotMemoryTokens 默认 900，hotMemoryMaxTokens 默认 1200），不写 config 块也能正常使用。

**Q: 数据存在哪里？会不会上云？**

A: 全部留在本机。结构化 JSON 存在 `~/.dsh/dsh-memoir.json`（唯一事实源），每个项目根目录还会自动生成 `PROJECT_MEMORY.md` 人类可读投影（可随 git 提交）。源码中没有任何 embedding API、向量库或云端记忆服务的调用。

**Q: 会把全部历史都塞进 system prompt 吗？**

A: 不会。v0.4 起只把 token 预算内的 Hot Memory（默认软目标 900 token、硬上限 1200 token）注入 system prompt；长尾历史需要时由 Agent 主动调用 `memoir_read` 触发 BM25 排序召回。

**Q: 同会话里我刚记录的内容，下一轮就自动生效吗？**

A: 不会立刻生效。注入文本会在同一会话首轮构建后冻结（保证 prompt 前缀稳定以命中前缀缓存），本会话不再重读；下一个新会话会重建并看到最新记录。

**Q: 同时开两个 DSH 进程会冲突丢数据吗？**

A: 不会。store 的读写走 `~/.dsh/dsh-memoir.lock` 跨进程互斥（O_EXCL 独占创建 + 25ms 重试 + 5s 超时），临界区内强制重读磁盘再改写。锁带 pid/createdAt/nonce 元信息，仅在 60s 以上且 pid 已死亡时才会回收。

**Q: Windows 路径大小写不同会被当成不同项目吗？**

A: 会被识别为同一个项目。canonical key 把整条 Windows 路径全小写（`C:\A`、`c:\a\`、`C:/A` 归到同一个 `c:/a` 桶），但显示用的 path 仍然保留原始大小写。

**Q: 如何标记一条过时记忆？会删除历史吗？**

A: 用 `memoir_update` 把 `status` 设为 `superseded` 或 `archived`，或在 Web 面板里点相应按钮。源码明确不删除历史，被替代的条目留在 store 里，Web 面板可切换状态查看。

**Q: 怎么卸载？**

A: 从 web profile 的 bundle 配置里移除这个插入行（cordis.patch.yml 里的 `memoir` / `dsh-memoir` 行），重启 dsh web 生效；插件本身不写 DSH 源码，移除后宿主立即回到无该插件状态。

## 上手难度
入门 — 安装一行命令、不需要写任何配置；普通用户只需要等 Agent 自动提示或在需要时口头要求「记一下这个」即可使用。

## 已知问题与限制
- 项目活跃度判断依赖 cwd 路径：会话工作目录不在 Agent session header 里时，注入段会退化为只输出引导文案不注入具体记忆；同时也会跳过快照冻结、每轮重新构建。
- 跨进程锁有 5 秒超时：写入冲突严重时 `memoir_record` 会抛出 `store lock timeout after 5000ms` 错误；锁文件被异常结束进程遗留时，只有超过 60 秒且持有 pid 已阵亡才会被下个进程回收。
- subagent / 嵌套委托会话不会被自动收尾提示打扰：自动蒸馏监听只在顶级会话（`origin !== 'subagent'` 且 `delegationDepth === 0`）上触发，避免子任务被循环催归纳。
- 没有真正多用户隔离：store 以单租户形式存在，`~/.dsh/dsh-memoir.json` 不做权限分离，所有项目写在同一个文件里。
- 默认 `queryCacheSize=128` 与 `sessionSnapshotMax=128` 是内存 LRU，长时间高并发运行（开几百个会话）会触发淘汰，但实测 10 万条索引的检索仍在毫秒级。

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [dsh-memoir](https://deepseek-plugin.org/plugins/Qinling-Melon-Farmers/dsh-memoir)
Wiki generated by AI (model: `MiniMax-M3`)
