ai-memory-layer 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +21 -0
  3. package/README.md +765 -0
  4. package/bin/memory-server.mjs +157 -0
  5. package/dist/adapters/memory/embeddings.d.ts +4 -0
  6. package/dist/adapters/memory/embeddings.d.ts.map +1 -0
  7. package/dist/adapters/memory/embeddings.js +53 -0
  8. package/dist/adapters/memory/embeddings.js.map +1 -0
  9. package/dist/adapters/memory/index.d.ts +7 -0
  10. package/dist/adapters/memory/index.d.ts.map +1 -0
  11. package/dist/adapters/memory/index.js +650 -0
  12. package/dist/adapters/memory/index.js.map +1 -0
  13. package/dist/adapters/postgres/index.d.ts +38 -0
  14. package/dist/adapters/postgres/index.d.ts.map +1 -0
  15. package/dist/adapters/postgres/index.js +982 -0
  16. package/dist/adapters/postgres/index.js.map +1 -0
  17. package/dist/adapters/sqlite/embeddings.d.ts +5 -0
  18. package/dist/adapters/sqlite/embeddings.d.ts.map +1 -0
  19. package/dist/adapters/sqlite/embeddings.js +122 -0
  20. package/dist/adapters/sqlite/embeddings.js.map +1 -0
  21. package/dist/adapters/sqlite/index.d.ts +8 -0
  22. package/dist/adapters/sqlite/index.d.ts.map +1 -0
  23. package/dist/adapters/sqlite/index.js +839 -0
  24. package/dist/adapters/sqlite/index.js.map +1 -0
  25. package/dist/adapters/sqlite/mappers.d.ts +40 -0
  26. package/dist/adapters/sqlite/mappers.d.ts.map +1 -0
  27. package/dist/adapters/sqlite/mappers.js +95 -0
  28. package/dist/adapters/sqlite/mappers.js.map +1 -0
  29. package/dist/adapters/sqlite/schema.d.ts +4 -0
  30. package/dist/adapters/sqlite/schema.d.ts.map +1 -0
  31. package/dist/adapters/sqlite/schema.js +394 -0
  32. package/dist/adapters/sqlite/schema.js.map +1 -0
  33. package/dist/adapters/sync-to-async.d.ts +15 -0
  34. package/dist/adapters/sync-to-async.d.ts.map +1 -0
  35. package/dist/adapters/sync-to-async.js +95 -0
  36. package/dist/adapters/sync-to-async.js.map +1 -0
  37. package/dist/cli/inspect.d.ts +34 -0
  38. package/dist/cli/inspect.d.ts.map +1 -0
  39. package/dist/cli/inspect.js +190 -0
  40. package/dist/cli/inspect.js.map +1 -0
  41. package/dist/contracts/async-storage.d.ts +86 -0
  42. package/dist/contracts/async-storage.d.ts.map +1 -0
  43. package/dist/contracts/async-storage.js +2 -0
  44. package/dist/contracts/async-storage.js.map +1 -0
  45. package/dist/contracts/embedding.d.ts +22 -0
  46. package/dist/contracts/embedding.d.ts.map +1 -0
  47. package/dist/contracts/embedding.js +2 -0
  48. package/dist/contracts/embedding.js.map +1 -0
  49. package/dist/contracts/identity.d.ts +29 -0
  50. package/dist/contracts/identity.d.ts.map +1 -0
  51. package/dist/contracts/identity.js +34 -0
  52. package/dist/contracts/identity.js.map +1 -0
  53. package/dist/contracts/observability.d.ts +18 -0
  54. package/dist/contracts/observability.d.ts.map +1 -0
  55. package/dist/contracts/observability.js +7 -0
  56. package/dist/contracts/observability.js.map +1 -0
  57. package/dist/contracts/policy.d.ts +108 -0
  58. package/dist/contracts/policy.d.ts.map +1 -0
  59. package/dist/contracts/policy.js +107 -0
  60. package/dist/contracts/policy.js.map +1 -0
  61. package/dist/contracts/storage.d.ts +78 -0
  62. package/dist/contracts/storage.d.ts.map +1 -0
  63. package/dist/contracts/storage.js +2 -0
  64. package/dist/contracts/storage.js.map +1 -0
  65. package/dist/contracts/types.d.ts +381 -0
  66. package/dist/contracts/types.d.ts.map +1 -0
  67. package/dist/contracts/types.js +94 -0
  68. package/dist/contracts/types.js.map +1 -0
  69. package/dist/core/circuit-breaker.d.ts +11 -0
  70. package/dist/core/circuit-breaker.d.ts.map +1 -0
  71. package/dist/core/circuit-breaker.js +38 -0
  72. package/dist/core/circuit-breaker.js.map +1 -0
  73. package/dist/core/context.d.ts +56 -0
  74. package/dist/core/context.d.ts.map +1 -0
  75. package/dist/core/context.js +345 -0
  76. package/dist/core/context.js.map +1 -0
  77. package/dist/core/events.d.ts +8 -0
  78. package/dist/core/events.d.ts.map +1 -0
  79. package/dist/core/events.js +25 -0
  80. package/dist/core/events.js.map +1 -0
  81. package/dist/core/extractor.d.ts +37 -0
  82. package/dist/core/extractor.d.ts.map +1 -0
  83. package/dist/core/extractor.js +448 -0
  84. package/dist/core/extractor.js.map +1 -0
  85. package/dist/core/formatter.d.ts +25 -0
  86. package/dist/core/formatter.d.ts.map +1 -0
  87. package/dist/core/formatter.js +97 -0
  88. package/dist/core/formatter.js.map +1 -0
  89. package/dist/core/knowledge-lifecycle.d.ts +15 -0
  90. package/dist/core/knowledge-lifecycle.d.ts.map +1 -0
  91. package/dist/core/knowledge-lifecycle.js +103 -0
  92. package/dist/core/knowledge-lifecycle.js.map +1 -0
  93. package/dist/core/maintenance.d.ts +13 -0
  94. package/dist/core/maintenance.d.ts.map +1 -0
  95. package/dist/core/maintenance.js +102 -0
  96. package/dist/core/maintenance.js.map +1 -0
  97. package/dist/core/manager.d.ts +110 -0
  98. package/dist/core/manager.d.ts.map +1 -0
  99. package/dist/core/manager.js +640 -0
  100. package/dist/core/manager.js.map +1 -0
  101. package/dist/core/monitor.d.ts +73 -0
  102. package/dist/core/monitor.d.ts.map +1 -0
  103. package/dist/core/monitor.js +395 -0
  104. package/dist/core/monitor.js.map +1 -0
  105. package/dist/core/orchestrator.d.ts +64 -0
  106. package/dist/core/orchestrator.d.ts.map +1 -0
  107. package/dist/core/orchestrator.js +916 -0
  108. package/dist/core/orchestrator.js.map +1 -0
  109. package/dist/core/presets.d.ts +15 -0
  110. package/dist/core/presets.d.ts.map +1 -0
  111. package/dist/core/presets.js +99 -0
  112. package/dist/core/presets.js.map +1 -0
  113. package/dist/core/provider-managers.d.ts +47 -0
  114. package/dist/core/provider-managers.d.ts.map +1 -0
  115. package/dist/core/provider-managers.js +112 -0
  116. package/dist/core/provider-managers.js.map +1 -0
  117. package/dist/core/quick.d.ts +62 -0
  118. package/dist/core/quick.d.ts.map +1 -0
  119. package/dist/core/quick.js +300 -0
  120. package/dist/core/quick.js.map +1 -0
  121. package/dist/core/retrieval.d.ts +29 -0
  122. package/dist/core/retrieval.d.ts.map +1 -0
  123. package/dist/core/retrieval.js +150 -0
  124. package/dist/core/retrieval.js.map +1 -0
  125. package/dist/core/runtime.d.ts +67 -0
  126. package/dist/core/runtime.d.ts.map +1 -0
  127. package/dist/core/runtime.js +84 -0
  128. package/dist/core/runtime.js.map +1 -0
  129. package/dist/core/streaming.d.ts +37 -0
  130. package/dist/core/streaming.d.ts.map +1 -0
  131. package/dist/core/streaming.js +51 -0
  132. package/dist/core/streaming.js.map +1 -0
  133. package/dist/core/sync.d.ts +13 -0
  134. package/dist/core/sync.d.ts.map +1 -0
  135. package/dist/core/sync.js +46 -0
  136. package/dist/core/sync.js.map +1 -0
  137. package/dist/core/telemetry.d.ts +8 -0
  138. package/dist/core/telemetry.d.ts.map +1 -0
  139. package/dist/core/telemetry.js +14 -0
  140. package/dist/core/telemetry.js.map +1 -0
  141. package/dist/core/tokens.d.ts +8 -0
  142. package/dist/core/tokens.d.ts.map +1 -0
  143. package/dist/core/tokens.js +59 -0
  144. package/dist/core/tokens.js.map +1 -0
  145. package/dist/core/trust.d.ts +23 -0
  146. package/dist/core/trust.d.ts.map +1 -0
  147. package/dist/core/trust.js +164 -0
  148. package/dist/core/trust.js.map +1 -0
  149. package/dist/core/validation.d.ts +36 -0
  150. package/dist/core/validation.d.ts.map +1 -0
  151. package/dist/core/validation.js +185 -0
  152. package/dist/core/validation.js.map +1 -0
  153. package/dist/embeddings/local.d.ts +5 -0
  154. package/dist/embeddings/local.d.ts.map +1 -0
  155. package/dist/embeddings/local.js +128 -0
  156. package/dist/embeddings/local.js.map +1 -0
  157. package/dist/embeddings/openai.d.ts +26 -0
  158. package/dist/embeddings/openai.d.ts.map +1 -0
  159. package/dist/embeddings/openai.js +48 -0
  160. package/dist/embeddings/openai.js.map +1 -0
  161. package/dist/embeddings/resilience.d.ts +5 -0
  162. package/dist/embeddings/resilience.d.ts.map +1 -0
  163. package/dist/embeddings/resilience.js +53 -0
  164. package/dist/embeddings/resilience.js.map +1 -0
  165. package/dist/embeddings/voyage.d.ts +30 -0
  166. package/dist/embeddings/voyage.d.ts.map +1 -0
  167. package/dist/embeddings/voyage.js +53 -0
  168. package/dist/embeddings/voyage.js.map +1 -0
  169. package/dist/index.d.ts +72 -0
  170. package/dist/index.d.ts.map +1 -0
  171. package/dist/index.js +40 -0
  172. package/dist/index.js.map +1 -0
  173. package/dist/integrations/claude-agent.d.ts +21 -0
  174. package/dist/integrations/claude-agent.d.ts.map +1 -0
  175. package/dist/integrations/claude-agent.js +44 -0
  176. package/dist/integrations/claude-agent.js.map +1 -0
  177. package/dist/integrations/claude-tools.d.ts +18 -0
  178. package/dist/integrations/claude-tools.d.ts.map +1 -0
  179. package/dist/integrations/claude-tools.js +60 -0
  180. package/dist/integrations/claude-tools.js.map +1 -0
  181. package/dist/integrations/langchain.d.ts +24 -0
  182. package/dist/integrations/langchain.d.ts.map +1 -0
  183. package/dist/integrations/langchain.js +48 -0
  184. package/dist/integrations/langchain.js.map +1 -0
  185. package/dist/integrations/mcp.d.ts +23 -0
  186. package/dist/integrations/mcp.d.ts.map +1 -0
  187. package/dist/integrations/mcp.js +60 -0
  188. package/dist/integrations/mcp.js.map +1 -0
  189. package/dist/integrations/middleware.d.ts +15 -0
  190. package/dist/integrations/middleware.d.ts.map +1 -0
  191. package/dist/integrations/middleware.js +27 -0
  192. package/dist/integrations/middleware.js.map +1 -0
  193. package/dist/integrations/openai-tools.d.ts +21 -0
  194. package/dist/integrations/openai-tools.d.ts.map +1 -0
  195. package/dist/integrations/openai-tools.js +69 -0
  196. package/dist/integrations/openai-tools.js.map +1 -0
  197. package/dist/integrations/vercel-ai.d.ts +19 -0
  198. package/dist/integrations/vercel-ai.d.ts.map +1 -0
  199. package/dist/integrations/vercel-ai.js +41 -0
  200. package/dist/integrations/vercel-ai.js.map +1 -0
  201. package/dist/server/http-server.d.ts +61 -0
  202. package/dist/server/http-server.d.ts.map +1 -0
  203. package/dist/server/http-server.js +684 -0
  204. package/dist/server/http-server.js.map +1 -0
  205. package/dist/server/index.d.ts +5 -0
  206. package/dist/server/index.d.ts.map +1 -0
  207. package/dist/server/index.js +3 -0
  208. package/dist/server/index.js.map +1 -0
  209. package/dist/server/mcp-server.d.ts +61 -0
  210. package/dist/server/mcp-server.d.ts.map +1 -0
  211. package/dist/server/mcp-server.js +465 -0
  212. package/dist/server/mcp-server.js.map +1 -0
  213. package/dist/summarizers/claude.d.ts +11 -0
  214. package/dist/summarizers/claude.d.ts.map +1 -0
  215. package/dist/summarizers/claude.js +39 -0
  216. package/dist/summarizers/claude.js.map +1 -0
  217. package/dist/summarizers/client.d.ts +23 -0
  218. package/dist/summarizers/client.d.ts.map +1 -0
  219. package/dist/summarizers/client.js +24 -0
  220. package/dist/summarizers/client.js.map +1 -0
  221. package/dist/summarizers/extractive.d.ts +6 -0
  222. package/dist/summarizers/extractive.d.ts.map +1 -0
  223. package/dist/summarizers/extractive.js +204 -0
  224. package/dist/summarizers/extractive.js.map +1 -0
  225. package/dist/summarizers/extractor.d.ts +12 -0
  226. package/dist/summarizers/extractor.d.ts.map +1 -0
  227. package/dist/summarizers/extractor.js +75 -0
  228. package/dist/summarizers/extractor.js.map +1 -0
  229. package/dist/summarizers/openai.d.ts +11 -0
  230. package/dist/summarizers/openai.d.ts.map +1 -0
  231. package/dist/summarizers/openai.js +41 -0
  232. package/dist/summarizers/openai.js.map +1 -0
  233. package/dist/summarizers/prompts.d.ts +11 -0
  234. package/dist/summarizers/prompts.d.ts.map +1 -0
  235. package/dist/summarizers/prompts.js +104 -0
  236. package/dist/summarizers/prompts.js.map +1 -0
  237. package/docs/DEPLOYMENT.md +84 -0
  238. package/docs/INTEGRATIONS.md +64 -0
  239. package/docs/MEMORY_QUALITY_BASELINE.md +55 -0
  240. package/docs/MEMORY_QUALITY_RELEASE_GATE.md +63 -0
  241. package/docs/MEMORY_QUALITY_RUBRIC.md +249 -0
  242. package/docs/OPERATIONS.md +49 -0
  243. package/docs/SECURITY.md +25 -0
  244. package/openapi.yaml +843 -0
  245. package/package.json +157 -0
@@ -0,0 +1,916 @@
1
+ import { normalizeScope } from '../contracts/identity.js';
2
+ import { DEFAULT_EXTRACTION_POLICY } from '../contracts/policy.js';
3
+ import { assertCompactionTrigger, assertFactConfidence, assertFactType, assertFactType as assertExtractedFactType, assertNonEmpty, assertStringArray, assertMaxEntries, nowSeconds, } from './validation.js';
4
+ import { classifyFactRelation, createRegexExtractor, normalizeFactText, normalizeExtractedFact, normalizeKnowledgeMemory, } from './extractor.js';
5
+ import { emitMemoryEvent } from './telemetry.js';
6
+ import { assessCandidateTrust, buildKnowledgeConflict } from './trust.js';
7
+ import { getNativeSyncAdapter } from '../adapters/sync-to-async.js';
8
+ function resolveExtractionPolicy(policy) {
9
+ return {
10
+ ...DEFAULT_EXTRACTION_POLICY,
11
+ ...policy,
12
+ };
13
+ }
14
+ function confidenceScore(confidence) {
15
+ if (confidence === 'high')
16
+ return 3;
17
+ if (confidence === 'medium')
18
+ return 2;
19
+ return 1;
20
+ }
21
+ function meetsConfidenceThreshold(confidence, minimum) {
22
+ return confidenceScore(confidence) >= confidenceScore(minimum);
23
+ }
24
+ function buildKnowledgeInput(scope, workingMemoryId, fact, options) {
25
+ return {
26
+ ...scope,
27
+ fact: fact.fact,
28
+ fact_type: fact.factType,
29
+ knowledge_state: options.knowledgeState,
30
+ knowledge_class: options.knowledgeClass,
31
+ fact_subject: fact.subject,
32
+ fact_attribute: fact.attribute,
33
+ fact_value: fact.value,
34
+ normalized_fact: fact.normalizedFact,
35
+ slot_key: fact.slotKey,
36
+ is_negated: fact.isNegated,
37
+ source: 'promoted_from_working',
38
+ confidence: options.confidence,
39
+ confidence_score: options.confidenceScore,
40
+ grounding_strength: (options.sourceTurnIds.length >= 2
41
+ ? 'strong'
42
+ : options.sourceTurnIds.length === 1
43
+ ? 'moderate'
44
+ : 'weak'),
45
+ evidence_count: options.sourceTurnIds.length,
46
+ trust_score: options.trustScore,
47
+ verification_status: options.verificationStatus,
48
+ verification_notes: options.verificationNotes ?? null,
49
+ last_verified_at: options.verificationStatus === 'unverified' ? null : nowSeconds(),
50
+ source_working_memory_id: workingMemoryId,
51
+ source_turn_ids: options.sourceTurnIds,
52
+ contradiction_score: options.contradictionScore ?? 0,
53
+ dispute_reason: options.disputeReason ?? null,
54
+ };
55
+ }
56
+ function deriveConfidenceScore(confidence, sourceTurnCount, relation) {
57
+ const base = confidence === 'high' ? 0.85 : confidence === 'medium' ? 0.65 : 0.4;
58
+ const corroborationBoost = Math.min(0.1, Math.max(0, sourceTurnCount - 1) * 0.05);
59
+ const conflictPenalty = relation === 'conflict' ? 0.1 : 0;
60
+ return Math.max(0, Math.min(1, base + corroborationBoost - conflictPenalty));
61
+ }
62
+ function deriveVerificationStatus(sourceTurnCount, override) {
63
+ if (override)
64
+ return override;
65
+ return sourceTurnCount >= 2 ? 'corroborated' : 'unverified';
66
+ }
67
+ function mapFactTypeToKnowledgeClass(factType) {
68
+ if (factType === 'entity')
69
+ return 'identity';
70
+ if (factType === 'preference')
71
+ return 'preference';
72
+ if (factType === 'constraint')
73
+ return 'constraint';
74
+ if (factType === 'decision')
75
+ return 'procedure';
76
+ return 'project_fact';
77
+ }
78
+ function mergeRecoveredFacts(extracted, recovered, maxFactsPerExtraction) {
79
+ const merged = [];
80
+ const seen = new Set();
81
+ for (const fact of [...extracted, ...recovered]) {
82
+ const key = `${fact.factType}:${fact.slotKey ?? fact.normalizedFact}`;
83
+ if (seen.has(key))
84
+ continue;
85
+ seen.add(key);
86
+ merged.push(fact);
87
+ if (merged.length >= maxFactsPerExtraction)
88
+ break;
89
+ }
90
+ return merged;
91
+ }
92
+ function evidenceSourceTypeForTurn(turn) {
93
+ if (turn.role === 'assistant')
94
+ return 'assistant_turn';
95
+ if (turn.role === 'system')
96
+ return 'system_turn';
97
+ return 'user_turn';
98
+ }
99
+ function tokenizeForGrounding(text) {
100
+ return normalizeFactText(text)
101
+ .split(/[^a-z0-9]+/g)
102
+ .filter((token) => token.length >= 3);
103
+ }
104
+ function buildEvidenceExcerpt(turn, fact) {
105
+ const sourceText = normalizeFactText(fact.sourceText ?? fact.fact);
106
+ const lowered = turn.content.toLowerCase();
107
+ if (sourceText.length > 0) {
108
+ const index = lowered.indexOf(sourceText);
109
+ if (index >= 0) {
110
+ return turn.content.slice(index, Math.min(turn.content.length, index + Math.max(80, sourceText.length)));
111
+ }
112
+ }
113
+ return turn.content.slice(0, 160);
114
+ }
115
+ function containsContradictionCue(text) {
116
+ return /\b(?:avoid|avoids|not|never|cannot|can't|must not|should not|do not|does not)\b/.test(text);
117
+ }
118
+ function groundFactAgainstTurns(scope, workingMemoryId, fact, turns) {
119
+ const sourceText = normalizeFactText(fact.sourceText ?? fact.fact);
120
+ const factTokens = new Set(tokenizeForGrounding(fact.sourceText ?? fact.fact));
121
+ const evidence = [];
122
+ const supportedTurnIds = [];
123
+ let strongestExplicitness = 0;
124
+ for (const turn of turns) {
125
+ const lowered = normalizeFactText(turn.content);
126
+ const exactMatch = sourceText.length > 0 && lowered.includes(sourceText);
127
+ const tokenHits = [...factTokens].filter((token) => lowered.includes(token)).length;
128
+ const overlap = factTokens.size > 0 ? tokenHits / factTokens.size : 0;
129
+ const matched = exactMatch || overlap >= 0.6 || tokenHits >= 3;
130
+ if (!matched)
131
+ continue;
132
+ const contradictory = !fact.isNegated &&
133
+ containsContradictionCue(lowered) &&
134
+ !containsContradictionCue(sourceText);
135
+ const explicitnessScore = exactMatch ? 1 : Math.min(0.95, 0.45 + overlap * 0.4);
136
+ strongestExplicitness = Math.max(strongestExplicitness, explicitnessScore);
137
+ if (!contradictory) {
138
+ supportedTurnIds.push(turn.id);
139
+ }
140
+ evidence.push({
141
+ knowledge_candidate_id: null,
142
+ knowledge_memory_id: null,
143
+ working_memory_id: workingMemoryId,
144
+ turn_id: turn.id,
145
+ source_type: evidenceSourceTypeForTurn(turn),
146
+ support_polarity: contradictory ? 'contradicts' : 'supports',
147
+ speaker_role: turn.role,
148
+ actor: turn.actor,
149
+ excerpt: buildEvidenceExcerpt(turn, fact),
150
+ start_offset: null,
151
+ end_offset: null,
152
+ is_explicit: exactMatch,
153
+ explicitness_score: explicitnessScore,
154
+ outcome: null,
155
+ });
156
+ }
157
+ const groundingStrength = supportedTurnIds.length >= 2
158
+ ? 'strong'
159
+ : supportedTurnIds.length === 1
160
+ ? strongestExplicitness >= 0.85
161
+ ? 'strong'
162
+ : 'moderate'
163
+ : 'weak';
164
+ const trustScore = supportedTurnIds.length === 0 ? 0.15 : Math.min(0.95, 0.45 + strongestExplicitness * 0.4);
165
+ return {
166
+ candidate: {
167
+ fact: fact.fact,
168
+ fact_type: fact.factType,
169
+ knowledge_class: mapFactTypeToKnowledgeClass(fact.factType),
170
+ normalized_fact: fact.normalizedFact,
171
+ slot_key: fact.slotKey,
172
+ confidence: fact.confidence,
173
+ source_summary: true,
174
+ source_turns: supportedTurnIds.length > 0,
175
+ grounding_strength: groundingStrength,
176
+ evidence_count: supportedTurnIds.length,
177
+ trust_score: trustScore,
178
+ state: supportedTurnIds.length > 0 ? 'provisional' : 'candidate',
179
+ },
180
+ evidence,
181
+ supportedTurnIds,
182
+ };
183
+ }
184
+ function sortTurnsAscending(turns) {
185
+ return [...turns].sort((a, b) => a.id - b.id);
186
+ }
187
+ async function runAtomicStorage(adapter, runAsync, runSync) {
188
+ const syncAdapter = getNativeSyncAdapter(adapter);
189
+ if (syncAdapter) {
190
+ return Promise.resolve(syncAdapter.transaction(() => runSync(syncAdapter)));
191
+ }
192
+ return adapter.transaction(runAsync);
193
+ }
194
+ function assertTurnsMatchScope(turns, scope, sessionId) {
195
+ const normalized = normalizeScope(scope);
196
+ for (const turn of turns) {
197
+ if (turn.session_id !== sessionId) {
198
+ throw new Error(`Memory validation: turn ${turn.id} session_id '${turn.session_id}' does not match '${sessionId}'`);
199
+ }
200
+ if (turn.tenant_id !== normalized.tenant_id ||
201
+ turn.system_id !== normalized.system_id ||
202
+ turn.workspace_id !== normalized.workspace_id ||
203
+ turn.collaboration_id !== normalized.collaboration_id ||
204
+ turn.scope_id !== normalized.scope_id) {
205
+ throw new Error(`Memory validation: turn ${turn.id} does not belong to the requested scope`);
206
+ }
207
+ }
208
+ }
209
+ export async function commitCompaction(adapter, input) {
210
+ const normalizedScope = normalizeScope(input.scope);
211
+ assertNonEmpty(input.sessionId, 'sessionId');
212
+ assertNonEmpty(input.summary, 'summary');
213
+ assertCompactionTrigger(input.trigger, 'trigger');
214
+ assertStringArray(input.keyEntities, 'keyEntities');
215
+ assertStringArray(input.topicTags, 'topicTags');
216
+ assertMaxEntries(input.topicTags, 'topicTags', 5);
217
+ const turnsToArchive = sortTurnsAscending(input.turnsToArchive);
218
+ if (turnsToArchive.length === 0) {
219
+ throw new Error("Memory validation: 'turnsToArchive' must not be empty");
220
+ }
221
+ assertTurnsMatchScope(turnsToArchive, normalizedScope, input.sessionId);
222
+ const turnIdStart = turnsToArchive[0].id;
223
+ const turnIdEnd = turnsToArchive[turnsToArchive.length - 1].id;
224
+ const tokensCompactedEstimate = turnsToArchive.reduce((acc, turn) => acc + turn.token_estimate, 0);
225
+ const archivedAt = nowSeconds();
226
+ return runAtomicStorage(adapter, async () => {
227
+ const workingMemory = await adapter.insertWorkingMemory({
228
+ ...normalizedScope,
229
+ session_id: input.sessionId,
230
+ summary: input.summary,
231
+ key_entities: input.keyEntities,
232
+ topic_tags: input.topicTags,
233
+ turn_id_start: turnIdStart,
234
+ turn_id_end: turnIdEnd,
235
+ turn_count: turnsToArchive.length,
236
+ compaction_trigger: input.trigger,
237
+ });
238
+ const compactionLog = await adapter.insertCompactionLog({
239
+ ...normalizedScope,
240
+ session_id: input.sessionId,
241
+ trigger_type: input.trigger,
242
+ turn_id_start: turnIdStart,
243
+ turn_id_end: turnIdEnd,
244
+ turns_compacted: turnsToArchive.length,
245
+ tokens_compacted_estimate: tokensCompactedEstimate,
246
+ working_memory_id: workingMemory.id,
247
+ active_turn_count_before: input.activeTurnCountBefore,
248
+ active_turn_count_after: input.activeTurnCountAfter,
249
+ duration_ms: input.durationMs,
250
+ model_call_made: input.modelCallMade,
251
+ });
252
+ for (const turn of turnsToArchive) {
253
+ await adapter.archiveTurn(turn.id, archivedAt, compactionLog.id);
254
+ }
255
+ return {
256
+ workingMemory,
257
+ compactionLog,
258
+ archivedTurnIds: turnsToArchive.map((turn) => turn.id),
259
+ };
260
+ }, (syncAdapter) => {
261
+ const workingMemory = syncAdapter.insertWorkingMemory({
262
+ ...normalizedScope,
263
+ session_id: input.sessionId,
264
+ summary: input.summary,
265
+ key_entities: input.keyEntities,
266
+ topic_tags: input.topicTags,
267
+ turn_id_start: turnIdStart,
268
+ turn_id_end: turnIdEnd,
269
+ turn_count: turnsToArchive.length,
270
+ compaction_trigger: input.trigger,
271
+ });
272
+ const compactionLog = syncAdapter.insertCompactionLog({
273
+ ...normalizedScope,
274
+ session_id: input.sessionId,
275
+ trigger_type: input.trigger,
276
+ turn_id_start: turnIdStart,
277
+ turn_id_end: turnIdEnd,
278
+ turns_compacted: turnsToArchive.length,
279
+ tokens_compacted_estimate: tokensCompactedEstimate,
280
+ working_memory_id: workingMemory.id,
281
+ active_turn_count_before: input.activeTurnCountBefore,
282
+ active_turn_count_after: input.activeTurnCountAfter,
283
+ duration_ms: input.durationMs,
284
+ model_call_made: input.modelCallMade,
285
+ });
286
+ for (const turn of turnsToArchive) {
287
+ syncAdapter.archiveTurn(turn.id, archivedAt, compactionLog.id);
288
+ }
289
+ return {
290
+ workingMemory,
291
+ compactionLog,
292
+ archivedTurnIds: turnsToArchive.map((turn) => turn.id),
293
+ };
294
+ }).then((result) => {
295
+ emitMemoryEvent('compaction', normalizedScope, input, input.durationMs, {
296
+ trigger: input.trigger,
297
+ archivedTurnCount: turnsToArchive.length,
298
+ activeTurnCountBefore: input.activeTurnCountBefore,
299
+ activeTurnCountAfter: input.activeTurnCountAfter,
300
+ workingMemoryId: result.workingMemory.id,
301
+ compactionLogId: result.compactionLog.id,
302
+ });
303
+ return result;
304
+ });
305
+ }
306
+ export async function compactTurns(adapter, scope, sessionId, turnsToCompact, summarize, trigger, retainedTurnCount, telemetry) {
307
+ assertCompactionTrigger(trigger, 'trigger');
308
+ if (!Number.isInteger(retainedTurnCount) || retainedTurnCount < 0) {
309
+ throw new Error(`Memory validation: 'retainedTurnCount' must be a non-negative integer, got '${retainedTurnCount}'`);
310
+ }
311
+ const orderedTurns = sortTurnsAscending(turnsToCompact);
312
+ assertTurnsMatchScope(orderedTurns, scope, sessionId);
313
+ const turnsToArchive = orderedTurns.slice(0, Math.max(0, orderedTurns.length - retainedTurnCount));
314
+ if (turnsToArchive.length === 0) {
315
+ throw new Error('Memory validation: no turns are eligible for compaction');
316
+ }
317
+ const startedAtMs = Date.now();
318
+ const summary = await summarize(turnsToArchive);
319
+ const durationMs = Date.now() - startedAtMs;
320
+ return commitCompaction(adapter, {
321
+ scope,
322
+ sessionId,
323
+ summary: summary.summary,
324
+ keyEntities: summary.key_entities,
325
+ topicTags: summary.topic_tags,
326
+ turnsToArchive,
327
+ activeTurnCountBefore: orderedTurns.length,
328
+ activeTurnCountAfter: orderedTurns.length - turnsToArchive.length,
329
+ trigger,
330
+ durationMs,
331
+ modelCallMade: true,
332
+ ...telemetry,
333
+ });
334
+ }
335
+ export async function promoteToKnowledge(adapter, workingMemoryId, input) {
336
+ assertNonEmpty(input.fact, 'fact');
337
+ assertFactType(input.factType);
338
+ assertFactConfidence(input.confidence);
339
+ const normalizedScope = normalizeScope(input.scope);
340
+ const workingMemory = await adapter.getWorkingMemoryById(workingMemoryId);
341
+ if (!workingMemory) {
342
+ throw new Error(`Memory validation: working memory ${workingMemoryId} was not found`);
343
+ }
344
+ if (workingMemory.tenant_id !== normalizedScope.tenant_id ||
345
+ workingMemory.system_id !== normalizedScope.system_id ||
346
+ workingMemory.workspace_id !== normalizedScope.workspace_id ||
347
+ workingMemory.collaboration_id !== normalizedScope.collaboration_id ||
348
+ workingMemory.scope_id !== normalizedScope.scope_id) {
349
+ throw new Error(`Memory validation: working memory ${workingMemoryId} does not belong to the requested scope`);
350
+ }
351
+ return runAtomicStorage(adapter, async () => {
352
+ const knowledgeMemory = await adapter.insertKnowledgeMemory({
353
+ ...normalizedScope,
354
+ fact: input.fact,
355
+ fact_type: input.factType,
356
+ source: 'promoted_from_working',
357
+ confidence: input.confidence,
358
+ confidence_score: deriveConfidenceScore(input.confidence, 1, 'created'),
359
+ verification_status: 'unverified',
360
+ source_working_memory_id: workingMemoryId,
361
+ source_turn_ids: [],
362
+ });
363
+ await adapter.markWorkingMemoryPromoted(workingMemoryId, knowledgeMemory.id);
364
+ return knowledgeMemory;
365
+ }, (syncAdapter) => {
366
+ const knowledgeMemory = syncAdapter.insertKnowledgeMemory({
367
+ ...normalizedScope,
368
+ fact: input.fact,
369
+ fact_type: input.factType,
370
+ source: 'promoted_from_working',
371
+ confidence: input.confidence,
372
+ confidence_score: deriveConfidenceScore(input.confidence, 1, 'created'),
373
+ verification_status: 'unverified',
374
+ source_working_memory_id: workingMemoryId,
375
+ source_turn_ids: [],
376
+ });
377
+ syncAdapter.markWorkingMemoryPromoted(workingMemoryId, knowledgeMemory.id);
378
+ return knowledgeMemory;
379
+ }).then((knowledgeMemory) => {
380
+ emitMemoryEvent('promotion', normalizedScope, input, 0, {
381
+ workingMemoryId,
382
+ knowledgeMemoryId: knowledgeMemory.id,
383
+ factType: input.factType,
384
+ });
385
+ return knowledgeMemory;
386
+ });
387
+ }
388
+ export async function extractKnowledge(adapter, workingMemoryId, scope, extractor, options) {
389
+ const startedAt = Date.now();
390
+ const normalizedScope = normalizeScope(scope);
391
+ const policy = resolveExtractionPolicy(options?.policy);
392
+ const workingMemory = await adapter.getWorkingMemoryById(workingMemoryId);
393
+ if (!workingMemory) {
394
+ throw new Error(`Memory validation: working memory ${workingMemoryId} was not found`);
395
+ }
396
+ if (workingMemory.tenant_id !== normalizedScope.tenant_id ||
397
+ workingMemory.system_id !== normalizedScope.system_id ||
398
+ workingMemory.workspace_id !== normalizedScope.workspace_id ||
399
+ workingMemory.collaboration_id !== normalizedScope.collaboration_id ||
400
+ workingMemory.scope_id !== normalizedScope.scope_id) {
401
+ throw new Error(`Memory validation: working memory ${workingMemoryId} does not belong to the requested scope`);
402
+ }
403
+ const extracted = (await extractor(workingMemory.summary, workingMemory.key_entities, workingMemory.topic_tags))
404
+ .slice(0, policy.maxFactsPerExtraction)
405
+ .map(normalizeExtractedFact);
406
+ const archivedSourceTurns = await adapter.getArchivedTurnRange(workingMemory.session_id, workingMemory.turn_id_start, workingMemory.turn_id_end, normalizedScope);
407
+ const sourceTurns = archivedSourceTurns.length > 0
408
+ ? archivedSourceTurns
409
+ : (await Promise.all(Array.from({ length: workingMemory.turn_id_end - workingMemory.turn_id_start + 1 }, (_, index) => adapter.getTurnById(workingMemory.turn_id_start + index)))).filter((turn) => turn !== null &&
410
+ turn.session_id === workingMemory.session_id &&
411
+ turn.tenant_id === normalizedScope.tenant_id &&
412
+ turn.system_id === normalizedScope.system_id &&
413
+ turn.workspace_id === normalizedScope.workspace_id &&
414
+ turn.collaboration_id === normalizedScope.collaboration_id &&
415
+ turn.scope_id === normalizedScope.scope_id);
416
+ const recoveredFromTurns = (await createRegexExtractor()(sourceTurns
417
+ .filter((turn) => turn.role !== 'assistant')
418
+ .map((turn) => turn.content)
419
+ .join(' '), workingMemory.key_entities, workingMemory.topic_tags))
420
+ .map(normalizeExtractedFact)
421
+ .filter((fact) => ['constraint', 'preference'].includes(fact.factType));
422
+ const recoveredFacts = mergeRecoveredFacts(extracted, recoveredFromTurns, policy.maxFactsPerExtraction);
423
+ const sourceTurnIds = sourceTurns.map((turn) => turn.id);
424
+ const activeKnowledge = await adapter.getActiveKnowledgeMemory(normalizedScope);
425
+ const duplicateLookup = new Map(activeKnowledge.map((fact) => [normalizeFactText(fact.fact), fact]));
426
+ const normalizedKnowledge = activeKnowledge.map((fact) => ({
427
+ memory: fact,
428
+ normalized: normalizeKnowledgeMemory(fact),
429
+ }));
430
+ const created = [];
431
+ for (const fact of recoveredFacts) {
432
+ assertNonEmpty(fact.fact, 'fact');
433
+ assertExtractedFactType(fact.factType);
434
+ assertFactConfidence(fact.confidence);
435
+ if (!meetsConfidenceThreshold(fact.confidence, policy.minConfidenceForPromotion)) {
436
+ await adapter.insertKnowledgeMemoryAudit({
437
+ ...normalizedScope,
438
+ working_memory_id: workingMemoryId,
439
+ fact: fact.fact,
440
+ fact_type: fact.factType,
441
+ fact_subject: fact.subject,
442
+ fact_attribute: fact.attribute,
443
+ fact_value: fact.value,
444
+ normalized_fact: fact.normalizedFact,
445
+ slot_key: fact.slotKey,
446
+ is_negated: fact.isNegated,
447
+ confidence: fact.confidence,
448
+ confidence_score: deriveConfidenceScore(fact.confidence, sourceTurnIds.length, 'created'),
449
+ verification_status: deriveVerificationStatus(sourceTurnIds.length),
450
+ source_text: fact.sourceText ?? fact.fact,
451
+ decision: 'skipped_low_confidence',
452
+ detail: `Below minimum confidence threshold '${policy.minConfidenceForPromotion}'`,
453
+ });
454
+ continue;
455
+ }
456
+ const normalizedFact = normalizeFactText(fact.fact);
457
+ const duplicate = duplicateLookup.get(normalizedFact);
458
+ if (policy.deduplicateFacts && duplicate) {
459
+ if (policy.touchDuplicates) {
460
+ await adapter.touchKnowledgeMemory(duplicate.id);
461
+ }
462
+ await adapter.insertKnowledgeMemoryAudit({
463
+ ...normalizedScope,
464
+ working_memory_id: workingMemoryId,
465
+ fact: fact.fact,
466
+ fact_type: fact.factType,
467
+ fact_subject: fact.subject,
468
+ fact_attribute: fact.attribute,
469
+ fact_value: fact.value,
470
+ normalized_fact: fact.normalizedFact,
471
+ slot_key: fact.slotKey,
472
+ is_negated: fact.isNegated,
473
+ confidence: fact.confidence,
474
+ confidence_score: deriveConfidenceScore(fact.confidence, sourceTurnIds.length, 'duplicate'),
475
+ verification_status: deriveVerificationStatus(sourceTurnIds.length),
476
+ source_text: fact.sourceText ?? fact.fact,
477
+ decision: 'duplicate',
478
+ related_knowledge_id: duplicate.id,
479
+ detail: 'Exact normalized fact already exists',
480
+ });
481
+ continue;
482
+ }
483
+ let strongestRelation = { relation: 'created', related: null };
484
+ for (const existing of normalizedKnowledge) {
485
+ const relation = classifyFactRelation(existing.normalized, fact);
486
+ if (relation === 'duplicate') {
487
+ strongestRelation = { relation, related: existing.memory };
488
+ break;
489
+ }
490
+ if (relation === 'conflict') {
491
+ strongestRelation = { relation, related: existing.memory };
492
+ }
493
+ else if (relation === 'update' &&
494
+ strongestRelation.relation !== 'conflict') {
495
+ strongestRelation = { relation, related: existing.memory };
496
+ }
497
+ else if (relation === 'compatible' &&
498
+ strongestRelation.related === null) {
499
+ strongestRelation = { relation, related: existing.memory };
500
+ }
501
+ }
502
+ const verificationResult = options?.verifier
503
+ ? await options.verifier(fact, {
504
+ workingMemory,
505
+ sourceTurns,
506
+ relatedKnowledge: activeKnowledge,
507
+ })
508
+ : undefined;
509
+ const verificationApproved = verificationResult === undefined || typeof verificationResult === 'boolean'
510
+ ? verificationResult ?? true
511
+ : verificationResult.approved ?? true;
512
+ const resolvedConfidence = verificationResult &&
513
+ typeof verificationResult !== 'boolean' &&
514
+ verificationResult.confidence
515
+ ? verificationResult.confidence
516
+ : fact.confidence;
517
+ const resolvedVerificationStatus = deriveVerificationStatus(sourceTurnIds.length, verificationResult && typeof verificationResult !== 'boolean'
518
+ ? verificationResult.verificationStatus
519
+ : undefined);
520
+ const resolvedConfidenceScore = verificationResult &&
521
+ typeof verificationResult !== 'boolean' &&
522
+ verificationResult.confidenceScore != null
523
+ ? verificationResult.confidenceScore
524
+ : deriveConfidenceScore(resolvedConfidence, sourceTurnIds.length, strongestRelation.relation);
525
+ const verificationNotes = verificationResult && typeof verificationResult !== 'boolean'
526
+ ? verificationResult.notes ?? null
527
+ : null;
528
+ if (!verificationApproved) {
529
+ await adapter.insertKnowledgeMemoryAudit({
530
+ ...normalizedScope,
531
+ working_memory_id: workingMemoryId,
532
+ fact: fact.fact,
533
+ fact_type: fact.factType,
534
+ fact_subject: fact.subject,
535
+ fact_attribute: fact.attribute,
536
+ fact_value: fact.value,
537
+ normalized_fact: fact.normalizedFact,
538
+ slot_key: fact.slotKey,
539
+ is_negated: fact.isNegated,
540
+ confidence: resolvedConfidence,
541
+ confidence_score: resolvedConfidenceScore,
542
+ verification_status: resolvedVerificationStatus,
543
+ source_text: fact.sourceText ?? fact.fact,
544
+ decision: 'skipped_low_confidence',
545
+ detail: verificationNotes ?? 'Verifier rejected candidate',
546
+ });
547
+ continue;
548
+ }
549
+ const grounded = groundFactAgainstTurns(normalizedScope, workingMemoryId, fact, sourceTurns);
550
+ const candidateTrustScore = Math.max(grounded.candidate.trust_score, resolvedConfidenceScore * (grounded.supportedTurnIds.length > 0 ? 0.9 : 0.25));
551
+ const candidateInput = {
552
+ ...normalizedScope,
553
+ working_memory_id: workingMemoryId,
554
+ fact: fact.fact,
555
+ fact_type: fact.factType,
556
+ knowledge_class: grounded.candidate.knowledge_class,
557
+ normalized_fact: grounded.candidate.normalized_fact,
558
+ slot_key: grounded.candidate.slot_key,
559
+ confidence: resolvedConfidence,
560
+ source_summary: grounded.candidate.source_summary,
561
+ source_turns: grounded.candidate.source_turns,
562
+ grounding_strength: grounded.candidate.grounding_strength,
563
+ evidence_count: grounded.candidate.evidence_count,
564
+ trust_score: candidateTrustScore,
565
+ state: grounded.candidate.state,
566
+ };
567
+ const candidatePreview = {
568
+ id: 0,
569
+ ...candidateInput,
570
+ created_at: nowSeconds(),
571
+ promoted_knowledge_id: null,
572
+ };
573
+ const candidateEvidenceInputs = grounded.evidence.map((item) => ({
574
+ ...normalizedScope,
575
+ ...item,
576
+ }));
577
+ const candidateEvidencePreview = candidateEvidenceInputs.map((item, index) => ({
578
+ id: index + 1,
579
+ tenant_id: item.tenant_id,
580
+ system_id: item.system_id,
581
+ workspace_id: item.workspace_id,
582
+ collaboration_id: item.collaboration_id ?? '',
583
+ scope_id: item.scope_id,
584
+ turn_id: item.turn_id ?? null,
585
+ working_memory_id: item.working_memory_id ?? null,
586
+ knowledge_memory_id: null,
587
+ knowledge_candidate_id: candidatePreview.id,
588
+ source_type: item.source_type,
589
+ support_polarity: item.support_polarity,
590
+ speaker_role: item.speaker_role ?? null,
591
+ actor: item.actor ?? null,
592
+ excerpt: item.excerpt,
593
+ start_offset: item.start_offset ?? null,
594
+ end_offset: item.end_offset ?? null,
595
+ is_explicit: item.is_explicit ?? false,
596
+ explicitness_score: item.explicitness_score ?? 0,
597
+ outcome: item.outcome ?? null,
598
+ created_at: item.created_at ?? nowSeconds(),
599
+ }));
600
+ const trustAssessment = assessCandidateTrust({
601
+ candidate: candidatePreview,
602
+ evidence: candidateEvidencePreview,
603
+ policy,
604
+ existingKnowledge: strongestRelation.related,
605
+ relation: strongestRelation.relation,
606
+ });
607
+ const conflict = strongestRelation.related && strongestRelation.relation !== 'created'
608
+ ? buildKnowledgeConflict({
609
+ existing: strongestRelation.related,
610
+ candidateId: null,
611
+ relation: strongestRelation.relation,
612
+ contradictionScore: candidateEvidencePreview.some((item) => item.support_polarity === 'contradicts')
613
+ ? 1
614
+ : 0,
615
+ policy,
616
+ })
617
+ : null;
618
+ if (strongestRelation.relation === 'duplicate' && strongestRelation.related) {
619
+ if (policy.touchDuplicates) {
620
+ await adapter.touchKnowledgeMemory(strongestRelation.related.id);
621
+ }
622
+ if (strongestRelation.related.knowledge_state !== 'trusted' &&
623
+ trustAssessment.state === 'trusted') {
624
+ await adapter.updateKnowledgeMemory(strongestRelation.related.id, {
625
+ knowledge_state: 'trusted',
626
+ trust_score: Math.max(strongestRelation.related.trust_score, trustAssessment.trust_score),
627
+ verification_status: resolvedVerificationStatus,
628
+ verification_notes: trustAssessment.reasons.join(', '),
629
+ last_verified_at: nowSeconds(),
630
+ });
631
+ }
632
+ await adapter.insertKnowledgeMemoryAudit({
633
+ ...normalizedScope,
634
+ working_memory_id: workingMemoryId,
635
+ fact: fact.fact,
636
+ fact_type: fact.factType,
637
+ fact_subject: fact.subject,
638
+ fact_attribute: fact.attribute,
639
+ fact_value: fact.value,
640
+ normalized_fact: fact.normalizedFact,
641
+ slot_key: fact.slotKey,
642
+ is_negated: fact.isNegated,
643
+ confidence: resolvedConfidence,
644
+ confidence_score: resolvedConfidenceScore,
645
+ verification_status: resolvedVerificationStatus,
646
+ source_text: fact.sourceText ?? fact.fact,
647
+ decision: 'duplicate',
648
+ related_knowledge_id: strongestRelation.related.id,
649
+ detail: verificationNotes ??
650
+ `Structured relation classified as duplicate (${trustAssessment.reasons.join(', ') || 'no extra trust signal'})`,
651
+ });
652
+ continue;
653
+ }
654
+ if (trustAssessment.decision === 'reject_candidate') {
655
+ await runAtomicStorage(adapter, async () => {
656
+ const rejectedCandidate = await adapter.insertKnowledgeCandidate(candidateInput);
657
+ if (candidateEvidenceInputs.length > 0) {
658
+ await adapter.insertKnowledgeEvidenceBatch(candidateEvidenceInputs.map((item) => ({
659
+ ...item,
660
+ knowledge_candidate_id: rejectedCandidate.id,
661
+ })));
662
+ }
663
+ }, (syncAdapter) => {
664
+ const rejectedCandidate = syncAdapter.insertKnowledgeCandidate(candidateInput);
665
+ if (candidateEvidenceInputs.length > 0) {
666
+ syncAdapter.insertKnowledgeEvidenceBatch(candidateEvidenceInputs.map((item) => ({
667
+ ...item,
668
+ knowledge_candidate_id: rejectedCandidate.id,
669
+ })));
670
+ }
671
+ });
672
+ await adapter.insertKnowledgeMemoryAudit({
673
+ ...normalizedScope,
674
+ working_memory_id: workingMemoryId,
675
+ fact: fact.fact,
676
+ fact_type: fact.factType,
677
+ fact_subject: fact.subject,
678
+ fact_attribute: fact.attribute,
679
+ fact_value: fact.value,
680
+ normalized_fact: fact.normalizedFact,
681
+ slot_key: fact.slotKey,
682
+ is_negated: fact.isNegated,
683
+ confidence: resolvedConfidence,
684
+ confidence_score: trustAssessment.trust_score,
685
+ verification_status: 'unverified',
686
+ source_text: fact.sourceText ?? fact.fact,
687
+ decision: 'skipped_low_confidence',
688
+ related_knowledge_id: strongestRelation.related?.id ?? null,
689
+ detail: trustAssessment.reasons.join(', ') || 'Rejected by trust assessment',
690
+ });
691
+ continue;
692
+ }
693
+ if (strongestRelation.related &&
694
+ (trustAssessment.decision === 'mark_disputed' ||
695
+ (strongestRelation.relation === 'conflict' && trustAssessment.state !== 'trusted') ||
696
+ conflict?.resolution === 'dispute')) {
697
+ await adapter.updateKnowledgeMemory(strongestRelation.related.id, {
698
+ knowledge_state: 'disputed',
699
+ disputed_at: nowSeconds(),
700
+ dispute_reason: trustAssessment.reasons.join(', ') || 'Contradictory evidence detected',
701
+ contradiction_score: Math.max(strongestRelation.related.contradiction_score, 1),
702
+ trust_score: Math.min(strongestRelation.related.trust_score, trustAssessment.trust_score),
703
+ });
704
+ await adapter.insertKnowledgeMemoryAudit({
705
+ ...normalizedScope,
706
+ working_memory_id: workingMemoryId,
707
+ fact: fact.fact,
708
+ fact_type: fact.factType,
709
+ fact_subject: fact.subject,
710
+ fact_attribute: fact.attribute,
711
+ fact_value: fact.value,
712
+ normalized_fact: fact.normalizedFact,
713
+ slot_key: fact.slotKey,
714
+ is_negated: fact.isNegated,
715
+ confidence: resolvedConfidence,
716
+ confidence_score: trustAssessment.trust_score,
717
+ verification_status: resolvedVerificationStatus,
718
+ source_text: fact.sourceText ?? fact.fact,
719
+ decision: 'conflict',
720
+ related_knowledge_id: strongestRelation.related.id,
721
+ detail: `Marked existing knowledge disputed (${trustAssessment.reasons.join(', ')})`,
722
+ });
723
+ continue;
724
+ }
725
+ if (strongestRelation.relation === 'conflict' &&
726
+ policy.conflictStrategy === 'skip' &&
727
+ strongestRelation.related) {
728
+ await adapter.insertKnowledgeMemoryAudit({
729
+ ...normalizedScope,
730
+ working_memory_id: workingMemoryId,
731
+ fact: fact.fact,
732
+ fact_type: fact.factType,
733
+ fact_subject: fact.subject,
734
+ fact_attribute: fact.attribute,
735
+ fact_value: fact.value,
736
+ normalized_fact: fact.normalizedFact,
737
+ slot_key: fact.slotKey,
738
+ is_negated: fact.isNegated,
739
+ confidence: resolvedConfidence,
740
+ confidence_score: resolvedConfidenceScore,
741
+ verification_status: resolvedVerificationStatus,
742
+ source_text: fact.sourceText ?? fact.fact,
743
+ decision: 'conflict',
744
+ related_knowledge_id: strongestRelation.related.id,
745
+ detail: verificationNotes ?? 'Conflict strategy skipped promotion',
746
+ });
747
+ continue;
748
+ }
749
+ const createdFact = await runAtomicStorage(adapter, async () => {
750
+ const candidate = await adapter.insertKnowledgeCandidate(candidateInput);
751
+ const candidateEvidence = candidateEvidenceInputs.length > 0
752
+ ? await adapter.insertKnowledgeEvidenceBatch(candidateEvidenceInputs.map((item) => ({
753
+ ...item,
754
+ knowledge_candidate_id: candidate.id,
755
+ })))
756
+ : [];
757
+ const supportEvidence = candidateEvidence.filter((item) => item.support_polarity === 'supports');
758
+ const outcomeFailures = supportEvidence.filter((item) => item.outcome === 'failure').length;
759
+ const outcomeSuccesses = supportEvidence.filter((item) => item.outcome === 'success').length;
760
+ const createdKnowledgeClass = outcomeFailures > outcomeSuccesses &&
761
+ ['strategy', 'procedure'].includes(grounded.candidate.knowledge_class)
762
+ ? 'anti_pattern'
763
+ : trustAssessment.state === 'trusted' &&
764
+ grounded.candidate.knowledge_class === 'procedure' &&
765
+ outcomeSuccesses > 0
766
+ ? 'strategy'
767
+ : grounded.candidate.knowledge_class;
768
+ const knowledge = await adapter.promoteKnowledgeCandidate(candidate.id, buildKnowledgeInput(normalizedScope, workingMemoryId, fact, {
769
+ confidence: resolvedConfidence,
770
+ confidenceScore: resolvedConfidenceScore,
771
+ trustScore: trustAssessment.trust_score,
772
+ knowledgeState: trustAssessment.state === 'trusted' ? 'trusted' : 'provisional',
773
+ knowledgeClass: createdKnowledgeClass,
774
+ verificationStatus: resolvedVerificationStatus,
775
+ verificationNotes: verificationNotes ?? (trustAssessment.reasons.length > 0 ? trustAssessment.reasons.join(', ') : null),
776
+ contradictionScore: conflict?.severity === 'high' ? 1 : 0,
777
+ sourceTurnIds: grounded.supportedTurnIds,
778
+ }));
779
+ if (candidateEvidence.length > 0) {
780
+ await adapter.insertKnowledgeEvidenceBatch(candidateEvidence.map(({ id: _id, ...item }) => ({
781
+ ...item,
782
+ knowledge_candidate_id: null,
783
+ knowledge_memory_id: knowledge.id,
784
+ })));
785
+ }
786
+ if (strongestRelation.related &&
787
+ (trustAssessment.decision === 'supersede_existing' ||
788
+ strongestRelation.relation === 'update' ||
789
+ (strongestRelation.relation === 'conflict' && policy.conflictStrategy === 'supersede'))) {
790
+ await adapter.supersedeKnowledgeMemory(strongestRelation.related.id, knowledge.id);
791
+ }
792
+ await adapter.insertKnowledgeMemoryAudit({
793
+ ...normalizedScope,
794
+ working_memory_id: workingMemoryId,
795
+ fact: fact.fact,
796
+ fact_type: fact.factType,
797
+ fact_subject: fact.subject,
798
+ fact_attribute: fact.attribute,
799
+ fact_value: fact.value,
800
+ normalized_fact: fact.normalizedFact,
801
+ slot_key: fact.slotKey,
802
+ is_negated: fact.isNegated,
803
+ confidence: resolvedConfidence,
804
+ confidence_score: trustAssessment.trust_score,
805
+ verification_status: resolvedVerificationStatus,
806
+ source_text: fact.sourceText ?? fact.fact,
807
+ decision: strongestRelation.relation === 'update'
808
+ ? 'updated'
809
+ : strongestRelation.relation === 'conflict'
810
+ ? 'conflict'
811
+ : strongestRelation.relation === 'compatible'
812
+ ? 'compatible'
813
+ : 'created',
814
+ created_knowledge_id: knowledge.id,
815
+ related_knowledge_id: strongestRelation.related?.id ?? null,
816
+ detail: strongestRelation.relation === 'conflict'
817
+ ? verificationNotes ?? `Conflict strategy '${policy.conflictStrategy}'`
818
+ : strongestRelation.relation === 'update'
819
+ ? verificationNotes ?? 'Superseded prior related knowledge'
820
+ : strongestRelation.relation === 'compatible'
821
+ ? verificationNotes ?? 'Created alongside compatible related knowledge'
822
+ : verificationNotes ?? 'Created new knowledge memory',
823
+ });
824
+ return knowledge;
825
+ }, (syncAdapter) => {
826
+ const candidate = syncAdapter.insertKnowledgeCandidate(candidateInput);
827
+ const candidateEvidence = candidateEvidenceInputs.length > 0
828
+ ? syncAdapter.insertKnowledgeEvidenceBatch(candidateEvidenceInputs.map((item) => ({
829
+ ...item,
830
+ knowledge_candidate_id: candidate.id,
831
+ })))
832
+ : [];
833
+ const supportEvidence = candidateEvidence.filter((item) => item.support_polarity === 'supports');
834
+ const outcomeFailures = supportEvidence.filter((item) => item.outcome === 'failure').length;
835
+ const outcomeSuccesses = supportEvidence.filter((item) => item.outcome === 'success').length;
836
+ const createdKnowledgeClass = outcomeFailures > outcomeSuccesses &&
837
+ ['strategy', 'procedure'].includes(grounded.candidate.knowledge_class)
838
+ ? 'anti_pattern'
839
+ : trustAssessment.state === 'trusted' &&
840
+ grounded.candidate.knowledge_class === 'procedure' &&
841
+ outcomeSuccesses > 0
842
+ ? 'strategy'
843
+ : grounded.candidate.knowledge_class;
844
+ const knowledge = syncAdapter.promoteKnowledgeCandidate(candidate.id, buildKnowledgeInput(normalizedScope, workingMemoryId, fact, {
845
+ confidence: resolvedConfidence,
846
+ confidenceScore: resolvedConfidenceScore,
847
+ trustScore: trustAssessment.trust_score,
848
+ knowledgeState: trustAssessment.state === 'trusted' ? 'trusted' : 'provisional',
849
+ knowledgeClass: createdKnowledgeClass,
850
+ verificationStatus: resolvedVerificationStatus,
851
+ verificationNotes: verificationNotes ?? (trustAssessment.reasons.length > 0 ? trustAssessment.reasons.join(', ') : null),
852
+ contradictionScore: conflict?.severity === 'high' ? 1 : 0,
853
+ sourceTurnIds: grounded.supportedTurnIds,
854
+ }));
855
+ if (candidateEvidence.length > 0) {
856
+ syncAdapter.insertKnowledgeEvidenceBatch(candidateEvidence.map(({ id: _id, ...item }) => ({
857
+ ...item,
858
+ knowledge_candidate_id: null,
859
+ knowledge_memory_id: knowledge.id,
860
+ })));
861
+ }
862
+ if (strongestRelation.related &&
863
+ (trustAssessment.decision === 'supersede_existing' ||
864
+ strongestRelation.relation === 'update' ||
865
+ (strongestRelation.relation === 'conflict' && policy.conflictStrategy === 'supersede'))) {
866
+ syncAdapter.supersedeKnowledgeMemory(strongestRelation.related.id, knowledge.id);
867
+ }
868
+ syncAdapter.insertKnowledgeMemoryAudit({
869
+ ...normalizedScope,
870
+ working_memory_id: workingMemoryId,
871
+ fact: fact.fact,
872
+ fact_type: fact.factType,
873
+ fact_subject: fact.subject,
874
+ fact_attribute: fact.attribute,
875
+ fact_value: fact.value,
876
+ normalized_fact: fact.normalizedFact,
877
+ slot_key: fact.slotKey,
878
+ is_negated: fact.isNegated,
879
+ confidence: resolvedConfidence,
880
+ confidence_score: trustAssessment.trust_score,
881
+ verification_status: resolvedVerificationStatus,
882
+ source_text: fact.sourceText ?? fact.fact,
883
+ decision: strongestRelation.relation === 'update'
884
+ ? 'updated'
885
+ : strongestRelation.relation === 'conflict'
886
+ ? 'conflict'
887
+ : strongestRelation.relation === 'compatible'
888
+ ? 'compatible'
889
+ : 'created',
890
+ created_knowledge_id: knowledge.id,
891
+ related_knowledge_id: strongestRelation.related?.id ?? null,
892
+ detail: strongestRelation.relation === 'conflict'
893
+ ? verificationNotes ?? `Conflict strategy '${policy.conflictStrategy}'`
894
+ : strongestRelation.relation === 'update'
895
+ ? verificationNotes ?? 'Superseded prior related knowledge'
896
+ : strongestRelation.relation === 'compatible'
897
+ ? verificationNotes ?? 'Created alongside compatible related knowledge'
898
+ : verificationNotes ?? 'Created new knowledge memory',
899
+ });
900
+ return knowledge;
901
+ });
902
+ duplicateLookup.set(normalizedFact, createdFact);
903
+ normalizedKnowledge.push({
904
+ memory: createdFact,
905
+ normalized: normalizeKnowledgeMemory(createdFact),
906
+ });
907
+ created.push(createdFact);
908
+ }
909
+ emitMemoryEvent('extraction', normalizedScope, options, Date.now() - startedAt, {
910
+ workingMemoryId,
911
+ extractedCount: extracted.length,
912
+ createdCount: created.length,
913
+ });
914
+ return created;
915
+ }
916
+ //# sourceMappingURL=orchestrator.js.map