@rlabs-inc/memory 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,505 @@
1
+ // ============================================================================
2
+ // MEMORY STORE - FatherStateDB-powered storage
3
+ // Per-project database management with reactive parallel arrays
4
+ // ============================================================================
5
+
6
+ import { createDatabase, type Database } from 'fatherstatedb'
7
+ import { homedir } from 'os'
8
+ import { join } from 'path'
9
+ import type {
10
+ CuratedMemory,
11
+ StoredMemory,
12
+ SessionSummary,
13
+ ProjectSnapshot,
14
+ } from '../types/memory.ts'
15
+ import {
16
+ memorySchema,
17
+ sessionSummarySchema,
18
+ projectSnapshotSchema,
19
+ sessionSchema,
20
+ type MemorySchema,
21
+ type SessionSummarySchema,
22
+ type ProjectSnapshotSchema,
23
+ type SessionSchema,
24
+ } from '../types/schema.ts'
25
+
26
+ /**
27
+ * Store configuration
28
+ */
29
+ export interface StoreConfig {
30
+ /**
31
+ * Base path for memory storage
32
+ * Default: ~/.local/share/memory
33
+ * Each project gets its own subdirectory
34
+ */
35
+ basePath?: string
36
+
37
+ /**
38
+ * Whether to watch for file changes
39
+ * Default: false
40
+ */
41
+ watchFiles?: boolean
42
+ }
43
+
44
+ /**
45
+ * Project database collection
46
+ */
47
+ interface ProjectDatabases {
48
+ memories: Database<typeof memorySchema>
49
+ summaries: Database<typeof sessionSummarySchema>
50
+ snapshots: Database<typeof projectSnapshotSchema>
51
+ sessions: Database<typeof sessionSchema>
52
+ }
53
+
54
+ /**
55
+ * MemoryStore - Manages per-project FatherStateDB instances
56
+ */
57
+ export class MemoryStore {
58
+ private _config: Required<StoreConfig>
59
+ private _projects = new Map<string, ProjectDatabases>()
60
+
61
+ constructor(config: StoreConfig = {}) {
62
+ this._config = {
63
+ basePath: config.basePath ?? join(homedir(), '.local', 'share', 'memory'),
64
+ watchFiles: config.watchFiles ?? false,
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Get or create databases for a project
70
+ */
71
+ async getProject(projectId: string): Promise<ProjectDatabases> {
72
+ if (this._projects.has(projectId)) {
73
+ console.log(`🔄 [DEBUG] Returning cached databases for ${projectId}`)
74
+ return this._projects.get(projectId)!
75
+ }
76
+
77
+ console.log(`🆕 [DEBUG] Creating NEW databases for ${projectId}`)
78
+ const projectPath = join(this._config.basePath, projectId)
79
+ console.log(` Path: ${projectPath}`)
80
+
81
+ // Create all databases for this project
82
+ const [memories, summaries, snapshots, sessions] = await Promise.all([
83
+ createDatabase({
84
+ path: join(projectPath, 'memories'),
85
+ schema: memorySchema,
86
+ contentColumn: 'content',
87
+ autoSave: true,
88
+ watchFiles: this._config.watchFiles,
89
+ }),
90
+ createDatabase({
91
+ path: join(projectPath, 'summaries'),
92
+ schema: sessionSummarySchema,
93
+ contentColumn: 'summary',
94
+ autoSave: true,
95
+ watchFiles: this._config.watchFiles,
96
+ }),
97
+ createDatabase({
98
+ path: join(projectPath, 'snapshots'),
99
+ schema: projectSnapshotSchema,
100
+ autoSave: true,
101
+ watchFiles: this._config.watchFiles,
102
+ }),
103
+ createDatabase({
104
+ path: join(projectPath, 'sessions'),
105
+ schema: sessionSchema,
106
+ autoSave: true,
107
+ watchFiles: this._config.watchFiles,
108
+ }),
109
+ ])
110
+
111
+ const dbs: ProjectDatabases = { memories, summaries, snapshots, sessions }
112
+ this._projects.set(projectId, dbs)
113
+
114
+ return dbs
115
+ }
116
+
117
+ // ================================================================
118
+ // MEMORY OPERATIONS
119
+ // ================================================================
120
+
121
+ /**
122
+ * Store a curated memory
123
+ */
124
+ async storeMemory(
125
+ projectId: string,
126
+ sessionId: string,
127
+ memory: CuratedMemory,
128
+ embedding?: Float32Array | number[]
129
+ ): Promise<string> {
130
+ const { memories } = await this.getProject(projectId)
131
+
132
+ const id = await memories.insert({
133
+ content: memory.content,
134
+ reasoning: memory.reasoning,
135
+ importance_weight: memory.importance_weight,
136
+ confidence_score: memory.confidence_score,
137
+ context_type: memory.context_type,
138
+ temporal_relevance: memory.temporal_relevance,
139
+ knowledge_domain: memory.knowledge_domain,
140
+ emotional_resonance: memory.emotional_resonance,
141
+ action_required: memory.action_required,
142
+ problem_solution_pair: memory.problem_solution_pair,
143
+ semantic_tags: memory.semantic_tags,
144
+ trigger_phrases: memory.trigger_phrases,
145
+ question_types: memory.question_types,
146
+ session_id: sessionId,
147
+ project_id: projectId,
148
+ embedding: embedding
149
+ ? (embedding instanceof Float32Array ? embedding : new Float32Array(embedding))
150
+ : undefined,
151
+ })
152
+
153
+ return id
154
+ }
155
+
156
+ /**
157
+ * Get all memories for a project
158
+ */
159
+ async getAllMemories(projectId: string): Promise<StoredMemory[]> {
160
+ const { memories } = await this.getProject(projectId)
161
+
162
+ return memories.all().map(record => ({
163
+ id: record.id,
164
+ content: record.content,
165
+ reasoning: record.reasoning,
166
+ importance_weight: record.importance_weight,
167
+ confidence_score: record.confidence_score,
168
+ context_type: record.context_type,
169
+ temporal_relevance: record.temporal_relevance,
170
+ knowledge_domain: record.knowledge_domain,
171
+ emotional_resonance: record.emotional_resonance,
172
+ action_required: record.action_required,
173
+ problem_solution_pair: record.problem_solution_pair,
174
+ semantic_tags: record.semantic_tags,
175
+ trigger_phrases: record.trigger_phrases,
176
+ question_types: record.question_types,
177
+ session_id: record.session_id,
178
+ project_id: record.project_id,
179
+ embedding: record.embedding,
180
+ created_at: record.created,
181
+ updated_at: record.updated,
182
+ stale: record.stale,
183
+ }))
184
+ }
185
+
186
+ /**
187
+ * Search memories by vector similarity
188
+ */
189
+ async searchMemories(
190
+ projectId: string,
191
+ queryEmbedding: Float32Array | number[],
192
+ options: { topK?: number; filter?: (m: StoredMemory) => boolean } = {}
193
+ ): Promise<StoredMemory[]> {
194
+ const { memories } = await this.getProject(projectId)
195
+ const { topK = 10, filter } = options
196
+
197
+ const results = memories.search('embedding', queryEmbedding, {
198
+ topK,
199
+ filter: filter ? (record) => {
200
+ // Convert record to StoredMemory format for filter
201
+ const mem: StoredMemory = {
202
+ id: record.id,
203
+ content: record.content,
204
+ reasoning: record.reasoning,
205
+ importance_weight: record.importance_weight,
206
+ confidence_score: record.confidence_score,
207
+ context_type: record.context_type,
208
+ temporal_relevance: record.temporal_relevance,
209
+ knowledge_domain: record.knowledge_domain,
210
+ emotional_resonance: record.emotional_resonance,
211
+ action_required: record.action_required,
212
+ problem_solution_pair: record.problem_solution_pair,
213
+ semantic_tags: record.semantic_tags,
214
+ trigger_phrases: record.trigger_phrases,
215
+ question_types: record.question_types,
216
+ session_id: record.session_id,
217
+ project_id: record.project_id,
218
+ created_at: 0,
219
+ updated_at: 0,
220
+ }
221
+ return filter(mem)
222
+ } : undefined,
223
+ })
224
+
225
+ return results.map(record => ({
226
+ id: record.id,
227
+ content: record.content,
228
+ reasoning: record.reasoning,
229
+ importance_weight: record.importance_weight,
230
+ confidence_score: record.confidence_score,
231
+ context_type: record.context_type,
232
+ temporal_relevance: record.temporal_relevance,
233
+ knowledge_domain: record.knowledge_domain,
234
+ emotional_resonance: record.emotional_resonance,
235
+ action_required: record.action_required,
236
+ problem_solution_pair: record.problem_solution_pair,
237
+ semantic_tags: record.semantic_tags,
238
+ trigger_phrases: record.trigger_phrases,
239
+ question_types: record.question_types,
240
+ session_id: record.session_id,
241
+ project_id: record.project_id,
242
+ embedding: record.embedding,
243
+ created_at: record.created,
244
+ updated_at: record.updated,
245
+ stale: record.stale,
246
+ }))
247
+ }
248
+
249
+ /**
250
+ * Update a memory's embedding
251
+ */
252
+ async setMemoryEmbedding(
253
+ projectId: string,
254
+ memoryId: string,
255
+ embedding: Float32Array | number[],
256
+ content: string
257
+ ): Promise<boolean> {
258
+ const { memories } = await this.getProject(projectId)
259
+ return memories.setEmbedding(memoryId, 'embedding', embedding, content)
260
+ }
261
+
262
+ /**
263
+ * Get stale memory IDs (embedding out of sync with content)
264
+ */
265
+ async getStaleMemoryIds(projectId: string): Promise<string[]> {
266
+ const { memories } = await this.getProject(projectId)
267
+ return memories.getStaleIds()
268
+ }
269
+
270
+ // ================================================================
271
+ // SESSION OPERATIONS
272
+ // ================================================================
273
+
274
+ /**
275
+ * Get or create a session
276
+ */
277
+ async getOrCreateSession(
278
+ projectId: string,
279
+ sessionId: string
280
+ ): Promise<{ isNew: boolean; messageCount: number; firstSessionCompleted: boolean }> {
281
+ const { sessions } = await this.getProject(projectId)
282
+
283
+ const existing = sessions.get(sessionId)
284
+ if (existing) {
285
+ return {
286
+ isNew: false,
287
+ messageCount: existing.message_count,
288
+ firstSessionCompleted: existing.first_session_completed,
289
+ }
290
+ }
291
+
292
+ // Check if this is the first session for the project
293
+ const allSessions = sessions.all()
294
+ const firstSessionCompleted = allSessions.some(s => s.first_session_completed)
295
+
296
+ await sessions.insert({
297
+ id: sessionId,
298
+ project_id: projectId,
299
+ message_count: 0,
300
+ first_session_completed: false,
301
+ last_active: Date.now(),
302
+ metadata: '{}',
303
+ })
304
+
305
+ return {
306
+ isNew: true,
307
+ messageCount: 0,
308
+ firstSessionCompleted,
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Increment message count for a session
314
+ */
315
+ async incrementMessageCount(projectId: string, sessionId: string): Promise<number> {
316
+ const { sessions } = await this.getProject(projectId)
317
+
318
+ const session = sessions.get(sessionId)
319
+ if (!session) {
320
+ throw new Error(`Session ${sessionId} not found`)
321
+ }
322
+
323
+ const newCount = session.message_count + 1
324
+ await sessions.update(sessionId, {
325
+ message_count: newCount,
326
+ last_active: Date.now(),
327
+ })
328
+
329
+ return newCount
330
+ }
331
+
332
+ /**
333
+ * Mark first session as completed
334
+ */
335
+ async markFirstSessionCompleted(projectId: string, sessionId: string): Promise<void> {
336
+ const { sessions } = await this.getProject(projectId)
337
+ await sessions.update(sessionId, { first_session_completed: true })
338
+ }
339
+
340
+ // ================================================================
341
+ // SUMMARY OPERATIONS
342
+ // ================================================================
343
+
344
+ /**
345
+ * Store a session summary
346
+ */
347
+ async storeSessionSummary(
348
+ projectId: string,
349
+ sessionId: string,
350
+ summary: string,
351
+ interactionTone: string = ''
352
+ ): Promise<string> {
353
+ const { summaries } = await this.getProject(projectId)
354
+
355
+ console.log(`📝 [DEBUG] Storing summary for ${projectId}:`)
356
+ console.log(` Summary length: ${summary.length} chars`)
357
+ console.log(` Summaries count before: ${summaries.all().length}`)
358
+
359
+ const id = await summaries.insert({
360
+ session_id: sessionId,
361
+ project_id: projectId,
362
+ summary,
363
+ interaction_tone: interactionTone,
364
+ })
365
+
366
+ console.log(` Summaries count after: ${summaries.all().length}`)
367
+ console.log(` Inserted ID: ${id}`)
368
+
369
+ return id
370
+ }
371
+
372
+ /**
373
+ * Get the latest session summary for a project
374
+ */
375
+ async getLatestSummary(projectId: string): Promise<SessionSummary | null> {
376
+ const { summaries } = await this.getProject(projectId)
377
+
378
+ console.log(`📖 [DEBUG] Getting latest summary for ${projectId}:`)
379
+ const all = summaries.all()
380
+ console.log(` Summaries found: ${all.length}`)
381
+
382
+ if (!all.length) {
383
+ console.log(` No summaries found!`)
384
+ return null
385
+ }
386
+
387
+ // Sort by created timestamp (most recent first)
388
+ all.sort((a, b) => b.created - a.created)
389
+
390
+ const latest = all[0]!
391
+ console.log(` Latest summary: ${latest.summary.slice(0, 50)}...`)
392
+
393
+ return {
394
+ id: latest.id,
395
+ session_id: latest.session_id,
396
+ project_id: latest.project_id,
397
+ summary: latest.summary,
398
+ interaction_tone: latest.interaction_tone,
399
+ created_at: latest.created,
400
+ }
401
+ }
402
+
403
+ // ================================================================
404
+ // SNAPSHOT OPERATIONS
405
+ // ================================================================
406
+
407
+ /**
408
+ * Store a project snapshot
409
+ */
410
+ async storeProjectSnapshot(
411
+ projectId: string,
412
+ sessionId: string,
413
+ snapshot: Omit<ProjectSnapshot, 'id' | 'session_id' | 'project_id' | 'created_at'>
414
+ ): Promise<string> {
415
+ const { snapshots } = await this.getProject(projectId)
416
+
417
+ return snapshots.insert({
418
+ session_id: sessionId,
419
+ project_id: projectId,
420
+ current_phase: snapshot.current_phase,
421
+ recent_achievements: snapshot.recent_achievements,
422
+ active_challenges: snapshot.active_challenges,
423
+ next_steps: snapshot.next_steps,
424
+ })
425
+ }
426
+
427
+ /**
428
+ * Get the latest project snapshot
429
+ */
430
+ async getLatestSnapshot(projectId: string): Promise<ProjectSnapshot | null> {
431
+ const { snapshots } = await this.getProject(projectId)
432
+
433
+ const all = snapshots.all()
434
+ if (!all.length) return null
435
+
436
+ // Sort by created timestamp (most recent first)
437
+ all.sort((a, b) => b.created - a.created)
438
+
439
+ const latest = all[0]!
440
+ return {
441
+ id: latest.id,
442
+ session_id: latest.session_id,
443
+ project_id: latest.project_id,
444
+ current_phase: latest.current_phase,
445
+ recent_achievements: latest.recent_achievements,
446
+ active_challenges: latest.active_challenges,
447
+ next_steps: latest.next_steps,
448
+ created_at: latest.created,
449
+ }
450
+ }
451
+
452
+ // ================================================================
453
+ // STATS & UTILITIES
454
+ // ================================================================
455
+
456
+ /**
457
+ * Get statistics for a project
458
+ */
459
+ async getProjectStats(projectId: string): Promise<{
460
+ totalMemories: number
461
+ totalSessions: number
462
+ staleMemories: number
463
+ latestSession: string | null
464
+ }> {
465
+ const { memories, sessions } = await this.getProject(projectId)
466
+
467
+ const allMemories = memories.all()
468
+ const allSessions = sessions.all()
469
+ const staleIds = memories.getStaleIds()
470
+
471
+ // Find latest session
472
+ let latestSession: string | null = null
473
+ if (allSessions.length) {
474
+ allSessions.sort((a, b) => b.last_active - a.last_active)
475
+ latestSession = allSessions[0]!.id
476
+ }
477
+
478
+ return {
479
+ totalMemories: allMemories.length,
480
+ totalSessions: allSessions.length,
481
+ staleMemories: staleIds.length,
482
+ latestSession,
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Close all project databases
488
+ */
489
+ close(): void {
490
+ for (const dbs of this._projects.values()) {
491
+ dbs.memories.close()
492
+ dbs.summaries.close()
493
+ dbs.snapshots.close()
494
+ dbs.sessions.close()
495
+ }
496
+ this._projects.clear()
497
+ }
498
+ }
499
+
500
+ /**
501
+ * Create a new memory store
502
+ */
503
+ export function createStore(config?: StoreConfig): MemoryStore {
504
+ return new MemoryStore(config)
505
+ }
package/src/index.ts ADDED
@@ -0,0 +1,58 @@
1
+ // ============================================================================
2
+ // @rlabs-inc/memory - AI Memory System
3
+ // Consciousness continuity through intelligent memory curation and retrieval
4
+ // ============================================================================
5
+
6
+ // Core exports
7
+ export {
8
+ MemoryEngine,
9
+ createEngine,
10
+ type EngineConfig,
11
+ type StorageMode,
12
+ type ContextRequest,
13
+ } from './core/engine.ts'
14
+
15
+ export {
16
+ MemoryStore,
17
+ createStore,
18
+ type StoreConfig,
19
+ } from './core/store.ts'
20
+
21
+ export {
22
+ SmartVectorRetrieval,
23
+ createRetrieval,
24
+ type SessionContext,
25
+ } from './core/retrieval.ts'
26
+
27
+ export {
28
+ Curator,
29
+ createCurator,
30
+ type CuratorConfig,
31
+ } from './core/curator.ts'
32
+
33
+ // Type exports
34
+ export type {
35
+ CuratedMemory,
36
+ StoredMemory,
37
+ SessionSummary,
38
+ ProjectSnapshot,
39
+ CurationResult,
40
+ RetrievalResult,
41
+ SessionPrimer,
42
+ ContextType,
43
+ TemporalRelevance,
44
+ EmotionalResonance,
45
+ KnowledgeDomain,
46
+ CurationTrigger,
47
+ } from './types/memory.ts'
48
+
49
+ export {
50
+ memorySchema,
51
+ sessionSummarySchema,
52
+ projectSnapshotSchema,
53
+ sessionSchema,
54
+ type MemorySchema,
55
+ type SessionSummarySchema,
56
+ type ProjectSnapshotSchema,
57
+ type SessionSchema,
58
+ } from './types/schema.ts'