← Back to home@quqxui

dsh-memgas

Long-term memory plugin for DeepSeek Harness (dsh): four-channel retrieval fusion and continuous memory evolution. Memory construction and retrieval are based on MemGAS (ICLR 2026).

Stars
25
Language
TypeScript
Created
Sep 5, 2026
Updated
Sep 29, 2026
GitHub repo

Introduction

dsh-memgas

npm npm CI license

English | 简体中文

Long-term memory for DeepSeek Harness (dsh). Your agent remembers this project's conventions, decisions and pitfalls across sessions, and keeps that memory organized as you work.

Retrieval builds on the multi-granularity association and adaptive selection method from the ICLR 2026 paper From Single to Multi-Granularity: Toward Long-Term Memory Association and Selection of Conversational Agents (MemGAS).

Install

dsh plugin --profile web add dsh-memgas               # from npm
dsh plugin --profile web add github:quqxui/dsh-memgas # or straight from GitHub

Both are equivalent. The repository ships a prebuilt single-file artifact, so a GitHub install needs no build step, no pnpm build authorization and pulls no runtime dependencies — the plugin uses nothing but Node built-ins.

Restart dsh and it is active. The package ships its own bundle patch, so there is nothing to wire up by hand. Zero configuration by default: no API key, no model download, no extra process.

What it does

Open a fresh session and the agent already knows. Verbatim output from two separate dsh processes (0.1.2-rc.1), translated from the Chinese original:

$ dsh --profile headless "Use memory_save to remember: this project deploys on port 8080,
                          and dependencies are managed with pnpm."

Saved to memory:
1. **Environment**: the project's deployment port is 8080.
2. **Convention**: dependencies are managed with pnpm (not npm/yarn).

# ——— process exits; a brand new session, no shared context ———

$ dsh --profile headless "What port does this project deploy on? What manages dependencies?
                          Answer directly."

From long-term memory:
- **Deployment port**: 8080
- **Dependency management**: pnpm

The second session called no tools and was never told to consult memory. The relevant entries were in its context before the model request went out.

This example asks for the save explicitly. In practice you do not have to: after each turn the plugin distils what is worth keeping into facts, summaries and keywords in the background.

Retrieval

Most memory plugins retrieve through one channel: vector similarity, or full-text match. dsh-memgas runs four in parallel and fuses them by rank:

ChannelWhat it isGood atCan be disabled
LexicalSQLite FTS5, identifiers kept intactPaths, package names, error codes, exact wordingNo
DenseVector similarityThe same question asked differentlyNo
GranularitySession / turn / summary / keyword retrieved separately, weighted by an entropy routerDeciding whether a whole session or one turn is the right unitYes
GraphPersonalized PageRank over the association graph, seeded by the baseline hitsMulti-hop links across sessionsYes

Fusion is Reciprocal Rank Fusion: rank-based, never score-based. Channel scores are not comparable (BM25 against cosine against PageRank mass), so a channel whose scores are miscalibrated can still only contribute through its ordering.

Baseline floor. At least half the final slots are reserved for the lexical and dense channels. The enhancement channels can add and reorder, never evict. The worst case equals plain retrieval.

Automatic degradation. Too few memories and only the baseline runs. An entropy router with no signal falls back to uniform weights. A graph too sparse to mean anything is skipped; a hub node gets its edges trimmed rather than the whole channel switched off. A channel that throws or overruns its budget is dropped and the rest still fuse. The whole path has a latency budget. A failed retrieval never affects the turn in progress.

/memory diag <query> prints every channel's raw results and the fusion that produced the final ranking, so each memory can be traced to the channel, rank and score that surfaced it.

Memory evolves

Memory is not a write-only log. Six background processes keep it in order, all on a queue, none on the critical path of a turn:

  • Associate — a new memory is clustered against the similarity distribution of existing ones and linked to those that genuinely relate
  • Reconcile — duplicates merge, superseded facts get a version chain, contradictions are kept as both sides with the conflict recorded
  • Reinforce — memories that were retrieved and actually used gain weight; memories repeatedly retrieved together become linked
  • Decay — untouched memories are archived (archived, never deleted, always restorable)
  • Abstract — once a cluster grows large enough it is summarized into a higher-level convention, sources left intact
  • Re-associate — the graph is refreshed as the store grows so it reflects the current distribution

A failure in any one process affects neither the others nor the conversation.

Tools and commands

Three tools for the model: memory_search, memory_save, memory_status.

/memory for you:

/memory status              store size, embedder, index and channel state
/memory search <query>      retrieve and list memory cards
/memory diag <query>        show each channel's results and the fusion
/memory list [n]            list this project's memories by importance
/memory forget <id>         archive one memory (reversible)
/memory restore <id>        bring an archived memory back
/memory pin <id>            exempt a memory from automatic decay
/memory review              handle memories waiting for confirmation
/memory export              export this project's memories as JSON
/memory purge --yes         permanently delete this project's memories

Configuration

Override by id in your profile's cordis.patch.yml. Every field is optional:

- id: memgas
  config:
    mode: hybrid             # lite (baseline only) | hybrid | memgas (heavier enhancements)
    k: 8                     # memories returned per search
    harvest: true            # distil memories from the conversation automatically
    recall: true             # inject relevant memories before a step
    evolve: true             # run the background maintenance
    confirmWrites: false     # true queues harvested memories for /memory review
    localModel: null         # see "Embeddings" below

The full field list is in the design document (Chinese).

Privacy and storage

The store is a local SQLite file per project scope under $DSH_HOME/memgas/, so a single project's memories can be exported or deleted on their own. Scope is derived from the normalized git remote, so a repository cloned on another machine maps to the same memories.

Nothing leaves your machine. Summarization and maintenance reuse the model you already configured in dsh: no extra API key, no third-party service. Credential-shaped content is stripped before anything is written.

Embeddings

The default embedder needs no download and works immediately. For semantic generalization, enable a local model:

pnpm add @huggingface/transformers
- id: memgas
  config:
    localModel:
      model: multilingual-e5-small
      mirror: https://hf-mirror.com   # optional

It loads in the background; the lexical embedder serves until it is ready and keeps serving if the load fails. Existing memories are re-embedded once it is available. It is deliberately not a dependency of this package — native modules like onnxruntime would make dsh plugin add hit pnpm's build-script approval gate, so it stays opt-in.

Other agents

memgas-mcp exposes the same store over MCP, so Claude Code, Codex and others can share it:

{ "command": "npx", "args": ["-y", "memgas-mcp"] }

It defaults to the same $DSH_HOME/memgas/ location. It has no model of its own: memory_ingest stores the transcript verbatim rather than summarizing, and there is no automatic harvesting or proactive recall.

Known limitations

  • The local embedding model has not been verified against real weights. The ONNX path was tested with an injected fake runtime; mean pooling and dimension handling have not run against an actual model. It is off by default.
  • memgas-mcp has not been connected to a real MCP client, only its JSON-RPC layer is verified.
  • In one-shot mode, distillation lags by one session. dsh --profile headless exits before the extraction model answers. The raw transcript is always stored synchronously and stays searchable; distillation is finished at the start of the next session. Long-running dsh web is unaffected.
  • Multiple workspaces. Scope is resolved per session from its working directory, but this path has not been exercised in a multi-workspace dsh web setup.
  • Default retrieval weights and thresholds are engineering judgements; they have not been tuned against any particular dataset.

Development

pnpm install
pnpm test        # vitest, 263 tests
pnpm run build   # tsc -b, also the typecheck

pnpm run build runs tsc -b and then bundles the plugin and core into dist/index.js with esbuild. That artifact is committed, so a change to the plugin has to be rebuilt and committed with it, or GitHub installs keep serving the previous build.

The source is three packages: packages/dsh-plugin (dsh wiring), memgas-core (storage, retrieval channels, evolution; no dsh dependency, published separately), memgas-mcp (MCP server).

Design trade-offs, decision records and open questions are in the design document (Chinese).

Citation

@inproceedings{xu2026memgas,
  title     = {From Single to Multi-Granularity: Toward Long-Term Memory Association and Selection of Conversational Agents},
  author    = {Xu, Derong and Wen, Yi and Jia, Pengyue and Zhang, Yingyi and Zhang, Wenlin and Wang, Yichao and Guo, Huifeng and Tang, Ruiming and Zhao, Xiangyu and Chen, Enhong and Xu, Tong},
  booktitle = {International Conference on Learning Representations (ICLR)},
  year      = {2026}
}

License

MIT