cozo-memory 1.1.6 → 1.1.7

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,355 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PipelineBuilder = exports.QueryPipeline = exports.postProcessStages = exports.rerankStages = exports.searchStages = exports.preprocessStages = void 0;
4
+ exports.createStandardPipeline = createStandardPipeline;
5
+ exports.createGraphRagPipeline = createGraphRagPipeline;
6
+ exports.createAgenticPipeline = createAgenticPipeline;
7
+ // Built-in Preprocessing Stages
8
+ exports.preprocessStages = {
9
+ embedQuery: (embeddingService) => ({
10
+ type: 'preprocess',
11
+ name: 'embed-query',
12
+ enabled: true,
13
+ execute: async (ctx) => {
14
+ const start = Date.now();
15
+ ctx.embedding = await embeddingService.embed(ctx.query);
16
+ ctx.metrics['preprocess.embedding'] = Date.now() - start;
17
+ return ctx;
18
+ }
19
+ }),
20
+ queryNormalization: () => ({
21
+ type: 'preprocess',
22
+ name: 'query-normalization',
23
+ enabled: true,
24
+ execute: async (ctx) => {
25
+ const start = Date.now();
26
+ ctx.query = ctx.query.trim().toLowerCase();
27
+ ctx.metrics['preprocess.normalization'] = Date.now() - start;
28
+ return ctx;
29
+ }
30
+ })
31
+ };
32
+ // Built-in Search Stages
33
+ exports.searchStages = {
34
+ hybridSearch: (hybridSearch) => ({
35
+ type: 'search',
36
+ name: 'hybrid-search',
37
+ enabled: true,
38
+ params: { limit: 10 },
39
+ execute: async (ctx) => {
40
+ const start = Date.now();
41
+ const limit = ctx.metadata.limit || 10;
42
+ ctx.results = await hybridSearch.search({
43
+ query: ctx.query,
44
+ limit,
45
+ includeEntities: true,
46
+ includeObservations: true
47
+ });
48
+ ctx.metrics['search.hybrid'] = Date.now() - start;
49
+ return ctx;
50
+ }
51
+ }),
52
+ graphRag: (hybridSearch) => ({
53
+ type: 'search',
54
+ name: 'graph-rag',
55
+ enabled: true,
56
+ params: { maxDepth: 3, limit: 10 },
57
+ execute: async (ctx) => {
58
+ const start = Date.now();
59
+ const limit = ctx.metadata.limit || 10;
60
+ ctx.results = await hybridSearch.graphRag({
61
+ query: ctx.query,
62
+ limit,
63
+ graphConstraints: {
64
+ maxDepth: ctx.metadata.maxDepth || 3
65
+ }
66
+ });
67
+ ctx.metrics['search.graphRag'] = Date.now() - start;
68
+ return ctx;
69
+ }
70
+ }),
71
+ agenticSearch: (hybridSearch) => ({
72
+ type: 'search',
73
+ name: 'agentic-search',
74
+ enabled: true,
75
+ params: { limit: 10 },
76
+ execute: async (ctx) => {
77
+ const start = Date.now();
78
+ const limit = ctx.metadata.limit || 10;
79
+ ctx.results = await hybridSearch.agenticRetrieve({
80
+ query: ctx.query,
81
+ limit
82
+ });
83
+ ctx.metrics['search.agentic'] = Date.now() - start;
84
+ return ctx;
85
+ }
86
+ })
87
+ };
88
+ // Built-in Reranking Stages
89
+ exports.rerankStages = {
90
+ crossEncoder: (reranker) => ({
91
+ type: 'rerank',
92
+ name: 'cross-encoder',
93
+ enabled: true,
94
+ execute: async (ctx) => {
95
+ const start = Date.now();
96
+ if (!ctx.results || ctx.results.length === 0) {
97
+ return ctx;
98
+ }
99
+ ctx.results = await reranker.rerank(ctx.query, ctx.results);
100
+ ctx.metrics['rerank.crossEncoder'] = Date.now() - start;
101
+ return ctx;
102
+ }
103
+ }),
104
+ diversityRerank: () => ({
105
+ type: 'rerank',
106
+ name: 'diversity-rerank',
107
+ enabled: true,
108
+ params: { diversityWeight: 0.3 },
109
+ execute: async (ctx) => {
110
+ const start = Date.now();
111
+ if (!ctx.results || ctx.results.length === 0) {
112
+ return ctx;
113
+ }
114
+ // MMR-style diversity reranking
115
+ const diversityWeight = ctx.metadata.diversityWeight || 0.3;
116
+ const reranked = [];
117
+ const remaining = [...ctx.results];
118
+ while (remaining.length > 0 && reranked.length < ctx.results.length) {
119
+ let bestIdx = 0;
120
+ let bestScore = -Infinity;
121
+ for (let i = 0; i < remaining.length; i++) {
122
+ const candidate = remaining[i];
123
+ const relevance = candidate.score || 0;
124
+ // Calculate diversity (min similarity to already selected)
125
+ let minSim = 1.0;
126
+ for (const selected of reranked) {
127
+ const sim = cosineSimilarity(candidate.embedding || [], selected.embedding || []);
128
+ minSim = Math.min(minSim, sim);
129
+ }
130
+ const score = (1 - diversityWeight) * relevance + diversityWeight * (1 - minSim);
131
+ if (score > bestScore) {
132
+ bestScore = score;
133
+ bestIdx = i;
134
+ }
135
+ }
136
+ reranked.push(remaining.splice(bestIdx, 1)[0]);
137
+ }
138
+ ctx.results = reranked;
139
+ ctx.metrics['rerank.diversity'] = Date.now() - start;
140
+ return ctx;
141
+ }
142
+ })
143
+ };
144
+ // Built-in Post-processing Stages
145
+ exports.postProcessStages = {
146
+ deduplication: () => ({
147
+ type: 'postprocess',
148
+ name: 'deduplication',
149
+ enabled: true,
150
+ params: { threshold: 0.95 },
151
+ execute: async (ctx) => {
152
+ const start = Date.now();
153
+ if (!ctx.results || ctx.results.length === 0) {
154
+ return ctx;
155
+ }
156
+ const threshold = ctx.metadata.dedupThreshold || 0.95;
157
+ const deduplicated = [];
158
+ for (const result of ctx.results) {
159
+ let isDuplicate = false;
160
+ for (const existing of deduplicated) {
161
+ if (result.entity_id === existing.entity_id) {
162
+ isDuplicate = true;
163
+ break;
164
+ }
165
+ const sim = cosineSimilarity(result.embedding || [], existing.embedding || []);
166
+ if (sim >= threshold) {
167
+ isDuplicate = true;
168
+ break;
169
+ }
170
+ }
171
+ if (!isDuplicate) {
172
+ deduplicated.push(result);
173
+ }
174
+ }
175
+ ctx.results = deduplicated;
176
+ ctx.metrics['postprocess.deduplication'] = Date.now() - start;
177
+ return ctx;
178
+ }
179
+ }),
180
+ scoreNormalization: () => ({
181
+ type: 'postprocess',
182
+ name: 'score-normalization',
183
+ enabled: true,
184
+ execute: async (ctx) => {
185
+ const start = Date.now();
186
+ if (!ctx.results || ctx.results.length === 0) {
187
+ return ctx;
188
+ }
189
+ const scores = ctx.results.map(r => r.score || 0);
190
+ const maxScore = Math.max(...scores);
191
+ const minScore = Math.min(...scores);
192
+ const range = maxScore - minScore;
193
+ if (range > 0) {
194
+ ctx.results = ctx.results.map(r => ({
195
+ ...r,
196
+ score: (r.score - minScore) / range
197
+ }));
198
+ }
199
+ ctx.metrics['postprocess.normalization'] = Date.now() - start;
200
+ return ctx;
201
+ }
202
+ }),
203
+ topK: () => ({
204
+ type: 'postprocess',
205
+ name: 'top-k',
206
+ enabled: true,
207
+ params: { k: 10 },
208
+ execute: async (ctx) => {
209
+ const start = Date.now();
210
+ const k = ctx.metadata.topK || 10;
211
+ if (ctx.results && ctx.results.length > k) {
212
+ ctx.results = ctx.results.slice(0, k);
213
+ }
214
+ ctx.metrics['postprocess.topK'] = Date.now() - start;
215
+ return ctx;
216
+ }
217
+ })
218
+ };
219
+ // Pipeline Executor
220
+ class QueryPipeline {
221
+ config;
222
+ constructor(config) {
223
+ this.config = config;
224
+ }
225
+ async execute(query, metadata = {}) {
226
+ const ctx = {
227
+ query,
228
+ metadata: { ...metadata },
229
+ metrics: {},
230
+ results: []
231
+ };
232
+ const pipelineStart = Date.now();
233
+ for (const stage of this.config.stages) {
234
+ if (!stage.enabled) {
235
+ continue;
236
+ }
237
+ // Check condition if present
238
+ if (stage.condition && !stage.condition(ctx)) {
239
+ console.log(`[Pipeline] Skipping stage ${stage.name} (condition not met)`);
240
+ continue;
241
+ }
242
+ try {
243
+ const stageStart = Date.now();
244
+ await stage.execute(ctx);
245
+ ctx.metrics[`stage.${stage.name}`] = Date.now() - stageStart;
246
+ }
247
+ catch (error) {
248
+ console.error(`[Pipeline] Error in stage ${stage.name}:`, error);
249
+ ctx.metadata[`error.${stage.name}`] = String(error);
250
+ }
251
+ }
252
+ ctx.metrics['pipeline.total'] = Date.now() - pipelineStart;
253
+ return {
254
+ results: ctx.results || [],
255
+ metrics: ctx.metrics,
256
+ metadata: ctx.metadata
257
+ };
258
+ }
259
+ // A/B Testing support
260
+ async executeWithVariants(query, metadata = {}) {
261
+ if (!this.config.abTest?.enabled) {
262
+ return { primary: await this.execute(query, metadata) };
263
+ }
264
+ const primary = await this.execute(query, metadata);
265
+ // Execute variant pipelines if configured
266
+ const variants = {};
267
+ if (this.config.abTest.variants) {
268
+ for (const variant of this.config.abTest.variants) {
269
+ variants[variant] = await this.execute(query, { ...metadata, variant });
270
+ }
271
+ }
272
+ return { primary, variants };
273
+ }
274
+ }
275
+ exports.QueryPipeline = QueryPipeline;
276
+ // Helper: Cosine Similarity
277
+ function cosineSimilarity(a, b) {
278
+ if (a.length !== b.length || a.length === 0)
279
+ return 0;
280
+ let dotProduct = 0;
281
+ let normA = 0;
282
+ let normB = 0;
283
+ for (let i = 0; i < a.length; i++) {
284
+ dotProduct += a[i] * b[i];
285
+ normA += a[i] * a[i];
286
+ normB += b[i] * b[i];
287
+ }
288
+ const denominator = Math.sqrt(normA) * Math.sqrt(normB);
289
+ return denominator === 0 ? 0 : dotProduct / denominator;
290
+ }
291
+ // Pipeline Builder for easy construction
292
+ class PipelineBuilder {
293
+ stages = [];
294
+ name;
295
+ constructor(name) {
296
+ this.name = name;
297
+ }
298
+ addStage(stage) {
299
+ this.stages.push(stage);
300
+ return this;
301
+ }
302
+ addPreprocess(stage) {
303
+ return this.addStage(stage);
304
+ }
305
+ addSearch(stage) {
306
+ return this.addStage(stage);
307
+ }
308
+ addRerank(stage) {
309
+ return this.addStage(stage);
310
+ }
311
+ addPostProcess(stage) {
312
+ return this.addStage(stage);
313
+ }
314
+ build() {
315
+ return {
316
+ name: this.name,
317
+ stages: this.stages
318
+ };
319
+ }
320
+ }
321
+ exports.PipelineBuilder = PipelineBuilder;
322
+ // Preset Pipelines
323
+ function createStandardPipeline(hybridSearch, embeddingService, reranker) {
324
+ const config = new PipelineBuilder('standard')
325
+ .addPreprocess(exports.preprocessStages.queryNormalization())
326
+ .addPreprocess(exports.preprocessStages.embedQuery(embeddingService))
327
+ .addSearch(exports.searchStages.hybridSearch(hybridSearch))
328
+ .addRerank(exports.rerankStages.crossEncoder(reranker))
329
+ .addPostProcess(exports.postProcessStages.deduplication())
330
+ .addPostProcess(exports.postProcessStages.topK())
331
+ .build();
332
+ return new QueryPipeline(config);
333
+ }
334
+ function createGraphRagPipeline(hybridSearch, embeddingService) {
335
+ const config = new PipelineBuilder('graph-rag')
336
+ .addPreprocess(exports.preprocessStages.embedQuery(embeddingService))
337
+ .addSearch(exports.searchStages.graphRag(hybridSearch))
338
+ .addRerank(exports.rerankStages.diversityRerank())
339
+ .addPostProcess(exports.postProcessStages.deduplication())
340
+ .addPostProcess(exports.postProcessStages.scoreNormalization())
341
+ .addPostProcess(exports.postProcessStages.topK())
342
+ .build();
343
+ return new QueryPipeline(config);
344
+ }
345
+ function createAgenticPipeline(hybridSearch, embeddingService, reranker) {
346
+ const config = new PipelineBuilder('agentic')
347
+ .addPreprocess(exports.preprocessStages.queryNormalization())
348
+ .addPreprocess(exports.preprocessStages.embedQuery(embeddingService))
349
+ .addSearch(exports.searchStages.agenticSearch(hybridSearch))
350
+ .addRerank(exports.rerankStages.crossEncoder(reranker))
351
+ .addPostProcess(exports.postProcessStages.deduplication())
352
+ .addPostProcess(exports.postProcessStages.topK())
353
+ .build();
354
+ return new QueryPipeline(config);
355
+ }
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const cozo_node_1 = require("cozo-node");
4
+ const memory_activation_1 = require("./memory-activation");
5
+ async function testMemoryActivation() {
6
+ console.log('=== Memory Activation Service Test ===\n');
7
+ const db = new cozo_node_1.CozoDb();
8
+ const activationService = new memory_activation_1.MemoryActivationService(db, {
9
+ retentionThreshold: 0.1,
10
+ initialStrength: 1.0,
11
+ strengthIncrement: 1.0,
12
+ timeUnit: 'days'
13
+ });
14
+ try {
15
+ // Setup: Create test entity and observations
16
+ console.log('--- Setup: Creating test data ---');
17
+ const entityId = 'test-entity-1';
18
+ const now = Date.now();
19
+ const oneDayAgo = now - (24 * 60 * 60 * 1000);
20
+ const threeDaysAgo = now - (3 * 24 * 60 * 60 * 1000);
21
+ const sevenDaysAgo = now - (7 * 24 * 60 * 60 * 1000);
22
+ const thirtyDaysAgo = now - (30 * 24 * 60 * 60 * 1000);
23
+ // Create entity relation
24
+ await db.run(`
25
+ :create entity {
26
+ id: String,
27
+ name: String,
28
+ type: String,
29
+ =>
30
+ metadata: String
31
+ }
32
+ `);
33
+ await db.run(`
34
+ ?[id, name, type, metadata] <- [
35
+ [$id, 'Test Entity', 'test', 'test entity']
36
+ ]
37
+ :put entity {id, name, type => metadata}
38
+ `, { id: entityId });
39
+ // Create observation relation
40
+ await db.run(`
41
+ :create observation {
42
+ id: String,
43
+ entity_id: String,
44
+ text: String,
45
+ =>
46
+ metadata: Any,
47
+ created_at: Int
48
+ }
49
+ `);
50
+ // Create observations with different access patterns
51
+ const observations = [
52
+ {
53
+ id: 'obs-1',
54
+ text: 'Recently accessed, high strength',
55
+ metadata: { access_count: 5, last_access_time: oneDayAgo },
56
+ created_at: thirtyDaysAgo
57
+ },
58
+ {
59
+ id: 'obs-2',
60
+ text: 'Moderately accessed',
61
+ metadata: { access_count: 2, last_access_time: threeDaysAgo },
62
+ created_at: thirtyDaysAgo
63
+ },
64
+ {
65
+ id: 'obs-3',
66
+ text: 'Rarely accessed, weak memory',
67
+ metadata: { access_count: 1, last_access_time: sevenDaysAgo },
68
+ created_at: thirtyDaysAgo
69
+ },
70
+ {
71
+ id: 'obs-4',
72
+ text: 'Never accessed, very weak',
73
+ metadata: { access_count: 0, last_access_time: thirtyDaysAgo },
74
+ created_at: thirtyDaysAgo
75
+ },
76
+ {
77
+ id: 'obs-5',
78
+ text: 'Frequently accessed, very strong',
79
+ metadata: { access_count: 10, last_access_time: now },
80
+ created_at: thirtyDaysAgo
81
+ }
82
+ ];
83
+ for (const obs of observations) {
84
+ await db.run(`
85
+ ?[id, entity_id, text, metadata, created_at] <- [
86
+ [$id, $entity_id, $text, $metadata, $created_at]
87
+ ]
88
+ :put observation {id, entity_id, text => metadata, created_at}
89
+ `, {
90
+ id: obs.id,
91
+ entity_id: entityId,
92
+ text: obs.text,
93
+ metadata: obs.metadata,
94
+ created_at: obs.created_at
95
+ });
96
+ }
97
+ console.log(`✓ Created ${observations.length} test observations\n`);
98
+ // Test 1: Calculate activation scores
99
+ console.log('--- Test 1: Calculate Activation Scores ---');
100
+ const scores = await activationService.calculateActivationScores(entityId);
101
+ console.log('Activation Scores (sorted by activation):');
102
+ for (const score of scores) {
103
+ console.log(` ${score.observationId}:`);
104
+ console.log(` Activation: ${score.activation.toFixed(4)}`);
105
+ console.log(` Strength: ${score.strength.toFixed(2)}`);
106
+ console.log(` Time since access: ${score.timeSinceAccess.toFixed(2)} days`);
107
+ console.log(` Access count: ${score.accessCount}`);
108
+ console.log(` Should retain: ${score.shouldRetain}`);
109
+ console.log(` Reason: ${score.reason}`);
110
+ }
111
+ console.log();
112
+ // Test 2: Get activation statistics
113
+ console.log('--- Test 2: Activation Statistics ---');
114
+ const stats = await activationService.getActivationStats(entityId);
115
+ console.log(`Total observations: ${stats.totalObservations}`);
116
+ console.log(`Average activation: ${stats.averageActivation.toFixed(4)}`);
117
+ console.log(`Average strength: ${stats.averageStrength.toFixed(2)}`);
118
+ console.log(`Below threshold: ${stats.belowThreshold}`);
119
+ console.log(`Above threshold: ${stats.aboveThreshold}`);
120
+ console.log('Distribution:');
121
+ console.log(` Very weak (<0.1): ${stats.distribution.veryWeak}`);
122
+ console.log(` Weak (0.1-0.3): ${stats.distribution.weak}`);
123
+ console.log(` Moderate (0.3-0.6): ${stats.distribution.moderate}`);
124
+ console.log(` Strong (0.6-0.9): ${stats.distribution.strong}`);
125
+ console.log(` Very strong (>0.9): ${stats.distribution.veryStrong}`);
126
+ console.log();
127
+ // Test 3: Record access (simulates recall)
128
+ console.log('--- Test 3: Record Access (Simulate Recall) ---');
129
+ const weakObsId = 'obs-3';
130
+ console.log(`Recording access for ${weakObsId}...`);
131
+ const beforeAccess = scores.find(s => s.observationId === weakObsId);
132
+ console.log(`Before: activation=${beforeAccess?.activation.toFixed(4)}, strength=${beforeAccess?.strength.toFixed(2)}`);
133
+ await activationService.recordAccess(weakObsId);
134
+ const afterScores = await activationService.calculateActivationScores(entityId);
135
+ const afterAccess = afterScores.find(s => s.observationId === weakObsId);
136
+ console.log(`After: activation=${afterAccess?.activation.toFixed(4)}, strength=${afterAccess?.strength.toFixed(2)}`);
137
+ console.log(`✓ Strength increased from ${beforeAccess?.strength.toFixed(2)} to ${afterAccess?.strength.toFixed(2)}`);
138
+ console.log();
139
+ // Test 4: Prune weak memories (dry run)
140
+ console.log('--- Test 4: Prune Weak Memories (Dry Run) ---');
141
+ const pruneResult = await activationService.pruneWeakMemories(true, entityId);
142
+ console.log(`Candidates for deletion: ${pruneResult.candidates.length}`);
143
+ for (const candidate of pruneResult.candidates) {
144
+ console.log(` ${candidate.observationId}: ${candidate.reason}`);
145
+ }
146
+ console.log(`Would preserve: ${pruneResult.preserved} observations`);
147
+ console.log();
148
+ // Test 5: Activation decay over time
149
+ console.log('--- Test 5: Activation Decay Simulation ---');
150
+ console.log('Simulating activation decay for obs-2 over time:');
151
+ const testObs = scores.find(s => s.observationId === 'obs-2');
152
+ if (testObs) {
153
+ const strength = testObs.strength;
154
+ const timePoints = [0, 1, 3, 7, 14, 30, 60, 90];
155
+ console.log(`Strength: ${strength.toFixed(2)}`);
156
+ console.log('Time (days) | Activation | Retained?');
157
+ console.log('------------|------------|----------');
158
+ for (const days of timePoints) {
159
+ const activation = Math.exp(-days / strength);
160
+ const retained = activation >= 0.1;
161
+ console.log(`${days.toString().padStart(11)} | ${activation.toFixed(4).padStart(10)} | ${retained ? 'Yes' : 'No'}`);
162
+ }
163
+ }
164
+ console.log();
165
+ // Test 6: Strength progression with repeated recalls
166
+ console.log('--- Test 6: Strength Progression with Repeated Recalls ---');
167
+ console.log('Simulating repeated recalls for a new observation:');
168
+ console.log('Recalls | Strength | Activation (7 days) | Retained?');
169
+ console.log('--------|----------|---------------------|----------');
170
+ for (let recalls = 0; recalls <= 10; recalls++) {
171
+ const strength = 1.0 + recalls * 1.0; // initialStrength + recalls * strengthIncrement
172
+ const activation = Math.exp(-7 / strength); // 7 days later
173
+ const retained = activation >= 0.1;
174
+ console.log(`${recalls.toString().padStart(7)} | ${strength.toFixed(2).padStart(8)} | ${activation.toFixed(4).padStart(19)} | ${retained ? 'Yes' : 'No'}`);
175
+ }
176
+ console.log();
177
+ // Test 7: Compare with different threshold values
178
+ console.log('--- Test 7: Impact of Different Retention Thresholds ---');
179
+ const thresholds = [0.05, 0.1, 0.2, 0.3];
180
+ console.log('Threshold | Retained | Deleted');
181
+ console.log('----------|----------|--------');
182
+ for (const threshold of thresholds) {
183
+ const testService = new memory_activation_1.MemoryActivationService(db, {
184
+ retentionThreshold: threshold,
185
+ initialStrength: 1.0,
186
+ strengthIncrement: 1.0,
187
+ timeUnit: 'days'
188
+ });
189
+ const testScores = await testService.calculateActivationScores(entityId);
190
+ const retained = testScores.filter(s => s.shouldRetain).length;
191
+ const deleted = testScores.filter(s => !s.shouldRetain).length;
192
+ console.log(`${threshold.toFixed(2).padStart(9)} | ${retained.toString().padStart(8)} | ${deleted.toString().padStart(7)}`);
193
+ }
194
+ console.log();
195
+ // Test 8: Verify Ebbinghaus curve shape
196
+ console.log('--- Test 8: Ebbinghaus Forgetting Curve Verification ---');
197
+ console.log('Classic Ebbinghaus curve: steep initial decline, then gradual leveling');
198
+ console.log('Time (days) | Retention (S=1) | Retention (S=5) | Retention (S=10)');
199
+ console.log('------------|-----------------|-----------------|------------------');
200
+ const curveTimePoints = [0, 0.5, 1, 2, 5, 10, 20, 30];
201
+ for (const days of curveTimePoints) {
202
+ const r1 = Math.exp(-days / 1);
203
+ const r5 = Math.exp(-days / 5);
204
+ const r10 = Math.exp(-days / 10);
205
+ console.log(`${days.toString().padStart(11)} | ${r1.toFixed(4).padStart(15)} | ${r5.toFixed(4).padStart(15)} | ${r10.toFixed(4).padStart(16)}`);
206
+ }
207
+ console.log();
208
+ console.log('✓ All memory activation tests completed successfully!');
209
+ console.log('\nKey Insights:');
210
+ console.log('- Higher strength (S) = slower forgetting');
211
+ console.log('- Each recall increases strength, making memories more durable');
212
+ console.log('- Activation threshold determines retention policy');
213
+ console.log('- System naturally implements spaced repetition via activation decay');
214
+ }
215
+ catch (error) {
216
+ console.error('Test failed:', error);
217
+ }
218
+ finally {
219
+ db.close();
220
+ }
221
+ }
222
+ testMemoryActivation();