Make the AI remember how you think, not what you said: actively captures your principles, redlines, and working habits across sessions — with memory capacity, graceful decay, and human-readable reasoning for every entry. 0.7.0 brings an intuitive persona portrait with evidence tracing.
Install
# from npm (prebuilt)
dsh plugin --profile web add @dsh-plugins/dsh-user-mirror
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:webkubor/dsh-mirror
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
With vs. without
| Without | dsh-user-mirror | |
|---|---|---|
| New session | You repeat your principles | Auto-injected — the model already knows |
| What's recorded | Opaque — you can only guess | Every entry shown in the Memory tab |
| Why it was recorded | Untraceable | Every entry carries its reason — click to see |
| Will it bloat context | Yes | Injected by token budget, not by entry count |
| Wrong entry recorded | You have to restart the session | Click ✕ to withdraw |
| Outdated preferences | Stays forever, misleads the model | Decays — entries unconfirmed for 30 days move to the fading section |
What this is
A DSH plugin: the model actively records how you think, persists it across sessions, and re-injects it into the system prompt.
- 🧠 Learn — when the model notices you expressed a reusable judgment, it calls
mirror_rememberand writes down why - 💾 Store — persisted locally via
ctx.storageDomain; capacity is bounded, entries decay, new beliefs overwrite old - 🎯 Use — auto-injected into the system prompt by token budget, not entry count
Only judgments, never facts
This is the only design choice that matters. Facts (paths, accounts, servers, repo addresses) have their own source of truth — recording them again here creates a second source of truth, and they will eventually drift apart. So this plugin only takes what isn't already in a source of truth: your principles, tradeoffs, redlines, working style.
This is also the answer to "the human brain can't grow infinitely": judgments are inherently bounded (dozens at most), facts are not. By taking only the former, capacity doesn't need to be brute-forced by eviction.
| Category | Records |
|---|---|
principle |
Principles / tradeoffs — how to choose under what conditions |
redline |
Redlines — never do X |
workflow |
Workflow — process & collaboration habits |
taste |
Aesthetic / expression — wording, style, presentation |
Why not infer from the thinking trace
Before v0.4 we regex-scanned the model's reasoning stream looking for phrases like "the user's preference is..." — zero hits on real data. The assumption that the model will paraphrase your preference in a specific Chinese phrasing is too brittle; and even when it does, "the user wants me to change this button" would get recorded as a long-term preference.
"Is this a long-term principle or a one-off request?" is a judgment. Regex is a hand. A hand can't make that judgment. So we let the model decide and call the tool itself — which also gives explainability: every recording happens in the conversation, visible to you, with a reason attached, instead of being silently written in the background.
What the Memory tab looks like
After install, a new Memory tab appears next to Conversation / Trace. It isn't a list — it's a portrait:
- One-line self-summary at the top — the most-confirmed judgment, i.e. the one the model is most sure about you
- Four sections — redlines in their own block (crossing one has cost and they don't decay), principles as the main body, workflow & taste compressed into two columns
- Weight = confidence — the more often a judgment is confirmed, the heavier the line; weak ones fade to grey. No "strength 3.7" numbers — those don't mean anything to humans
- Evidence folded inside — the front face is the abstract judgment; click "which sessions" to expand into the concrete events
- Decaying — judgments unconfirmed for 30 days move into a folded section, so "it decays" is visible, not theoretical
- Every entry can be withdrawn via
✕. Two-step: first click becomes "confirm withdrawal?", the second click deletes — the deletion endpoint only accepts DELETE/POST, returns 405 on GET, so browser pre-fetch or back/forward replay never accidentally deletes - An ai-orb status pill speaks up: reading / refreshing / just-recorded (with badge) / failed
Memory model: structured and decaying
Memory isn't unbounded — humans forget, and new beliefs overwrite old ones:
| Mechanism | Rule |
|---|---|
| Strength | strength = hits × time-decay (forgetting curve) |
| Decay | Half-life default 30 days: how long without re-confirmation until strength halves |
| Overwrite | Same-topic new statements overwrite old (no near-synonym piling) |
| Cap | Default 20 entries; weakest evicted first |
| Budget | Injected by token budget (default 500), not "dump everything" |
Same-topic detection uses two channels: English entities (r2 / picx / main — in tech context these are basically topic tags; two shared = same topic) plus Chinese bigram overlap. Known boundary: purely-Chinese paraphrases with no shared entities ("reply in Chinese" vs "always Chinese, keep technical terms in English") fall through, recorded as two. To truly fix it you'd need embeddings — not worth the weight for one edge case. Plus the model sees existing memory when recording, so dedup is mostly the model's job; findSimilar is only the safety net.
Known limitation: heavy-paraphrase Chinese gets recorded twice
Same-topic detection runs on two channels — English entities (r2 / picx etc.; two shared = same topic) and Chinese bigram overlap (threshold 0.3). Pure-Chinese statements that differ by only one or two characters merge correctly (measured similarity 0.67–0.77), but restated freely slips through:
| A | B | bigram similarity | Outcome |
|---|---|---|---|
| Always run tests before committing | Always run the tests once before commit | 0.77 | ✅ merged |
| Must ask me before deleting anything | Before you delete something, come ask me first | 0.18 | ❌ recorded twice |
The cause: bigram only sees surface overlap, not synonymy. With a hard entry cap, duplicates push out useful memory.
Current workaround: when calling mirror_remember, reuse the existing phrasing; if you spot a duplicate, call mirror_forget to drop one.
Real fix is synonym merging (word vectors or letting the model self-dedupe) — not done yet. This limitation is locked down by a test (test/memory-core.test.mjs "known defect" case); improving it later will fail that test on purpose, reminding you to update this section.
Install
dsh plugin --profile web add @dsh-plugins/dsh-user-mirror
⚠️ Only works on profiles that provide storageDomain and webServer (i.e. web). A headless profile has neither — the plugin will sit in pending (waiting for services: storageDomain, webServer) and break boot. The first is the foundation for persistence; the second is the channel the Memory tab reads from.
On ai-orb vendoring
The sole runtime dependency is ai-orb (an SVG status pill, zero deps). It's declared in dependencies but is not bundled into the client — the client loads it from same-origin via import('/dsh-mirror/vendor/ai-orb/index.js'). The path is served by the host side, which exposes node_modules/ai-orb/src/ as the static route /dsh-mirror/vendor/ai-orb.
Why not bundle: the status pill is instantiated by multiple pages at once; a separate ESM module rides on HTTP/2 multiplexing without blocking first paint. And upgrading ai-orb is just pnpm update ai-orb — no hand-copied source to keep in sync.
How it works
- The model sees existing memory in the system prompt (sorted by strength, bounded by token budget)
- When you express a reusable judgment, the model calls
mirror_remember(text, kind, reason) - Normalize + run entity/bigram dual-channel matching to decide: overwrite same-topic old memory, or add new
- If capacity is exceeded after adding, evict the weakest entry
- Next time the system prompt is assembled, this entry is already in it
Tool calls are visible, so "what did it record and when" you see right in the conversation. The Memory tab also shows the reason (why recorded) plus strength, confirmation count, and last-confirmed time.
Configuration
Override in cordis.patch.yml:
- insert:
- id: dsh-mirror
name: dsh-user-mirror
config:
maxPreferences: 20 # Memory capacity cap (entries)
halfLifeDays: 30 # Half-life in days; longer = slower decay
maxTokens: 500 # Token budget for injection
sectionOrder: 160 # Position in the system prompt (smaller = earlier)
Tools
mirror_remember(text, kind, reason)— record a judgmentmirror_forget(text, reason)— withdraw an incorrectly-recorded one (matched by topic, exact wording not needed)
There is no query tool, by design. Memory is already injected into the system prompt; adding a query tool would be a second read path, eating tokens for nothing. v0.6 replaced the old mirror_preferences with mirror_forget — without withdrawal, a wrongly-recorded entry would either take half a month to decay or wait for the same-topic new statement to overwrite it (and Chinese paraphrases miss), polluting every system prompt turn in the meantime.
Privacy
- Preferences stay local (
~/.dsh/storages/) - No network, no upload
- Want to wipe? Delete the
dsh_mirrordomain
License
MIT © webkubor
Links
More in this category
volcengine/OpenViking#examples/dsh-memory-plugin★ 38231
OpenViking memory and context bundle for DeepSeek Harness: pre-step auto-recall and profile injection, session capture, `viking://` URI guarding, and recall/write memory tools backed by an OpenViking server.
vectorize-io/hindsight#coding-agents★ 24080
Hindsight, agent memory that learns: long-term project memory with auto recall and retain, knowledge pages, deep reflection, and per-repo memory banks.
agentscope-ai/ReMe#dsh★ 3496
Connects DeepSeek Harness to ReMe's local-first, self-evolving personal knowledge base: automatically captures completed main-agent conversations as user-owned Markdown memory, searches conversations and source material through reme_search with BM25, optional embeddings, and wikilink expansion, and schedules daily memory consolidation.
zilliztech/memsearch#MemSearch★ 2626
Shared Markdown memory for DSH and other coding agents, with automatic capture, pre-step context injection, searchable recall, and memory-to-skill self-evolution through a review panel.
vshulcz/deja-vu#extensions/dsh★ 900
Reads the session files twenty-two other coding agents on this machine already wrote — Claude Code, Codex, Cursor, VS Code Copilot Chat, opencode, OpenClaw, Hermes, Kimi, Cline, Zed and more — including sessions from before it was installed: six tools (deja_recall, deja_session, deja_blame, deja_fix, deja_how, deja_remember), a /deja command, and optional automatic recall added to the runtime context. Local BM25 index, no LLM, no embeddings, no network (dsh plugin --profile web add dsh-deja).
adoresever/graph-memory★ 626
Traceable, searchable cross-session memory for DeepSeek Harness — conversation knowledge as typed graph nodes (TASK/SKILL/EVENT) and typed edges.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.