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.
- package/README.md +161 -1159
- package/dist/adaptive-query-fusion.js +397 -0
- package/dist/dynamic-fusion.js +63 -8
- package/dist/explainable-retrieval.js +552 -0
- package/dist/hierarchical-memory.js +358 -0
- package/dist/proactive-suggestions.js +382 -0
- package/dist/temporal-conflict-resolution.js +386 -0
- package/dist/temporal-pattern-detection-backup.js +358 -0
- package/dist/temporal-pattern-detection.js +482 -0
- package/dist/test-adaptive-query-fusion.js +208 -0
- package/dist/test-explainable-retrieval.js +408 -0
- package/dist/test-hierarchical-and-patterns.js +17 -0
- package/dist/test-hierarchical-memory.js +205 -0
- package/dist/test-proactive-suggestions.js +240 -0
- package/dist/test-temporal-conflict-resolution.js +228 -0
- package/dist/test-temporal-patterns.js +317 -0
- package/package.json +1 -1
|
@@ -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;
|