cozo-memory 1.1.4 → 1.1.6

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,358 @@
1
+ "use strict";
2
+ /**
3
+ * Hierarchical Memory Levels Service
4
+ *
5
+ * Based on 2026 SOTA Research:
6
+ * - Multi-level memory architecture (L0-L3)
7
+ * - Importance scoring using PageRank + Recency + Access Frequency
8
+ * - Intelligent compression with context preservation
9
+ * - Vector store + summarization approach
10
+ *
11
+ * Memory Levels:
12
+ * - L0: Raw observations (immediate context)
13
+ * - L1: Session summaries (short-term memory)
14
+ * - L2: Weekly summaries (medium-term memory)
15
+ * - L3: Monthly summaries (long-term memory)
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.HierarchicalMemoryService = exports.MemoryLevel = void 0;
19
+ const uuid_1 = require("uuid");
20
+ /**
21
+ * Memory levels following AI agent memory hierarchy
22
+ */
23
+ var MemoryLevel;
24
+ (function (MemoryLevel) {
25
+ MemoryLevel[MemoryLevel["L0_RAW"] = 0] = "L0_RAW";
26
+ MemoryLevel[MemoryLevel["L1_SESSION"] = 1] = "L1_SESSION";
27
+ MemoryLevel[MemoryLevel["L2_WEEKLY"] = 2] = "L2_WEEKLY";
28
+ MemoryLevel[MemoryLevel["L3_MONTHLY"] = 3] = "L3_MONTHLY"; // Monthly summaries (30+ days)
29
+ })(MemoryLevel || (exports.MemoryLevel = MemoryLevel = {}));
30
+ /**
31
+ * Hierarchical Memory Service
32
+ */
33
+ class HierarchicalMemoryService {
34
+ db;
35
+ embeddings;
36
+ config;
37
+ constructor(db, embeddings, config = {}) {
38
+ this.db = db;
39
+ this.embeddings = embeddings;
40
+ this.config = {
41
+ l0_retention_hours: config.l0_retention_hours ?? 24,
42
+ l1_retention_days: config.l1_retention_days ?? 7,
43
+ l2_retention_days: config.l2_retention_days ?? 30,
44
+ l3_retention_days: config.l3_retention_days ?? 365,
45
+ importance_weights: {
46
+ pagerank: config.importance_weights?.pagerank ?? 0.4,
47
+ recency: config.importance_weights?.recency ?? 0.3,
48
+ access_frequency: config.importance_weights?.access_frequency ?? 0.3
49
+ },
50
+ compression_threshold: config.compression_threshold ?? 0.5,
51
+ min_observations_for_compression: config.min_observations_for_compression ?? 10,
52
+ llm_model: config.llm_model ?? 'demyagent-4b-i1:Q6_K'
53
+ };
54
+ }
55
+ /**
56
+ * Calculate importance score for an observation
57
+ */
58
+ async calculateImportanceScore(observationId) {
59
+ try {
60
+ // Get observation details
61
+ const obsResult = await this.db.run(`
62
+ ?[id, entity_id, created_at, metadata] :=
63
+ *observation{id, entity_id, created_at, metadata, @ "NOW"},
64
+ id = $id
65
+ `, { id: observationId });
66
+ if (obsResult.rows.length === 0) {
67
+ return { pagerank: 0, recency: 0, accessFrequency: 0, combined: 0 };
68
+ }
69
+ const [id, entityId, createdAt, metadata] = obsResult.rows[0];
70
+ const createdAtMs = Array.isArray(createdAt) ? createdAt[0] / 1000 : createdAt;
71
+ // 1. PageRank score (entity centrality)
72
+ let pagerank = 0;
73
+ try {
74
+ const pagerankResult = await this.db.run(`
75
+ ?[entity_id, rank] :=
76
+ *entity_rank{entity_id, rank},
77
+ entity_id = $entity_id
78
+ `, { entity_id: entityId });
79
+ if (pagerankResult.rows.length > 0) {
80
+ pagerank = Number(pagerankResult.rows[0][1]);
81
+ }
82
+ }
83
+ catch (e) {
84
+ // PageRank not computed yet, use default
85
+ pagerank = 0.5;
86
+ }
87
+ // 2. Recency score (exponential decay)
88
+ const ageHours = (Date.now() - createdAtMs) / (1000 * 60 * 60);
89
+ const halfLifeHours = 24 * 30; // 30 days half-life
90
+ const recency = Math.pow(0.5, ageHours / halfLifeHours);
91
+ // 3. Access frequency score
92
+ const accessCount = metadata?.access_count ?? 0;
93
+ const maxAccessCount = 100; // Normalize to 0-1
94
+ const accessFrequency = Math.min(1.0, accessCount / maxAccessCount);
95
+ // 4. Combined score (weighted)
96
+ const combined = (pagerank * this.config.importance_weights.pagerank) +
97
+ (recency * this.config.importance_weights.recency) +
98
+ (accessFrequency * this.config.importance_weights.access_frequency);
99
+ return {
100
+ pagerank,
101
+ recency,
102
+ accessFrequency,
103
+ combined
104
+ };
105
+ }
106
+ catch (error) {
107
+ console.error('[HierarchicalMemory] Error calculating importance:', error);
108
+ return { pagerank: 0, recency: 0, accessFrequency: 0, combined: 0 };
109
+ }
110
+ }
111
+ /**
112
+ * Get observations eligible for compression at a given level
113
+ */
114
+ async getObservationsForCompression(entityId, level) {
115
+ // Determine time threshold based on level
116
+ let retentionMs;
117
+ switch (level) {
118
+ case MemoryLevel.L0_RAW:
119
+ retentionMs = this.config.l0_retention_hours * 60 * 60 * 1000;
120
+ break;
121
+ case MemoryLevel.L1_SESSION:
122
+ retentionMs = this.config.l1_retention_days * 24 * 60 * 60 * 1000;
123
+ break;
124
+ case MemoryLevel.L2_WEEKLY:
125
+ retentionMs = this.config.l2_retention_days * 24 * 60 * 60 * 1000;
126
+ break;
127
+ case MemoryLevel.L3_MONTHLY:
128
+ retentionMs = this.config.l3_retention_days * 24 * 60 * 60 * 1000;
129
+ break;
130
+ default:
131
+ retentionMs = 24 * 60 * 60 * 1000; // 1 day default
132
+ }
133
+ const cutoffTime = (Date.now() - retentionMs) * 1000; // Convert to microseconds
134
+ // Get observations older than retention period at this level
135
+ const result = await this.db.run(`
136
+ ?[id, text, created_at_ts, memory_level] :=
137
+ *observation{id, entity_id, text, created_at, metadata, @ "NOW"},
138
+ entity_id = $entity_id,
139
+ created_at_ts = to_int(created_at),
140
+ created_at_ts < $cutoff,
141
+ memory_level = get(metadata, "memory_level", 0),
142
+ memory_level = $level
143
+
144
+ :order created_at_ts
145
+ `, {
146
+ entity_id: entityId,
147
+ cutoff: cutoffTime,
148
+ level
149
+ });
150
+ // Calculate importance for each observation
151
+ const observations = await Promise.all(result.rows.map(async (row) => {
152
+ const id = row[0];
153
+ const text = row[1];
154
+ const createdAt = Array.isArray(row[2]) ? row[2][0] / 1000 : row[2];
155
+ const importanceScore = await this.calculateImportanceScore(id);
156
+ return {
157
+ id,
158
+ text,
159
+ created_at: createdAt,
160
+ importance: importanceScore.combined
161
+ };
162
+ }));
163
+ return observations;
164
+ }
165
+ /**
166
+ * Compress observations at a given level using LLM summarization
167
+ */
168
+ async compressMemoryLevel(entityId, level) {
169
+ try {
170
+ console.error(`[HierarchicalMemory] Compressing level ${level} for entity ${entityId}`);
171
+ // Get observations eligible for compression
172
+ const observations = await this.getObservationsForCompression(entityId, level);
173
+ if (observations.length < this.config.min_observations_for_compression) {
174
+ console.error(`[HierarchicalMemory] Not enough observations (${observations.length}) for compression`);
175
+ return null;
176
+ }
177
+ // Separate high-importance vs low-importance observations
178
+ const highImportance = observations.filter(o => o.importance >= this.config.compression_threshold);
179
+ const lowImportance = observations.filter(o => o.importance < this.config.compression_threshold);
180
+ console.error(`[HierarchicalMemory] High importance: ${highImportance.length}, Low importance: ${lowImportance.length}`);
181
+ // Generate summary using LLM
182
+ const summaryText = await this.generateSummary(observations, level);
183
+ // Create summary observation at next level
184
+ const summaryId = (0, uuid_1.v4)();
185
+ const summaryEmbedding = await this.embeddings.embed(summaryText);
186
+ const now = Date.now() * 1000; // microseconds
187
+ await this.db.run(`
188
+ ?[id, created_at, entity_id, text, embedding, metadata] :=
189
+ id = $id,
190
+ created_at = $created_at,
191
+ entity_id = $entity_id,
192
+ text = $text,
193
+ embedding = $embedding,
194
+ metadata = $metadata
195
+
196
+ :put observation {
197
+ id, created_at => entity_id, text, embedding, metadata
198
+ }
199
+ `, {
200
+ id: summaryId,
201
+ created_at: [now, true],
202
+ entity_id: entityId,
203
+ text: summaryText,
204
+ embedding: summaryEmbedding,
205
+ metadata: {
206
+ memory_level: level + 1,
207
+ compression_source: level,
208
+ compressed_count: observations.length,
209
+ compression_time: Date.now(),
210
+ is_summary: true
211
+ }
212
+ });
213
+ // Delete low-importance observations
214
+ const deletedIds = [];
215
+ for (const obs of lowImportance) {
216
+ await this.db.run(`
217
+ ?[id, created_at, entity_id, text, embedding, metadata] :=
218
+ *observation{id, created_at, entity_id, text, embedding, metadata, @ "NOW"},
219
+ id = $id
220
+
221
+ :delete observation {
222
+ id, created_at, entity_id, text, embedding, metadata
223
+ }
224
+ `, { id: obs.id });
225
+ deletedIds.push(obs.id);
226
+ }
227
+ // Update high-importance observations to next level
228
+ for (const obs of highImportance) {
229
+ await this.db.run(`
230
+ ?[id, created_at, entity_id, text, embedding, metadata] :=
231
+ *observation{id, created_at, entity_id, text, embedding, metadata, @ "NOW"},
232
+ id = $id,
233
+ new_metadata = {
234
+ "memory_level": ${level + 1},
235
+ "preserved_by_importance": true,
236
+ "importance_score": ${obs.importance}
237
+ }
238
+
239
+ :put observation {
240
+ id, created_at => entity_id, text, embedding, metadata: new_metadata
241
+ }
242
+ `, { id: obs.id });
243
+ }
244
+ console.error(`[HierarchicalMemory] Compression complete: ${deletedIds.length} deleted, ${highImportance.length} preserved`);
245
+ return {
246
+ level,
247
+ compressed_observations: observations.length,
248
+ summary_id: summaryId,
249
+ summary_text: summaryText,
250
+ preserved_observations: highImportance.map(o => o.id),
251
+ deleted_observations: deletedIds,
252
+ importance_threshold: this.config.compression_threshold
253
+ };
254
+ }
255
+ catch (error) {
256
+ console.error('[HierarchicalMemory] Error compressing memory:', error);
257
+ return null;
258
+ }
259
+ }
260
+ /**
261
+ * Generate summary using LLM
262
+ */
263
+ async generateSummary(observations, level) {
264
+ try {
265
+ // Dynamic import to avoid hard dependency
266
+ const ollamaModule = await import('ollama');
267
+ const ollama = ollamaModule?.default ?? ollamaModule;
268
+ const levelName = ['Raw', 'Session', 'Weekly', 'Monthly'][level];
269
+ const observationTexts = observations
270
+ .sort((a, b) => b.importance - a.importance) // Sort by importance
271
+ .slice(0, 50) // Limit to top 50
272
+ .map(o => `- ${o.text}`)
273
+ .join('\n');
274
+ const prompt = `Summarize the following ${observations.length} observations into a concise ${levelName}-level memory summary. Focus on key themes, important facts, and recurring patterns. Preserve critical details while reducing redundancy.
275
+
276
+ Observations:
277
+ ${observationTexts}
278
+
279
+ Summary:`;
280
+ const response = await ollama.chat({
281
+ model: this.config.llm_model,
282
+ messages: [
283
+ { role: 'system', content: 'You are a memory compression expert. Create concise, information-dense summaries that preserve key facts and patterns.' },
284
+ { role: 'user', content: prompt }
285
+ ]
286
+ });
287
+ const summary = response?.message?.content?.trim?.() ?? 'Summary generation failed';
288
+ return `[${levelName} Summary] ${summary}`;
289
+ }
290
+ catch (error) {
291
+ console.error('[HierarchicalMemory] LLM summarization failed:', error);
292
+ // Fallback: simple concatenation
293
+ return `[${['Raw', 'Session', 'Weekly', 'Monthly'][level]} Summary] Compressed ${observations.length} observations.`;
294
+ }
295
+ }
296
+ /**
297
+ * Compress all levels for an entity
298
+ */
299
+ async compressAllLevels(entityId) {
300
+ const results = [];
301
+ // Compress in order: L0 -> L1 -> L2 -> L3
302
+ for (let level = MemoryLevel.L0_RAW; level < MemoryLevel.L3_MONTHLY; level++) {
303
+ const result = await this.compressMemoryLevel(entityId, level);
304
+ if (result) {
305
+ results.push(result);
306
+ }
307
+ }
308
+ return results;
309
+ }
310
+ /**
311
+ * Get memory statistics for an entity
312
+ */
313
+ async getMemoryStats(entityId) {
314
+ try {
315
+ // Get all observations and group by level
316
+ const result = await this.db.run(`
317
+ ?[memory_level, count(id)] :=
318
+ *observation{id, entity_id, metadata, @ "NOW"},
319
+ entity_id = $entity_id,
320
+ memory_level = get(metadata, "memory_level", 0)
321
+ `, { entity_id: entityId });
322
+ const byLevel = {};
323
+ let total = 0;
324
+ for (const row of result.rows) {
325
+ const level = Number(row[0]);
326
+ const count = Number(row[1]);
327
+ byLevel[level] = count;
328
+ total += count;
329
+ }
330
+ return {
331
+ total_observations: total,
332
+ by_level: byLevel,
333
+ avg_importance: 0.5 // TODO: Calculate actual average
334
+ };
335
+ }
336
+ catch (error) {
337
+ console.error('[HierarchicalMemory] Error getting stats:', error);
338
+ return {
339
+ total_observations: 0,
340
+ by_level: {},
341
+ avg_importance: 0
342
+ };
343
+ }
344
+ }
345
+ /**
346
+ * Update configuration
347
+ */
348
+ updateConfig(config) {
349
+ this.config = { ...this.config, ...config };
350
+ }
351
+ /**
352
+ * Get current configuration
353
+ */
354
+ getConfig() {
355
+ return { ...this.config };
356
+ }
357
+ }
358
+ exports.HierarchicalMemoryService = HierarchicalMemoryService;