@rlabs-inc/memory 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -30
- package/dist/index.js +803 -179
- package/dist/index.mjs +803 -179
- package/dist/server/index.js +36774 -2643
- package/dist/server/index.mjs +1034 -185
- package/package.json +3 -2
- package/skills/memory-management.md +686 -0
- package/src/cli/commands/migrate.ts +423 -0
- package/src/cli/commands/serve.ts +88 -0
- package/src/cli/index.ts +21 -0
- package/src/core/curator.ts +151 -17
- package/src/core/engine.ts +159 -11
- package/src/core/manager.ts +484 -0
- package/src/core/retrieval.ts +547 -420
- package/src/core/store.ts +383 -8
- package/src/server/index.ts +108 -8
- package/src/types/memory.ts +142 -0
- package/src/types/schema.ts +80 -7
- package/src/utils/logger.ts +310 -46
package/src/core/store.ts
CHANGED
|
@@ -6,21 +6,25 @@
|
|
|
6
6
|
import { createDatabase, type Database, type PersistentCollection } from '@rlabs-inc/fsdb'
|
|
7
7
|
import { homedir } from 'os'
|
|
8
8
|
import { join } from 'path'
|
|
9
|
-
import
|
|
10
|
-
CuratedMemory,
|
|
11
|
-
StoredMemory,
|
|
12
|
-
SessionSummary,
|
|
13
|
-
ProjectSnapshot,
|
|
9
|
+
import {
|
|
10
|
+
type CuratedMemory,
|
|
11
|
+
type StoredMemory,
|
|
12
|
+
type SessionSummary,
|
|
13
|
+
type ProjectSnapshot,
|
|
14
|
+
V2_DEFAULTS,
|
|
14
15
|
} from '../types/memory.ts'
|
|
15
16
|
import {
|
|
16
17
|
memorySchema,
|
|
17
18
|
sessionSummarySchema,
|
|
18
19
|
projectSnapshotSchema,
|
|
19
20
|
sessionSchema,
|
|
21
|
+
managementLogSchema,
|
|
22
|
+
MEMORY_SCHEMA_VERSION,
|
|
20
23
|
type MemorySchema,
|
|
21
24
|
type SessionSummarySchema,
|
|
22
25
|
type ProjectSnapshotSchema,
|
|
23
26
|
type SessionSchema,
|
|
27
|
+
type ManagementLogSchema,
|
|
24
28
|
} from '../types/schema.ts'
|
|
25
29
|
|
|
26
30
|
/**
|
|
@@ -34,6 +38,13 @@ export interface StoreConfig {
|
|
|
34
38
|
*/
|
|
35
39
|
basePath?: string
|
|
36
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Path for global memories (shared across all projects)
|
|
43
|
+
* Default: ~/.local/share/memory/global
|
|
44
|
+
* Global memories are ALWAYS stored centrally, even in local mode
|
|
45
|
+
*/
|
|
46
|
+
globalPath?: string
|
|
47
|
+
|
|
37
48
|
/**
|
|
38
49
|
* Whether to watch for file changes
|
|
39
50
|
* Default: false
|
|
@@ -52,20 +63,343 @@ interface ProjectDB {
|
|
|
52
63
|
sessions: PersistentCollection<typeof sessionSchema>
|
|
53
64
|
}
|
|
54
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Global database with collections (shared across all projects)
|
|
68
|
+
*/
|
|
69
|
+
interface GlobalDB {
|
|
70
|
+
db: Database
|
|
71
|
+
memories: PersistentCollection<typeof memorySchema>
|
|
72
|
+
managementLogs: PersistentCollection<typeof managementLogSchema>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Personal primer structure
|
|
77
|
+
*/
|
|
78
|
+
export interface PersonalPrimer {
|
|
79
|
+
content: string
|
|
80
|
+
updated: number // timestamp
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Special ID for the personal primer record
|
|
85
|
+
*/
|
|
86
|
+
const PERSONAL_PRIMER_ID = 'personal-primer'
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Default central path for global memories
|
|
90
|
+
* Global memories are ALWAYS stored here, even in local mode
|
|
91
|
+
*/
|
|
92
|
+
const DEFAULT_GLOBAL_PATH = join(homedir(), '.local', 'share', 'memory', 'global')
|
|
93
|
+
|
|
55
94
|
/**
|
|
56
95
|
* MemoryStore - Manages per-project fsDB instances
|
|
57
96
|
*/
|
|
58
97
|
export class MemoryStore {
|
|
59
98
|
private _config: Required<StoreConfig>
|
|
60
99
|
private _projects = new Map<string, ProjectDB>()
|
|
100
|
+
private _global: GlobalDB | null = null
|
|
61
101
|
|
|
62
102
|
constructor(config: StoreConfig = {}) {
|
|
63
103
|
this._config = {
|
|
64
104
|
basePath: config.basePath ?? join(homedir(), '.local', 'share', 'memory'),
|
|
105
|
+
// Global path is ALWAYS central, never local
|
|
106
|
+
globalPath: config.globalPath ?? DEFAULT_GLOBAL_PATH,
|
|
65
107
|
watchFiles: config.watchFiles ?? false,
|
|
66
108
|
}
|
|
67
109
|
}
|
|
68
110
|
|
|
111
|
+
// ================================================================
|
|
112
|
+
// GLOBAL DATABASE OPERATIONS
|
|
113
|
+
// ================================================================
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get or create the global database (shared across all projects)
|
|
117
|
+
* Global is ALWAYS in central location, even when using local mode for projects
|
|
118
|
+
*/
|
|
119
|
+
async getGlobal(): Promise<GlobalDB> {
|
|
120
|
+
if (this._global) {
|
|
121
|
+
return this._global
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Use the configured global path (always central)
|
|
125
|
+
const globalPath = this._config.globalPath
|
|
126
|
+
console.log(`🌐 [DEBUG] Initializing global database at ${globalPath}`)
|
|
127
|
+
|
|
128
|
+
const db = createDatabase({
|
|
129
|
+
name: 'global',
|
|
130
|
+
basePath: globalPath,
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// Global memories collection (personal, philosophy, preferences, general breakthroughs)
|
|
134
|
+
const memories = db.collection('memories', {
|
|
135
|
+
schema: memorySchema,
|
|
136
|
+
contentColumn: 'content',
|
|
137
|
+
autoSave: true,
|
|
138
|
+
watchFiles: this._config.watchFiles,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Management log collection (tracks management agent activity)
|
|
142
|
+
const managementLogs = db.collection('management-logs', {
|
|
143
|
+
schema: managementLogSchema,
|
|
144
|
+
contentColumn: 'summary',
|
|
145
|
+
autoSave: true,
|
|
146
|
+
watchFiles: this._config.watchFiles,
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
await Promise.all([memories.load(), managementLogs.load()])
|
|
150
|
+
|
|
151
|
+
this._global = { db, memories, managementLogs }
|
|
152
|
+
return this._global
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get all global memories
|
|
157
|
+
*/
|
|
158
|
+
async getGlobalMemories(): Promise<StoredMemory[]> {
|
|
159
|
+
const { memories } = await this.getGlobal()
|
|
160
|
+
|
|
161
|
+
return memories.all().map(record => ({
|
|
162
|
+
id: record.id,
|
|
163
|
+
content: record.content,
|
|
164
|
+
reasoning: record.reasoning,
|
|
165
|
+
importance_weight: record.importance_weight,
|
|
166
|
+
confidence_score: record.confidence_score,
|
|
167
|
+
context_type: record.context_type as StoredMemory['context_type'],
|
|
168
|
+
temporal_relevance: record.temporal_relevance as StoredMemory['temporal_relevance'],
|
|
169
|
+
knowledge_domain: record.knowledge_domain as StoredMemory['knowledge_domain'],
|
|
170
|
+
emotional_resonance: record.emotional_resonance as StoredMemory['emotional_resonance'],
|
|
171
|
+
action_required: record.action_required,
|
|
172
|
+
problem_solution_pair: record.problem_solution_pair,
|
|
173
|
+
semantic_tags: record.semantic_tags,
|
|
174
|
+
trigger_phrases: record.trigger_phrases,
|
|
175
|
+
question_types: record.question_types,
|
|
176
|
+
session_id: record.session_id,
|
|
177
|
+
project_id: 'global',
|
|
178
|
+
embedding: record.embedding ?? undefined,
|
|
179
|
+
created_at: record.created,
|
|
180
|
+
updated_at: record.updated,
|
|
181
|
+
stale: record.stale,
|
|
182
|
+
}))
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Store a global memory (personal, philosophy, preference, etc.)
|
|
187
|
+
* Global memories are ALWAYS scope: 'global' and have their own type defaults
|
|
188
|
+
*/
|
|
189
|
+
async storeGlobalMemory(
|
|
190
|
+
sessionId: string,
|
|
191
|
+
memory: CuratedMemory,
|
|
192
|
+
embedding?: Float32Array | number[],
|
|
193
|
+
sessionNumber?: number
|
|
194
|
+
): Promise<string> {
|
|
195
|
+
const { memories } = await this.getGlobal()
|
|
196
|
+
|
|
197
|
+
// Get type-specific defaults (personal, philosophy, preference tend to be eternal)
|
|
198
|
+
const contextType = memory.context_type ?? 'personal'
|
|
199
|
+
const typeDefaults = V2_DEFAULTS.typeDefaults[contextType] ?? V2_DEFAULTS.typeDefaults.personal
|
|
200
|
+
|
|
201
|
+
const id = memories.insert({
|
|
202
|
+
// Core v1 fields
|
|
203
|
+
content: memory.content,
|
|
204
|
+
reasoning: memory.reasoning,
|
|
205
|
+
importance_weight: memory.importance_weight,
|
|
206
|
+
confidence_score: memory.confidence_score,
|
|
207
|
+
context_type: memory.context_type,
|
|
208
|
+
temporal_relevance: memory.temporal_relevance,
|
|
209
|
+
knowledge_domain: memory.knowledge_domain,
|
|
210
|
+
emotional_resonance: memory.emotional_resonance,
|
|
211
|
+
action_required: memory.action_required,
|
|
212
|
+
problem_solution_pair: memory.problem_solution_pair,
|
|
213
|
+
semantic_tags: memory.semantic_tags,
|
|
214
|
+
trigger_phrases: memory.trigger_phrases,
|
|
215
|
+
question_types: memory.question_types,
|
|
216
|
+
session_id: sessionId,
|
|
217
|
+
project_id: 'global',
|
|
218
|
+
embedding: embedding
|
|
219
|
+
? (embedding instanceof Float32Array ? embedding : new Float32Array(embedding))
|
|
220
|
+
: null,
|
|
221
|
+
|
|
222
|
+
// v2 lifecycle fields - global memories are always scope: 'global'
|
|
223
|
+
status: V2_DEFAULTS.fallback.status,
|
|
224
|
+
scope: 'global', // Always global for global memories
|
|
225
|
+
temporal_class: memory.temporal_class ?? typeDefaults?.temporal_class ?? 'eternal',
|
|
226
|
+
fade_rate: typeDefaults?.fade_rate ?? 0, // Global memories typically don't fade
|
|
227
|
+
session_created: sessionNumber ?? 0,
|
|
228
|
+
session_updated: sessionNumber ?? 0,
|
|
229
|
+
sessions_since_surfaced: 0,
|
|
230
|
+
domain: memory.domain ?? null,
|
|
231
|
+
feature: memory.feature ?? null,
|
|
232
|
+
related_files: memory.related_files ?? [],
|
|
233
|
+
awaiting_implementation: memory.awaiting_implementation ?? false,
|
|
234
|
+
awaiting_decision: memory.awaiting_decision ?? false,
|
|
235
|
+
retrieval_weight: memory.importance_weight,
|
|
236
|
+
exclude_from_retrieval: false,
|
|
237
|
+
schema_version: MEMORY_SCHEMA_VERSION,
|
|
238
|
+
|
|
239
|
+
// Initialize empty relationship fields
|
|
240
|
+
supersedes: null,
|
|
241
|
+
superseded_by: null,
|
|
242
|
+
related_to: [],
|
|
243
|
+
resolves: [],
|
|
244
|
+
resolved_by: null,
|
|
245
|
+
parent_id: null,
|
|
246
|
+
child_ids: [],
|
|
247
|
+
blocked_by: null,
|
|
248
|
+
blocks: [],
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
return id
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ================================================================
|
|
255
|
+
// PERSONAL PRIMER OPERATIONS
|
|
256
|
+
// ================================================================
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get the personal primer content
|
|
260
|
+
* Returns null if no primer exists yet (grows organically with personal memories)
|
|
261
|
+
*/
|
|
262
|
+
async getPersonalPrimer(): Promise<PersonalPrimer | null> {
|
|
263
|
+
const { memories } = await this.getGlobal()
|
|
264
|
+
|
|
265
|
+
// Personal primer is stored as a special memory record
|
|
266
|
+
const primer = memories.get(PERSONAL_PRIMER_ID)
|
|
267
|
+
if (!primer) {
|
|
268
|
+
return null
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
content: primer.content,
|
|
273
|
+
updated: primer.updated,
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Update the personal primer
|
|
279
|
+
* Creates it if it doesn't exist
|
|
280
|
+
*/
|
|
281
|
+
async setPersonalPrimer(content: string): Promise<void> {
|
|
282
|
+
const { memories } = await this.getGlobal()
|
|
283
|
+
|
|
284
|
+
const existing = memories.get(PERSONAL_PRIMER_ID)
|
|
285
|
+
if (existing) {
|
|
286
|
+
memories.update(PERSONAL_PRIMER_ID, { content })
|
|
287
|
+
} else {
|
|
288
|
+
// Create the primer as a special memory record
|
|
289
|
+
memories.insert({
|
|
290
|
+
id: PERSONAL_PRIMER_ID,
|
|
291
|
+
content,
|
|
292
|
+
reasoning: 'Personal relationship context injected at session start',
|
|
293
|
+
importance_weight: 1.0,
|
|
294
|
+
confidence_score: 1.0,
|
|
295
|
+
context_type: 'personal',
|
|
296
|
+
temporal_relevance: 'persistent',
|
|
297
|
+
knowledge_domain: 'personal',
|
|
298
|
+
emotional_resonance: 'neutral',
|
|
299
|
+
action_required: false,
|
|
300
|
+
problem_solution_pair: false,
|
|
301
|
+
semantic_tags: ['personal', 'primer', 'relationship'],
|
|
302
|
+
trigger_phrases: [],
|
|
303
|
+
question_types: [],
|
|
304
|
+
session_id: 'system',
|
|
305
|
+
project_id: 'global',
|
|
306
|
+
embedding: null,
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Check if personal memories are enabled
|
|
313
|
+
* For now, always returns true. Later can be configured.
|
|
314
|
+
*/
|
|
315
|
+
isPersonalMemoriesEnabled(): boolean {
|
|
316
|
+
// TODO: Read from config file if needed
|
|
317
|
+
return true
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ================================================================
|
|
321
|
+
// MANAGEMENT LOG OPERATIONS
|
|
322
|
+
// ================================================================
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Store a management log entry
|
|
326
|
+
* Stores complete data with no truncation
|
|
327
|
+
*/
|
|
328
|
+
async storeManagementLog(entry: {
|
|
329
|
+
projectId: string
|
|
330
|
+
sessionNumber: number
|
|
331
|
+
memoriesProcessed: number
|
|
332
|
+
supersededCount: number
|
|
333
|
+
resolvedCount: number
|
|
334
|
+
linkedCount: number
|
|
335
|
+
primerUpdated: boolean
|
|
336
|
+
success: boolean
|
|
337
|
+
durationMs: number
|
|
338
|
+
summary: string
|
|
339
|
+
fullReport?: string
|
|
340
|
+
error?: string
|
|
341
|
+
details?: Record<string, any>
|
|
342
|
+
}): Promise<string> {
|
|
343
|
+
const { managementLogs } = await this.getGlobal()
|
|
344
|
+
|
|
345
|
+
const id = managementLogs.insert({
|
|
346
|
+
project_id: entry.projectId,
|
|
347
|
+
session_number: entry.sessionNumber,
|
|
348
|
+
memories_processed: entry.memoriesProcessed,
|
|
349
|
+
superseded_count: entry.supersededCount,
|
|
350
|
+
resolved_count: entry.resolvedCount,
|
|
351
|
+
linked_count: entry.linkedCount,
|
|
352
|
+
primer_updated: entry.primerUpdated,
|
|
353
|
+
success: entry.success,
|
|
354
|
+
duration_ms: entry.durationMs,
|
|
355
|
+
summary: entry.summary,
|
|
356
|
+
full_report: entry.fullReport ?? '',
|
|
357
|
+
error: entry.error ?? '',
|
|
358
|
+
details: entry.details ? JSON.stringify(entry.details) : '',
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
return id
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Get recent management logs
|
|
366
|
+
*/
|
|
367
|
+
async getManagementLogs(limit: number = 10): Promise<Array<{
|
|
368
|
+
id: string
|
|
369
|
+
projectId: string
|
|
370
|
+
sessionNumber: number
|
|
371
|
+
memoriesProcessed: number
|
|
372
|
+
supersededCount: number
|
|
373
|
+
resolvedCount: number
|
|
374
|
+
linkedCount: number
|
|
375
|
+
primerUpdated: boolean
|
|
376
|
+
success: boolean
|
|
377
|
+
durationMs: number
|
|
378
|
+
summary: string
|
|
379
|
+
createdAt: number
|
|
380
|
+
}>> {
|
|
381
|
+
const { managementLogs } = await this.getGlobal()
|
|
382
|
+
|
|
383
|
+
return managementLogs
|
|
384
|
+
.all()
|
|
385
|
+
.sort((a, b) => b.created - a.created)
|
|
386
|
+
.slice(0, limit)
|
|
387
|
+
.map(record => ({
|
|
388
|
+
id: record.id,
|
|
389
|
+
projectId: record.project_id,
|
|
390
|
+
sessionNumber: record.session_number,
|
|
391
|
+
memoriesProcessed: record.memories_processed,
|
|
392
|
+
supersededCount: record.superseded_count,
|
|
393
|
+
resolvedCount: record.resolved_count,
|
|
394
|
+
linkedCount: record.linked_count,
|
|
395
|
+
primerUpdated: record.primer_updated,
|
|
396
|
+
success: record.success,
|
|
397
|
+
durationMs: record.duration_ms,
|
|
398
|
+
summary: record.summary,
|
|
399
|
+
createdAt: record.created,
|
|
400
|
+
}))
|
|
401
|
+
}
|
|
402
|
+
|
|
69
403
|
/**
|
|
70
404
|
* Get or create database for a project
|
|
71
405
|
*/
|
|
@@ -131,17 +465,23 @@ export class MemoryStore {
|
|
|
131
465
|
// ================================================================
|
|
132
466
|
|
|
133
467
|
/**
|
|
134
|
-
* Store a curated memory
|
|
468
|
+
* Store a curated memory with v2 lifecycle fields
|
|
135
469
|
*/
|
|
136
470
|
async storeMemory(
|
|
137
471
|
projectId: string,
|
|
138
472
|
sessionId: string,
|
|
139
473
|
memory: CuratedMemory,
|
|
140
|
-
embedding?: Float32Array | number[]
|
|
474
|
+
embedding?: Float32Array | number[],
|
|
475
|
+
sessionNumber?: number
|
|
141
476
|
): Promise<string> {
|
|
142
477
|
const { memories } = await this.getProject(projectId)
|
|
143
478
|
|
|
479
|
+
// Get type-specific defaults
|
|
480
|
+
const contextType = memory.context_type ?? 'general'
|
|
481
|
+
const typeDefaults = V2_DEFAULTS.typeDefaults[contextType] ?? V2_DEFAULTS.typeDefaults.technical
|
|
482
|
+
|
|
144
483
|
const id = memories.insert({
|
|
484
|
+
// Core v1 fields
|
|
145
485
|
content: memory.content,
|
|
146
486
|
reasoning: memory.reasoning,
|
|
147
487
|
importance_weight: memory.importance_weight,
|
|
@@ -160,6 +500,34 @@ export class MemoryStore {
|
|
|
160
500
|
embedding: embedding
|
|
161
501
|
? (embedding instanceof Float32Array ? embedding : new Float32Array(embedding))
|
|
162
502
|
: null,
|
|
503
|
+
|
|
504
|
+
// v2 lifecycle fields - use curator-provided values or smart defaults
|
|
505
|
+
status: V2_DEFAULTS.fallback.status,
|
|
506
|
+
scope: memory.scope ?? typeDefaults?.scope ?? V2_DEFAULTS.fallback.scope,
|
|
507
|
+
temporal_class: memory.temporal_class ?? typeDefaults?.temporal_class ?? V2_DEFAULTS.fallback.temporal_class,
|
|
508
|
+
fade_rate: typeDefaults?.fade_rate ?? V2_DEFAULTS.fallback.fade_rate,
|
|
509
|
+
session_created: sessionNumber ?? 0,
|
|
510
|
+
session_updated: sessionNumber ?? 0,
|
|
511
|
+
sessions_since_surfaced: 0,
|
|
512
|
+
domain: memory.domain ?? null,
|
|
513
|
+
feature: memory.feature ?? null,
|
|
514
|
+
related_files: memory.related_files ?? [],
|
|
515
|
+
awaiting_implementation: memory.awaiting_implementation ?? false,
|
|
516
|
+
awaiting_decision: memory.awaiting_decision ?? false,
|
|
517
|
+
retrieval_weight: memory.importance_weight, // Start with importance as retrieval weight
|
|
518
|
+
exclude_from_retrieval: false,
|
|
519
|
+
schema_version: MEMORY_SCHEMA_VERSION,
|
|
520
|
+
|
|
521
|
+
// Initialize empty relationship fields
|
|
522
|
+
supersedes: null,
|
|
523
|
+
superseded_by: null,
|
|
524
|
+
related_to: [],
|
|
525
|
+
resolves: [],
|
|
526
|
+
resolved_by: null,
|
|
527
|
+
parent_id: null,
|
|
528
|
+
child_ids: [],
|
|
529
|
+
blocked_by: null,
|
|
530
|
+
blocks: [],
|
|
163
531
|
})
|
|
164
532
|
|
|
165
533
|
return id
|
|
@@ -498,13 +866,20 @@ export class MemoryStore {
|
|
|
498
866
|
}
|
|
499
867
|
|
|
500
868
|
/**
|
|
501
|
-
* Close all project databases
|
|
869
|
+
* Close all project databases (including global)
|
|
502
870
|
*/
|
|
503
871
|
close(): void {
|
|
872
|
+
// Close project databases
|
|
504
873
|
for (const projectDB of this._projects.values()) {
|
|
505
874
|
projectDB.db.close()
|
|
506
875
|
}
|
|
507
876
|
this._projects.clear()
|
|
877
|
+
|
|
878
|
+
// Close global database
|
|
879
|
+
if (this._global) {
|
|
880
|
+
this._global.db.close()
|
|
881
|
+
this._global = null
|
|
882
|
+
}
|
|
508
883
|
}
|
|
509
884
|
}
|
|
510
885
|
|
package/src/server/index.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { MemoryEngine, createEngine, type EngineConfig } from '../core/engine.ts'
|
|
7
7
|
import { Curator, createCurator, type CuratorConfig } from '../core/curator.ts'
|
|
8
8
|
import { EmbeddingGenerator, createEmbeddings } from '../core/embeddings.ts'
|
|
9
|
+
import { Manager, createManager, type ManagerConfig } from '../core/manager.ts'
|
|
9
10
|
import type { CurationTrigger } from '../types/memory.ts'
|
|
10
11
|
import { logger } from '../utils/logger.ts'
|
|
11
12
|
|
|
@@ -16,6 +17,21 @@ export interface ServerConfig extends EngineConfig {
|
|
|
16
17
|
port?: number
|
|
17
18
|
host?: string
|
|
18
19
|
curator?: CuratorConfig
|
|
20
|
+
manager?: ManagerConfig
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Enable the management agent (convenience shortcut)
|
|
24
|
+
* When false, memories are stored but not organized/linked asynchronously
|
|
25
|
+
* Default: true
|
|
26
|
+
*/
|
|
27
|
+
managerEnabled?: boolean
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Enable personal memories extraction and storage (convenience shortcut)
|
|
31
|
+
* When false, personal/relationship memories are not extracted or surfaced
|
|
32
|
+
* Default: true
|
|
33
|
+
*/
|
|
34
|
+
personalMemoriesEnabled?: boolean
|
|
19
35
|
}
|
|
20
36
|
|
|
21
37
|
/**
|
|
@@ -55,6 +71,9 @@ export async function createServer(config: ServerConfig = {}) {
|
|
|
55
71
|
port = 8765,
|
|
56
72
|
host = 'localhost',
|
|
57
73
|
curator: curatorConfig,
|
|
74
|
+
manager: managerConfig,
|
|
75
|
+
managerEnabled,
|
|
76
|
+
personalMemoriesEnabled,
|
|
58
77
|
...engineConfig
|
|
59
78
|
} = config
|
|
60
79
|
|
|
@@ -63,12 +82,27 @@ export async function createServer(config: ServerConfig = {}) {
|
|
|
63
82
|
logger.info('Initializing embedding model (this may take a moment on first run)...')
|
|
64
83
|
await embeddings.initialize()
|
|
65
84
|
|
|
66
|
-
//
|
|
85
|
+
// Merge top-level convenience options with nested configs
|
|
86
|
+
const finalCuratorConfig: CuratorConfig = {
|
|
87
|
+
...curatorConfig,
|
|
88
|
+
// Top-level option overrides nested if set
|
|
89
|
+
personalMemoriesEnabled: personalMemoriesEnabled ?? curatorConfig?.personalMemoriesEnabled,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const finalManagerConfig: ManagerConfig = {
|
|
93
|
+
...managerConfig,
|
|
94
|
+
// Top-level option overrides nested if set
|
|
95
|
+
enabled: managerEnabled ?? managerConfig?.enabled,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Create engine with embedder and personalMemoriesEnabled flag
|
|
67
99
|
const engine = createEngine({
|
|
68
100
|
...engineConfig,
|
|
69
101
|
embedder: embeddings.createEmbedder(),
|
|
102
|
+
personalMemoriesEnabled: finalCuratorConfig.personalMemoriesEnabled,
|
|
70
103
|
})
|
|
71
|
-
const curator = createCurator(
|
|
104
|
+
const curator = createCurator(finalCuratorConfig)
|
|
105
|
+
const manager = createManager(finalManagerConfig)
|
|
72
106
|
|
|
73
107
|
const server = Bun.serve({
|
|
74
108
|
port,
|
|
@@ -180,6 +214,61 @@ export async function createServer(config: ServerConfig = {}) {
|
|
|
180
214
|
|
|
181
215
|
logger.logCurationComplete(result.memories.length, result.session_summary)
|
|
182
216
|
logger.logCuratedMemories(result.memories)
|
|
217
|
+
|
|
218
|
+
// Fire and forget - spawn management agent to update/organize memories
|
|
219
|
+
const sessionNumber = await engine.getSessionNumber(body.project_id, body.project_path)
|
|
220
|
+
// Get resolved storage paths from engine config (runtime values, not hardcoded)
|
|
221
|
+
const storagePaths = engine.getStoragePaths(body.project_id, body.project_path)
|
|
222
|
+
|
|
223
|
+
setImmediate(async () => {
|
|
224
|
+
try {
|
|
225
|
+
logger.logManagementStart(result.memories.length)
|
|
226
|
+
const startTime = Date.now()
|
|
227
|
+
|
|
228
|
+
const managementResult = await manager.manageWithCLI(
|
|
229
|
+
body.project_id,
|
|
230
|
+
sessionNumber,
|
|
231
|
+
result,
|
|
232
|
+
storagePaths
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
logger.logManagementComplete({
|
|
236
|
+
success: managementResult.success,
|
|
237
|
+
superseded: managementResult.superseded || undefined,
|
|
238
|
+
resolved: managementResult.resolved || undefined,
|
|
239
|
+
linked: managementResult.linked || undefined,
|
|
240
|
+
filesRead: managementResult.filesRead || undefined,
|
|
241
|
+
filesWritten: managementResult.filesWritten || undefined,
|
|
242
|
+
primerUpdated: managementResult.primerUpdated,
|
|
243
|
+
actions: managementResult.actions,
|
|
244
|
+
fullReport: managementResult.fullReport,
|
|
245
|
+
error: managementResult.error,
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
// Store management log with full action history (no truncation)
|
|
249
|
+
await engine.storeManagementLog({
|
|
250
|
+
projectId: body.project_id,
|
|
251
|
+
sessionNumber,
|
|
252
|
+
memoriesProcessed: result.memories.length,
|
|
253
|
+
supersededCount: managementResult.superseded,
|
|
254
|
+
resolvedCount: managementResult.resolved,
|
|
255
|
+
linkedCount: managementResult.linked,
|
|
256
|
+
primerUpdated: managementResult.primerUpdated,
|
|
257
|
+
success: managementResult.success,
|
|
258
|
+
durationMs: Date.now() - startTime,
|
|
259
|
+
summary: managementResult.summary,
|
|
260
|
+
fullReport: managementResult.fullReport,
|
|
261
|
+
error: managementResult.error,
|
|
262
|
+
details: {
|
|
263
|
+
actions: managementResult.actions,
|
|
264
|
+
filesRead: managementResult.filesRead,
|
|
265
|
+
filesWritten: managementResult.filesWritten,
|
|
266
|
+
},
|
|
267
|
+
})
|
|
268
|
+
} catch (error) {
|
|
269
|
+
logger.error(`Management failed: ${error}`)
|
|
270
|
+
}
|
|
271
|
+
})
|
|
183
272
|
} else {
|
|
184
273
|
logger.logCurationComplete(0)
|
|
185
274
|
}
|
|
@@ -228,6 +317,7 @@ export async function createServer(config: ServerConfig = {}) {
|
|
|
228
317
|
server,
|
|
229
318
|
engine,
|
|
230
319
|
curator,
|
|
320
|
+
manager,
|
|
231
321
|
embeddings,
|
|
232
322
|
stop: () => server.stop(),
|
|
233
323
|
}
|
|
@@ -240,10 +330,20 @@ if (import.meta.main) {
|
|
|
240
330
|
const storageMode = (process.env.MEMORY_STORAGE_MODE ?? 'central') as 'central' | 'local'
|
|
241
331
|
const apiKey = process.env.ANTHROPIC_API_KEY
|
|
242
332
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
333
|
+
// Feature toggles (default: enabled)
|
|
334
|
+
// Set to '0' or 'false' to disable
|
|
335
|
+
const managerEnabled = !['0', 'false'].includes(process.env.MEMORY_MANAGER_ENABLED?.toLowerCase() ?? '')
|
|
336
|
+
const personalMemoriesEnabled = !['0', 'false'].includes(process.env.MEMORY_PERSONAL_ENABLED?.toLowerCase() ?? '')
|
|
337
|
+
|
|
338
|
+
// Wrap in async IIFE for CJS compatibility
|
|
339
|
+
void (async () => {
|
|
340
|
+
await createServer({
|
|
341
|
+
port,
|
|
342
|
+
host,
|
|
343
|
+
storageMode,
|
|
344
|
+
managerEnabled,
|
|
345
|
+
personalMemoriesEnabled,
|
|
346
|
+
curator: { apiKey },
|
|
347
|
+
})
|
|
348
|
+
})()
|
|
249
349
|
}
|