
Security News
Deno Goes the Way of the Dinosaur
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.
dsh-plugin-memory
Advanced tools
Long-term memory plugin for DeepSeek Harness: persistent cross-session markdown memory with a soul/persona file, auto-injected at every session start, plus remember/recall/consolidate/forget workflows and portable migration tooling.
English | 中文
A long-term memory plugin for DeepSeek Harness: a cross-session, portable, markdown memory store that comes with a soul.
A Cordis plugin for DeepSeek Harness that gives agents persistent, cross-session, migratable long-term memory: a markdown + git store (inspired by Karpathy's LLM Wiki pattern) with a SOUL.md persona file, auto-injected at every session start through the system-prompt runtime context, plus remember / recall / consolidate / forget workflows and portable CLI tooling.
Three steps, about five minutes:
# 1. install
dsh plugin --profile <profile> add dsh-plugin-memory
Restart the app, open a session and say anything. The boot block loads automatically — and because the store has no soul yet, the agent starts the soul-forging conversation by itself: what its name is, how to address you, what you are to each other, what personality it should have. Answer as much or as little as you like; each answer is written back.
Confirm it took. dsh-memory doctor gives an environment check-up (scaffold, orphan pages, git state, boot budget) and ends with the command that fixes each finding. Or open Settings → Memory for the health dashboard and hot-editable options.
After that the loop is simple: the agent writes memories back as you work, and you can steer the store by hand whenever you like — it is plain markdown plus git, nothing locked in.
SOUL.md persona + MEMORY.md protocol + index.md directory + recent activity) into the session context via ctx.systemPrompt.context(). The host deduplicates by projection: an unchanged memory is injected once, and a changed memory replaces the previous snapshot — this is the hard guarantee that a new session always loads memory first, rather than hoping the model calls a skill. The total budget is bootMaxChars, allocated per file: every file first gets an equal share (capped by its real size), and the remaining budget goes to index.md first, then to files that still have content left — so as the store grows, the tail of the directory is not the first thing to be truncated (see "boot budget allocation"). Two polite gates ship enabled by default: deferUntilUserSpeaks (inject only after the user speaks) and activeSessionOnly (inject only into the currently active session) — see "Configuration".last_access stamping: the salience decay rules in MEMORY.md depend on last_access, which used to be pure self-discipline — the field existed in the template and the rules mentioned it, but no code ever wrote it, so every page silently aged and the decay table was decorative. Now, on the first real user message of a session, the plugin stamps today's date onto the pages the boot block actually injected (SOUL.md / MEMORY.md / index.md plus the pages the directory references) — once per session, idempotent per day, touching only the last_access line. dsh-memory touch <pages...> backfills manually; trackPageAccess: false disables it.BOOTSTRAP.md checklist, which takes priority over ordinary tasks until complete.BOOTSTRAP.md not complete, or SOUL.md still the placeholder template), the boot block prepends a first-person prompt — "my first job is to establish who I am and who you are: what my name is, what to call you, what we are to each other, and what personality I should have" — so the agent starts the forging conversation itself, item by item, writing each answer back, OpenClaw-style, instead of waiting to be fed. The prompt disappears once forging completes, at zero cost.dsh-memory pack/unpack moves it between machines.memory skill through ctx.skills.register() (the operating protocol ships with the plugin); a project-level .dsh/skills/memory file skill can still override it.digestNudgeAfterMinutes, the plugin injects a digest reminder (a synthetic message through agent.followup), turning "settle the session at the end" from self-discipline into a mechanism, with cooldown and a per-session cap so it never nags. Independent of dsh-plugin-heartbeat — either plugin can be installed alone.autoCommitQuietSeconds of quiet, store changes are silently git add -A && git commit (skipped when there is no .git) — history becomes rollback-able without depending on the agent remembering to commit.summary: in its frontmatter, and dsh-memory index --write rewrites the line as a projection of it (tag ≤32 / summary ≤48 / whole line ≤132 characters), changing only line content while preserving sections and order byte for byte, and idempotently. index --check gives a machine verdict (non-zero exit when there is drift), and index --sync-frontmatter backfills summaries already written in the index into the pages (a one-off migration for older stores). The digest reminder appends a note when the index has drifted, so the agent can fix it in passing.index.md is the directory (who exists); page-to-page links are the relationships (who relates to whom). In a real store measured here, 29 pages had only 1 genuine cross-link, yet the graph and all cross-page synthesis rest on that layer. Three places now push it together: the remember flow in skills/memory.md, the workflow in the MEMORY.md template, and the closing instruction of the digest reminder all require "add 1–3 related-page links for every page you touched this time".dsh-memory graph --suggest lists, for pages that have only their index entry and no content relationship, who they ought to reference — scored by shared topic tags, title words, and same type, with the reason stated (shared #dsh / title word dsh / same type). Report-only: whether and how to link stays the agent's call. Also available as GET /api/memory/graph?suggest=1.index.md star (the index is connected to everything), but the relationships pages already encode and nobody ever drew: index routing (index.md points at every page — the largest edge set in the store, 43 edges; earlier versions skipped meta files, which left the index stranded on the graph), explicit markdown links between pages (with relative path resolution), and shared frontmatter tags (generic container tags like project/skill are ignored; dense tags go through an anchor chain rather than all-to-all). Coordinates are computed once on the server by lib/graph-layout.js (deterministic, no randomness, no dependencies); the client only draws SVG. Interactive: wheel zoom (cursor-centred, 0.4×–2.5×, non-passive listener so the settings page does not scroll along), drag empty space to pan, Reset view to return to the whole graph; hold a node to drag it, direct neighbours follow slightly by distance, and it eases back to the layout on release — transitions pause during a drag for 1:1 tracking, using an O(neighbours) local relaxation instead of a per-frame full force simulation (which stalls at a few hundred pages). Hover highlights the neighbourhood; the first render blooms out from the centre (respecting prefers-reduced-motion). The graph auto-refreshes every 25 seconds (changes appear while the panel is open, with "updated at HH:MM:SS" in the subtitle); a failed fetch keeps the previous graph instead of clearing it. The view maths (zoomAt/panBy/toGraphPoint) are pure functions in lib/graph-view.js with tests — "the zoom centre must not drift" is pinned down. GET /api/memory/graph (read-only, live; ?types=1 adds weak same-type edges). Three edge kinds are distinguishable: page links = solid blue, index routing = grey dashed, shared tags = thin lines.dsh-memory checkup: folds the four readers (lint's integrity, status's counts, the dashboard's size and freshness, graph's relationships) into one prioritised report — size, freshness bars, connectivity, consistency checks, ending with "the N things most worth doing". The four numbers are diagnosis; "do these three things" is the product. Exits non-zero when there is work to do, so scripts can consume it. --boot=N computes the index share against your boot budget.GET /api/memory/insights (computed live per request, never cached), and its criteria share a source with dsh-memory lint, so the dashboard and the CLI cannot contradict each other.pnpm add.The plugin owns the workflow, never the data format:
dsh-plugin-memory (this plugin)
├── lib/index.js # Cordis entry: boot injection + runtime skill registration + settings hot-edits
├── lib/boot.js # boot block rendering (SOUL/MEMORY/index + recent log, budgeted per file)
├── lib/pages.js # page access accounting: extractReferencedPages / touchPages (last_access)
├── lib/insights.js # data layer for the health dashboard (pages/distribution/freshness/checks, read-only)
├── lib/activity-tracker.js # the two polite gates: has the user spoken + is this the active session
├── lib/digest-guard.js # anti-laziness digest nudge (idle + store unwritten -> followup reminder)
├── lib/recall-nudge.js # proactive recall (idle -> one true recollection in the first person, never writes)
├── lib/scaffold.js # store scaffolding (templates are created, never overwritten)
├── lib/client.js # client half: the "Memory" settings section + health dashboard
├── skills/memory.md # the embedded skill's operating protocol
└── scripts/memory.mjs # CLI: init/search/touch/lint/status/pack/unpack
The store (your data, default ~/.memory)
├── SOUL.md # persona and soul (user-owned)
├── BOOTSTRAP.md # soul-forging checklist (takes priority until complete)
├── MEMORY.md # schema and maintenance protocol (self-describing)
├── index.md # page directory log.md # timeline (append-only)
├── identity/ user/ skills/ decisions/ projects/{active,archive}/ concepts/
└── raw/ # immutable source material
dsh plugin --profile <profile> add dsh-plugin-memory
(The package ships a dsh.bundle manifest, so dsh plugin add mounts it into the profile's bundles layer automatically; one-click install from dsh-market uses the same channel.)
Restart the profile (restart the DSH Desktop app) to take effect.
📌 Version requirement: DSH session format v4 (official DeepSeek Harness 0.1.7+)
Injected messages use the producer-owned source kind
plugin:memory. v4's native admission explicitly rejects the v3-era{ kind: 'plugin', plugin: 'memory' }wrapper withformat v4 message requires a producer-owned source kind(the error is wrapped ascode: "UNKNOWN", so it surfaces in the UI as... source kind UNKNOWN—UNKNOWNis the error code, not a kind value).The v4 and v3 whitelists are mutually exclusive (v3 only recognises
plugin), so one build cannot support both. 0.8.0 and earlier trigger this error on v4; older DSH should stay on 0.8.0.
⚠️ Do not hand-write
- insert: {id: dsh-memory, ...}into the profile'scordis.patch.yml: that produces two entries with the same name alongside the bundle manifest's automatic mount, and the whole profile fails to start withduplicate loader entry id "dsh-memory"(a real incident on 2026-08-18). Runtime settings (enabled / memoryDir / autoInject / registerSkill / recallEnabled) go through<dshHome>/memory.json(hot-edited from the settings panel); composition settings are in the table below. To override a composition key, use an id override entry withoutinsert(see Configuration).
| Key | Default | Meaning |
|---|---|---|
enabled | true | Master switch: when off, no boot injection and no memory skill registration |
memoryDir | ~/.memory | Absolute path to the store (~ is expanded) |
bootFiles | [SOUL.md, MEMORY.md, index.md] | Files injected at session start |
bootMaxChars | 12000 | Total character budget for the boot block (keeps context usage bounded); per-file allocation below |
bootFileBudgets | — | Per-file character caps, e.g. { index.md: 3500 }; unlisted files share the remainder (composition layer, restart required) |
trackPageAccess | true | On the session's first user message, stamp last_access on the pages the boot block actually injected (when off, the decay table needs manual upkeep) |
autoInject | true | Inject the boot block at session start |
deferUntilUserSpeaks | true | Inject only after the user's first real message (the boot block, recall and digest reminders all obey it); hot-editable |
activeSessionOnly | true | Inject only into the "currently active session" (the one that most recently received a user message); background sessions are left alone; hot-editable |
registerSkill | true | Register the embedded memory skill |
scaffold | true | Create the store templates when missing (create only, never overwrite) |
configFile | <dshHome>/memory.json | Path to the user-editable JSON config (read/written by the settings panel) |
digestNudgeEnabled | true | Master switch for the anti-laziness digest reminder (composition) |
digestNudgeAfterMinutes | 120 | How long the store may go unwritten before reminding |
digestNudgeCooldownMinutes | 180 | Minimum gap between two reminders |
digestNudgeMaxPerSession | 2 | Maximum reminders per session |
recallEnabled | true | Master switch for proactive recall (hot-editable) |
recallIntervalMinMinutes | 30 | Lower bound of the random interval (minutes, hot-editable) |
recallIntervalMaxMinutes | 240 | Upper bound of the random interval (minutes, hot-editable) |
recallMaxPerSession | 3 | Maximum recalls per session (hot-editable) |
autoCommit | true | Git auto-commit switch for the store (composition) |
autoCommitQuietSeconds | 60 | How long changes must be quiet before committing (debounce) |
autoCommitIntervalSeconds | 60 | Change polling interval |
Ten settings are editable in the "Memory" section of the DSH settings page and take effect immediately: enabled / memoryDir / autoInject / deferUntilUserSpeaks / activeSessionOnly / registerSkill / recallEnabled / recallIntervalMinMinutes / recallIntervalMaxMinutes / recallMaxPerSession — boot injection, the two polite gates, skill registration, and proactive recall (including its random interval and cap) all follow the change at once; switching the store directory scaffolds the new one automatically (when scaffold: true). The remaining keys (bootFiles / bootMaxChars / bootFileBudgets / trackPageAccess / scaffold / configFile / digestNudge* / autoCommit*) only apply at the composition layer and need a restart.
bootMaxChars is a total budget, allocated per file rather than split evenly:
bootFileBudgets entry take their own allowance first;index.md first, then the rest by descending size — so which file gets truncated is decided by size, not by the order in bootFiles.When every file is under the equal share, the result is byte-for-byte identical to the old even-split rule; it only diverges once the store grows. This also means: index.md is a routing table, not a summary table — it is injected in full, and bloat costs you the persona and other memories.
How is the "currently active session" decided? The DSH host has no "currently focused session" signal (active session is a front-end concept). The plugin uses the live root agent that most recently received a real user message as a proxy: whichever session you speak in is the active one; switching away without sending a message is invisible to the host (a known limit of the proxy). Exact "expanded/focused" precision would need a small client-side focus report.
Below the "Memory" section is a read-only dashboard fed by GET /api/memory/insights:
| Display | Meaning |
|---|---|
| pages / index routes / word count | Store size (counted in characters, so Chinese is not inflated 3× by bytes) |
| Health score (0–100) | Weighted from orphan pages, missing salience/type, stale pages, and broken index links |
| Access freshness bars | today / ≤7 / ≤30 / ≤90 / >90 days / never stamped (driven by automatic last_access stamping) |
| Four checks | Index links complete, no orphan pages, frontmatter complete, access freshness — same source as dsh-memory lint |
| Stale candidates | Pages older than 90 days or never stamped, sorted by staleness (top 8) |
| Recent activity | The 5 newest log.md headlines |
The dashboard is live: refetched each time the settings page opens (or after any config change), never persisted, cached, or written back to the store. On a headless deployment without webServer, that route and the dashboard simply do not exist; everything else is unaffected.
To override a composition key (for example raising the boot budget), write an id override entry without insert in the profile's cordis.patch.yml:
- id: dsh-memory
config:
bootMaxChars: 12000
Implementation note: DSH's settings wire only serves a hard-coded namespace allowlist, so a plugin namespace cannot be written through it. This plugin therefore uses its own channel: config lives in
<dshHome>/memory.json(schema-validated, atomically written), served by the plugin's self-registeredGET/POST /api/memory/configroutes, which the client section fetches directly.
After installation, the agent's first task in the first session is not to work, but to talk with you about its soul: name, personality, values, tone, boundaries, plus who you are and what you are to each other. Each item is confirmed and written back to SOUL.md / user/profile.md until BOOTSTRAP.md's status becomes complete. You can skip or defer at any time.
index.md and appending to log.md in the same pass. Keep routing-table discipline when maintaining the index: one line per page, a one-sentence summary (≤ 80 characters) plus salience.index.md first and then drill into pages; use dsh-memory search <keywords> when needed.dsh-memory lint finds contradictions, orphans, and cold pages due for archiving; dsh-memory status shows the stale-page distribution.last_access is maintained by the plugin — no manual edits.dsh-memory init [dir] # create the store scaffold
dsh-memory search <query> [--touch] # full-text search (--touch stamps hits with last_access)
dsh-memory touch [pages...] # stamp last_access manually (no args = every page)
dsh-memory graph --suggest # relationship report + "who should this page link to" (report only)
dsh-memory query --type decision --tag dsh --hot --stale [keywords]
# structured query: filter by frontmatter, then match the body
dsh-memory checkup [--boot=N] # one report: size/freshness/connectivity/consistency + prioritised actions
dsh-memory lint # integrity check
dsh-memory doctor # environment check-up + the command that fixes each finding
dsh-memory status # health overview (including stale-page counts)
dsh-memory pack [out.tar.gz] # export an archive (with manifest)
dsh-memory unpack <archive> [--force] # restore from an archive
Store resolution order: $MEMORY_DIR → ./.memory (when present) → ~/.memory.
The store is plain markdown + git: copying it is the migration. Cross-machine, cross-agent, and capability-degradation tiers are covered in docs/MIGRATION.md.
Q: How does this relate to a hand-written .dsh/skills/memory file skill?
The file skill is a soft guarantee (only the skill's blurb is injected; the body depends on the model choosing to load it). This plugin is a hard guarantee (the boot block rides the system prompt's runtime context automatically). They can coexist: the file skill (rank 100) overrides the plugin's embedded skill (rank 250). If you previously edited the system prompt persona (say a boot instruction in a profile patch) to get the soft guarantee, remove that persona after installing this plugin to avoid double injection.
Q: Does the boot block get re-injected on every request and burn tokens? No. Runtime context is deduplicated by projection: an unchanged payload is injected once, and an updated memory replaces the old snapshot.
Q: Where should the store live?
~/.memory by default (global, cross-project). For per-project isolation, point memoryDir inside the project, or let the agent maintain .memory/ there.
Q: Can it be encrypted?
If the memory holds sensitive content, put memoryDir on an encrypted volume or in a private repository. The format is unchanged and the plugin is unaware.
git clone https://github.com/LittleBlackTong/dsh-plugin-memory.git
cd dsh-plugin-memory
node scripts/memory.mjs --self-test # smoke test (no dependencies to install)
Zero build: lib/ is runtime code as-is, and lib/types/index.d.ts serves TypeScript consumers. boot.js / scaffold.js depend only on node:* built-ins and can be reused standalone.
--self-test and lint)Requests, bug reports, and pull requests are welcome in Issues.
FAQs
Long-term memory plugin for DeepSeek Harness: persistent cross-session markdown memory with a soul/persona file, auto-injected at every session start, plus remember/recall/consolidate/forget workflows and portable migration tooling.
The npm package dsh-plugin-memory receives a total of 0 weekly downloads. As such, dsh-plugin-memory popularity was classified as not popular.
We found that dsh-plugin-memory demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.

Security News
Anthropic’s free OSS Scanner uses Mythos and other Claude models to find open source vulnerabilities and send maintainers reports with proposed fixes.

Security News
On Front Lines’ Early Adopters: CISO podcast, Andrew Becherer shares lessons from Datadog, weighs the long-term costs of AI-built tools, and explains how founders can actually gain a CISO’s trust.