@ai-agent-forge/plugin-memory 0.88.1 → 0.88.3

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 (99) hide show
  1. package/README.md +10 -4
  2. package/agent-forge.json +1 -1
  3. package/bin/agent-forge-memory-inference.js +8 -0
  4. package/dist/capability.d.ts +42 -59
  5. package/dist/capability.d.ts.map +1 -1
  6. package/dist/capability.js +263 -2055
  7. package/dist/capability.js.map +1 -1
  8. package/dist/entry.d.ts +11 -0
  9. package/dist/entry.d.ts.map +1 -1
  10. package/dist/entry.js +38 -2
  11. package/dist/entry.js.map +1 -1
  12. package/dist/index.d.ts +5 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +7 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/inference-bin.d.ts +21 -0
  17. package/dist/inference-bin.d.ts.map +1 -0
  18. package/dist/inference-bin.js +85 -0
  19. package/dist/inference-bin.js.map +1 -0
  20. package/dist/memory/command-face.d.ts +86 -0
  21. package/dist/memory/command-face.d.ts.map +1 -0
  22. package/dist/memory/command-face.js +377 -0
  23. package/dist/memory/command-face.js.map +1 -0
  24. package/dist/memory/embedding-provider.d.ts.map +1 -1
  25. package/dist/memory/embedding-provider.js +14 -2
  26. package/dist/memory/embedding-provider.js.map +1 -1
  27. package/dist/memory/embedding-reranker.d.ts.map +1 -1
  28. package/dist/memory/embedding-reranker.js +14 -2
  29. package/dist/memory/embedding-reranker.js.map +1 -1
  30. package/dist/memory/enabling.d.ts +104 -0
  31. package/dist/memory/enabling.d.ts.map +1 -0
  32. package/dist/memory/enabling.js +321 -0
  33. package/dist/memory/enabling.js.map +1 -0
  34. package/dist/memory/hooks.d.ts +110 -0
  35. package/dist/memory/hooks.d.ts.map +1 -0
  36. package/dist/memory/hooks.js +478 -0
  37. package/dist/memory/hooks.js.map +1 -0
  38. package/dist/memory/inference-channel.d.ts +33 -0
  39. package/dist/memory/inference-channel.d.ts.map +1 -0
  40. package/dist/memory/inference-channel.js +101 -0
  41. package/dist/memory/inference-channel.js.map +1 -0
  42. package/dist/memory/inference-client.d.ts +25 -0
  43. package/dist/memory/inference-client.d.ts.map +1 -0
  44. package/dist/memory/inference-client.js +216 -0
  45. package/dist/memory/inference-client.js.map +1 -0
  46. package/dist/memory/inference-protocol.d.ts +95 -0
  47. package/dist/memory/inference-protocol.d.ts.map +1 -0
  48. package/dist/memory/inference-protocol.js +27 -0
  49. package/dist/memory/inference-protocol.js.map +1 -0
  50. package/dist/memory/inference-server.d.ts +70 -0
  51. package/dist/memory/inference-server.d.ts.map +1 -0
  52. package/dist/memory/inference-server.js +395 -0
  53. package/dist/memory/inference-server.js.map +1 -0
  54. package/dist/memory/inference-service-main.d.ts +31 -0
  55. package/dist/memory/inference-service-main.d.ts.map +1 -0
  56. package/dist/memory/inference-service-main.js +89 -0
  57. package/dist/memory/inference-service-main.js.map +1 -0
  58. package/dist/memory/ledger.d.ts +15 -0
  59. package/dist/memory/ledger.d.ts.map +1 -1
  60. package/dist/memory/ledger.js +35 -2
  61. package/dist/memory/ledger.js.map +1 -1
  62. package/dist/memory/model-cache-hygiene.d.ts +10 -0
  63. package/dist/memory/model-cache-hygiene.d.ts.map +1 -1
  64. package/dist/memory/model-cache-hygiene.js +15 -0
  65. package/dist/memory/model-cache-hygiene.js.map +1 -1
  66. package/dist/memory/preference-promotion.d.ts +0 -14
  67. package/dist/memory/preference-promotion.d.ts.map +1 -1
  68. package/dist/memory/preference-promotion.js +17 -0
  69. package/dist/memory/preference-promotion.js.map +1 -1
  70. package/dist/memory/recall-pipeline.d.ts +85 -0
  71. package/dist/memory/recall-pipeline.d.ts.map +1 -0
  72. package/dist/memory/recall-pipeline.js +678 -0
  73. package/dist/memory/recall-pipeline.js.map +1 -0
  74. package/dist/memory/retention-sweep.d.ts +59 -0
  75. package/dist/memory/retention-sweep.d.ts.map +1 -0
  76. package/dist/memory/retention-sweep.js +103 -0
  77. package/dist/memory/retention-sweep.js.map +1 -0
  78. package/dist/memory/store.d.ts +17 -2
  79. package/dist/memory/store.d.ts.map +1 -1
  80. package/dist/memory/store.js +18 -2
  81. package/dist/memory/store.js.map +1 -1
  82. package/dist/memory/vector-channel.d.ts +120 -0
  83. package/dist/memory/vector-channel.d.ts.map +1 -0
  84. package/dist/memory/vector-channel.js +495 -0
  85. package/dist/memory/vector-channel.js.map +1 -0
  86. package/dist/memory/vector-projection.d.ts +87 -0
  87. package/dist/memory/vector-projection.d.ts.map +1 -0
  88. package/dist/memory/vector-projection.js +215 -0
  89. package/dist/memory/vector-projection.js.map +1 -0
  90. package/dist/memory/write-face.d.ts +128 -0
  91. package/dist/memory/write-face.d.ts.map +1 -0
  92. package/dist/memory/write-face.js +504 -0
  93. package/dist/memory/write-face.js.map +1 -0
  94. package/dist/testing/memory-testkit.d.ts +2 -0
  95. package/dist/testing/memory-testkit.d.ts.map +1 -1
  96. package/dist/testing/memory-testkit.js +1 -0
  97. package/dist/testing/memory-testkit.js.map +1 -1
  98. package/package.json +7 -3
  99. package/plugin.json +1 -1
@@ -0,0 +1,504 @@
1
+ import { Type } from "typebox";
2
+ import { readMemorySettingsMode, writeMemorySettingsMode } from "./enabling.js";
3
+ import { buildMemoryAtomV1, } from "./foundation.js";
4
+ /**
5
+ * light→full 档位自动升级阈值 (2026-10-06 用户裁决「完全自动」; 记忆系统设计
6
+ * 「档位自动升级」): 本域活跃记忆 (store 计数, 过期记录不计) 达到该条数后,
7
+ * 写入提交旁路自动执行与 /memory full 同通道的动作 (下会话生效)。定档依据
8
+ * 真实模型规模梯度实测 (docs/plans/记忆档位自动升档阈值实测底稿.md): 衰减
9
+ * 平缓无拐点、主因是同主题近邻密度而非条数, 50,000 落在实测已见明显衰减区间
10
+ * 且远超个人稳态记忆量——未校准保守初值, 后续有真实评估证据只改本常量。
11
+ */
12
+ export const MEMORY_TIER_UPGRADE_STORE_THRESHOLD = 50_000;
13
+ /**
14
+ * 写入证据诚实化 (记忆写入与删除授权诚实化设计 §1.2, 2026-10-05 裁决): 各证据
15
+ * 类别的保守置信常量。经模型之手写入的内容不再硬编码 confidence 1 / explicit —
16
+ * 真正的 explicit 高置信只来自用户命令链 (/promote 的 confirmedAt)。
17
+ * 出厂默认值, 已登记 docs/design/产品默认值清单.md。
18
+ */
19
+ const MEMORY_EVIDENCE_CONFIDENCE = {
20
+ "user-stated": 0.9,
21
+ "tool-observed": 0.7,
22
+ inferred: 0.6,
23
+ };
24
+ /**
25
+ * 证据确定性映射 (设计 §1.2): 模型/机器声明的证据类别 → atom evidenceClass 与
26
+ * 偏好 envelope evidence.class。推断缺省映射为 derived/inferred — "把推断标成
27
+ * 显式确认"自此在写入面结构性不可能 (记忆系统设计 §4 :304 的落地)。
28
+ */
29
+ const ATOM_EVIDENCE_CLASS_OF = {
30
+ "user-stated": "explicit",
31
+ "tool-observed": "tool_or_test",
32
+ inferred: "derived",
33
+ };
34
+ const ENVELOPE_EVIDENCE_CLASS_OF = {
35
+ "user-stated": "explicit",
36
+ "tool-observed": "inferred",
37
+ inferred: "inferred",
38
+ };
39
+ export const TAG_FACET_NAMESPACE = "builtin.memory";
40
+ /** memory.changed 事件类型 (写入面与命令面的 capability 发布事件共用)。 */
41
+ export const EVENT_TYPE = "memory.changed";
42
+ const MAX_TAGS = 8;
43
+ // ---------------------------------------------------------------------------
44
+ // 语义召回管线 (2.4d6 S2, 记忆系统语义化重设计 §4) 与全局档位 (S1, §3)。
45
+ // 召回唯一路径: embed → KNN+FTS → RRF → 精排 → store 回查; tag 词面 bridge
46
+ // 已退役 (tags 仅入 FTS body 与管理面)。off 档 = 整个 capability 不注册。
47
+ // ---------------------------------------------------------------------------
48
+ /** memory_write 内容长度契约 (POC 报告 §7): reranker 512-token 上下文足够。 */
49
+ export const MEMORY_CONTENT_MAX_LENGTH = 700;
50
+ // ---------------------------------------------------------------------------
51
+ // 语义写入合并 (2.4d6 S3, 实施计划 S3 阈值标定定版)。full 档独有: light 档
52
+ // (bge-small-zh) 重复变体/同主题/无关三组余弦分布完全重叠, 余弦不可用作去重
53
+ // 信号 → 不探测不合并 (行为与 S3 之前逐字节一致); off 档整体不注册。
54
+ // ---------------------------------------------------------------------------
55
+ /** 合并下限 (bge-m3 实测: 重复变体 0.664–0.833, 同主题 ≤0.652 → 0.72 零误合并)。 */
56
+ const SEMANTIC_MERGE_T_HIGH = 0.72;
57
+ /** 冲突带下限 (bge-m3 实测: 无关语句最高 0.450 → 0.55 以下零越界)。 */
58
+ const SEMANTIC_MERGE_T_LOW = 0.55;
59
+ /** 写入探测的同 owner KNN 近邻数 (标定定版: top-5)。 */
60
+ const SEMANTIC_MERGE_KNN_LIMIT = 5;
61
+ /** supersedes/conflict 关系的 capability 命名空间 (与 tag facet 同域)。 */
62
+ const SEMANTIC_RELATION_NAMESPACE = "builtin.memory";
63
+ /** 嵌入向量已 L2 归一化 (provider 契约), 点积即余弦。 */
64
+ function cosineSimilarity(left, right) {
65
+ let dot = 0;
66
+ for (const [axis, value] of left.entries())
67
+ dot += value * (right[axis] ?? 0);
68
+ return dot;
69
+ }
70
+ /** tags 重叠判定 (至少一个共同 tag, trim 后大小写不敏感)。 */
71
+ function tagsOverlapIgnoreCase(left, right) {
72
+ if (left.length === 0 || right.length === 0)
73
+ return false;
74
+ const rightSet = new Set(right.map((tag) => tag.trim().toLowerCase()));
75
+ return left.some((tag) => rightSet.has(tag.trim().toLowerCase()));
76
+ }
77
+ /**
78
+ * 语义合并判定 (实施计划 S3 标定定版, 三分支):
79
+ * - 合并: cos ≥ 0.72, 或 0.55 ≤ cos < 0.72 且新 tags 与既有 tags 有重叠
80
+ * (复合条件救回跨语言改写 — 余弦偏低但 tags 同源; 取首个满足条件的近邻);
81
+ * - 冲突带: 0.55 ≤ cos < 0.72 且无 tag 重叠 → 双存 + conflict 关系
82
+ * (单向声明在新 atom 上; 正确性优先于存储省略, 宁可双存不错杀);
83
+ * - 低相似/无近邻: 普通新条目。
84
+ * 近邻按 KNN 序传入。
85
+ */
86
+ function decideSemanticMergePlan(input) {
87
+ for (const neighbor of input.neighbors) {
88
+ const merged = neighbor.cosine >= SEMANTIC_MERGE_T_HIGH ||
89
+ (neighbor.cosine >= SEMANTIC_MERGE_T_LOW && tagsOverlapIgnoreCase(input.newTags, neighbor.tags));
90
+ if (merged)
91
+ return { kind: "merge", targetMemoryId: neighbor.memoryId };
92
+ }
93
+ const conflictMemoryIds = input.neighbors
94
+ .filter((neighbor) => neighbor.cosine >= SEMANTIC_MERGE_T_LOW)
95
+ .map((neighbor) => neighbor.memoryId);
96
+ return conflictMemoryIds.length > 0 ? { kind: "conflict", conflictMemoryIds } : { kind: "new" };
97
+ }
98
+ const writeSchema = Type.Object({
99
+ content: Type.String({
100
+ description: "The memory content: one self-contained fact or preference statement",
101
+ maxLength: MEMORY_CONTENT_MAX_LENGTH,
102
+ }),
103
+ kind: Type.Union([Type.Literal("fact"), Type.Literal("preference")], {
104
+ description: "fact = a durable piece of information; preference = a user preference (requires subject)",
105
+ }),
106
+ subject: Type.Optional(Type.Union([Type.Literal("user"), Type.Literal("project"), Type.Literal("task"), Type.Literal("environment")], {
107
+ description: "Required when kind is preference: whose preference this is",
108
+ })),
109
+ tags: Type.Optional(Type.Array(Type.String({ minLength: 1 }), {
110
+ description: "Optional short keywords; stored with the memory, full-text searchable and shown in memory_list; at most 8",
111
+ maxItems: MAX_TAGS,
112
+ })),
113
+ evidence: Type.Optional(Type.Object({
114
+ class: Type.Union([Type.Literal("user-stated"), Type.Literal("tool-observed"), Type.Literal("inferred")], {
115
+ description: "How this content is known: user-stated = the user explicitly said it this session; tool-observed = a tool or test result verified it; inferred = your own conclusion. Default inferred — never dress an inference up as user-stated",
116
+ }),
117
+ }, {
118
+ description: "Evidence declaration (defaults to inferred). Inferences must not be saved as facts at all; the class only grades honest writes",
119
+ })),
120
+ });
121
+ export function createMemoryWriteFace(deps) {
122
+ const { logger, registerTool, publish, owner, sessionId, contractVersion, memoryMode, agentDir, now, store, schedulerApi, vectorProjection, vectorChannelCtl, resolveDomain, memoryToolInput, identityHash, statementOf, tagFacetsOf, } = deps;
123
+ const { manifest: capabilityManifest } = deps;
124
+ // 写入身份确定性: observationId `memory_write:<sessionId>:<n>` 哈希进
125
+ // memoryId。计数器实例本地, 恢复会话经 resolveDomain 重放抬高写序地板
126
+ // (advanceWriteSequenceFloor) — 完整语义见 capability.ts resolveDomain 注释。
127
+ const writeSequencePrefix = `memory_write:${sessionId}:`;
128
+ const writeSequenceOf = (observationId) => {
129
+ if (!observationId.startsWith(writeSequencePrefix))
130
+ return undefined;
131
+ const sequence = observationId.slice(writeSequencePrefix.length);
132
+ return /^\d+$/.test(sequence) ? Number(sequence) : undefined;
133
+ };
134
+ let writeSequence = 0;
135
+ const advanceWriteSequenceFloor = (sequence) => {
136
+ if (sequence > writeSequence)
137
+ writeSequence = sequence;
138
+ };
139
+ /**
140
+ * 写入埋点 (canonical 已提交后): fire-and-forget, 不阻塞工具返回、不抛出。
141
+ * embed/upsert 失败记一次 degraded, 该条由启动对账补齐 — canonical 与工具
142
+ * 返回不受影响 (向量投影只是 index 副本)。full 档语义合并 (S3) 时携带探测
143
+ * 产物: 复用探测已算出的新 statement 向量; 合并命中在 upsert 新行后移除被
144
+ * supersedes 的旧行 (upsert 新 id 是插入而非覆盖, 旧行必须显式移除, 失败走
145
+ * 同一 degraded 告警 — 残行由召回侧 superseded 过滤 + 对账跳过回填兜底)。
146
+ */
147
+ /**
148
+ * light→full 档位自动升级 (2026-10-06 用户裁决「完全自动」, 记忆系统设计
149
+ * 「档位自动升级」): canonical 提交成功后的微任务旁路 — 本域记忆 (store 域内
150
+ * 计数, 与召回同一 suite 可见性边界) 达到阈值即执行与 /memory full 完全同
151
+ * 通道的动作: 持久化 memorySettingsMode=full、清除拒绝标记、下会话生效
152
+ * (新模型经既有后台预热自动下载, 启动对账自动重建投影)。不打断当前会话、
153
+ * 不注入对话块, 仅结构化日志 + /memory 状态面可见。declined 会话不触发
154
+ * (首次启用的征询门与 declined 语义不在本裁决覆盖内)。
155
+ *
156
+ * 边界 (2026-10-06 复审收口): 用户在同一通道的显式表达优先于自动升档 —
157
+ * agent-dir 配置里已有显式 `memorySettingsMode: "light"` (即 /memory light
158
+ * 的最近一次落盘) 时跳过, 显式降档不被自动翻回; 档位来自 env 的会话
159
+ * (AGENT_FORGE_MEMORY_MODE / 旧别名非空, 解析链中 env 高于 agent-dir 配置)
160
+ * 升档永不生效, 同样跳过以免每会话无谓写盘 + 永不生效的失实提示。
161
+ * 持久化失败降级为一条 warn、不置闩锁 (下次写入重试) — 微任务异常即
162
+ * uncaughtException, 绝不允许写盘失败砸穿会话进程。
163
+ */
164
+ let tierUpgradeTriggered = false;
165
+ const maybeTierUpgradeAfterWrite = () => {
166
+ if (tierUpgradeTriggered || memoryMode !== "light" || vectorChannelCtl.declined())
167
+ return;
168
+ const envPinned = (process.env.AGENT_FORGE_MEMORY_MODE?.trim() ?? "") !== "" ||
169
+ (process.env.AGENT_FORGE_MEMORY_VECTOR?.trim() ?? "") !== "";
170
+ if (envPinned)
171
+ return;
172
+ let domainCount;
173
+ let explicitLightPersisted = false;
174
+ try {
175
+ const { domain } = resolveDomain();
176
+ domainCount = store.list({
177
+ owner,
178
+ ...(domain.kind === "suite" ? { suiteId: domain.suiteId } : {}),
179
+ }).length;
180
+ explicitLightPersisted = readMemorySettingsMode(agentDir) === "light";
181
+ }
182
+ catch {
183
+ // 规模探测失败不波及写路径; 下一次写入再探测。
184
+ return;
185
+ }
186
+ if (domainCount < MEMORY_TIER_UPGRADE_STORE_THRESHOLD || explicitLightPersisted)
187
+ return;
188
+ try {
189
+ writeMemorySettingsMode(agentDir, "full");
190
+ }
191
+ catch (error) {
192
+ logger?.warn("memory auto-upgrade: persisting the full preset failed; it retries on the next write", {
193
+ error: error instanceof Error ? error.message : String(error),
194
+ });
195
+ return;
196
+ }
197
+ tierUpgradeTriggered = true;
198
+ vectorChannelCtl.clearDeclined();
199
+ logger?.info(`memory auto-upgrade: ${domainCount} domain memories reached the ${MEMORY_TIER_UPGRADE_STORE_THRESHOLD} threshold — ` +
200
+ "preset persisted to full, effective next session (bge-m3 downloads via background preheat, disk ≥2GB; /memory light keeps it off while the explicit light persists)");
201
+ };
202
+ /**
203
+ * 当前 store 内被 supersedes 关系指向的 memoryId 集 (S3): 写入探测跳过已
204
+ * 继任的近邻 (其继任条目才是当前事实), 启动对账跳过回填 (投影只维护未继任
205
+ * 条目)。O(n) 扫描, 与 recall 关系扩散建图同量级。
206
+ */
207
+ const supersededMemoryIds = () => {
208
+ const superseded = new Set();
209
+ for (const record of store.list({ owner })) {
210
+ for (const relation of record.atom.relations) {
211
+ if (relation.kind === "supersedes" && relation.targetMemoryId !== undefined) {
212
+ superseded.add(relation.targetMemoryId);
213
+ }
214
+ }
215
+ }
216
+ return superseded;
217
+ };
218
+ /** 语义合并探测失败的一次性降级告警标记 (失败按普通新条目写入, 不刷屏)。 */
219
+ let semanticMergeProbeWarned = false;
220
+ /**
221
+ * 写入语义合并探测 (S3, full 档独有; 在 canonical 提交前执行 — supersedes/
222
+ * conflict 关系必须随新 atom 一次性提交, atom 不可变)。同一 vectorQueue 串
223
+ * 行域内: embed(statement) → 同 owner KNN top-5 → canonical 回查 (同域可见、
224
+ * 未被继任) → 重嵌各近邻的 canonical statement 计算余弦 (阈值作用在 canonical
225
+ * 文本的向量上, 不依赖可能陈旧的投影 body) → 三分支判定。时序保证"排除自身
226
+ * observationId 条目": 新 atom 尚未提交, 其 memoryId 不可能在投影中;
227
+ * observationId 重放 (deduped) 消费不到探测产物, 幂等零副作用。探测失败不
228
+ * 阻塞写入 (降级为普通新条目并告警一次); enabling 未落定/failed 不等待 —
229
+ * 写入不得被模型加载阻塞。
230
+ */
231
+ const semanticMergeProbe = (input) => {
232
+ const channelState = vectorChannelCtl.rawState();
233
+ if (channelState === "enabling" || channelState === "failed")
234
+ return Promise.resolve(undefined);
235
+ return vectorProjection.enqueue(async () => {
236
+ const channel = await vectorChannelCtl.ensure();
237
+ if (channel === undefined)
238
+ return undefined;
239
+ try {
240
+ const [vector] = await channel.embed.embed([input.statement]);
241
+ const hits = await channel.index.queryKnn(vector, {
242
+ owner,
243
+ limit: SEMANTIC_MERGE_KNN_LIMIT,
244
+ });
245
+ if (hits.length === 0)
246
+ return { plan: { kind: "new" }, vector };
247
+ const superseded = supersededMemoryIds();
248
+ const neighborAtoms = [];
249
+ for (const hit of hits) {
250
+ const record = store.get(hit.memoryId, {
251
+ owner,
252
+ ...(input.domain.kind === "suite" ? { suiteId: input.domain.suiteId } : {}),
253
+ });
254
+ if (record === undefined)
255
+ continue;
256
+ // 域隔离: 合并只发生在同域条目之间 (suite 写入不得 supersedes 跨
257
+ // suite/跨域条目 — 那会移除别域召回所需的投影行; promoted 的跨
258
+ // suite 可见偏好 canonical 归属其原 suite, 同样不作为合并目标)。
259
+ if (input.domain.kind === "suite"
260
+ ? record.atom.suiteId !== input.domain.suiteId
261
+ : record.atom.suiteId !== undefined) {
262
+ continue;
263
+ }
264
+ if (superseded.has(record.atom.memoryId))
265
+ continue;
266
+ neighborAtoms.push(record.atom);
267
+ }
268
+ if (neighborAtoms.length === 0)
269
+ return { plan: { kind: "new" }, vector };
270
+ const neighborVectors = await channel.embed.embed(neighborAtoms.map((atom) => statementOf(atom.payload)));
271
+ const neighbors = neighborAtoms.map((atom, position) => ({
272
+ memoryId: atom.memoryId,
273
+ tags: tagFacetsOf(atom),
274
+ cosine: cosineSimilarity(vector, neighborVectors[position] ?? []),
275
+ }));
276
+ return { plan: decideSemanticMergePlan({ newTags: input.newTags, neighbors }), vector };
277
+ }
278
+ catch (error) {
279
+ if (!semanticMergeProbeWarned) {
280
+ semanticMergeProbeWarned = true;
281
+ logger?.warn("memory semantic merge probe failed; the entry is written as a new memory", {
282
+ error: error instanceof Error ? error.message : String(error),
283
+ });
284
+ }
285
+ return undefined;
286
+ }
287
+ });
288
+ };
289
+ /**
290
+ * 判定结论 → 新 atom 的 relations (canonical 侧合并/冲突痕迹)。canonical
291
+ * atom 不可变且 Foundation 无"更新 payload"操作, 关系只在新 atom 上单向声明
292
+ * (可发现语义由此满足): supersedes 指向被合并的旧条目, conflict 指向冲突带
293
+ * 近邻。ids 由 (kind, target, content) 确定性派生。
294
+ */
295
+ const semanticMergeRelations = (plan, content) => {
296
+ const relation = (kind, targetMemoryId) => {
297
+ const relationId = `rel-${identityHash(`${kind}\u0000${targetMemoryId}\u0000${content}`)}`;
298
+ return {
299
+ relationId,
300
+ namespace: SEMANTIC_RELATION_NAMESPACE,
301
+ schemaVersion: 1,
302
+ kind,
303
+ targetMemoryId,
304
+ relationRevision: `r-${identityHash(relationId)}`,
305
+ };
306
+ };
307
+ if (plan.kind === "merge")
308
+ return [relation("supersedes", plan.targetMemoryId)];
309
+ if (plan.kind === "conflict")
310
+ return plan.conflictMemoryIds.map((target) => relation("conflict", target));
311
+ return [];
312
+ };
313
+ const buildWriteAtom = (input) => {
314
+ writeSequence += 1;
315
+ const observationId = `memory_write:${sessionId}:${writeSequence}`;
316
+ const memoryId = `mem-${identityHash(`${owner}\u0000${observationId}`)}`;
317
+ const occurredAt = new Date(now()).toISOString();
318
+ const sourceRef = { kind: "tool", id: `memory_write:${observationId}` };
319
+ const tags = (input.tags ?? []).map((tag) => tag.trim()).filter((tag) => tag !== "");
320
+ const facets = tags.map((tag) => ({
321
+ namespace: TAG_FACET_NAMESPACE,
322
+ schemaVersion: 1,
323
+ key: "tag",
324
+ value: tag,
325
+ }));
326
+ const evidenceClass = input.evidenceClass ?? "inferred";
327
+ const evidenceConfidence = MEMORY_EVIDENCE_CONFIDENCE[evidenceClass];
328
+ const preferenceEnvelope = input.kind === "preference"
329
+ ? {
330
+ subject: input.subject,
331
+ key: "statement",
332
+ preferredValue: input.content,
333
+ scope: { level: "profile-private" },
334
+ evidence: {
335
+ class: ENVELOPE_EVIDENCE_CLASS_OF[evidenceClass],
336
+ sourceRefs: [sourceRef],
337
+ confidence: evidenceConfidence,
338
+ },
339
+ applicabilityConfidence: evidenceConfidence,
340
+ }
341
+ : undefined;
342
+ return buildMemoryAtomV1({
343
+ memoryId,
344
+ contractVersion,
345
+ schemaVersion: 1,
346
+ scope: "long-term",
347
+ retentionMode: "long",
348
+ owner,
349
+ profileId: capabilityManifest.id,
350
+ ...(input.domain.kind === "suite" ? { suiteId: input.domain.suiteId } : {}),
351
+ retentionPolicyVersion: "retention@1",
352
+ memoryKind: input.kind === "preference" ? "preference" : "fact",
353
+ payload: { statement: input.content },
354
+ ...(preferenceEnvelope === undefined ? {} : { preference: preferenceEnvelope }),
355
+ occurredAt,
356
+ recordedAt: occurredAt,
357
+ sourceRefs: [sourceRef],
358
+ observationId,
359
+ sessionRefs: [sessionId],
360
+ agentInstanceRefs: [],
361
+ projectRefs: [],
362
+ subjectRefs: [],
363
+ facets,
364
+ relations: input.relations ?? [],
365
+ confidence: evidenceConfidence,
366
+ importance: 0.5,
367
+ evidenceClass: ATOM_EVIDENCE_CLASS_OF[evidenceClass],
368
+ contentRevision: `c-${identityHash(input.content)}`,
369
+ writeReason: input.writeReason ?? `${capabilityManifest.id}/memory_write`,
370
+ });
371
+ };
372
+ const writeTool = registerTool({
373
+ name: "memory_write",
374
+ label: "Write memory",
375
+ description: "Persist one durable memory (a fact or a user preference) scoped to the current suite; it stays recallable across sessions until forgotten",
376
+ parameters: writeSchema,
377
+ execute: async (first, second) => {
378
+ const input = memoryToolInput(first, second);
379
+ deps.assertNonEmptyString(input.content, "content");
380
+ // 长度契约 (混合检索工程化 §5): 超长显式拒绝并提示拆分, 而非静默截断。
381
+ if (input.content.length > MEMORY_CONTENT_MAX_LENGTH) {
382
+ throw new Error(`content is ${input.content.length} characters; the memory content limit is ${MEMORY_CONTENT_MAX_LENGTH} — split it into multiple self-contained memories`);
383
+ }
384
+ if (input.kind !== "fact" && input.kind !== "preference")
385
+ throw new Error('kind must be "fact" or "preference"');
386
+ if (input.kind === "preference") {
387
+ deps.assertNonEmptyString(input.subject, 'subject (required when kind is "preference")');
388
+ }
389
+ if (input.subject !== undefined)
390
+ deps.assertNonEmptyString(input.subject, "subject");
391
+ if (input.tags !== undefined) {
392
+ if (!Array.isArray(input.tags) || input.tags.length > MAX_TAGS) {
393
+ throw new Error(`tags must be an array of at most ${MAX_TAGS} strings`);
394
+ }
395
+ for (const tag of input.tags)
396
+ deps.assertNonEmptyString(tag, "tags entry");
397
+ }
398
+ // 证据声明解析 (诚实化设计 §1.1): 非法值显式拒绝, 不静默回落 — 模型
399
+ // 声明了证据就必须是合法类别。缺省 inferred (诚实默认)。
400
+ let evidenceClass = "inferred";
401
+ const rawEvidence = input.evidence;
402
+ if (rawEvidence !== undefined) {
403
+ if (rawEvidence === null || typeof rawEvidence !== "object") {
404
+ throw new Error("evidence must be an object with a class field");
405
+ }
406
+ const rawClass = rawEvidence.class;
407
+ if (rawClass !== "user-stated" && rawClass !== "tool-observed" && rawClass !== "inferred") {
408
+ throw new Error('evidence.class must be "user-stated", "tool-observed", or "inferred"');
409
+ }
410
+ evidenceClass = rawClass;
411
+ }
412
+ const { domain, domainName, ledger } = resolveDomain();
413
+ // 语义合并探测 (2.4d6 S3, full 档独有): light 档完全跳过 (写向量投影
414
+ // 不查近邻, 行为与现状一致)。探测在 canonical 提交前执行, 判定决定新
415
+ // atom 的 supersedes/conflict relations (atom 不可变, 随提交一次写入);
416
+ // 探测失败降级为普通新条目 (告警一次, 不阻塞写入)。
417
+ const mergeOutcome = memoryMode === "full"
418
+ ? await semanticMergeProbe({
419
+ statement: input.content,
420
+ newTags: input.tags ?? [],
421
+ domain,
422
+ })
423
+ : undefined;
424
+ const atom = buildWriteAtom({
425
+ content: input.content,
426
+ kind: input.kind,
427
+ domain,
428
+ evidenceClass,
429
+ ...(input.subject === undefined ? {} : { subject: input.subject }),
430
+ ...(input.tags === undefined ? {} : { tags: input.tags }),
431
+ ...(mergeOutcome === undefined
432
+ ? {}
433
+ : { relations: semanticMergeRelations(mergeOutcome.plan, input.content) }),
434
+ });
435
+ const observation = schedulerApi.submitObservation({
436
+ owner,
437
+ observationId: atom.observationId,
438
+ draft: atom,
439
+ });
440
+ if (observation.state === "rejected") {
441
+ throw new Error(`memory rejected: ${observation.reason ?? "rejected"}`);
442
+ }
443
+ // 持久化先行 (2026-10-06 复审): ledger append 先于 candidate accept,
444
+ // 与 bash 教训/promote 两条路径同序。append 失败 = 整个写入失败 (异常照
445
+ // 抛, store 无该 atom、账本无半行)——旧的先 accept 后 append 序会在
446
+ // append 失败时留下会话内可见、重启即失的幻影 atom。
447
+ // 拒绝不落账: append 只在 submitObservation 成功后执行, 且 observe →
448
+ // append → accept 之间无任何 await (本块全同步)——machine 记录由
449
+ // observe 刚建立或命中既有记录, accept 的 rejected 分支在该窗口结构性
450
+ // 不可达, 不会出现"账本有行而 candidate 被拒"。重复提交已 committed 的
451
+ // observation 时 observe 返回可区分的 "committed" 状态 (candidates.ts
452
+ // 状态枚举), 此时跳过 append——首次写入已有 durable 行, 避免账本重复
453
+ // 行 (即使重复, load 的 duplicateSkipped 容错也会跳过)。
454
+ if (observation.state !== "committed")
455
+ ledger.append(atom);
456
+ const candidate = schedulerApi.submitCandidate({
457
+ observationId: atom.observationId,
458
+ owner,
459
+ });
460
+ if (candidate.status === "rejected")
461
+ throw new Error(`memory rejected: ${candidate.reason ?? "rejected"}`);
462
+ if (candidate.status === "committed") {
463
+ // 向量投影埋点 (混合检索工程化): canonical 已提交, 投影写入异步旁路,
464
+ // 失败不阻塞不抛出 (vectorProjection.upsertAtom 内部消化)。full 档携带语义合并
465
+ // 探测产物 (复用探测向量; 合并命中同时移除被 supersedes 的旧行)。
466
+ vectorProjection.upsertAtom(atom, mergeOutcome);
467
+ queueMicrotask(maybeTierUpgradeAfterWrite);
468
+ }
469
+ await publish({
470
+ type: EVENT_TYPE,
471
+ version: 1,
472
+ correlationId: capabilityManifest.id,
473
+ data: { action: "written", memoryId: candidate.memoryId, memoryKind: atom.memoryKind, domain: domainName },
474
+ });
475
+ // 偏好晋升触发 (B2): the /promote capability command is the production
476
+ // caller of promotePreferenceToUserDefault; the tip is what tells the
477
+ // model (so it can tell the user) that the cross-suite promotion path
478
+ // exists. Facts have no promotion path — no tip.
479
+ return {
480
+ memoryId: candidate.memoryId,
481
+ kind: atom.memoryKind,
482
+ domain: domainName,
483
+ ...(atom.memoryKind === "preference"
484
+ ? {
485
+ tip: "[tip] If this is a general preference (not project-specific), the user can promote it across all suites using /promote.",
486
+ }
487
+ : {}),
488
+ };
489
+ },
490
+ });
491
+ return {
492
+ writeTool,
493
+ supersededMemoryIds,
494
+ buildWriteAtom,
495
+ maybeTierUpgradeAfterWrite,
496
+ tierUpgradePending: () => tierUpgradeTriggered,
497
+ clearTierUpgrade: () => {
498
+ tierUpgradeTriggered = false;
499
+ },
500
+ writeSequenceOf,
501
+ advanceWriteSequenceFloor,
502
+ };
503
+ }
504
+ //# sourceMappingURL=write-face.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-face.js","sourceRoot":"","sources":["../../src/memory/write-face.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 写入面(capability 拆分战役 slice 5):memory_write 工具、语义合并探测、写入\n * atom 构造与写后自动升档旁路。闭包状态(写序计数、探测降级告警闩、升档闩)归\n * 本模块工厂所有, 经 deps 显式注入, 不新增模块级可变状态; 搬移函数体逐字节保留。\n */\nimport type { CapabilityAPI, DisposableRegistration } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { readMemorySettingsMode, writeMemorySettingsMode } from \"./enabling.ts\";\nimport {\n\tbuildMemoryAtomV1,\n\ttype MemoryAtomV1,\n\ttype MemoryPreferenceEnvelopeV1,\n\ttype MemoryRelationV1,\n} from \"./foundation.ts\";\nimport type { DurableMemoryLedgerV1 } from \"./ledger.ts\";\nimport type { MemorySchedulerApiV1 } from \"./scheduler-api.ts\";\nimport type { MemoryStoreV1 } from \"./store.ts\";\nimport type { SuiteMemoryDomainV1 } from \"./suite-memory.ts\";\nimport type { MemoryVectorChannelV1 } from \"./vector-channel.ts\";\nimport type { MemoryVectorProjectionV1, SemanticMergeOutcomeV1, SemanticMergePlanV1 } from \"./vector-projection.ts\";\n\n/**\n * light→full 档位自动升级阈值 (2026-10-06 用户裁决「完全自动」; 记忆系统设计\n * 「档位自动升级」): 本域活跃记忆 (store 计数, 过期记录不计) 达到该条数后,\n * 写入提交旁路自动执行与 /memory full 同通道的动作 (下会话生效)。定档依据\n * 真实模型规模梯度实测 (docs/plans/记忆档位自动升档阈值实测底稿.md): 衰减\n * 平缓无拐点、主因是同主题近邻密度而非条数, 50,000 落在实测已见明显衰减区间\n * 且远超个人稳态记忆量——未校准保守初值, 后续有真实评估证据只改本常量。\n */\nexport const MEMORY_TIER_UPGRADE_STORE_THRESHOLD = 50_000;\n\n/**\n * 写入证据诚实化 (记忆写入与删除授权诚实化设计 §1.2, 2026-10-05 裁决): 各证据\n * 类别的保守置信常量。经模型之手写入的内容不再硬编码 confidence 1 / explicit —\n * 真正的 explicit 高置信只来自用户命令链 (/promote 的 confirmedAt)。\n * 出厂默认值, 已登记 docs/design/产品默认值清单.md。\n */\nconst MEMORY_EVIDENCE_CONFIDENCE = {\n\t\"user-stated\": 0.9,\n\t\"tool-observed\": 0.7,\n\tinferred: 0.6,\n} as const;\n\nexport type MemoryWriteEvidenceClassV1 = keyof typeof MEMORY_EVIDENCE_CONFIDENCE;\n\n/**\n * 证据确定性映射 (设计 §1.2): 模型/机器声明的证据类别 → atom evidenceClass 与\n * 偏好 envelope evidence.class。推断缺省映射为 derived/inferred — \"把推断标成\n * 显式确认\"自此在写入面结构性不可能 (记忆系统设计 §4 :304 的落地)。\n */\nconst ATOM_EVIDENCE_CLASS_OF: Readonly<Record<MemoryWriteEvidenceClassV1, MemoryAtomV1[\"evidenceClass\"]>> = {\n\t\"user-stated\": \"explicit\",\n\t\"tool-observed\": \"tool_or_test\",\n\tinferred: \"derived\",\n};\n\nconst ENVELOPE_EVIDENCE_CLASS_OF: Readonly<\n\tRecord<MemoryWriteEvidenceClassV1, MemoryPreferenceEnvelopeV1[\"evidence\"][\"class\"]>\n> = {\n\t\"user-stated\": \"explicit\",\n\t\"tool-observed\": \"inferred\",\n\tinferred: \"inferred\",\n};\n\nexport const TAG_FACET_NAMESPACE = \"builtin.memory\";\n/** memory.changed 事件类型 (写入面与命令面的 capability 发布事件共用)。 */\nexport const EVENT_TYPE = \"memory.changed\";\nconst MAX_TAGS = 8;\n\n// ---------------------------------------------------------------------------\n// 语义召回管线 (2.4d6 S2, 记忆系统语义化重设计 §4) 与全局档位 (S1, §3)。\n// 召回唯一路径: embed → KNN+FTS → RRF → 精排 → store 回查; tag 词面 bridge\n// 已退役 (tags 仅入 FTS body 与管理面)。off 档 = 整个 capability 不注册。\n// ---------------------------------------------------------------------------\n\n/** memory_write 内容长度契约 (POC 报告 §7): reranker 512-token 上下文足够。 */\nexport const MEMORY_CONTENT_MAX_LENGTH = 700;\n\n// ---------------------------------------------------------------------------\n// 语义写入合并 (2.4d6 S3, 实施计划 S3 阈值标定定版)。full 档独有: light 档\n// (bge-small-zh) 重复变体/同主题/无关三组余弦分布完全重叠, 余弦不可用作去重\n// 信号 → 不探测不合并 (行为与 S3 之前逐字节一致); off 档整体不注册。\n// ---------------------------------------------------------------------------\n\n/** 合并下限 (bge-m3 实测: 重复变体 0.664–0.833, 同主题 ≤0.652 → 0.72 零误合并)。 */\nconst SEMANTIC_MERGE_T_HIGH = 0.72;\n/** 冲突带下限 (bge-m3 实测: 无关语句最高 0.450 → 0.55 以下零越界)。 */\nconst SEMANTIC_MERGE_T_LOW = 0.55;\n/** 写入探测的同 owner KNN 近邻数 (标定定版: top-5)。 */\nconst SEMANTIC_MERGE_KNN_LIMIT = 5;\n/** supersedes/conflict 关系的 capability 命名空间 (与 tag facet 同域)。 */\nconst SEMANTIC_RELATION_NAMESPACE = \"builtin.memory\";\n\n/** 探测到的近邻 (canonical 回查后): 既有 tags + 与新向量的余弦。 */\ninterface SemanticMergeNeighborV1 {\n\treadonly memoryId: string;\n\treadonly tags: readonly string[];\n\treadonly cosine: number;\n}\n\n/** 嵌入向量已 L2 归一化 (provider 契约), 点积即余弦。 */\nfunction cosineSimilarity(left: readonly number[], right: readonly number[]): number {\n\tlet dot = 0;\n\tfor (const [axis, value] of left.entries()) dot += value * (right[axis] ?? 0);\n\treturn dot;\n}\n\n/** tags 重叠判定 (至少一个共同 tag, trim 后大小写不敏感)。 */\nfunction tagsOverlapIgnoreCase(left: readonly string[], right: readonly string[]): boolean {\n\tif (left.length === 0 || right.length === 0) return false;\n\tconst rightSet = new Set(right.map((tag) => tag.trim().toLowerCase()));\n\treturn left.some((tag) => rightSet.has(tag.trim().toLowerCase()));\n}\n\n/**\n * 语义合并判定 (实施计划 S3 标定定版, 三分支):\n * - 合并: cos ≥ 0.72, 或 0.55 ≤ cos < 0.72 且新 tags 与既有 tags 有重叠\n * (复合条件救回跨语言改写 — 余弦偏低但 tags 同源; 取首个满足条件的近邻);\n * - 冲突带: 0.55 ≤ cos < 0.72 且无 tag 重叠 → 双存 + conflict 关系\n * (单向声明在新 atom 上; 正确性优先于存储省略, 宁可双存不错杀);\n * - 低相似/无近邻: 普通新条目。\n * 近邻按 KNN 序传入。\n */\nfunction decideSemanticMergePlan(input: {\n\treadonly newTags: readonly string[];\n\treadonly neighbors: readonly SemanticMergeNeighborV1[];\n}): SemanticMergePlanV1 {\n\tfor (const neighbor of input.neighbors) {\n\t\tconst merged =\n\t\t\tneighbor.cosine >= SEMANTIC_MERGE_T_HIGH ||\n\t\t\t(neighbor.cosine >= SEMANTIC_MERGE_T_LOW && tagsOverlapIgnoreCase(input.newTags, neighbor.tags));\n\t\tif (merged) return { kind: \"merge\", targetMemoryId: neighbor.memoryId };\n\t}\n\tconst conflictMemoryIds = input.neighbors\n\t\t.filter((neighbor) => neighbor.cosine >= SEMANTIC_MERGE_T_LOW)\n\t\t.map((neighbor) => neighbor.memoryId);\n\treturn conflictMemoryIds.length > 0 ? { kind: \"conflict\", conflictMemoryIds } : { kind: \"new\" };\n}\n\nconst writeSchema = Type.Object({\n\tcontent: Type.String({\n\t\tdescription: \"The memory content: one self-contained fact or preference statement\",\n\t\tmaxLength: MEMORY_CONTENT_MAX_LENGTH,\n\t}),\n\tkind: Type.Union([Type.Literal(\"fact\"), Type.Literal(\"preference\")], {\n\t\tdescription: \"fact = a durable piece of information; preference = a user preference (requires subject)\",\n\t}),\n\tsubject: Type.Optional(\n\t\tType.Union([Type.Literal(\"user\"), Type.Literal(\"project\"), Type.Literal(\"task\"), Type.Literal(\"environment\")], {\n\t\t\tdescription: \"Required when kind is preference: whose preference this is\",\n\t\t}),\n\t),\n\ttags: Type.Optional(\n\t\tType.Array(Type.String({ minLength: 1 }), {\n\t\t\tdescription:\n\t\t\t\t\"Optional short keywords; stored with the memory, full-text searchable and shown in memory_list; at most 8\",\n\t\t\tmaxItems: MAX_TAGS,\n\t\t}),\n\t),\n\tevidence: Type.Optional(\n\t\tType.Object(\n\t\t\t{\n\t\t\t\tclass: Type.Union([Type.Literal(\"user-stated\"), Type.Literal(\"tool-observed\"), Type.Literal(\"inferred\")], {\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"How this content is known: user-stated = the user explicitly said it this session; tool-observed = a tool or test result verified it; inferred = your own conclusion. Default inferred — never dress an inference up as user-stated\",\n\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tdescription:\n\t\t\t\t\t\"Evidence declaration (defaults to inferred). Inferences must not be saved as facts at all; the class only grades honest writes\",\n\t\t\t},\n\t\t),\n\t),\n});\n\n/** 写入 atom 构造入参 (原 buildWriteAtom 内联参数类型, 随 slice 5 迁出)。 */\nexport interface MemoryWriteAtomInputV1 {\n\treadonly content: string;\n\treadonly kind: \"fact\" | \"preference\";\n\treadonly subject?: string;\n\treadonly tags?: readonly string[];\n\t/** 语义合并 (S3) 判定产生的 supersedes/conflict 关系; 缺省 = 空关系。 */\n\treadonly relations?: readonly MemoryRelationV1[];\n\t/** 写入来源标记; 缺省 = 模型工具面。 */\n\treadonly writeReason?: string;\n\t/**\n\t * 证据声明 (记忆写入与删除授权诚实化设计 §1): 缺省 inferred (诚实默认 —\n\t * 经模型之手写入的内容, 未经确认前都是推断); bash 教训等机器写入传\n\t * tool-observed。映射见 MEMORY_EVIDENCE_CONFIDENCE / *_EVIDENCE_CLASS_OF。\n\t */\n\treadonly evidenceClass?: MemoryWriteEvidenceClassV1;\n\treadonly domain: SuiteMemoryDomainV1;\n}\n\n/**\n * 写入面对 capability 闭包的显式依赖视图 (slice 5): 逐项来自\n * createMemoryCapabilityHarness 的闭包捕获核实。状态机 (vectorChannelCtl) 与\n * 写侧投影 (vectorProjection) 归 capability 装配所有, 本模块经对象只读消费;\n * capability 模块级纯函数经 deps 注入避免双头定义。\n */\nexport interface MemoryWriteFaceDepsV1 {\n\t/** capability 日志面 (api.logger): 探测降级/升档/持久化失败日志。 */\n\treadonly logger: CapabilityAPI[\"logger\"];\n\t/** 工具注册面 (api.registerTool): memory_write 经同一公共通道注册。 */\n\treadonly registerTool: CapabilityAPI[\"registerTool\"];\n\t/** 事件发布面 (api.publish): memory.changed 写入事件。 */\n\treadonly publish: CapabilityAPI[\"publish\"];\n\t/** 检索行 owner (capability.BUILTIN_MEMORY_OWNER)。 */\n\treadonly owner: string;\n\t/** 会话 id (harness sessionId): 写入 observationId 序列前缀。 */\n\treadonly sessionId: string;\n\t/** capability 清单 (capabilityManifest): profileId / writeReason / 事件 correlationId 来源。 */\n\treadonly manifest: { readonly id: string };\n\t/** 契约版本 (capability.MEMORY_CONTRACT_VERSION = transfer.MEMORY_CONTRACT_VERSION_V2)。 */\n\treadonly contractVersion: string;\n\t/** 已解析的全局档位 (harness memoryMode, off 已被工厂入口拦截)。 */\n\treadonly memoryMode: \"light\" | \"full\";\n\t/** agent 根目录 (MemoryCapabilityContext.agentDir): 档位持久化路径域。 */\n\treadonly agentDir: string;\n\t/** 时钟 (harness now)。 */\n\treadonly now: () => number;\n\t/** canonical store (per-capability 单例): 近邻回查/规模探测/继任扫描。 */\n\treadonly store: MemoryStoreV1;\n\t/** 写入观察调度 (capability.schedulerApi): submitObservation/submitCandidate。 */\n\treadonly schedulerApi: MemorySchedulerApiV1;\n\t/** 写侧投影 (slice 3a 的 vectorProjection): 探测入队与提交后埋点。 */\n\treadonly vectorProjection: MemoryVectorProjectionV1;\n\t/** 向量通道 (slice 3b 的 vectorChannelCtl): declined/rawState/ensure 只读与动作面。 */\n\treadonly vectorChannelCtl: MemoryVectorChannelV1;\n\t/** 域解析 (capability.resolveDomain): 每次写入调用时惰性解析 (B1 语义)。 */\n\treadonly resolveDomain: () => {\n\t\treadonly domain: SuiteMemoryDomainV1;\n\t\treadonly domainName: string;\n\t\treadonly ledger: DurableMemoryLedgerV1;\n\t};\n\t// ---- capability 模块级纯函数 (留守方, 经 deps 注入避免双头定义) ----\n\treadonly assertNonEmptyString: (value: unknown, label: string) => asserts value is string;\n\treadonly memoryToolInput: (first: unknown, second: unknown) => Record<string, unknown>;\n\treadonly identityHash: (value: string) => string;\n\treadonly statementOf: (payload: unknown) => string;\n\treadonly tagFacetsOf: (atom: MemoryAtomV1) => string[];\n}\n\n/** 写入面工厂产物: 工具注册项 + 装配 (resolveDomain 重放) 与钩子面消费的闭包视图。 */\nexport interface MemoryWriteFaceV1 {\n\t/** memory_write 工具注册项 (capability registrations 收集)。 */\n\treadonly writeTool: DisposableRegistration;\n\t/** 当前 store 内被 supersedes 关系指向的 memoryId 集 (S3): 投影对账 deps。 */\n\treadonly supersededMemoryIds: () => ReadonlySet<string>;\n\t/** 写入 atom 构造 (原 buildWriteAtom): bash 教训 (hooks, slice 7) 复用。 */\n\treadonly buildWriteAtom: (input: MemoryWriteAtomInputV1) => MemoryAtomV1;\n\t/** 写后自动升档旁路 (原 maybeTierUpgradeAfterWrite): 教训写入后复用。 */\n\treadonly maybeTierUpgradeAfterWrite: () => void;\n\t/** 升档声明只读视图: /memory 状态行的 pending 尾巴 (命令面消费)。 */\n\treadonly tierUpgradePending: () => boolean;\n\t/** 用户显式档位表达取代本实例升档声明 (/memory light, 命令面消费)。 */\n\treadonly clearTierUpgrade: () => void;\n\t/** 写入 observationId → 会话内序号 (resolveDomain 重放解析)。 */\n\treadonly writeSequenceOf: (observationId: string) => number | undefined;\n\t/** 持久化序号抬高写序地板 (resolveDomain 重放; 只升不降)。 */\n\treadonly advanceWriteSequenceFloor: (sequence: number) => void;\n}\n\nexport function createMemoryWriteFace(deps: MemoryWriteFaceDepsV1): MemoryWriteFaceV1 {\n\tconst {\n\t\tlogger,\n\t\tregisterTool,\n\t\tpublish,\n\t\towner,\n\t\tsessionId,\n\t\tcontractVersion,\n\t\tmemoryMode,\n\t\tagentDir,\n\t\tnow,\n\t\tstore,\n\t\tschedulerApi,\n\t\tvectorProjection,\n\t\tvectorChannelCtl,\n\t\tresolveDomain,\n\t\tmemoryToolInput,\n\t\tidentityHash,\n\t\tstatementOf,\n\t\ttagFacetsOf,\n\t} = deps;\n\tconst { manifest: capabilityManifest } = deps;\n\n\t// 写入身份确定性: observationId `memory_write:<sessionId>:<n>` 哈希进\n\t// memoryId。计数器实例本地, 恢复会话经 resolveDomain 重放抬高写序地板\n\t// (advanceWriteSequenceFloor) — 完整语义见 capability.ts resolveDomain 注释。\n\tconst writeSequencePrefix = `memory_write:${sessionId}:`;\n\tconst writeSequenceOf = (observationId: string): number | undefined => {\n\t\tif (!observationId.startsWith(writeSequencePrefix)) return undefined;\n\t\tconst sequence = observationId.slice(writeSequencePrefix.length);\n\t\treturn /^\\d+$/.test(sequence) ? Number(sequence) : undefined;\n\t};\n\tlet writeSequence = 0;\n\tconst advanceWriteSequenceFloor = (sequence: number): void => {\n\t\tif (sequence > writeSequence) writeSequence = sequence;\n\t};\n\n\t/**\n\t * 写入埋点 (canonical 已提交后): fire-and-forget, 不阻塞工具返回、不抛出。\n\t * embed/upsert 失败记一次 degraded, 该条由启动对账补齐 — canonical 与工具\n\t * 返回不受影响 (向量投影只是 index 副本)。full 档语义合并 (S3) 时携带探测\n\t * 产物: 复用探测已算出的新 statement 向量; 合并命中在 upsert 新行后移除被\n\t * supersedes 的旧行 (upsert 新 id 是插入而非覆盖, 旧行必须显式移除, 失败走\n\t * 同一 degraded 告警 — 残行由召回侧 superseded 过滤 + 对账跳过回填兜底)。\n\t */\n\t/**\n\t * light→full 档位自动升级 (2026-10-06 用户裁决「完全自动」, 记忆系统设计\n\t * 「档位自动升级」): canonical 提交成功后的微任务旁路 — 本域记忆 (store 域内\n\t * 计数, 与召回同一 suite 可见性边界) 达到阈值即执行与 /memory full 完全同\n\t * 通道的动作: 持久化 memorySettingsMode=full、清除拒绝标记、下会话生效\n\t * (新模型经既有后台预热自动下载, 启动对账自动重建投影)。不打断当前会话、\n\t * 不注入对话块, 仅结构化日志 + /memory 状态面可见。declined 会话不触发\n\t * (首次启用的征询门与 declined 语义不在本裁决覆盖内)。\n\t *\n\t * 边界 (2026-10-06 复审收口): 用户在同一通道的显式表达优先于自动升档 —\n\t * agent-dir 配置里已有显式 `memorySettingsMode: \"light\"` (即 /memory light\n\t * 的最近一次落盘) 时跳过, 显式降档不被自动翻回; 档位来自 env 的会话\n\t * (AGENT_FORGE_MEMORY_MODE / 旧别名非空, 解析链中 env 高于 agent-dir 配置)\n\t * 升档永不生效, 同样跳过以免每会话无谓写盘 + 永不生效的失实提示。\n\t * 持久化失败降级为一条 warn、不置闩锁 (下次写入重试) — 微任务异常即\n\t * uncaughtException, 绝不允许写盘失败砸穿会话进程。\n\t */\n\tlet tierUpgradeTriggered = false;\n\tconst maybeTierUpgradeAfterWrite = (): void => {\n\t\tif (tierUpgradeTriggered || memoryMode !== \"light\" || vectorChannelCtl.declined()) return;\n\t\tconst envPinned =\n\t\t\t(process.env.AGENT_FORGE_MEMORY_MODE?.trim() ?? \"\") !== \"\" ||\n\t\t\t(process.env.AGENT_FORGE_MEMORY_VECTOR?.trim() ?? \"\") !== \"\";\n\t\tif (envPinned) return;\n\t\tlet domainCount: number;\n\t\tlet explicitLightPersisted = false;\n\t\ttry {\n\t\t\tconst { domain } = resolveDomain();\n\t\t\tdomainCount = store.list({\n\t\t\t\towner,\n\t\t\t\t...(domain.kind === \"suite\" ? { suiteId: domain.suiteId } : {}),\n\t\t\t}).length;\n\t\t\texplicitLightPersisted = readMemorySettingsMode(agentDir) === \"light\";\n\t\t} catch {\n\t\t\t// 规模探测失败不波及写路径; 下一次写入再探测。\n\t\t\treturn;\n\t\t}\n\t\tif (domainCount < MEMORY_TIER_UPGRADE_STORE_THRESHOLD || explicitLightPersisted) return;\n\t\ttry {\n\t\t\twriteMemorySettingsMode(agentDir, \"full\");\n\t\t} catch (error) {\n\t\t\tlogger?.warn(\"memory auto-upgrade: persisting the full preset failed; it retries on the next write\", {\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\ttierUpgradeTriggered = true;\n\t\tvectorChannelCtl.clearDeclined();\n\t\tlogger?.info(\n\t\t\t`memory auto-upgrade: ${domainCount} domain memories reached the ${MEMORY_TIER_UPGRADE_STORE_THRESHOLD} threshold — ` +\n\t\t\t\t\"preset persisted to full, effective next session (bge-m3 downloads via background preheat, disk ≥2GB; /memory light keeps it off while the explicit light persists)\",\n\t\t);\n\t};\n\n\t/**\n\t * 当前 store 内被 supersedes 关系指向的 memoryId 集 (S3): 写入探测跳过已\n\t * 继任的近邻 (其继任条目才是当前事实), 启动对账跳过回填 (投影只维护未继任\n\t * 条目)。O(n) 扫描, 与 recall 关系扩散建图同量级。\n\t */\n\tconst supersededMemoryIds = (): ReadonlySet<string> => {\n\t\tconst superseded = new Set<string>();\n\t\tfor (const record of store.list({ owner })) {\n\t\t\tfor (const relation of record.atom.relations) {\n\t\t\t\tif (relation.kind === \"supersedes\" && relation.targetMemoryId !== undefined) {\n\t\t\t\t\tsuperseded.add(relation.targetMemoryId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn superseded;\n\t};\n\n\t/** 语义合并探测失败的一次性降级告警标记 (失败按普通新条目写入, 不刷屏)。 */\n\tlet semanticMergeProbeWarned = false;\n\n\t/**\n\t * 写入语义合并探测 (S3, full 档独有; 在 canonical 提交前执行 — supersedes/\n\t * conflict 关系必须随新 atom 一次性提交, atom 不可变)。同一 vectorQueue 串\n\t * 行域内: embed(statement) → 同 owner KNN top-5 → canonical 回查 (同域可见、\n\t * 未被继任) → 重嵌各近邻的 canonical statement 计算余弦 (阈值作用在 canonical\n\t * 文本的向量上, 不依赖可能陈旧的投影 body) → 三分支判定。时序保证\"排除自身\n\t * observationId 条目\": 新 atom 尚未提交, 其 memoryId 不可能在投影中;\n\t * observationId 重放 (deduped) 消费不到探测产物, 幂等零副作用。探测失败不\n\t * 阻塞写入 (降级为普通新条目并告警一次); enabling 未落定/failed 不等待 —\n\t * 写入不得被模型加载阻塞。\n\t */\n\tconst semanticMergeProbe = (input: {\n\t\treadonly statement: string;\n\t\treadonly newTags: readonly string[];\n\t\treadonly domain: SuiteMemoryDomainV1;\n\t}): Promise<SemanticMergeOutcomeV1 | undefined> => {\n\t\tconst channelState = vectorChannelCtl.rawState();\n\t\tif (channelState === \"enabling\" || channelState === \"failed\") return Promise.resolve(undefined);\n\t\treturn vectorProjection.enqueue(async (): Promise<SemanticMergeOutcomeV1 | undefined> => {\n\t\t\tconst channel = await vectorChannelCtl.ensure();\n\t\t\tif (channel === undefined) return undefined;\n\t\t\ttry {\n\t\t\t\tconst [vector] = await channel.embed.embed([input.statement]);\n\t\t\t\tconst hits = await channel.index.queryKnn(vector, {\n\t\t\t\t\towner,\n\t\t\t\t\tlimit: SEMANTIC_MERGE_KNN_LIMIT,\n\t\t\t\t});\n\t\t\t\tif (hits.length === 0) return { plan: { kind: \"new\" }, vector };\n\t\t\t\tconst superseded = supersededMemoryIds();\n\t\t\t\tconst neighborAtoms: MemoryAtomV1[] = [];\n\t\t\t\tfor (const hit of hits) {\n\t\t\t\t\tconst record = store.get(hit.memoryId, {\n\t\t\t\t\t\towner,\n\t\t\t\t\t\t...(input.domain.kind === \"suite\" ? { suiteId: input.domain.suiteId } : {}),\n\t\t\t\t\t});\n\t\t\t\t\tif (record === undefined) continue;\n\t\t\t\t\t// 域隔离: 合并只发生在同域条目之间 (suite 写入不得 supersedes 跨\n\t\t\t\t\t// suite/跨域条目 — 那会移除别域召回所需的投影行; promoted 的跨\n\t\t\t\t\t// suite 可见偏好 canonical 归属其原 suite, 同样不作为合并目标)。\n\t\t\t\t\tif (\n\t\t\t\t\t\tinput.domain.kind === \"suite\"\n\t\t\t\t\t\t\t? record.atom.suiteId !== input.domain.suiteId\n\t\t\t\t\t\t\t: record.atom.suiteId !== undefined\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (superseded.has(record.atom.memoryId)) continue;\n\t\t\t\t\tneighborAtoms.push(record.atom);\n\t\t\t\t}\n\t\t\t\tif (neighborAtoms.length === 0) return { plan: { kind: \"new\" }, vector };\n\t\t\t\tconst neighborVectors = await channel.embed.embed(neighborAtoms.map((atom) => statementOf(atom.payload)));\n\t\t\t\tconst neighbors: SemanticMergeNeighborV1[] = neighborAtoms.map((atom, position) => ({\n\t\t\t\t\tmemoryId: atom.memoryId,\n\t\t\t\t\ttags: tagFacetsOf(atom),\n\t\t\t\t\tcosine: cosineSimilarity(vector, neighborVectors[position] ?? []),\n\t\t\t\t}));\n\t\t\t\treturn { plan: decideSemanticMergePlan({ newTags: input.newTags, neighbors }), vector };\n\t\t\t} catch (error) {\n\t\t\t\tif (!semanticMergeProbeWarned) {\n\t\t\t\t\tsemanticMergeProbeWarned = true;\n\t\t\t\t\tlogger?.warn(\"memory semantic merge probe failed; the entry is written as a new memory\", {\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t});\n\t};\n\n\t/**\n\t * 判定结论 → 新 atom 的 relations (canonical 侧合并/冲突痕迹)。canonical\n\t * atom 不可变且 Foundation 无\"更新 payload\"操作, 关系只在新 atom 上单向声明\n\t * (可发现语义由此满足): supersedes 指向被合并的旧条目, conflict 指向冲突带\n\t * 近邻。ids 由 (kind, target, content) 确定性派生。\n\t */\n\tconst semanticMergeRelations = (plan: SemanticMergePlanV1, content: string): readonly MemoryRelationV1[] => {\n\t\tconst relation = (kind: \"supersedes\" | \"conflict\", targetMemoryId: string): MemoryRelationV1 => {\n\t\t\tconst relationId = `rel-${identityHash(`${kind}\\u0000${targetMemoryId}\\u0000${content}`)}`;\n\t\t\treturn {\n\t\t\t\trelationId,\n\t\t\t\tnamespace: SEMANTIC_RELATION_NAMESPACE,\n\t\t\t\tschemaVersion: 1,\n\t\t\t\tkind,\n\t\t\t\ttargetMemoryId,\n\t\t\t\trelationRevision: `r-${identityHash(relationId)}`,\n\t\t\t};\n\t\t};\n\t\tif (plan.kind === \"merge\") return [relation(\"supersedes\", plan.targetMemoryId)];\n\t\tif (plan.kind === \"conflict\") return plan.conflictMemoryIds.map((target) => relation(\"conflict\", target));\n\t\treturn [];\n\t};\n\n\tconst buildWriteAtom = (input: MemoryWriteAtomInputV1): MemoryAtomV1 => {\n\t\twriteSequence += 1;\n\t\tconst observationId = `memory_write:${sessionId}:${writeSequence}`;\n\t\tconst memoryId = `mem-${identityHash(`${owner}\\u0000${observationId}`)}`;\n\t\tconst occurredAt = new Date(now()).toISOString();\n\t\tconst sourceRef = { kind: \"tool\" as const, id: `memory_write:${observationId}` };\n\t\tconst tags = (input.tags ?? []).map((tag) => tag.trim()).filter((tag) => tag !== \"\");\n\t\tconst facets = tags.map((tag) => ({\n\t\t\tnamespace: TAG_FACET_NAMESPACE,\n\t\t\tschemaVersion: 1,\n\t\t\tkey: \"tag\",\n\t\t\tvalue: tag,\n\t\t}));\n\t\tconst evidenceClass = input.evidenceClass ?? \"inferred\";\n\t\tconst evidenceConfidence = MEMORY_EVIDENCE_CONFIDENCE[evidenceClass];\n\t\tconst preferenceEnvelope: MemoryPreferenceEnvelopeV1 | undefined =\n\t\t\tinput.kind === \"preference\"\n\t\t\t\t? {\n\t\t\t\t\t\tsubject: input.subject as MemoryPreferenceEnvelopeV1[\"subject\"],\n\t\t\t\t\t\tkey: \"statement\",\n\t\t\t\t\t\tpreferredValue: input.content,\n\t\t\t\t\t\tscope: { level: \"profile-private\" },\n\t\t\t\t\t\tevidence: {\n\t\t\t\t\t\t\tclass: ENVELOPE_EVIDENCE_CLASS_OF[evidenceClass],\n\t\t\t\t\t\t\tsourceRefs: [sourceRef],\n\t\t\t\t\t\t\tconfidence: evidenceConfidence,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tapplicabilityConfidence: evidenceConfidence,\n\t\t\t\t\t}\n\t\t\t\t: undefined;\n\t\treturn buildMemoryAtomV1({\n\t\t\tmemoryId,\n\t\t\tcontractVersion,\n\t\t\tschemaVersion: 1,\n\t\t\tscope: \"long-term\",\n\t\t\tretentionMode: \"long\",\n\t\t\towner,\n\t\t\tprofileId: capabilityManifest.id,\n\t\t\t...(input.domain.kind === \"suite\" ? { suiteId: input.domain.suiteId } : {}),\n\t\t\tretentionPolicyVersion: \"retention@1\",\n\t\t\tmemoryKind: input.kind === \"preference\" ? \"preference\" : \"fact\",\n\t\t\tpayload: { statement: input.content },\n\t\t\t...(preferenceEnvelope === undefined ? {} : { preference: preferenceEnvelope }),\n\t\t\toccurredAt,\n\t\t\trecordedAt: occurredAt,\n\t\t\tsourceRefs: [sourceRef],\n\t\t\tobservationId,\n\t\t\tsessionRefs: [sessionId],\n\t\t\tagentInstanceRefs: [],\n\t\t\tprojectRefs: [],\n\t\t\tsubjectRefs: [],\n\t\t\tfacets,\n\t\t\trelations: input.relations ?? [],\n\t\t\tconfidence: evidenceConfidence,\n\t\t\timportance: 0.5,\n\t\t\tevidenceClass: ATOM_EVIDENCE_CLASS_OF[evidenceClass],\n\t\t\tcontentRevision: `c-${identityHash(input.content)}`,\n\t\t\twriteReason: input.writeReason ?? `${capabilityManifest.id}/memory_write`,\n\t\t});\n\t};\n\n\tconst writeTool = registerTool({\n\t\tname: \"memory_write\",\n\t\tlabel: \"Write memory\",\n\t\tdescription:\n\t\t\t\"Persist one durable memory (a fact or a user preference) scoped to the current suite; it stays recallable across sessions until forgotten\",\n\t\tparameters: writeSchema,\n\t\texecute: async (first: unknown, second?: unknown) => {\n\t\t\tconst input = memoryToolInput(first, second);\n\t\t\tdeps.assertNonEmptyString(input.content, \"content\");\n\t\t\t// 长度契约 (混合检索工程化 §5): 超长显式拒绝并提示拆分, 而非静默截断。\n\t\t\tif (input.content.length > MEMORY_CONTENT_MAX_LENGTH) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`content is ${input.content.length} characters; the memory content limit is ${MEMORY_CONTENT_MAX_LENGTH} — split it into multiple self-contained memories`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (input.kind !== \"fact\" && input.kind !== \"preference\")\n\t\t\t\tthrow new Error('kind must be \"fact\" or \"preference\"');\n\t\t\tif (input.kind === \"preference\") {\n\t\t\t\tdeps.assertNonEmptyString(input.subject, 'subject (required when kind is \"preference\")');\n\t\t\t}\n\t\t\tif (input.subject !== undefined) deps.assertNonEmptyString(input.subject, \"subject\");\n\t\t\tif (input.tags !== undefined) {\n\t\t\t\tif (!Array.isArray(input.tags) || input.tags.length > MAX_TAGS) {\n\t\t\t\t\tthrow new Error(`tags must be an array of at most ${MAX_TAGS} strings`);\n\t\t\t\t}\n\t\t\t\tfor (const tag of input.tags) deps.assertNonEmptyString(tag, \"tags entry\");\n\t\t\t}\n\t\t\t// 证据声明解析 (诚实化设计 §1.1): 非法值显式拒绝, 不静默回落 — 模型\n\t\t\t// 声明了证据就必须是合法类别。缺省 inferred (诚实默认)。\n\t\t\tlet evidenceClass: MemoryWriteEvidenceClassV1 = \"inferred\";\n\t\t\tconst rawEvidence: unknown = input.evidence;\n\t\t\tif (rawEvidence !== undefined) {\n\t\t\t\tif (rawEvidence === null || typeof rawEvidence !== \"object\") {\n\t\t\t\t\tthrow new Error(\"evidence must be an object with a class field\");\n\t\t\t\t}\n\t\t\t\tconst rawClass: unknown = (rawEvidence as { class?: unknown }).class;\n\t\t\t\tif (rawClass !== \"user-stated\" && rawClass !== \"tool-observed\" && rawClass !== \"inferred\") {\n\t\t\t\t\tthrow new Error('evidence.class must be \"user-stated\", \"tool-observed\", or \"inferred\"');\n\t\t\t\t}\n\t\t\t\tevidenceClass = rawClass;\n\t\t\t}\n\t\t\tconst { domain, domainName, ledger } = resolveDomain();\n\t\t\t// 语义合并探测 (2.4d6 S3, full 档独有): light 档完全跳过 (写向量投影\n\t\t\t// 不查近邻, 行为与现状一致)。探测在 canonical 提交前执行, 判定决定新\n\t\t\t// atom 的 supersedes/conflict relations (atom 不可变, 随提交一次写入);\n\t\t\t// 探测失败降级为普通新条目 (告警一次, 不阻塞写入)。\n\t\t\tconst mergeOutcome =\n\t\t\t\tmemoryMode === \"full\"\n\t\t\t\t\t? await semanticMergeProbe({\n\t\t\t\t\t\t\tstatement: input.content,\n\t\t\t\t\t\t\tnewTags: input.tags ?? [],\n\t\t\t\t\t\t\tdomain,\n\t\t\t\t\t\t})\n\t\t\t\t\t: undefined;\n\t\t\tconst atom = buildWriteAtom({\n\t\t\t\tcontent: input.content,\n\t\t\t\tkind: input.kind,\n\t\t\t\tdomain,\n\t\t\t\tevidenceClass,\n\t\t\t\t...(input.subject === undefined ? {} : { subject: input.subject }),\n\t\t\t\t...(input.tags === undefined ? {} : { tags: input.tags }),\n\t\t\t\t...(mergeOutcome === undefined\n\t\t\t\t\t? {}\n\t\t\t\t\t: { relations: semanticMergeRelations(mergeOutcome.plan, input.content) }),\n\t\t\t});\n\t\t\tconst observation = schedulerApi.submitObservation({\n\t\t\t\towner,\n\t\t\t\tobservationId: atom.observationId,\n\t\t\t\tdraft: atom,\n\t\t\t});\n\t\t\tif (observation.state === \"rejected\") {\n\t\t\t\tthrow new Error(`memory rejected: ${observation.reason ?? \"rejected\"}`);\n\t\t\t}\n\t\t\t// 持久化先行 (2026-10-06 复审): ledger append 先于 candidate accept,\n\t\t\t// 与 bash 教训/promote 两条路径同序。append 失败 = 整个写入失败 (异常照\n\t\t\t// 抛, store 无该 atom、账本无半行)——旧的先 accept 后 append 序会在\n\t\t\t// append 失败时留下会话内可见、重启即失的幻影 atom。\n\t\t\t// 拒绝不落账: append 只在 submitObservation 成功后执行, 且 observe →\n\t\t\t// append → accept 之间无任何 await (本块全同步)——machine 记录由\n\t\t\t// observe 刚建立或命中既有记录, accept 的 rejected 分支在该窗口结构性\n\t\t\t// 不可达, 不会出现\"账本有行而 candidate 被拒\"。重复提交已 committed 的\n\t\t\t// observation 时 observe 返回可区分的 \"committed\" 状态 (candidates.ts\n\t\t\t// 状态枚举), 此时跳过 append——首次写入已有 durable 行, 避免账本重复\n\t\t\t// 行 (即使重复, load 的 duplicateSkipped 容错也会跳过)。\n\t\t\tif (observation.state !== \"committed\") ledger.append(atom);\n\t\t\tconst candidate = schedulerApi.submitCandidate({\n\t\t\t\tobservationId: atom.observationId,\n\t\t\t\towner,\n\t\t\t});\n\t\t\tif (candidate.status === \"rejected\") throw new Error(`memory rejected: ${candidate.reason ?? \"rejected\"}`);\n\t\t\tif (candidate.status === \"committed\") {\n\t\t\t\t// 向量投影埋点 (混合检索工程化): canonical 已提交, 投影写入异步旁路,\n\t\t\t\t// 失败不阻塞不抛出 (vectorProjection.upsertAtom 内部消化)。full 档携带语义合并\n\t\t\t\t// 探测产物 (复用探测向量; 合并命中同时移除被 supersedes 的旧行)。\n\t\t\t\tvectorProjection.upsertAtom(atom, mergeOutcome);\n\t\t\t\tqueueMicrotask(maybeTierUpgradeAfterWrite);\n\t\t\t}\n\t\t\tawait publish({\n\t\t\t\ttype: EVENT_TYPE,\n\t\t\t\tversion: 1,\n\t\t\t\tcorrelationId: capabilityManifest.id,\n\t\t\t\tdata: { action: \"written\", memoryId: candidate.memoryId, memoryKind: atom.memoryKind, domain: domainName },\n\t\t\t});\n\t\t\t// 偏好晋升触发 (B2): the /promote capability command is the production\n\t\t\t// caller of promotePreferenceToUserDefault; the tip is what tells the\n\t\t\t// model (so it can tell the user) that the cross-suite promotion path\n\t\t\t// exists. Facts have no promotion path — no tip.\n\t\t\treturn {\n\t\t\t\tmemoryId: candidate.memoryId,\n\t\t\t\tkind: atom.memoryKind,\n\t\t\t\tdomain: domainName,\n\t\t\t\t...(atom.memoryKind === \"preference\"\n\t\t\t\t\t? {\n\t\t\t\t\t\t\ttip: \"[tip] If this is a general preference (not project-specific), the user can promote it across all suites using /promote.\",\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t};\n\t\t},\n\t});\n\n\treturn {\n\t\twriteTool,\n\t\tsupersededMemoryIds,\n\t\tbuildWriteAtom,\n\t\tmaybeTierUpgradeAfterWrite,\n\t\ttierUpgradePending: (): boolean => tierUpgradeTriggered,\n\t\tclearTierUpgrade: (): void => {\n\t\t\ttierUpgradeTriggered = false;\n\t\t},\n\t\twriteSequenceOf,\n\t\tadvanceWriteSequenceFloor,\n\t};\n}\n"]}
@@ -21,6 +21,8 @@ export interface MemoryTestKitOptions {
21
21
  }
22
22
  export interface MemoryTestKitAtomSeed {
23
23
  readonly owner?: string;
24
+ /** Canonical suite id stamped onto the atom (domain-boundary tests). */
25
+ readonly suiteId?: string;
24
26
  readonly scope?: MemoryScopeV1;
25
27
  readonly retentionMode?: string;
26
28
  readonly memoryId?: string;