dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Cairn — long-term memory for the DeepSeek Harness (@chenhw7/dsh-memory). Persistent cross-session memory: facts, preferences, corrections, lessons. BM25 retrieval, three-layer scoping, human-confirmed writes.
Cairn is long-term memory for the DeepSeek Harness, packaged as the @chenhw7/dsh-memory profile bundle — facts, preferences, corrections, and lessons survive across sessions and restarts.
This is a self-contained single package (not a multi-package workspace). It depends on dsh core services as peer dependencies (provided by the dsh installation you already have) and ships its own cordis.patch.yml so dsh plugin add activates it as one profile layer.
English | 简体中文
A single-file, zero-dependency illustrated walkthrough of the whole pipeline — signal capture, storage, retrieval, injection, and decay. The figures are not static: the extraction loop, the write-path security gates, the BM25 kernel ported from src/store/bm25.ts, and a decay simulator all run live in your browser. It is served from this repo via GitHub Pages:
→ Open the live page: memory-architecture.html · 简体中文版
Your dsh agent normally forgets everything when you close a session. This bundle fixes that:
global (cross-project), project (per-repo, auto-detected), and user (cross-project profile).memory_search, memory_add, memory_replace, memory_remove, memory_list, memory_get, memory_pin, memory_unpin, memory_forget, plus identity_update (below).identity_update and injected into every session (soul @80 / user-profile @81, coexisting with — and yielding to — the deployment persona; explicit conversation instructions always win). Seeded once from built-in Chinese copy, versioned with full history, never decayed. You hold no editor — only governance: a read-only Identity settings section with version history, a two-step revert valve (default on, separate from the remote write switch), and markdown export. Extraction never persists identity restatements (prompt rule + mechanical prefilter), and an eval slice + --identity axis pin the injection surface (see TECH_DESIGN §7.10).project-notes section). Nothing is written into your repository: memory lives entirely in the host-side store, is managed in the Memory settings UI, and upgrades automatically clean up notes files left by ≤0.5.x installs.<memory-digest> inventory of the store (category counts + anchor topic words, re-appended once after compaction) on the session's first step, and per-step <recalled-memory> fences when the step's text matches stored entries. The prompt prefix stays byte-stable all session, and nothing resident has to be evicted on re-freeze.global/user entries are soft-decayed (hidden from standing injections, still searchable, un-stamped on recall); entries can also be manually archived from the UI; every write is audited.<recalled-memory> message without touching the system prompt, keeping the KV-cache prefix stable. Fence hits refresh an entry's recency clock but never its access count, so query-word luck cannot inflate the eviction signal.[BLOCKED: …]) wherever it would re-enter a prompt.confirmBeforeWrite) and every extraction and tool write becomes a proposal in a pending queue (repeated signals accumulate hits and float up); adopting applies it (with your edits), rejecting discards it. The model never self-promotes: a proposed change to an existing entry rewrites nothing until you accept it.memory_list returns newest-first with earliest/latest/hasStale metadata, accepts since/until epoch-ms bounds so "what did we learn last week" pages within the window, and suggests widening the filter when a narrowed query comes back empty over a non-empty store.memory_add/memory_replace accept a summary, and extraction can emit a [summary:…] tag; index mode and auto-recall render summaries instead of truncated content.Node.js installed.
The dsh CLI available (globally or from a source checkout):
npm install -g @deepseek-ai/dsh
(If you prefer, npx @deepseek-ai/dsh also works.)
Or build dsh from source:
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
With a source checkout, run dsh commands as pnpm dsh ... from the deepseek-harness directory (for example pnpm dsh web).
pnpm (used by dsh to manage profile dependencies). If it is not installed yet:
npm install -g pnpm
pnpm --version
A profile you want to add memory to (this guide uses web).
[!IMPORTANT] Harness channel:
alpha(0.1.2-alpha.x). This bundle is built against the@deepseek-ai/dsh-settings0.1.2-alpha API (ctx.settings.installSection) and declares^0.1.2-alpha.2peers. Install dsh from thealphachannel — the rc (next) line ships the removed module-level helpers and cannot load this bundle. See Known Limitations.
One command. The published tarball ships prebuilt, so pnpm does not run any build script on your machine and no extra pnpm configuration is needed:
dsh plugin add --profile web @chenhw7/dsh-memory
With a source checkout, run it from the deepseek-harness directory instead:
pnpm dsh plugin add --profile web @chenhw7/dsh-memory
To pin a specific version:
dsh plugin add --profile web @chenhw7/dsh-memory@0.4.0
If you want to hack on the plugin, clone and build it first, then install from the local path:
git clone https://github.com/chenhw7/dsh-memory.git
cd dsh-memory
npm install && npm run build
dsh plugin add --profile web file:.
pnpm does not run build scripts for file: dependencies, so no allowBuilds entry is needed — the install copies the files as they are, which is why the npm run build step above matters: a missing or stale lib/ is what you get installed.
If you'd rather not install from the npm registry, pack a tarball from a checkout with lib/ already built and install it — a tarball ships prebuilt, so no allowBuilds entry is needed:
cd dsh-memory
npm install && npm run build
npm pack # produces chenhw7-dsh-memory-0.5.0.tgz
dsh plugin add --profile web ./chenhw7-dsh-memory-0.5.0.tgz
dsh plugin add always installs the latest version from npm on a fresh profile. But once a version is installed, running dsh plugin add again will not update it — pnpm sees the existing version range (e.g. ^0.2.0) is already satisfied by the latest (e.g. 0.2.1) and skips the update.
To update to the latest published version:
dsh plugin --profile web update @chenhw7/dsh-memory
With a source checkout:
pnpm dsh plugin --profile web update @chenhw7/dsh-memory
Version 0.6 stops exporting memory into repository files. The docs/agent-memory/CONVENTIONS.md / PITFALLS.md renders and the managed AGENTS.md pointer block are gone; memory is prompt-injected and managed in the Memory settings UI only. On the first session in each project, the plugin removes what ≤0.5.x left behind: the managed pointer block is stripped from AGENTS.md (a pointer-only AGENTS.md is deleted) and the generated files under docs/agent-memory/ are deleted (only files the plugin generated; anything else in that directory is kept). You can also delete them by hand at any time — nothing writes there anymore.
Remove the plugin from a profile:
dsh plugin remove --profile web @chenhw7/dsh-memory
(with a source checkout: pnpm dsh plugin remove --profile web @chenhw7/dsh-memory from the deepseek-harness directory). This runs pnpm remove in the profile directory and reconciles the layer list, so the seven memory-* rows disappear from the composed config — you can confirm with the --dump-config check below.
Uninstall does not delete your saved memories. They live under dsh's storage area, named by the memory-store row's storage config: host-medium (default) keeps everything in memory.json; sqlite moves the store into the plugin-owned memory.db with a one-time migration (memory.json then holds only the migration marker):
# macOS/Linux
~/.dsh/storages/memory.json
~/.dsh/storages/memory.db
# Windows
%USERPROFILE%\.dsh\storages\memory.json
%USERPROFILE%\.dsh\storages\memory.db
# or $DSH_HOME/storages/memory.json (or memory.db) if you set DSH_HOME
Stop dsh, then delete the file for your backend (under sqlite: memory.db — leftover -wal/-shm sidecars are empty and harmless) to wipe all saved memories. Other files in the same directory belong to other features — do not remove the whole directory.
After install, confirm the seven memory rows are in the composed profile tree:
# Windows
dsh --profile web --dump-config | findstr memory
# macOS / Linux
dsh --profile web --dump-config | grep memory
You should see seven rows pointing at @chenhw7/dsh-memory/*:
- id: memory-root
name: '@chenhw7/dsh-memory'
- id: memory-store
name: '@chenhw7/dsh-memory/store'
- id: tool-memory
name: '@chenhw7/dsh-memory/tool'
- id: memory-review
name: '@chenhw7/dsh-memory/review'
- id: memory-notes
name: '@chenhw7/dsh-memory/notes'
- id: memory-context
name: '@chenhw7/dsh-memory/context'
- id: memory-remote
name: '@chenhw7/dsh-memory/remote-service'
Then start dsh and check the settings UI shows the memory namespace:
dsh web
The bundle owns five settings namespaces — one per plugin-configuration card (the host's plugins tab dispatches a card only when its slot key names a served settings namespace) — and they all apply live: a change takes effect on the next event or call, no restart:
memory (card: Memory) — injection modes and the memory budgets. Owned by memory-context.memory-notes (card: Project Notes) — the project-notes prompt-section switch and budgets. Owned by memory-context.memory-autorecall (card: Auto Recall) — the step-level recall fence and the usage-hit signal. Owned by memory-context.memory-identity (card: Identity) — the identity layer's switch, budgets, and seed directory. Owned by memory-context.memory-review (card: Automatic Extraction) — extraction pipeline, model routing, dedup judge, pitfall streaks, curator pass. Owned by the memory-review plugin.Every namespace resolves in layers: schema defaults → the composition config: entry (the base) → the user document in $DSH_HOME/settings.yaml. A field absent from the user layer inherits the composition value, so a deployment can pin a default and users override only what they need. With no settings service mounted (e.g. a headless profile), each plugin falls back to its composition entry exactly as composed.
memory namespace| Setting | Default | Meaning |
|---|---|---|
memoryMode | digest | digest: guidance-only prompt sections; the data rides step-tail messages — a one-time <memory-digest> store inventory (counts + topic words) plus per-step <recalled-memory> fences. full: inject frozen memory content + guidance. policy-only: inject guidance only, model searches on demand. custom: inject user-defined policy text. off: no injection. index: inject an existence index (one line per entry) so the model can see what is stored and route to memory_get/memory_search. |
memoryPolicyCustomText | — | Custom policy text used when memoryMode is custom. |
memoryCharLimit | 5000 | Character budget for the frozen per-session memory snapshot injected in full mode (0 = no content). |
memoryDigestCharLimit | 800 | Character budget for the one-time <memory-digest> inventory message in digest mode (≈250 tokens; CJK topics run higher). 0 turns the digest message off. |
memoryMaxEntries | 20 | Entry-count cap for the same frozen snapshot (0 = no limit). The snapshot ends with a ≈N tokens estimate so injection cost stays visible. |
maxSearchResults | 50 | Default cap for memory_search / memory_list when the call omits limit; read live by the tool plugin. 0 = no limit. |
decayDays | 30 | Lifecycle window for entries not recalled within N days; read live by the review plugin's janitor. 0 = disabled. Overdue project entries are removed (hard decay); overdue global/user entries are instead soft-decayed — stamped stale, hidden from injection surfaces and notes files, still searchable, un-stamped automatically once recalled. Pinned entries are always exempt. |
memory-notes namespace| Setting | Default | Meaning |
|---|---|---|
notesEnabled | true | Inject the project-notes prompt section (conventions + pitfall log) into the system prompt. Entries rendered into the section are excluded from the memory section to avoid double injection. No repo files are written. |
notesConventionsCharLimit | 1600 | Character budget for the conventions half of the notes section. Entries the budget squeezes out fold into a count line pointing at memory_search — never silently dropped. The budget applies at freeze time; live changes take effect at the next session/compaction boundary. |
notesPitfallsCharLimit | 800 | Character budget for the pitfall-log half of the notes section, so conventions can no longer starve it. Same freeze-time semantics and count-line folding. |
notesMaxEntriesPerFile | 100 | Max entries selected into the notes section (pinned first, then importance, then recency); the rest fold into count lines. |
Settings migration: the 0.9.x combined key
notesCharLimitstill works — when it is the only budget key set, both halves derive from it (conventions 60% / pitfalls 40%); each new key you set wins its own half. Rename the key in$DSH_HOME/settings.yaml(or the compositionconfig:) to migrate.
memory-autorecall namespace| Setting | Default | Meaning |
|---|---|---|
autoRecallEnabled | true | Step-level auto recall: on every agent step, run a BM25 search over the store keyed on the step's user text and append a fenced <recalled-memory> message. The system prompt is untouched, so the KV-cache prefix stays stable; fence hits stamp recency only, never the access count. |
autoRecallLimit | 5 | Max entries in one auto-recall fence (min 1). The fence itself is capped at 1200 characters. |
autoRecallMinChars | 12 | Skip recall when the step's user text is shorter than this many characters (min 1). |
hitSignalEnabled | false | Enable the usage-hit signal: answers echoing injected entries above hitSignalThreshold bump hitCount; purely a selection signal, never deletion. |
hitSignalThreshold | 0.25 | IDF-weighted token coverage above which an answer counts as a hit. |
memory-identity namespace| Setting | Default | Meaning |
|---|---|---|
identityEnabled | false | Enable the identity layer: inject soul and user-profile prompt sections and expose the identity_update tool. See TECH_DESIGN §7.10. |
soulCharLimit | 2000 | Character budget for the soul section (0 = disabled). |
userCharLimit | 3000 | Character budget for the user-profile section (0 = disabled). |
identitySeedDir | "" | Directory holding custom SOUL.md / USER.md seeds; empty uses builtin seeds. |
memory-review namespace| Setting | Default | Meaning |
|---|---|---|
reviewEnabled | true | Enable automatic periodic review extraction. |
reviewCandidateThreshold | 10 | Unprocessed candidate signals that trigger one extraction drain (min 1). |
flushOnCompaction | true | Extract memories from shadowed events after compaction. |
flushOnDispose | true | Extract remaining context when a session is disposed (5 s cap). |
extractionModelProvider | "" (session route) | Override the LLM provider for extraction/judge/curator calls. Empty = use the session's conversational model (the default — extraction reuses the same model the user is chatting with, no extra keys or billing channel). |
extractionModelModel | "" (session route) | Override the model name for extraction/judge/curator calls. Empty = use the session's conversational model. Set both fields to route extraction to a cheaper/faster model. |
extractionBudget | 20 | LLM-call charges per session, shared across review drains, both flushes, and curator passes. 0 = unlimited. |
judgeEnabled | true | Run the LLM dedup judge on prefilter hits. When false, prefilter hits merge directly (cheaper, but may false-merge same-template different-topic pairs). |
pitfallStreakThreshold | 2 | Consecutive same-signature tool failures that must occur (and then be resolved by a success) before one structured pitfall candidate is emitted for extraction into the notes files. One-shot failures are not extracted. |
curatorEnabled | true | Low-frequency curator pass: every curatorEveryNSessions session creations, the longest oversized entries are rewritten into concise one-liners by the extraction model (budget-gated). |
curatorEveryNSessions | 20 | Run the curator pass every N session creations. |
curatorMaxEntries | 5 | Max entries selected per curation pass (longest first). |
curatorMinChars | 400 | Only entries at least this long are selected for re-summarization. |
confirmBeforeWrite | false | Human-review mode: every extraction (review/flush/curator) and every memory_add/memory_replace call lands as a proposal in the pending queue instead of the store, until a human adopts it in the Memory settings section (Review tab). Repeated observations of the same proposal accumulate hits and float it to the top; a proposed change to an existing entry carries its id and rewrites nothing until adopted. |
There is deliberately no separate
tool-memorysettings namespace: the tool plugin readsmaxSearchResultslive from thememorynamespace above. Its compositionconfig.maxSearchResultsonly serves as the fallback base when no settings service is mounted.
Every namespace accepts the same keys from both layers. A composition config: entry sets the base; the UI writes the user layer on top. The four memory-family namespaces share one composition row (memory-context's config: carries all their keys), while the user document keeps one section per namespace. For example, to pin maxSearchResults: 100 as the deployment default while still letting a user override it:
memory:
config:
maxSearchResults: 100
By default, extraction, judging, and curation use the same model the user is chatting with — the session's provider/model route. To run them on a dedicated cheaper model, set extractionModelProvider and extractionModelModel (in the composition config or the UI — the UI offers dropdowns fed by the host model catalog). Naming a provider here sends conversation excerpts and stored entries to it, so pick one you trust with conversation content; leaving both empty adds no data path beyond the model you are already talking to:
memory-review:
config:
extractionModelProvider: deepseek
extractionModelModel: deepseek-chat
Example $DSH_HOME/settings.yaml (all namespaces):