Five memory kinds
global and persona every turn.
DSH plugin · local-first memory
Long-term memory for DeepSeek Harness.
Two minutes, five chapters: what it is, how it works, and how the data survives.
Streaming only — the video is served for playback in this page and cannot be downloaded.
Every feature exists to answer one question.
global and persona every turn.
Only matching entries enter the prompt.
Each topic gets its own representative entries.
Ask for everything about a topic.
Inferences wait for your approval.
See the byte and token split.
Persona entries must be dated and anchored.
One fact, one entry.
One exe with Node embedded.
Every turn re-reads the store from disk.
Local store read every assembly (SQLite, JSON fallback).
CJK grams plus latin tokens.
Tag hits weigh the most.
Always-on first, then per cluster.
Labelled sections plus index.
Nothing is injected silently.
Windows 10/11.
Double-click the exe.
Tray ▸ Attach to DSH.
Bundles load at boot only.
:: attach the plugin to every detected DSH profile dsh-memory-manager.exe --install :: memory page only dsh-memory-manager.exe --start :: --stop / --restart / --status :: keep the store outside the plugin folder dsh-memory-manager.exe --store D:\dsh-memory-data :: port · language · diagnostics dsh-memory-manager.exe --port 7900 :: refuses a port already taken dsh-memory-manager.exe --lang zh-CN dsh-memory-manager.exe --dir / --data / --version
Program, source package and documentation are served from this site.
The single-file manager.
Full source release.
Install, configure, daily use and troubleshooting.
Feature overview and configuration table.
No.
One local database: SQLite (JSON fallback).
No, and that is deliberate.
Yes.
DSH Desktop, portable and CLI.
What changed, release by release. Entries are kept for reference only: historical versions are not offered for download here.
revision are read in one transaction, so a write can no longer accept a stale snapshot and delete a fresh entry. Reproduced with four writers (revision 101, table 100) and verified over three rounds.storageInfo() no longer opens a missing database file, so the lost-store degraded state survives a page refresh instead of being replaced by an empty store.MEMORY_CONFIRM_MISMATCH when the numbering moved; ephemeral entries finally carry their session; oversized request bodies answer 413.confirm: true is rejected with 409.storageInfo no longer reports zeros. It read revision, last-backup time and vector count from an unopened database handle, so the memory page always claimed there was no backup.PRAGMA wal_checkpoint(TRUNCATE), so memory.sqlite itself holds all memory: backing up that one file is enough.memory.sqlite.migrated: once a store has been migrated it is never resurrected from the old JSON, even if the database file goes missing.VACUUM INTO) into backups/, one self-contained file, last 30 kept. Writes take an optimistic lock (revision), so the host and the memory page can no longer swallow each other's entries.node:sqlite, WAL, zero dependencies); an existing JSON store is migrated read-only on first read, and the plugin falls back to JSON when the runtime has no node:sqlite.%APPDATA%\dsh-thirdparty-memory\), so updating the plugin no longer touches memory files.Earlier releases: see CHANGELOG in the repository.
Sorted by severity. Each entry states what broke, why it broke, and what changed in the code.
P levels
revision were read in two separate statementsThe host's remember tool and the memory page always write at the same time. If one committed between the read of the rows and the read of the revision, the other got old rows with a new revision; the optimistic lock accepted that stale copy and the entry that had just been added vanished — with no error. Four processes × 25 entries reproduced it: revision 101, table 100.
fixRows, settings and revision are now read inside one transaction (BEGIN … COMMIT), so a write is always validated against the exact snapshot it read.
storageInfo() created an empty database and quietly cancelled the degraded stateThe page's status call opened the SQLite file. Opening a missing path creates an empty database, so after one refresh a lost store looked like an ordinary empty store — and writes were accepted into it.
fixA missing file is left alone; the status reports zeros instead of creating a store.
“Latest user message” was one process-wide value. A subagent or a second window overwrote it, so the main session's memory block was chosen by somebody else's prompt (observed: the main session asked about a plugin bug, and its injection switched to another project's memories).
fixThe last message is cached per session and looked up through the assembly context; the process-wide fallback only accepts top-level sessions.
remove echoes the entry first and deletes on confirm=true. If a write moved the numbering in between, the confirmed number pointed at another entry and the wrong memory was deleted (reproduced: preview #2, delete #1, confirm #2 — the entry that had been #3 was deleted).
fixThe preview records the entry id; a confirm that resolves to a different id is refused with MEMORY_CONFIRM_MISMATCH, and deleting by id skips the check.
ephemeral entries never injected, oversized body answered with a dropped connectionephemeral entries were stored without a session id, so they never matched the running session and were never injected. A request body over 128 KB ended with a connection reset instead of the intended 413.
fixThe session id now comes from the tool call itself, and oversized bodies answer 413 after draining the request.
memory.sqlite-wal; the database file was a 4 KB shellThe WAL was never checkpointed, so memory.sqlite held no data at all: copying that one file produced an empty database, and deleting -wal lost everything — in both cases without an error.
fixA wal_checkpoint(TRUNCATE) now runs after every write, so the main file is the real store.
On a read failure the loader returned memory.json — the snapshot taken before the SQLite migration — with no warning. The next write then persisted that stale snapshot, permanently deleting everything added since.
fixA read failure now yields a degraded store with a visible warning and writes are refused; a migration sentinel stops the old JSON from being resurrected.
Each save ran DELETE FROM entries and reinserted the in-memory copy, with no lock and no version check. The plugin always has two writers — the host's remember tool and its own memory page — so a concurrent save dropped whole entries, silently.
fixA revision value in the meta table acts as an optimistic lock; a conflicting write is retried against a fresh read.
remember toolsearch printed hit ordinals instead of global numbers, so read <number> opened the wrong entry; update skipped the 2048-byte limit; status=archived was silently rewritten to active; a global entry could not be un-pinned; and the tag 画像 was dropped on save.
fixAll five corrected. remove now asks for a second confirmation, which is what this page always claimed it did.
The profile's package.json was written as UTF-8 with a BOM. The host's readProfileManifest hands the file straight to JSON.parse without stripping the BOM, so it throws SyntaxError: Unexpected token '\uFEFF' and the app exits with code 1. After three consecutive crashes the host stops restarting it.
fixThe installer writes manifests without a BOM, the reader now tolerates a BOM, and -Repair rewrites existing manifests.
data/memory.json inside itThe store defaulted to data/memory.json inside the plugin's own directory, so unpacking a new version over the old one destroyed the memories together with the old code.
fixThe default data directory now lives outside the plugin folder; existing stores are copied during migration and the source file is left byte-for-byte unchanged.
attach.ps1 without -DataFile silently dropped the configured dataFile lineCalled without -DataFile, the script rewrote the profile config without that field, so the plugin quietly switched to another memory file: the old entries were still on disk but no longer read.
fixExisting configuration is preserved; the field is only written when the parameter is passed.