dsh-memu-memory 1.1.3 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +39 -6
  3. package/README.zh.md +39 -7
  4. package/docs/active-memory-ab.md +385 -0
  5. package/docs/binding-lifecycle.md +99 -0
  6. package/docs/contributing.md +3 -3
  7. package/docs/emotion-model-spec.md +281 -0
  8. package/docs/env-and-commands.md +8 -0
  9. package/docs/host-api.md +2 -2
  10. package/docs/memory-semantics.md +12 -10
  11. package/docs/recall-benchmark.md +238 -0
  12. package/docs/troubleshooting.md +396 -395
  13. package/lib/active-memory.js +165 -0
  14. package/lib/activity-log.js +23 -6
  15. package/lib/binding-diagnostics.js +8 -3
  16. package/lib/client.js +335 -35
  17. package/lib/consolidation-ledger.js +347 -0
  18. package/lib/consolidation-receipt.js +284 -0
  19. package/lib/consolidation-snapshot.js +274 -0
  20. package/lib/consolidation-worker.js +144 -0
  21. package/lib/curator.js +86 -31
  22. package/lib/cycle.js +40 -6
  23. package/lib/graph.js +139 -5
  24. package/lib/index.js +33 -1
  25. package/lib/inject.js +104 -76
  26. package/lib/local-embedding-manager.js +24 -6
  27. package/lib/memory.js +534 -88
  28. package/lib/mood.js +268 -39
  29. package/lib/preset-scope.js +1 -1
  30. package/lib/private.js +430 -43
  31. package/lib/prompts/private-consolidation.coding.md +1 -0
  32. package/lib/prompts/private-consolidation.md +1 -0
  33. package/lib/prompts/private-consolidation.mixed.md +1 -0
  34. package/lib/prompts/private-consolidation.rp.md +1 -0
  35. package/lib/prompts.js +4 -4
  36. package/lib/remote-summary.js +51 -25
  37. package/lib/rpc.js +54 -5
  38. package/lib/state.js +29 -12
  39. package/lib/text-similarity.js +106 -0
  40. package/lib/tools.js +113 -10
  41. package/lib/vector-migration.js +28 -3
  42. package/lib/vector-profile.js +51 -0
  43. package/package.json +4 -2
  44. package/scripts/acceptance-local.mjs +2 -1
  45. package/scripts/active-memory-ab-live.mjs +655 -0
  46. package/scripts/active-memory-ab.mjs +1302 -0
  47. package/scripts/chain-matrix.mjs +11 -1
  48. package/scripts/check-all.mjs +10 -1
  49. package/scripts/replay.mjs +573 -0
  50. package/tests/activation.test.mjs +89 -0
  51. package/tests/active-memory-ab.test.mjs +745 -0
  52. package/tests/active-memory.test.mjs +129 -0
  53. package/tests/activity-log.test.mjs +126 -0
  54. package/tests/activity-wiring.test.mjs +103 -0
  55. package/tests/appendix-b.test.mjs +216 -0
  56. package/tests/appendix-b2.test.mjs +166 -0
  57. package/tests/audit-regression.test.mjs +272 -0
  58. package/tests/audit-safety.test.mjs +227 -0
  59. package/tests/backend-compensation.test.mjs +272 -0
  60. package/tests/backup.test.mjs +793 -0
  61. package/tests/binding-diagnostics.test.mjs +720 -0
  62. package/tests/browser.test.mjs +366 -0
  63. package/tests/client-render.test.mjs +892 -0
  64. package/tests/coalesce.test.mjs +113 -0
  65. package/tests/config.test.mjs +159 -0
  66. package/tests/consolidation-credential.test.mjs +331 -0
  67. package/tests/consolidation-feed.test.mjs +96 -0
  68. package/tests/consolidation-fork.test.mjs +489 -0
  69. package/tests/consolidation-ledger.test.mjs +298 -0
  70. package/tests/consolidation-snapshot.test.mjs +360 -0
  71. package/tests/consolidation-value.test.mjs +89 -0
  72. package/tests/context-scope.test.mjs +40 -0
  73. package/tests/cue.test.mjs +44 -0
  74. package/tests/curator-commit.test.mjs +277 -0
  75. package/tests/curator-probe.test.mjs +433 -0
  76. package/tests/curator.test.mjs +150 -0
  77. package/tests/cycle.test.mjs +411 -0
  78. package/tests/disclosure.test.mjs +231 -0
  79. package/tests/doc-drift.test.mjs +222 -0
  80. package/tests/embedding-catalog.test.mjs +266 -0
  81. package/tests/embedding-download.test.mjs +391 -0
  82. package/tests/embedding-runtime.test.mjs +389 -0
  83. package/tests/emotion-chain.test.mjs +84 -0
  84. package/tests/emotion-contract.test.mjs +445 -0
  85. package/tests/engine.test.mjs +35 -0
  86. package/tests/fixtures/active-memory-ab/README.md +56 -0
  87. package/tests/fixtures/active-memory-ab/cases/01-clean-dense-turn.json +351 -0
  88. package/tests/fixtures/active-memory-ab/cases/02-clean-thin-turn.json +122 -0
  89. package/tests/fixtures/active-memory-ab/cases/03-clean-starved-small.json +125 -0
  90. package/tests/fixtures/active-memory-ab/cases/04-clean-large-thin.json +137 -0
  91. package/tests/fixtures/active-memory-ab/cases/05-clean-half-step.json +166 -0
  92. package/tests/fixtures/active-memory-ab/cases/06-clean-duplicate-reread.json +197 -0
  93. package/tests/fixtures/active-memory-ab/cases/07-clean-recent-recall.json +125 -0
  94. package/tests/fixtures/active-memory-ab/cases/08-global-relevant.json +111 -0
  95. package/tests/fixtures/active-memory-ab/cases/09-global-irrelevant-conflict.json +158 -0
  96. package/tests/fixtures/active-memory-ab/cases/10-no-human-turn.json +97 -0
  97. package/tests/fixtures/active-memory-ab/cases/11-clean-scale-100.json +137 -0
  98. package/tests/fixtures/active-memory-ab/cases/12-clean-scale-1000.json +137 -0
  99. package/tests/fixtures/active-memory-ab/cases/13-clean-scale-5000.json +137 -0
  100. package/tests/fixtures/active-memory-ab/cases/14-clean-scale-20000.json +137 -0
  101. package/tests/fixtures/active-memory-ab/lead-ai-reviewed-reference.json +191 -0
  102. package/tests/fixtures/active-memory-ab/manifest.json +199 -0
  103. package/tests/fixtures/recall-bench/01-paraphrase.json +184 -0
  104. package/tests/fixtures/recall-bench/02-exact.json +153 -0
  105. package/tests/fixtures/recall-bench/03-negation.json +173 -0
  106. package/tests/fixtures/recall-bench/04-no-answer.json +162 -0
  107. package/tests/fixtures/recall-bench/05-conflict.json +194 -0
  108. package/tests/fixtures/recall-bench/06-boundary.json +273 -0
  109. package/tests/fixtures/recall-bench/07-secret.json +191 -0
  110. package/tests/fixtures/recall-bench/08-coding.json +164 -0
  111. package/tests/fixtures/recall-bench-gold-candidates/01-paraphrase.json +193 -0
  112. package/tests/fixtures/recall-bench-gold-candidates/02-exact.json +197 -0
  113. package/tests/fixtures/recall-bench-gold-candidates/03-negation.json +244 -0
  114. package/tests/fixtures/recall-bench-gold-candidates/04-no-answer.json +189 -0
  115. package/tests/fixtures/recall-bench-gold-candidates/05-conflict.json +245 -0
  116. package/tests/fixtures/recall-bench-gold-candidates/06-boundary.json +279 -0
  117. package/tests/fixtures/recall-bench-gold-candidates/07-secret.json +224 -0
  118. package/tests/fixtures/recall-bench-gold-candidates/08-coding.json +218 -0
  119. package/tests/fixtures/repetition-eval/01-zh-rp.json +93 -0
  120. package/tests/fixtures/repetition-eval/02-en.json +97 -0
  121. package/tests/fixtures/repetition-eval/03-code.json +91 -0
  122. package/tests/fixtures/repetition-eval/04-names.json +116 -0
  123. package/tests/fixtures/repetition-eval/05-user-quote.json +96 -0
  124. package/tests/fixtures/repetition-eval/06-agreement.json +86 -0
  125. package/tests/graph-cascade.test.mjs +190 -0
  126. package/tests/graph-view.test.mjs +187 -0
  127. package/tests/graph.test.mjs +94 -0
  128. package/tests/hook-order.test.mjs +72 -0
  129. package/tests/humanlike.test.mjs +232 -0
  130. package/tests/hybrid-recall.test.mjs +122 -0
  131. package/tests/inbound.test.mjs +393 -0
  132. package/tests/inject-guide.test.mjs +364 -0
  133. package/tests/inject.test.mjs +222 -0
  134. package/tests/injection-budget-settings.test.mjs +35 -0
  135. package/tests/injection-budget.test.mjs +64 -0
  136. package/tests/injection-chain.test.mjs +191 -0
  137. package/tests/loader-register.mjs +3 -0
  138. package/tests/loader.mjs +12 -0
  139. package/tests/local-embedding-entry.test.mjs +95 -0
  140. package/tests/local-embedding-manager.test.mjs +476 -0
  141. package/tests/local-embedding-protocol.test.mjs +95 -0
  142. package/tests/local-embedding-worker.test.mjs +346 -0
  143. package/tests/managed-memory.test.mjs +591 -0
  144. package/tests/managed-runtime.test.mjs +40 -0
  145. package/tests/media.test.mjs +132 -0
  146. package/tests/migration-payload.test.mjs +42 -0
  147. package/tests/mood.test.mjs +109 -0
  148. package/tests/multiprocess.test.mjs +29 -0
  149. package/tests/new-session-gate.test.mjs +361 -0
  150. package/tests/package-shipping.test.mjs +84 -0
  151. package/tests/preset-scope.test.mjs +212 -0
  152. package/tests/prompts.test.mjs +250 -0
  153. package/tests/react-snapshot.test.mjs +23 -0
  154. package/tests/recall-bench.test.mjs +306 -0
  155. package/tests/recall-dedup.test.mjs +417 -0
  156. package/tests/remote.test.mjs +140 -0
  157. package/tests/rename.test.mjs +30 -0
  158. package/tests/repetition-detector-snapshot.test.mjs +231 -0
  159. package/tests/repetition-eval-live.test.mjs +177 -0
  160. package/tests/repetition-eval.test.mjs +373 -0
  161. package/tests/salient-date.test.mjs +142 -0
  162. package/tests/settings-beat.test.mjs +221 -0
  163. package/tests/short-term.test.mjs +166 -0
  164. package/tests/sidebar-beat.test.mjs +212 -0
  165. package/tests/sidebar-refresh.test.mjs +123 -0
  166. package/tests/sidebar.test.mjs +70 -0
  167. package/tests/snippet.test.mjs +47 -0
  168. package/tests/source-kind.test.mjs +52 -0
  169. package/tests/state.test.mjs +150 -0
  170. package/tests/stats.test.mjs +112 -0
  171. package/tests/storage.test.mjs +125 -0
  172. package/tests/stubs/dsh-llm.js +5 -0
  173. package/tests/stubs/dsh-session.js +7 -0
  174. package/tests/stubs/dsh-tools.js +101 -0
  175. package/tests/tool-schema.test.mjs +73 -0
  176. package/tests/tools.test.mjs +184 -0
  177. package/tests/ui-defaults.test.mjs +57 -0
  178. package/tests/update-fields.test.mjs +138 -0
  179. package/tests/vector-migration.test.mjs +790 -0
  180. package/tests/vector-profile.test.mjs +425 -0
  181. package/tests/visibility-migration.test.mjs +152 -0
  182. package/tests/webui-auth.test.mjs +454 -0
  183. package/tests/webui.test.mjs +165 -0
  184. package/tests/windows-env.test.mjs +89 -0
package/AGENTS.md CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  1. **不改 DSH 源码**,只用插件、公开 service、RPC、slot;只用公开扩展点。
16
16
  2. **不让主 agent 写全局记忆**:全局只由 curator 写,主 agent 只读。
17
- 3. **动态记忆不进 system prompt**(静态 guide 可以):走独立 plugin 消息,保护 KV cache 也让注入可折叠。`persona` 带 `complete: true` 的预设会丢弃本插件的 system prompt 段,这类会话的 guide 改由首步一条 `<memory-guide>` 消息投放(`withGuideMessage`,按 `binding.guideMessageAt` 只发一次,`lib/inject.js`)——仍然是 plugin 消息,不是把动态记忆写进 system prompt。
17
+ 3. **记忆规则与动态记忆都走 plugin 消息**:当前版本不再注册 system prompt 段;静态 guide 在回合首步以 `<memory-guide rules="…">` 投放(`rulesKey` 处理切域/改稿/压缩丢失),动态记忆走独立 plugin 消息,保护 KV cache 并让注入可折叠。
18
18
  4. **不每步注入**:普通回合首步注入;长工具回合只在出现尚未处理的真实人类消息批次时追加一次。工具日志、心跳不算人类消息。
19
19
  5. **不自动删除记忆**:永久记忆移出 = 边缘化;删库必须显式确认;删会话默认只解绑。
20
20
  6. **Python 不装进系统解释器 / 用户 site-packages**,只装 `engine/<version>/venv`,且 venv 里不留 pip(`--without-pip` 建,再用基础 Python 的 `pip --python` 指进去装)。
package/README.md CHANGED
@@ -23,14 +23,14 @@
23
23
 
24
24
  ---
25
25
 
26
- `dsh-memu-memory` is built for long-form RP and companion chats. It gives a DSH conversation a memory that behaves like memory instead of a log: three retention layers that fade, a first-turn anchor plus at most one per-turn recall, provenance that cannot be quietly upgraded from "I inferred it" to "they said it", contradictions that wait for a decision instead of both being read as true, and an optional relationship graph with evidence. Speakers, characters, worlds and story branches form hard boundaries, and what a character is allowed to know stays separate from what actually happened.
26
+ `dsh-memu-memory` is built for long-form RP and companion chats. It gives a DSH conversation a memory that behaves like memory instead of a log: three retention layers that fade, a first-turn anchor plus at most one per-turn recall, provenance that cannot be quietly upgraded from "I inferred it" to "they said it", contradictions that wait for a decision instead of both being read as true, near-identical facts collapse to one entry and one recall seat, and an optional relationship graph with evidence. Speakers, characters, worlds and story branches form hard boundaries, and what a character is allowed to know stays separate from what actually happened.
27
27
 
28
28
  It is **local-first by default**. The default `agent` backend stores full text in JSON indexes and recalls lexically — no Python, no embedding service, no API key. The `api` backend switches recall to the optional, pinned MemU engine (vector search). MemU is a plugin for people who want it, not a requirement for the plugin to start.
29
29
 
30
30
  > [!WARNING]
31
31
  > **Early-stage software.** This plugin is still early and may contain unforeseen errors, bugs or breaking changes. Back up your data, and please [report an issue](https://github.com/Tkingxiao/dsh-memu-memory/issues) or send a pull request — feedback helps the project improve.
32
32
 
33
- > **Status:** 1.1.2. The first public release was 1.0.0. The manual now lives in [`docs/`](docs/), with [`AGENTS.md`](AGENTS.md) as a short index. Back up `~/.dsh/dsh-memu-memory` before upgrades, and read [Compatibility & limits](#compatibility--limits) before storing data you cannot re-create.
33
+ > **Status:** 1.2.1. The first public release was 1.0.0. The manual now lives in [`docs/`](docs/), with [`AGENTS.md`](AGENTS.md) as a short index. Back up `~/.dsh/dsh-memu-memory` before upgrades, and read [Compatibility & limits](#compatibility--limits) before storing data you cannot re-create.
34
34
 
35
35
  ## Screenshots
36
36
 
@@ -59,7 +59,7 @@ All screenshots are the real WebUI on isolated example data, not anyone's memori
59
59
  - **Three layers, one timeline.** `short_term` drafts expire after 72 hours by default; `long_term` is the body of ordinary memory; `must_remember` needs a reason and a source, decays over 365 days with a 0.35 strength floor, and can be *marginalized* but never auto-deleted to make room.
60
60
  - **Recall is a moment, not a dump.** The first turn after a session starts (or after compaction) gets an anchor block with budgets for pinned, core and global memory. After that, each user turn gets at most one recall. Deduplication and a token budget are enforced in code, not left to the prompt.
61
61
  - **A date is either an anniversary or one day.** `MM-DD` recurs every year (a birthday); `YYYY-MM-DD` fires the day before and the day itself, then is spent. A memory that says "tomorrow" is stored as the dated form, so an appointment does not come back every year.
62
- - **Memory fades unevenly.** Recency, access count, peak and end effects, primacy, habituation and flashbulb accuracy feed one strength score. Arousal and mood congruence are conditional rather than always-on: both are off by default and only engage when the mood is far enough from flat (|valence| >= 0.15, or |arousal - 0.35| >= 0.1), so in a calm conversation both multipliers are exactly 1. A vivid memory can be more confident than it is accurate, and the UI says so.
62
+ - **Memory fades unevenly.** Recency, access count, peak and end effects, primacy, habituation and flashbulb accuracy feed one strength score. Mood is described by four components (joy / anger / sorrow / fear, summing to 1) plus arousal (intensity) and dominance (sense of control); a compound mood such as jealousy is a named mix of the four, not a new axis. Mood only reorders recall: it never enters the injected text and never rewrites a memory body. Gating is the OR of three independent conditions — the dominant share is >= 0.35, |arousal - 0.35| >= 0.1, or |dominance| >= 0.25; when none holds, both multipliers are exactly 1. Older entries carry no emotion and keep the old valence path; the four axes are never inferred back from valence. A vivid memory can be more confident than it is accurate, and the UI says so.
63
63
  - **Provenance is a fact, not a style.** Every entry carries `source` (`user_said` / `inferred` / `imagined` / `told`), `confidence`, `accuracy`, and disclosure rules. A vague recollection cannot impersonate a quote.
64
64
  - **Contradictions wait.** A write that rewrites or contradicts an existing entry lands as `pending_review`: it is not injected, not recalled, and appears in a review queue until someone accepts or rejects it.
65
65
  - **Secrets stay excluded.** `secret` visibility, group-chat channels, characters, worlds, branches and `knownBy` all constrain reads and edits. Automatic recall never injects a `secret` memory, and a `secret` is still readable by an explicit recall from the same folder — it hides a memory from the prompt, it is not a lock. What actually separates two conversations in one folder is the `context` scope (`channelId` / `characterId` / `worldId` / `branchId`); a blank scope on both sides means one shared scope, which is the point of a folder. The old `visibility: 'session'` level is gone — reading turns it into `shared` with its `context` untouched, and `node scripts/migrate-visibility.mjs` rewrites libraries that still store it (`--apply`, backs up first).
@@ -67,10 +67,40 @@ All screenshots are the real WebUI on isolated example data, not anyone's memori
67
67
  - **Images are pointers and captions, not pretend vision.** Attachments are deduplicated by SHA-256, quota-limited and tiered; an image without a description is remembered as "received an image", never as a scene the model did not see.
68
68
  - **Housekeeping happens in the background.** Consolidation runs in a seeded fork sub-session, never as an editable turn in your chat. Failed runs back off instead of reporting success.
69
69
 
70
+ ### Active memory, measured
71
+
72
+ Idle consolidation is not "every quiet session gets a fork". Before a fork the plugin scores the evidence since the last cursor: human turns (`H`), content volume (`C`: human text, assistant text and deduped successful tool results, 2,000 characters per result) and effective tool work units (`U`), with `score = 0.35*min(H/3,2) + 0.45*min(C/4000,2) + 0.20*min(U/20,2)` (default threshold 0.8). A session idle for 15 minutes wakes below that score only when it is small; transcripts of 2,000+ events must earn their fork, because the host seeds the full prefix. A recent consolidation or a recent recall suppresses the idle fork.
73
+
74
+ More is not better: thin turns on a large transcript are skipped by the guard (about 37% fewer prompt tokens on the same scenarios, at the cost of roughly 1 in 22 labels arriving a little later), but once the unconsolidated range passes 600 events the check runs unconditionally, so decisions made early in a long session do not stay out of memory forever.
75
+
76
+ Four real long sessions from this machine were replayed read-only (no new chat) and their turns were counted:
77
+
78
+ | Session | Events | Turns | Score-qualified | Below threshold | Fresh human turns after cursor |
79
+ |---|---:|---:|---:|---:|---:|
80
+ | `62056bff…` | 5,913 | 12 | 6 | 6 | 0 |
81
+ | `adedb9ac…` | 5,766 | 23 | 16 | 7 | 0 |
82
+ | `e38a0de0…` | 85 | 2 | 0 | 2 | 0 |
83
+ | `edf7dc5c…` | 78 | 4 | 0 | 4 | 0 |
84
+
85
+ Below-threshold turns on the two small sessions can still wake through starvation; on the 2,000+ event sessions they do not. None of the four had new human input after its real cursor, so none would fork again immediately.
86
+
87
+ Provider-reported usage from real forks: one consolidation costs about **41k–217k input tokens** (including cache reads; it depends on memory-tool steps) and **0.8k–3.4k output**. At one 4-hour long session per day and 2–6 effective consolidations:
88
+
89
+ | Consolidations/day | Monthly input | Monthly output |
90
+ |---|---:|---:|
91
+ | 2 | ~5.6M | ~0.11M |
92
+ | 4 | ~11M | ~0.23M |
93
+ | 6 | ~17M | ~0.34M |
94
+ | 6 (worst measured fork each) | ~39M | ~0.7M |
95
+
96
+ For scale, the whole 4-hour session tree measured here consumed 132M input and 1.28M output in one day, so active-memory forks are about **0.2%–1%** of the total. These are token counts, not money; cache reads are usually billed at a lower rate. The exact figure depends on turn count, memory-tool steps and how often the session actually goes idle.
97
+
70
98
  ## One WebUI
71
99
 
72
100
  The sidebar has exactly **one** entry: **MemU WebUI**. The memory wall and all settings live in the same navigation, with deep links such as `#/settings/memories`, `#/settings/folders` and `#/settings/stats`. The interface follows the browser locale (Chinese or English).
73
101
 
102
+ **Private / global groups with multi-select.** The "Memories" page splits into **Private** and **Global** blocks (private first, default). The private block supports **multi-select**: bulk archive, restore and permanent delete (two-step confirm) — one failed row never eats the rest. Every row on the Global memory page has a "View" action that jumps straight to that entry on the wall.
103
+
74
104
  **In-place work.** Clicking a memory card opens a right-side drawer with the full text and the fields that decide how it behaves — title, content, tags, disclosure, confidence, open loop — plus save, archive, restore and "open in the browser" for heavier work. Archived memories can be deleted in the same drawer; the first click only arms it and the second confirms. Global memories are read-only in the drawer and link to their own page. Filters and scroll position stay where they were. Cards that carry images use the first usable attachment as a cover, and the drawer opens the full gallery; `secret` and `never_say` entries never receive a pixel URL, so they show a label instead.
75
105
 
76
106
  **Graph and list together.** The **Relations** tab renders the graph canvas and the adjacency list side by side (stacked on narrow screens), with the focus selector above them and link/entity/relationship tools below. There is no mode switch to forget.
@@ -79,13 +109,13 @@ The locally bundled D3 force layout preserves positions and the camera across ba
79
109
 
80
110
  | Page | What it is for |
81
111
  |---|---|
82
- | Memories | Cross-library wall, source/status/library/preview, in-place drawer, Relations tab |
112
+ | Memories | Private/global grouped wall, source/status/library/preview, in-place drawer, multi-select bulk ops, Relations tab |
83
113
  | Engine & connection | Optional MemU engine install/switch, embedding endpoint, backups and restore |
84
114
  | Memory settings | Injection mode, token budgets, topK, relevance threshold, retention, automation |
85
115
  | Folders | Folder-level private/global ceiling, expandable conversations and their own switches |
86
116
  | Libraries | Create, rename, archive, restore, explicitly delete libraries |
87
117
  | Memory browser | Full-text editing, attachments, review queue, graph and bounded canvas, JSON/ZIP import/export |
88
- | Global memory | Domain-scoped global memories, evidence, review queue and the separate curator |
118
+ | Global memory | Domain-scoped global memories, evidence, review queue and the separate curator; every row can jump to the wall |
89
119
  | Prompts | General / RP / Coding / Mixed variants, overrides and per-key reset; new conversations start on RP and can pick a domain in the first-run prompt |
90
120
  | Stats | Daily activity, hit distribution, health, never-recalled shortlist, 7-day write/read ratio |
91
121
 
@@ -296,7 +326,8 @@ the idle window starts from real message activity or the agent's return to idle.
296
326
  - **No cloud sync, no accounts, no cross-device sync.** This is a local plugin with no server-side identity.
297
327
  - **Images without vision:** without an optional caption/vision endpoint, an image memory is a pointer plus what a person wrote about it. The plugin does not claim to see pixels.
298
328
  - **Poisoned v4 sessions:** historical `format v4 message requires a producer-owned source kind` rows can be diagnosed but not rewritten; the plugin never edits private session files.
299
- - **Agent presets:** a conversation whose preset (`i-am-yuike`, `yuike-standard`, …) carries its own `cordis.yml` composes its model-facing plugins from that list alone, and its scope never delivers `agent/pre-step` to this plugin. At runtime `lib/preset-scope.js` attaches a second injection listener to that agent's own context, so **anchor and recall delivery works**; because a preset persona usually sets `complete: true`, system-prompt sections are dropped and the memory rules are delivered once as a `<memory-guide>` plugin message on the first step instead. The **final tool list** is a third, separate question: it follows the preset's own plugin list and the host scope, and it cannot be inferred from whether this plugin is listed — observations in this repo record both "no `memu_*` tools" and `memu_private_*` present in some presets' tool chains. **Per-preset tool availability still needs a real-host check (W3)**; until then do not read this as "presets have no tools", nor as "listing the plugin is enough". The plugin never edits anyone's preset files.
329
+ - **Agent presets:** a conversation whose preset (`i-am-yuike`, `yuike-standard`, …) carries its own `cordis.yml` composes its model-facing plugins from that list alone, and its scope never delivers `agent/pre-step` to this plugin. At runtime `lib/preset-scope.js` attaches an injection listener to that agent's own context, so **anchor and recall delivery works**. All conversations now receive the memory rules as one `<memory-guide rules="…">` plugin message on the first step of a turn; the plugin no longer registers a system-prompt section, and the message is replaced after a domain/prompt change or a compaction (`rulesKey` / `guideLost`). The **final tool list** is a third, separate question: it follows the preset's own plugin list and the host scope, and it cannot be inferred from whether this plugin is listed — observations in this repo record both "no `memu_*` tools" and `memu_private_*` present in some presets' tool chains. **Per-preset tool availability still needs a real-host check (W3)**; until then do not read this as "presets have no tools", nor as "listing the plugin is enough". The plugin never edits anyone's preset files.
330
+ - **Consolidation worker (C 包, 2026-10-30).** An idle or manual pass now works from a *fixed snapshot*: it takes the public `sessionQuery.observeSession()` lease (falling back to `readSession()` when the host offers no lease), cuts at the last complete `turn/end`, and drops unpaired tool calls and half steps — a live transcript whose last turn is still open is **deferred**, never consolidated. A job is settled only by a structured completion receipt that is cross-checked against the library index (an id the library does not contain is not a success, and a rewrite of an existing id counts as an update, not a new memory). Checked / pending / in-flight ranges and a lease are persisted separately in `consolidation.json`, so a crash, a timeout or a failed turn leaves the range pending instead of looking consolidated. **Limits:** the worker still runs inside the parent preset's own system prompt — it receives an explicit "you are a background consolidation worker, not this character" brief, which reduces but does not remove persona carry-over, because the host has no public API to replace that prompt; and when the conversation's tool surface carries no `memu_private_remember` / `memu_private_update` (or exposes memory only through PTC), consolidation reports `unavailable` (`worker-tools-missing` / `worker-tools-ptc-only`) rather than forking a worker that could not write. The parent's library, scope, domain and memory context are re-checked before dispatch, on every worker tool call, and again before any result is credited.
300
331
  - **"Tomorrow" is resolved at write time.** A relative day (今天 / 明天 / 后天 / today / tomorrow) together with a month-day settles into one dated memory; a memory that only says "sometime next month" keeps no date rather than inventing one. A date that was already written as `MM-DD` is not rewritten — the fix applies to new writes.
301
332
  - **Not in scope:** cloud MemU integration, source backfill for old entries, automatic repair of already-written v4 corruption.
302
333
 
@@ -320,6 +351,8 @@ The suite includes one real-engine integration case that is skipped unless `DSH_
320
351
 
321
352
  ## Changelog
322
353
 
354
+ **1.2.1 (2026-10-31)**: fact-level dedup (write-side gate + recall-side cluster folding); consolidation now works in PTC (run_code) sessions; sidebar consolidation-failure alerts (cross-session, clearable, jump-to-library); the "currently in vector store" count follows real additions and deletions; private/global groups and multi-select bulk ops on the memory wall; one-click view from the global page; graph delete cascades with orphan-entity cleanup; remote consolidation counts only what actually landed.
355
+
323
356
  **1.1.3 (2026-10-30)**: managed local vectors are usable end to end (runtime/model download, switch-and-rebuild, progress with per-item failures); activity log with a Logs page; consolidation results move under the sidebar button; archive/restore and list refresh drop from seconds to milliseconds; a date written in the text is picked up when the field is empty; default topK 5/5; fixes the blank settings/WebUI panel (React #310).
324
357
 
325
358
  **1.1.2 (2026-10-29)**: anchor and recall delivery in agent-preset conversations; startup catch-up consolidation; panel TDZ crash fix.
package/README.zh.md CHANGED
@@ -23,14 +23,14 @@
23
23
 
24
24
  ---
25
25
 
26
- `dsh-memu-memory` 面向长篇 RP 与陪伴对话,给 DSH 一份「像记忆」的记忆,而不是一本日志:三种留存层会淡;首轮有锚点,之后每个用户回合最多召回一次;来源不能被悄悄从「我推断」升级成「对方说过」;互相矛盾的记忆会挂起等待裁决,而不是两条都被当成真的;关系可以是一条带证据的边。说话人、角色、世界线与剧情分支构成硬边界,角色「知道什么」与实际发生过什么分开保存。
26
+ `dsh-memu-memory` 面向长篇 RP 与陪伴对话,给 DSH 一份「像记忆」的记忆,而不是一本日志:三种留存层会淡;首轮有锚点,之后每个用户回合最多召回一次;来源不能被悄悄从「我推断」升级成「对方说过」;互相矛盾的记忆会挂起等待裁决,而不是两条都被当成真的;几乎相同的事实只入一条、召回时同簇只占一席;关系可以是一条带证据的边。说话人、角色、世界线与剧情分支构成硬边界,角色「知道什么」与实际发生过什么分开保存。
27
27
 
28
28
  默认**本地优先**。`agent` 后端把完整正文放在 JSON 索引里、用词法检索——不需要 Python、embedding 服务或 API key。`api` 后端才把检索交给可选的、锁定版本的 MemU 引擎(向量)。MemU 是「想要就用」的后端,不是插件能否启动的前提。
29
29
 
30
30
  > [!WARNING]
31
31
  > **早期版本提示。** 本插件仍处于早期阶段,可能存在无法预见的错误、bug 或不兼容变更。请先备份数据;欢迎[提交 Issue](https://github.com/Tkingxiao/dsh-memu-memory/issues) 或 PR 帮助改进。
32
32
 
33
- > **状态:** 1.1.0。首个公开发布版本为 1.0.0。细节文档已迁到 [`docs/`](docs/),[`AGENTS.md`](AGENTS.md) 只做索引。升级前请备份 `~/.dsh/dsh-memu-memory`;如果要存无法重建的数据,请先读[兼容性与边界](#兼容性与边界)。
33
+ > **状态:** 1.2.1。首个公开发布版本为 1.0.0。细节文档已迁到 [`docs/`](docs/),[`AGENTS.md`](AGENTS.md) 只做索引。升级前请备份 `~/.dsh/dsh-memu-memory`;如果要存无法重建的数据,请先读[兼容性与边界](#兼容性与边界)。
34
34
 
35
35
  ## 界面
36
36
 
@@ -59,7 +59,7 @@
59
59
  - **三层,一条时间线。** `short_term` 草稿默认 72 小时到期;`long_term` 是记忆主体;`must_remember` 需要理由与来源,按 365 天半衰并带 0.35 强度地板,可以**边缘化**,但不会为腾名额被自动删除。
60
60
  - **召回是一个瞬间,不是一次倾倒。** 新会话或压缩后的第一个回合得到锚点块(pin / core / global 分段预算);之后每个用户回合最多一次召回。去重与 token 预算由代码执行,不交给提示词自觉。
61
61
  - **日期要么是纪念日,要么是一天。** `MM-DD` 每年复发(生日);`YYYY-MM-DD` 在前一天和当天各响一次,之后就算用过了。说「明天」的记忆存成带年份的日期,不会变成每年都冒出来的周年。
62
- - **记忆会不均匀地淡。** 新近、访问次数、峰终、首因、习惯化、闪光灯记忆的准确度共同参与强度;唤醒度与情绪同调是**条件项**而不是恒常生效——默认关闭,只有情绪离平缓够远时(|valence| >= 0.15,或 |arousal - 0.35| >= 0.1)才打开,平缓对话里两项乘数恒为 1。一条生动但可能记错的记忆会显示「细节未必准」,而不是假装确定。
62
+ - **记忆会不均匀地淡。** 新近、访问次数、峰终、首因、习惯化、闪光灯记忆的准确度共同参与强度。情绪用四项构成(joy / anger / sorrow / fear,和为 1)加唤醒度 `arousal`(强度)与掌控感 `dominance` 描述;「嫉妒」这类复合情绪是四项的具名混合比例,不是新维度。情绪只影响召回排序:不进注入文本,也不改记忆正文。门控是三条独立条件的 OR——主导项占比 ≥ 0.35、`|arousal − 0.35| ≥ 0.1`、`|dominance| ≥ 0.25`;三条都不满足时两项乘数恒为 1。旧条目没有 emotion 时继续走原来的 valence 路径,不从旧 valence 反推四轴。一条生动但可能记错的记忆会显示「细节未必准」,而不是假装确定。
63
63
  - **来源是事实,不是文风。** 每条记忆带 `source`(`user_said` / `inferred` / `imagined` / `told`)、`confidence`、`accuracy` 与说出口径。模糊的推断不能冒充原话。
64
64
  - **矛盾先挂起。** 与已有记忆同题改写或说法相反的新条进入 `pending_review`:不注入、不召回,只出现在复核队列,等人或整理作业裁决。
65
65
  - **秘密不进自动召回。** `secret`、群聊频道、角色、世界线、剧情分支与 `knownBy` 共同约束读写;`secret` 永远不会被自动注入,但同文件夹里**主动检索仍然读得到**——它挡的是「自己冒出来」,不是保险箱。真正把同一文件夹里的两段对话隔开的是 `context` 作用域(`channelId` / `characterId` / `worldId` / `branchId`);两边都没填就是同一个作用域,这正是「文件夹」的含义。旧的 `visibility: 'session'` 级别已删除:读到就按 `shared` 处理、`context` 原样保留,仍存着该级别的库用 `node scripts/migrate-visibility.mjs` 重写(`--apply` 才写,先备份)。
@@ -67,25 +67,55 @@
67
67
  - **图片是指针加描述,不是假装能看图。** 附件按 SHA-256 去重、有配额、分三级留存;没有描述时只记「收到一张图片」,绝不编造画面。
68
68
  - **整理在后台。** 整理跑在种子 fork 子会话里,不会在你的待发区多出一轮;失败退避重试,不伪记成功。
69
69
 
70
+ ### 主动记忆:实测触发与月开销
71
+
72
+ 空闲整理不是“每个安静会话都 fork”。进 fork 前会先给上次游标之后的证据打分:真人回合 `H`、内容量 `C`(真人文字 + 助手正文 + 去重后的成功工具结果,单结果最多 2000 字符)、有效工具工作单元 `U`,`score = 0.35*min(H/3,2) + 0.45*min(C/4000,2) + 0.20*min(U/20,2)`,默认门槛 0.8。空闲 15 分钟时,只有小会话能凭饥饿直接唤醒;2000 条事件以上的 transcript 必须分数达标,因为宿主会种完整前缀。近期整理过或注入过记忆的会话不 fork。
73
+
74
+ 整理不是越勤越好:大 transcript 的薄回合会被守卫跳过(同一批用例上约 −37% prompt token,代价是被跳过的那几个用例里约 1/22 条标签晚一点进记忆),但待整理区间超过 600 事件时无条件是检查一次,避免长会话早期的决定永远进不了记忆。
75
+
76
+ 用本机 4 个真实长会话做只读回放(不开新对话),统计合格回合:
77
+
78
+ | 会话 | 事件数 | 回合数 | 分数触发 | 未达分数 | 游标后新真人回合 |
79
+ |---|---:|---:|---:|---:|---:|
80
+ | `62056bff…` | 5,913 | 12 | 6 | 6 | 0 |
81
+ | `adedb9ac…` | 5,766 | 23 | 16 | 7 | 0 |
82
+ | `e38a0de0…` | 85 | 2 | 0 | 2 | 0 |
83
+ | `edf7dc5c…` | 78 | 4 | 0 | 4 | 0 |
84
+
85
+ 两条小会话的未达分数回合仍可能因饥饿唤醒;2000 条事件以上的大会话不会。四个会话在真实游标之后都没有新的真人输入,因此不会立即再 fork。
86
+
87
+ 从真实 fork 的 provider usage 汇总:一次整理约 **4.1 万~21.7 万 input tokens**(含缓存读,取决于记忆工具步数)+ **0.8k~3.4k output**。按每天一次 4 小时长会话、每天有效整理 2~6 次:
88
+
89
+ | 每天整理次数 | 每月 input | 每月 output |
90
+ |---|---:|---:|
91
+ | 2 次 | ~5.6M | ~0.11M |
92
+ | 4 次 | ~11M | ~0.23M |
93
+ | 6 次 | ~17M | ~0.34M |
94
+ | 6 次(都按实测最贵的单次) | ~39M | ~0.7M |
95
+
96
+ 作为参照,今天这棵 4 小时会话树实测 132M input + 1.28M output/日,主动记忆整理约占 **0.2%~1%**。以上是 token 量,不是金额;缓存读通常按更低单价计费。实际取决于回合数、记忆工具步数和会话真正空闲的次数。
97
+
70
98
  ## 一个 WebUI
71
99
 
72
100
  侧边栏只留**一个**入口:**MemU WebUI**。记忆墙与全部设置在同一套导航内,支持深链,如 `#/settings/memories`、`#/settings/folders`、`#/settings/stats`。
73
101
 
102
+ **私有 / 全局分组与多选。** 「MemU 记忆」页按 **私有 / 全局** 分块(默认私有),私有块支持**多选**:批量归档、恢复、彻底删除(两段确认),一条失败不影响其余成果。「全局记忆」页每条记忆带「查看此记忆」,点击直达 MemU 记忆页对应条目。
103
+
74
104
  **原地操作。** 点记忆卡片会从右侧滑出详情抽屉:完整正文,以及决定它行为的字段——标题、正文、标签、说出口径、置信度、未了之事——外加保存、归档、恢复,以及需要重编辑时的「在记忆浏览中打开」。已归档的记忆可以直接在抽屉里删除:第一次点击只是准备,第二次才真正删除。全局记忆在抽屉里只读,并给出跳转。筛选条件与滚动位置保持不动。带图片的卡片会把第一张可用附件作为封面,抽屉里展开完整图片组;`secret` 与 `never_say` 条目不会拿到像素 URL,只显示标签占位。
75
105
 
76
106
  **关系图与邻接列表一起看。** 「关系」页签把关系画布与邻接列表并排显示(窄屏上下堆叠),焦点选择在上方,建边/实体/关系状态工具在下方;没有需要记得切换的模式。
77
107
 
78
- 图谱使用本地打包的 D3 力导向布局,后台数据刷新保留节点位置与视角。支持拖拽节点、空白处平移、滚轮/双指缩放、适应全图与关联高亮;标签自动避让,设置中可调整斥力、连线距离,以及关闭标签或动态布局。图谱布局只表达已记录的关联,不会推断或写入新的关系。
108
+ 图谱使用本地打包的 D3 力导向布局,后台数据刷新保留节点位置与视角。支持拖拽节点、空白处平移、滚轮/双指缩放、适应全图与关联高亮;标签自动避让,设置中可调整斥力、连线距离,以及关闭标签或动态布局。图谱布局只表达已记录的关联,不会推断或写入新的关系。实体可以直接在图谱里删除(仍被引用时会明确拒绝);永久删除一条记忆会连带清理指向它的边与孤立实体,孤立节点有启动扫描自动回收。
79
109
 
80
110
  | 页面 | 用途 |
81
111
  |---|---|
82
- | MemU 记忆 | 跨库记忆墙、来源/状态/库名/预览、原地详情、关系统一视图 |
112
+ | MemU 记忆 | 私有/全局分组记忆墙、来源/状态/库名/预览、原地详情、多选批量操作、关系统一视图 |
83
113
  | 引擎与连接 | 可选 MemU 引擎的安装与切换、embedding 配置、备份恢复 |
84
114
  | 记忆设置 | 注入模式、token 预算、topK、相关度下限、留存与自动化 |
85
115
  | 文件夹 | 文件夹级私有/全局上限,展开对话修改各自选择 |
86
116
  | 记忆库 | 新建、改名、归档、恢复、显式删除 |
87
117
  | 记忆浏览 | 全文编辑、附件、复核池、有界图谱、JSON/ZIP 导入导出 |
88
- | 全局记忆 | 分域全局记忆、证据、复核队列与独立 curator |
118
+ | 全局记忆 | 分域全局记忆、证据、复核队列与独立 curator,每条可跳转到记忆墙查看 |
89
119
  | 提示词 | 通用 / RP / Coding / Mixed 变体,覆盖与单项恢复;新会话默认 RP,并可在首次弹窗里直接选领域 |
90
120
  | 统计 | 每日活跃、命中分布、库健康度、最久没被想起的 5 条、7 天写读比 |
91
121
 
@@ -288,7 +318,7 @@ return { content: originalContent, meta: { ...originalMeta, memoryInbound: batch
288
318
  - **没有云同步、账号或跨设备同步。** 这是本地插件,没有服务端身份。
289
319
  - **没有视觉时:** 图片记忆只是「指针 + 人写的描述」。插件不会声称看过像素。
290
320
  - **v4 污染会话:** 历史 `format v4 message requires a producer-owned source kind` 行只能诊断,不能改写;插件不会编辑私有会话文件。
291
- - **人格预设(agent preset):** 预设(`i-am-yuike`、`yuike-standard` 等)自带 `cordis.yml`,会话的模型可见插件集只由那份清单决定,它的 scope 也不会把 `agent/pre-step` 送到本插件。运行时 `lib/preset-scope.js` 会把第二个注入监听挂到该 agent 自己的 ctx 上,所以**锚点与召回的送达是能用的**;而 `persona` 通常带 `complete: true`、会把 system prompt 段整段丢弃,因此记忆规则改为首步一次性投放 `<memory-guide>` 插件消息。**最终工具面**是第三件事,由该预设自己的清单与宿主 scope 决定——不能从「有没有列上本插件」推断,仓库里的观察既有「没有 `memu_*` 工具」,也有某些预设的工具链里存在 `memu_private_*`。**逐预设的真实工具名单仍需真机核实(W3)**;在那之前,别读成「预设都没有工具」,也别读成「列上插件就一定可用」。插件不会改任何人的预设文件。
321
+ - **人格预设(agent preset):** 预设(`i-am-yuike`、`yuike-standard` 等)自带 `cordis.yml`,会话的模型可见插件集只由那份清单决定,它的 scope 也不会把 `agent/pre-step` 送到本插件。运行时 `lib/preset-scope.js` 会把注入监听挂到该 agent 自己的 ctx 上,所以**锚点与召回的送达是能用的**。所有会话现在都通过回合首步的一条 `<memory-guide rules="…">` 插件消息接收记忆规则;插件不再注册 system prompt 段,规则在切域、改稿或压缩后由 `rulesKey`/`guideLost` 替换。**最终工具面**是第三件事,由该预设自己的清单与宿主 scope 决定——不能从「有没有列上本插件」推断,仓库里的观察既有「没有 `memu_*` 工具」,也有某些预设的工具链里存在 `memu_private_*`。**逐预设的真实工具名单仍需真机核实(W3)**;在那之前,别读成「预设都没有工具」,也别读成「列上插件就一定可用」。插件不会改任何人的预设文件。
292
322
  - **相对日期在写入时结算:** 「今天 / 明天 / 后天 / today / tomorrow」加上一个月日落成带年份的日期;只说「下个月某天」的记忆不产生日期,也不替它编一个。已经按 `MM-DD` 写进去的旧条目不会被改写——这条只对新写入生效。
293
323
  - **不在范围:** 云端 MemU 联调、旧条目的 source 回填、已写坏 v4 消息的自动修复。
294
324
 
@@ -312,6 +342,8 @@ node --import ./tests/loader-register.mjs scripts/preview.mjs --port 7789
312
342
 
313
343
  ## 更新日志
314
344
 
345
+ **1.2.1(2026-10-31)**:事实级去重(写入近似门 + 召回按簇折叠);PTC(run_code)会话整理可用;侧边栏整理失败提醒(跨会话、可清除、可跳转);「当前已入向量库」计数跟随真实增减;记忆墙私有/全局分组与多选批量操作;全局记忆一键查看;关系图删除级联与孤儿回收;整理写入计数说真话。
346
+
315
347
  **1.1.3(2026-10-30)**:受管本地向量可用(运行时/模型下载、切换重建、进度与失败明细);活动日志与「日志」页;整理结果落到侧边栏按钮下方;归档/还原与列表刷新提速到毫秒级;日期漏填时从正文兜底;默认 topK 5/5;修复设置/WebUI 白屏。
316
348
 
317
349
  **1.1.2(2026-10-29)**:人格预设会话的锚点与召回送达;启动补扫空闲整理;面板 TDZ 崩溃修复。