# meow-memory ๐Ÿฑ๐Ÿ“ | [ไธญๆ–‡](README.md) | [English](README.en.md) | [MIT License](LICENSE) | | :---: | :---: | :---: | Cross-session memory for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH). **The idea**: every workspace keeps a structured memory database (`.dsh-meow/memory.db`, SQLite via `node:sqlite`). The static tool manual (seven layers + every `memory_*` tool's usage) lives in the **system prompt** as a fixed section โ€” constant text, so your LLM provider's KV/context cache stays untouched. Dynamic content (soul/user in full, design principles, memory guide) is injected as a **prefix of the first user message**, and the first turn injects long-term memory only โ€” no keyword hits. From the second user message on, every message gets a keyword hit (top-2). The model deep-dives into the rest on demand with `memory_search` / `memory_project`. Each window's own agent consolidates its memories at night ("dream") โ€” memories it created plus ones it was shown โ€” with the window's knowledge frozen at the last conversation timestamp. ## โœจ Features - **Seven memory layers** (`soul` = the AI itself / `user` = user basics & preferences / `project` = per-project info with `subcategory` (overview/structure/decisions/quotes/ops/todo) / `fact` = atomic facts / `lesson` = mistakes & corrections / `topic` = ongoing discussion arcs with a goal sentence / `rules` = design principles & behavioral rules). One SQLite table per layer, UUIDs are time-prefixed so id order == creation order. - **First-turn injection (long-term memory block)**: before the first user message, a fixed format is injected: `===== ้•ฟๆœŸ่ฎฐๅฟ† =====` โ†’ `ใ€ๅ…ณไบŽไฝ ใ€‘` (all soul entries) โ†’ `ใ€ๅ…ณไบŽuserใ€‘` (all user entries) โ†’ `ใ€่ฎพ่ฎกๅŽŸๅˆ™ใ€‘` (global rules with importance โ‰ฅ 2 โ€” few, imperative guidelines) โ†’ `ใ€่ฎฐๅฟ†ๅฏผๅผ•ใ€‘` (usage note + the dynamic "all your projects" list for `memory_project`) โ†’ `===== ้•ฟๆœŸ่ฎฐๅฟ†็ป“ๆŸ =====` + `ๆœฌ่ฝฎ็”จๆˆทprompt๏ผš`. **No keyword hits on the first turn** (hits start from the second message). Even when the first user message arrives batched with a plugin notice (e.g. an approval-policy change notification), the snapshot still lands on the real user message and hits never fire early. - **Per-message keyword hits**: from the second user message on, every real user message is matched against fact/lesson/rules/topic (scope = global + current-project anchor), top-2 hits are injected under a "ๅฏ่ƒฝ็›ธๅ…ณ็š„่ฎฐๅฟ†๏ผŒไป…ไพ›ๅ‚่€ƒ๏ผš" prefix. Matching is based on **entry keywords** (LLM-extracted or auto bigram) โ€” not full text, which is noisy. Scoring = intersection ร— idf ร— coverage ร— Ebbinghaus decay (by memory timestamp) ร— importance weight ร— title bonus. - **Current-project anchor**: any `memory_remember/search/update/project` call with a `project` parameter anchors the session's current project; unanchored sessions only hit global entries (casual chat stays unaffected). - **Cache-friendly by design**: the static `meow-memory:guide` section (order 130, right after the `tool:*` guidance sections) is registered in the system prompt once โ€” constant text, KV-cache friendly. Already-seen memories (`injected` + `searched`) are recorded per session (`.dsh-meow/sessions/.json`): injection never repeats, and `memory_search` takes the top 5 by relevance unconditionally (seen / this-session memories included), then fills the rest from the ranking while skipping already-seen entries; a session-compaction signal (`compaction/*`) releases the seen records so compressed-away memories can be hit again. - **Toolset**: `memory_remember` (write, dedup merge, returns read-back confirmation: keywords/project; accepts a `keywords` parameter โ€” reflection/dream turns have the LLM summarize 5โ€“10 content words, auto bigram extraction as fallback) / `memory_search` (BM25 ร— recency, filters: level/project/status/days, default top10 = top 5 by relevance without excluding seen + 5 more skipping already-seen entries, sorted by memory timestamp) / `memory_project` (whole-project injection paragraph: **`project` parameter is required** โ€” which project do you want? grouped by subcategory, all non-stale entries, todo section with latest 5 done + to-do list, plus memory-db & session-history pointers) / `memory_find_similar` (duplicate & conflict detection) / `memory_read` / `memory_update` (incl. status active/archived/stale, importance, goal, manual keyword fixes) / `memory_dream` (manual trigger). - **Memory timestamp** (`updated_at` = last update time): refreshed by dream stamping or any `memory_update`. Displayed timestamps are always `updated_at`; search (work view) shows relative time, hit-injection / memory_project (full-text view) show relative + absolute (e.g. "2026-08-15 10:58 [2 days ago]"). - **Project attribution**: global info gets `project: "ๅ…จๅฑ€"` (distinct from blank = unmarked); multi-project info uses comma-separated names (e.g. `"dsh, femwa"`) โ€” search/hits match "contains current project name OR global". - **Per-window dream**: at night (00:00โ€“07:00 in the configured `timeZone`, default Asia/Shanghai, idle) every window whose last chat is newer than its last dream gets consolidated by its own main agent โ€” two rounds (atomic: project/fact/lesson/rules/soul/user, then topic), project sub-headings, memories it created plus ones it was shown โ€” using its full conversation context. Old windows (no live agent, >24h) and archived sessions are left alone. - **Reflection**: after โ‰ฅ7 consecutive tool steps within one task the plugin asks the model whether anything since the last consolidation is worth remembering. A turn whose last tool is a `memory_*` tool counts as already having consolidated (no re-reflection); cancelled turns never trigger it. - **Injection-fold UI (client)**: first-turn long-term memory / per-message keyword hits collapse into a slim "injected memory" bar (same width as the user bubble) โ€” click to see the full injected text; the user prompt shows as a bubble, keeping the flow clean. Only plain-text messages are folded (attachment-bearing ones stay untouched). - **Reflection-fold UI (client)**: reflection/dream turns (prompt, think, tool calls and the report) collapse into a slim bar (collapsed by default, showing "N memories added" / "dream task"); clicking expands it into a card โ€” Think / tool calls / context injections inside the card are expandable for details. - **Session-list dream icon (client)**: sessions that have been dream-consolidated with no new conversation activity since show a **pale-yellow crescent-moon icon ๐ŸŒ™**; while a dream turn is running the moon **breathes whiteโ†’gold** (replacing dsh's running-blue animation so it can't be mistaken for normal work); new activity removes the icon. The icon lives inside the dsh session row's status slot (replacing its content โ€” no layout shift). Event-driven, no polling: the `/meow-memory/dream-events` SSE stream pushes `state:'dreaming'` when a dream starts, `state:'dreamed'` when it finishes, `state:'active'` when a session gets new activity; the client reconciles once against `/meow-memory/dreamed-sessions` on mount/reconnect. Row targeting needs zero dsh changes: it reads the React 18 fiber (`__reactFiber$` internal property) to get the row's render key = session id โ€” no title matching. - **Dream anti-repeat**: DB-atomic 60s check gate + atomic start claim (`dream_pending`) + interrupted-dream auto-recovery + orphan finalization (a finished dream turn always lands `last_dream_time`, even across hot-reload instances); plugin-turn events don't refresh window activity โ€” an already-dreamed window is never re-dreamed. - **Zero runtime dependencies**: `node:sqlite` (default-enabled on Node โ‰ฅ22.13; 22.5โ€“22.12 needs `--experimental-sqlite`) + self-contained esbuild bundle (`lib/index.js`). No native modules. ## ๐Ÿ“ฆ Install ### Via npm (published package) ```sh # 1. Install into the profile's node_modules (the loader resolves plugins there) cd $DSH_HOME/profiles/web # default home: ~/.dsh/profiles/web npm install meow-memory # 2. Add the package to the profile's assembly bundles in package.json (recommended since v0.9.0): # "dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "meow-memory"] } } # (the package ships a dsh.bundle.patch; bundle assembly inserts it. Profile-patch # `insert` entries address existing ids โ€” a new plugin not in the tree reports # "entry not found", so new plugins go through the bundles array.) # 3. Restart dsh web. New sessions pick up the plugin automatically. ``` ### By hand (any DSH install, no npm needed) 1. Copy (or symlink) this package into the profile's `node_modules`: ```sh mkdir -p ~/.dsh/profiles/web/node_modules ln -s /path/to/meow-memory ~/.dsh/profiles/web/node_modules/meow-memory ``` (On Windows: `New-Item -ItemType Junction ...` โ€” NTFS junction, no admin needed.) 2. Add `meow-memory` to the profile `package.json`'s `dsh.profile.bundles` (same as above). 3. Restart `dsh web`. New sessions pick up the plugin automatically. ## โš™๏ธ Configuration All fields are optional (profile patch or `cordis.patch.yml`): ```yaml - id: meow-memory name: 'meow-memory' config: enabled: true # master switch projectDir: '.dsh-meow' # memory directory, relative to the workspace hitTopK: 2 # max keyword-hit entries injected per user message (fact/lesson/rules/topic) reflect: true # auto-reflection after โ‰ฅreflectTurns tool turns reflectTurns: 7 # consecutive tool turns before reflection triggers dream: enabled: true windowStart: 0 # night window hours, computed in timeZone (below) windowEnd: 7 idleMinutes: 30 # no session events for this long before dreaming checkMinutes: 15 timeZone: 'Asia/Shanghai' # the user's machine clock is US time; the night # window must follow this fixed zone instead ``` ## ๐Ÿง  How it works ``` First user message (turn 1) Every message from turn 2 night โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ ===== ้•ฟๆœŸ่ฎฐๅฟ† ===== โ”‚ โ”‚ ๅฏ่ƒฝ็›ธๅ…ณ็š„่ฎฐๅฟ†๏ผŒไป…ไพ› โ”‚ โ”‚ per-window dream: โ”‚ โ”‚ ใ€ๅ…ณไบŽไฝ ใ€‘(soul) โ”‚ โ”‚ ๅ‚่€ƒ๏ผškeyword hits โ”‚ โ”‚ two rounds (atomic/ โ”‚ โ”‚ ใ€ๅ…ณไบŽuserใ€‘ โ”‚ โ”‚ top-2 (global + โ”‚ โ”‚ topic), 7 layers + โ”‚ โ”‚ ใ€่ฎพ่ฎกๅŽŸๅˆ™ใ€‘(rules) โ”‚ โ”‚ current-project โ”‚ โ”‚ extracted, updated_at โ”‚ โ”‚ ใ€่ฎฐๅฟ†ๅฏผๅผ•ใ€‘ โ”‚ โ”‚ anchor) โ”‚ โ”‚ stamped at T โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ ๆœฌ่ฝฎ็”จๆˆทprompt๏ผš โ”‚ seen ids recorded โ”‚ [user text] โ”‚ per session (sessions/.json) โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ compaction signal โ†’ seen released injected once per session, no hits on turn 1 ``` ## ๐Ÿ›  Development ```sh npm install npm run build # esbuild bundle โ†’ lib/index.js (self-contained) npm run test # 214 logic tests: db / bm25 / migrate / inject / reflect / dream / tools / apply ``` The `@deepseek-ai/*` packages live in the dsh-meow pnpm workspace, not in this package's `node_modules`. On Windows, `npm run link-workspace` (or `scripts/link-workspace.ps1`) creates junction mirrors of the workspace packages so esbuild can resolve them; `build.mjs` uses `nodePaths` to pick them up. The links are build-time only. ## ๐Ÿ“„ License MIT โ€” see [LICENSE](LICENSE).