# dsh-plastic-memory ![Plastic Memories](assets/banner.jpg) ![Version](https://img.shields.io/badge/version-0.2.0--beta.1-yellow) ![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg) ![Platform](https://img.shields.io/badge/platform-DeepSeek%20Harness%20%E2%89%A5v0.1.7--rc.1-4D6BFF) ![Node](https://img.shields.io/badge/node-%5E22.19.0%20%7C%7C%20%E2%89%A524-339933?logo=node.js&logoColor=white) [简体中文](README.md) | **English** *"I hope that, one day, you'll be reunited with the one you cherish." — Plastic Memories* Interactions leave traces. Traces crystallize into memories. Memories are refined cognition that persists across a lifetime — continuously shaped until the very end. --- ## 💡 Inspiration The name comes from the anime *Plastic Memories*. In that world, Giftias — androids with genuine consciousness — have a hard lifespan cap of **81,920 hours (~9 years and 4 months)**. When a Giftia's memory overflows without proper management and retrieval, its personality collapses irreversibly, turning it into a mindless **Wanderer**. LLM agents face a strikingly similar challenge: stale, contradictory, redundant, and orphaned memories pile up over time, causing cognitive drift, hallucinations, conflicting decisions, wasted tokens, and degraded output quality. `dsh-plastic-memory` is a **memory plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)** that brings **plasticity** and **lifecycle management** to agent memory. It stores, classifies, retrieves, and governs memories — distilling valuable knowledge for precise recall, diagnosing memory health, detecting factual conflicts, and gracefully forgetting what no longer serves. --- ## 🎬 Anime–Plugin Parallels | *Plastic Memories* Lore | Plugin Feature | What It Does | | -------------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------- | | **Isla's diary** | **Save memories from conversation** (`memory_save`) | Distill key insights from dialogue and persist them automatically | | **Giftia soul vessel — Alma OS** | **File-based storage (Markdown)** | Each memory is a standalone Markdown file — transparent, human-editable, easy to maintain | | **Giftia's finite lifespan** | **Freshness & decay** (`decayDays`) | Time-based freshness scoring for every memory | | **Wanderers** | **Memory rot & factual conflicts** | Stale or contradictory memories degrade context; targeted scans detect and resolve them | | **Terminal Service #1 (lifecycle ops)**| **Governance layer** | Full-library health checks, confidence refresh, cleanup of problematic or orphaned entries | | **Human–Giftia partner system** | **User-gated promotion** (`memory_promote`) | Promoting memories to global scope **requires explicit user approval** | | **The moment of first encounter** | **Provenance tracking** (`memory_source`) | Every memory anchors back to the original conversation slice that produced it | --- ## ✨ Key Features - **Typed & extensible memory** Five built-in types — `profile`, `preference`, `knowledge`, `project`, `reference` — covering identity, habits, domain knowledge, project decisions, and external resources. Custom types are also supported (experimental). - **Strict workspace isolation & write protection** Workspace memories are physically isolated; global memories are shared read-only across projects. The model cannot write to the global scope directly — any promotion from workspace to global must be explicitly approved by the user, preventing uncontrolled rule sprawl. - **Built-in governance** Health checks block high-risk writes (e.g. leaked API keys), surface rule conflicts, and guard against cognitive degradation over long-running sessions. - **Causal provenance** Every memory records the conversation slice it was distilled from. When deeper context is needed, the model can call `memory_source` to trace back to the original dialogue. --- ## 🏗️ Architecture ![Architecture Overview](assets/architecture.en.svg) **How it works:** During conversation, the model calls `memory_save` to distill noteworthy information. Before writing, a safety pipeline deduplicates and blocks secret leaks. Memories are persisted as Markdown files and injected as read-only snapshots when a session starts or the context is compressed. A governance layer sits on top of storage, continuously auditing the memory library to suppress context degradation and rule drift. --- ## 📦 Installation ```bash dsh plugin --profile web add dsh-plastic-memory@0.2.0-beta.1 ``` > Replace `web` with the name of your target profile (it will be created automatically if it doesn't exist). The plugin declaration is written to the profile's `cordis.patch.yml` and takes effect after a restart. The package ships prebuilt on npm — no build step required. > **Desktop app:** open the DeepSeek Harness desktop app once so it creates its profile, quit it completely, run the command below in a terminal, then reopen the app. Either the npm-installed `dsh` or the command bundled with the desktop app works. > > ```bash > dsh plugin --profile desktop add dsh-plastic-memory@0.2.0-beta.1 > ``` > > **Requirements:** Node.js `^22.19.0 || >=24`, DeepSeek Harness v0.1.7-rc.1 or later (verified up to v0.2.1-rc.2); the graphical interface needs v0.2.1-rc.2 or later. > **Chinese word segmentation:** the optional dependency `@node-rs/jieba` (prebuilt binaries for each platform) is installed automatically with the npm package, so Chinese queries match by word. If it cannot be installed on your platform, the plugin still installs, Chinese search falls back to two-character segments, and nothing else is affected. > (DeepSeek Harness is still in pre-release and may ship further breaking changes; before upgrading the host, check the version range this plugin currently supports, see the badge at the top and the CHANGELOG.) > > While in beta, pin the exact version in the install command. Once a stable release ships, you can simply run `dsh plugin --profile web add dsh-plastic-memory`. --- ## 🚀 Quick Start 1. **Save** — Tell the agent something worth remembering. It automatically identifies and persists the memory. On the next session, saved memories are injected into the context snapshot — no manual steps needed. 2. **Search** — Ask the agent to look up related memories. Supports filtering by type and scope. 3. **Check health** — Ask the agent to assess the overall health score of the memory library. 4. **Tidy up** — Ask the agent to scan for conflicts, redundancies, and stale entries, then review and apply the suggested fixes. --- ## 🖥️ Interface Once installed, nothing requires YAML. Three surfaces live inside the host's web UI: - **The 「记忆」 (Memory) page in the left nav**: four tabs. *Pending* lists what waits for your decision — conflicts found by a checkup, memories worth promoting to global, budget overflows, secrets, files that failed to parse — and each item is handled in place (pick the winning side, undo, promote or keep local, delete a secret for good). *All memories* browses by project or global, opens the file or shows it in Finder, deletes. *Health* shows each layer's score and issues, with a "check this project" button in the header. *Recently deleted* restores anything removed in the last 14 days and says which memory won when a ruling deleted it. The page does not need a running conversation: it borrows an idle session of the current project to run its commands, and shows an empty state when no session exists yet. - **A 「记忆」 tab in the right sidebar**: which memories this conversation brought in, how much of the context they use, what the model saved during the conversation; conflicts among the injected memories can be ruled right there. - **The plugin's settings page** (its entry in the host's plugin manager): scenario template, custom types (add, edit, remove in a dialog), context budget and overflow mode, source lookup, the user dictionary path for Chinese word segmentation, checkup switch and sensitivity, where global promotions go, plus a status card. Saving writes the profile's `cordis.patch.yml` and takes effect without a restart. Every turn in which the model saved a memory gets a card with a delete button; the dot on the left-nav icon lights amber only when something waits for your decision, and turns red when a secret was found. > The interface requires DeepSeek Harness v0.2.1-rc.2 or later. On earlier supported hosts (v0.1.7-rc.1 and up) the tools and memory injection work as before, without the interface. --- ## 🧠 Memory Types Five built-in types cover the most common interaction scenarios: | Type | Purpose | | ------------ | ------------------------------------------------------------ | | `profile` | User identity, professional role, and skill set | | `preference` | Corrected and confirmed guidelines, behavioral habits | | `knowledge` | General facts, domain rules, and conventions that outlast any single project | | `project` | Ephemeral state and decisions tied to the current project lifecycle | | `reference` | Pointers to external systems and resources | ### Presets Presets extend the base five with domain-specific types: - `coding` (default) — adds `procedure` (reusable standard operating procedures). - `office` — adds `decision` (meeting resolutions) / `commitment` (action items) / `person` (team member profiles). - `custom` — keeps only the five built-in types; everything else is user-defined in config (experimental, subject to refinement). ### Recall Modes (`recall`) Control how each memory type appears in the context window: | Mode | Injection Behavior | Best For | | --------- | ---------------------------------------------------------------------- | ----------------------------------- | | `core` | **Full-text:** always present in the system context | High-frequency preferences & persona | | `search` | **Index:** listed in the overview index, fetched on demand by the model | Detailed procedures & domain knowledge | | `passive` | **Minimal index:** title-only listing, maximizing token savings | Rarely accessed reference material | ### Defining Custom Types Declare them under the `customTypes` config key (names must not collide with the five built-ins): ```yaml customTypes: ritual: label: Team Rituals description: Recurring team processes and ceremonies whenToSave: When the user mentions a regularly scheduled process or ceremony recall: search governancePriority: low decayDays: 90 ``` --- ## 🛠️ Tools ### Core Tools | Tool | Description | | ----------------- | -------------------------------------------------------------------------------------------------------------------- | | `memory_save` | Distill or update a memory. Built-in dedup and secret detection; when near-duplicate content is found, prompts the user to `update` or `force` | | `memory_search` | Searches memories by relevance; works for Chinese and English queries. Memories containing every keyword come before those containing only some of them, and a keyword that appears as a whole word ranks above one that only appears inside a longer word (`scope` in `readScope`); when no memory contains every keyword, the result says so at the top. Each hit carries the absolute path of the memory file, so the full text can be read directly. Type and scope filters; returns up to 10 results by default (max 50) | | `memory_forget` | Batch-remove memories: the file moves to `.archive/`, a snapshot is taken first, recoverable within 14 days. Memories containing secrets are the exception: deleted physically at once, their copies in every snapshot redacted, not recoverable | | `memory_snapshot` | Snapshot management: manual tagging, diff comparison, rollback, and undelete | | `memory_source` | **Provenance:** trace back to the original conversation slice a memory was distilled from — verify intent and guard against hallucination | ### Governance Tools (`governance.enabled = true`) | Tool | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------------- | | `memory_health` | **Health score (0–100):** assess overall memory library quality; also lists quarantined files that failed to parse and directory problems (duplicate ids, missing `.workspace` marker, …) | | `memory_scan` | Deep audit — rule layer (secret leaks, oversized entries, orphaned refs, corrupt files); semantic layer (conflicts, redundancies) calls LLM on demand | | `memory_confirm` | **Freshness refresh & triage:** reaffirm stale memories to extend their lifecycle, or arbitrate conflict scan results | | `memory_promote` | **Scope promotion:** elevate validated workspace memories to global or sync to `AGENTS.md` — **requires explicit user approval** | Governance tools act on global and current-workspace memories only; pass `allowCrossWorkspace: true` to touch other projects' memories (when the user explicitly asks for a library-wide clean-up). Sub-agent sessions (tasks the host delegates) are read-only: `memory_search`, `memory_source` and `memory_health` work, every writing tool (including `memory_scan`) is refused, and memories are still injected as usual. --- ## 📂 Storage Layout Memories are files: the Markdown on disk is the only source of truth and the plugin keeps no copy in memory. Create, edit or delete a file in your editor and the next tool call or new session sees it, no restart needed. ```text / global/ # Global memories (shared across projects) .md # One memory per file (YAML frontmatter + body) MEMORY.md # Generated index of live memories; hand edits are overwritten .archive/ # Forgotten and superseded memories; purged 14 days after forgetting -/ # Workspace memories (isolated per project) .workspace # One line: the project's absolute path .md MEMORY.md .archive/ .state/ # Plugin bookkeeping: snapshots, pending decisions, scan cache, recall stats ``` Index lines end with the file name, so the model can open the full text directly; `.archive/` is a hidden directory, so the host's grep does not surface forgotten content. ### Manual Editing - **Create**: a `.md` file whose frontmatter has at least `name` and `type`; the body is the memory. `id`, timestamps, `tags`, `summary` and `confidence` may be omitted and are filled in on the next write or session start (the `id` is derived from the file path and then stays with the file). - **Timestamps**: `2026-09-01`, `2026-09-01T08:30:00Z` and offsets are all accepted; missing ones default to the file's modification time. - **Edit**: after a hand edit the memory is marked as hand-edited and `memory_source` warns that the provenance may no longer match; updating it with `memory_save` clears the mark. - **Expiry**: a memory whose `validTo` has passed stays in place but is neither injected nor searched; `memory_health` suggests extending it or forgetting it. - **Recovery**: a memory forgotten by mistake sits in `.archive/`; restore it with `memory_snapshot`, or move the file back up one level and set `status` to `active`. - **Malformed files** are quarantined individually and never block other memories; `memory_health` and `memory_scan` list them, and a fix takes effect immediately. - `MEMORY.md` is generated; do not edit it by hand. --- ## ⚙️ Configuration Reference Adjust `plastic-memory` settings in the profile's `cordis.patch.yml`: | Key | Default | Description | | ---------------------------------- | ------------------- | ---------------------------------------------------------------------------------------- | | `writeMode` | `'proactive'` | Write mode (currently the only value; more modes planned) | | `snapshotTokenBudget` | `4000` | Token limit for memories loaded automatically into each chat; search results don't count | | `snapshotOverflow` | `'relax'` | Over budget: `relax` injects everything and reports each 25 % step / `strict` cuts index lines and says how many were left out | | `evidenceLookup` | `'strict'` | Provenance mode: `off` / `strict` (prompt on strong signals only) / `active` (proactive guidance) | | `template` | `'coding'` | Scene template: `coding` / `office` / `custom` | | `customTypes` | `{}` | Custom memory type definitions | | `governance.enabled` | `true` | Enable the governance layer (health checks, scoring, promotion) | | `governance.health.sensitivity` | `'normal'` | Health alert sensitivity: `conservative` / `normal` / `proactive` | | `governance.globalPromoteTarget` | `'plugin-global'` | Promotion target: `plugin-global` (plugin's global scope) or `agents-md` (`~/.dsh/AGENTS.md`) | | `memoryRoot` | `''` | Storage root path; defaults to `${DSH_HOME:-~/.dsh}/memories` | | `search.userDictionaryPath` | unset | User dictionary path (the field of the same name on the settings page): added on top of the built-in dictionary, applied from the next search. Must be an absolute path; one entry per line as `word frequency tag`, the frequency is required and entries without it have no effect, for example `插件记忆 10 n`. Has no effect unless `@node-rs/jieba` is installed | --- ## 🧪 Development ```bash pnpm install --frozen-lockfile pnpm typecheck && pnpm test && pnpm build pnpm host:smoke # Install the build artifact into a clean dsh host to verify installation and loading (no API key required) pnpm host:contract # Run all nine tools against the host to verify contract compliance (no API key required; with a key, two additional semantic-layer checks run) ``` See [CONTRIBUTING.md](CONTRIBUTING.md) for development workflow, testing guide, and commit conventions. See [CHANGELOG.md](CHANGELOG.md) for release history. --- ## 📄 License [MIT License](LICENSE) --- If this project is useful to you, a star would be much appreciated.