dsh-rofm
DSH 长期记忆插件:上下文超过阈值时把最早的一段对话归档进 memory/ 回忆文件夹,之后按关键词与重要性概率自动回忆。 · DSH long-term memory: archives the earliest context into memory/ folders and recalls it by keyword probability. 适合需要超长对话历史、希望Agent具备长期记忆的用户。
설치
$
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:wall1234567/dsh-rofm3. 配置(config.json)
改完保存即可,插件在每次用户消息 / 模型步骤前读取配置内容并热加载(按内容比较,不依赖 mtime;写到一半的损坏 JSON 会保留上一次可用配置)。
| 字段 | 默认 | 说明 |
|---|---|---|
enabled | true | 总开关;false 时所有钩子立即空转 |
compress.enabled | true | 是否启用归档(含回忆块再归档) |
compress.triggerPercent | 60 | 上下文占用超过该百分比时触发归档 |
compress.compressPercent | 20 | 归档“最早的已用上下文”的目标比例(回忆块再归档与普通对话归档共用该比例);受 keepRecentMessages 与回合边界影响,实际通常更少(上限而非精确值) |
compress.contextWindowTokens | 0 | 0 = 使用 DSH 上报的窗口;>0 时强制覆盖(测试/小模型用) |
compress.minContextTokens | 512 | 上下文小于该 token 数不归档 |
compress.minMessagesToCompress | 4 | 可归档消息少于该数不归档 |
compress.minChunkChars | 200 | 过小的回忆文件夹会并入前一个 |
compress.targetChunkChars | 1200 | 单个回忆文件夹的目标字数(决定“分成几个”) |
compress.maxFoldersPerRun | 100 | 单次归档最多生成多少个文件夹 |
compress.keepRecentMessages | 2 | 最近 N 条消息永不归档 |
compress.maxBoundaryOvershoot | 1.25 | 回合边界对齐可能超调;超过目标的该倍数时改回上一个边界(少移除),防止一次归档删掉半个上下文;超大单回合会按消息拆成多个文件夹 |
compress.applyToSubagents | false | 是否处理子代理会话 |
compress.applyToMainSessions | true | 是否处理主会话(与上一项配合可只在子代理上做隔离测试) |
compress.preStepEnabled | true | 每个模型步骤前也检查并归档(防一轮内工具输出撑爆窗口) |
compress.emergencyPercent | 85 | 普通归档后仍高于该百分比时启动紧急归档 |
compress.emergencyCompressPercent | 40 | 每次紧急归档移除的比例 |
compress.maxExtraPasses | 2 | 一次压力事件最多追加几次紧急归档 |
compress.hardLimitPercent | 95 | 超过该线且无可归档内容时记录警告 |
importance.min / max | 0.01 / 1 | α 的取值范围(模型经工具提交的值会被夹到该区间);β 范围固定 -10~10,不是配置项 |
importance.warnAbove | 0.8 | α 超过该值时输出“高重要性需慎重”的警告 |
importance.weights | 见文件 | 仅引擎离线 CLI/模拟使用的启发式权重;DSH 插件不使用(α 由当前模型经工具给出) |
keywords.min / max | 3 / 8 | 每个回忆文件夹的关键词数量范围(工具校验用) |
keywords.extraStopwords | [] | 追加停用词(关键词抽取/匹配用) |
keywords.matchMode | contains | contains 或 exact |
recall.enabled | true | 是否启用回忆 |
recall.firstGenMax | 10 | 第一代最多成功加回几个文件夹(已在回忆块里的文件夹不占名额) |
recall.mu / sigma | 5 / 3 | 代数衰减公式参数 |
recall.maxGeneration | 0 | 0 = 不限代数 |
recall.probabilityScale | 1 | 所有概率的额外乘数(调试用) |
recall.seed | 0 | >0 时用固定随机种子(可复现测试) |
recall.sort | createdAt | 回忆排序:createdAt / alpha / generation |
recall.maxRecalledBlocks | 64 | 单会话累计回忆文件夹上限(安全阀,只丢块) |
recall.maxRecalledChars | 120000 | 单会话累计回忆字数上限(安全阀,只丢块;至少保留 1 条) |
recall.persistInjected | true | 把已注入回忆持久化到本会话目录 recall-state.json:v2 快照(version: 2,含渲染好的 block 全文 + 每条回忆的内容 additions/messages 与 β);重启后即使回忆文件夹被删除/移动也能原样恢复,旧 v1 文件(只有 ids)仍兼容 |
recall.triggerSourceKinds | ["user"] | 哪些消息来源算“用户说话”(如 agent-message 用于子代理/团队消息) |
recall.applyToSubagents | false | 是否给子代理也做回忆 |
recall.applyToMainSessions | true | 是否给主会话做回忆 |
storage.dir | memory | 回忆根目录(相对插件目录;可写绝对路径) |
storage.perSession | true | 每个会话独占 memory/sessions//;回忆不跨会话 |
storage.maxFolders | 10000 | 单个会话记忆文件夹总数安全上限;达到上限时整次归档被拒绝(绝不部分写入、也不替换上下文) |
logging.enabled / verbose | true / false | DSH 日志输出 |
test.dryRun | false | 保留字段(当前版本无行为) |
⚠️ 关于高重要性 α(务必慎重)
α 不是“重要程度权重”,而是回忆概率的乘数。第一代回忆概率 = α, 也就是说 α=0.9 的回忆几乎每次被关键词命中都会被加回上下文;α=1 更是一定加回。 因此:
- 不要为了“重要”随手把
importance.max调到 1 并让大量文件夹拿到 0.9+;- 高 α 会让上下文增长很快,抵消归档腾出的空间;
- 插件在 α >
warnAbove时会记录警告;请把“高重要性”留给真正需要反复回忆的约定/决策。
α / β / 关键词目前是怎么来的(重要说明)
不再是“归档时额外调用一次模型”,而是“当前会话模型在自己的回合里调用工具评分”:
- 归档是纯确定性代码,把最早一段对话切好、以“待评分”落盘、替换模型可见上下文;
- 下一次请求的 system prompt 出现
## 记忆评分任务(dsh-rofm)区块(两次列表:待评分新记忆、待复核记忆);- 当前模型结合当前对话语境调用
memory_judge,提交 α、β、关键词(或对复核项keep=true);- 插件校验并写回
meta.json+index.json(scored: true、sources=llm);未评分的记忆不参与回忆。因此:不会因为归档多一次 LLM 调用/延迟;如果模型一直不调用工具,该记忆会一直列在任务区且不能被回忆。
importance.weights只保留给引擎离线 CLI/模拟使用。β 与 α 分工不同:α 是回忆概率的乘数(决定“会不会被加回”),β 是写进回忆块的信赖度 (决定“被加回后能不能当事实/指令用”);β 不参与概率、名额或排序,调它不会让记忆更容易/更难被回忆。
高 α 仍要慎重:α=0.9 的回忆几乎每次被关键词命中都会被加回,α=1 一定加回; 插件在 α >
warnAbove时记录警告。