@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.
- package/README.md +10 -4
- package/agent-forge.json +1 -1
- package/bin/agent-forge-memory-inference.js +8 -0
- package/dist/capability.d.ts +42 -59
- package/dist/capability.d.ts.map +1 -1
- package/dist/capability.js +263 -2055
- package/dist/capability.js.map +1 -1
- package/dist/entry.d.ts +11 -0
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +38 -2
- package/dist/entry.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/inference-bin.d.ts +21 -0
- package/dist/inference-bin.d.ts.map +1 -0
- package/dist/inference-bin.js +85 -0
- package/dist/inference-bin.js.map +1 -0
- package/dist/memory/command-face.d.ts +86 -0
- package/dist/memory/command-face.d.ts.map +1 -0
- package/dist/memory/command-face.js +377 -0
- package/dist/memory/command-face.js.map +1 -0
- package/dist/memory/embedding-provider.d.ts.map +1 -1
- package/dist/memory/embedding-provider.js +14 -2
- package/dist/memory/embedding-provider.js.map +1 -1
- package/dist/memory/embedding-reranker.d.ts.map +1 -1
- package/dist/memory/embedding-reranker.js +14 -2
- package/dist/memory/embedding-reranker.js.map +1 -1
- package/dist/memory/enabling.d.ts +104 -0
- package/dist/memory/enabling.d.ts.map +1 -0
- package/dist/memory/enabling.js +321 -0
- package/dist/memory/enabling.js.map +1 -0
- package/dist/memory/hooks.d.ts +110 -0
- package/dist/memory/hooks.d.ts.map +1 -0
- package/dist/memory/hooks.js +478 -0
- package/dist/memory/hooks.js.map +1 -0
- package/dist/memory/inference-channel.d.ts +33 -0
- package/dist/memory/inference-channel.d.ts.map +1 -0
- package/dist/memory/inference-channel.js +101 -0
- package/dist/memory/inference-channel.js.map +1 -0
- package/dist/memory/inference-client.d.ts +25 -0
- package/dist/memory/inference-client.d.ts.map +1 -0
- package/dist/memory/inference-client.js +216 -0
- package/dist/memory/inference-client.js.map +1 -0
- package/dist/memory/inference-protocol.d.ts +95 -0
- package/dist/memory/inference-protocol.d.ts.map +1 -0
- package/dist/memory/inference-protocol.js +27 -0
- package/dist/memory/inference-protocol.js.map +1 -0
- package/dist/memory/inference-server.d.ts +70 -0
- package/dist/memory/inference-server.d.ts.map +1 -0
- package/dist/memory/inference-server.js +395 -0
- package/dist/memory/inference-server.js.map +1 -0
- package/dist/memory/inference-service-main.d.ts +31 -0
- package/dist/memory/inference-service-main.d.ts.map +1 -0
- package/dist/memory/inference-service-main.js +89 -0
- package/dist/memory/inference-service-main.js.map +1 -0
- package/dist/memory/ledger.d.ts +15 -0
- package/dist/memory/ledger.d.ts.map +1 -1
- package/dist/memory/ledger.js +35 -2
- package/dist/memory/ledger.js.map +1 -1
- package/dist/memory/model-cache-hygiene.d.ts +10 -0
- package/dist/memory/model-cache-hygiene.d.ts.map +1 -1
- package/dist/memory/model-cache-hygiene.js +15 -0
- package/dist/memory/model-cache-hygiene.js.map +1 -1
- package/dist/memory/preference-promotion.d.ts +0 -14
- package/dist/memory/preference-promotion.d.ts.map +1 -1
- package/dist/memory/preference-promotion.js +17 -0
- package/dist/memory/preference-promotion.js.map +1 -1
- package/dist/memory/recall-pipeline.d.ts +85 -0
- package/dist/memory/recall-pipeline.d.ts.map +1 -0
- package/dist/memory/recall-pipeline.js +678 -0
- package/dist/memory/recall-pipeline.js.map +1 -0
- package/dist/memory/retention-sweep.d.ts +59 -0
- package/dist/memory/retention-sweep.d.ts.map +1 -0
- package/dist/memory/retention-sweep.js +103 -0
- package/dist/memory/retention-sweep.js.map +1 -0
- package/dist/memory/store.d.ts +17 -2
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js +18 -2
- package/dist/memory/store.js.map +1 -1
- package/dist/memory/vector-channel.d.ts +120 -0
- package/dist/memory/vector-channel.d.ts.map +1 -0
- package/dist/memory/vector-channel.js +495 -0
- package/dist/memory/vector-channel.js.map +1 -0
- package/dist/memory/vector-projection.d.ts +87 -0
- package/dist/memory/vector-projection.d.ts.map +1 -0
- package/dist/memory/vector-projection.js +215 -0
- package/dist/memory/vector-projection.js.map +1 -0
- package/dist/memory/write-face.d.ts +128 -0
- package/dist/memory/write-face.d.ts.map +1 -0
- package/dist/memory/write-face.js +504 -0
- package/dist/memory/write-face.js.map +1 -0
- package/dist/testing/memory-testkit.d.ts +2 -0
- package/dist/testing/memory-testkit.d.ts.map +1 -1
- package/dist/testing/memory-testkit.js +1 -0
- package/dist/testing/memory-testkit.js.map +1 -1
- package/package.json +7 -3
- package/plugin.json +1 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 写入侧向量投影管线(capability 拆分战役 slice 3a):对账分块、upsert/purge 埋点与串行队列。状态机(vectorEnsure/failEnabling)不属于本模块;降级/弃用经 deps 回调上报。
|
|
3
|
+
*/
|
|
4
|
+
import type { CapabilityAPI } from "@agent-forge/plugin-sdk";
|
|
5
|
+
import type { MemoryEmbeddingProviderV1 } from "./embedding-provider.ts";
|
|
6
|
+
import type { MemoryRerankerV1 } from "./embedding-reranker.ts";
|
|
7
|
+
import type { MemoryAtomV1 } from "./foundation.ts";
|
|
8
|
+
import type { MemoryStoreV1 } from "./store.ts";
|
|
9
|
+
import type { MemoryVectorIndexV1 } from "./vector-index.ts";
|
|
10
|
+
export interface ActiveVectorChannelV1 {
|
|
11
|
+
readonly embed: MemoryEmbeddingProviderV1;
|
|
12
|
+
/** undefined = 精排不可用 (跳过精排, 直接用 RRF 序)。 */
|
|
13
|
+
readonly reranker: MemoryRerankerV1 | undefined;
|
|
14
|
+
readonly index: MemoryVectorIndexV1;
|
|
15
|
+
readonly queryPrefix: string;
|
|
16
|
+
/** 预设 embedding modelId — 对账时与投影存储的 modelId 比较, 不一致即清空重建。 */
|
|
17
|
+
readonly embeddingModelId: string;
|
|
18
|
+
}
|
|
19
|
+
/** 语义合并判定结论 (投影探测产物; relations 随新 atom 一次性提交, atom 不可变)。 */
|
|
20
|
+
export type SemanticMergePlanV1 = {
|
|
21
|
+
readonly kind: "merge";
|
|
22
|
+
readonly targetMemoryId: string;
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: "conflict";
|
|
25
|
+
readonly conflictMemoryIds: readonly string[];
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: "new";
|
|
28
|
+
};
|
|
29
|
+
/** 探测产物: 判定结论 + 新 statement 的嵌入 (投影 upsert 复用同一向量)。 */
|
|
30
|
+
export interface SemanticMergeOutcomeV1 {
|
|
31
|
+
readonly plan: SemanticMergePlanV1;
|
|
32
|
+
readonly vector: readonly number[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 投影管线对 capability 闭包的只读依赖视图 (slice 3a): 状态机变量 (vectorState/
|
|
36
|
+
* vectorChannel/vectorEnsure) 仍归 capability 闭包所有, 本模块经只读取用与回调
|
|
37
|
+
* 上报参与, 不持有任何状态机变量。
|
|
38
|
+
*/
|
|
39
|
+
export interface MemoryVectorProjectionDepsV1 {
|
|
40
|
+
/** 档位门 (capability.vectorEnabled): 档位关停时埋点 no-op。 */
|
|
41
|
+
readonly vectorEnabled: () => boolean;
|
|
42
|
+
/**
|
|
43
|
+
* 开启状态机的只读使用入口 (capability.vectorEnsure): 惰性装配语义原样保留
|
|
44
|
+
* (埋点/对账在通道未装配时仍触发 enabling 并等待), 本模块不感知其内部状态。
|
|
45
|
+
*/
|
|
46
|
+
readonly ensureChannel: () => Promise<ActiveVectorChannelV1 | undefined>;
|
|
47
|
+
/** 已就绪通道的同步快照 (vectorChannel 变量只读): purge 埋点不触发装配。 */
|
|
48
|
+
readonly readyChannel: () => ActiveVectorChannelV1 | undefined;
|
|
49
|
+
/** 通道可操作 (ready|degraded) 的只读判定: purge 前置检查。 */
|
|
50
|
+
readonly isChannelActive: () => boolean;
|
|
51
|
+
/** capability dispose 竞态闩锁 (capabilityDisposed) 的只读视图。 */
|
|
52
|
+
readonly isDisposed: () => boolean;
|
|
53
|
+
/**
|
|
54
|
+
* 跨进程 canonical 身份刷新 (2026-10-07 终审守卫): 只读扫描 owner 全部域
|
|
55
|
+
* 账本的 memoryId 并集。反向清理的孤儿判定 = store 活跃集 ∪ 刷新集 双缺席
|
|
56
|
+
* 才删——多会话共享向量库时, 其他会话刚写入、本进程 store 尚未重放的原子
|
|
57
|
+
* 在刷新集内, 不再被误删; complete=false (有账本文件读取失败) 时本轮禁删
|
|
58
|
+
* 只回填, 防瞬时 IO 错误放大成整文件行误删。
|
|
59
|
+
*/
|
|
60
|
+
readonly refreshCanonicalMemoryIds: () => {
|
|
61
|
+
readonly memoryIds: ReadonlySet<string>;
|
|
62
|
+
readonly complete: boolean;
|
|
63
|
+
};
|
|
64
|
+
/** 对账失败的 degraded 转换回调 (capability 侧写 vectorState, 状态转换语义归状态机)。 */
|
|
65
|
+
readonly markDegraded: () => void;
|
|
66
|
+
readonly logger: CapabilityAPI["logger"];
|
|
67
|
+
/** 投影行 owner (capability.BUILTIN_MEMORY_OWNER)。 */
|
|
68
|
+
readonly owner: string;
|
|
69
|
+
readonly store: MemoryStoreV1;
|
|
70
|
+
/** 被继任 memoryId 集 (capability.supersededMemoryIds): 对账不回填已继任条目。 */
|
|
71
|
+
readonly supersededMemoryIds: () => ReadonlySet<string>;
|
|
72
|
+
readonly statementOf: (payload: unknown) => string;
|
|
73
|
+
readonly tagFacetsOf: (atom: MemoryAtomV1) => string[];
|
|
74
|
+
}
|
|
75
|
+
export interface MemoryVectorProjectionV1 {
|
|
76
|
+
/** 串行队列入队 (原 vectorEnqueue): 同一队列串行域的写侧操作 (如语义合并探测) 复用。 */
|
|
77
|
+
enqueue: <T>(operation: () => Promise<T>) => Promise<T>;
|
|
78
|
+
/** 当前队尾 (await 它只等待调用时刻已入队的部分): recall 与 harness.settleVectorWork 用。 */
|
|
79
|
+
settle: () => Promise<unknown>;
|
|
80
|
+
upsertAtom: (atom: MemoryAtomV1, mergeOutcome?: SemanticMergeOutcomeV1) => void;
|
|
81
|
+
purgeMemories: (memoryIds: readonly string[]) => void;
|
|
82
|
+
reconcileOnce: () => void;
|
|
83
|
+
/** 重置对账一次性闸 (模型下载就绪后补跑启动对账)。 */
|
|
84
|
+
resetReconcile: () => void;
|
|
85
|
+
}
|
|
86
|
+
export declare function createMemoryVectorProjection(deps: MemoryVectorProjectionDepsV1): MemoryVectorProjectionV1;
|
|
87
|
+
//# sourceMappingURL=vector-projection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vector-projection.d.ts","sourceRoot":"","sources":["../../src/memory/vector-projection.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 写入侧向量投影管线(capability 拆分战役 slice 3a):对账分块、upsert/purge 埋点与串行队列。状态机(vectorEnsure/failEnabling)不属于本模块;降级/弃用经 deps 回调上报。\n */\n\nimport type { CapabilityAPI } from \"@agent-forge/plugin-sdk\";\nimport type { MemoryEmbeddingProviderV1 } from \"./embedding-provider.ts\";\nimport type { MemoryRerankerV1 } from \"./embedding-reranker.ts\";\nimport type { MemoryAtomV1 } from \"./foundation.ts\";\nimport type { MemoryStoreV1 } from \"./store.ts\";\nimport type { MemoryVectorIndexV1 } from \"./vector-index.ts\";\n\n/** 单次启动对账 embed+upsert 上限, 超出留待下次启动继续。 */\nconst RECONCILE_MAX_UPSERTS = 2000;\n/**\n * 启动对账的队列作业分块 (2026-10-06 复审): 对账拆成自续链式作业, 每块至多该\n * 条数——召回/显式查询的 `await vectorQueue` 只捕获调用时刻的队尾, 故最多等待\n * 当前块而非整个 backfill (2000 条 CPU embedding 是 10-20 秒量级)。与\n * {@link RECONCILE_MAX_UPSERTS} 的分工: 2000 是单次对账总上限 (超出下次启动\n * 继续), 256 是队列作业粒度。\n */\nconst RECONCILE_CHUNK_UPSERTS = 256;\n\nexport interface ActiveVectorChannelV1 {\n\treadonly embed: MemoryEmbeddingProviderV1;\n\t/** undefined = 精排不可用 (跳过精排, 直接用 RRF 序)。 */\n\treadonly reranker: MemoryRerankerV1 | undefined;\n\treadonly index: MemoryVectorIndexV1;\n\treadonly queryPrefix: string;\n\t/** 预设 embedding modelId — 对账时与投影存储的 modelId 比较, 不一致即清空重建。 */\n\treadonly embeddingModelId: string;\n}\n\n/** 语义合并判定结论 (投影探测产物; relations 随新 atom 一次性提交, atom 不可变)。 */\nexport type SemanticMergePlanV1 =\n\t| { readonly kind: \"merge\"; readonly targetMemoryId: string }\n\t| { readonly kind: \"conflict\"; readonly conflictMemoryIds: readonly string[] }\n\t| { readonly kind: \"new\" };\n\n/** 探测产物: 判定结论 + 新 statement 的嵌入 (投影 upsert 复用同一向量)。 */\nexport interface SemanticMergeOutcomeV1 {\n\treadonly plan: SemanticMergePlanV1;\n\treadonly vector: readonly number[];\n}\n\n/**\n * 投影管线对 capability 闭包的只读依赖视图 (slice 3a): 状态机变量 (vectorState/\n * vectorChannel/vectorEnsure) 仍归 capability 闭包所有, 本模块经只读取用与回调\n * 上报参与, 不持有任何状态机变量。\n */\nexport interface MemoryVectorProjectionDepsV1 {\n\t/** 档位门 (capability.vectorEnabled): 档位关停时埋点 no-op。 */\n\treadonly vectorEnabled: () => boolean;\n\t/**\n\t * 开启状态机的只读使用入口 (capability.vectorEnsure): 惰性装配语义原样保留\n\t * (埋点/对账在通道未装配时仍触发 enabling 并等待), 本模块不感知其内部状态。\n\t */\n\treadonly ensureChannel: () => Promise<ActiveVectorChannelV1 | undefined>;\n\t/** 已就绪通道的同步快照 (vectorChannel 变量只读): purge 埋点不触发装配。 */\n\treadonly readyChannel: () => ActiveVectorChannelV1 | undefined;\n\t/** 通道可操作 (ready|degraded) 的只读判定: purge 前置检查。 */\n\treadonly isChannelActive: () => boolean;\n\t/** capability dispose 竞态闩锁 (capabilityDisposed) 的只读视图。 */\n\treadonly isDisposed: () => boolean;\n\t/**\n\t * 跨进程 canonical 身份刷新 (2026-10-07 终审守卫): 只读扫描 owner 全部域\n\t * 账本的 memoryId 并集。反向清理的孤儿判定 = store 活跃集 ∪ 刷新集 双缺席\n\t * 才删——多会话共享向量库时, 其他会话刚写入、本进程 store 尚未重放的原子\n\t * 在刷新集内, 不再被误删; complete=false (有账本文件读取失败) 时本轮禁删\n\t * 只回填, 防瞬时 IO 错误放大成整文件行误删。\n\t */\n\treadonly refreshCanonicalMemoryIds: () => {\n\t\treadonly memoryIds: ReadonlySet<string>;\n\t\treadonly complete: boolean;\n\t};\n\t/** 对账失败的 degraded 转换回调 (capability 侧写 vectorState, 状态转换语义归状态机)。 */\n\treadonly markDegraded: () => void;\n\treadonly logger: CapabilityAPI[\"logger\"];\n\t/** 投影行 owner (capability.BUILTIN_MEMORY_OWNER)。 */\n\treadonly owner: string;\n\treadonly store: MemoryStoreV1;\n\t/** 被继任 memoryId 集 (capability.supersededMemoryIds): 对账不回填已继任条目。 */\n\treadonly supersededMemoryIds: () => ReadonlySet<string>;\n\treadonly statementOf: (payload: unknown) => string;\n\treadonly tagFacetsOf: (atom: MemoryAtomV1) => string[];\n}\n\nexport interface MemoryVectorProjectionV1 {\n\t/** 串行队列入队 (原 vectorEnqueue): 同一队列串行域的写侧操作 (如语义合并探测) 复用。 */\n\tenqueue: <T>(operation: () => Promise<T>) => Promise<T>;\n\t/** 当前队尾 (await 它只等待调用时刻已入队的部分): recall 与 harness.settleVectorWork 用。 */\n\tsettle: () => Promise<unknown>;\n\tupsertAtom: (atom: MemoryAtomV1, mergeOutcome?: SemanticMergeOutcomeV1) => void;\n\tpurgeMemories: (memoryIds: readonly string[]) => void;\n\treconcileOnce: () => void;\n\t/** 重置对账一次性闸 (模型下载就绪后补跑启动对账)。 */\n\tresetReconcile: () => void;\n}\n\nexport function createMemoryVectorProjection(deps: MemoryVectorProjectionDepsV1): MemoryVectorProjectionV1 {\n\tlet vectorQueue: Promise<unknown> = Promise.resolve();\n\tconst vectorEnqueue = <T>(operation: () => Promise<T>): Promise<T> => {\n\t\tconst next = vectorQueue.then(operation, operation);\n\t\t// 队尾永不满仓 reject: 前序失败不阻断后续操作, 错误由操作自己消化。\n\t\tvectorQueue = next.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\treturn next;\n\t};\n\t/** 投影写/清失败的一次性降级告警标记 (重复失败由启动对账修复, 不刷屏)。 */\n\tlet vectorDegradedWarned = false;\n\n\tconst vectorUpsertAtom = (atom: MemoryAtomV1, mergeOutcome?: SemanticMergeOutcomeV1): void => {\n\t\tif (!deps.vectorEnabled()) return;\n\t\tvoid vectorEnqueue(async () => {\n\t\t\tconst channel = await deps.ensureChannel();\n\t\t\tif (channel === undefined) return;\n\t\t\ttry {\n\t\t\t\tconst statement = deps.statementOf(atom.payload);\n\t\t\t\tconst [vector] =\n\t\t\t\t\tmergeOutcome === undefined ? await channel.embed.embed([statement]) : [mergeOutcome.vector];\n\t\t\t\tawait channel.index.upsert([\n\t\t\t\t\t{\n\t\t\t\t\t\tmemoryId: atom.memoryId,\n\t\t\t\t\t\towner: deps.owner,\n\t\t\t\t\t\tmodelId: channel.embed.modelId,\n\t\t\t\t\t\tstatement,\n\t\t\t\t\t\ttags: deps.tagFacetsOf(atom),\n\t\t\t\t\t\tvector,\n\t\t\t\t\t},\n\t\t\t\t]);\n\t\t\t\tif (mergeOutcome?.plan.kind === \"merge\") {\n\t\t\t\t\tawait channel.index.remove([mergeOutcome.plan.targetMemoryId]);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tif (!vectorDegradedWarned) {\n\t\t\t\t\tvectorDegradedWarned = true;\n\t\t\t\t\tdeps.logger?.warn(\n\t\t\t\t\t\t\"memory vector projection write failed; the entry is rebuilt by startup reconciliation\",\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmemoryId: atom.memoryId,\n\t\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t};\n\n\t/**\n\t * purge 的 index 副本分派: 通道非 ready/degraded 时 no-op 且不报错 (enabling\n\t * 未完成/failed)。持久索引下该 no-op 会漏删 (门把它记为已确认, 不重试), 残行\n\t * 由启动对账的反向清理收敛 (投影行以 store 活跃集为准, 2026-10-07 审计收口);\n\t * 收敛前 recall 的 store 回查也保证残行不可出线。remove 异步且失败只记\n\t * degraded: 同样由对账反向清理兜底。\n\t */\n\tconst vectorPurgeMemories = (memoryIds: readonly string[]): void => {\n\t\tif (!deps.isChannelActive()) return;\n\t\tvoid vectorEnqueue(async () => {\n\t\t\tconst channel = deps.readyChannel();\n\t\t\tif (channel === undefined) return;\n\t\t\ttry {\n\t\t\t\tawait channel.index.remove(memoryIds);\n\t\t\t} catch (error) {\n\t\t\t\tif (!vectorDegradedWarned) {\n\t\t\t\t\tvectorDegradedWarned = true;\n\t\t\t\t\tdeps.logger?.warn(\n\t\t\t\t\t\t\"memory vector projection purge failed; stale rows stay unreachable via the store lookup\",\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t};\n\n\t/**\n\t * 启动对账 (一次性, capability 初始化语义内; fire-and-forget 不阻塞首工具):\n\t * 1) 投影 modelId 与预设不一致 (含 undefined 且表非空) → 清空向量表;\n\t * 2) diff store 全量 memoryId vs 投影, 缺失的分块 embed+upsert 回填: 单次\n\t * 对账总上限 {@link RECONCILE_MAX_UPSERTS} (超出留待下次启动继续), 队列\n\t * 作业按 {@link RECONCILE_CHUNK_UPSERTS} 分块——首个作业做 prep+diff 后\n\t * upsert 第 1 块, 完成后 `void vectorEnqueue(下一块)` 自续成链。召回/\n\t * 显式查询的 `await vectorQueue` 只捕获调用时刻的队尾, 故最多等待当前块\n\t * 而非整个 backfill; 写入→召回的 FIFO 语义不变 (写入埋点先于查询入队)。\n\t * 对账失败不阻塞 capability 启动, 状态转 degraded (运行期故障, 可自愈:\n\t * 后续向量操作重新触发补齐语义, canonical 面不受影响), 该块之后停止续链\n\t * (残行由下次启动对账继续)。\n\t */\n\tlet vectorReconcileStarted = false;\n\tconst vectorReconcileOnce = (): void => {\n\t\tif (!deps.vectorEnabled() || vectorReconcileStarted) return;\n\t\tvectorReconcileStarted = true;\n\t\tvoid vectorEnqueue(async () => {\n\t\t\tconst channel = await deps.ensureChannel();\n\t\t\tif (channel === undefined) return;\n\t\t\tconst reconcileFailure = (error: unknown): void => {\n\t\t\t\t// dispose 后在途分块对已关闭索引的失败是预期结果, 不是通道降级:\n\t\t\t\t// 静默停链, 不产生 degraded 态与噪声 warn。\n\t\t\t\tif (deps.isDisposed()) return;\n\t\t\t\tdeps.markDegraded();\n\t\t\t\tdeps.logger?.warn(\n\t\t\t\t\t\"memory vector reconciliation failed; the channel is degraded (canonical recall degrades, self-heals on the next successful channel op)\",\n\t\t\t\t\t{\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};\n\t\t\ttry {\n\t\t\t\tconst storedModelId = await channel.index.getStoredModelId();\n\t\t\t\tif (storedModelId !== channel.embeddingModelId) {\n\t\t\t\t\tconst stale = await channel.index.listMemoryIds();\n\t\t\t\t\tif (stale.size > 0) await channel.index.remove([...stale]);\n\t\t\t\t}\n\t\t\t\tconst indexed = await channel.index.listMemoryIds();\n\t\t\t\tconst superseded = deps.supersededMemoryIds();\n\t\t\t\tconst storeRecords = deps.store.list({ owner: deps.owner });\n\t\t\t\tconst storeIds = new Set(storeRecords.map((record) => record.atom.memoryId));\n\t\t\t\t// 反向清理 (2026-10-07 审计收口, 同日终审加跨进程守卫): 投影中不在\n\t\t\t\t// canonical 集的行是孤儿——被保留清扫清走的过期原子 (冷通道时 index\n\t\t\t\t// 副本 no-op 且被门记为已确认, 不会再重试)、forget 残行、被继任的\n\t\t\t\t// 旧行。canonical 集 = store 活跃集 ∪ 账本盘面刷新集: 多会话共享\n\t\t\t\t// 向量库时, 其他会话刚写入、本进程 store 尚未重放的原子在刷新集内,\n\t\t\t\t// 双缺席才删。刷新不完整 (有账本文件读取失败) 时本轮禁删只回填。\n\t\t\t\t// 冷启动接缝在此自愈: sweep 在 resolveDomain 同步逐出 store, 本作业\n\t\t\t\t// 随后 (通道已就绪) 才运行, 被逐出的 id 自然落入本清理。\n\t\t\t\tconst refreshed = deps.refreshCanonicalMemoryIds();\n\t\t\t\tconst canonicalIds = refreshed.complete ? new Set([...storeIds, ...refreshed.memoryIds]) : storeIds;\n\t\t\t\tconst orphans = refreshed.complete\n\t\t\t\t\t? [...indexed].filter((memoryId) => !canonicalIds.has(memoryId) || superseded.has(memoryId))\n\t\t\t\t\t: [];\n\t\t\t\tif (orphans.length > 0) await channel.index.remove(orphans);\n\t\t\t\t// 被继任的旧条目不回填投影 (S3): 合并已移除其行, 对账的重放/重建\n\t\t\t\t// 不得复活 (召回可见性由继任条目承担, canonical 侧保留可直查)。\n\t\t\t\tconst missing: MemoryAtomV1[] = [];\n\t\t\t\tfor (const record of storeRecords) {\n\t\t\t\t\tif (superseded.has(record.atom.memoryId)) continue;\n\t\t\t\t\tif (!indexed.has(record.atom.memoryId)) missing.push(record.atom);\n\t\t\t\t}\n\t\t\t\tif (missing.length > RECONCILE_MAX_UPSERTS) {\n\t\t\t\t\tdeps.logger?.warn(\"memory vector reconciliation capped; remaining entries rebuild on the next startup\", {\n\t\t\t\t\t\ttotal: missing.length,\n\t\t\t\t\t\tcap: RECONCILE_MAX_UPSERTS,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst backlog = missing.slice(0, RECONCILE_MAX_UPSERTS);\n\t\t\t\tif (backlog.length === 0) return;\n\t\t\t\tconst upsertChunk = async (chunk: readonly MemoryAtomV1[]): Promise<void> => {\n\t\t\t\t\tconst statements = chunk.map((atom) => deps.statementOf(atom.payload));\n\t\t\t\t\tconst vectors = await channel.embed.embed(statements);\n\t\t\t\t\tawait channel.index.upsert(\n\t\t\t\t\t\tchunk.map((atom, position) => ({\n\t\t\t\t\t\t\tmemoryId: atom.memoryId,\n\t\t\t\t\t\t\towner: deps.owner,\n\t\t\t\t\t\t\tmodelId: channel.embed.modelId,\n\t\t\t\t\t\t\tstatement: statements[position],\n\t\t\t\t\t\t\ttags: deps.tagFacetsOf(atom),\n\t\t\t\t\t\t\tvector: vectors[position],\n\t\t\t\t\t\t})),\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t\t// 首块在本作业内 upsert; 后续块由续链作业完成一块自续一块。续链必须\n\t\t\t\t// fire-and-forget: 续链作业入队时当前作业仍是队尾, 在作业体内 await\n\t\t\t\t// 自己刚入队的下一块等于自己等自己 (死锁)。\n\t\t\t\tconst continueChunks = (from: number): void => {\n\t\t\t\t\tvoid vectorEnqueue(async () => {\n\t\t\t\t\t\tconst chunk = backlog.slice(from, from + RECONCILE_CHUNK_UPSERTS);\n\t\t\t\t\t\tif (chunk.length === 0) return;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait upsertChunk(chunk);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t// 块失败: degraded + 停止续链 (prep 失败走外层 catch, 同文案)。\n\t\t\t\t\t\t\treconcileFailure(error);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst next = from + chunk.length;\n\t\t\t\t\t\tif (next < backlog.length && !deps.isDisposed()) continueChunks(next);\n\t\t\t\t\t});\n\t\t\t\t};\n\t\t\t\tawait upsertChunk(backlog.slice(0, RECONCILE_CHUNK_UPSERTS));\n\t\t\t\tif (RECONCILE_CHUNK_UPSERTS < backlog.length) continueChunks(RECONCILE_CHUNK_UPSERTS);\n\t\t\t} catch (error) {\n\t\t\t\treconcileFailure(error);\n\t\t\t}\n\t\t});\n\t};\n\n\treturn {\n\t\tenqueue: vectorEnqueue,\n\t\tsettle: (): Promise<unknown> => vectorQueue,\n\t\tupsertAtom: vectorUpsertAtom,\n\t\tpurgeMemories: vectorPurgeMemories,\n\t\treconcileOnce: vectorReconcileOnce,\n\t\tresetReconcile: () => {\n\t\t\tvectorReconcileStarted = false;\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 写入侧向量投影管线(capability 拆分战役 slice 3a):对账分块、upsert/purge 埋点与串行队列。状态机(vectorEnsure/failEnabling)不属于本模块;降级/弃用经 deps 回调上报。
|
|
3
|
+
*/
|
|
4
|
+
/** 单次启动对账 embed+upsert 上限, 超出留待下次启动继续。 */
|
|
5
|
+
const RECONCILE_MAX_UPSERTS = 2000;
|
|
6
|
+
/**
|
|
7
|
+
* 启动对账的队列作业分块 (2026-10-06 复审): 对账拆成自续链式作业, 每块至多该
|
|
8
|
+
* 条数——召回/显式查询的 `await vectorQueue` 只捕获调用时刻的队尾, 故最多等待
|
|
9
|
+
* 当前块而非整个 backfill (2000 条 CPU embedding 是 10-20 秒量级)。与
|
|
10
|
+
* {@link RECONCILE_MAX_UPSERTS} 的分工: 2000 是单次对账总上限 (超出下次启动
|
|
11
|
+
* 继续), 256 是队列作业粒度。
|
|
12
|
+
*/
|
|
13
|
+
const RECONCILE_CHUNK_UPSERTS = 256;
|
|
14
|
+
export function createMemoryVectorProjection(deps) {
|
|
15
|
+
let vectorQueue = Promise.resolve();
|
|
16
|
+
const vectorEnqueue = (operation) => {
|
|
17
|
+
const next = vectorQueue.then(operation, operation);
|
|
18
|
+
// 队尾永不满仓 reject: 前序失败不阻断后续操作, 错误由操作自己消化。
|
|
19
|
+
vectorQueue = next.then(() => undefined, () => undefined);
|
|
20
|
+
return next;
|
|
21
|
+
};
|
|
22
|
+
/** 投影写/清失败的一次性降级告警标记 (重复失败由启动对账修复, 不刷屏)。 */
|
|
23
|
+
let vectorDegradedWarned = false;
|
|
24
|
+
const vectorUpsertAtom = (atom, mergeOutcome) => {
|
|
25
|
+
if (!deps.vectorEnabled())
|
|
26
|
+
return;
|
|
27
|
+
void vectorEnqueue(async () => {
|
|
28
|
+
const channel = await deps.ensureChannel();
|
|
29
|
+
if (channel === undefined)
|
|
30
|
+
return;
|
|
31
|
+
try {
|
|
32
|
+
const statement = deps.statementOf(atom.payload);
|
|
33
|
+
const [vector] = mergeOutcome === undefined ? await channel.embed.embed([statement]) : [mergeOutcome.vector];
|
|
34
|
+
await channel.index.upsert([
|
|
35
|
+
{
|
|
36
|
+
memoryId: atom.memoryId,
|
|
37
|
+
owner: deps.owner,
|
|
38
|
+
modelId: channel.embed.modelId,
|
|
39
|
+
statement,
|
|
40
|
+
tags: deps.tagFacetsOf(atom),
|
|
41
|
+
vector,
|
|
42
|
+
},
|
|
43
|
+
]);
|
|
44
|
+
if (mergeOutcome?.plan.kind === "merge") {
|
|
45
|
+
await channel.index.remove([mergeOutcome.plan.targetMemoryId]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (!vectorDegradedWarned) {
|
|
50
|
+
vectorDegradedWarned = true;
|
|
51
|
+
deps.logger?.warn("memory vector projection write failed; the entry is rebuilt by startup reconciliation", {
|
|
52
|
+
memoryId: atom.memoryId,
|
|
53
|
+
error: error instanceof Error ? error.message : String(error),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* purge 的 index 副本分派: 通道非 ready/degraded 时 no-op 且不报错 (enabling
|
|
61
|
+
* 未完成/failed)。持久索引下该 no-op 会漏删 (门把它记为已确认, 不重试), 残行
|
|
62
|
+
* 由启动对账的反向清理收敛 (投影行以 store 活跃集为准, 2026-10-07 审计收口);
|
|
63
|
+
* 收敛前 recall 的 store 回查也保证残行不可出线。remove 异步且失败只记
|
|
64
|
+
* degraded: 同样由对账反向清理兜底。
|
|
65
|
+
*/
|
|
66
|
+
const vectorPurgeMemories = (memoryIds) => {
|
|
67
|
+
if (!deps.isChannelActive())
|
|
68
|
+
return;
|
|
69
|
+
void vectorEnqueue(async () => {
|
|
70
|
+
const channel = deps.readyChannel();
|
|
71
|
+
if (channel === undefined)
|
|
72
|
+
return;
|
|
73
|
+
try {
|
|
74
|
+
await channel.index.remove(memoryIds);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (!vectorDegradedWarned) {
|
|
78
|
+
vectorDegradedWarned = true;
|
|
79
|
+
deps.logger?.warn("memory vector projection purge failed; stale rows stay unreachable via the store lookup", {
|
|
80
|
+
error: error instanceof Error ? error.message : String(error),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* 启动对账 (一次性, capability 初始化语义内; fire-and-forget 不阻塞首工具):
|
|
88
|
+
* 1) 投影 modelId 与预设不一致 (含 undefined 且表非空) → 清空向量表;
|
|
89
|
+
* 2) diff store 全量 memoryId vs 投影, 缺失的分块 embed+upsert 回填: 单次
|
|
90
|
+
* 对账总上限 {@link RECONCILE_MAX_UPSERTS} (超出留待下次启动继续), 队列
|
|
91
|
+
* 作业按 {@link RECONCILE_CHUNK_UPSERTS} 分块——首个作业做 prep+diff 后
|
|
92
|
+
* upsert 第 1 块, 完成后 `void vectorEnqueue(下一块)` 自续成链。召回/
|
|
93
|
+
* 显式查询的 `await vectorQueue` 只捕获调用时刻的队尾, 故最多等待当前块
|
|
94
|
+
* 而非整个 backfill; 写入→召回的 FIFO 语义不变 (写入埋点先于查询入队)。
|
|
95
|
+
* 对账失败不阻塞 capability 启动, 状态转 degraded (运行期故障, 可自愈:
|
|
96
|
+
* 后续向量操作重新触发补齐语义, canonical 面不受影响), 该块之后停止续链
|
|
97
|
+
* (残行由下次启动对账继续)。
|
|
98
|
+
*/
|
|
99
|
+
let vectorReconcileStarted = false;
|
|
100
|
+
const vectorReconcileOnce = () => {
|
|
101
|
+
if (!deps.vectorEnabled() || vectorReconcileStarted)
|
|
102
|
+
return;
|
|
103
|
+
vectorReconcileStarted = true;
|
|
104
|
+
void vectorEnqueue(async () => {
|
|
105
|
+
const channel = await deps.ensureChannel();
|
|
106
|
+
if (channel === undefined)
|
|
107
|
+
return;
|
|
108
|
+
const reconcileFailure = (error) => {
|
|
109
|
+
// dispose 后在途分块对已关闭索引的失败是预期结果, 不是通道降级:
|
|
110
|
+
// 静默停链, 不产生 degraded 态与噪声 warn。
|
|
111
|
+
if (deps.isDisposed())
|
|
112
|
+
return;
|
|
113
|
+
deps.markDegraded();
|
|
114
|
+
deps.logger?.warn("memory vector reconciliation failed; the channel is degraded (canonical recall degrades, self-heals on the next successful channel op)", {
|
|
115
|
+
error: error instanceof Error ? error.message : String(error),
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
try {
|
|
119
|
+
const storedModelId = await channel.index.getStoredModelId();
|
|
120
|
+
if (storedModelId !== channel.embeddingModelId) {
|
|
121
|
+
const stale = await channel.index.listMemoryIds();
|
|
122
|
+
if (stale.size > 0)
|
|
123
|
+
await channel.index.remove([...stale]);
|
|
124
|
+
}
|
|
125
|
+
const indexed = await channel.index.listMemoryIds();
|
|
126
|
+
const superseded = deps.supersededMemoryIds();
|
|
127
|
+
const storeRecords = deps.store.list({ owner: deps.owner });
|
|
128
|
+
const storeIds = new Set(storeRecords.map((record) => record.atom.memoryId));
|
|
129
|
+
// 反向清理 (2026-10-07 审计收口, 同日终审加跨进程守卫): 投影中不在
|
|
130
|
+
// canonical 集的行是孤儿——被保留清扫清走的过期原子 (冷通道时 index
|
|
131
|
+
// 副本 no-op 且被门记为已确认, 不会再重试)、forget 残行、被继任的
|
|
132
|
+
// 旧行。canonical 集 = store 活跃集 ∪ 账本盘面刷新集: 多会话共享
|
|
133
|
+
// 向量库时, 其他会话刚写入、本进程 store 尚未重放的原子在刷新集内,
|
|
134
|
+
// 双缺席才删。刷新不完整 (有账本文件读取失败) 时本轮禁删只回填。
|
|
135
|
+
// 冷启动接缝在此自愈: sweep 在 resolveDomain 同步逐出 store, 本作业
|
|
136
|
+
// 随后 (通道已就绪) 才运行, 被逐出的 id 自然落入本清理。
|
|
137
|
+
const refreshed = deps.refreshCanonicalMemoryIds();
|
|
138
|
+
const canonicalIds = refreshed.complete ? new Set([...storeIds, ...refreshed.memoryIds]) : storeIds;
|
|
139
|
+
const orphans = refreshed.complete
|
|
140
|
+
? [...indexed].filter((memoryId) => !canonicalIds.has(memoryId) || superseded.has(memoryId))
|
|
141
|
+
: [];
|
|
142
|
+
if (orphans.length > 0)
|
|
143
|
+
await channel.index.remove(orphans);
|
|
144
|
+
// 被继任的旧条目不回填投影 (S3): 合并已移除其行, 对账的重放/重建
|
|
145
|
+
// 不得复活 (召回可见性由继任条目承担, canonical 侧保留可直查)。
|
|
146
|
+
const missing = [];
|
|
147
|
+
for (const record of storeRecords) {
|
|
148
|
+
if (superseded.has(record.atom.memoryId))
|
|
149
|
+
continue;
|
|
150
|
+
if (!indexed.has(record.atom.memoryId))
|
|
151
|
+
missing.push(record.atom);
|
|
152
|
+
}
|
|
153
|
+
if (missing.length > RECONCILE_MAX_UPSERTS) {
|
|
154
|
+
deps.logger?.warn("memory vector reconciliation capped; remaining entries rebuild on the next startup", {
|
|
155
|
+
total: missing.length,
|
|
156
|
+
cap: RECONCILE_MAX_UPSERTS,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
const backlog = missing.slice(0, RECONCILE_MAX_UPSERTS);
|
|
160
|
+
if (backlog.length === 0)
|
|
161
|
+
return;
|
|
162
|
+
const upsertChunk = async (chunk) => {
|
|
163
|
+
const statements = chunk.map((atom) => deps.statementOf(atom.payload));
|
|
164
|
+
const vectors = await channel.embed.embed(statements);
|
|
165
|
+
await channel.index.upsert(chunk.map((atom, position) => ({
|
|
166
|
+
memoryId: atom.memoryId,
|
|
167
|
+
owner: deps.owner,
|
|
168
|
+
modelId: channel.embed.modelId,
|
|
169
|
+
statement: statements[position],
|
|
170
|
+
tags: deps.tagFacetsOf(atom),
|
|
171
|
+
vector: vectors[position],
|
|
172
|
+
})));
|
|
173
|
+
};
|
|
174
|
+
// 首块在本作业内 upsert; 后续块由续链作业完成一块自续一块。续链必须
|
|
175
|
+
// fire-and-forget: 续链作业入队时当前作业仍是队尾, 在作业体内 await
|
|
176
|
+
// 自己刚入队的下一块等于自己等自己 (死锁)。
|
|
177
|
+
const continueChunks = (from) => {
|
|
178
|
+
void vectorEnqueue(async () => {
|
|
179
|
+
const chunk = backlog.slice(from, from + RECONCILE_CHUNK_UPSERTS);
|
|
180
|
+
if (chunk.length === 0)
|
|
181
|
+
return;
|
|
182
|
+
try {
|
|
183
|
+
await upsertChunk(chunk);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
// 块失败: degraded + 停止续链 (prep 失败走外层 catch, 同文案)。
|
|
187
|
+
reconcileFailure(error);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const next = from + chunk.length;
|
|
191
|
+
if (next < backlog.length && !deps.isDisposed())
|
|
192
|
+
continueChunks(next);
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
await upsertChunk(backlog.slice(0, RECONCILE_CHUNK_UPSERTS));
|
|
196
|
+
if (RECONCILE_CHUNK_UPSERTS < backlog.length)
|
|
197
|
+
continueChunks(RECONCILE_CHUNK_UPSERTS);
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
reconcileFailure(error);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
return {
|
|
205
|
+
enqueue: vectorEnqueue,
|
|
206
|
+
settle: () => vectorQueue,
|
|
207
|
+
upsertAtom: vectorUpsertAtom,
|
|
208
|
+
purgeMemories: vectorPurgeMemories,
|
|
209
|
+
reconcileOnce: vectorReconcileOnce,
|
|
210
|
+
resetReconcile: () => {
|
|
211
|
+
vectorReconcileStarted = false;
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=vector-projection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vector-projection.js","sourceRoot":"","sources":["../../src/memory/vector-projection.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 写入侧向量投影管线(capability 拆分战役 slice 3a):对账分块、upsert/purge 埋点与串行队列。状态机(vectorEnsure/failEnabling)不属于本模块;降级/弃用经 deps 回调上报。\n */\n\nimport type { CapabilityAPI } from \"@agent-forge/plugin-sdk\";\nimport type { MemoryEmbeddingProviderV1 } from \"./embedding-provider.ts\";\nimport type { MemoryRerankerV1 } from \"./embedding-reranker.ts\";\nimport type { MemoryAtomV1 } from \"./foundation.ts\";\nimport type { MemoryStoreV1 } from \"./store.ts\";\nimport type { MemoryVectorIndexV1 } from \"./vector-index.ts\";\n\n/** 单次启动对账 embed+upsert 上限, 超出留待下次启动继续。 */\nconst RECONCILE_MAX_UPSERTS = 2000;\n/**\n * 启动对账的队列作业分块 (2026-10-06 复审): 对账拆成自续链式作业, 每块至多该\n * 条数——召回/显式查询的 `await vectorQueue` 只捕获调用时刻的队尾, 故最多等待\n * 当前块而非整个 backfill (2000 条 CPU embedding 是 10-20 秒量级)。与\n * {@link RECONCILE_MAX_UPSERTS} 的分工: 2000 是单次对账总上限 (超出下次启动\n * 继续), 256 是队列作业粒度。\n */\nconst RECONCILE_CHUNK_UPSERTS = 256;\n\nexport interface ActiveVectorChannelV1 {\n\treadonly embed: MemoryEmbeddingProviderV1;\n\t/** undefined = 精排不可用 (跳过精排, 直接用 RRF 序)。 */\n\treadonly reranker: MemoryRerankerV1 | undefined;\n\treadonly index: MemoryVectorIndexV1;\n\treadonly queryPrefix: string;\n\t/** 预设 embedding modelId — 对账时与投影存储的 modelId 比较, 不一致即清空重建。 */\n\treadonly embeddingModelId: string;\n}\n\n/** 语义合并判定结论 (投影探测产物; relations 随新 atom 一次性提交, atom 不可变)。 */\nexport type SemanticMergePlanV1 =\n\t| { readonly kind: \"merge\"; readonly targetMemoryId: string }\n\t| { readonly kind: \"conflict\"; readonly conflictMemoryIds: readonly string[] }\n\t| { readonly kind: \"new\" };\n\n/** 探测产物: 判定结论 + 新 statement 的嵌入 (投影 upsert 复用同一向量)。 */\nexport interface SemanticMergeOutcomeV1 {\n\treadonly plan: SemanticMergePlanV1;\n\treadonly vector: readonly number[];\n}\n\n/**\n * 投影管线对 capability 闭包的只读依赖视图 (slice 3a): 状态机变量 (vectorState/\n * vectorChannel/vectorEnsure) 仍归 capability 闭包所有, 本模块经只读取用与回调\n * 上报参与, 不持有任何状态机变量。\n */\nexport interface MemoryVectorProjectionDepsV1 {\n\t/** 档位门 (capability.vectorEnabled): 档位关停时埋点 no-op。 */\n\treadonly vectorEnabled: () => boolean;\n\t/**\n\t * 开启状态机的只读使用入口 (capability.vectorEnsure): 惰性装配语义原样保留\n\t * (埋点/对账在通道未装配时仍触发 enabling 并等待), 本模块不感知其内部状态。\n\t */\n\treadonly ensureChannel: () => Promise<ActiveVectorChannelV1 | undefined>;\n\t/** 已就绪通道的同步快照 (vectorChannel 变量只读): purge 埋点不触发装配。 */\n\treadonly readyChannel: () => ActiveVectorChannelV1 | undefined;\n\t/** 通道可操作 (ready|degraded) 的只读判定: purge 前置检查。 */\n\treadonly isChannelActive: () => boolean;\n\t/** capability dispose 竞态闩锁 (capabilityDisposed) 的只读视图。 */\n\treadonly isDisposed: () => boolean;\n\t/**\n\t * 跨进程 canonical 身份刷新 (2026-10-07 终审守卫): 只读扫描 owner 全部域\n\t * 账本的 memoryId 并集。反向清理的孤儿判定 = store 活跃集 ∪ 刷新集 双缺席\n\t * 才删——多会话共享向量库时, 其他会话刚写入、本进程 store 尚未重放的原子\n\t * 在刷新集内, 不再被误删; complete=false (有账本文件读取失败) 时本轮禁删\n\t * 只回填, 防瞬时 IO 错误放大成整文件行误删。\n\t */\n\treadonly refreshCanonicalMemoryIds: () => {\n\t\treadonly memoryIds: ReadonlySet<string>;\n\t\treadonly complete: boolean;\n\t};\n\t/** 对账失败的 degraded 转换回调 (capability 侧写 vectorState, 状态转换语义归状态机)。 */\n\treadonly markDegraded: () => void;\n\treadonly logger: CapabilityAPI[\"logger\"];\n\t/** 投影行 owner (capability.BUILTIN_MEMORY_OWNER)。 */\n\treadonly owner: string;\n\treadonly store: MemoryStoreV1;\n\t/** 被继任 memoryId 集 (capability.supersededMemoryIds): 对账不回填已继任条目。 */\n\treadonly supersededMemoryIds: () => ReadonlySet<string>;\n\treadonly statementOf: (payload: unknown) => string;\n\treadonly tagFacetsOf: (atom: MemoryAtomV1) => string[];\n}\n\nexport interface MemoryVectorProjectionV1 {\n\t/** 串行队列入队 (原 vectorEnqueue): 同一队列串行域的写侧操作 (如语义合并探测) 复用。 */\n\tenqueue: <T>(operation: () => Promise<T>) => Promise<T>;\n\t/** 当前队尾 (await 它只等待调用时刻已入队的部分): recall 与 harness.settleVectorWork 用。 */\n\tsettle: () => Promise<unknown>;\n\tupsertAtom: (atom: MemoryAtomV1, mergeOutcome?: SemanticMergeOutcomeV1) => void;\n\tpurgeMemories: (memoryIds: readonly string[]) => void;\n\treconcileOnce: () => void;\n\t/** 重置对账一次性闸 (模型下载就绪后补跑启动对账)。 */\n\tresetReconcile: () => void;\n}\n\nexport function createMemoryVectorProjection(deps: MemoryVectorProjectionDepsV1): MemoryVectorProjectionV1 {\n\tlet vectorQueue: Promise<unknown> = Promise.resolve();\n\tconst vectorEnqueue = <T>(operation: () => Promise<T>): Promise<T> => {\n\t\tconst next = vectorQueue.then(operation, operation);\n\t\t// 队尾永不满仓 reject: 前序失败不阻断后续操作, 错误由操作自己消化。\n\t\tvectorQueue = next.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\treturn next;\n\t};\n\t/** 投影写/清失败的一次性降级告警标记 (重复失败由启动对账修复, 不刷屏)。 */\n\tlet vectorDegradedWarned = false;\n\n\tconst vectorUpsertAtom = (atom: MemoryAtomV1, mergeOutcome?: SemanticMergeOutcomeV1): void => {\n\t\tif (!deps.vectorEnabled()) return;\n\t\tvoid vectorEnqueue(async () => {\n\t\t\tconst channel = await deps.ensureChannel();\n\t\t\tif (channel === undefined) return;\n\t\t\ttry {\n\t\t\t\tconst statement = deps.statementOf(atom.payload);\n\t\t\t\tconst [vector] =\n\t\t\t\t\tmergeOutcome === undefined ? await channel.embed.embed([statement]) : [mergeOutcome.vector];\n\t\t\t\tawait channel.index.upsert([\n\t\t\t\t\t{\n\t\t\t\t\t\tmemoryId: atom.memoryId,\n\t\t\t\t\t\towner: deps.owner,\n\t\t\t\t\t\tmodelId: channel.embed.modelId,\n\t\t\t\t\t\tstatement,\n\t\t\t\t\t\ttags: deps.tagFacetsOf(atom),\n\t\t\t\t\t\tvector,\n\t\t\t\t\t},\n\t\t\t\t]);\n\t\t\t\tif (mergeOutcome?.plan.kind === \"merge\") {\n\t\t\t\t\tawait channel.index.remove([mergeOutcome.plan.targetMemoryId]);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tif (!vectorDegradedWarned) {\n\t\t\t\t\tvectorDegradedWarned = true;\n\t\t\t\t\tdeps.logger?.warn(\n\t\t\t\t\t\t\"memory vector projection write failed; the entry is rebuilt by startup reconciliation\",\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmemoryId: atom.memoryId,\n\t\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t};\n\n\t/**\n\t * purge 的 index 副本分派: 通道非 ready/degraded 时 no-op 且不报错 (enabling\n\t * 未完成/failed)。持久索引下该 no-op 会漏删 (门把它记为已确认, 不重试), 残行\n\t * 由启动对账的反向清理收敛 (投影行以 store 活跃集为准, 2026-10-07 审计收口);\n\t * 收敛前 recall 的 store 回查也保证残行不可出线。remove 异步且失败只记\n\t * degraded: 同样由对账反向清理兜底。\n\t */\n\tconst vectorPurgeMemories = (memoryIds: readonly string[]): void => {\n\t\tif (!deps.isChannelActive()) return;\n\t\tvoid vectorEnqueue(async () => {\n\t\t\tconst channel = deps.readyChannel();\n\t\t\tif (channel === undefined) return;\n\t\t\ttry {\n\t\t\t\tawait channel.index.remove(memoryIds);\n\t\t\t} catch (error) {\n\t\t\t\tif (!vectorDegradedWarned) {\n\t\t\t\t\tvectorDegradedWarned = true;\n\t\t\t\t\tdeps.logger?.warn(\n\t\t\t\t\t\t\"memory vector projection purge failed; stale rows stay unreachable via the store lookup\",\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t};\n\n\t/**\n\t * 启动对账 (一次性, capability 初始化语义内; fire-and-forget 不阻塞首工具):\n\t * 1) 投影 modelId 与预设不一致 (含 undefined 且表非空) → 清空向量表;\n\t * 2) diff store 全量 memoryId vs 投影, 缺失的分块 embed+upsert 回填: 单次\n\t * 对账总上限 {@link RECONCILE_MAX_UPSERTS} (超出留待下次启动继续), 队列\n\t * 作业按 {@link RECONCILE_CHUNK_UPSERTS} 分块——首个作业做 prep+diff 后\n\t * upsert 第 1 块, 完成后 `void vectorEnqueue(下一块)` 自续成链。召回/\n\t * 显式查询的 `await vectorQueue` 只捕获调用时刻的队尾, 故最多等待当前块\n\t * 而非整个 backfill; 写入→召回的 FIFO 语义不变 (写入埋点先于查询入队)。\n\t * 对账失败不阻塞 capability 启动, 状态转 degraded (运行期故障, 可自愈:\n\t * 后续向量操作重新触发补齐语义, canonical 面不受影响), 该块之后停止续链\n\t * (残行由下次启动对账继续)。\n\t */\n\tlet vectorReconcileStarted = false;\n\tconst vectorReconcileOnce = (): void => {\n\t\tif (!deps.vectorEnabled() || vectorReconcileStarted) return;\n\t\tvectorReconcileStarted = true;\n\t\tvoid vectorEnqueue(async () => {\n\t\t\tconst channel = await deps.ensureChannel();\n\t\t\tif (channel === undefined) return;\n\t\t\tconst reconcileFailure = (error: unknown): void => {\n\t\t\t\t// dispose 后在途分块对已关闭索引的失败是预期结果, 不是通道降级:\n\t\t\t\t// 静默停链, 不产生 degraded 态与噪声 warn。\n\t\t\t\tif (deps.isDisposed()) return;\n\t\t\t\tdeps.markDegraded();\n\t\t\t\tdeps.logger?.warn(\n\t\t\t\t\t\"memory vector reconciliation failed; the channel is degraded (canonical recall degrades, self-heals on the next successful channel op)\",\n\t\t\t\t\t{\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};\n\t\t\ttry {\n\t\t\t\tconst storedModelId = await channel.index.getStoredModelId();\n\t\t\t\tif (storedModelId !== channel.embeddingModelId) {\n\t\t\t\t\tconst stale = await channel.index.listMemoryIds();\n\t\t\t\t\tif (stale.size > 0) await channel.index.remove([...stale]);\n\t\t\t\t}\n\t\t\t\tconst indexed = await channel.index.listMemoryIds();\n\t\t\t\tconst superseded = deps.supersededMemoryIds();\n\t\t\t\tconst storeRecords = deps.store.list({ owner: deps.owner });\n\t\t\t\tconst storeIds = new Set(storeRecords.map((record) => record.atom.memoryId));\n\t\t\t\t// 反向清理 (2026-10-07 审计收口, 同日终审加跨进程守卫): 投影中不在\n\t\t\t\t// canonical 集的行是孤儿——被保留清扫清走的过期原子 (冷通道时 index\n\t\t\t\t// 副本 no-op 且被门记为已确认, 不会再重试)、forget 残行、被继任的\n\t\t\t\t// 旧行。canonical 集 = store 活跃集 ∪ 账本盘面刷新集: 多会话共享\n\t\t\t\t// 向量库时, 其他会话刚写入、本进程 store 尚未重放的原子在刷新集内,\n\t\t\t\t// 双缺席才删。刷新不完整 (有账本文件读取失败) 时本轮禁删只回填。\n\t\t\t\t// 冷启动接缝在此自愈: sweep 在 resolveDomain 同步逐出 store, 本作业\n\t\t\t\t// 随后 (通道已就绪) 才运行, 被逐出的 id 自然落入本清理。\n\t\t\t\tconst refreshed = deps.refreshCanonicalMemoryIds();\n\t\t\t\tconst canonicalIds = refreshed.complete ? new Set([...storeIds, ...refreshed.memoryIds]) : storeIds;\n\t\t\t\tconst orphans = refreshed.complete\n\t\t\t\t\t? [...indexed].filter((memoryId) => !canonicalIds.has(memoryId) || superseded.has(memoryId))\n\t\t\t\t\t: [];\n\t\t\t\tif (orphans.length > 0) await channel.index.remove(orphans);\n\t\t\t\t// 被继任的旧条目不回填投影 (S3): 合并已移除其行, 对账的重放/重建\n\t\t\t\t// 不得复活 (召回可见性由继任条目承担, canonical 侧保留可直查)。\n\t\t\t\tconst missing: MemoryAtomV1[] = [];\n\t\t\t\tfor (const record of storeRecords) {\n\t\t\t\t\tif (superseded.has(record.atom.memoryId)) continue;\n\t\t\t\t\tif (!indexed.has(record.atom.memoryId)) missing.push(record.atom);\n\t\t\t\t}\n\t\t\t\tif (missing.length > RECONCILE_MAX_UPSERTS) {\n\t\t\t\t\tdeps.logger?.warn(\"memory vector reconciliation capped; remaining entries rebuild on the next startup\", {\n\t\t\t\t\t\ttotal: missing.length,\n\t\t\t\t\t\tcap: RECONCILE_MAX_UPSERTS,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst backlog = missing.slice(0, RECONCILE_MAX_UPSERTS);\n\t\t\t\tif (backlog.length === 0) return;\n\t\t\t\tconst upsertChunk = async (chunk: readonly MemoryAtomV1[]): Promise<void> => {\n\t\t\t\t\tconst statements = chunk.map((atom) => deps.statementOf(atom.payload));\n\t\t\t\t\tconst vectors = await channel.embed.embed(statements);\n\t\t\t\t\tawait channel.index.upsert(\n\t\t\t\t\t\tchunk.map((atom, position) => ({\n\t\t\t\t\t\t\tmemoryId: atom.memoryId,\n\t\t\t\t\t\t\towner: deps.owner,\n\t\t\t\t\t\t\tmodelId: channel.embed.modelId,\n\t\t\t\t\t\t\tstatement: statements[position],\n\t\t\t\t\t\t\ttags: deps.tagFacetsOf(atom),\n\t\t\t\t\t\t\tvector: vectors[position],\n\t\t\t\t\t\t})),\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t\t// 首块在本作业内 upsert; 后续块由续链作业完成一块自续一块。续链必须\n\t\t\t\t// fire-and-forget: 续链作业入队时当前作业仍是队尾, 在作业体内 await\n\t\t\t\t// 自己刚入队的下一块等于自己等自己 (死锁)。\n\t\t\t\tconst continueChunks = (from: number): void => {\n\t\t\t\t\tvoid vectorEnqueue(async () => {\n\t\t\t\t\t\tconst chunk = backlog.slice(from, from + RECONCILE_CHUNK_UPSERTS);\n\t\t\t\t\t\tif (chunk.length === 0) return;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait upsertChunk(chunk);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t// 块失败: degraded + 停止续链 (prep 失败走外层 catch, 同文案)。\n\t\t\t\t\t\t\treconcileFailure(error);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst next = from + chunk.length;\n\t\t\t\t\t\tif (next < backlog.length && !deps.isDisposed()) continueChunks(next);\n\t\t\t\t\t});\n\t\t\t\t};\n\t\t\t\tawait upsertChunk(backlog.slice(0, RECONCILE_CHUNK_UPSERTS));\n\t\t\t\tif (RECONCILE_CHUNK_UPSERTS < backlog.length) continueChunks(RECONCILE_CHUNK_UPSERTS);\n\t\t\t} catch (error) {\n\t\t\t\treconcileFailure(error);\n\t\t\t}\n\t\t});\n\t};\n\n\treturn {\n\t\tenqueue: vectorEnqueue,\n\t\tsettle: (): Promise<unknown> => vectorQueue,\n\t\tupsertAtom: vectorUpsertAtom,\n\t\tpurgeMemories: vectorPurgeMemories,\n\t\treconcileOnce: vectorReconcileOnce,\n\t\tresetReconcile: () => {\n\t\t\tvectorReconcileStarted = false;\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 写入面(capability 拆分战役 slice 5):memory_write 工具、语义合并探测、写入
|
|
3
|
+
* atom 构造与写后自动升档旁路。闭包状态(写序计数、探测降级告警闩、升档闩)归
|
|
4
|
+
* 本模块工厂所有, 经 deps 显式注入, 不新增模块级可变状态; 搬移函数体逐字节保留。
|
|
5
|
+
*/
|
|
6
|
+
import type { CapabilityAPI, DisposableRegistration } from "@agent-forge/plugin-sdk";
|
|
7
|
+
import { type MemoryAtomV1, type MemoryRelationV1 } from "./foundation.ts";
|
|
8
|
+
import type { DurableMemoryLedgerV1 } from "./ledger.ts";
|
|
9
|
+
import type { MemorySchedulerApiV1 } from "./scheduler-api.ts";
|
|
10
|
+
import type { MemoryStoreV1 } from "./store.ts";
|
|
11
|
+
import type { SuiteMemoryDomainV1 } from "./suite-memory.ts";
|
|
12
|
+
import type { MemoryVectorChannelV1 } from "./vector-channel.ts";
|
|
13
|
+
import type { MemoryVectorProjectionV1 } from "./vector-projection.ts";
|
|
14
|
+
/**
|
|
15
|
+
* light→full 档位自动升级阈值 (2026-10-06 用户裁决「完全自动」; 记忆系统设计
|
|
16
|
+
* 「档位自动升级」): 本域活跃记忆 (store 计数, 过期记录不计) 达到该条数后,
|
|
17
|
+
* 写入提交旁路自动执行与 /memory full 同通道的动作 (下会话生效)。定档依据
|
|
18
|
+
* 真实模型规模梯度实测 (docs/plans/记忆档位自动升档阈值实测底稿.md): 衰减
|
|
19
|
+
* 平缓无拐点、主因是同主题近邻密度而非条数, 50,000 落在实测已见明显衰减区间
|
|
20
|
+
* 且远超个人稳态记忆量——未校准保守初值, 后续有真实评估证据只改本常量。
|
|
21
|
+
*/
|
|
22
|
+
export declare const MEMORY_TIER_UPGRADE_STORE_THRESHOLD = 50000;
|
|
23
|
+
/**
|
|
24
|
+
* 写入证据诚实化 (记忆写入与删除授权诚实化设计 §1.2, 2026-10-05 裁决): 各证据
|
|
25
|
+
* 类别的保守置信常量。经模型之手写入的内容不再硬编码 confidence 1 / explicit —
|
|
26
|
+
* 真正的 explicit 高置信只来自用户命令链 (/promote 的 confirmedAt)。
|
|
27
|
+
* 出厂默认值, 已登记 docs/design/产品默认值清单.md。
|
|
28
|
+
*/
|
|
29
|
+
declare const MEMORY_EVIDENCE_CONFIDENCE: {
|
|
30
|
+
readonly "user-stated": 0.9;
|
|
31
|
+
readonly "tool-observed": 0.7;
|
|
32
|
+
readonly inferred: 0.6;
|
|
33
|
+
};
|
|
34
|
+
export type MemoryWriteEvidenceClassV1 = keyof typeof MEMORY_EVIDENCE_CONFIDENCE;
|
|
35
|
+
export declare const TAG_FACET_NAMESPACE = "builtin.memory";
|
|
36
|
+
/** memory.changed 事件类型 (写入面与命令面的 capability 发布事件共用)。 */
|
|
37
|
+
export declare const EVENT_TYPE = "memory.changed";
|
|
38
|
+
/** memory_write 内容长度契约 (POC 报告 §7): reranker 512-token 上下文足够。 */
|
|
39
|
+
export declare const MEMORY_CONTENT_MAX_LENGTH = 700;
|
|
40
|
+
/** 写入 atom 构造入参 (原 buildWriteAtom 内联参数类型, 随 slice 5 迁出)。 */
|
|
41
|
+
export interface MemoryWriteAtomInputV1 {
|
|
42
|
+
readonly content: string;
|
|
43
|
+
readonly kind: "fact" | "preference";
|
|
44
|
+
readonly subject?: string;
|
|
45
|
+
readonly tags?: readonly string[];
|
|
46
|
+
/** 语义合并 (S3) 判定产生的 supersedes/conflict 关系; 缺省 = 空关系。 */
|
|
47
|
+
readonly relations?: readonly MemoryRelationV1[];
|
|
48
|
+
/** 写入来源标记; 缺省 = 模型工具面。 */
|
|
49
|
+
readonly writeReason?: string;
|
|
50
|
+
/**
|
|
51
|
+
* 证据声明 (记忆写入与删除授权诚实化设计 §1): 缺省 inferred (诚实默认 —
|
|
52
|
+
* 经模型之手写入的内容, 未经确认前都是推断); bash 教训等机器写入传
|
|
53
|
+
* tool-observed。映射见 MEMORY_EVIDENCE_CONFIDENCE / *_EVIDENCE_CLASS_OF。
|
|
54
|
+
*/
|
|
55
|
+
readonly evidenceClass?: MemoryWriteEvidenceClassV1;
|
|
56
|
+
readonly domain: SuiteMemoryDomainV1;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 写入面对 capability 闭包的显式依赖视图 (slice 5): 逐项来自
|
|
60
|
+
* createMemoryCapabilityHarness 的闭包捕获核实。状态机 (vectorChannelCtl) 与
|
|
61
|
+
* 写侧投影 (vectorProjection) 归 capability 装配所有, 本模块经对象只读消费;
|
|
62
|
+
* capability 模块级纯函数经 deps 注入避免双头定义。
|
|
63
|
+
*/
|
|
64
|
+
export interface MemoryWriteFaceDepsV1 {
|
|
65
|
+
/** capability 日志面 (api.logger): 探测降级/升档/持久化失败日志。 */
|
|
66
|
+
readonly logger: CapabilityAPI["logger"];
|
|
67
|
+
/** 工具注册面 (api.registerTool): memory_write 经同一公共通道注册。 */
|
|
68
|
+
readonly registerTool: CapabilityAPI["registerTool"];
|
|
69
|
+
/** 事件发布面 (api.publish): memory.changed 写入事件。 */
|
|
70
|
+
readonly publish: CapabilityAPI["publish"];
|
|
71
|
+
/** 检索行 owner (capability.BUILTIN_MEMORY_OWNER)。 */
|
|
72
|
+
readonly owner: string;
|
|
73
|
+
/** 会话 id (harness sessionId): 写入 observationId 序列前缀。 */
|
|
74
|
+
readonly sessionId: string;
|
|
75
|
+
/** capability 清单 (capabilityManifest): profileId / writeReason / 事件 correlationId 来源。 */
|
|
76
|
+
readonly manifest: {
|
|
77
|
+
readonly id: string;
|
|
78
|
+
};
|
|
79
|
+
/** 契约版本 (capability.MEMORY_CONTRACT_VERSION = transfer.MEMORY_CONTRACT_VERSION_V2)。 */
|
|
80
|
+
readonly contractVersion: string;
|
|
81
|
+
/** 已解析的全局档位 (harness memoryMode, off 已被工厂入口拦截)。 */
|
|
82
|
+
readonly memoryMode: "light" | "full";
|
|
83
|
+
/** agent 根目录 (MemoryCapabilityContext.agentDir): 档位持久化路径域。 */
|
|
84
|
+
readonly agentDir: string;
|
|
85
|
+
/** 时钟 (harness now)。 */
|
|
86
|
+
readonly now: () => number;
|
|
87
|
+
/** canonical store (per-capability 单例): 近邻回查/规模探测/继任扫描。 */
|
|
88
|
+
readonly store: MemoryStoreV1;
|
|
89
|
+
/** 写入观察调度 (capability.schedulerApi): submitObservation/submitCandidate。 */
|
|
90
|
+
readonly schedulerApi: MemorySchedulerApiV1;
|
|
91
|
+
/** 写侧投影 (slice 3a 的 vectorProjection): 探测入队与提交后埋点。 */
|
|
92
|
+
readonly vectorProjection: MemoryVectorProjectionV1;
|
|
93
|
+
/** 向量通道 (slice 3b 的 vectorChannelCtl): declined/rawState/ensure 只读与动作面。 */
|
|
94
|
+
readonly vectorChannelCtl: MemoryVectorChannelV1;
|
|
95
|
+
/** 域解析 (capability.resolveDomain): 每次写入调用时惰性解析 (B1 语义)。 */
|
|
96
|
+
readonly resolveDomain: () => {
|
|
97
|
+
readonly domain: SuiteMemoryDomainV1;
|
|
98
|
+
readonly domainName: string;
|
|
99
|
+
readonly ledger: DurableMemoryLedgerV1;
|
|
100
|
+
};
|
|
101
|
+
readonly assertNonEmptyString: (value: unknown, label: string) => asserts value is string;
|
|
102
|
+
readonly memoryToolInput: (first: unknown, second: unknown) => Record<string, unknown>;
|
|
103
|
+
readonly identityHash: (value: string) => string;
|
|
104
|
+
readonly statementOf: (payload: unknown) => string;
|
|
105
|
+
readonly tagFacetsOf: (atom: MemoryAtomV1) => string[];
|
|
106
|
+
}
|
|
107
|
+
/** 写入面工厂产物: 工具注册项 + 装配 (resolveDomain 重放) 与钩子面消费的闭包视图。 */
|
|
108
|
+
export interface MemoryWriteFaceV1 {
|
|
109
|
+
/** memory_write 工具注册项 (capability registrations 收集)。 */
|
|
110
|
+
readonly writeTool: DisposableRegistration;
|
|
111
|
+
/** 当前 store 内被 supersedes 关系指向的 memoryId 集 (S3): 投影对账 deps。 */
|
|
112
|
+
readonly supersededMemoryIds: () => ReadonlySet<string>;
|
|
113
|
+
/** 写入 atom 构造 (原 buildWriteAtom): bash 教训 (hooks, slice 7) 复用。 */
|
|
114
|
+
readonly buildWriteAtom: (input: MemoryWriteAtomInputV1) => MemoryAtomV1;
|
|
115
|
+
/** 写后自动升档旁路 (原 maybeTierUpgradeAfterWrite): 教训写入后复用。 */
|
|
116
|
+
readonly maybeTierUpgradeAfterWrite: () => void;
|
|
117
|
+
/** 升档声明只读视图: /memory 状态行的 pending 尾巴 (命令面消费)。 */
|
|
118
|
+
readonly tierUpgradePending: () => boolean;
|
|
119
|
+
/** 用户显式档位表达取代本实例升档声明 (/memory light, 命令面消费)。 */
|
|
120
|
+
readonly clearTierUpgrade: () => void;
|
|
121
|
+
/** 写入 observationId → 会话内序号 (resolveDomain 重放解析)。 */
|
|
122
|
+
readonly writeSequenceOf: (observationId: string) => number | undefined;
|
|
123
|
+
/** 持久化序号抬高写序地板 (resolveDomain 重放; 只升不降)。 */
|
|
124
|
+
readonly advanceWriteSequenceFloor: (sequence: number) => void;
|
|
125
|
+
}
|
|
126
|
+
export declare function createMemoryWriteFace(deps: MemoryWriteFaceDepsV1): MemoryWriteFaceV1;
|
|
127
|
+
export {};
|
|
128
|
+
//# sourceMappingURL=write-face.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-face.d.ts","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"]}
|