# DSH Third-Party Long-Term Memory Plugin (Unofficial)

**An unofficial, third-party long-term memory plugin for DeepSeek Harness (DSH).** Package id: `dsh-thirdparty-memory`. 中文名：DSH 第三方长期记忆插件（非官方）。

> **Not affiliated.** This is an independent open-source project. It is not associated with DeepSeek, Doubao, or their official teams, and carries no authorisation or endorsement from them. Product and company names are used only to describe compatibility.

One master switch, automatic extraction, five kinds of memory, a review inbox, keyword-relevant injection, a local memory page, and a single-file Windows manager — with English / 简体中文 / 繁體中文 UI.

> **Docs**: this file is the English root. Translations: [简体中文](README.zh-CN.md) · [繁體中文](README.zh-TW.md).
> Every document also ships as plain text: `README.txt`, `README.zh-CN.txt`, `README.zh-TW.txt`.
> **Deep dive**: [docs/tutorial.md](docs/tutorial.md) (also as `.txt`, with zh-CN / zh-TW versions in `docs/`).

```
Platforms : DSH Desktop (MSIX) · DeepSeek Harness portable (Electron) · CLI `dsh web`
Runtime   : Node.js 18+ (the exe ships its own Node 24)
Storage   : one local JSON file — no database, no vector store, no cloud, no telemetry
License   : MIT
```

---

## Why

AI conversations fail at memory in two opposite ways: it remembers **too little** (you repeat your preferences, project paths and past mistakes in every new chat) and **too much** (everything gets stuffed into the system prompt, burning tokens and confusing the model with irrelevant facts).

dsh-thirdparty-memory splits the difference: memories are **classified**, injection is **relevance-filtered**, and anything the AI infers about you has to pass a **review inbox** before it can ever reach the prompt.

---

## Features

| | |
| --- | --- |
| **Four categories** | `global` (always injected) · `project` (activates with its project) · `persona` (always injected) · `ephemeral` (this session only) · plus `general` |
| **Keyword-relevant injection** | Only entries matching the current message enter the prompt; the rest collapse into a numbered index. Typically 6.5 KB → 1.2 KB on unrelated questions. |
| **Multi-topic coverage** | Mention several things in one sentence and each topic gets its own representative entries, so one hot topic cannot eat the whole budget. |
| **Explicit recall** | Say "extract all memories about X" to drop the relevance threshold and get the whole batch (the budget widens too). |
| **Review inbox** | Auto-extracted persona facts wait in the inbox; they are injected only after you accept them. |
| **Self-directed writing** | The agent judges what is worth keeping (pitfalls, conclusions, personal facts) and writes it without asking — `judgeMode: model` (default). |
| **Update, don't duplicate** | The agent searches first and updates the existing entry: one fact, one entry, always the latest text. |
| **`[全局]` prefix** | Any memory whose text starts with `[全局]` / `[global]` is injected every turn. |
| **Token dashboard** | See exactly how many bytes/tokens each section costs, with warnings when always-on memories crowd the budget. |
| **Prompt preview** | One click shows the exact text that would be injected into a new chat. |
| **Single-file manager** | `dsh-memory-manager.exe` (~34 MB) embeds Node + everything; double-click, tray icon, no URL to remember. |
| **Zero runtime deps** | The server is `node:http`; there is nothing to `npm install`. |

---

## The five kinds of memory

| Category | Meaning | When injected |
| --- | --- | --- |
| 🌍 `global` | Language preference, hard prohibitions, workflow rules | **every turn** |
| 📁 `project` | Project-bound paths, pitfalls, scripts, specs | only while that project is active (named in the message, or `currentProject`) |
| 👤 `persona` | What you care about, what annoys you, taste, habits | **every turn** |
| ⏳ `ephemeral` | Scratch context for this conversation | same session only |
| 📝 `general` | Everything else | keyword relevance |

Each entry also carries **priority** (`high` must follow / `medium` recommended / `low` reference) and an optional **TTL** (never / N days).

> **Project memories are a gate *and* a filter**: a project entry becomes *eligible* when its project is active, and is still keyword-filtered before injection. Activating a project never dumps all of its entries into the prompt.

### What a `persona` entry has to look like

A memory system that profiles its user in the third person turns creepy fast. What separates a usable `persona` entry from a creepy one is not tone, it is **evidence** — a well-formed entry has four properties:

| Property | What it means | Why it matters |
| --- | --- | --- |
| **dated** | it carries the date of the observation | an undated observation cannot expire, and persona notes age |
| **scene-bound** | it names what actually happened | a scene can be confirmed; a character trait cannot |
| **anchored** | it keeps their own words or a concrete action | paraphrasing is where invention enters |
| **one line of reading** | judgement comes last and stays short | the evidence stays intact and the interpretation is clearly separate |

A badly formed entry has the opposite profile: adjective-first, undated, scene-free, nothing the reader could confirm or deny. A file full of those is what makes a person ask *"why are you keeping notes on me?"*

Template (structure only, no content): `YYYY-MM-DD  <what happened>  — "<their words>"  — <one line of reading>`.

If no scene and no quote come to mind, write nothing — an empty persona section beats an invented one.

---

## Install

### 1. Manager exe (easiest)

Run `dsh-memory-manager.exe`. The first launch asks for the UI language (English / 简体中文 / 繁體中文), extracts the bundled runtime into `%LOCALAPPDATA%\dsh-thirdparty-memory\`, starts the memory page and opens it in your browser. A tray icon stays around:

`Open memory page` · `Copy address` · `Storage location…` · `Open storage folder` · `Change port…` · `Restart/Stop service` · `Attach to DSH…` · `Language` · `Quit`

```
dsh-memory-manager.exe                     open the page, stay in the tray
dsh-memory-manager.exe --install           attach the plugin to every detected DSH profile
dsh-memory-manager.exe --start / --stop / --restart / --status
dsh-memory-manager.exe --port 7900         custom port (refuses if the port is taken)
dsh-memory-manager.exe --store D:\dsh-mem  custom storage folder (rules below)
dsh-memory-manager.exe --lang zh-CN        switch UI language
```

### 2. Script

```powershell
powershell -ExecutionPolicy Bypass -File .\attach.ps1                 # all detected profiles
powershell -ExecutionPolicy Bypass -File .\attach.ps1 -CreateWeb      # create a missing web profile too
powershell -ExecutionPolicy Bypass -File .\attach.ps1 -ProfileDir "C:\Users\you\.dsh\profiles\web"
powershell -ExecutionPolicy Bypass -File .\attach.ps1 -DataFile "D:/dsh-mem/memory.json"
```

### 3. Manual (four steps)

For a profile directory such as `%APPDATA%\com.yeagoo.dsh-desktop\harness\profiles\web`:

1. `mklink /J "<profile>\node_modules\dsh-thirdparty-memory" "<plugin dir>"` (or copy the folder)
2. add `"dsh-thirdparty-memory"` to `dsh.profile.bundles` in the profile's `package.json`
3. append to `cordis.patch.yml`:
   ```yaml
   - id: thirdparty-memory
     name: dsh-thirdparty-memory
     config:
       enabled: true
       judgeMode: model
       inbox: true
   ```
4. give the plugin its peer: junction `<plugin>\node_modules\@deepseek-ai\dsh-tools` → the host's `@deepseek-ai\dsh-tools`

**Restart DSH.** Bundles are read at boot only. Afterwards *Settings → Plugins* lists `dsh-thirdparty-memory`, and the memory page is up at <http://127.0.0.1:7891/>.

---

## Daily use

```
You:  remember: <one fact, plus the reason when the reason is not obvious>
      → stored immediately (explicit request), no review needed

You:  [全局] <a rule that has to hold on every single turn>
      → injected on every turn from now on; global entries are the priciest bytes

You:  <a task about topic A>, then <a task about topic B>
      → only the A and B entries are injected; everything else collapses into the index

You:  extract every memory about <topic>
      → explicit recall: relevance threshold off, budget widened, whole batch delivered

You:  forget entry 3 / update entry 9 to: <new text>
      → remember remove / remember update; numbering is shared with the page
```

Nothing to type for persona facts: the agent applies the four properties above by itself (`judgeMode: model`) and only records an entry once it has a date, a scene and your own words. If a note is wrong, delete it on the memory page — every deletion asks for a second confirmation.

---

## The memory page

<http://127.0.0.1:7891/> (served on `127.0.0.1` as well)

| Panel | What it does |
| --- | --- |
| 🧠 **Memory Hub** | All entries with category filters, pin/unpin, delete (two-step confirm); click an entry → workbench drawer |
| 📥 **Inbox** | Auto-extracted persona facts; accept / edit / reject, individually or in bulk |
| 📊 **Token Dashboard** | Byte/token split per section, budget warnings, library KPIs |
| 🔍 **Search** | Keyword search over content, tags and project |
| ⚙️ **Settings** | Master switch, auto extraction, **who decides what to remember**, index on/off, injection parameters, language, export JSON |
| 👁 **Prompt preview** | The exact prompt a new chat would receive |

---

## Configuration

Two layers, later wins: `cordis.patch.yml` (deployment) → memory page settings stored in `memory.json`.

| Key | Default | Meaning |
| --- | --- | --- |
| `enabled` | `true` | Hard switch. Off = no injection, no extraction, `add` fails. |
| `judgeMode` | `model` | `model` = the agent decides what to remember · `rules` = built-in regex fallback · `off` = never auto-record |
| `autoExtract` | `true` | Enables rule extraction when `judgeMode: rules` |
| `inbox` | `true` | Rule-extracted persona facts require review |
| `injectMode` | `relevant` | `relevant` = keyword-selected · `all` = everything |
| `maxBytes` | `8192` | Injection byte budget |
| `maxRelevant` | `6` | Max matched entries per turn |
| `perCluster` | `2` | Entries per topic when several topics appear |
| `minScore` | `6` | Relevance threshold (higher = stricter) |
| `recallMaxBytes` | `16384` | Widened budget for explicit recall |
| `maxRecall` | `40` | Max entries returned by explicit recall |
| `showIndex` | `true` | Include the numbered index of unselected entries |
| `indexChars` | `18` | Title length in the index |
| `currentProject` | `""` | Active project name (project entries become eligible) |
| `dataFile` | `""` | Memory file path, empty = `<plugin>/data/memory.json` |
| `pageServer` / `pageHost` / `pagePort` | `true` / `127.5.0.1` / `7891` | Memory page process |

### Storage rules

The manager enforces them whenever you change the folder (`Storage location…` or `--store`):

1. the **drive must exist**;
2. every folder name must be **ASCII** (`A-Z a-z 0-9 space _ - .`);
3. the target folder must be **empty** (or already your current store);
4. a bare drive root is refused.

If the old folder still holds memories the manager asks **copy them over?** — *Yes* copies, *No* switches only, *Cancel* aborts. After a switch, `dataFile` is written into every attached profile's `cordis.patch.yml`, so plugin and page keep reading the same store.

---

## Data

One JSON file (`memory.json`, version 2):

```json
{
  "version": 2,
  "entries": [
    {
      "id": "mem-xxxx", "content": "<the fact, one self-contained sentence>",
      "category": "global", "project": "", "priority": "high",
      "tags": ["<tag>"], "source": "user", "status": "active",
      "pinned": true, "expiresAt": null, "sessionId": "",
      "createdAt": "2026-01-01T00:00:00.000Z", "updatedAt": "2026-01-01T00:00:00.000Z"
    }
  ],
  "settings": { "enabled": true, "lang": "en" }
}
```

`status`: `active` · `pending` (inbox) · `rejected`. Version-1 files migrate automatically (a `[全局]` prefix becomes `category: "global"`).

---

## HTTP API

| Method | Path | Purpose |
| --- | --- | --- |
| GET | `/` | memory page |
| GET | `/api/state?q=` | state + entries + dashboard |
| GET | `/api/export` | download JSON |
| POST | `/api/entry/add` · `/update` · `/delete` | entry CRUD (one delete at a time, by design) |
| POST | `/api/inbox` · `/api/inbox/bulk` | accept / reject |
| POST | `/api/settings` | write user-layer settings (`{reset:true}` restores defaults) |
| POST | `/api/preview` | render the injection for a given `q` |

Loopback only; POST requires `application/json`, which blocks cross-site form posts.

---

## Troubleshooting (short list — full table in the tutorial)

| Symptom | Cause / fix |
| --- | --- |
| Nothing happens after install | DSH was not restarted; bundles load at boot only |
| Plugin missing from *Settings → Plugins* | run `attach.ps1` again and read its output |
| Memory page blank | the page process died; run `dsh-memory-manager.exe --start` |
| `Port 7891 is used by another program` | another app owns the port: `--port 7900`, or *Change port…* |
| The agent searches the plugin folder instead of just answering | the plugin was not loaded, so no memory tool existed; restart DSH |
| Defender flags a scheduled task | never self-install via Task Scheduler (heuristic trojan detection); use the exe's tray or the Startup folder |
| Memories look like regulations | switch to `judgeMode: model` and let the agent write persona notes; the inbox shows what it inferred |

---

## Privacy

Everything stays in one local JSON file. No network calls, no upload, no telemetry. Memories do enter the model context — never store secrets. Destructive actions require two confirmations, and bulk deletion is intentionally not implemented: delete entries one by one.

---

## Compatibility

| Host | Status |
| --- | --- |
| DSH Desktop (MSIX, desktop 0.2.x) | tested |
| DeepSeek Harness portable (Electron) | tested (`remember list` / `search` verified) |
| CLI `dsh web` | composition verified via `--dump-config` |

The plugin deliberately has **no `schemastery` dependency** (its `Config` implements the Standard Schema `~standard` contract), so it survives across DSH release lines; the only peer it needs is `@deepseek-ai/dsh-tools`.

---

## Build from source

```bat
:: rebuild the single-file manager (needs .NET Framework 4.x csc.exe + a node.exe)
powershell -ExecutionPolicy Bypass -File build-single-exe.ps1

:: package release\dsh-thirdparty-memory-<version>.zip (includes a personal-path leak check)
powershell -ExecutionPolicy Bypass -File build-release.ps1

:: regenerate the plain-text docs after editing the Markdown
node tools/md2txt.mjs README.md README.zh-CN.md README.zh-TW.md docs/tutorial.md docs/tutorial.zh-CN.md docs/tutorial.zh-TW.md
```

---

## License

MIT — see [LICENSE](LICENSE). The bundled Node.js runtime is redistributed under its own MIT license (see `docs/NODE-LICENSE.txt`).
