Zum Hauptinhalt springen
K

dsh-memory

kagurazaka135/dsh-memory

Layered cross-session memory kept as plain YAML: facts carry a version history instead of being overwritten, a briefing is injected at session start, and a nightly pass consolidates and shards by topic past a threshold. Zero runtime dependencies, so it also runs inside the dsh Android app (verified there).

Installation

dsh plugin --profile web add github:kagurazaka135/dsh-memory

README

dsh-memory

给 DeepSeek Harness(dsh)加一层跨会话的长期记忆。

dsh 的会话是孤立的:新开一场,之前的项目约定、你的偏好、上周定下的方案都不在里面。 这个插件把该长期保留的东西抽出来存成明文文件,下次会话开始时递回给模型。

不依赖数据库、不调外部服务、不需要 API key。记忆就是你磁盘上的几个 Markdown 和 YAML。


状态

环境状态
dsh 0.2.x(PC)已实测,功能完整
dsh 安卓版(App 内插件)已实测,功能完整
dsh 0.1.x工具可用;无 schedule 服务时不会自动定时整理

验证覆盖:工具注册、写读落盘、版本链、分片、检索、简报注入、跨进程/重启持久、真实环境零污染。


它做什么

存事实,不存聊天记录。 「发布分支叫 stable」「周报周五下午发」这类长期有效的信息写成一条条事实。 值变了就追加新版本,旧版本留在 history 里 —— 所以能回答"这个什么时候改的、改成什么了"。

新会话开始时把简报交给模型。 一段固定的能力说明常驻系统提示(字节稳定,不破坏 prefix 缓存), 当前简报和记忆目录走动态快照,内容变了才更新。

按需整理。 手动调 memory_organize,或在 profile 有 schedule 服务时到点自动跑: 扫最近几场会话,把反复出现、值得长期保留的内容提炼进事实和简报。只增不删。

事实多了按主题拆文件。 默认超过 80 条开始分片,配一份索引;检索先按索引挑文件,判不准就全读 —— 宁可慢,不会漏。 只在首次分片时询问模型分组方案,之后新事实走纯代码增量归属,布局稳定。


记忆会过期

存下来的是写入那一刻的断言,不是现值。文件改了、配置换了,记忆不会自动跟着变。

所以插件在三个地方提醒模型:

  • 系统提示里常驻一条规则:涉及文件名、路径、配置值时,行动前先核验当前状态
  • memory_recall 命中内容含具体路径/配置时,结果末尾附提醒
  • 查不到时不给空列表,而是给出下一步:换更少更独特的词 → 按标签查 → 看简报

0 命中不等于从未记录。 换个措辞就是另一条命中。


数据长什么样

全部在 $DSH_HOME/memory/(默认 ~/.dsh/memory/,可用 memory_dir 改):

memory/
  brief.md              当前摘要(Markdown,可直接读改)
  facts.yaml            事实(分片之前在这里)
  candidates.yaml       待确认的线索
  facts/                分片之后的事实(超过阈值才出现)
    _index.yaml         分片索引(缓存,可删,不影响任何事实)
    <topic>.yaml        按主题拆出来的分片
    unsorted.yaml       归不进去的兜底
  .archive/             旧的 facts.yaml 归档(移动过来,不删)
  log/YYYY-MM-DD.json   整理日志(追加式)
  .sessions.json        已处理会话台账

一条事实:

- id: "mem-1792927799706-001"
  fact: "发布分支"
  since: "2026-03-02"
  history:                      # 版本链,最后一条 = 当前值
    - date: "2026-03-02"
      value: "release"
    - date: "2026-04-18"
      value: "stable"
      note: "用户要求改名"

安装

dsh plugin --profile <你的profile> add <包路径 / 包名>

装上后六个工具立即可用。夜间自动整理需要 profile 里存在 schedule 服务 (如 dsh-experimental-schedule-bundle);没有的话手动整理一样能用。

不需要配 API key 或代理。 整理走宿主自己的模型服务(ctx.llm), 你在 dsh 里配的模型就是它用的模型。默认跟随宿主默认模型,想指定就在配置里写 organizer_provider + organizer_model。

配置

在 profile 的 cordis.patch.yml 里覆盖:

配置项默认说明
memory_dir$DSH_HOME/memory记忆存放目录
organizer_provider跟随宿主整理用的 provider
organizer_model跟随宿主整理用的模型名
daily_time04:00:00夜间整理时间(本地时区)
time_zone宿主时区IANA 时区名
scan_sessions12每次整理最多扫多少场会话(上限 40)
shard_threshold80事实超过多少条开始分片;设 0 关闭
enable_shardtrue完全关闭分片
enable_scheduletrue关掉自动注册夜间任务
organizer_sessionmemory-organizer整理专用会话 id

工具

工具用途
memory_brief读当前简报
memory_recall按关键词或标签检索事实
memory_note记一条事实(同主题自动出新版本)
memory_candidate记一条待确认的线索
memory_promote把线索升格成正式事实
memory_organize整理一次;dry_run 只报告不落盘,reshard 重排已有分片

一次整理做什么

顺序固定:

  1. 归并同义的待确认线索(多余的标 merged 保留,不删)
  2. 扫最近几场会话,按 id 剔掉整理会话自身和子 agent 会话
  3. 把会话摘录 + 现有事实 + 现有候选 + 当前简报交给模型,要一份 JSON
  4. 落地:事实追加版本、候选去重后新增、简报换新(旧的归档)
  5. 事实超阈值时分片

它不会做的事(有负面清单约束):

  • 不把整理过程本身写进记忆("扫了几场""跑了几次"这类)
  • 不记绝对路径、主机名、端口,除非用户明确要求长期记住某个地址
  • 不把一次性状态写成事实
  • 模型输出解析不了就什么都不写,宁可不记,不编造

所有更新都是只增不删:旧简报进 log/ 归档,旧事实版本留在 history, 被合并的候选标 merged 仍可见。代码里没有删除历史条目的路径。


已知边界

  • 检索是子串匹配,不是语义搜索。 换措辞可能查不到,所以 0 命中时会给升级路径。
  • 候选去重只处理格式噪音。 会剥掉 (出现 N 次) 这类尾巴,真正换说法的仍可能分成两条 —— 这是有意的,误合并比多存一条更糟。
  • 整理由模型判断"什么值得记"。 可能漏记也可能多记。文件是明文的,看不过去直接改。
  • 分片后新增的事先进 unsorted,下次整理才按标签归位。这是为了布局稳定,不丢数据。
  • reshard 只对已分片的库有效;空库不会凭空造片,首次分片由 shard_threshold 触发。

已知限制

L4 摘要只存结论,细节回不去

简报是压缩过的——它记得"做了什么",但不记得"为什么那样做"。

干完一段活,那 100 多步的判断过程会被压成几行。下次遇到同类任务,它能想起"上次做过",但想不起"上次那个坑是怎么绕的"。

这个需求是它自己在真机上提的(原文和截图在 docs/field-notes/):

「干完活,这段经历就压缩成几行摘要,细节全丢了。下一代要是能记住'上次那个 docx 为什么那样排''那次点击为什么没反应',会比多两个工具管用得多。」

想好的方向是摘要里留指针,细节沉淀到原文,再给模型一个工具去翻。但还没做——原因和判据记在 TODO.md 第 8 条。

顺手能做的活,欢迎认领。

断档不可感知

服务重启或长时间没跑之后,它拿到简报看不出中间空了一段。它知道现在几点,但不知道自己上次醒着是几点。

对应 TODO 里同一节的后半条。


开发

npm run build     # tsc
node e2e/run.mjs  # 端到端,默认零网络

e2e 默认用桩数据跑通全链路。要对接真实模型:

E2E_LLM_URL=https://.../v1/chat/completions E2E_LLM_KEY=... node e2e/run.mjs

许可

MIT

Ähnliche Plugins