@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.
- package/LICENSE +21 -0
- package/README.md +450 -0
- package/dist/chunk-GACX3FPR.js +1402 -0
- package/dist/chunk-M44NB53O.js +1226 -0
- package/dist/chunk-MQDWBPZU.js +972 -0
- package/dist/chunk-TPC7MYWK.js +1495 -0
- package/dist/chunk-XD2CQGSD.js +1540 -0
- package/dist/chunk-YI7RPDEV.js +1215 -0
- package/dist/core.types-lkxKv-bW.d.cts +242 -0
- package/dist/core.types-lkxKv-bW.d.ts +242 -0
- package/dist/debug/index.cjs +1248 -0
- package/dist/debug/index.d.cts +3 -0
- package/dist/debug/index.d.ts +3 -0
- package/dist/debug/index.js +20 -0
- package/dist/index-7SsAJ4et.d.ts +525 -0
- package/dist/index-BGxYqpFb.d.cts +601 -0
- package/dist/index-BX62efZu.d.ts +565 -0
- package/dist/index-Bbc3COw0.d.cts +748 -0
- package/dist/index-Bczz1Eyk.d.ts +637 -0
- package/dist/index-C7pEiT8L.d.cts +637 -0
- package/dist/index-CHetLTb0.d.ts +389 -0
- package/dist/index-CloeiFyx.d.ts +748 -0
- package/dist/index-DNOhq-3y.d.cts +525 -0
- package/dist/index-Da-M8FOV.d.cts +389 -0
- package/dist/index-Dy8UjRFz.d.cts +565 -0
- package/dist/index-aVcITW0B.d.ts +601 -0
- package/dist/index.cjs +8554 -0
- package/dist/index.d.cts +293 -0
- package/dist/index.d.ts +293 -0
- package/dist/index.js +742 -0
- package/dist/processing/index.cjs +1575 -0
- package/dist/processing/index.d.cts +2 -0
- package/dist/processing/index.d.ts +2 -0
- package/dist/processing/index.js +24 -0
- package/dist/retrieval/index.cjs +1262 -0
- package/dist/retrieval/index.d.cts +2 -0
- package/dist/retrieval/index.d.ts +2 -0
- package/dist/retrieval/index.js +26 -0
- package/dist/sharing/index.cjs +1003 -0
- package/dist/sharing/index.d.cts +3 -0
- package/dist/sharing/index.d.ts +3 -0
- package/dist/sharing/index.js +16 -0
- package/dist/stores/index.cjs +1445 -0
- package/dist/stores/index.d.cts +2 -0
- package/dist/stores/index.d.ts +2 -0
- package/dist/stores/index.js +20 -0
- package/dist/structures/index.cjs +1530 -0
- package/dist/structures/index.d.cts +3 -0
- package/dist/structures/index.d.ts +3 -0
- package/dist/structures/index.js +24 -0
- 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
|
+
};
|