dsh-memu-memory 1.1.2 → 1.2.0
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.
- package/AGENTS.md +8 -6
- package/README.md +50 -5
- package/README.zh.md +49 -5
- package/docs/active-memory-ab.md +385 -0
- package/docs/binding-lifecycle.md +99 -0
- package/docs/contributing.md +6 -4
- package/docs/emotion-model-spec.md +281 -0
- package/docs/env-and-commands.md +13 -0
- package/docs/host-api.md +35 -3
- package/docs/memory-semantics.md +40 -11
- package/docs/recall-benchmark.md +238 -0
- package/docs/troubleshooting.md +395 -366
- package/lib/active-memory.js +165 -0
- package/lib/activity-log.js +190 -0
- package/lib/backup.js +1103 -0
- package/lib/binding-diagnostics.js +635 -0
- package/lib/client.js +728 -58
- package/lib/consolidation-ledger.js +347 -0
- package/lib/consolidation-receipt.js +284 -0
- package/lib/consolidation-snapshot.js +274 -0
- package/lib/consolidation-worker.js +115 -0
- package/lib/curator.js +81 -37
- package/lib/cycle.js +43 -6
- package/lib/embedding-catalog.js +763 -0
- package/lib/embedding-download.js +407 -0
- package/lib/embedding-runtime.js +710 -0
- package/lib/engine.js +2 -2
- package/lib/index.js +36 -0
- package/lib/inject.js +232 -103
- package/lib/local-embedding-entry.js +145 -0
- package/lib/local-embedding-manager.js +267 -0
- package/lib/local-embedding-protocol.js +173 -0
- package/lib/local-embedding-worker.js +652 -0
- package/lib/local-vector-service.js +283 -0
- package/lib/memory.js +599 -57
- package/lib/memu-config.js +10 -22
- package/lib/memu.js +53 -19
- package/lib/mood.js +268 -39
- package/lib/paths.js +24 -0
- package/lib/preset-scope.js +1 -1
- package/lib/private.js +450 -29
- package/lib/prompts/private-consolidation.coding.md +1 -0
- package/lib/prompts/private-consolidation.md +1 -0
- package/lib/prompts/private-consolidation.mixed.md +1 -0
- package/lib/prompts/private-consolidation.rp.md +1 -0
- package/lib/prompts.js +4 -4
- package/lib/remote-summary.js +51 -25
- package/lib/rpc.js +54 -5
- package/lib/state.js +137 -172
- package/lib/tools.js +58 -7
- package/lib/vector-migration.js +878 -0
- package/lib/vector-profile.js +968 -0
- package/package.json +4 -2
- package/scripts/acceptance-local.mjs +7 -5
- package/scripts/active-memory-ab-live.mjs +655 -0
- package/scripts/active-memory-ab.mjs +1302 -0
- package/scripts/chain-matrix.mjs +537 -0
- package/scripts/check-all.mjs +29 -1
- package/scripts/check-doc-drift.mjs +353 -0
- package/scripts/diagnose-bindings.mjs +176 -0
- package/scripts/local-runtime-install.mjs +142 -0
- package/scripts/local-runtime-verify.mjs +56 -0
- package/scripts/local-vector-verify.mjs +93 -0
- package/scripts/recall-bench.mjs +813 -0
- package/scripts/repetition-ab-scenarios.mjs +93 -0
- package/scripts/repetition-detector-snapshot.mjs +314 -0
- package/scripts/repetition-eval-live.mjs +360 -0
- package/scripts/repetition-eval.mjs +702 -0
- package/tests/activation.test.mjs +89 -0
- package/tests/active-memory-ab.test.mjs +745 -0
- package/tests/active-memory.test.mjs +129 -0
- package/tests/activity-log.test.mjs +126 -0
- package/tests/activity-wiring.test.mjs +103 -0
- package/tests/appendix-b.test.mjs +212 -0
- package/tests/appendix-b2.test.mjs +166 -0
- package/tests/audit-regression.test.mjs +272 -0
- package/tests/audit-safety.test.mjs +227 -0
- package/tests/backend-compensation.test.mjs +272 -0
- package/tests/backup.test.mjs +793 -0
- package/tests/binding-diagnostics.test.mjs +720 -0
- package/tests/browser.test.mjs +366 -0
- package/tests/client-render.test.mjs +892 -0
- package/tests/coalesce.test.mjs +113 -0
- package/tests/config.test.mjs +159 -0
- package/tests/consolidation-credential.test.mjs +320 -0
- package/tests/consolidation-fork.test.mjs +357 -0
- package/tests/consolidation-ledger.test.mjs +298 -0
- package/tests/consolidation-snapshot.test.mjs +321 -0
- package/tests/consolidation-value.test.mjs +89 -0
- package/tests/context-scope.test.mjs +40 -0
- package/tests/cue.test.mjs +44 -0
- package/tests/curator-probe.test.mjs +433 -0
- package/tests/curator.test.mjs +150 -0
- package/tests/cycle.test.mjs +411 -0
- package/tests/disclosure.test.mjs +231 -0
- package/tests/doc-drift.test.mjs +222 -0
- package/tests/embedding-catalog.test.mjs +266 -0
- package/tests/embedding-download.test.mjs +391 -0
- package/tests/embedding-runtime.test.mjs +389 -0
- package/tests/emotion-chain.test.mjs +84 -0
- package/tests/emotion-contract.test.mjs +445 -0
- package/tests/engine.test.mjs +35 -0
- package/tests/fixtures/active-memory-ab/README.md +56 -0
- package/tests/fixtures/active-memory-ab/cases/01-clean-dense-turn.json +351 -0
- package/tests/fixtures/active-memory-ab/cases/02-clean-thin-turn.json +122 -0
- package/tests/fixtures/active-memory-ab/cases/03-clean-starved-small.json +125 -0
- package/tests/fixtures/active-memory-ab/cases/04-clean-large-thin.json +137 -0
- package/tests/fixtures/active-memory-ab/cases/05-clean-half-step.json +166 -0
- package/tests/fixtures/active-memory-ab/cases/06-clean-duplicate-reread.json +197 -0
- package/tests/fixtures/active-memory-ab/cases/07-clean-recent-recall.json +125 -0
- package/tests/fixtures/active-memory-ab/cases/08-global-relevant.json +111 -0
- package/tests/fixtures/active-memory-ab/cases/09-global-irrelevant-conflict.json +158 -0
- package/tests/fixtures/active-memory-ab/cases/10-no-human-turn.json +97 -0
- package/tests/fixtures/active-memory-ab/cases/11-clean-scale-100.json +137 -0
- package/tests/fixtures/active-memory-ab/cases/12-clean-scale-1000.json +137 -0
- package/tests/fixtures/active-memory-ab/cases/13-clean-scale-5000.json +137 -0
- package/tests/fixtures/active-memory-ab/cases/14-clean-scale-20000.json +137 -0
- package/tests/fixtures/active-memory-ab/lead-ai-reviewed-reference.json +191 -0
- package/tests/fixtures/active-memory-ab/manifest.json +199 -0
- package/tests/fixtures/recall-bench/01-paraphrase.json +184 -0
- package/tests/fixtures/recall-bench/02-exact.json +153 -0
- package/tests/fixtures/recall-bench/03-negation.json +173 -0
- package/tests/fixtures/recall-bench/04-no-answer.json +162 -0
- package/tests/fixtures/recall-bench/05-conflict.json +194 -0
- package/tests/fixtures/recall-bench/06-boundary.json +273 -0
- package/tests/fixtures/recall-bench/07-secret.json +191 -0
- package/tests/fixtures/recall-bench/08-coding.json +164 -0
- package/tests/fixtures/recall-bench-gold-candidates/01-paraphrase.json +193 -0
- package/tests/fixtures/recall-bench-gold-candidates/02-exact.json +197 -0
- package/tests/fixtures/recall-bench-gold-candidates/03-negation.json +244 -0
- package/tests/fixtures/recall-bench-gold-candidates/04-no-answer.json +189 -0
- package/tests/fixtures/recall-bench-gold-candidates/05-conflict.json +245 -0
- package/tests/fixtures/recall-bench-gold-candidates/06-boundary.json +279 -0
- package/tests/fixtures/recall-bench-gold-candidates/07-secret.json +224 -0
- package/tests/fixtures/recall-bench-gold-candidates/08-coding.json +218 -0
- package/tests/fixtures/repetition-eval/01-zh-rp.json +93 -0
- package/tests/fixtures/repetition-eval/02-en.json +97 -0
- package/tests/fixtures/repetition-eval/03-code.json +91 -0
- package/tests/fixtures/repetition-eval/04-names.json +116 -0
- package/tests/fixtures/repetition-eval/05-user-quote.json +96 -0
- package/tests/fixtures/repetition-eval/06-agreement.json +86 -0
- package/tests/graph-view.test.mjs +187 -0
- package/tests/graph.test.mjs +77 -0
- package/tests/hook-order.test.mjs +72 -0
- package/tests/humanlike.test.mjs +232 -0
- package/tests/hybrid-recall.test.mjs +92 -0
- package/tests/inbound.test.mjs +393 -0
- package/tests/inject-guide.test.mjs +364 -0
- package/tests/inject.test.mjs +222 -0
- package/tests/injection-budget-settings.test.mjs +35 -0
- package/tests/injection-budget.test.mjs +64 -0
- package/tests/injection-chain.test.mjs +191 -0
- package/tests/loader-register.mjs +3 -0
- package/tests/loader.mjs +12 -0
- package/tests/local-embedding-entry.test.mjs +95 -0
- package/tests/local-embedding-manager.test.mjs +476 -0
- package/tests/local-embedding-protocol.test.mjs +95 -0
- package/tests/local-embedding-worker.test.mjs +346 -0
- package/tests/managed-memory.test.mjs +536 -0
- package/tests/managed-runtime.test.mjs +40 -0
- package/tests/media.test.mjs +132 -0
- package/tests/migration-payload.test.mjs +42 -0
- package/tests/mood.test.mjs +109 -0
- package/tests/multiprocess.test.mjs +29 -0
- package/tests/new-session-gate.test.mjs +361 -0
- package/tests/package-shipping.test.mjs +84 -0
- package/tests/preset-scope.test.mjs +212 -0
- package/tests/prompts.test.mjs +250 -0
- package/tests/react-snapshot.test.mjs +23 -0
- package/tests/recall-bench.test.mjs +306 -0
- package/tests/remote.test.mjs +140 -0
- package/tests/rename.test.mjs +30 -0
- package/tests/repetition-detector-snapshot.test.mjs +231 -0
- package/tests/repetition-eval-live.test.mjs +177 -0
- package/tests/repetition-eval.test.mjs +373 -0
- package/tests/salient-date.test.mjs +142 -0
- package/tests/settings-beat.test.mjs +221 -0
- package/tests/short-term.test.mjs +166 -0
- package/tests/sidebar-beat.test.mjs +208 -0
- package/tests/sidebar-refresh.test.mjs +123 -0
- package/tests/sidebar.test.mjs +70 -0
- package/tests/snippet.test.mjs +47 -0
- package/tests/source-kind.test.mjs +52 -0
- package/tests/state.test.mjs +150 -0
- package/tests/stats.test.mjs +112 -0
- package/tests/storage.test.mjs +125 -0
- package/tests/stubs/dsh-llm.js +5 -0
- package/tests/stubs/dsh-session.js +7 -0
- package/tests/stubs/dsh-tools.js +101 -0
- package/tests/tool-schema.test.mjs +73 -0
- package/tests/tools.test.mjs +184 -0
- package/tests/ui-defaults.test.mjs +57 -0
- package/tests/update-fields.test.mjs +138 -0
- package/tests/vector-migration.test.mjs +733 -0
- package/tests/vector-profile.test.mjs +425 -0
- package/tests/visibility-migration.test.mjs +152 -0
- package/tests/webui-auth.test.mjs +454 -0
- package/tests/webui.test.mjs +164 -0
- package/tests/windows-env.test.mjs +89 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# 情绪模型改造方案:四项构成与双辅助轴
|
|
2
|
+
|
|
3
|
+
状态:**已实施(P0–P3)**,WebUI 只读展示随 P2 落地
|
|
4
|
+
版本:v3(2026-10-30)
|
|
5
|
+
适用范围:私有记忆的本地情绪读取、记忆写入与召回排序;不扩展全局记忆。
|
|
6
|
+
|
|
7
|
+
> 本文仍是目标契约:实现应与本文一致;不一致时以本文为准并修实现。实施结果与三处已修正的偏差见 §10。
|
|
8
|
+
|
|
9
|
+
## 1. 目标与边界
|
|
10
|
+
|
|
11
|
+
### 1.1 目标
|
|
12
|
+
|
|
13
|
+
1. 在不调用模型的前提下,把当前单一 valence 扩展为四项情绪构成、arousal 和 dominance。
|
|
14
|
+
2. 保留现有 valence 读写兼容性,并为旧条目提供确定的降级路径。
|
|
15
|
+
3. 让情绪对召回的影响有界、可关闭、可回归测试。
|
|
16
|
+
4. 让后台整理、远程整理和前台写入明确区分情绪来源。
|
|
17
|
+
|
|
18
|
+
### 1.2 非目标
|
|
19
|
+
|
|
20
|
+
- 不给全局记忆增加情绪字段。
|
|
21
|
+
- 不把情绪写入注入文本或记忆正文。
|
|
22
|
+
- 不改变 peak、confidence、accuracyOf、negativityBias 的既有语义。
|
|
23
|
+
- 不引入模型调用或真实模型标注作为运行时依赖。
|
|
24
|
+
- 不在本阶段制作 WebUI 编辑控件;读取展示可以在后续阶段加入。
|
|
25
|
+
|
|
26
|
+
## 2. 当前实现事实
|
|
27
|
+
|
|
28
|
+
在开始实现前,以代码为准的现状是:
|
|
29
|
+
|
|
30
|
+
| 位置 | 当前行为 |
|
|
31
|
+
|---|---|
|
|
32
|
+
| lib/mood.js:29-71 | readMood、moodDecay、moodCongruence 只处理 valence/arousal |
|
|
33
|
+
| lib/state.js:893-900 | 会话 mood 只保存 valence/arousal/at |
|
|
34
|
+
| lib/memory.js:1135-1141 | 条目只写入 moodValence/arousal |
|
|
35
|
+
| lib/tools.js:190-193,309-320 | 工具尚无 emotion/dominance 参数 |
|
|
36
|
+
| lib/prompts.js:85-90 | 远程 JSON 契约尚无 emotion 字段 |
|
|
37
|
+
| lib/remote-summary.js:15-40 | 远程解析只保留现有 mood 字段 |
|
|
38
|
+
| lib/rpc.js:87-90 | RPC 只投影 moodValence/arousal |
|
|
39
|
+
|
|
40
|
+
本文中的路径和行号只用于定位现状;实现时应按符号搜索,不依赖固定行号。
|
|
41
|
+
|
|
42
|
+
## 3. 规范化数据契约
|
|
43
|
+
|
|
44
|
+
### 3.1 情绪构成
|
|
45
|
+
|
|
46
|
+
使用四个固定轴:joy、anger、sorrow、fear。
|
|
47
|
+
|
|
48
|
+
有效的非空 emotion 满足:
|
|
49
|
+
- 四个键都存在;值必须是有限数。
|
|
50
|
+
- 每个值必须大于等于 0。
|
|
51
|
+
- 四项之和必须大于 0;写入前归一化到和为 1。
|
|
52
|
+
- 归一化误差要求 abs(sum - 1) < 1e-9。
|
|
53
|
+
|
|
54
|
+
emotion: null 是规范的中性表示。为了兼容工具调用,显式全零对象也接受,但在校验后规范化为 null;它不能被当作四轴全都有少量情绪。
|
|
55
|
+
|
|
56
|
+
非法输入必须拒绝并在错误中包含 emotion:缺键、非有限数、负数、数组、字符串和非正和都不能静默修复。实现可对极小浮点误差使用固定容差,但不得把负数 clamp 成合法值。
|
|
57
|
+
|
|
58
|
+
### 3.2 强度与掌控感
|
|
59
|
+
|
|
60
|
+
| 字段 | 范围 | 缺失默认 | 说明 |
|
|
61
|
+
|---|---:|---:|---|
|
|
62
|
+
| arousal | 0..1 | 0.35 | 情绪激活程度;显式 0 保留为 0 |
|
|
63
|
+
| dominance | -1..1 | 0 | 主动/失控程度;显式 0 保留为 0 |
|
|
64
|
+
|
|
65
|
+
缺失和显式值必须区分:缺失使用默认值,显式 arousal: 0 或 dominance: 0 不得被 || 覆盖。
|
|
66
|
+
|
|
67
|
+
内部存储、工具和远程协议统一使用字段 dominance,不再引入 mood_dominance。若未来必须兼容该别名,只能在输入边界映射一次,内部和输出只保留 dominance。
|
|
68
|
+
|
|
69
|
+
### 3.3 记忆条目字段
|
|
70
|
+
|
|
71
|
+
| 字段 | 类型/范围 | 说明 |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| emotion | 四轴对象或 null | 事件的情绪构成 |
|
|
74
|
+
| arousal | 0..1 | 事件激活程度 |
|
|
75
|
+
| dominance | -1..1 | 事件中的主动/失控程度 |
|
|
76
|
+
| emotionSource | model/session/content/unknown | 系统审计字段 |
|
|
77
|
+
| moodValence | -1..1 | 派生的旧路径兼容投影 |
|
|
78
|
+
| peak | boolean | 保持现有语义 |
|
|
79
|
+
|
|
80
|
+
emotionSource 不属于普通用户可编辑字段:model 表示工具参数显式提供;session 表示允许继承当前会话 mood;content 表示从记忆正文读取;unknown 表示没有足够证据。
|
|
81
|
+
|
|
82
|
+
旧条目没有 emotion 时保持原样,不从旧 valence 反推四轴。
|
|
83
|
+
|
|
84
|
+
## 4. 本地读取算法
|
|
85
|
+
|
|
86
|
+
### 4.1 readMood 返回值
|
|
87
|
+
|
|
88
|
+
返回值必须继续包含 valence,并追加目标字段:
|
|
89
|
+
|
|
90
|
+
{ valence, emotion, arousal, dominance }
|
|
91
|
+
|
|
92
|
+
现有否定预处理必须保留。实现建议按句切分,在每句内取各轴的最大命中权重,再按规则合并;不得继续使用会把多个命中平均到接近中性的旧算法。
|
|
93
|
+
|
|
94
|
+
- 缓和词按所在分句生效:有点、稍微、一点点、a bit、somewhat。
|
|
95
|
+
- 一点都、一点也不、一点也没不属于缓和词。
|
|
96
|
+
- 强度词和重复标点可以提高 arousal,但必须有明确上限。
|
|
97
|
+
- 未命中可靠情绪词时返回 emotion: null,而不是强行生成均匀分布。
|
|
98
|
+
|
|
99
|
+
### 4.2 dominance
|
|
100
|
+
|
|
101
|
+
初始实现采用可解释的词表加构成先验:
|
|
102
|
+
|
|
103
|
+
dominance = clamp(lexicalDominance + 0.4 * (anger - fear), -1, 1)
|
|
104
|
+
|
|
105
|
+
词表中存在歧义词时,不能把结果宣称为事实;dominance 只作为有界排序辅助。词表测试必须覆盖高控制、低控制、怒和惧的相反方向。
|
|
106
|
+
|
|
107
|
+
### 4.3 派生 valence
|
|
108
|
+
|
|
109
|
+
必须先确定并测试纯轴结果,再实现公式。v3 采用以下固定系数:
|
|
110
|
+
|
|
111
|
+
raw = 0.8 * joy - 0.8 * anger - 0.8 * sorrow - 0.8 * fear
|
|
112
|
+
moodValence = clamp(raw, -1, 1)
|
|
113
|
+
|
|
114
|
+
这保证四个纯负轴都为负,纯喜为正;emotion 的构成仍由四轴本身表达,valence 只是旧路径的兼容投影。所有纯轴和代表性复合轴都必须有单元测试,不能同时保留与该结论冲突的经验公式。
|
|
115
|
+
|
|
116
|
+
### 4.4 衰减
|
|
117
|
+
|
|
118
|
+
情绪构成不按比例衰减;强度和掌控感衰减回基线:
|
|
119
|
+
|
|
120
|
+
fade = 0.5 ** (minutes / 90)
|
|
121
|
+
arousal = 0.35 + (arousal - 0.35) * fade
|
|
122
|
+
dominance = dominance * fade
|
|
123
|
+
|
|
124
|
+
当 fade < 0.35 时,emotion 变为 null。边界是严格小于;fade === 0.35 仍保留构成。即使 emotion 变为 null,arousal 和 dominance 仍返回并继续衰减。
|
|
125
|
+
|
|
126
|
+
## 5. 召回语义与兼容降级
|
|
127
|
+
|
|
128
|
+
### 5.1 新路径
|
|
129
|
+
|
|
130
|
+
双方都有合法 emotion 时:
|
|
131
|
+
|
|
132
|
+
overlap(p, q) = sum(min(p[i], q[i]))
|
|
133
|
+
moodBias = 1 + 0.3 * fab * (2 * overlap - 1)
|
|
134
|
+
|
|
135
|
+
moodBias 必须限制在 [0.7, 1.3]。dominance 双方都有有限值时:
|
|
136
|
+
|
|
137
|
+
domBias = 1 + 0.2 * (0.5 - abs(entry.dominance - context.dominance) / 2)
|
|
138
|
+
|
|
139
|
+
domBias 必须限制在 [0.9, 1.1]。任一 dominance 缺失或非法时,domBias 必须为 1,而不是传播 NaN。
|
|
140
|
+
|
|
141
|
+
### 5.2 旧路径
|
|
142
|
+
|
|
143
|
+
读路径必须使用唯一分流:
|
|
144
|
+
1. 双方都有合法 emotion:使用 overlap;
|
|
145
|
+
2. 否则双方都有可用 legacy valence:使用既有 moodCongruence/fadedValence;
|
|
146
|
+
3. 否则:情绪乘数为 1。
|
|
147
|
+
|
|
148
|
+
旧条目不反推 emotion。新字段接入召回后,必须验证旧条目、旧会话 mood 和缺失字段不会产生 NaN。
|
|
149
|
+
|
|
150
|
+
### 5.3 门控
|
|
151
|
+
|
|
152
|
+
门控是三条独立条件的 OR:
|
|
153
|
+
|
|
154
|
+
emotionActive = (emotion !== null && maxShare(emotion) >= 0.35) || abs(arousal - 0.35) >= 0.1 || abs(dominance) >= 0.25
|
|
155
|
+
|
|
156
|
+
emotion === null 只会关闭第一条,不会关闭 arousal 或 dominance 两条。必须测试 null+高 arousal 开启、null+高 dominance 开启、全基线关闭,以及非空低 share。
|
|
157
|
+
|
|
158
|
+
arousal intensity 代理应使用 max(arousal, maxShare(emotion), legacyValenceProxy, peak ? 1 : 0);没有 emotion 时只使用旧字段和 arousal。
|
|
159
|
+
|
|
160
|
+
## 6. 写入、远程和 fork 语义
|
|
161
|
+
|
|
162
|
+
### 6.1 工具参数
|
|
163
|
+
|
|
164
|
+
memu_private_remember 和 memu_private_update 使用 emotion: object|null、arousal: number、dominance: number。
|
|
165
|
+
|
|
166
|
+
工具描述必须说明:emotion 描述记忆所述事件,不是整理时的心情;省略、null、全零的语义分别是继承、明确中性、规范化为 null。对工程事实(owner=about_project)默认使用 null。
|
|
167
|
+
|
|
168
|
+
更新操作单独定义:省略字段表示保留原值;显式 null 清除 emotion 并将来源设为 unknown;显式对象经过同一套校验和归一化。
|
|
169
|
+
|
|
170
|
+
### 6.2 远程 JSON
|
|
171
|
+
|
|
172
|
+
远程协议只输出一个 JSON 对象,字段使用统一名称:
|
|
173
|
+
|
|
174
|
+
{ memories: [{ title, content, emotion: null, arousal: 0.35, dominance: 0, mood_valence: 0, source: inferred, visibility: shared }] }
|
|
175
|
+
|
|
176
|
+
visibility 只允许当前代码支持的 shared 或 secret;不得再使用已废弃的 session。远程缺字段时回落到兼容默认;字段存在但非法时应记录可诊断错误并拒绝该条,不静默改成合法情绪。
|
|
177
|
+
|
|
178
|
+
### 6.3 后台整理 fork
|
|
179
|
+
|
|
180
|
+
后台 fork 不继承父会话的即时 mood。创建子 binding 时显式使用 mood: null,避免把整理时的心情误写成事件情绪。
|
|
181
|
+
|
|
182
|
+
写入来源优先级:模型显式 emotion -> model;允许继承且存在 session mood -> session;从该条 content 读取出可靠情绪 -> content;否则 emotion=null、dominance=0、arousal=0.35、source=unknown。工程事实固定使用中性。
|
|
183
|
+
|
|
184
|
+
## 7. 分期
|
|
185
|
+
|
|
186
|
+
### P0:本地读取,保持旧 API
|
|
187
|
+
|
|
188
|
+
只改 lib/mood.js 及其单元测试:四轴词表、校验/归一化、dominance、派生 valence、分句和衰减。返回值追加字段但保留 valence。P0 不是零召回行为变化:inject.js 会把读取结果写入 binding,arousal/valence 变化可能影响后续排序;若产品必须零变化,则 P0 必须由 feature flag 隔离新字段。
|
|
189
|
+
|
|
190
|
+
### P1:召回
|
|
191
|
+
|
|
192
|
+
在 lib/memory.js 接入 overlap、domBias、三腿门控和 intensity 代理;保留旧路径。补充 property tests、legacy fixtures 和 NaN 防护。
|
|
193
|
+
|
|
194
|
+
### P2:写入与协议
|
|
195
|
+
|
|
196
|
+
更新 state.js、memory.js、tools.js、prompts.js、remote-summary.js、rpc.js。完成 remember/list/update/recall 往返测试,确认缺失字段和旧 index 可读取。
|
|
197
|
+
|
|
198
|
+
### P3:来源与 fork
|
|
199
|
+
|
|
200
|
+
更新 private.js、整理提示词和来源审计;验证 fork 不继承即时 mood,验证 emotionSource 不可被普通 update 伪造。
|
|
201
|
+
|
|
202
|
+
### P4:展示与文档
|
|
203
|
+
|
|
204
|
+
更新 UI 只读展示、文档和 CHANGELOG。全局、图谱、备份和 active-memory A/B 不属于本功能范围。
|
|
205
|
+
|
|
206
|
+
## 8. 验收标准
|
|
207
|
+
|
|
208
|
+
- 有效 emotion 归一后和为 1,所有分量非负。
|
|
209
|
+
- 缺键、负数、非有限数、数组和非法 dominance 被拒绝。
|
|
210
|
+
- 全零输入规范化为 null;null 不生成均匀分布。
|
|
211
|
+
- 四个纯轴的 derived valence 方向正确;复合轴结果可复算。
|
|
212
|
+
- overlap 自反、对称且在 [0,1];bias 范围始终满足上界。
|
|
213
|
+
- 门控测试覆盖 null、高 arousal、高 dominance 和低 share。
|
|
214
|
+
- decay 在 0.35 两侧有明确、稳定结果。
|
|
215
|
+
- readMood(...).valence 继续存在;旧 setSessionMood({ valence, arousal }) 继续可读。
|
|
216
|
+
- 旧条目无 emotion 时不反推四轴。
|
|
217
|
+
- 远程缺字段可解析,非法字段可诊断拒绝。
|
|
218
|
+
- fork 不继承父即时 mood;工程事实保持中性。
|
|
219
|
+
- remember -> list -> update -> recall 往返保留字段和来源。
|
|
220
|
+
|
|
221
|
+
项目门禁:node --check changed JS files;npm test 必须 exit 0(允许明确列出的 skipped);node scripts/acceptance-local.mjs;npm run check;git diff --check。文档中的测试数字不是永久常量,报告必须记录本次实际结果,不得以 fail 0 替代成功退出。
|
|
222
|
+
|
|
223
|
+
## 9. 风险与默认决策
|
|
224
|
+
|
|
225
|
+
- 词典推断不是事实识别;dominance 只能作为低权重排序信号。
|
|
226
|
+
- 四轴归一化会丢失强弱,强弱由 arousal 承载。
|
|
227
|
+
- fade < 0.35 有离散边界;实现不得私自改变契约。
|
|
228
|
+
- 远程模型可能输出不符合协议的 JSON;严格校验优先于尽量解析。
|
|
229
|
+
|
|
230
|
+
默认决策:四轴为 joy/anger/sorrow/fear;保留 valence;dominance 参与 P1 召回;门控阈值 0.35;旧路径至少保留一个版本;WebUI 本轮只读;远程和工具统一使用 dominance。
|
|
231
|
+
|
|
232
|
+
## 附录 A:初始词表与测试样例
|
|
233
|
+
|
|
234
|
+
初始词表至少覆盖:
|
|
235
|
+
- joy:开心、高兴、太好了、喜欢、爱、满意、惊喜、谢谢、期待;
|
|
236
|
+
- anger:气、烦、讨厌、气死、烦死、离谱、凭什么、受不了、恨;
|
|
237
|
+
- sorrow:难过、失望、崩溃、委屈、心疼、累、困、没意思;
|
|
238
|
+
- fear:害怕、恐慌、焦虑、担心、紧张、慌、吓死、不敢、万一;
|
|
239
|
+
- compounds:尴尬、嫉妒等必须有明确词条或明确组合规则,不能只出现在测试期望中。
|
|
240
|
+
|
|
241
|
+
复合预设是可解释的初始建议,不是硬编码事实:
|
|
242
|
+
|
|
243
|
+
| 表达 | 建议构成 | 建议 dominance |
|
|
244
|
+
|---|---|---:|
|
|
245
|
+
| 委屈 | sorrow 0.70 / anger 0.30 | -0.4 |
|
|
246
|
+
| 尴尬 | sorrow 0.50 / fear 0.50 | -0.5 |
|
|
247
|
+
| 嫉妒 | anger 0.50 / sorrow 0.30 / fear 0.20 | -0.1 |
|
|
248
|
+
| 心疼 | sorrow 0.80 / joy 0.20 | -0.2 |
|
|
249
|
+
| 不甘 | anger 0.60 / sorrow 0.40 | +0.4 |
|
|
250
|
+
|
|
251
|
+
验收样例必须把期望写成可计算范围:分别统计主导轴准确率、null precision/recall、arousal 误差和 dominance 误差;复合情绪允许 top-2 轴集合匹配并记录容差。
|
|
252
|
+
|
|
253
|
+
## 附录 B:实施前检查清单
|
|
254
|
+
|
|
255
|
+
- [ ] 实现并测试 normalizeEmotion、derivedValence、overlap 和门控真值表。
|
|
256
|
+
- [ ] 决定 P0 是否启用 feature flag;不要同时声称改 mood.js 且零召回变化。
|
|
257
|
+
- [ ] 明确 remember/update 的 omitted/null/zero 语义。
|
|
258
|
+
- [ ] 统一远程字段名和 visibility 枚举。
|
|
259
|
+
- [ ] 为旧 index、旧 binding、后台 fork 增加兼容测试。
|
|
260
|
+
- [ ] 以 npm test 退出码作为门禁,不引用过时的 878/取消测试数字。
|
|
261
|
+
- [x] 实现完成后再更新本文状态、版本和变更日志。
|
|
262
|
+
|
|
263
|
+
## 10. 实施结果(2026-10-30)
|
|
264
|
+
|
|
265
|
+
| 阶段 | 落地位置 | 结果 |
|
|
266
|
+
|---|---|---|
|
|
267
|
+
| P0 | `lib/mood.js` 重写:四轴词表、`normalizeEmotion`、`derivedValence`(统一 0.8 系数)、`maxShare`、`overlap`、`dominanceBias`、`emotionActive`、分句读取、`moodDecay` 的 0.35 边界 | 完成 |
|
|
268
|
+
| P1 | `lib/memory.js` 召回:overlap 路径 + 旧 valence 回落、dominance 乘数、三腿门控、`arousalBias` 的强度代理纳入 `maxShare` | 完成;`moodBias` 实测边界 0.7 / 1.3 |
|
|
269
|
+
| P2 | `state.js` / `tools.js` / `prompts.js` / `remote-summary.js` / `rpc.js` / `client.js` | 完成;remember → list → update → recall 往返通过 |
|
|
270
|
+
| P3 | `private.js` fork 显式 `mood: null`;四份整理提示词按回合还原情绪 | 完成 |
|
|
271
|
+
| P4 | 文档与只读展示 | 完成;WebUI 编辑控件按 §1.2 不做 |
|
|
272
|
+
|
|
273
|
+
实现期间发现并修正的三处偏差,记在这里避免重犯:
|
|
274
|
+
|
|
275
|
+
1. **`derivedValence` 早期草稿把 anger 记成正贡献**,与"四个纯负轴都为负"冲突。统一为 `0.8 * (joy - anger - sorrow - fear)`。
|
|
276
|
+
2. **`emotion: null` 曾在工具层被 `?? null` 与"省略"合并**,导致"明确中性"被会话心情覆盖。现在三态分开:`undefined` = 继承,`null` = 明确中性,全零对象 = 规范化为 null。
|
|
277
|
+
3. **`dominanceBias(null, x)` 曾因 `Number(null) === 0` 返回 1.05**。现在缺失或非法一律返回 1。
|
|
278
|
+
|
|
279
|
+
验收:`tests/emotion-contract.test.mjs`(17 条,由独立验收者按 §8 编写,不迁就实现)+ 全量 `npm test` 944 / 942 pass / 0 fail / exit 0。
|
|
280
|
+
|
|
281
|
+
端到端链路验证覆盖 8 条:读取、写入四态来源、持久化、更新三态、召回、旧条目降级、展示与协议、远程清洗;36 项断言全通过。
|
package/docs/env-and-commands.md
CHANGED
|
@@ -52,4 +52,17 @@ python -m memu commit payload.json --json --db <db>
|
|
|
52
52
|
|
|
53
53
|
`commit` payload:`{ recall_files: [{ name, track: memory, description, content }], resource: [], user: {} }`。
|
|
54
54
|
|
|
55
|
+
### 9.5 本地向量运行时(受管,2026-10-30)
|
|
56
|
+
|
|
57
|
+
**不再需要手工环境变量**:运行时(`onnxruntime-node` 1.31.1 + `onnxruntime-common` + `@huggingface/tokenizers`)由插件按 catalog pin 装进 `<dataDir>/downloads/embeddings/runtime/`,规格见 §14.13。`DSH_MEMU_EMBED_RUNTIME_DIR` 只剩「显式覆盖」用途:指向的目录里没有那三个包时会被忽略(`warn`)并回落到受管目录;也不需要 Ollama。
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
node scripts/local-runtime-install.mjs # 只读:打印 pin 的制品、状态、已装 checks
|
|
61
|
+
node scripts/local-runtime-install.mjs --download # 显式联网装(≈ 设置页「安装运行时」)
|
|
62
|
+
node scripts/local-runtime-install.mjs --download --cache <dir> # 用离线 tarball 装,仍按 pin 校验 bytes/sha256
|
|
63
|
+
node scripts/local-runtime-install.mjs --target <dir> --platform win32-x64 --id <modelId> --shallow
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
状态 `installed / absent / unsupported / downloading / verifying / installing / failed / cancelled`;`unsupported` = catalog 没 pin 该平台原生库(如 `darwin-x64`)。装失败只 `warn` + 回词法召回,不会自动改用云端 embedding。
|
|
67
|
+
|
|
55
68
|
---
|
package/docs/host-api.md
CHANGED
|
@@ -31,8 +31,9 @@
|
|
|
31
31
|
### 6.5 fork、followup 与「等它跑完」
|
|
32
32
|
|
|
33
33
|
- 会话 fork:`ctx.sessions.fork(source, boundary?, childSessionId?)`——**source 必须 live**,同步返回 `Session`,不自己启动 agent,没有 parent/delivery。错误码 `SESSION_NOT_FOUND | SESSION_NOT_LIVE | SESSION_ALREADY_EXISTS | INVALID_BOUNDARY`。
|
|
34
|
-
- 历史会话整理走**种子重建**:`sessionQuery.readSession(id)` → `{ session, events, inheritedEventCount }` → `buildForkSeed(events, lastSeq)`(`@deepseek-ai/dsh-session`,可选 peer,动态 import)→ `ctx.agents.create({ sessionId, meta: { cwd, parentSession, isSeeded, agentPreset }, seed, signal })` → `{ agent, dispose }`。
|
|
35
|
-
- `
|
|
34
|
+
- 历史会话整理走**种子重建**:`sessionQuery.readSession(id)` → `{ session, events, inheritedEventCount }` → `buildForkSeed(events, lastSeq)`(`@deepseek-ai/dsh-session`,可选 peer,动态 import)→ `ctx.agents.create({ sessionId, meta: { cwd, parentSession, isSeeded, agentPreset }, inheritedEventCount: lastSeq + 1, seed, agentOptions, signal })` → `{ agent, dispose }`。
|
|
35
|
+
- **fork 不继承父 Agent 的 options**:`agentOptions` 必须显式传顶层字段(不是 `meta`)。活跃会话取 `ctx.get("agents").get(id).session.requestHeader().config`;关闭会话取事件里最后一条 `request/header` 的 `data.header.config`。至少要有 `provider` / `model`(可带 `reasoningEffort`、`maxTokens`);两处都没有就不要创建 fork,否则标准预设会在首步因 `{{model}}` 无值而死。
|
|
36
|
+
- `agent.followup(msg)` 只入队并唤醒 driver,**返回 void**;`await agent.whenIdle()` 只表示 driver 停了,**不代表这一轮成功**。followup 前订阅 `ctx.on("session/event", (session, event) => ...)`,只认 `session === agent.session` 且 `event.type === "turn/end"`;`whenIdle` 后检查 `event.data.reason.kind === "completed"`,`error` / `aborted` / 没有 `turn/end` 都按失败处理,不能推进整理游标。fork 整理必须 `whenIdle` 后再 `dispose`。
|
|
36
37
|
- **followup 会把消息画在输入框上方**(可编辑/可删)——这就是「整理提示出现在待发栏」的原因。**v0.4 起所有整理走 fork(§3.11),后台作业一律不碰 followup**。
|
|
37
38
|
|
|
38
39
|
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
|
|
41
42
|
`ctx.schedule`(`@deepseek-ai/dsh-schedule`:create/list/catalog/history/delete/update,`every_seconds | daily | weekly | cron`,最小 60s)**绑定单个 sessionId、往该会话投递 prompt**、需要 sessionPersistence、且拒绝 subagent 会话——**不适合插件级全局清扫**。插件自己的清扫用 `ctx.interval`(cordis 随作用域回收),没有才退回 unref 的 `setInterval`。
|
|
42
43
|
|
|
43
|
-
事件:`ctx.on("agent/status", { agent, status })`,`AgentStatus = idle | running`;另有 `session/created`、`session/disposed`、`session/flush`;事件类型含 `turn/start`、`turn/end`(`step/*`、`user/message…`)与 `compaction/*`。**`turn/end` 不 await flush
|
|
44
|
+
事件:`ctx.on("agent/status", { agent, status })`,`AgentStatus = idle | running`;另有 `session/created`、`session/disposed`、`session/flush`;事件类型含 `turn/start`、`turn/end`(`step/*`、`user/message…`)与 `compaction/*`。**`turn/end` 不 await flush,不要在那里读存储;判 fork 成败只读事件对象本身。**
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
### 6.7 RPC、slot 与注入折叠
|
|
@@ -58,3 +59,34 @@ ctx.inject([sidebarRightTabs], ...) + ctx.slots.register({ name: sidebar.right.p
|
|
|
58
59
|
- producer label 就是 **kind 原文**(如 `plugin:dsh-memu-memory`),不可自定义。`form: recall` 只有在 `kind: session-reference` + `references[].label` 时才走专门的召回正文;本插件用 `recall` 只是拿一个折叠行,要一行可读的折叠摘要就用 `form: notice` + `source.summary`。
|
|
59
60
|
|
|
60
61
|
---
|
|
62
|
+
|
|
63
|
+
### 6.8 预设工具面与 guide 的真机证据边界(2026-10-29)
|
|
64
|
+
|
|
65
|
+
本节只记录**本轮可复核的真机事实**。证据来自本机 DSH Desktop `0.2.1-rc.3`(`resources/app.asar/package.json`)、插件 `1.1.2` 的 v4 会话日志;日志是多个 zstd frame 拼接,必须解完所有 frame 再计数。计数口径是**唯一 plugin 消息 id / `request/header.data.header.tools` 的实际工具名**,不是字符串出现次数,也不读取聊天正文。
|
|
66
|
+
|
|
67
|
+
#### 已证实:标准会话
|
|
68
|
+
|
|
69
|
+
- 标准会话 `session-<redacted-standard-a>`(`agentPreset: "standard"`,21 个非 session 事件):模型请求头 `request/header` 的工具表为 **82 个工具**,首尾两次一致;其中 `memu_*` 共 **10 个**:`memu_private_list`、`memu_private_recall`、`memu_private_remember`、`memu_private_update`、`memu_private_archive`、`memu_private_link`、`memu_private_unpin`、`memu_global_list`、`memu_global_recall`、`memu_status`。同一会话中 `===== memory anchor =====` 出现 1 次,锚点内是真实私有 id。
|
|
70
|
+
- 标准会话 `session-<redacted-standard-b>`(`agentPreset: "standard"`,本轮扫描时 1737 个非 session 事件):`<memory-guide>` 只对应 **1 个** plugin 消息 id(`<redacted-plugin-message-id>`),锚点 1 次。该 id 在多个快照/工具记录里重复出现是日志回放,不是重复投放。
|
|
71
|
+
- 因此“标准会话的工具表里有 `memu_*`”“标准会话 anchor/guide 只投放一次”是**已证实**。注意这是 2026-10-29 的历史快照:当前版本标准和预设都走 `<memory-guide rules="…">` 插件消息,system prompt 不再有本插件段。
|
|
72
|
+
|
|
73
|
+
#### 仅机制 + mock:独立人格预设 `yuike`
|
|
74
|
+
|
|
75
|
+
- 预设来源:`~/.dsh/.agent-presets/yuike/agent.cordis.yml`,15,645 bytes,mtime `2026-10-20T16:33:05Z`,sha256 `a5a978ab5cf62d24b3061bef5560ba7abfb84a635b73d8500fd893fe0fa1422e`;32 个 `name:` 条目中 **0 处 `memu`**,即该 bundle **没有列入 `dsh-memu-memory`**。
|
|
76
|
+
- 本机 95 个持久会话里 **没有 `agentPreset: "yuike"` 的会话**;报告中的“yuike 会话只有 1 行空壳”也无法作为真实对话证据。另有 3 个 `agentPreset: "yuike-ptc"` 会话(来自另一份 `dsh-yuike-ptc` bundle),13 次带工具表的请求头均无 `memu_*`,且日志中 `<memory-guide>`、`<memory-anchor>`、`<memory-context` 均为 0;这只能说明那个**不同 preset** 的历史状态,不能替代 `yuike` 的真机验证。
|
|
77
|
+
- 结论:`yuike` 的 guide 投放、anchor 送达、最终工具面**均只有 `tests/inject-guide.test.mjs` / `tests/preset-scope.test.mjs` 的 fake agents/fiber 机制证据**,真实会话未验证;不得写成“yuike 已验证有/没有 `memu_*` 工具”。
|
|
78
|
+
|
|
79
|
+
#### 未验证:显式列入本插件的预设
|
|
80
|
+
|
|
81
|
+
- 本机 `~/.dsh/.agent-presets/` 只有 `yuike` 这一份 agent preset;没有任何预设 bundle 显式列入 `dsh-memu-memory`。因此“预设显式列出本插件时工具可用”只有 mock 证据,本机没有可核对的真实会话。
|
|
82
|
+
- `lib/preset-scope.js` 的 `agents.list()` 真机形状本轮未直接采样;代码兼容 `{ agent, owner, ctx }` record、裸 agent、以及 `agents.get(sessionId)?.ctx` 回退,三种形状由 `tests/preset-scope.test.mjs` 的 fake agents 覆盖。真实宿主若只返回裸 agent 且无 `agents.get()` 上下文,仍会走“无 ctx”日志分支,不能当作已适配成功。
|
|
83
|
+
|
|
84
|
+
#### 不可推断
|
|
85
|
+
|
|
86
|
+
- **不能从“预设类型”推断工具面。** 最终工具名单由该预设自己的 `cordis.yml` 清单和宿主 scope 决定;本插件只能补注入监听,不能挂载工具,也不修改别人的预设文件。
|
|
87
|
+
- **工具已注册 ≠ 已向当前模型暴露。** 上文的工具名单取自 `request/header.data.header.tools`;没有该字段或只有标题请求的会话,不能拿插件侧注册表冒充模型可见工具。
|
|
88
|
+
- **guide 有 `<memory-guide>` ≠ 有 `memu_*`。** 正文自带“若本会话的工具列表里没有 `memu_*` 工具,请忽略本段”,它只声明规则可用性,不证明工具存在。
|
|
89
|
+
|
|
90
|
+
复核方法:用 `zlib.zstdDecompressSync` 逐 frame 解压 `~/.dsh/sessions/<bucket>/<id>/session.v4.jsonl.zstd`;只统计 `request/header` 的工具名、`session.v4` 消息 id 和 `<memory-guide`/`===== memory anchor =====` 标签。不要打印会话正文,不要把同一消息在快照中的重复出现计成多次投放。
|
|
91
|
+
|
|
92
|
+
---
|
package/docs/memory-semantics.md
CHANGED
|
@@ -28,21 +28,23 @@
|
|
|
28
28
|
|
|
29
29
|
### 8.4 注入
|
|
30
30
|
|
|
31
|
-
- 静态 guide
|
|
31
|
+
- 静态 guide 与动态记忆都走 plugin 消息;当前版本不再注册本插件的 system prompt 段。所有会话(含 `persona` `complete: true`)统一由 `lib/inject.js` 的 `ensureUnifiedGuide` 在**回合首步**投放一条 `<memory-guide rules="…">` 插件消息;`rulesKey` 由协议版本、domain 和实际渲染正文计算,domain/覆盖/默认稿变化或压缩丢失(`guideLost`)时在下一个合法首步替换,同一规则不逐回合重复。旧 binding 只有 `guideMessageAt`、没有 `guideRulesKey` 时视为旧版,补投一次;失败 fail-open,不写新版本。
|
|
32
32
|
- 首轮锚点 + 每用户回合最多一次;长工具回合仅有新的真实人类消息批次时追加一次,普通工具步骤不触发。私有优先、全局补充;冲突时私有胜。
|
|
33
33
|
- **`injectedIds` 只能记真正进入文本的条目**,否则从未展示的记忆被白白压制 8 轮。
|
|
34
34
|
- 锚点分段预算 pin 40% / core 30% / global 30%;pin 装不下时末尾明示「另有 N 条未展开」,**不静默丢弃**。
|
|
35
35
|
- 预算单位是**模型 token**不是字符:中文 ≈1 字符/token、英文 ≈3.4,一律走 `lib/budget.js`,**不要再用 `tokens * 3`**(英文口径,中文超支约 3 倍)。
|
|
36
36
|
- 压缩后下一条用户消息重注入锚点;子代理不注入。文件夹两开关都关 → 不注入(看 clamp 后生效值,不是 binding 原始值)。召回为空必须留信号(§1.10)。
|
|
37
|
+
- **召回排序的准确口径**:`privateWeighted` 只服务**私有召回流水线**(`privateRecall` 调用它);全局召回(`globalRecall`)走 `localRecall`,不经过它。cue 会经 `cueRecall` **间接**调用 `privateRecall`,所以**不能**概括成「cue 不走私有排序」。排序里的心境项默认关闭——`privateWeighted` 自己的缺省上下文就是 `moodEnabled: false`,真正打开它的是 `privateRecall`,条件是 `|valence| ≥ MOOD_MIN_STRENGTH` 或 `|arousal − BASE_AROUSAL| ≥ 0.1`(`BASE_AROUSAL` 来自 `lib/mood.js`);平缓对话里两项乘数恒为 1。`privateRecall` 的 topK 兜底是「传入 topK → `settings.privateTopK`(默认 5)→ 4」:引用 3 或 4 时必须说清是**设置默认值**还是**代码兜底值**,两个数不是同一个。
|
|
38
|
+
- **情绪模型(v3)**:一条记忆的情绪由四项构成 `joy / anger / sorrow / fear`(归一后和为 1)、强度 `arousal` 与掌控感 `dominance` 组成,`moodValence` 只是四轴在旧路径上的兼容投影。门控是三条**独立**条件的 OR:主导项 share ≥ 0.35、`|arousal − 0.35| ≥ 0.1`、`|dominance| ≥ 0.25`——「没有情绪构成」只关掉第一条,高唤起或明确的掌控感仍会打开排序项。双方都有合法 emotion 时用 overlap(各轴取较小值再求和)算一致性,任一侧缺失就回落到旧的 valence 路径;旧条目不从 valence 反推四轴。情绪只影响排序权重,不写进注入文本,也不进记忆正文。
|
|
37
39
|
|
|
38
40
|
### 8.5 提示词管理
|
|
39
41
|
|
|
40
42
|
- 默认稿在 `lib/prompts/*.md`(随包);用户覆盖在 `~/.dsh/dsh-memu-memory/prompts/*.md`;`manifest.json` 记 `defaultHash / userHash / defaultUpdated`。运行时统一 `lib/prompts.js` 的 `loadPrompt(key, vars)` / `loadPromptForDomain(key, vars, domain)`,**业务代码里不许硬编码正文**。
|
|
41
|
-
-
|
|
43
|
+
- 规则消息每次首步重新渲染;`rulesKey` 只含版本、domain 和渲染后的正文,不含时间/回合,避免每轮不同。`private-policy` 模板保留供查阅和覆盖记录,但当前版本不再自动投放主会话;字段契约由工具 schema 与运行校验提供。
|
|
42
44
|
- 模板只做单趟 `{{variable}}` 替换,变量值里的 `{{` 不会被二次展开——所以把记忆正文当变量塞进模板是安全的。**改成多趟插值前先想清楚这点。**
|
|
43
45
|
- 恢复默认 = 只删用户覆盖;全部恢复 = 清空覆盖目录。升级后:无覆盖用新默认,有覆盖保留并标 `defaultUpdated`。
|
|
44
46
|
- 分层:**作用域 → 类型页 → 项**。类型页是数据(`settings.promptDomains`,默认 `[rp, coding, mixed]` 全启用;`domainOverride` 默认 `rp`),`general` 内置不可删。变体 `<name>.<domain>.md` 由 `resolveDef` 解析:domain 在列表内且变体文件存在(默认**或**覆盖目录)→ 用变体,否则回落基础稿。
|
|
45
|
-
- **职责分工**:`system-guide.*`
|
|
47
|
+
- **职责分工**:`system-guide.*` 是唯一常驻规则,只写领域行为、检索时机、回答口径;`private-policy.*` 保留为字段规约参考,不再投放,避免两套常驻文本互相漂移。
|
|
46
48
|
- **只有三个 key 分域**:`system-guide`、`private-policy`、`private-consolidation`;`global-curator` 不分域。
|
|
47
49
|
- **远程与本地共用一份 `private-consolidation`**:正文讲「记什么、字段怎么填」,差异只有输出协议(`CONSOLIDATION_CONTRACTS.local` 调工具 / `.remote` 返回 JSON,代码追加)。不要拆第二份。
|
|
48
50
|
- RP 与 coding 是**内容**差异:coding 记事实(决定、约束、踩过的坑、工程偏好),召回当「可能过期的事实」,与代码冲突时以代码为准;rp 记经验(具体瞬间、半句原话、身体感觉、没说出口的话),允许模糊记错,不确定就标低 `confidence`。
|
|
@@ -87,22 +89,49 @@
|
|
|
87
89
|
### 8.9 空闲整理:什么时候跑,以及为什么可以什么都不写
|
|
88
90
|
- 触发:`settings.sleepConsolidation` 开着时,每 5 分钟扫一遍(`lib/cycle.js` 的 `runSleepPass`),挑「`lastTurnAt` 早于 `lastSleepAt` 且已经安静 ≥ `sleepAfterIdleMinutes`」的会话,交给 `requestPrivateConsolidation` 起一个 fork 去读转录、写记忆。**默认 3 分钟**(`lib/state.js` 的 `DEFAULT_SETTINGS`);旧值 8/20 的安装保留原选择,只是新装默认更短。
|
|
89
91
|
- 应用启动后 8 秒补跑一次(`STARTUP_SWEEP_MS`),因为定时器只在应用开着时存在:一段对话如果是在应用关着的时候结束的,它永远等不到那个「空闲窗口」——用户一回来发消息,`lastTurnAt` 就被顶新了。补偿每次最多处理 **2 个**会话(`runSleepPass` 的 `limit`),按最近活动排序。
|
|
90
|
-
- **有新回合 ≠ 该写记忆。** 进 fork
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
92
|
+
- **有新回合 ≠ 该写记忆。** 进 fork 之前先做两道本地评估(只看 `lastSleepSeq` 之后的转录):① `assessConsolidationValue` 的硬门槛——没有任何人类消息(只有插件通知、工具结果、心跳)→ 跳过;只有一条**纯应答**(「谢谢」「好的」「ok」「收到」,且没有任何工具调用)→ 跳过。**这里刻意不按字数判断**:字数少的偏好句(「我不吃香菜」)比一千字的闲聊更该留,长度是坏代理。② `assessWake` 的加权分(`lib/active-memory.js`):`H` 真人回合、`C` 真人文字 + 助手正文 + 去重后的成功工具结果(每个结果最多 2000 字符)、`U` 去重后的有效工具单元;`score = 0.35*min(H/3,2) + 0.45*min(C/4000,2) + 0.20*min(U/20,2)`,默认 `activeMemoryMinScore = 0.8`。空闲未到 `activeMemoryStarvationMinutes`(默认 15 分钟)时分数必须达标;到了饥饿阈值,**小于 2000 条事件的会话**无条件唤醒,**大 transcript 不吃饥饿豁免**——fork 的 seed 是整段前缀(真机 5913 条事件约 26MB),宿主虽然会压缩上下文,实测一次整理仍要约 4~22 万 input tokens(含缓存读,主要取决于记忆工具步数);一个「继续」级薄回合只有 0.14~0.43 分,不能靠饥饿反复付这笔钱;大会话里的密集回合仍按分数正常触发。
|
|
93
|
+
- **近期触发过记忆就不 fork。** `runSleepPass` 先看 `suppressionReason`:距上次整理不足 `activeMemoryCooldownMinutes`(默认 0)或距上次**注入过记忆**不足 `activeMemoryRecallCooldownMinutes`(默认 15 分钟)→ 跳过并发 `check` 日志。被抑制或低分跳过的回合返回 `settled: false`,不推进 `lastSleepSeq`/`lastSleepAt`;下一轮扫描重新评估,等真正有密集新内容再整理。
|
|
94
|
+
- 评估通过但整理写了 0 条 = 正常成功(`lastSleepAt` 前移),不会每小时重来。整理日志额外记录 `scanned/fresh/index +N`(扫描多少事件、上次游标后新增多少、索引实际新增多少),`check` 日志记录 `score/reason/parts/空闲分钟/会话事件数`,用来区分「没触发、被门挡住、提取空转」。
|
|
95
|
+
- 会话被删:host 对 `readSession` 报 `session "…" not found`,这是**不变量**而不是瞬时故障。`runSleepPass` 一旦看到就把 binding 标 `sessionGone: true` 并清掉重试计数,此后直接跳过(以前会按 2 的幂退避、封顶 1 小时,永远重试);同名会话后来真的收到新消息时(`lib/inbound.js`)会清掉这个标记。**"读不出来"不是"被删了"**:转录读失败(例如 `ENOENT`)由快照层显式标成可重试(`lib/consolidation-snapshot.js` 的 `failed()` → `retryable: true`),`runSleepPass` 只按退避重试、不打 `sessionGone`——`isGoneSessionError` 只认宿主那句 `session "…" not found` / `no such session`。
|
|
96
|
+
- 手动「整理这场对话」(RPC `generatePrivate`)带 `force: true`,跳过上面两道评估与抑制:用户已经决定了。
|
|
94
97
|
|
|
95
98
|
---
|
|
96
99
|
|
|
97
|
-
### 8.10 人格预设(agent preset
|
|
100
|
+
### 8.10 人格预设(agent preset)会话:注入要另挂,工具面按预设清单
|
|
98
101
|
|
|
99
102
|
预设会话的「模型可见插件集」不是 host composition,而是它自己那份 `cordis.yml`——整棵子树是独立 scope,`agent/pre-step` 只向自己的祖先冒泡,到不了部署级实例的监听器。所以预设会话最初的表现是「连 binding 都没有」,不是「写了没读到」。
|
|
100
103
|
|
|
101
|
-
插件侧的处理(`lib/preset-scope.js`):每 10 秒枚举 `ctx.get('agents').list()`,沿 `agent.ctx.fiber` 往上找本插件的 fiber——找不到(=
|
|
104
|
+
插件侧的处理(`lib/preset-scope.js`):每 10 秒枚举 `ctx.get('agents').list()`,沿 `agent.ctx.fiber` 往上找本插件的 fiber——找不到(= 不在自己这棵树里)才在该 agent 自己的 ctx(`lib/preset-scope.js:71` 取 `raw?.ctx || agent?.ctx || agents.get(sessionId)?.ctx`)上补挂 `registerInjection(actx, { guide: false, guideMessage: true })`(`lib/preset-scope.js:79`)。**首参必须是这个 agent 自己的 ctx**,不是部署级实例的 ctx,否则绑定与注入会挂错会话。`guide: false` 只表示**不注册 system prompt 的 guide 段**(预设未列本插件时没有 `memu_*` 工具;即便列了,`persona` 的 `complete: true` 也会丢弃该段),规则改由 `guideMessage: true` 用一次性消息投放,正文自带「工具列表里没有 `memu_*` 就忽略本段」。同树的正常会话跳过,避免注入两遍;agent 消失就卸载。
|
|
102
105
|
|
|
103
|
-
|
|
106
|
+
**提示词怎么办**:所有会话现在都走同一条消息路径,预设的 `complete: true` 不再造成投放差异;`ensureUnifiedGuide` 在回合首步追加 `<memory-guide rules="…">`,文末提示「工具列表里没有 `memu_*` 就忽略本段」。切域、改稿、默认稿升级和压缩丢失由 `rulesKey`/`guideLost` 处理;旧会话只有 `guideMessageAt` 时补投一次。
|
|
104
107
|
|
|
105
|
-
|
|
108
|
+
**边界要分三件事讲**:① **注入监听**——补挂到 agent 自己的 ctx 后,锚点与召回上下文能送达;② **guide 投放**——首步一条 `<memory-guide>` 消息,规则变化或压缩丢失时替换,不是每回合重复;正文自己声明「没有工具就忽略」;③ **最终工具面**——由该预设自己的 `cordis.yml` 清单与宿主 scope 决定,本插件**不能**从「有没有列上它」推断出一定有或一定没有。历史记录里曾出现过某些 RP 会话工具链含 `memu_private_*`,但 2026-10-29 复核无法把它归到本机 `yuike` bundle(该 bundle 未列本插件,且本机无 `agentPreset=yuike` 的真机会话),只能保留为未复核历史观察;**逐预设的真实工具名单仍须真机核实(W3)**,核实前不要写成「只有注入、没有工具」,也不要反过来写成「列上插件就一定可用」。真要在没有工具的会话里写记忆,进一次标准会话仍是最直接的办法——但如果该预设确实列了本插件且工具注册成功,记忆可以直接在那个会话里写。**不要为了这个去改别人的人格插件**:预设文件属于它的维护者,插件只能在自己的 scope 里做适配。
|
|
109
|
+
|
|
110
|
+
### 8.11 全局整理的间接输入边界
|
|
111
|
+
|
|
112
|
+
`startGlobalCuration` 不直接读私有 index:curator 输入里的 `existing_global_index` 来自 `compactGlobalIndex()`,只含全局条目(`archived` 被滤掉、`pending_review` 保留,正文截 600 字符)。但同一份输入还包含每个会话经 `condenseEvents`(`lib/curator.js:27`)压缩后的转录,这条是**内容级**通路,不按 `visibility` 过滤:
|
|
113
|
+
|
|
114
|
+
- 保留:`source.kind === 'user'` 的用户原话、助手正文、工具调用参数(截 300 字符)、工具结果正文(截 400 字符)。`memu_private_recall` 的 secret 条目正是从工具结果进入转录的,助手也可能在正文里转述它。
|
|
115
|
+
- 丢弃:所有插件消息(`source.kind` 不是 `user`),包括自动锚点与自动召回注入。原因是来源类型,**不是因为条目带 `secret`**;即便构造一条带 secret 的自动注入消息,它也止步于转录之前(`secret` 本身并不会自动注入,这不是一条生产通路)。
|
|
116
|
+
- 唯一的字符串过滤是 `redactSecrets`(`lib/paths.js`):只遮 `sk-…`、`Bearer …`、`api_key=…`、URL 内嵌凭据这类**凭据形状**,不识别普通私密叙述,也不看 `visibility`;提交层同样只过这一道,不校验内容来源。
|
|
117
|
+
|
|
118
|
+
因此 **`secret` 不是保险箱**:能进入 curator prompt 的内容,理论上也能被模型抄进输出并落到全局 index。合成探针已证明到「输入通道存在」,未证明真实模型会照抄,也未证明真机发生过泄漏。本次**不新增**防泄漏、来源追踪或输入白名单,止于「探针 + 文档」;哪些层必须剥离什么、助手/用户转述如何处理、失败与重试是否同样拦截,属于强契约,留待用户决定后单独实施。
|
|
119
|
+
|
|
120
|
+
探针:`tests/curator-probe.test.mjs` 用六通道各自唯一的合成标记,逐层断言压缩转录 → 真实 curator prompt → 模拟输出 → 提交后的全局 index;只写 `os.tmpdir()` + `DSH_MEMU_HOME`,不读取真实私有库。
|
|
121
|
+
|
|
122
|
+
### 8.12 向量来源与受管召回(MemU 路径)
|
|
123
|
+
|
|
124
|
+
默认 `memoryBackend="agent"` 的有效路由是 `lexical`,不调用 embedding。启用向量库(`memoryBackend="api"`)后,`vectorSource="local"` 对应 `managed-local`,`vectorSource="api"` 对应 `remote-api`;API 来源也可配置 localhost 端点。两者复用 MemU 存储与检索,不维护平行向量数据库。`memoryMode`(`local` / `cloud`)仅保留 MemU 存储兼容语义,不是 embedding 开关,也不覆盖来源选择。旧 `hybridRecall` / `hybridRecallMode` 生产开关已移除。
|
|
125
|
+
|
|
126
|
+
总结生成与向量路由解耦:常规私有整理通过 Agent fork;显式远程总结动作调用配置的聊天端点,其产物同样经 `privateRemember` 提交。启用向量不自动启用远程总结;聊天/总结模型不参与召回重排,也不作为向量失败回退。
|
|
127
|
+
|
|
128
|
+
受管召回只在候选阶段把词法结果与 MemU semantic retrieve 结果按 RRF(k=60)融合:先按 `isMemoryEligible`、`visibleToSession`、自动召回的 `secret` 排除及 context/scope 过滤,再按 canonical id 去重与候选上限处理。显式与自动召回共用候选融合;`touchAccessMany` 仍只对显式召回执行。精确记忆 id 走直达路径,切库期间中止以避免混合两个库。
|
|
129
|
+
|
|
130
|
+
服务不可用时仅回退词法并保留可见错误信号;不暗切远端、不调用 LLM。真实 embedding 质量、延迟和跨模型收益仍需单独评测;W8(助手重复提示)已决定不做,结论与理由见 `AGENTS.md`。
|
|
131
|
+
|
|
132
|
+
模型下载、进度与 profile 切换/迁移已有实现,但真实模型/runtime 下载、原生推理及切换全链路在本轮仍未验证。candidate / `pending` artifact 不是可用制品,必须通过制品、校验与迁移门槛后才可进入可用/激活状态。
|
|
133
|
+
|
|
134
|
+
回归与管线测试:`tests/hybrid-recall.test.mjs` 仅保留 RRF、去重、稳定排序、资格过滤与候选上限等通用融合 helper 覆盖;managed vector 行为由 `tests/managed-memory.test.mjs` 覆盖。
|
|
106
135
|
|
|
107
136
|
---
|
|
108
137
|
|