dsh-memory
Persistent, human-editable memory for DeepSeek Harness.
English | 中文
The agent forgets everything when a session ends. dsh-memory gives it one small place to
remember — a plain JSONL file you can read, edit, delete, and back up.
Tell it once:
You › I always use pnpm, never npm. And answer me in Chinese.
Agent › Saved. (memory 4f2a91c8)
Open a brand-new session tomorrow:
You › 这个项目装依赖该用什么?
Agent › pnpm。
No tool call, no re-explanation, no "as you mentioned earlier" — the fact is simply in the prompt from the first token.
Why this instead of AGENTS.md?
AGENTS.md is project guidance: static, shared, committed to the repository, written by
whoever maintains it. Memory is personal: written by the agent as it learns you, kept outside
the repo, and applied to every project.
AGENTS.md |
dsh-memory |
|
|---|---|---|
| Written by | a human, by hand | the agent, during work |
| Scope | one repository | you, everywhere |
| Lives in | the repo (committed) | ~/.dsh/memory/ (private) |
| Grows | when someone edits it | automatically, deduplicated |
| Budget | whatever fits | capped in bytes, newest first |
They compose. Keep coding conventions in AGENTS.md, keep you in memory.
Install
dsh plugin --profile desktop add github:AaronWang-cpu/dsh-memory
Then restart DSH. That is the whole setup: there is nothing to configure, no API key, no network access, no background service.
Install from a local checkoutdsh plugin --profile desktop add /absolute/path/to/dsh-memory
Or add it by hand to ~/.dsh/profiles/desktop/package.json:
{
"dependencies": { "dsh-memory": "github:AaronWang-cpu/dsh-memory" },
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"dsh-memory"
]
}
}
}
Use
Nothing to learn: memory is used by talking.
| You say | What happens |
|---|---|
| "I always use pnpm." | Saved as a durable preference |
| "以后的提交信息都用中文" | Saved |
| "Our staging DB is read-only, never write to it." | Saved — a constraint worth not re-learning |
| "What do I usually prefer?" | The agent searches memory |
| "Forget what I said about pnpm." | List, find, forget |
The agent decides when something is worth remembering; the tool's description tells it to skip transient task state and anything already readable from a file.
How it works
Two mechanisms, one store:
- The
memorytool — one tool with four actions (save,search,list,forget).savefolds whitespace and de-duplicates case-insensitively, so restating a fact updates it instead of piling up near-copies. - A prompt section — registered through
ctx.systemPrompt.section()at order10150, after the Web surface and before the persona suffix. Its text is a function, so it is re-rendered at every assembly: a fact saved in one session is visible in the next step of every other one, with no restart.
The section is byte-bounded (maxBytes, default 4096). Newest entries win; the section says
how many older ones were left out, and a tight budget degrades to a shorter header rather
than silently dropping the feature.
Storage
~/.dsh/memory/memory.jsonl
One JSON object per line:
{"id":"4f2a91c8","content":"Prefers pnpm over npm.","tags":["preference","tooling"],"createdAt":"2026-11-09T08:12:44.101Z","updatedAt":"2026-11-09T08:12:44.101Z"}
- Yours. It is a text file:
cat,grep,git, edit it by hand, delete a line. - Safe to edit. A malformed line is skipped, not fatal — one typo does not disable the plugin.
- Atomic. Writes go to a temp file and are renamed, so a crash mid-write cannot truncate it.
- Local. Nothing is uploaded. There is no account, no telemetry, no network code at all.
Configuration
Optional; the defaults are what most people want. In your profile's cordis.patch.yml:
- id: dsh-memory
name: 'dsh-memory'
config:
dir: 'C:/Users/me/.dsh/memory' # default: <DSH home>/memory
maxBytes: 4096 # prompt-section budget
maxEntries: 500 # oldest evicted beyond this
| Field | Default | Meaning |
|---|---|---|
dir |
<DSH home>/memory |
Store directory. DSH_HOME is honoured when set. |
maxBytes |
4096 |
Byte budget for the injected section. |
maxEntries |
500 |
Hard cap on stored entries; the oldest is evicted first. |
Limits
Honest list of what this version does not do:
- One global store. Memories apply to every workspace. Per-project scope is not implemented.
- Substring search. No embeddings, no ranking —
searchis a case-insensitive substring match on content and tags. That is enough for a few hundred short facts and costs nothing. - No automatic extraction. The agent saves explicitly; there is no post-turn summariser burning tokens behind your back.
- No UI. No settings page, no panel — a tool and a prompt section only.
- Prompt tokens. The section costs what it costs: 4 KB is roughly 1 K tokens on every
request. Lower
maxBytesif that matters to you.
Requirements
- DeepSeek Harness
0.2.x— the Host API this plugin targets is the one shipped in0.2.2-rc.1(ctx.tools.register,ctx.systemPrompt.section) - Node
>= 20 - No dependencies. The plugin imports only Node's standard library.
Development
node test/smoke.mjs # 39 checks, no dependencies
node test/cordis.mjs # lifecycle in the real runtime
smoke drives the tool and the prompt section against a temporary store: de-duplication,
budget trimming, eviction, malformed-line resilience, argument validation, and unload
cleanup. No test framework, no install step.
cordis mounts the plugin through the real @deepseek-ai/cordis and proves the two things
a stub cannot: that ctx.plugin() loads it, and that unloading removes both registrations.
It skips when the runtime is not installed, which is why CI runs only the first — run it
inside a DSH installation.
Licence
MIT — see LICENSE.
No comments yet. Be the first to write one.