ai-memory-layer 2.0.1 → 3.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 (186) hide show
  1. package/CHANGELOG.md +19 -12
  2. package/README.md +435 -320
  3. package/bin/memory-server.mjs +0 -0
  4. package/dist/adapters/memory/embeddings.d.ts.map +1 -1
  5. package/dist/adapters/memory/embeddings.js +12 -1
  6. package/dist/adapters/memory/embeddings.js.map +1 -1
  7. package/dist/adapters/memory/index.d.ts.map +1 -1
  8. package/dist/adapters/memory/index.js +1281 -48
  9. package/dist/adapters/memory/index.js.map +1 -1
  10. package/dist/adapters/postgres/index.d.ts +1 -0
  11. package/dist/adapters/postgres/index.d.ts.map +1 -1
  12. package/dist/adapters/postgres/index.js +1770 -42
  13. package/dist/adapters/postgres/index.js.map +1 -1
  14. package/dist/adapters/sqlite/embeddings.d.ts.map +1 -1
  15. package/dist/adapters/sqlite/embeddings.js +49 -12
  16. package/dist/adapters/sqlite/embeddings.js.map +1 -1
  17. package/dist/adapters/sqlite/index.d.ts.map +1 -1
  18. package/dist/adapters/sqlite/index.js +1720 -38
  19. package/dist/adapters/sqlite/index.js.map +1 -1
  20. package/dist/adapters/sqlite/mappers.d.ts +39 -4
  21. package/dist/adapters/sqlite/mappers.d.ts.map +1 -1
  22. package/dist/adapters/sqlite/mappers.js +87 -0
  23. package/dist/adapters/sqlite/mappers.js.map +1 -1
  24. package/dist/adapters/sqlite/schema.d.ts +1 -1
  25. package/dist/adapters/sqlite/schema.d.ts.map +1 -1
  26. package/dist/adapters/sqlite/schema.js +297 -1
  27. package/dist/adapters/sqlite/schema.js.map +1 -1
  28. package/dist/adapters/sync-to-async.d.ts.map +1 -1
  29. package/dist/adapters/sync-to-async.js +54 -0
  30. package/dist/adapters/sync-to-async.js.map +1 -1
  31. package/dist/contracts/async-storage.d.ts +61 -1
  32. package/dist/contracts/async-storage.d.ts.map +1 -1
  33. package/dist/contracts/cognitive.d.ts +37 -0
  34. package/dist/contracts/cognitive.d.ts.map +1 -0
  35. package/dist/contracts/cognitive.js +24 -0
  36. package/dist/contracts/cognitive.js.map +1 -0
  37. package/dist/contracts/coordination.d.ts +101 -0
  38. package/dist/contracts/coordination.d.ts.map +1 -0
  39. package/dist/contracts/coordination.js +26 -0
  40. package/dist/contracts/coordination.js.map +1 -0
  41. package/dist/contracts/embedding.d.ts +1 -1
  42. package/dist/contracts/embedding.d.ts.map +1 -1
  43. package/dist/contracts/errors.d.ts +28 -0
  44. package/dist/contracts/errors.d.ts.map +1 -0
  45. package/dist/contracts/errors.js +41 -0
  46. package/dist/contracts/errors.js.map +1 -0
  47. package/dist/contracts/identity.d.ts +2 -0
  48. package/dist/contracts/identity.d.ts.map +1 -1
  49. package/dist/contracts/identity.js +26 -1
  50. package/dist/contracts/identity.js.map +1 -1
  51. package/dist/contracts/observability.d.ts +2 -1
  52. package/dist/contracts/observability.d.ts.map +1 -1
  53. package/dist/contracts/observability.js +11 -0
  54. package/dist/contracts/observability.js.map +1 -1
  55. package/dist/contracts/profile.d.ts +29 -0
  56. package/dist/contracts/profile.d.ts.map +1 -0
  57. package/dist/contracts/profile.js +2 -0
  58. package/dist/contracts/profile.js.map +1 -0
  59. package/dist/contracts/session-state.d.ts +10 -0
  60. package/dist/contracts/session-state.d.ts.map +1 -0
  61. package/dist/contracts/session-state.js +2 -0
  62. package/dist/contracts/session-state.js.map +1 -0
  63. package/dist/contracts/storage.d.ts +73 -1
  64. package/dist/contracts/storage.d.ts.map +1 -1
  65. package/dist/contracts/storage.js +16 -1
  66. package/dist/contracts/storage.js.map +1 -1
  67. package/dist/contracts/temporal.d.ts +112 -0
  68. package/dist/contracts/temporal.d.ts.map +1 -0
  69. package/dist/contracts/temporal.js +31 -0
  70. package/dist/contracts/temporal.js.map +1 -0
  71. package/dist/contracts/types.d.ts +135 -0
  72. package/dist/contracts/types.d.ts.map +1 -1
  73. package/dist/contracts/types.js +27 -0
  74. package/dist/contracts/types.js.map +1 -1
  75. package/dist/core/associations.d.ts +18 -0
  76. package/dist/core/associations.d.ts.map +1 -0
  77. package/dist/core/associations.js +185 -0
  78. package/dist/core/associations.js.map +1 -0
  79. package/dist/core/circuit-breaker.d.ts +9 -0
  80. package/dist/core/circuit-breaker.d.ts.map +1 -1
  81. package/dist/core/circuit-breaker.js +13 -1
  82. package/dist/core/circuit-breaker.js.map +1 -1
  83. package/dist/core/cognitive.d.ts +5 -0
  84. package/dist/core/cognitive.d.ts.map +1 -0
  85. package/dist/core/cognitive.js +120 -0
  86. package/dist/core/cognitive.js.map +1 -0
  87. package/dist/core/context.d.ts +72 -1
  88. package/dist/core/context.d.ts.map +1 -1
  89. package/dist/core/context.js +471 -45
  90. package/dist/core/context.js.map +1 -1
  91. package/dist/core/episodic.d.ts +28 -0
  92. package/dist/core/episodic.d.ts.map +1 -0
  93. package/dist/core/episodic.js +371 -0
  94. package/dist/core/episodic.js.map +1 -0
  95. package/dist/core/formatter.d.ts +4 -0
  96. package/dist/core/formatter.d.ts.map +1 -1
  97. package/dist/core/formatter.js +103 -0
  98. package/dist/core/formatter.js.map +1 -1
  99. package/dist/core/maintenance.d.ts +1 -0
  100. package/dist/core/maintenance.d.ts.map +1 -1
  101. package/dist/core/maintenance.js +75 -0
  102. package/dist/core/maintenance.js.map +1 -1
  103. package/dist/core/manager.d.ts +159 -7
  104. package/dist/core/manager.d.ts.map +1 -1
  105. package/dist/core/manager.js +740 -31
  106. package/dist/core/manager.js.map +1 -1
  107. package/dist/core/orchestrator.d.ts.map +1 -1
  108. package/dist/core/orchestrator.js +210 -178
  109. package/dist/core/orchestrator.js.map +1 -1
  110. package/dist/core/playbook.d.ts +35 -0
  111. package/dist/core/playbook.d.ts.map +1 -0
  112. package/dist/core/playbook.js +184 -0
  113. package/dist/core/playbook.js.map +1 -0
  114. package/dist/core/profile.d.ts +8 -0
  115. package/dist/core/profile.d.ts.map +1 -0
  116. package/dist/core/profile.js +103 -0
  117. package/dist/core/profile.js.map +1 -0
  118. package/dist/core/quick.d.ts +5 -0
  119. package/dist/core/quick.d.ts.map +1 -1
  120. package/dist/core/quick.js +10 -1
  121. package/dist/core/quick.js.map +1 -1
  122. package/dist/core/runtime.d.ts +17 -1
  123. package/dist/core/runtime.d.ts.map +1 -1
  124. package/dist/core/runtime.js +88 -5
  125. package/dist/core/runtime.js.map +1 -1
  126. package/dist/core/streaming.d.ts +1 -1
  127. package/dist/core/streaming.d.ts.map +1 -1
  128. package/dist/core/temporal.d.ts +29 -0
  129. package/dist/core/temporal.d.ts.map +1 -0
  130. package/dist/core/temporal.js +447 -0
  131. package/dist/core/temporal.js.map +1 -0
  132. package/dist/core/validation.d.ts +3 -0
  133. package/dist/core/validation.d.ts.map +1 -1
  134. package/dist/core/validation.js +25 -10
  135. package/dist/core/validation.js.map +1 -1
  136. package/dist/core/workspace-detect.d.ts +17 -0
  137. package/dist/core/workspace-detect.d.ts.map +1 -0
  138. package/dist/core/workspace-detect.js +55 -0
  139. package/dist/core/workspace-detect.js.map +1 -0
  140. package/dist/embeddings/resilience.d.ts.map +1 -1
  141. package/dist/embeddings/resilience.js +19 -8
  142. package/dist/embeddings/resilience.js.map +1 -1
  143. package/dist/index.d.ts +21 -4
  144. package/dist/index.d.ts.map +1 -1
  145. package/dist/index.js +9 -0
  146. package/dist/index.js.map +1 -1
  147. package/dist/integrations/claude-agent.d.ts +6 -0
  148. package/dist/integrations/claude-agent.d.ts.map +1 -1
  149. package/dist/integrations/claude-agent.js +5 -1
  150. package/dist/integrations/claude-agent.js.map +1 -1
  151. package/dist/integrations/claude-tools.d.ts +5 -4
  152. package/dist/integrations/claude-tools.d.ts.map +1 -1
  153. package/dist/integrations/claude-tools.js +155 -2
  154. package/dist/integrations/claude-tools.js.map +1 -1
  155. package/dist/integrations/middleware.d.ts +6 -0
  156. package/dist/integrations/middleware.d.ts.map +1 -1
  157. package/dist/integrations/middleware.js +11 -1
  158. package/dist/integrations/middleware.js.map +1 -1
  159. package/dist/integrations/openai-tools.d.ts +5 -4
  160. package/dist/integrations/openai-tools.d.ts.map +1 -1
  161. package/dist/integrations/openai-tools.js +170 -2
  162. package/dist/integrations/openai-tools.js.map +1 -1
  163. package/dist/integrations/vercel-ai.d.ts +6 -0
  164. package/dist/integrations/vercel-ai.d.ts.map +1 -1
  165. package/dist/integrations/vercel-ai.js +4 -0
  166. package/dist/integrations/vercel-ai.js.map +1 -1
  167. package/dist/server/http-server.d.ts +8 -0
  168. package/dist/server/http-server.d.ts.map +1 -1
  169. package/dist/server/http-server.js +976 -58
  170. package/dist/server/http-server.js.map +1 -1
  171. package/dist/server/mcp-server.d.ts +8 -0
  172. package/dist/server/mcp-server.d.ts.map +1 -1
  173. package/dist/server/mcp-server.js +1157 -37
  174. package/dist/server/mcp-server.js.map +1 -1
  175. package/dist/server/parsing.d.ts +12 -0
  176. package/dist/server/parsing.d.ts.map +1 -0
  177. package/dist/server/parsing.js +42 -0
  178. package/dist/server/parsing.js.map +1 -0
  179. package/dist/summarizers/prompts.d.ts +4 -0
  180. package/dist/summarizers/prompts.d.ts.map +1 -1
  181. package/dist/summarizers/prompts.js +42 -0
  182. package/dist/summarizers/prompts.js.map +1 -1
  183. package/docs/ULTIMATE_MEMORY_LAYER_ROADMAP.md +291 -0
  184. package/docs/prd.json +1498 -0
  185. package/openapi.yaml +1945 -112
  186. package/package.json +4 -2
@@ -1,10 +1,13 @@
1
1
  import { normalizeScope } from '../contracts/identity.js';
2
+ import { ResourceNotFoundError, ScopeMismatchError, ValidationError, } from '../contracts/errors.js';
2
3
  import { DEFAULT_EXTRACTION_POLICY } from '../contracts/policy.js';
4
+ import { UniqueConstraintError } from '../contracts/storage.js';
3
5
  import { assertCompactionTrigger, assertFactConfidence, assertFactType, assertFactType as assertExtractedFactType, assertNonEmpty, assertStringArray, assertMaxEntries, nowSeconds, } from './validation.js';
4
6
  import { classifyFactRelation, createRegexExtractor, normalizeFactText, normalizeExtractedFact, normalizeKnowledgeMemory, } from './extractor.js';
5
7
  import { emitMemoryEvent } from './telemetry.js';
6
8
  import { assessCandidateTrust, buildKnowledgeConflict } from './trust.js';
7
9
  import { getNativeSyncAdapter } from '../adapters/sync-to-async.js';
10
+ import { autoDetectAssociations } from './associations.js';
8
11
  function resolveExtractionPolicy(policy) {
9
12
  return {
10
13
  ...DEFAULT_EXTRACTION_POLICY,
@@ -89,6 +92,185 @@ function mergeRecoveredFacts(extracted, recovered, maxFactsPerExtraction) {
89
92
  }
90
93
  return merged;
91
94
  }
95
+ function resolveCreatedKnowledgeClass(knowledgeClass, knowledgeState, evidence) {
96
+ const supportEvidence = evidence.filter((item) => item.support_polarity === 'supports');
97
+ const outcomeFailures = supportEvidence.filter((item) => item.outcome === 'failure').length;
98
+ const outcomeSuccesses = supportEvidence.filter((item) => item.outcome === 'success').length;
99
+ if (outcomeFailures > outcomeSuccesses && ['strategy', 'procedure'].includes(knowledgeClass)) {
100
+ return 'anti_pattern';
101
+ }
102
+ if (knowledgeState === 'trusted' && knowledgeClass === 'procedure' && outcomeSuccesses > 0) {
103
+ return 'strategy';
104
+ }
105
+ return knowledgeClass;
106
+ }
107
+ function shouldSupersedeExistingKnowledge(relation, relatedKnowledge, decision, conflictStrategy) {
108
+ return Boolean(relatedKnowledge &&
109
+ (decision === 'supersede_existing' ||
110
+ relation === 'update' ||
111
+ (relation === 'conflict' && conflictStrategy === 'supersede')));
112
+ }
113
+ function buildPromotionAuditPayload(input) {
114
+ return {
115
+ ...input.normalizedScope,
116
+ working_memory_id: input.workingMemoryId,
117
+ fact: input.fact.fact,
118
+ fact_type: input.fact.factType,
119
+ fact_subject: input.fact.subject,
120
+ fact_attribute: input.fact.attribute,
121
+ fact_value: input.fact.value,
122
+ normalized_fact: input.fact.normalizedFact,
123
+ slot_key: input.fact.slotKey,
124
+ is_negated: input.fact.isNegated,
125
+ confidence: input.confidence,
126
+ confidence_score: input.confidenceScore,
127
+ verification_status: input.verificationStatus,
128
+ source_text: input.sourceText,
129
+ decision: input.relation === 'update'
130
+ ? 'updated'
131
+ : input.relation === 'conflict'
132
+ ? 'conflict'
133
+ : input.relation === 'compatible'
134
+ ? 'compatible'
135
+ : 'created',
136
+ created_knowledge_id: input.createdKnowledgeId,
137
+ related_knowledge_id: input.relatedKnowledgeId,
138
+ detail: input.relation === 'conflict'
139
+ ? input.verificationNotes ?? `Conflict strategy '${input.conflictStrategy}'`
140
+ : input.relation === 'update'
141
+ ? input.verificationNotes ?? 'Superseded prior related knowledge'
142
+ : input.relation === 'compatible'
143
+ ? input.verificationNotes ?? 'Created alongside compatible related knowledge'
144
+ : input.verificationNotes ?? 'Created new knowledge memory',
145
+ };
146
+ }
147
+ function bindCandidateEvidenceToCandidate(candidateEvidenceInputs, candidateId) {
148
+ return candidateEvidenceInputs.map((item) => ({
149
+ ...item,
150
+ knowledge_candidate_id: candidateId,
151
+ }));
152
+ }
153
+ function bindCandidateEvidenceToKnowledge(candidateEvidence, knowledgeId) {
154
+ return candidateEvidence.map(({ id: _id, ...item }) => ({
155
+ ...item,
156
+ knowledge_candidate_id: null,
157
+ knowledge_memory_id: knowledgeId,
158
+ }));
159
+ }
160
+ function isPromiseLike(value) {
161
+ return typeof value === 'object' && value !== null && 'then' in value;
162
+ }
163
+ function chainMaybe(value, next) {
164
+ return isPromiseLike(value) ? value.then(next) : next(value);
165
+ }
166
+ function attemptMaybe(work, onError) {
167
+ try {
168
+ const result = work();
169
+ return isPromiseLike(result) ? result.catch(onError) : result;
170
+ }
171
+ catch (error) {
172
+ return onError(error);
173
+ }
174
+ }
175
+ function assertSyncResult(value) {
176
+ if (isPromiseLike(value)) {
177
+ throw new Error('Expected synchronous persistence result');
178
+ }
179
+ return value;
180
+ }
181
+ function ignoreUniqueConstraint(error) {
182
+ if (!(error instanceof UniqueConstraintError)) {
183
+ throw error;
184
+ }
185
+ }
186
+ function buildPromotionPersistencePlan(input) {
187
+ const promotedState = input.trustAssessment.state === 'trusted' ? 'trusted' : 'provisional';
188
+ const createdKnowledgeClass = resolveCreatedKnowledgeClass(input.grounded.candidate.knowledge_class, promotedState, input.candidateEvidenceInputs);
189
+ const promotedKnowledgeInput = buildKnowledgeInput(input.normalizedScope, input.workingMemoryId, input.fact, {
190
+ confidence: input.resolvedConfidence,
191
+ confidenceScore: input.resolvedConfidenceScore,
192
+ trustScore: input.trustAssessment.trust_score,
193
+ knowledgeState: promotedState,
194
+ knowledgeClass: createdKnowledgeClass,
195
+ verificationStatus: input.resolvedVerificationStatus,
196
+ verificationNotes: input.verificationNotes ??
197
+ (input.trustAssessment.reasons.length > 0
198
+ ? input.trustAssessment.reasons.join(', ')
199
+ : null),
200
+ contradictionScore: input.conflict?.severity === 'high' ? 1 : 0,
201
+ sourceTurnIds: input.grounded.supportedTurnIds,
202
+ });
203
+ const auditInput = buildPromotionAuditPayload({
204
+ normalizedScope: input.normalizedScope,
205
+ workingMemoryId: input.workingMemoryId,
206
+ fact: input.fact,
207
+ confidence: input.resolvedConfidence,
208
+ confidenceScore: input.trustAssessment.trust_score,
209
+ verificationStatus: input.resolvedVerificationStatus,
210
+ sourceText: input.fact.sourceText ?? input.fact.fact,
211
+ relation: input.strongestRelation.relation,
212
+ createdKnowledgeId: 0,
213
+ relatedKnowledgeId: input.strongestRelation.related?.id ?? null,
214
+ verificationNotes: input.verificationNotes,
215
+ conflictStrategy: input.policy.conflictStrategy,
216
+ });
217
+ return {
218
+ candidateInput: input.candidateInput,
219
+ candidateEvidenceInputs: input.candidateEvidenceInputs,
220
+ promotedKnowledgeInput,
221
+ auditInput,
222
+ supersededKnowledgeId: shouldSupersedeExistingKnowledge(input.strongestRelation.relation, input.strongestRelation.related, input.trustAssessment.decision, input.policy.conflictStrategy)
223
+ ? input.strongestRelation.related.id
224
+ : null,
225
+ };
226
+ }
227
+ function persistRejectedKnowledgeCandidateCore(adapter, candidateInput, candidateEvidenceInputs) {
228
+ return chainMaybe(adapter.insertKnowledgeCandidate(candidateInput), (rejectedCandidate) => {
229
+ if (candidateEvidenceInputs.length === 0) {
230
+ return undefined;
231
+ }
232
+ return chainMaybe(adapter.insertKnowledgeEvidenceBatch(bindCandidateEvidenceToCandidate(candidateEvidenceInputs, rejectedCandidate.id)), () => undefined);
233
+ });
234
+ }
235
+ async function persistRejectedKnowledgeCandidateAsync(adapter, candidateInput, candidateEvidenceInputs) {
236
+ await Promise.resolve(persistRejectedKnowledgeCandidateCore(adapter, candidateInput, candidateEvidenceInputs));
237
+ }
238
+ function persistRejectedKnowledgeCandidateSync(adapter, candidateInput, candidateEvidenceInputs) {
239
+ assertSyncResult(persistRejectedKnowledgeCandidateCore(adapter, candidateInput, candidateEvidenceInputs));
240
+ }
241
+ function persistPromotedKnowledgeCore(adapter, normalizedScope, plan) {
242
+ return chainMaybe(adapter.insertKnowledgeCandidate(plan.candidateInput), (candidate) => {
243
+ const candidateEvidenceResult = plan.candidateEvidenceInputs.length > 0
244
+ ? adapter.insertKnowledgeEvidenceBatch(bindCandidateEvidenceToCandidate(plan.candidateEvidenceInputs, candidate.id))
245
+ : [];
246
+ return chainMaybe(candidateEvidenceResult, (candidateEvidence) => chainMaybe(adapter.promoteKnowledgeCandidate(candidate.id, plan.promotedKnowledgeInput), (knowledge) => chainMaybe(candidateEvidence.length > 0
247
+ ? adapter.insertKnowledgeEvidenceBatch(bindCandidateEvidenceToKnowledge(candidateEvidence, knowledge.id))
248
+ : undefined, () => chainMaybe(plan.supersededKnowledgeId != null
249
+ ? chainMaybe(adapter.supersedeKnowledgeMemory(plan.supersededKnowledgeId, knowledge.id), () => attemptMaybe(() => adapter.insertAssociation({
250
+ ...normalizedScope,
251
+ source_kind: 'knowledge',
252
+ source_id: knowledge.id,
253
+ target_kind: 'knowledge',
254
+ target_id: plan.supersededKnowledgeId,
255
+ association_type: 'supersedes',
256
+ confidence: 1,
257
+ auto_generated: true,
258
+ }), (error) => {
259
+ ignoreUniqueConstraint(error);
260
+ return undefined;
261
+ }))
262
+ : undefined, () => chainMaybe(adapter.insertKnowledgeMemoryAudit({
263
+ ...plan.auditInput,
264
+ created_knowledge_id: knowledge.id,
265
+ }), () => knowledge)))));
266
+ });
267
+ }
268
+ async function persistPromotedKnowledgeAsync(adapter, normalizedScope, plan) {
269
+ return Promise.resolve(persistPromotedKnowledgeCore(adapter, normalizedScope, plan));
270
+ }
271
+ function persistPromotedKnowledgeSync(adapter, normalizedScope, plan) {
272
+ return assertSyncResult(persistPromotedKnowledgeCore(adapter, normalizedScope, plan));
273
+ }
92
274
  function evidenceSourceTypeForTurn(turn) {
93
275
  if (turn.role === 'assistant')
94
276
  return 'assistant_turn';
@@ -195,14 +377,14 @@ function assertTurnsMatchScope(turns, scope, sessionId) {
195
377
  const normalized = normalizeScope(scope);
196
378
  for (const turn of turns) {
197
379
  if (turn.session_id !== sessionId) {
198
- throw new Error(`Memory validation: turn ${turn.id} session_id '${turn.session_id}' does not match '${sessionId}'`);
380
+ throw new ValidationError(`Memory validation: turn ${turn.id} session_id '${turn.session_id}' does not match '${sessionId}'`);
199
381
  }
200
382
  if (turn.tenant_id !== normalized.tenant_id ||
201
383
  turn.system_id !== normalized.system_id ||
202
384
  turn.workspace_id !== normalized.workspace_id ||
203
385
  turn.collaboration_id !== normalized.collaboration_id ||
204
386
  turn.scope_id !== normalized.scope_id) {
205
- throw new Error(`Memory validation: turn ${turn.id} does not belong to the requested scope`);
387
+ throw new ScopeMismatchError(`Memory validation: turn ${turn.id} does not belong to the requested scope`);
206
388
  }
207
389
  }
208
390
  }
@@ -216,7 +398,7 @@ export async function commitCompaction(adapter, input) {
216
398
  assertMaxEntries(input.topicTags, 'topicTags', 5);
217
399
  const turnsToArchive = sortTurnsAscending(input.turnsToArchive);
218
400
  if (turnsToArchive.length === 0) {
219
- throw new Error("Memory validation: 'turnsToArchive' must not be empty");
401
+ throw new ValidationError("Memory validation: 'turnsToArchive' must not be empty");
220
402
  }
221
403
  assertTurnsMatchScope(turnsToArchive, normalizedScope, input.sessionId);
222
404
  const turnIdStart = turnsToArchive[0].id;
@@ -306,13 +488,13 @@ export async function commitCompaction(adapter, input) {
306
488
  export async function compactTurns(adapter, scope, sessionId, turnsToCompact, summarize, trigger, retainedTurnCount, telemetry) {
307
489
  assertCompactionTrigger(trigger, 'trigger');
308
490
  if (!Number.isInteger(retainedTurnCount) || retainedTurnCount < 0) {
309
- throw new Error(`Memory validation: 'retainedTurnCount' must be a non-negative integer, got '${retainedTurnCount}'`);
491
+ throw new ValidationError(`Memory validation: 'retainedTurnCount' must be a non-negative integer, got '${retainedTurnCount}'`);
310
492
  }
311
493
  const orderedTurns = sortTurnsAscending(turnsToCompact);
312
494
  assertTurnsMatchScope(orderedTurns, scope, sessionId);
313
495
  const turnsToArchive = orderedTurns.slice(0, Math.max(0, orderedTurns.length - retainedTurnCount));
314
496
  if (turnsToArchive.length === 0) {
315
- throw new Error('Memory validation: no turns are eligible for compaction');
497
+ throw new ValidationError('Memory validation: no turns are eligible for compaction');
316
498
  }
317
499
  const startedAtMs = Date.now();
318
500
  const summary = await summarize(turnsToArchive);
@@ -339,14 +521,14 @@ export async function promoteToKnowledge(adapter, workingMemoryId, input) {
339
521
  const normalizedScope = normalizeScope(input.scope);
340
522
  const workingMemory = await adapter.getWorkingMemoryById(workingMemoryId);
341
523
  if (!workingMemory) {
342
- throw new Error(`Memory validation: working memory ${workingMemoryId} was not found`);
524
+ throw new ResourceNotFoundError(`Memory validation: working memory ${workingMemoryId} was not found`);
343
525
  }
344
526
  if (workingMemory.tenant_id !== normalizedScope.tenant_id ||
345
527
  workingMemory.system_id !== normalizedScope.system_id ||
346
528
  workingMemory.workspace_id !== normalizedScope.workspace_id ||
347
529
  workingMemory.collaboration_id !== normalizedScope.collaboration_id ||
348
530
  workingMemory.scope_id !== normalizedScope.scope_id) {
349
- throw new Error(`Memory validation: working memory ${workingMemoryId} does not belong to the requested scope`);
531
+ throw new ScopeMismatchError(`Memory validation: working memory ${workingMemoryId} does not belong to the requested scope`);
350
532
  }
351
533
  return runAtomicStorage(adapter, async () => {
352
534
  const knowledgeMemory = await adapter.insertKnowledgeMemory({
@@ -391,14 +573,14 @@ export async function extractKnowledge(adapter, workingMemoryId, scope, extracto
391
573
  const policy = resolveExtractionPolicy(options?.policy);
392
574
  const workingMemory = await adapter.getWorkingMemoryById(workingMemoryId);
393
575
  if (!workingMemory) {
394
- throw new Error(`Memory validation: working memory ${workingMemoryId} was not found`);
576
+ throw new ResourceNotFoundError(`Memory validation: working memory ${workingMemoryId} was not found`);
395
577
  }
396
578
  if (workingMemory.tenant_id !== normalizedScope.tenant_id ||
397
579
  workingMemory.system_id !== normalizedScope.system_id ||
398
580
  workingMemory.workspace_id !== normalizedScope.workspace_id ||
399
581
  workingMemory.collaboration_id !== normalizedScope.collaboration_id ||
400
582
  workingMemory.scope_id !== normalizedScope.scope_id) {
401
- throw new Error(`Memory validation: working memory ${workingMemoryId} does not belong to the requested scope`);
583
+ throw new ScopeMismatchError(`Memory validation: working memory ${workingMemoryId} does not belong to the requested scope`);
402
584
  }
403
585
  const extracted = (await extractor(workingMemory.summary, workingMemory.key_entities, workingMemory.topic_tags))
404
586
  .slice(0, policy.maxFactsPerExtraction)
@@ -652,23 +834,7 @@ export async function extractKnowledge(adapter, workingMemoryId, scope, extracto
652
834
  continue;
653
835
  }
654
836
  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
- });
837
+ await runAtomicStorage(adapter, () => persistRejectedKnowledgeCandidateAsync(adapter, candidateInput, candidateEvidenceInputs), (syncAdapter) => persistRejectedKnowledgeCandidateSync(syncAdapter, candidateInput, candidateEvidenceInputs));
672
838
  await adapter.insertKnowledgeMemoryAudit({
673
839
  ...normalizedScope,
674
840
  working_memory_id: workingMemoryId,
@@ -746,165 +912,31 @@ export async function extractKnowledge(adapter, workingMemoryId, scope, extracto
746
912
  });
747
913
  continue;
748
914
  }
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;
915
+ const promotionPlan = buildPromotionPersistencePlan({
916
+ normalizedScope,
917
+ workingMemoryId,
918
+ fact,
919
+ candidateInput,
920
+ candidateEvidenceInputs,
921
+ grounded,
922
+ trustAssessment,
923
+ strongestRelation,
924
+ resolvedConfidence,
925
+ resolvedConfidenceScore,
926
+ resolvedVerificationStatus,
927
+ verificationNotes,
928
+ conflict,
929
+ policy,
901
930
  });
931
+ const createdFact = await runAtomicStorage(adapter, () => persistPromotedKnowledgeAsync(adapter, normalizedScope, promotionPlan), (syncAdapter) => persistPromotedKnowledgeSync(syncAdapter, normalizedScope, promotionPlan));
902
932
  duplicateLookup.set(normalizedFact, createdFact);
903
933
  normalizedKnowledge.push({
904
934
  memory: createdFact,
905
935
  normalized: normalizeKnowledgeMemory(createdFact),
906
936
  });
907
937
  created.push(createdFact);
938
+ // Auto-detect associations between the new fact and all known knowledge (including batch-created)
939
+ await autoDetectAssociations(adapter, normalizedScope, createdFact, [...activeKnowledge, ...created.slice(0, -1)]);
908
940
  }
909
941
  emitMemoryEvent('extraction', normalizedScope, options, Date.now() - startedAt, {
910
942
  workingMemoryId,