mycai 1.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.
@@ -0,0 +1,444 @@
1
+ /**
2
+ * M4 Living Resonance Memory Engine
3
+ * Core holographic memory system with Hebbian reinforcement, dynamic decay,
4
+ * multi-factor scoring, contradiction engine, and non-blocking asynchronous persistence.
5
+ * Uses 4-Table Multi-Probe LSH indexing with Uint32Array-like index pointers for RAM optimization.
6
+ */
7
+
8
+ import fs from 'fs';
9
+ import { SpectralEngine } from './spectral.js';
10
+
11
+ export class MemoryEngine {
12
+ /**
13
+ * @param {Object} config
14
+ * @param {number} [config.alpha=0.3] - Reinforcement saturating factor
15
+ * @param {number} [config.lambda=0.01] - Base decay rate per hour
16
+ * @param {number} [config.ws=0.4] - Semantic similarity weight
17
+ * @param {number} [config.wr=0.2] - Spectral resonance weight
18
+ * @param {number} [config.wc=0.1] - Confidence weight
19
+ * @param {number} [config.wf=0.1] - Reinforcement frequency weight
20
+ * @param {number} [config.wd=0.1] - Decay penalty weight
21
+ * @param {number} [config.wx=0.1] - Contradiction penalty weight
22
+ */
23
+ constructor(config = {}) {
24
+ this.records = new Map();
25
+ this.recordsList = [];
26
+ this.spectral = new SpectralEngine();
27
+
28
+ this.alpha = config.alpha !== undefined ? config.alpha : 0.3;
29
+ this.lambda = config.lambda !== undefined ? config.lambda : 0.01;
30
+
31
+ // Multi-factor weights
32
+ this.ws = config.ws !== undefined ? config.ws : 0.4;
33
+ this.wr = config.wr !== undefined ? config.wr : 0.2;
34
+ this.wc = config.wc !== undefined ? config.wc : 0.1;
35
+ this.wf = config.wf !== undefined ? config.wf : 0.1;
36
+ this.wd = config.wd !== undefined ? config.wd : 0.1;
37
+ this.wx = config.wx !== undefined ? config.wx : 0.1;
38
+
39
+ // LSH Settings
40
+ this.numTables = 4;
41
+ this.numProjections = 8;
42
+ this.tablesProjections = null;
43
+ this.tablesBuckets = null;
44
+
45
+ // Dedicated registry for high-priority records to ensure exact recall without O(N) scanning
46
+ this.highPriorityRecords = new Map();
47
+
48
+ this._saveTimeout = null;
49
+ this._savePromise = Promise.resolve();
50
+ }
51
+
52
+ _initLSH(D) {
53
+ this.D = D;
54
+ this.tablesProjections = [];
55
+ this.tablesBuckets = [];
56
+ for (let t = 0; t < this.numTables; t++) {
57
+ const projections = [];
58
+ for (let i = 0; i < this.numProjections; i++) {
59
+ const vec = new Float32Array(D);
60
+ for (let j = 0; j < D; j++) {
61
+ vec[j] = Math.random() * 2.0 - 1.0;
62
+ }
63
+ projections.push(vec);
64
+ }
65
+ this.tablesProjections.push(projections);
66
+ // Initialize 256 buckets for the 8-bit hash keys
67
+ this.tablesBuckets.push(Array.from({ length: 256 }, () => []));
68
+ }
69
+ }
70
+
71
+ _hash(vector, tableIndex) {
72
+ let hash = 0;
73
+ const projections = this.tablesProjections[tableIndex];
74
+ for (let i = 0; i < this.numProjections; i++) {
75
+ let dot = 0.0;
76
+ const p = projections[i];
77
+ for (let j = 0; j < this.D; j++) {
78
+ dot += vector[j] * p[j];
79
+ }
80
+ if (dot > 0.0) {
81
+ hash |= (1 << i);
82
+ }
83
+ }
84
+ return hash;
85
+ }
86
+
87
+ /**
88
+ * Add a new MemoryRecord and index it in the LSH tables
89
+ * @param {Object} item
90
+ * @returns {Object} Added memory record
91
+ */
92
+ addRecord(item) {
93
+ const t = Date.now();
94
+ const id = item.id || Math.random().toString(36).substring(2, 11);
95
+
96
+ let repr = item.representation;
97
+ if (!repr || repr.length === 0) {
98
+ repr = new Float32Array(this.D || 8192);
99
+ } else if (!(repr instanceof Float32Array)) {
100
+ repr = new Float32Array(repr);
101
+ }
102
+
103
+ const record = {
104
+ id,
105
+ representation: repr,
106
+ content: item.content,
107
+ confidence: item.confidence !== undefined ? item.confidence : 1.0,
108
+ reinforcement: item.reinforcement !== undefined ? item.reinforcement : 1,
109
+ createdAt: item.createdAt || t,
110
+ lastAccessedAt: item.lastAccessedAt || t,
111
+ lastReinforcedAt: item.lastReinforcedAt || t,
112
+ source: item.source || 'user',
113
+ sourceType: item.sourceType || 'user',
114
+ status: item.status || 'active',
115
+ contradictionIds: item.contradictionIds || [],
116
+ priority: item.priority || 'normal' // Support priority field
117
+ };
118
+
119
+ const recordIndex = this.recordsList.length;
120
+ this.recordsList.push(record);
121
+ this.records.set(id, record);
122
+
123
+ if (record.priority === 'high') {
124
+ this.highPriorityRecords.set(id, recordIndex);
125
+ }
126
+
127
+ if (!this.tablesProjections) {
128
+ this._initLSH(record.representation.length);
129
+ }
130
+
131
+ // Index using numerical pointers in all 4 LSH tables to minimize RAM overhead
132
+ for (let t = 0; t < this.numTables; t++) {
133
+ const hash = this._hash(record.representation, t);
134
+ this.tablesBuckets[t][hash].push(recordIndex);
135
+ }
136
+
137
+ // Auto-check for contradiction
138
+ this._checkAndFlagContradictions(record);
139
+
140
+ return record;
141
+ }
142
+
143
+ /**
144
+ * Reinforce a memory record using saturating Hebbian confidence updates
145
+ * @param {string} id - Record ID
146
+ * @param {number} [alpha=this.alpha] - Custom alpha
147
+ */
148
+ reinforce(id, alpha = this.alpha) {
149
+ const record = this.records.get(id);
150
+ if (!record) return;
151
+
152
+ const t = Date.now();
153
+ const currentC = this.getDecayedConfidence(record, t);
154
+
155
+ record.confidence = currentC + alpha * (1.0 - currentC);
156
+ record.reinforcement += 1;
157
+ record.lastReinforcedAt = t;
158
+ }
159
+
160
+ /**
161
+ * Get dynamically decayed confidence at a specific timestamp
162
+ * @param {Object} record
163
+ * @param {number} [currentTime=Date.now()]
164
+ * @returns {number} Decayed confidence in [0.0, 1.0]
165
+ */
166
+ getDecayedConfidence(record, currentTime = Date.now()) {
167
+ const dtMs = currentTime - record.lastReinforcedAt;
168
+ const dtHours = dtMs / (1000 * 3600);
169
+
170
+ const logReinforce = Math.log2(record.reinforcement + 1);
171
+ const effLambda = this.lambda / (logReinforce || 1.0);
172
+
173
+ return record.confidence * Math.exp(-effLambda * dtHours);
174
+ }
175
+
176
+ /**
177
+ * Set dynamic contradiction status on two conflicting memory records
178
+ * @param {string} idA
179
+ * @param {string} idB
180
+ */
181
+ flagContradiction(idA, idB) {
182
+ const recA = this.records.get(idA);
183
+ const recB = this.records.get(idB);
184
+ if (recA && recB) {
185
+ if (!recA.contradictionIds.includes(idB)) recA.contradictionIds.push(idB);
186
+ if (!recB.contradictionIds.includes(idA)) recB.contradictionIds.push(idA);
187
+ recA.status = 'conflicted';
188
+ recB.status = 'conflicted';
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Retrieve matched memory candidates using Multi-Table LSH and Multi-Probe Hamming search
194
+ * @param {Float32Array} queryRepr - Query FHRR vector
195
+ * @param {number} [limit=5]
196
+ * @param {number} [currentTime=Date.now()]
197
+ * @returns {Array} List of matched candidates and scores
198
+ */
199
+ retrieve(queryRepr, limit = 5, currentTime = Date.now()) {
200
+ const D = queryRepr.length;
201
+ if (!this.tablesProjections) {
202
+ this._initLSH(D);
203
+ }
204
+
205
+ // Gather candidate index pointers from all 4 tables + 8 Hamming neighbors (Multi-probe)
206
+ const candidateIndices = new Set();
207
+
208
+ // 1. High-Priority Injection: Always search high-priority records directly (exact search on small subset)
209
+ for (const [id, recordIndex] of this.highPriorityRecords.entries()) {
210
+ candidateIndices.add(recordIndex);
211
+ }
212
+
213
+ let lshFoundCount = 0;
214
+ for (let t = 0; t < this.numTables; t++) {
215
+ const queryHash = this._hash(queryRepr, t);
216
+
217
+ // Query bucket
218
+ const bucket = this.tablesBuckets[t][queryHash];
219
+ for (let i = 0; i < bucket.length; i++) {
220
+ candidateIndices.add(bucket[i]);
221
+ lshFoundCount++;
222
+ }
223
+
224
+ // Multi-probe neighbors (Hamming distance <= 1)
225
+ for (let bit = 0; bit < this.numProjections; bit++) {
226
+ const neighborHash = queryHash ^ (1 << bit);
227
+ const neighborBucket = this.tablesBuckets[t][neighborHash];
228
+ for (let i = 0; i < neighborBucket.length; i++) {
229
+ candidateIndices.add(neighborBucket[i]);
230
+ lshFoundCount++;
231
+ }
232
+ }
233
+ }
234
+
235
+ // 2. Exact Fallback Conditions (triggered via OR condition):
236
+ // Trigger A: LSH yielded no general candidates (excluding high-priority injection)
237
+ // Trigger B: Highest decayed confidence C among current candidates is < 0.45
238
+ let fallbackTriggered = false;
239
+ if (lshFoundCount === 0) {
240
+ fallbackTriggered = true;
241
+ } else {
242
+ let maxC = -1;
243
+ let maxSim = -1;
244
+ for (const idx of candidateIndices) {
245
+ const record = this.recordsList[idx];
246
+ const C = this.getDecayedConfidence(record, currentTime);
247
+ if (C > maxC) maxC = C;
248
+ const sim = this._cosineSimilarity(queryRepr, record.representation);
249
+ if (sim > maxSim) maxSim = sim;
250
+ }
251
+ if (maxC < 0.45 || maxSim < 0.35) {
252
+ fallbackTriggered = true;
253
+ }
254
+ }
255
+
256
+ let indicesToSearch = candidateIndices;
257
+ if (fallbackTriggered) {
258
+ indicesToSearch = new Set(Array.from({ length: this.recordsList.length }, (_, i) => i));
259
+ }
260
+
261
+ const results = [];
262
+ const specQuery = this.spectral.spectralTransform({ type: 'real', values: queryRepr, D });
263
+
264
+ for (const idx of indicesToSearch) {
265
+ const record = this.recordsList[idx];
266
+
267
+ const sSem = this._cosineSimilarity(queryRepr, record.representation);
268
+ const specRecord = record.spectralTransform || (record.spectralTransform = this.spectral.spectralTransform({ type: 'real', values: record.representation, D }));
269
+ const sRes = this.spectral.spectralSimilarity(specQuery, specRecord);
270
+ const C = this.getDecayedConfidence(record, currentTime);
271
+ const F = Math.min(1.0, Math.log10(record.reinforcement + 1));
272
+ const D_penalty = record.confidence - C;
273
+ const X = (record.status === 'conflicted' || record.contradictionIds.length > 0) ? 1.0 : 0.0;
274
+
275
+ const score = this.ws * sSem + this.wr * sRes + this.wc * C + this.wf * F - this.wd * D_penalty - this.wx * X;
276
+
277
+ results.push({ record, score });
278
+ }
279
+
280
+ results.sort((a, b) => b.score - a.score);
281
+ const sliced = results.slice(0, limit);
282
+ for (const item of sliced) {
283
+ item.record.lastAccessedAt = currentTime;
284
+ }
285
+
286
+ return sliced.map(item => ({
287
+ record: item.record,
288
+ score: item.score
289
+ }));
290
+ }
291
+
292
+ /**
293
+ * Non-blocking debounced save to file system
294
+ * @param {string} filePath
295
+ */
296
+ saveToFile(filePath) {
297
+ if (this._saveTimeout) clearTimeout(this._saveTimeout);
298
+
299
+ const data = [];
300
+ for (const record of this.recordsList) {
301
+ data.push({
302
+ id: record.id,
303
+ representation: Array.from(record.representation),
304
+ content: record.content,
305
+ confidence: record.confidence,
306
+ reinforcement: record.reinforcement,
307
+ createdAt: record.createdAt,
308
+ lastAccessedAt: record.lastAccessedAt,
309
+ lastReinforcedAt: record.lastReinforcedAt,
310
+ source: record.source,
311
+ sourceType: record.sourceType,
312
+ status: record.status,
313
+ contradictionIds: record.contradictionIds
314
+ });
315
+ }
316
+
317
+ this._savePromise = new Promise((resolve) => {
318
+ this._saveTimeout = setTimeout(async () => {
319
+ try {
320
+ await fs.promises.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8');
321
+ } catch (e) {
322
+ console.error("MemoryEngine: Asynchronous write failed:", e.message);
323
+ }
324
+ resolve();
325
+ }, 50);
326
+ });
327
+ }
328
+
329
+ /**
330
+ * Load memory records synchronously and rebuild LSH tables
331
+ * @param {string} filePath
332
+ */
333
+ loadFromFile(filePath) {
334
+ if (!fs.existsSync(filePath)) return;
335
+ try {
336
+ const raw = fs.readFileSync(filePath, 'utf8');
337
+ const data = JSON.parse(raw);
338
+ this.records.clear();
339
+ this.recordsList = [];
340
+ this.tablesProjections = null;
341
+ this.tablesBuckets = null;
342
+
343
+ for (const item of data) {
344
+ this.addRecord({
345
+ id: item.id,
346
+ representation: new Float32Array(item.representation),
347
+ content: item.content,
348
+ confidence: item.confidence,
349
+ reinforcement: item.reinforcement,
350
+ createdAt: item.createdAt,
351
+ lastAccessedAt: item.lastAccessedAt,
352
+ lastReinforcedAt: item.lastReinforcedAt,
353
+ source: item.source,
354
+ sourceType: item.sourceType,
355
+ status: item.status,
356
+ contradictionIds: item.contradictionIds
357
+ });
358
+ }
359
+ } catch (e) {
360
+ console.error("MemoryEngine: Failed to load from file:", e.message);
361
+ }
362
+ }
363
+
364
+ _cosineSimilarity(a, b) {
365
+ let sum = 0;
366
+ const D = a.length;
367
+ for (let i = 0; i < D; i++) {
368
+ sum += Math.cos(a[i] - b[i]);
369
+ }
370
+ return sum / D;
371
+ }
372
+
373
+ _checkAndFlagContradictions(record) {
374
+ if (this.recordsList.length <= 1) return;
375
+ const existingResults = this.retrieve(record.representation, 3);
376
+ for (const existing of existingResults) {
377
+ const sim = this._cosineSimilarity(record.representation, existing.record.representation);
378
+ const isContra = this._isContradictory(record.content, existing.record.content);
379
+ if (sim > 0.35 && existing.record.id !== record.id && existing.record.content !== record.content) {
380
+ if (isContra) {
381
+ this.flagContradiction(record.id, existing.record.id);
382
+ }
383
+ }
384
+ }
385
+ }
386
+
387
+ _isContradictory(t1, t2) {
388
+ const getBloodType = (t) => {
389
+ if (t.includes('a pozitif') || t.includes('a+') || t.includes('a poz')) return 'A+';
390
+ if (t.includes('b negatif') || t.includes('b-') || t.includes('b neg')) return 'B-';
391
+ if (t.includes('0') || t.includes('sıfır')) return '0';
392
+ if (t.includes('ab')) return 'AB';
393
+ return null;
394
+ };
395
+ const getRisk = (t) => {
396
+ if (t.includes('yüksek') || t.includes('riskli')) return 'high';
397
+ if (t.includes('normal') || t.includes('düşük') || t.includes('risk yok')) return 'normal';
398
+ return null;
399
+ };
400
+
401
+ const l1 = t1.toLowerCase();
402
+ const l2 = t2.toLowerCase();
403
+
404
+ if ((l1.includes('kan grubu') || l1.includes('grubu')) && (l2.includes('kan grubu') || l2.includes('grubu'))) {
405
+ const b1 = getBloodType(l1);
406
+ const b2 = getBloodType(l2);
407
+ if (b1 && b2 && b1 !== b2) return true;
408
+ }
409
+
410
+ if (l1.includes('risk') && l2.includes('risk')) {
411
+ const r1 = getRisk(l1);
412
+ const r2 = getRisk(l2);
413
+ if (r1 && r2 && r1 !== r2) return true;
414
+ }
415
+
416
+ if ((l1.includes('tansiyon') || l1.includes('basıncı')) && (l2.includes('tansiyon') || l2.includes('basıncı'))) {
417
+ const nums1 = l1.match(/\d+/g);
418
+ const nums2 = l2.match(/\d+/g);
419
+ if (nums1 && nums2 && nums1[0] !== nums2[0]) return true;
420
+ }
421
+
422
+ return false;
423
+ }
424
+
425
+ resolveConflict(winnerId, loserId) {
426
+ const winner = this.records.get(winnerId);
427
+ const loser = this.records.get(loserId);
428
+
429
+ if (winner) {
430
+ const t = Date.now();
431
+ const currentC = this.getDecayedConfidence(winner, t);
432
+ winner.confidence = Math.min(1.0, currentC + 0.3 * (1.0 - currentC));
433
+ winner.status = 'active';
434
+ winner.contradictionIds = [];
435
+ winner.lastReinforcedAt = t;
436
+ }
437
+
438
+ if (loser) {
439
+ loser.confidence = 0.0;
440
+ loser.status = 'rejected';
441
+ loser.contradictionIds = [];
442
+ }
443
+ }
444
+ }