dsh-memory-delta
分层跨会话记忆,只推变化的部分:首轮注入全部有效条目,之后只推新增 / 已更新 / 已失效,没有任何变化时一个字都不注入。事实与决策带语义键(一个 key 只有一个有效结论),模型只能写收件箱、由人确认后才成为常驻事实;条目可以带复核时间,到期后提醒一次;检索按相关度排序,中文按 bigram 匹配;侧边栏有一个只读页签,显示常驻条目、待复核项、收件箱候选与当前注入体积。附带零依赖 CLI,不装 DSH 也能用这套记忆库。
安装
dsh plugin --profile web add github:lpf20200901/dsh-memory-delta
需要可复现安装时,可在仓库后追加 #commit 固定提交。
分层跨会话记忆,只推变化的部分:首轮注入全部有效条目,之后只推新增 / 已更新 / 已失效,没有任何变化时一个字都不注入。事实与决策带语义键(一个 key 只有一个有效结论),模型只能写收件箱、由人确认后才成为常驻事实;条目可以带复核时间,到期后提醒一次;检索按相关度排序,中文按 bigram 匹配;侧边栏有一个只读页签,显示常驻条目、待复核项、收件箱候选与当前注入体积。附带零依赖 CLI,不装 DSH 也能用这套记忆库。
该插件未提供要点说明,请参考仓库 README。
- 安装并启动 DeepSeek Harness:
npx @deepseek-ai/dsh web - 在终端执行上面的安装命令(CLI 会解析插件并核验来源)
- 用 dsh plugins list 确认已安装,必要时重启 Harness 生效
插件以当前 dsh 进程的权限运行,安装时可能执行代码。请先通读仓库源码与许可证,确认无破坏性命令与越权访问;本站只做索引,不对第三方插件安全性作担保。
| 代码仓库 | github.com/lpf20200901/dsh-memory-delta |
| 许可证 | MIT |
| 主要语言 | master |
| 下载量 | 1 |
| GitHub 星标 | 0 |
| 最近推送 | 2026-09-18 |
| 收录日期 | 2026-09-19 |
| 分类 | 会话与消息 |
事实信息来自公开插件目录快照(2026-10-01),介绍文案由本站再加工。
以下为插件仓库 README 全文(原始内容,由公开目录抓取整理)。
# dsh-memory-delta
English | [中文](README.zh.md)
**Layered, auto-injected cross-session memory for AI coding agents.**
A [DSH](https://github.com/deepseek-ai/deepseek-harness) (DeepSeek Harness) plugin, plus a
zero-dependency standalone CLI. It borrows the *spec / change / archive* discipline from
[OpenSpec](https://github.com/Fission-AI/OpenSpec) — but **pushes** instead of pulls.
> **Canonical repository: [GitHub](https://github.com/lpf20200901/dsh-memory-delta)** ·
> [Gitee](https://gitee.com/xingluzhe/dsh-memory-delta) is a read-only mirror —
> please file issues and pull requests on GitHub.
> Status: **M1–M4 done**; M3/M4 (differential injection, both tools, the distillation nudge) were
> verified inside a real DSH session, and the M5 improvements below are covered by 419 assertions
> plus a real-machine preflight. See [Verification](#verification).
## Why
AI coding assistants have two recurring problems:
1. **A new session remembers nothing.** You re-explain the background, your preferences, and every
conclusion you already reached.
2. **What does get remembered is unmanaged.** Everything piles into one or two Markdown files that grow
without bound, cost more every session, and — worst of all — **stale conclusions are never removed.**
Existing spec-driven tools (OpenSpec and friends) solve "the code drifts away from the plan". But they
are **pull-based**: the agent has to be told to go read the specs, so a fresh session does not
spontaneously remember anything. dsh-memory-delta is **push-based**: at session start the agent is handed what
it should know — but only the *distilled* part, and only *what changed*. Details stay retrievable on demand.
## Design
```
┌─ PUSH: injected automatically at session start (hard byte budget)
│ T0 identity & conventions user preferences / machine facts / accounts
│ T1 index & next actions what to pick up
store ──┤
└─ PULL: retrieved on demand (costs nothing by default)
inbox/ candidate entries — **the only layer the model may write**
facts/ current truth (only status=active is injected)
decisions/ choices plus their reasons (append-only)
archive/ superseded entries
journal.md activity log (never injected)
```
Seven rules:
- **The pushed part must be tiny.** Everything in the injected layer is paid for on every session, so the
journal and design docs stay out of it. Measured on a real store (6 entries): **953 bytes** total, 68% of
it the entry lines themselves, ~300 bytes of framing — about **159 bytes per entry**, so the 3 KB default
budget holds ~19 entries. Entry ids are deliberately **not** written into the text (they ride along in the
message's structured `source.entries`); inlining them used to eat 34% of the budget.
- **Only the delta is pushed.** Every entry carries a 12-char content hash; the plugin remembers the
previous round's state and next round pushes only *added / updated / removed*. When nothing changed it
injects **nothing at all**. (The upstream `dsh-agent-instructions` plugin has no diffing: any file
change re-injects the whole file — measured at ~58k wasted tokens for 15 edits of one 8.5 KB file.)
- **State is recovered from the conversation itself.** No side-car state file: the plugin reads back the
`{id: hash}` map from the message it previously injected, so session resume, replay and compaction all
stay correct.
- **The model may only write to the inbox.** A wrong conclusion that silently reaches the standing layer
gets **re-injected forever**. Promotion is an explicit `promote`.
- **One key, one truth.** Facts and decisions carry a semantic `key`, and only one *active* entry may
exist per `scope+key`. A new conclusion must explicitly `--supersedes` the old one — that gate is what
keeps memory rot out of the injected layer.
- **Entries have state**: `active` / `superseded` / `expired`. Superseded entries get bidirectional links
and are archived, never appended forever.
- **Plain Markdown + frontmatter**: human-readable, diffable, reviewable, committable like code.
## Install (as a DSH plugin)
⚠️ This section is the result of real trial and error — both wrong turns below are silent failures:
```text
❌ Adding the package to package.json's dsh.profile.bundles
→ DSH regenerates that list from the market registry (.generations/desired.json) at boot;
entries it does not know about are dropped.
❌ Writing a bare entry in cordis.patch.yml
→ silently ignored (a patch entry only targets an existing id for config/disable).
✅ Wrapping it in `- insert:` inside cordis.patch.yml
```
**Steps** (`DSH_HOME` is usually `%APPDATA%\dsh-desktop\harness`):
1. Copy this package into the profile's `node_modules`:
```
\profiles\web\node_modules\dsh-memory-delta\
package.json
bin\mem.mjs
src\plugin.mjs src\hook.mjs src\planner.mjs
```
2. Append to `\profiles\web\cordis.patch.yml`:
```yaml
- insert:
- id: dsh-memory-delta
name: dsh-memory-delta
config:
root: '' # empty = /memory
maxBytes: 3072 # byte budget for the baseline injection
enabled: true
```
3. Save. The patch layer is watched (`watchUserPatches`) — it **hot-reloads, no restart needed**.
**Uninstall**: remove that `- insert:` block and delete `node_modules\dsh-memory-delta`.
> The market/registry publishing flow was not investigated yet; the above is the local install path.
### What the plugin provides
| Capability | Detail |
| --- | --- |
| **Differential injection** | First round injects every active entry (baseline); afterwards only *added / updated / removed*; **nothing at all** when unchanged |
| `memory_search` | Relevance-ranked search across facts / decisions / inbox / archive / journal / session index. Field weights (key/id > tags > conclusion > body), a whole-phrase bonus, and Chinese matched by **bigram** so a query like `沙箱禁管道` hits `沙箱禁止命名管道` without spaces. Each hit carries a score and a snippet from its best-matching line |
| `memory_write` | Record a candidate into the inbox — **the model cannot touch the standing layer** |
| Distillation nudge | Once a session has run a few steps and memory is already current, it reminds the model to record conclusions with `memory_write`; one nudge per session, and the nudge message carries **no state**, so it cannot corrupt the diff baseline |
| Due-for-review reminder | `verify_when` is no longer a dead field: when an entry reaches its review date, the session is told once — "this conclusion may be stale, re-check it" — with the exact command to supersede or expire it. Prose values (`等换机器时`) never trigger it, so the reminder can always be resolved; it fires only on a step that injects nothing else, and it carries **no state** either |
| Sidebar memory tab | With [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) installed, a **记忆** tab lists the standing entries, the due-for-review items and the inbox candidates, plus the current injection size. The client half is a **hand-written, zero-build browser bundle** (a `window.__ModuleLoader__.load({id, factory})` wrapper, no bundler); its data comes from a read-only `POST /dsh-memory-delta/state` route owned by this plugin — loopback-only, JSON in / JSON out, and it reads nothing but the memory store |
The plugin never spawns the CLI: the DSH sandbox forbids named pipes (capturing a child's output fails
with EPERM), and there is no need — it imports the same store module directly (`bin/mem.mjs` only runs
the CLI when executed as the entry point). It also never *requires* the sidebar: `webServer` is read
through `ctx.get('webServer')` (an optional capability), so a headless or CLI-only composition loads the
plugin unchanged and simply skips the panel route.
## CLI usage
```bash
# init (defaults to /memory; override with --root or $DSH_MEMORY_ROOT)
mem init --root ./memory --scope "workspace:/path/to/project"
# record a candidate (lands in inbox, never injected)
# --id prefer an explicit short id; otherwise derived from the conclusion (capped at 20 chars)
# --key semantic key: only one active truth per scope+key
mem new --type fact --id win-update-cache --key disk-cleanup \
--conclusion "Cleaning the update cache reclaimed nothing measurable" \
--reason "Directory emptied but free space did not move" --tags windows,disk --source session-abc
# promote it once confirmed; when the key already has an active entry you must say who supersedes whom
mem promote win-update-cache
mem promote win-update-cache-v2 --supersedes win-update-cache
mem set --key k --tags a,b --conclusion "…" # edit an entry (add a key, reword, mark expired)
mem list --status active --tag windows
mem show
mem validate [--fix] # format / ids / bidirectional links / cycles / same-key conflicts / index / budget
mem index # rebuild index.md
mem inject [--json] [--budget 3072] # render what should be injected; --json adds per-entry hashes
mem recall [--where all|facts|decisions|inbox|archive|journal|sessions|index] [--limit N] [--json]
# relevance-ranked: Chinese is matched by bigram, no spaces needed
mem due [--within N] [--json] # entries whose verify_when is due (--within N also warns N days ahead)
mem journal add "one line"
```
`verify_when` takes either a date (`2027-03-01`) or a relative phrase measured from the entry's own
date (`3个月后`, `2周后`, `立即`); anything else is treated as prose and simply never auto-fires.
## Verification
Checked item by item inside a real DSH session:
| Capability | Live evidence |
| --- | --- |
| baseline injection | the session received every active entry |
| no change → zero injection | the next step injected nothing, only the one-time nudge |
| delta · added | "新增:", explicitly noting "the other N entries are unchanged" |
| delta · updated | after editing one entry, only "已更新:" was pushed |
| due-for-review reminder | adding an entry whose `verify_when` was 16 days overdue produced a one-time `form='due'` reminder on the next no-change step, and the step after it injected nothing (the reminder did not reset the diff baseline) |
| sidebar **记忆** tab | the tab opened on a live store and showed the real root, "常驻 12 条", "注入 1792 / 3072 字节", the facts/decisions split and the (empty) inbox |
| `memory_search` / `memory_write` | both called successfully in the real runtime |
| writes land only in the inbox | the written candidate did **not** enter the injection payload; it appeared as a delta only after promotion |
## Development
```bash
npm test # 419 assertions, zero dependencies
```
| Suite | Assertions | Covers |
| --- | --- | --- |
| `test/run-tests.mjs` | 109 | CLI end-to-end (incl. a non-ASCII path regression, ranked recall, `mem due`) |
| `test/planner-tests.mjs` | 43 | the diff algorithm (pure logic) |
| `test/search-tests.mjs` | 51 | tokenizing / scoring / snippet selection (pure logic) |
| `test/due-tests.mjs` | 93 | `verify_when` parsing (dates, relative phrases, prose) and due collection (pure logic) |
| `test/hook-tests.mjs` | 63 | plugin wiring (fake agent / decision): diff injection, nudge, due reminder |
| `test/plugin-tests.mjs` | 60 | plugin integration (stubbed DSH modules, real `apply()` + both tools) |
`test/plugin-tests.mjs` replaces the four `@deepseek-ai/*` packages with the stubs in `test/stubs/`
(via `test/stub-loader.mjs`) and **actually `apply()`s the plugin**, so its behaviour is verifiable
without a DSH installation. `test/preflight-import.mjs` goes one step further: run it from inside a
profile and it exercises the **real** `@deepseek-ai/*` modules (does the real `defineTool` accept our
tool definitions, does the real `schemastery` accept our config schema).
Regression tests baked in from real bugs:
- With a **non-ASCII** path, Node's `fs.rmSync` fails **silently** (and can crash the process with
`recursive`) — `unlinkSync` must be used instead;
- The DSH sandbox forbids named pipes, so `spawnSync` with the default `stdio: 'pipe'` hits EPERM —
tests must redirect child output to a **file**;
- An entry written by the tool must carry the **session workspace** scope, not the harness process cwd;
- `new URL(import.meta.url).pathname` **percent-encodes a non-ASCII user name**
(`C:\Users\李鹏飞` → `C:\Users\%E6%9D%8E%E9%B9%8F%E9%A3%9E`), which turns "write into my plugin folder"
into "write into a path that does not exist" — always use `fileURLToPath`;
- A field added to *some* early-return paths of an internal planner function (`due`) was destructured
into `undefined` and threw on every step, which the outer `try/catch` silently reported as
"failed to load memory" — hence the defensive read and the zero-warning assertion.
## Roadmap
- **M1 ✅** CLI + structured entries + validate + index/injection budget
- **M2 ✅** explicit short ids, semantic keys and "one key one truth", `inject --json` diff payload,
`validate --fix`, `mem set`
- **M3 ✅** DSH plugin: differential injection + both tools + the distillation nudge (verified live)
- **M4 ✅** published (GitHub primary / Gitee mirror)
- **M5 ✅** the memory got *usable at scale*: index-style injection (id-free text, ~159 bytes per
entry), relevance-ranked search with Chinese bigrams, and `verify_when` turned into a real
due-for-review reminder
- **Next** official distribution (plugin market) and a memory tab in the DSH sidebar
## License
MIT
数据来源:公开的 DeepSeek Harness 插件目录与各插件 GitHub 仓库。本站为独立第三方目录,与 DeepSeek、幻方(High-Flyer)及插件作者均无隶属或背书关系。