Memory
dsh-memory
vpromise/dsh-memory · ★ 3
Persistent context across sessions for every agent: a DeepSeek Harness Cordis plugin that captures activity, c
安装
dsh plugin --profile web add github:vpromise/dsh-memoryGitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试
For install and environment setup, plus plugin development tutorials, see DSH community docs ↗
README
Table of contents
Persistent context across sessions for every agent.
A DeepSeek Harness plugin (Cordis) that gives every agent long-term memory: it watches each session's activity, distills it into durable semantic memories with the LLM, stores them per project, and injects the relevant ones into future sessions automatically.
Inspired by the core loop of claude-mem — capture session activity → compress it with the LLM → inject relevant context into new sessions — but implemented from scratch on DeepSeek Harness primitives: native session events, the agent lifecycle waterfall, and the built-in LLM service. No external processes, no database servers.
Why
- Every session starts cold. dsh-memory closes the loop: what one session learned — decisions and their reasons, user preferences, codebase facts, solved problems — is automatically recalled by the next one.
- Zero infrastructure: one JSONL file per project under
$DSH_HOME/memory, an in-memory index, and the LLM you already have. Nothing to install beyond the plugin itself. - Model-callable tools (
memory_search/memory_save/memory_list) let agents explicitly query and write the same memory store.
How it works
- Capture — subscribes to each session's persisted event stream
(
session/event):- direct human prompts (
user/message, onlysource.kind === 'user', so plugin-injected context is never re-memorized), - assistant reply summaries (
assistant/message, truncated), - tool calls and their results (
tool/call+tool/result, paired bycallId, with failure flags).
- direct human prompts (
- Compress — at the end of each turn (or when the buffer reaches its
threshold), the buffered observations are sent to the LLM (reusing
ctx.llm.stream), asking for a JSON array of[{text, tags}]. The request routes through the agent's default model, or an explicitly configured provider/model. Every failure is logged and silently dropped — compression never blocks or breaks the agent loop. - Store — one JSONL file per project
(
$DSH_HOME/memory/<project>-<sha1-8>.jsonl), one memory per line with id, text, tags, timestamp, and source session. The index lives in memory (synchronous reads), appends are serialized, and corrupted lines are skipped without errors. - Inject — in the
agent/pre-stepwaterfall, on the first step and every N turns, the most recent memories plus the memories relevant to the current prompt's keywords are rendered into asystem-reminderframe and folded into the step batch as a persisted user message withsource.kind === 'memory', formrecall, right after the direct prompt. Byte-budgeted; the same payload is never injected twice into the visible surface. - Tools —
memory_search,memory_save, andmemory_listare exposed as model-callable tools so agents can explicitly query and write the same memory store (the equivalent of claude-mem's MCP search tool).
Installation
Requirements: a running DeepSeek Harness installation. Building from source requires Node.js ≥ 20.
The published bundle dist/index.js is self-contained: it uses only Node
built-ins plus a bare side-effect import of @deepseek-ai/cordis, a peer
dependency that every DSH installation already provides. The bundle is
committed to this repository, so no build step is needed to install.
- Clone the repository (or download the source of the latest tag):
git clone https://github.com/vpromise/dsh-memory - Add one entry to your profile patch file:
- insert: - id: memory name: /path/to/dsh-memory/dist/index.js- web profile on this machine:
$DSH_HOME/profiles/web/cordis.patch.yml - all profiles:
$DSH_HOME/cordis.patch.yml
- web profile on this machine:
- Restart the DSH host. Memory context appears before the first reply of a
new session; when a session ends (
turn/end— natural completion or a full buffer), the background compression runs and the memories are visible to the next session.
One-shot headless CLI note: a long-running host (the web GUI, etc.) compresses
reliably in the background. A one-shot headless CLI only gets about 4 seconds
of drain at exit (bounded by the CLI's 5-second graceful shutdown), so an
in-flight compression request that has not returned by then is dropped.
Explicit memory_save calls are unaffected — they persist synchronously.
Configuration
All fields have defaults; the config block can be omitted entirely.
| Field | Default | Meaning |
|---|---|---|
enabled |
true |
master switch |
autoCapture |
true |
capture session events |
autoCompress |
true |
compress buffered observations with the LLM |
autoInject |
true |
inject memories into new/continued sessions |
maxInjectedBytes |
4096 |
max bytes of context injected at once |
maxRecentMemories |
6 |
recent memories injected first |
maxRelevantMemories |
4 |
additional memories matched by keyword relevance |
refreshEveryTurns |
6 |
re-inject every N turns within the same session |
compressAfterItems |
5 |
buffer size that triggers compression |
maxCompressInputBytes |
24000 |
upper bound of one compression input |
maxObservationChars |
3000 |
per-observation character cap |
maxMemoryChars |
800 |
per-memory character cap |
maxOutputTokens |
1024 |
compression output token cap |
timeoutMs |
60000 |
compression request timeout |
provider / model |
empty | compression route; defaults to the agent's model |
storageDir |
empty | memory directory; defaults to $DSH_HOME/memory |
Storage
- The project root is detected by walking up for
.git,.hg,.svn, orpnpm-workspace.yaml. - One JSONL file per project:
$DSH_HOME/memory/<basename>-<sha1-8>.jsonl; sessions without a project root write toglobal.jsonl. - Files are plain JSONL — view and edit them directly; delete a line and restart to make it disappear.
Development
pnpm install # dev deps (tsdown / vitest / typescript)
pnpm test # 46 unit + integration tests
pnpm typecheck # tsc --noEmit
pnpm build # emits dist/index.js (self-contained ESM)
- Type resolution:
tsconfig.jsonmaps@deepseek-ai/*to the builtlibtypes of your local DeepSeek Harness checkout; vitest aliases to the checkout sources viavitest.config.ts. - Integration tests drive the full loop — capture → compress → persist →
inject → dedupe → tool read/write — against a mock LLM adapter, and verify
graceful degradation when the
llm/toolsservices are missing. tests/dist.spec.tschecks the bundle against the loader contract (name/inject/Config/apply).- Real loader smoke test: with
DSH_CHECKOUTpointing at a compiled checkout, run fromsmoke/:DSH_CHECKOUT=/path/to/deepseek-harness node smoke.mjs - Real-LLM e2e: from the repo root, a real
deepseek-v4-flashcall through the plugin's compression pipeline. Credentials come fromDEEPSEEK_API_KEYorDSH_CREDENTIALSpointing at a.credentials.yaml:DSH_CHECKOUT=/path/to/deepseek-harness node e2e/compress-live.mjs
How it compares to claude-mem
| claude-mem | dsh-memory |
|---|---|
| 5 lifecycle hook scripts | native session/event + agent/pre-step events |
| standalone Bun worker + HTTP API | in-process Cordis plugin, zero external processes |
| SQLite + FTS5 + Chroma vector store | per-project JSONL + in-memory index + token-overlap scoring |
| Stop hook triggers compression | turn/end triggers compression, serialized tail dedupe |
| SessionStart hook injection | folded into the agent/pre-step waterfall with a recall context form |
| MCP search tool | memory_search / memory_save / memory_list |
| per-project worker data | per-project JSONL file |
License
MIT — see LICENSE.