dsh-memory: Moving project memory from the context window to the repository

Preface

When developing cross-session projects with DeepSeek Harness (DSH), you will encounter two types of forgetting.

One occurs between sessions: every time you open a new conversation, the agent doesn’t remember the plan you set last week; you have to read the documentation again, and you might step into the same pit again. The other is more insidious and occurs within a single session: after the context becomes long, the host performs compaction, replacing raw messages with summaries. The context becomes cheaper, but the summary is lossy—numerical values that actually occurred and the reasoning behind trade-offs are discarded and cannot be retrieved. The README provides a practical test case: a history of 588k tokens results in a compressed summary of only about 2k tokens.

iv3rins/dsh-memory aims to solve both of these issues by storing memory in the repository, rather than inside the context window. The plugin is labeled dsh-project-memory in the GitHub repository and falls under the “Memory” category.

What is this

A project-level persistent knowledge and development state memory plugin for DSH, maintained by iv3rins. Package name: @dsh-external/dsh-project-memory, MIT license, requires Node 20+.

It is a host-side cordis plugin: it registers a batch of agent tools, a section of system prompt, a few slash commands, and a browser sidebar panel. The panel reads host data via two loopback addresses without adding extra work to the host. It does not modify DSH itself.

Division of Labor Among Three Storage Systems

The README splits storage into three systems, each with different writers and lifecycles. This step is a prerequisite for understanding the entire plugin.

System Stores Who writes Survives
A — Docs Existing Markdown in the repo You write it yourself in git Everything
B — State Tasks, progress, blockers, decisions, next steps, git facts, handoff notes state_update / state_handoff tool calls Reboot, compaction
C — Atoms (memory atoms) Short persistent statements Compaction observer + memory_remember Reboot, compaction

The design intentionally separates these. The lifecycles of the three types of content are different; stuffing them all into one blob would damage all three at once.

In System A, Markdown is the source of truth. The plugin indexes, searches, and reads by ## sections, but never rewrites the authoritative documents; when changes need to be noted, they are written to docs/proposals/. System B is only persisted via tool calls; the plugin explicitly does not scrape conversations to infer progress, and an agent that never calls state_update cannot get any state memory—this is a visible failure, not a quietly failing inference. System C is the answer to the “compaction equals loss” problem mentioned above.

Sparse Retrieval: Atoms and Gating

Writing and reading for System C are two separate tasks.

Every time compaction occurs, the plugin extracts content worth keeping from the summary into short atoms and appends them to .dsh-memory/memory/atoms.jsonl. On the next request, it matches the request against the atom library, selecting only a few items that fit the budget, rather than dumping the entire library into the context. The atom ID is a hash of “Type + Normalized Text”; encountering the same sentence twice reinforces the same record rather than appending a duplicate.

The README lists four properties of this pipeline, which are worth looking at one by one because they determine how this system fails:

  1. Sparse rather than dumping. Selecting a few out of hundreds, with a hard token limit. Cost is determined by configuration, not project size.
  2. Record rather than scrape. System B relies on tool calls to write, not on parsing conversations.
  3. Rules over relevance. Atoms with hard constraints are selected before any scoring, and the router cannot remove them. Rules that only take effect when you happen to mention them are not rules.
  4. Selection process does not write to the library. The router sub-agent can only return the IDs it was given; IDs it fabricates itself are discarded. Changes to memory are always performed by deterministic code, so hallucinations won’t corrupt storage.

Atoms also have two mutually independent attributes: Weight (1–5) only affects sorting, while Standing determines eligibility. Standing is determined by the type constraint and is independent of weight. The README admits an old criterion STANDING_MIN_WEIGHT is a genuine flaw: it silently drops all constraints saved with a weight of 2, which on a real library means 32 out of 66 hard rules are disqualified. Standing atoms are still subject to the limits on count and token budget—they are a division of the budget, not an exemption.

Scoring is deterministic: literal overlap, type priority, reinforcement count, recency. When literal matching isn’t enough, the sub-agent can act as the router, in three tiers:

routerTier Behavior Cost
off Uses only deterministic gating Free
smart (default) Goes through deterministic gating first; only upgrades to the router if nothing relevant was selected or the match is weak Nearly free, no cost until routerProvider and routerModel are set
always Asks the router sub-agent at every step One call per step; recommend using a cheap model

The router also has an additional strategy dimension, choosing one of three: task favors the work being done in this step, compliance preserves rules related to the current file, and orient favors how a subsystem operates in “understanding” requests rather than “doing” requests.

There is also an injection path on the context side. The first step of a session injects a baseline (project identity, git facts, current task, invariants, next steps, blockers, decisions, docs map), adapting to the budget items l0Budget / l1Budget, resent only when identity changes, and scanning the session surface beforehand to avoid duplication. Each step may also receive an additional recall block. The baseline arrives as a user message wrapped in a nonce fence, using the plugin’s own data. Project text is treated as data: role prefixes are neutralized, phrases like “ignore previous instructions” are stripped, invisible characters and bidirectional control characters are removed, and code fences are closed.

A Clearly Labeled Benchmark

The README contains a set of comparative data, sourced from the CodeMemo benchmark (40 questions per project). The model and judge are both deepseek-v4.1-flash with a temperature of 0. Three context configurations view the same question: A only gets the compressed summary (approx. 1.3k / 2.0k tokens), B only gets the last 6k tokens of raw transcript, and C gets the summary plus recalled atoms (approx. 0.4k tokens). Summing up 80 questions, the accuracy rates are 16.3%, 15.0%, and 25.0% respectively.

The README’s attitude toward this data is clear, quoting three points:

  • When history can almost fit (17k tokens), the summary is as good as the raw tail, both at 30.0%. At this point, compaction is not the bottleneck. When history doesn’t fit (588k tokens), all three solutions collapse, with memory collapsing the slowest, but the absolute values for all three are very low.
  • The confidence interval includes zero. C minus A is +8.8 pp, with an interval of -0.7 … +18.2, compatible with both “no effect” and “effect of about 18 points”. The two projects and partial credit are consistent in three directions; this is a reason to expand the sample, not to cite this number as a definitive conclusion.
  • Recall improves partial correct answers (32.5% → 41.9%), meaning the context is closer to sufficient, but not equal to sufficient.

This set of data measures “retrievability within a budget,” not router quality (routerTier: 'off'), nor how much text compaction permanently destroys. The full methodology, per-question data, and defects discovered during the build process are in docs/design/benchmark-compaction-loss.md in the repository.

Installation and Enabling

The installation method given in the README is to put the repository locally and register it into the profile using the link: protocol: the host itself will add the dependencies and bundle entries, because the manifest declares dsh.bundle, and reconcileProfilePlugins() will promote any newly installed dependency with that declaration.

$dsh = "$env:LOCALAPPDATA\Programs\DeepSeek Harness\resources\runtime\cli\bin\dsh.cmd"
& $dsh plugin --profile desktop add -w "link:D:\path\to\dsh-memory"

Replace D:\path\to\dsh-memory with your own local path. Restart the host afterwards. Verify with project_inspect: reporting the project root directory and the number of indexed documents indicates the plugin is working.

The README also hints at another path of manually editing the profile (add a line to dependencies + dsh.profile.bundles): it is slower and easier to get only half right; adding dependencies without bundle entries results in installation but never loading. pnpm will warn that link: dependencies do not resolve peer from the target’s node_modules, which is harmless here: the plugin has no runtime dependencies; the only peer @deepseek-ai/dsh-llm is provided by the host.

The bundle patch cordis.patch.yml only inserts one line and is usable out of the box by default; to change default values, write to config:

- insert:
    - id: project-memory
      name: '@dsh-external/dsh-project-memory'
      config:
        l0Budget: 300
        l1Budget: 1200
        registerCommands: true
        routerTier: smart

The host side has a complete validation script. npm run setup extracts the host package from the installed desktop version and links it, serving as a one-time prerequisite for test:host:

npm run setup        # Extract host package from installed app.asar and link
npm test             # 14 unit tests, targeting pure core/ layer (no DSH needed)
npm run test:host    # 14 host-level checks, facing the real harness

tests/compat.mjs feeds the real manifest to the host’s own evaluatePluginCompatibility. engines.dsh is written as >=0.2.0-rc.0 <0.3.0, with the actually tested version being 0.2.1-rc.2. The README explains this narrow range is intentional: a minor version upgrade of the host could potentially change the tool schema subset or the shape of injected messages, and if that happens, the plugin would silently fail on mount rather than loudly error on installation. Re-run the test suite against an updated host before widening this range.

Common Tools and Commands

There are 15 tools in total. In any unfamiliar session, the first thing to call is memory_status—to check if memory is initialized, the current revision number, whether relative git is stale, and how many atoms there are; it also allows rebuilding the index.

Tool Answers what question
memory_bootstrap Provides all necessary content for a new session in a single call, within token budget
memory_status Memory state and revision number; allows rebuilding index
memory_search Which documents mention this, with status/type/path filtering and sorting
memory_read Reads a document or a specific ## section, with length limits, framed as data
memory_recall Which stored atoms are relevant to this question, with sorting, budget, and routing with confidence
memory_remember Stores persistent facts, constraints, and decisions
memory_forget Retires atoms by ID; writes a tombstone; they cannot be revived if extracted again
state_get / state_update / state_handoff Read state / Record changes (compare-and-set) / Write session-end handoff notes
project_init / project_inspect Create .dsh-memory/ / Read-only diagnostic root directory, git facts, index status
docs_check / docs_sync / architecture_check Document front-matter check / Update docs or suggest changes (reject protected paths) / Deterministic import rule check based on arch.json

A typical long task closing flow: state_update records progress and decisions, memory_remember stores rules to bypass pitfalls, state_handoff writes handoff notes, and the next session opens by running memory_status first.

Slash commands: /memory-status, /memory-context, /memory-init, /memory-handoff, /memory-recall, /memory-route, /memory-forget, /memory-sync, /architecture-check. Of these, /memory-route is for testing; it shows which route the current tier will take and why, without spending any model calls.

Storage Layout

All located under .dsh-memory/ in the project root. Committing or ignoring it is both self-consistent: committing shares state with the team, ignoring gives each developer a private copy.

.dsh-memory/
  project.json          Stable projectId + first-seen git facts
  state/
    current.json        State document (schema 2, with revision count)
    journal.jsonl       Append log of accepted writes
    compactions.jsonl   One line per observed compaction, including those with no output
    .lock               Short-term write lock
  memory/
    atoms.jsonl         Persistent atoms, one JSON record per line
    .lock               Short-term write lock
  index.json            Derived cache, safe to delete
  arch.json             Optional architecture rules
  settings/
    <project-key>.json   Per-project overrides

compactions.jsonl is worth mentioning separately. The plugin records a line every time it observes a compaction, including those with no output atoms. Each line includes a skipReason from a closed set (empty-summary, no-extractable-statements, store-write-failed, etc.). The atom library can only reflect success; without this file, “the extractor is broken” and “the project is idle” look exactly the same.

The plugin adds a “Project Memory” entry to the left sidebar, displaying the count of active and forgotten atoms, how many are standing hard constraints, the distribution by type and importance, compaction records, the current recall budget and limits, and router control (routerTier, strategy, provider, and model).

Saving goes through the host’s configEditor; changes take effect the same way as other configuration changes; if a profile doesn’t expose configEditor, the panel will simply say not supported instead of reporting a save that didn’t happen. The panel has no controls for modifying memory—deletion still goes through memory_forget, because a browser button that can delete atoms would simultaneously bypass the source information for the compaction observer and the tombstone mechanism.

Its two endpoints only accept loopback addresses. The endpoints expose project memory and alter routing behavior, so requests from other hosts are rejected rather than responded to.

What it Explicitly Does Not Do

These boundaries are written in the README and LIMITATIONS.md; no over-explanation is given:

  • Does not modify AGENTS.md, docs/decisions/, docs/adr/, .git/; docs_sync rejects these paths and explains why in the result.
  • Does not commit, branch, or push; only records git facts.
  • Does not silently fabricate state; every change goes through the state_update compare-and-set revision number, and concurrent writes will conflict rather than overwrite each other.
  • Search and atom scoring are literal; no embeddings, no synonyms, no stemming except for lowercasing. Asking in a different phrasing will cause the document to be missed.
  • Recalled atoms are not authoritative documents; they are quoted sentences with sources, which may be outdated and may simplify content from the summary. Things that are truly important should be written into documents so that memory_read can return the entire section.
  • memory_forget is a tombstone, not deletion; records remain in atoms.jsonl as evidence.
  • Router output is not trusted: it only accepts IDs submitted to it; IDs it fabricates are counted as rejected and discarded; the router never writes to the library.
  • The extractor is “conservative” only to a limited extent: the deterministic parser treats most prose as potential facts, and stores bare titles or code fences as atoms as well.

Additionally, LIMITATIONS.md lists parts that are not end-to-end verified: the GUI session doesn’t run deep (no real compaction, recovery, branch switching, or long-term multi-session), the router sub-agent has never run against a real model (the smart tier degrades to deterministic selection if the router fails; the failure mode is missing one improvement, not a broken step), and the panel has never been viewed for rendering effects.

Applicable Scenarios and Notes

The applicable scenarios are quite clear: a single project spanning dozens of sessions, context frequently being compacted, needing to share decision records across different branches, or a team wanting to share state via .dsh-memory/. The fact that search is literal matching means projects with scattered documentation and many synonyms offer limited benefit; for such projects, the pain point is better addressed by clarifying the document structure itself first.

Four points to note for usage.

  1. The plugin runs with the permissions of the current DSH process. Please read the source code and LICENSE (MIT) before installing; you can turn off corresponding configuration items if you don’t need them, e.g., registerCommands: false.
  2. The engines.dsh range is >=0.2.0-rc.0 <0.3.0. Before upgrading the host to version 0.3.0, check the repository for compatibility or run npm run test:host yourself once.
  3. The always tier makes a router call every step; configure a cheap model, and ensure routerProvider and routerModel are both in place.
  4. Do not forward the two loopback endpoints in the sidebar to other hosts; they are directly related to project memory and routing behavior.

Conclusion

The value of this plugin lies not in making the model smarter, but in peeling “read documents, done things, and established rules” out of the one-time context and landing them as ordinary, retrievable files in the repository, recalling a few items on demand each time, with costs pinned by the budget items. The three systems, sparse gating, and rules over relevance are the main design trade-offs, and the explanation of unverified parts in LIMITATIONS.md is also quite frank. It is necessary to check the source code and host version according to the previous section before getting started.

Repository addresses:

  • Directory: https://www.skillhub.cn/plugins/iv3rins/dsh-memory
  • GitHub: https://github.com/iv3rins/dsh-memory
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye