Skip to main content
W

dsh-memory-keeper

wu079/dsh-memory-keeper

Cross-session memory for DSH: export every session to readable Markdown transcripts, keep one long-term MEMORY.md, and get a wake-up summary in a fresh window.

Install

dsh plugin --profile web add github:wu079/dsh-memory-keeper

README

dsh-memory-keeper

Cross-session memory for DSH. Every session is exported to a readable Markdown transcript (dialogue only), one long-term MEMORY.md is kept as the main file, and a new window gets a compact wake-up summary instead of starting from nothing.

English | 中文

What it is

dsh-memory-keeper gives an agent a memory that survives windows. It reads DSH's own session files (read-only), turns each conversation into plain Markdown with every message tagged by provenance, keeps a long-term MEMORY.md that the agent writes to, and assembles a small "wake up" digest when a fresh conversation begins.

Why

DSH sessions are isolated. Open a new window and the previous conversation is simply gone from the agent's view — only a compressed session file stays on disk, in a format meant for DSH itself, not for reading. Long conversations also get compacted, and details quietly fall out of context. So an agent that was told something last week has no reliable way to remember it today.

This plugin closes that gap:

  • Session transcripts — every session becomes <dataDir>/sessions/<workspace>/<date>_<id8>.md, dialogue only (user / assistant text), each message carrying a sessionId#seq provenance marker. A table index (sessions/INDEX.md) and a machine-readable sessions/index.json are maintained alongside. Sub-agent sessions (origin: subagent / sessions with a parentSession) are worker transcripts of a parent conversation and are skipped, so search results stay dialogue rather than delegation plumbing.
  • One long-term main file — MEMORY.md, the durable file you and the agent actually curate.
  • Wake-up digest — mem_wake returns a compact summary bounded by wakeMaxChars.
  • Optional housekeeping — archive stale sessions out of the way, and optionally back the data directory up to a git remote.

Nothing is hard-coded to a particular home directory or persona; dataDir is configurable.

How it differs from DSH's built-in memory tools

DSH ships its own memory_* tools: small key-value notes, scoped to project or user, saved and searched by the agent itself. Those are for facts the agent decides are worth pinning — a preference, a decision, a convention.

This plugin handles a different job:

built-in memory_* toolsdsh-memory-keeper
unit of memorya short note in a key-value storea whole session transcript + MEMORY.md
scopeproject / usera data directory you choose
what you get backnotes matching a keywordthe original dialogues, with file + line provenance
lifecyclewritten when the agent decidesexported on a timer, archived when old

They solve different problems and are happy to coexist — the tool names differ (mem_* vs memory_*), so installing this plugin does not shadow or conflict with the built-in ones.

Install

# from npm (after it is published)
dsh plugin --profile <your-profile> add dsh-memory-keeper

# from a local checkout
dsh plugin --profile <your-profile> add link:F:\path\to\dsh-memory-keeper

The plugin declares dsh.bundle, so the profile just has to list the package in dsh.profile.bundles. Restart the profile afterwards (or reload it) and the mem_* tools appear.

Then open Settings → Plugins → Cross-session memory and point it at a data directory, or leave that empty and accept the default.

The four tools

mem_wake

No arguments. Returns the "what happened while you were away" digest: data directory, last export time, read/write mode, the first wakeEntries items of MEMORY.md, the last three days of diary titles, and the recent session index — all capped at wakeMaxChars characters.

mem_wake

Searches the data directory and returns hits with file, line number and the matching text. scope is all | memory | diary | sessions.

mem_search { "query": "语音克隆", "limit": 10, "scope": "all" }

mem_write

Writes to the main file or to today's diary. target is memory (default) | diary; mode is append (default) or replace-section (which requires section); section names the heading to replace. A .bak copy is made before the write, and only the targeted section (or the end of the diary) is touched — never a full rewrite. The result reports bytes and sha256 before/after so you can verify. Refused when writable is false. Optional note says why you are writing (plugin log only, not stored in the main file), withDate controls the date suffix on single-line entries (default true), and day picks the diary date (default today).

mem_write { "content": "- 2026-10-24: 插件脚手架开工", "target": "memory", "mode": "append" }
mem_write { "content": "...", "target": "memory", "mode": "replace-section", "section": "事实台" }
mem_write { "content": "Finished the settings panel today.", "target": "diary" }

mem_status

No arguments. Returns the current state: data directory and where it came from, mode (read-only / read-write), main file size and mtime, diary and session counts, last export / archive / backup times, and the effective config.

mem_status

/mem command

commandwhat it does
/mem wakesame digest as mem_wake
/mem tidyincremental export, then archive sessions older than keepDays that are not the newest in their workspace (archive off = export only)
/mem exportexport sessions right now (--force regenerates everything)
/mem search <word>same search as mem_search
/mem statussame state as mem_status

Settings

The panel lives at Settings → Plugins → Cross-session memory. It reads and writes $DSH_HOME/memory-keeper/config.json, the same file the /api/memory-keeper/* routes use.

keydefaultmeaning
enabledtruemaster switch for background work
autoExporttruewhether the timer actually runs
dataDir""data directory; empty = $DSH_HOME/memory/<workspace name>
exportIntervalHours6auto-export interval in hours, clamped to 1–168
archivefalsemove expired old sessions into $DSH_HOME/sessions-archive
keepDays14archive threshold in days, clamped to 1–3650
wakeMaxChars2000cap on the mem_wake digest, clamped to 200–20000
wakeEntries6how many MEMORY.md entries the digest shows, clamped to 1–50
writabletrueallow mem_write; false = read-only mode
userLabel用户speaker label for the user in exported transcripts
assistantLabel助手speaker label for the assistant in exported transcripts
githubRemote""git remote for backup; empty = backup disabled
githubPushfalsepush after a backup
searchLimit20default number of mem_search hits, clamped to 1–200

The userLabel / assistantLabel defaults are the Chinese labels 用户 / 助手; change them if you prefer other speaker names in the exported transcripts.

HTTP routes

The panel uses these (same-origin, same local login as other DSH panels):

methodpathpurpose
GET/api/memory-keeper/statusstate view (what mem_status returns)
GET/api/memory-keeper/configcurrent config + defaults + resolved data directory
POST/api/memory-keeper/configmerge and save a config patch, returns the new config
POST/api/memory-keeper/exportexport now; body may carry { "force": true, "limit": 10 }
POST/api/memory-keeper/archivearchive; body may carry { "dryRun": true } to only preview
GET/api/memory-keeper/search?q=word&limit=20same as mem_search
GET/api/memory-keeper/wakesame as mem_wake

Data directory

dataDir defaults to $DSH_HOME/memory/<workspace name> (on a default install that is ~/.dsh/memory/<workspace name>). Set it explicitly if you want memory in a synced folder instead.

<dataDir>/
  MEMORY.md                  main file: the durable memory you curate
  diary/YYYY-MM-DD.md        one diary file per day
  sessions/INDEX.md          human-readable table index
  sessions/index.json        machine-readable index
  sessions/<wsName>/<YYYY-MM-DD>_<id8>.md
  state.json                 { lastExportAt, lastArchiveAt, lastBackupAt, lastBackupError, lastExportResult }

Safety and read-only guarantees

  • DSH's sessions are read-only. The plugin only ever reads ~/.dsh/sessions/**. It never writes back and never deletes anything there.
  • Archiving is off by default and explicit. With archive: true, sessions older than keepDays that are not the latest session of their workspace and that were already exported successfully once are moved with rename into $DSH_HOME/sessions-archive. Sessions that were never exported are never touched, and when archive is false, nothing is moved.
  • Writes to the main file are guarded. Before touching MEMORY.md (or a diary file) a .bak copy is left next to it, and only the targeted section is replaced — the rest of the file is left byte-for-byte alone.
  • No network by default. Leave githubRemote empty and the plugin makes no outbound connection at all. Backup only happens after you fill that in.

Privacy

Session transcripts contain the full conversation content. By default everything stays on your local disk under dataDir. Do not put dataDir inside a public repository, and if you enable the GitHub backup, remember you are pushing your conversations to that remote — leave it empty unless you are comfortable with that.

Requirements

piecewhat
DSH0.2.x, on a profile that can install plugins
Node.js>= 22

License

MIT — see LICENSE.

Related plugins