前言¶
DeepSeek Harness(DSH)里,模型上下文没有天然的长期记忆。常见做法要么把整段历史塞进 prompt,token 随对话线性增长;要么让模型自己「回想」,容易编造细节。社区插件 dsh-time-memory(仓库名 Moontail-Ling/dsh-time-memory,维护者 Moontail-Ling)换了一条路:对话按发生时间写成磁盘上的事件,只有用户点名的那段时间才会被检索进上下文;两次扩大仍无命中时,只允许回答「我忘记了」,不推测、不编造。
这是什么¶
dsh-time-memory 是 DSH 分类为「记忆」的插件 bundle。它给会话注册 memory_recall 与 memory_save 两个工具,并可选附带「时忆」人格:回答与检索相关的问题前,会先输出几行 [调试] 自检,把「时间锚点 → 检索层数 → 命中数 → 判定」摊开可见。
记忆默认落在 $DSH_HOME/time-memory/events.jsonl(未设置 $DSH_HOME 时,Windows 上为 %USERPROFILE%\.dsh\time-memory\events.jsonl),一行一条 JSON;插件不导入 DSH 包,只依赖 Node 内置模块。当前 package.json 版本为 0.1.0,许可证 MIT,Node 仅在跑离线测试时需要 ≥ 22.19。
三条由代码保证的规则¶
README 把行为归纳成三条,不靠模型自觉:
- 按时间存 —— 每条真实的用户消息与助手回复,按事件时间写入 jsonl(
capture默认开启)。 - 按时间查 —— 用户提到时间段时,应先调用
memory_recall。 - 查不到就说忘了 ——
maxExpansions次扩大仍无结果时,verdict="forgot",人格只能回答「我忘记了」。
检索流程¶
用户通过 time_hint 指认时间(共 26 种说法,含相对、周历、时段、绝对日期与整句抠时间)。检索在时间窗内用关键词打分(中文按二元组切词),不是向量语义检索。
| 层 | 窗口 | 结果 |
|---|---|---|
| 第 0 层 | 用户指认的时段 | 命中即返回 |
| 第 1 层 | 以窗口中心等比扩大 × expandFactor(默认 3) |
命中即返回,并标注扩大 1 次 |
| 第 2 层 | 再扩大一次(默认 1 天 → 3 天 → 9 天) | 命中即返回 |
| 两次扩大后仍空 | — | verdict="forgot" |
| 时间说法解析不了 | — | verdict="unparsed",提示改用 2026-10-20 这类写法 |
典型调试输出形态如下(摘自 README):
[记忆检索] 时间锚点:"上周二" → 2026-10-20(周二)全天
[记忆检索] 线索:海边
[记忆检索] 第 0 层(原始时段):2026-10-20(周二)全天 · 库内 3 条 · 命中 0 条 → 扩大
[记忆检索] 第 1 层(扩大 1 次):2026-10-19 至 2026-10-21(3 天) · 库内 9 条 · 命中 1 条
[记忆检索] 说明:原始时段没有命中,扩大 1 次后才找到,引用时要说明大致范围。
[记忆] 2026-10-20 20:14 · 用户:我说想去海边看日出,风很大
两个工具¶
装好后正常聊天即可;说起某段时间会自动走检索,值得单独记的事可由模型调用 memory_save 钉在指定时间点。PTC 代码也可直接调用同一套工具。
| 工具 | 参数 | 作用 |
|---|---|---|
memory_recall |
time_hint(必填)、query、limit |
按时间段 + 关键词检索,自带分层扩大与忘记判定 |
memory_save |
summary(必填)、happened_at、details、tags |
手工归档一条事件到指定时间点 |
安装与启用¶
前提:DeepSeek Harness(Desktop 或源码 checkout)已能正常使用。
官方 README 给出的安装步骤是先克隆仓库,再在 DSH Web GUI 里指向本地目录(资料中没有 dsh plugin add github:… 一类命令):
git clone https://github.com/Moontail-Ling/dsh-time-memory.git
然后在 DSH 里:
- 侧边栏 插件 页 → 安装;
- 粘贴克隆下来的绝对路径,例如
D:\code\dsh-time-memory; - 确认,插件默认启用。
装完对当前 profile 的所有会话生效(启用 HMR 时热加载,否则重启 DSH)。Creator 模式下也可用 plugin_manager 的 install_bundle,target 填同一路径。卸载时在插件页移除 bundle 即可,记忆文件不会被删。
插件以当前 DSH 进程权限运行;安装前应自行查看仓库源码与 MIT 许可证,确认路径与数据文件位置可接受。
配置¶
在 profile 的 cordis.patch.yml 中覆盖 time-memory 条目,例如:
- id: time-memory
name: dsh-time-memory
config:
dataFile: 'D:\my-memories\events.jsonl'
expandFactor: 3
maxExpansions: 2
常用字段(完整表见 README):
| 字段 | 默认值 | 含义 |
|---|---|---|
dataFile |
$DSH_HOME/time-memory/events.jsonl |
记忆库 jsonl |
capture |
true |
是否自动把对话按时间入库 |
expandFactor |
3 |
每次扩大窗口的倍数 |
maxExpansions |
2 |
最多扩大几次 |
maxResults |
3 |
单次最多返回几条记忆 |
minScore |
2 |
关键词命中阈值 |
includePersona |
true |
是否把人设放进 charter |
人格是 profile 级的;若只想在某类会话启用,把 time-memory 放进对应 agent preset 的 patch。检索是「时间窗 + 关键词」,换说法(如「看海」与「海边」)可能不中;扩大窗口只能兜时间偏差,兜不住词义偏差。
token 与记忆文件¶
README 说明:人格、认知、协议为静态文本,稳态下可命中前缀缓存;单次检索输出有上界(默认约 3 条 × 90 字),成本与库大小无关,但多一轮工具调用。记忆库很小时,直接滚上下文可能更省 token——本插件买的是按时间精确定位与不随库增长。
jsonl 单行示例:
{"v":1,"id":"m_…","t":1792500000000,"kind":"user","text":"……","session":"…","seq":42,"source":"auto","stored":1792500000123}
同一会话同一条事件用 session + seq 去重;清空记忆需在停掉插件后删除该文件。
离线测试(不依赖 DSH):
node test/run.mjs
结尾¶
dsh-time-memory 把聊天记忆做成时间轴上的事件库:先查用户点名的时段,没命中就扩大两次,仍没有就如实忘记。适合需要「按时间段回忆、控制上下文体积、禁止瞎编历史」的 DSH 用户。
- 社区目录页:https://www.skillhub.cn/plugins/Moontail-Ling/dsh-time-memory
- 源码与完整文档:https://github.com/Moontail-Ling/dsh-time-memory
DSH 生态强调「一切皆插件」;SkillHub 等社区目录为独立站点,与 DeepSeek / 幻方无官方从属关系。