@lov3kaizen/agentsea-memory 0.5.1

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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +450 -0
  3. package/dist/chunk-GACX3FPR.js +1402 -0
  4. package/dist/chunk-M44NB53O.js +1226 -0
  5. package/dist/chunk-MQDWBPZU.js +972 -0
  6. package/dist/chunk-TPC7MYWK.js +1495 -0
  7. package/dist/chunk-XD2CQGSD.js +1540 -0
  8. package/dist/chunk-YI7RPDEV.js +1215 -0
  9. package/dist/core.types-lkxKv-bW.d.cts +242 -0
  10. package/dist/core.types-lkxKv-bW.d.ts +242 -0
  11. package/dist/debug/index.cjs +1248 -0
  12. package/dist/debug/index.d.cts +3 -0
  13. package/dist/debug/index.d.ts +3 -0
  14. package/dist/debug/index.js +20 -0
  15. package/dist/index-7SsAJ4et.d.ts +525 -0
  16. package/dist/index-BGxYqpFb.d.cts +601 -0
  17. package/dist/index-BX62efZu.d.ts +565 -0
  18. package/dist/index-Bbc3COw0.d.cts +748 -0
  19. package/dist/index-Bczz1Eyk.d.ts +637 -0
  20. package/dist/index-C7pEiT8L.d.cts +637 -0
  21. package/dist/index-CHetLTb0.d.ts +389 -0
  22. package/dist/index-CloeiFyx.d.ts +748 -0
  23. package/dist/index-DNOhq-3y.d.cts +525 -0
  24. package/dist/index-Da-M8FOV.d.cts +389 -0
  25. package/dist/index-Dy8UjRFz.d.cts +565 -0
  26. package/dist/index-aVcITW0B.d.ts +601 -0
  27. package/dist/index.cjs +8554 -0
  28. package/dist/index.d.cts +293 -0
  29. package/dist/index.d.ts +293 -0
  30. package/dist/index.js +742 -0
  31. package/dist/processing/index.cjs +1575 -0
  32. package/dist/processing/index.d.cts +2 -0
  33. package/dist/processing/index.d.ts +2 -0
  34. package/dist/processing/index.js +24 -0
  35. package/dist/retrieval/index.cjs +1262 -0
  36. package/dist/retrieval/index.d.cts +2 -0
  37. package/dist/retrieval/index.d.ts +2 -0
  38. package/dist/retrieval/index.js +26 -0
  39. package/dist/sharing/index.cjs +1003 -0
  40. package/dist/sharing/index.d.cts +3 -0
  41. package/dist/sharing/index.d.ts +3 -0
  42. package/dist/sharing/index.js +16 -0
  43. package/dist/stores/index.cjs +1445 -0
  44. package/dist/stores/index.d.cts +2 -0
  45. package/dist/stores/index.d.ts +2 -0
  46. package/dist/stores/index.js +20 -0
  47. package/dist/structures/index.cjs +1530 -0
  48. package/dist/structures/index.d.cts +3 -0
  49. package/dist/structures/index.d.ts +3 -0
  50. package/dist/structures/index.js +24 -0
  51. package/package.json +141 -0
@@ -0,0 +1,1495 @@
1
+ // src/structures/WorkingMemory.ts
2
+ import { EventEmitter } from "eventemitter3";
3
+ var WorkingMemory = class extends EventEmitter {
4
+ store;
5
+ config;
6
+ contextWindow = [];
7
+ attentionBuffer = /* @__PURE__ */ new Map();
8
+ currentQuery = "";
9
+ constructor(store, config = {}) {
10
+ super();
11
+ this.store = store;
12
+ this.config = {
13
+ maxItems: config.maxItems ?? 20,
14
+ maxSize: config.maxSize ?? 20,
15
+ ttl: config.ttl ?? 3e5,
16
+ // 5 minutes default
17
+ importance: config.importance ?? "recency",
18
+ onEvict: config.onEvict ?? (() => {
19
+ }),
20
+ attentionWindow: config.attentionWindow ?? 5,
21
+ decayRate: config.decayRate ?? 0.1,
22
+ relevanceThreshold: config.relevanceThreshold ?? 0.3,
23
+ autoEvict: config.autoEvict ?? true
24
+ };
25
+ }
26
+ /**
27
+ * Add an entry to working memory
28
+ */
29
+ async add(entry) {
30
+ const existingIdx = this.contextWindow.findIndex((e) => e.id === entry.id);
31
+ if (existingIdx !== -1) {
32
+ this.contextWindow.splice(existingIdx, 1);
33
+ }
34
+ this.contextWindow.unshift(entry);
35
+ this.attentionBuffer.set(entry.id, 1);
36
+ if (this.contextWindow.length > this.config.maxSize) {
37
+ await this.evictLowestAttention();
38
+ }
39
+ this.emit("contextUpdate", this.contextWindow);
40
+ }
41
+ /**
42
+ * Get all entries in working memory
43
+ */
44
+ getContext() {
45
+ return [...this.contextWindow];
46
+ }
47
+ /**
48
+ * Get entries with attention scores
49
+ */
50
+ getContextWithAttention() {
51
+ return this.contextWindow.map((entry, index) => {
52
+ const recency = 1 - index / this.config.maxSize;
53
+ const attention = this.attentionBuffer.get(entry.id) ?? 0;
54
+ const relevance = this.calculateRelevance(entry);
55
+ const importance = entry.importance;
56
+ const total = recency * 0.3 + attention * 0.3 + relevance * 0.25 + importance * 0.15;
57
+ return {
58
+ entry,
59
+ relevance,
60
+ recency,
61
+ importance,
62
+ total
63
+ };
64
+ });
65
+ }
66
+ /**
67
+ * Update attention for an entry (e.g., when referenced)
68
+ */
69
+ attend(id) {
70
+ const current = this.attentionBuffer.get(id) ?? 0;
71
+ const newScore = Math.min(current + 0.2, 1);
72
+ this.attentionBuffer.set(id, newScore);
73
+ const entry = this.contextWindow.find((e) => e.id === id);
74
+ if (entry) {
75
+ this.emit("attention", entry, newScore);
76
+ }
77
+ }
78
+ /**
79
+ * Set the current query/context for relevance calculation
80
+ */
81
+ setQuery(query) {
82
+ this.currentQuery = query;
83
+ }
84
+ /**
85
+ * Decay attention scores
86
+ */
87
+ decay() {
88
+ for (const [id, score] of this.attentionBuffer) {
89
+ const newScore = score * (1 - this.config.decayRate);
90
+ if (newScore < 0.01) {
91
+ this.attentionBuffer.delete(id);
92
+ } else {
93
+ this.attentionBuffer.set(id, newScore);
94
+ }
95
+ }
96
+ }
97
+ /**
98
+ * Get the most attended entries
99
+ */
100
+ getFocused(limit = 5) {
101
+ const scored = this.getContextWithAttention();
102
+ scored.sort((a, b) => b.total - a.total);
103
+ return scored.slice(0, limit).map((s) => s.entry);
104
+ }
105
+ /**
106
+ * Clear working memory
107
+ */
108
+ clear() {
109
+ const evicted = [...this.contextWindow];
110
+ this.contextWindow = [];
111
+ this.attentionBuffer.clear();
112
+ this.emit("overflow", evicted);
113
+ this.emit("contextUpdate", []);
114
+ }
115
+ /**
116
+ * Remove a specific entry
117
+ */
118
+ remove(id) {
119
+ const idx = this.contextWindow.findIndex((e) => e.id === id);
120
+ if (idx === -1) return false;
121
+ this.contextWindow.splice(idx, 1);
122
+ this.attentionBuffer.delete(id);
123
+ this.emit("contextUpdate", this.contextWindow);
124
+ return true;
125
+ }
126
+ /**
127
+ * Get current size
128
+ */
129
+ get size() {
130
+ return this.contextWindow.length;
131
+ }
132
+ /**
133
+ * Check if at capacity
134
+ */
135
+ get isFull() {
136
+ return this.contextWindow.length >= this.config.maxSize;
137
+ }
138
+ /**
139
+ * Consolidate important items to long-term storage
140
+ */
141
+ async consolidate(targetStore) {
142
+ const scored = this.getContextWithAttention();
143
+ const toConsolidate = scored.filter(
144
+ (s) => s.total > this.config.relevanceThreshold
145
+ );
146
+ let count = 0;
147
+ for (const { entry } of toConsolidate) {
148
+ await targetStore.add(entry);
149
+ count++;
150
+ }
151
+ return count;
152
+ }
153
+ /**
154
+ * Load context from store
155
+ */
156
+ async loadFromStore(options) {
157
+ const { entries } = await this.store.query({
158
+ namespace: options?.namespace,
159
+ userId: options?.userId,
160
+ conversationId: options?.conversationId,
161
+ limit: options?.limit ?? this.config.maxSize
162
+ });
163
+ this.contextWindow = entries;
164
+ this.attentionBuffer.clear();
165
+ for (const entry of entries) {
166
+ this.attentionBuffer.set(entry.id, 0.5);
167
+ }
168
+ this.emit("contextUpdate", this.contextWindow);
169
+ }
170
+ /**
171
+ * Calculate relevance to current query
172
+ */
173
+ calculateRelevance(entry) {
174
+ if (!this.currentQuery) return 0.5;
175
+ const queryWords = this.currentQuery.toLowerCase().split(/\s+/);
176
+ const contentWords = entry.content.toLowerCase();
177
+ let matches = 0;
178
+ for (const word of queryWords) {
179
+ if (word.length > 2 && contentWords.includes(word)) {
180
+ matches++;
181
+ }
182
+ }
183
+ return queryWords.length > 0 ? matches / queryWords.length : 0;
184
+ }
185
+ /**
186
+ * Evict entry with lowest attention
187
+ */
188
+ async evictLowestAttention() {
189
+ const scored = this.getContextWithAttention();
190
+ scored.sort((a, b) => a.total - b.total);
191
+ const toEvict = scored[0];
192
+ if (toEvict) {
193
+ const idx = this.contextWindow.findIndex(
194
+ (e) => e.id === toEvict.entry.id
195
+ );
196
+ if (idx !== -1) {
197
+ const evicted = this.contextWindow.splice(idx, 1);
198
+ this.attentionBuffer.delete(toEvict.entry.id);
199
+ this.emit("overflow", evicted);
200
+ }
201
+ }
202
+ return Promise.resolve();
203
+ }
204
+ /**
205
+ * Get summary of working memory state
206
+ */
207
+ getSummary() {
208
+ const types = {};
209
+ let totalAttention = 0;
210
+ for (const entry of this.contextWindow) {
211
+ types[entry.type] = (types[entry.type] ?? 0) + 1;
212
+ totalAttention += this.attentionBuffer.get(entry.id) ?? 0;
213
+ }
214
+ return {
215
+ size: this.contextWindow.length,
216
+ maxSize: this.config.maxSize,
217
+ avgAttention: this.contextWindow.length > 0 ? totalAttention / this.contextWindow.length : 0,
218
+ topTypes: types
219
+ };
220
+ }
221
+ };
222
+ function createWorkingMemory(store, config) {
223
+ return new WorkingMemory(store, config);
224
+ }
225
+
226
+ // src/structures/EpisodicMemory.ts
227
+ import { EventEmitter as EventEmitter2 } from "eventemitter3";
228
+ var EpisodicMemory = class extends EventEmitter2 {
229
+ store;
230
+ config;
231
+ episodes = /* @__PURE__ */ new Map();
232
+ currentEpisode = null;
233
+ episodeTimeout = null;
234
+ constructor(store, config = {}) {
235
+ super();
236
+ this.store = store;
237
+ this.config = {
238
+ store: config.store ?? store,
239
+ consolidateAfter: config.consolidateAfter ?? 24 * 60 * 60 * 1e3,
240
+ // 24 hours
241
+ summarizeThreshold: config.summarizeThreshold ?? 10,
242
+ retentionDays: config.retentionDays ?? 90,
243
+ maxEpisodeLength: config.maxEpisodeLength ?? 50,
244
+ episodeTimeout: config.episodeTimeout ?? 30 * 60 * 1e3,
245
+ // 30 minutes
246
+ autoSummarize: config.autoSummarize ?? true,
247
+ minEventsForEpisode: config.minEventsForEpisode ?? 3,
248
+ emotionTracking: config.emotionTracking ?? false
249
+ };
250
+ }
251
+ /**
252
+ * Record an event
253
+ */
254
+ async recordEvent(entry) {
255
+ const eventEntry = {
256
+ ...entry,
257
+ type: "event"
258
+ };
259
+ await this.store.add(eventEntry);
260
+ let episode = this.currentEpisode;
261
+ if (!episode) {
262
+ episode = this.startEpisode();
263
+ }
264
+ episode.events.push(eventEntry);
265
+ this.resetEpisodeTimeout();
266
+ if (episode.events.length >= this.config.maxEpisodeLength) {
267
+ await this.endCurrentEpisode();
268
+ }
269
+ this.emit("eventAdded", eventEntry, episode);
270
+ return episode;
271
+ }
272
+ /**
273
+ * Start a new episode
274
+ */
275
+ startEpisode(metadata) {
276
+ if (this.currentEpisode) {
277
+ void this.endCurrentEpisode();
278
+ }
279
+ const episode = {
280
+ id: this.generateId(),
281
+ startTime: Date.now(),
282
+ events: [],
283
+ metadata: metadata ?? {}
284
+ };
285
+ this.currentEpisode = episode;
286
+ this.episodes.set(episode.id, episode);
287
+ this.emit("episodeStart", episode);
288
+ return episode;
289
+ }
290
+ /**
291
+ * End the current episode
292
+ */
293
+ async endCurrentEpisode() {
294
+ if (!this.currentEpisode) return null;
295
+ const episode = this.currentEpisode;
296
+ episode.endTime = Date.now();
297
+ if (this.config.autoSummarize && episode.events.length >= this.config.minEventsForEpisode) {
298
+ episode.summary = this.generateEpisodeSummary(episode);
299
+ }
300
+ if (episode.summary) {
301
+ await this.store.add({
302
+ id: `episode-summary-${episode.id}`,
303
+ content: episode.summary,
304
+ type: "summary",
305
+ importance: this.calculateEpisodeImportance(episode),
306
+ metadata: {
307
+ source: "system",
308
+ confidence: 0.9,
309
+ episodeId: episode.id,
310
+ eventCount: episode.events.length,
311
+ duration: episode.endTime - episode.startTime,
312
+ ...episode.metadata
313
+ },
314
+ timestamp: episode.startTime,
315
+ accessCount: 0,
316
+ createdAt: Date.now(),
317
+ updatedAt: Date.now()
318
+ });
319
+ }
320
+ this.currentEpisode = null;
321
+ if (this.episodeTimeout) {
322
+ clearTimeout(this.episodeTimeout);
323
+ this.episodeTimeout = null;
324
+ }
325
+ this.emit("episodeEnd", episode);
326
+ return episode;
327
+ }
328
+ /**
329
+ * Recall events from a time period
330
+ */
331
+ async recall(options) {
332
+ const { entries } = await this.store.query({
333
+ types: ["event"],
334
+ startTime: options.startTime,
335
+ endTime: options.endTime,
336
+ conversationId: options.conversationId,
337
+ userId: options.userId,
338
+ limit: options.limit ?? 50
339
+ });
340
+ return entries;
341
+ }
342
+ /**
343
+ * Search episodes by content
344
+ */
345
+ async searchEpisodes(query, limit = 10) {
346
+ const { entries } = await this.store.query({
347
+ query,
348
+ types: ["summary"],
349
+ limit
350
+ });
351
+ const results = [];
352
+ for (const entry of entries) {
353
+ const episodeId = entry.metadata.episodeId;
354
+ if (episodeId && this.episodes.has(episodeId)) {
355
+ results.push(this.episodes.get(episodeId));
356
+ }
357
+ }
358
+ return results;
359
+ }
360
+ /**
361
+ * Get episode by ID
362
+ */
363
+ getEpisode(id) {
364
+ return this.episodes.get(id);
365
+ }
366
+ /**
367
+ * Get current episode
368
+ */
369
+ getCurrentEpisode() {
370
+ return this.currentEpisode;
371
+ }
372
+ /**
373
+ * Get all episodes in a time range
374
+ */
375
+ getEpisodesByTimeRange(startTime, endTime) {
376
+ return Array.from(this.episodes.values()).filter(
377
+ (ep) => ep.startTime >= startTime && (ep.endTime ?? Date.now()) <= endTime
378
+ );
379
+ }
380
+ /**
381
+ * Get recent episodes
382
+ */
383
+ getRecentEpisodes(limit = 5) {
384
+ const sorted = Array.from(this.episodes.values()).sort(
385
+ (a, b) => b.startTime - a.startTime
386
+ );
387
+ return sorted.slice(0, limit);
388
+ }
389
+ /**
390
+ * Find similar episodes
391
+ */
392
+ async findSimilarEpisodes(episode, embedFn, limit = 5) {
393
+ if (!episode.summary) return [];
394
+ const embedding = await embedFn(episode.summary);
395
+ const results = await this.store.search(embedding, {
396
+ topK: limit + 1
397
+ // +1 to potentially exclude self
398
+ });
399
+ const similar = [];
400
+ for (const result of results) {
401
+ const episodeId = result.entry.metadata.episodeId;
402
+ if (episodeId && episodeId !== episode.id && this.episodes.has(episodeId)) {
403
+ similar.push({
404
+ episode: this.episodes.get(episodeId),
405
+ similarity: result.score
406
+ });
407
+ }
408
+ }
409
+ return Promise.resolve(similar.slice(0, limit));
410
+ }
411
+ /**
412
+ * Merge related episodes
413
+ */
414
+ mergeEpisodes(episodeIds, newTitle) {
415
+ const episodes = episodeIds.map((id) => this.episodes.get(id)).filter((ep) => ep !== void 0);
416
+ if (episodes.length < 2) return null;
417
+ episodes.sort((a, b) => a.startTime - b.startTime);
418
+ const merged = {
419
+ id: this.generateId(),
420
+ title: newTitle ?? `Merged episode (${episodes.length} episodes)`,
421
+ startTime: episodes[0].startTime,
422
+ endTime: episodes[episodes.length - 1].endTime,
423
+ events: episodes.flatMap((ep) => ep.events),
424
+ metadata: {
425
+ mergedFrom: episodeIds
426
+ }
427
+ };
428
+ merged.summary = this.generateEpisodeSummary(merged);
429
+ this.episodes.set(merged.id, merged);
430
+ return merged;
431
+ }
432
+ /**
433
+ * Reset episode timeout
434
+ */
435
+ resetEpisodeTimeout() {
436
+ if (this.episodeTimeout) {
437
+ clearTimeout(this.episodeTimeout);
438
+ }
439
+ this.episodeTimeout = setTimeout(() => {
440
+ void this.endCurrentEpisode();
441
+ }, this.config.episodeTimeout);
442
+ }
443
+ /**
444
+ * Generate episode summary
445
+ */
446
+ generateEpisodeSummary(episode) {
447
+ const eventSummaries = episode.events.slice(0, 10).map((e) => e.content.slice(0, 100)).join("; ");
448
+ const duration = episode.endTime ? Math.round((episode.endTime - episode.startTime) / 6e4) : "ongoing";
449
+ return `Episode with ${episode.events.length} events over ${duration} minutes: ${eventSummaries}`;
450
+ }
451
+ /**
452
+ * Calculate episode importance
453
+ */
454
+ calculateEpisodeImportance(episode) {
455
+ if (episode.events.length === 0) return 0;
456
+ const avgImportance = episode.events.reduce((sum, e) => sum + e.importance, 0) / episode.events.length;
457
+ const lengthBonus = Math.min(episode.events.length / 20, 0.2);
458
+ return Math.min(avgImportance + lengthBonus, 1);
459
+ }
460
+ /**
461
+ * Generate unique ID
462
+ */
463
+ generateId() {
464
+ return `ep-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
465
+ }
466
+ /**
467
+ * Get statistics
468
+ */
469
+ getStats() {
470
+ const totalEvents = Array.from(this.episodes.values()).reduce(
471
+ (sum, ep) => sum + ep.events.length,
472
+ 0
473
+ );
474
+ return {
475
+ totalEpisodes: this.episodes.size,
476
+ currentEpisodeSize: this.currentEpisode?.events.length ?? 0,
477
+ totalEvents,
478
+ avgEventsPerEpisode: this.episodes.size > 0 ? totalEvents / this.episodes.size : 0
479
+ };
480
+ }
481
+ };
482
+ function createEpisodicMemory(store, config) {
483
+ return new EpisodicMemory(store, config);
484
+ }
485
+
486
+ // src/structures/SemanticMemory.ts
487
+ import { EventEmitter as EventEmitter3 } from "eventemitter3";
488
+ var SemanticMemory = class extends EventEmitter3 {
489
+ store;
490
+ config;
491
+ concepts = /* @__PURE__ */ new Map();
492
+ relationships = /* @__PURE__ */ new Map();
493
+ conceptIndex = /* @__PURE__ */ new Map();
494
+ // category -> concept IDs
495
+ constructor(store, config = {}) {
496
+ super();
497
+ this.store = store;
498
+ this.config = {
499
+ store: config.store ?? store,
500
+ extractEntities: config.extractEntities ?? true,
501
+ extractRelations: config.extractRelations ?? true,
502
+ deduplication: config.deduplication ?? true,
503
+ deduplicationThreshold: config.deduplicationThreshold ?? 0.9,
504
+ maxConcepts: config.maxConcepts ?? 1e4,
505
+ enableInference: config.enableInference ?? true,
506
+ conflictResolution: config.conflictResolution ?? "newest",
507
+ minConfidence: config.minConfidence ?? 0.5
508
+ };
509
+ }
510
+ /**
511
+ * Learn a new fact
512
+ */
513
+ async learnFact(content, metadata) {
514
+ const fact = {
515
+ id: this.generateId("fact"),
516
+ content,
517
+ type: "fact",
518
+ importance: metadata?.importance ?? 0.5,
519
+ metadata: {
520
+ source: "explicit",
521
+ confidence: metadata?.confidence ?? 0.8,
522
+ ...metadata,
523
+ verified: metadata?.verified ?? false
524
+ },
525
+ timestamp: Date.now(),
526
+ accessCount: 0,
527
+ createdAt: Date.now(),
528
+ updatedAt: Date.now()
529
+ };
530
+ await this.store.add(fact);
531
+ this.emit("factLearned", fact);
532
+ return fact;
533
+ }
534
+ /**
535
+ * Add or update a concept
536
+ */
537
+ addConcept(concept) {
538
+ const existing = Array.from(this.concepts.values()).find(
539
+ (c) => c.name.toLowerCase() === concept.name.toLowerCase()
540
+ );
541
+ if (existing) {
542
+ const updated = {
543
+ ...existing,
544
+ ...concept,
545
+ id: existing.id,
546
+ createdAt: existing.createdAt,
547
+ updatedAt: Date.now()
548
+ };
549
+ this.concepts.set(existing.id, updated);
550
+ this.emit("conceptUpdated", updated);
551
+ return updated;
552
+ }
553
+ const newConcept = {
554
+ ...concept,
555
+ id: this.generateId("concept"),
556
+ createdAt: Date.now(),
557
+ updatedAt: Date.now()
558
+ };
559
+ this.concepts.set(newConcept.id, newConcept);
560
+ if (newConcept.category) {
561
+ if (!this.conceptIndex.has(newConcept.category)) {
562
+ this.conceptIndex.set(newConcept.category, /* @__PURE__ */ new Set());
563
+ }
564
+ this.conceptIndex.get(newConcept.category).add(newConcept.id);
565
+ }
566
+ this.emit("conceptAdded", newConcept);
567
+ return newConcept;
568
+ }
569
+ /**
570
+ * Create a relationship between concepts
571
+ */
572
+ addRelationship(sourceId, targetId, type, metadata) {
573
+ const source = this.concepts.get(sourceId);
574
+ const target = this.concepts.get(targetId);
575
+ if (!source || !target) {
576
+ return null;
577
+ }
578
+ const existing = Array.from(this.relationships.values()).find(
579
+ (r) => r.sourceId === sourceId && r.targetId === targetId && r.type === type
580
+ );
581
+ if (existing) {
582
+ existing.weight = Math.min(existing.weight + 0.1, 1);
583
+ return existing;
584
+ }
585
+ const relationship = {
586
+ id: this.generateId("rel"),
587
+ sourceId,
588
+ targetId,
589
+ type,
590
+ weight: metadata?.weight ?? 0.5,
591
+ metadata: metadata ?? {},
592
+ createdAt: Date.now()
593
+ };
594
+ this.relationships.set(relationship.id, relationship);
595
+ this.emit("relationshipAdded", relationship);
596
+ return relationship;
597
+ }
598
+ /**
599
+ * Get a concept by ID or name
600
+ */
601
+ getConcept(idOrName) {
602
+ const byId = this.concepts.get(idOrName);
603
+ if (byId) return byId;
604
+ return Array.from(this.concepts.values()).find(
605
+ (c) => c.name.toLowerCase() === idOrName.toLowerCase()
606
+ );
607
+ }
608
+ /**
609
+ * Get concepts by category
610
+ */
611
+ getConceptsByCategory(category) {
612
+ const ids = this.conceptIndex.get(category);
613
+ if (!ids) return [];
614
+ return Array.from(ids).map((id) => this.concepts.get(id)).filter((c) => c !== void 0);
615
+ }
616
+ /**
617
+ * Get relationships for a concept
618
+ */
619
+ getRelationships(conceptId, direction = "both") {
620
+ return Array.from(this.relationships.values()).filter((r) => {
621
+ if (direction === "outgoing") return r.sourceId === conceptId;
622
+ if (direction === "incoming") return r.targetId === conceptId;
623
+ return r.sourceId === conceptId || r.targetId === conceptId;
624
+ });
625
+ }
626
+ /**
627
+ * Get related concepts
628
+ */
629
+ getRelatedConcepts(conceptId, relationshipType, maxDepth = 1) {
630
+ const results = /* @__PURE__ */ new Map();
631
+ const visited = /* @__PURE__ */ new Set();
632
+ const queue = [{ id: conceptId, path: [], distance: 0 }];
633
+ while (queue.length > 0) {
634
+ const current = queue.shift();
635
+ if (visited.has(current.id) || current.distance > maxDepth) continue;
636
+ visited.add(current.id);
637
+ const relationships = this.getRelationships(current.id);
638
+ for (const rel of relationships) {
639
+ if (relationshipType && rel.type !== relationshipType) continue;
640
+ const relatedId = rel.sourceId === current.id ? rel.targetId : rel.sourceId;
641
+ if (relatedId === conceptId) continue;
642
+ const concept = this.concepts.get(relatedId);
643
+ if (!concept) continue;
644
+ const newPath = [...current.path, rel];
645
+ const existing = results.get(relatedId);
646
+ if (!existing || newPath.length < existing.path.length) {
647
+ results.set(relatedId, {
648
+ concept,
649
+ path: newPath,
650
+ distance: current.distance + 1
651
+ });
652
+ if (current.distance + 1 < maxDepth) {
653
+ queue.push({
654
+ id: relatedId,
655
+ path: newPath,
656
+ distance: current.distance + 1
657
+ });
658
+ }
659
+ }
660
+ }
661
+ }
662
+ return Array.from(results.values()).sort((a, b) => a.distance - b.distance);
663
+ }
664
+ /**
665
+ * Query facts
666
+ */
667
+ async queryFacts(query, limit = 10) {
668
+ const { entries } = await this.store.query({
669
+ query,
670
+ types: ["fact"],
671
+ limit
672
+ });
673
+ return entries;
674
+ }
675
+ /**
676
+ * Search facts by embedding
677
+ */
678
+ async searchFacts(embedding, options) {
679
+ const results = await this.store.search(embedding, {
680
+ topK: options?.topK ?? 10,
681
+ minScore: options?.minScore ?? this.config.minConfidence
682
+ });
683
+ return results.filter((r) => r.entry.type === "fact");
684
+ }
685
+ /**
686
+ * Find path between two concepts
687
+ */
688
+ findPath(sourceId, targetId, maxDepth = 5) {
689
+ const visited = /* @__PURE__ */ new Set();
690
+ const queue = [
691
+ { id: sourceId, path: [] }
692
+ ];
693
+ while (queue.length > 0) {
694
+ const current = queue.shift();
695
+ if (visited.has(current.id) || current.path.length >= maxDepth) continue;
696
+ visited.add(current.id);
697
+ const relationships = this.getRelationships(current.id);
698
+ for (const rel of relationships) {
699
+ const nextId = rel.sourceId === current.id ? rel.targetId : rel.sourceId;
700
+ const newPath = [...current.path, rel];
701
+ if (nextId === targetId) {
702
+ return newPath;
703
+ }
704
+ queue.push({ id: nextId, path: newPath });
705
+ }
706
+ }
707
+ return null;
708
+ }
709
+ /**
710
+ * Infer new relationships based on existing ones
711
+ */
712
+ inferRelationships() {
713
+ if (!this.config.enableInference) return [];
714
+ const inferred = [];
715
+ const transitiveTypes = ["is_a", "part_of", "related_to"];
716
+ for (const rel1 of this.relationships.values()) {
717
+ if (!transitiveTypes.includes(rel1.type)) continue;
718
+ for (const rel2 of this.relationships.values()) {
719
+ if (rel1.id === rel2.id) continue;
720
+ if (rel1.type !== rel2.type) continue;
721
+ if (rel1.targetId !== rel2.sourceId) continue;
722
+ const exists = Array.from(this.relationships.values()).some(
723
+ (r) => r.sourceId === rel1.sourceId && r.targetId === rel2.targetId && r.type === rel1.type
724
+ );
725
+ if (!exists) {
726
+ const newRel = this.addRelationship(
727
+ rel1.sourceId,
728
+ rel2.targetId,
729
+ rel1.type,
730
+ {
731
+ inferred: true,
732
+ weight: rel1.weight * rel2.weight * 0.8
733
+ }
734
+ );
735
+ if (newRel) {
736
+ inferred.push(newRel);
737
+ }
738
+ }
739
+ }
740
+ }
741
+ return inferred;
742
+ }
743
+ /**
744
+ * Get conflicting facts
745
+ */
746
+ async findConflicts(fact) {
747
+ const { entries } = await this.store.query({
748
+ query: fact.content,
749
+ types: ["fact"],
750
+ limit: 20
751
+ });
752
+ return entries.filter((e) => {
753
+ if (e.id === fact.id) return false;
754
+ const contentLower = e.content.toLowerCase();
755
+ const factLower = fact.content.toLowerCase();
756
+ const negationPatterns = ["not ", "isn't", "aren't", "never", "false"];
757
+ const hasNegation = negationPatterns.some(
758
+ (p) => contentLower.includes(p) && !factLower.includes(p) || !contentLower.includes(p) && factLower.includes(p)
759
+ );
760
+ return hasNegation;
761
+ });
762
+ }
763
+ /**
764
+ * Resolve conflict between facts
765
+ */
766
+ resolveConflict(fact1, fact2) {
767
+ switch (this.config.conflictResolution) {
768
+ case "newest":
769
+ return fact1.timestamp > fact2.timestamp ? fact1 : fact2;
770
+ case "highest-confidence": {
771
+ const conf1 = fact1.metadata.confidence ?? 0.5;
772
+ const conf2 = fact2.metadata.confidence ?? 0.5;
773
+ return conf1 > conf2 ? fact1 : fact2;
774
+ }
775
+ case "merge":
776
+ return {
777
+ ...fact1,
778
+ content: `${fact1.content} (Note: conflicting fact exists: ${fact2.content})`,
779
+ metadata: {
780
+ ...fact1.metadata,
781
+ hasConflict: true,
782
+ conflictingFactId: fact2.id
783
+ }
784
+ };
785
+ default:
786
+ return fact1;
787
+ }
788
+ }
789
+ /**
790
+ * Export knowledge graph
791
+ */
792
+ exportGraph() {
793
+ return {
794
+ concepts: Array.from(this.concepts.values()),
795
+ relationships: Array.from(this.relationships.values())
796
+ };
797
+ }
798
+ /**
799
+ * Import knowledge graph
800
+ */
801
+ importGraph(data) {
802
+ for (const concept of data.concepts) {
803
+ this.concepts.set(concept.id, concept);
804
+ if (concept.category) {
805
+ if (!this.conceptIndex.has(concept.category)) {
806
+ this.conceptIndex.set(concept.category, /* @__PURE__ */ new Set());
807
+ }
808
+ this.conceptIndex.get(concept.category).add(concept.id);
809
+ }
810
+ }
811
+ for (const relationship of data.relationships) {
812
+ this.relationships.set(relationship.id, relationship);
813
+ }
814
+ }
815
+ /**
816
+ * Generate unique ID
817
+ */
818
+ generateId(prefix) {
819
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
820
+ }
821
+ /**
822
+ * Get statistics
823
+ */
824
+ getStats() {
825
+ return {
826
+ conceptCount: this.concepts.size,
827
+ relationshipCount: this.relationships.size,
828
+ categoryCount: this.conceptIndex.size,
829
+ avgRelationshipsPerConcept: this.concepts.size > 0 ? this.relationships.size / this.concepts.size : 0
830
+ };
831
+ }
832
+ };
833
+ function createSemanticMemory(store, config) {
834
+ return new SemanticMemory(store, config);
835
+ }
836
+
837
+ // src/structures/LongTermMemory.ts
838
+ import { EventEmitter as EventEmitter4 } from "eventemitter3";
839
+ var LongTermMemory = class extends EventEmitter4 {
840
+ store;
841
+ config;
842
+ consolidatedMemories = /* @__PURE__ */ new Map();
843
+ constructor(store, config = {}) {
844
+ super();
845
+ this.store = store;
846
+ this.config = {
847
+ store: config.store ?? store,
848
+ indexing: config.indexing ?? "hybrid",
849
+ compression: config.compression ?? true,
850
+ compressionThreshold: config.compressionThreshold ?? 1e3,
851
+ consolidationThreshold: config.consolidationThreshold ?? 100,
852
+ compressionRatio: config.compressionRatio ?? 5,
853
+ minImportance: config.minImportance ?? 0.3,
854
+ retentionPeriod: config.retentionPeriod ?? 365 * 24 * 60 * 60 * 1e3,
855
+ // 1 year
856
+ autoConsolidate: config.autoConsolidate ?? true,
857
+ maxStorageSize: config.maxStorageSize ?? 1e5
858
+ };
859
+ }
860
+ /**
861
+ * Store a memory in long-term storage
862
+ */
863
+ async store_memory(entry) {
864
+ if (entry.importance < this.config.minImportance) {
865
+ return entry.id;
866
+ }
867
+ const entryWithExpiry = {
868
+ ...entry,
869
+ expiresAt: entry.expiresAt ?? Date.now() + this.config.retentionPeriod
870
+ };
871
+ await this.store.add(entryWithExpiry);
872
+ if (this.config.autoConsolidate) {
873
+ const count = await this.store.count();
874
+ if (count >= this.config.consolidationThreshold) {
875
+ await this.consolidateOldMemories();
876
+ }
877
+ }
878
+ return entry.id;
879
+ }
880
+ /**
881
+ * Retrieve memories by similarity
882
+ */
883
+ async retrieve(embedding, options) {
884
+ const results = await this.store.search(embedding, {
885
+ topK: options?.topK ?? 10,
886
+ minScore: options?.minScore ?? 0.5
887
+ });
888
+ this.emit(
889
+ "retrieved",
890
+ results.map((r) => r.entry)
891
+ );
892
+ return results;
893
+ }
894
+ /**
895
+ * Query long-term memories
896
+ */
897
+ async query(options) {
898
+ const { entries } = await this.store.query({
899
+ ...options,
900
+ types: options.types,
901
+ minImportance: options.minImportance ?? this.config.minImportance,
902
+ limit: options.limit ?? 100
903
+ });
904
+ return entries;
905
+ }
906
+ /**
907
+ * Consolidate old memories into summaries
908
+ */
909
+ async consolidateOldMemories(options) {
910
+ const olderThan = options?.olderThan ?? Date.now() - 7 * 24 * 60 * 60 * 1e3;
911
+ const { entries } = await this.store.query({
912
+ endTime: olderThan,
913
+ limit: 1e3
914
+ });
915
+ if (entries.length < this.config.compressionRatio) {
916
+ return [];
917
+ }
918
+ const groups = this.groupMemories(entries, options?.groupBy ?? "day");
919
+ const consolidated = [];
920
+ for (const [key, groupEntries] of groups) {
921
+ if (groupEntries.length < 2) continue;
922
+ const summary = options?.summarizeFn ? await options.summarizeFn(groupEntries) : this.generateSimpleSummary(groupEntries);
923
+ const consolidatedMemory = {
924
+ id: this.generateId(),
925
+ summary,
926
+ sourceIds: groupEntries.map((e) => e.id),
927
+ sourceCount: groupEntries.length,
928
+ avgImportance: groupEntries.reduce((sum, e) => sum + e.importance, 0) / groupEntries.length,
929
+ timeRange: {
930
+ start: Math.min(...groupEntries.map((e) => e.timestamp)),
931
+ end: Math.max(...groupEntries.map((e) => e.timestamp))
932
+ },
933
+ metadata: {
934
+ groupKey: key
935
+ },
936
+ createdAt: Date.now()
937
+ };
938
+ await this.store.add({
939
+ id: consolidatedMemory.id,
940
+ content: consolidatedMemory.summary,
941
+ type: "summary",
942
+ importance: consolidatedMemory.avgImportance,
943
+ metadata: {
944
+ source: "system",
945
+ confidence: 0.85,
946
+ consolidated: true,
947
+ sourceCount: consolidatedMemory.sourceCount,
948
+ ...consolidatedMemory.metadata
949
+ },
950
+ timestamp: consolidatedMemory.timeRange.start,
951
+ accessCount: 0,
952
+ createdAt: consolidatedMemory.createdAt,
953
+ updatedAt: consolidatedMemory.createdAt
954
+ });
955
+ this.consolidatedMemories.set(consolidatedMemory.id, consolidatedMemory);
956
+ for (const entry of groupEntries) {
957
+ await this.store.delete(entry.id);
958
+ }
959
+ consolidated.push(consolidatedMemory);
960
+ this.emit("consolidated", consolidatedMemory, groupEntries);
961
+ }
962
+ return consolidated;
963
+ }
964
+ /**
965
+ * Prune low-importance memories
966
+ */
967
+ async prune(options) {
968
+ const { entries } = await this.store.query({
969
+ limit: 1e4
970
+ });
971
+ const now = Date.now();
972
+ const maxAge = options?.maxAge ?? this.config.retentionPeriod;
973
+ const maxImportance = options?.maxImportance ?? this.config.minImportance;
974
+ const toPrune = entries.filter((entry) => {
975
+ const age = now - entry.timestamp;
976
+ const isOld = age > maxAge;
977
+ const isLowImportance = entry.importance < maxImportance;
978
+ const isLowAccess = entry.accessCount < 2;
979
+ return isOld || isLowImportance && isLowAccess;
980
+ });
981
+ const limit = options?.maxCount ?? toPrune.length;
982
+ const pruneList = toPrune.slice(0, limit);
983
+ for (const entry of pruneList) {
984
+ await this.store.delete(entry.id);
985
+ }
986
+ this.emit("pruned", pruneList);
987
+ return pruneList.length;
988
+ }
989
+ /**
990
+ * Reinforce a memory (increase importance)
991
+ */
992
+ async reinforce(id, amount = 0.1) {
993
+ const entry = await this.store.get(id);
994
+ if (!entry) return false;
995
+ const newImportance = Math.min(entry.importance + amount, 1);
996
+ return this.store.update(id, {
997
+ importance: newImportance,
998
+ expiresAt: Date.now() + this.config.retentionPeriod
999
+ // Reset expiration
1000
+ });
1001
+ }
1002
+ /**
1003
+ * Get consolidated memory by ID
1004
+ */
1005
+ getConsolidated(id) {
1006
+ return this.consolidatedMemories.get(id);
1007
+ }
1008
+ /**
1009
+ * Get all consolidated memories
1010
+ */
1011
+ getAllConsolidated() {
1012
+ return Array.from(this.consolidatedMemories.values());
1013
+ }
1014
+ /**
1015
+ * Expand a consolidated memory to show original summaries
1016
+ */
1017
+ expandConsolidated(id) {
1018
+ const consolidated = this.consolidatedMemories.get(id);
1019
+ if (!consolidated) return null;
1020
+ return [
1021
+ {
1022
+ id: consolidated.id,
1023
+ content: consolidated.summary,
1024
+ type: "summary",
1025
+ importance: consolidated.avgImportance,
1026
+ metadata: {
1027
+ source: "system",
1028
+ confidence: 0.85,
1029
+ expanded: true,
1030
+ sourceCount: consolidated.sourceCount
1031
+ },
1032
+ timestamp: consolidated.timeRange.start,
1033
+ accessCount: 0,
1034
+ createdAt: consolidated.createdAt,
1035
+ updatedAt: consolidated.createdAt
1036
+ }
1037
+ ];
1038
+ }
1039
+ /**
1040
+ * Group memories by time or topic
1041
+ */
1042
+ groupMemories(entries, groupBy) {
1043
+ const groups = /* @__PURE__ */ new Map();
1044
+ for (const entry of entries) {
1045
+ let key;
1046
+ switch (groupBy) {
1047
+ case "day":
1048
+ key = new Date(entry.timestamp).toISOString().split("T")[0];
1049
+ break;
1050
+ case "week": {
1051
+ const date = new Date(entry.timestamp);
1052
+ const weekStart = new Date(date);
1053
+ weekStart.setDate(date.getDate() - date.getDay());
1054
+ key = weekStart.toISOString().split("T")[0];
1055
+ break;
1056
+ }
1057
+ case "topic":
1058
+ key = String(entry.metadata.topic ?? entry.type);
1059
+ break;
1060
+ default:
1061
+ key = "default";
1062
+ }
1063
+ if (!groups.has(key)) {
1064
+ groups.set(key, []);
1065
+ }
1066
+ groups.get(key).push(entry);
1067
+ }
1068
+ return groups;
1069
+ }
1070
+ /**
1071
+ * Generate simple summary from entries
1072
+ */
1073
+ generateSimpleSummary(entries) {
1074
+ const types = new Set(entries.map((e) => e.type));
1075
+ const snippets = entries.slice(0, 5).map((e) => e.content.slice(0, 50)).join("; ");
1076
+ return `Consolidated ${entries.length} memories (${Array.from(types).join(", ")}): ${snippets}...`;
1077
+ }
1078
+ /**
1079
+ * Generate unique ID
1080
+ */
1081
+ generateId() {
1082
+ return `ltm-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
1083
+ }
1084
+ /**
1085
+ * Get statistics
1086
+ */
1087
+ async getStats() {
1088
+ const count = await this.store.count();
1089
+ const { entries } = await this.store.query({ limit: 1e3 });
1090
+ const avgImportance = entries.length > 0 ? entries.reduce((sum, e) => sum + e.importance, 0) / entries.length : 0;
1091
+ const timestamps = entries.map((e) => e.timestamp);
1092
+ return {
1093
+ totalMemories: count,
1094
+ consolidatedCount: this.consolidatedMemories.size,
1095
+ avgImportance,
1096
+ oldestMemory: timestamps.length > 0 ? Math.min(...timestamps) : null,
1097
+ newestMemory: timestamps.length > 0 ? Math.max(...timestamps) : null
1098
+ };
1099
+ }
1100
+ };
1101
+ function createLongTermMemory(store, config) {
1102
+ return new LongTermMemory(store, config);
1103
+ }
1104
+
1105
+ // src/structures/HierarchicalMemory.ts
1106
+ import { EventEmitter as EventEmitter5 } from "eventemitter3";
1107
+ var HierarchicalMemory = class extends EventEmitter5 {
1108
+ workingMemory;
1109
+ episodicMemory;
1110
+ semanticMemory;
1111
+ longTermMemory;
1112
+ config;
1113
+ embedFn;
1114
+ constructor(stores, config = {}) {
1115
+ super();
1116
+ this.config = {
1117
+ working: config.working ?? {},
1118
+ episodic: config.episodic ?? {},
1119
+ semantic: config.semantic ?? {},
1120
+ longTerm: config.longTerm ?? {},
1121
+ routing: config.routing ?? {},
1122
+ routingStrategy: config.routingStrategy ?? "auto",
1123
+ consolidationInterval: config.consolidationInterval ?? 60 * 60 * 1e3,
1124
+ // 1 hour
1125
+ workingMemorySize: config.workingMemorySize ?? 20,
1126
+ promotionThreshold: config.promotionThreshold ?? 0.7
1127
+ };
1128
+ this.workingMemory = new WorkingMemory(stores.working, {
1129
+ maxSize: this.config.workingMemorySize
1130
+ });
1131
+ this.episodicMemory = new EpisodicMemory(stores.episodic);
1132
+ this.semanticMemory = new SemanticMemory(stores.semantic);
1133
+ this.longTermMemory = new LongTermMemory(stores.longterm);
1134
+ this.setupEventHandlers();
1135
+ }
1136
+ /**
1137
+ * Set embedding function for semantic search
1138
+ */
1139
+ setEmbeddingFunction(fn) {
1140
+ this.embedFn = fn;
1141
+ }
1142
+ /**
1143
+ * Add a memory entry with automatic routing
1144
+ */
1145
+ async add(entry) {
1146
+ const layer = this.route(entry);
1147
+ switch (layer) {
1148
+ case "working":
1149
+ await this.workingMemory.add(entry);
1150
+ break;
1151
+ case "episodic":
1152
+ await this.episodicMemory.recordEvent(entry);
1153
+ break;
1154
+ case "semantic":
1155
+ await this.semanticMemory.learnFact(entry.content, entry.metadata);
1156
+ break;
1157
+ case "longterm":
1158
+ await this.longTermMemory.store_memory(entry);
1159
+ break;
1160
+ }
1161
+ this.emit("routed", entry, layer);
1162
+ return layer;
1163
+ }
1164
+ /**
1165
+ * Add to a specific layer
1166
+ */
1167
+ async addToLayer(entry, layer) {
1168
+ switch (layer) {
1169
+ case "working":
1170
+ await this.workingMemory.add(entry);
1171
+ break;
1172
+ case "episodic":
1173
+ await this.episodicMemory.recordEvent(entry);
1174
+ break;
1175
+ case "semantic":
1176
+ await this.semanticMemory.learnFact(entry.content, entry.metadata);
1177
+ break;
1178
+ case "longterm":
1179
+ await this.longTermMemory.store_memory(entry);
1180
+ break;
1181
+ }
1182
+ this.emit("routed", entry, layer);
1183
+ }
1184
+ /**
1185
+ * Search across all memory layers
1186
+ */
1187
+ async search(query, options) {
1188
+ const layers = options?.layers ?? [
1189
+ "working",
1190
+ "episodic",
1191
+ "semantic",
1192
+ "longterm"
1193
+ ];
1194
+ const topK = options?.topK ?? 10;
1195
+ const minScore = options?.minScore ?? 0.3;
1196
+ const results = [];
1197
+ let embedding;
1198
+ if (this.embedFn) {
1199
+ embedding = await this.embedFn(query);
1200
+ }
1201
+ for (const layer of layers) {
1202
+ const layerResults = await this.searchLayer(layer, query, embedding, {
1203
+ topK,
1204
+ minScore
1205
+ });
1206
+ results.push(...layerResults);
1207
+ }
1208
+ results.sort((a, b) => b.score - a.score);
1209
+ const seen = /* @__PURE__ */ new Set();
1210
+ const deduplicated = results.filter((r) => {
1211
+ if (seen.has(r.entry.id)) return false;
1212
+ seen.add(r.entry.id);
1213
+ return true;
1214
+ });
1215
+ this.emit(
1216
+ "retrieved",
1217
+ deduplicated.map((r) => r.entry),
1218
+ layers
1219
+ );
1220
+ return deduplicated.slice(0, topK);
1221
+ }
1222
+ /**
1223
+ * Search a specific layer
1224
+ */
1225
+ async searchLayer(layer, query, embedding, options) {
1226
+ const topK = options?.topK ?? 10;
1227
+ switch (layer) {
1228
+ case "working": {
1229
+ const context = this.workingMemory.getContextWithAttention();
1230
+ return context.filter(
1231
+ (c) => c.entry.content.toLowerCase().includes(query.toLowerCase())
1232
+ ).map((c) => ({
1233
+ entry: c.entry,
1234
+ score: c.total,
1235
+ layer: "working"
1236
+ })).slice(0, topK);
1237
+ }
1238
+ case "episodic": {
1239
+ const episodes = await this.episodicMemory.recall({ limit: topK * 2 });
1240
+ return episodes.filter((e) => e.content.toLowerCase().includes(query.toLowerCase())).map((entry) => ({
1241
+ entry,
1242
+ score: this.calculateTextScore(query, entry.content),
1243
+ layer: "episodic"
1244
+ })).slice(0, topK);
1245
+ }
1246
+ case "semantic": {
1247
+ const facts = await this.semanticMemory.queryFacts(query, topK);
1248
+ return facts.map((entry) => ({
1249
+ entry,
1250
+ score: this.calculateTextScore(query, entry.content),
1251
+ layer: "semantic"
1252
+ }));
1253
+ }
1254
+ case "longterm": {
1255
+ if (embedding) {
1256
+ const results = await this.longTermMemory.retrieve(embedding, {
1257
+ topK
1258
+ });
1259
+ return results.map((r) => ({
1260
+ entry: r.entry,
1261
+ score: r.score,
1262
+ layer: "longterm"
1263
+ }));
1264
+ }
1265
+ const entries = await this.longTermMemory.query({ query, limit: topK });
1266
+ return entries.map((entry) => ({
1267
+ entry,
1268
+ score: this.calculateTextScore(query, entry.content),
1269
+ layer: "longterm"
1270
+ }));
1271
+ }
1272
+ default:
1273
+ return [];
1274
+ }
1275
+ }
1276
+ /**
1277
+ * Get context from working memory
1278
+ */
1279
+ getWorkingContext() {
1280
+ return this.workingMemory.getContext();
1281
+ }
1282
+ /**
1283
+ * Promote a memory to a higher layer
1284
+ */
1285
+ async promote(entryId, from, to) {
1286
+ let entry = null;
1287
+ switch (from) {
1288
+ case "working":
1289
+ entry = this.workingMemory.getContext().find((e) => e.id === entryId) ?? null;
1290
+ break;
1291
+ case "episodic": {
1292
+ const events = await this.episodicMemory.recall({ limit: 1e3 });
1293
+ entry = events.find((e) => e.id === entryId) ?? null;
1294
+ break;
1295
+ }
1296
+ }
1297
+ if (!entry) return false;
1298
+ await this.addToLayer(entry, to);
1299
+ this.emit("promoted", entry, from, to);
1300
+ return true;
1301
+ }
1302
+ /**
1303
+ * Consolidate memories from one layer to another
1304
+ */
1305
+ async consolidate(from, to) {
1306
+ let count = 0;
1307
+ switch (from) {
1308
+ case "working":
1309
+ if (to === "episodic" || to === "longterm") {
1310
+ const context = this.workingMemory.getContext();
1311
+ const important = context.filter(
1312
+ (e) => e.importance >= this.config.promotionThreshold
1313
+ );
1314
+ for (const entry of important) {
1315
+ await this.addToLayer(entry, to);
1316
+ count++;
1317
+ }
1318
+ }
1319
+ break;
1320
+ case "episodic":
1321
+ if (to === "longterm") {
1322
+ const episodes = this.episodicMemory.getRecentEpisodes(10);
1323
+ for (const episode of episodes) {
1324
+ if (episode.summary) {
1325
+ await this.longTermMemory.store_memory({
1326
+ id: `consolidated-${episode.id}`,
1327
+ content: episode.summary,
1328
+ type: "summary",
1329
+ importance: 0.7,
1330
+ metadata: {
1331
+ source: "system",
1332
+ confidence: 0.85,
1333
+ sourceEpisodeId: episode.id,
1334
+ eventCount: episode.events.length
1335
+ },
1336
+ timestamp: episode.startTime,
1337
+ accessCount: 0,
1338
+ createdAt: Date.now(),
1339
+ updatedAt: Date.now()
1340
+ });
1341
+ count++;
1342
+ }
1343
+ }
1344
+ }
1345
+ break;
1346
+ }
1347
+ if (count > 0) {
1348
+ this.emit("consolidated", from, to, count);
1349
+ }
1350
+ return count;
1351
+ }
1352
+ /**
1353
+ * Route a memory entry to the appropriate layer
1354
+ */
1355
+ route(entry) {
1356
+ if (this.config.routingStrategy === "manual") {
1357
+ return entry.metadata.targetLayer ?? "working";
1358
+ }
1359
+ const decision = this.makeRoutingDecision(entry);
1360
+ return decision.layer;
1361
+ }
1362
+ /**
1363
+ * Make routing decision
1364
+ */
1365
+ makeRoutingDecision(entry) {
1366
+ if (entry.type === "event") {
1367
+ return {
1368
+ layer: "episodic",
1369
+ confidence: 0.9,
1370
+ reason: "Event type maps to episodic memory"
1371
+ };
1372
+ }
1373
+ if (entry.type === "fact") {
1374
+ return {
1375
+ layer: "semantic",
1376
+ confidence: 0.9,
1377
+ reason: "Fact type maps to semantic memory"
1378
+ };
1379
+ }
1380
+ if (entry.importance >= 0.8) {
1381
+ return {
1382
+ layer: "longterm",
1383
+ confidence: 0.8,
1384
+ reason: "High importance memory"
1385
+ };
1386
+ }
1387
+ if (entry.type === "context") {
1388
+ return {
1389
+ layer: "working",
1390
+ confidence: 0.9,
1391
+ reason: "Context type maps to working memory"
1392
+ };
1393
+ }
1394
+ if (entry.type === "summary") {
1395
+ return {
1396
+ layer: "longterm",
1397
+ confidence: 0.8,
1398
+ reason: "Summary type maps to long-term memory"
1399
+ };
1400
+ }
1401
+ return {
1402
+ layer: "working",
1403
+ confidence: 0.6,
1404
+ reason: "Default routing to working memory"
1405
+ };
1406
+ }
1407
+ /**
1408
+ * Calculate text similarity score
1409
+ */
1410
+ calculateTextScore(query, content) {
1411
+ const queryWords = query.toLowerCase().split(/\s+/);
1412
+ const contentLower = content.toLowerCase();
1413
+ let matches = 0;
1414
+ for (const word of queryWords) {
1415
+ if (word.length > 2 && contentLower.includes(word)) {
1416
+ matches++;
1417
+ }
1418
+ }
1419
+ return queryWords.length > 0 ? matches / queryWords.length : 0;
1420
+ }
1421
+ /**
1422
+ * Set up event handlers between layers
1423
+ */
1424
+ setupEventHandlers() {
1425
+ this.workingMemory.on("overflow", (evicted) => {
1426
+ void (async () => {
1427
+ for (const entry of evicted) {
1428
+ if (entry.importance >= this.config.promotionThreshold) {
1429
+ await this.longTermMemory.store_memory(entry);
1430
+ }
1431
+ }
1432
+ })();
1433
+ });
1434
+ this.episodicMemory.on("episodeEnd", (episode) => {
1435
+ void (async () => {
1436
+ if (episode.summary && episode.events.length >= 5) {
1437
+ await this.longTermMemory.store_memory({
1438
+ id: `episode-${episode.id}`,
1439
+ content: episode.summary,
1440
+ type: "summary",
1441
+ importance: 0.6,
1442
+ metadata: {
1443
+ source: "system",
1444
+ confidence: 0.8,
1445
+ episodeId: episode.id,
1446
+ eventCount: episode.events.length
1447
+ },
1448
+ timestamp: episode.startTime,
1449
+ accessCount: 0,
1450
+ createdAt: Date.now(),
1451
+ updatedAt: Date.now()
1452
+ });
1453
+ }
1454
+ })();
1455
+ });
1456
+ }
1457
+ /**
1458
+ * Get statistics for all layers
1459
+ */
1460
+ async getStats() {
1461
+ return {
1462
+ working: this.workingMemory.getSummary(),
1463
+ episodic: this.episodicMemory.getStats(),
1464
+ semantic: this.semanticMemory.getStats(),
1465
+ longterm: await this.longTermMemory.getStats()
1466
+ };
1467
+ }
1468
+ /**
1469
+ * Access individual memory layers
1470
+ */
1471
+ get layers() {
1472
+ return {
1473
+ working: this.workingMemory,
1474
+ episodic: this.episodicMemory,
1475
+ semantic: this.semanticMemory,
1476
+ longterm: this.longTermMemory
1477
+ };
1478
+ }
1479
+ };
1480
+ function createHierarchicalMemory(stores, config) {
1481
+ return new HierarchicalMemory(stores, config);
1482
+ }
1483
+
1484
+ export {
1485
+ WorkingMemory,
1486
+ createWorkingMemory,
1487
+ EpisodicMemory,
1488
+ createEpisodicMemory,
1489
+ SemanticMemory,
1490
+ createSemanticMemory,
1491
+ LongTermMemory,
1492
+ createLongTermMemory,
1493
+ HierarchicalMemory,
1494
+ createHierarchicalMemory
1495
+ };