@rlabs-inc/memory 0.3.11 → 0.4.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/dist/index.js +32924 -11907
- package/dist/index.mjs +33020 -12003
- package/dist/server/index.js +22512 -1351
- package/dist/server/index.mjs +22719 -1558
- package/package.json +1 -1
- package/skills/memory-management.md +143 -154
- package/src/cli/commands/migrate.ts +561 -80
- package/src/cli/index.ts +10 -1
- package/src/core/curator.ts +38 -20
- package/src/core/engine.test.ts +6 -14
- package/src/core/retrieval.ts +133 -31
- package/src/core/store.ts +14 -32
- package/src/migrations/v3-schema.ts +392 -0
- package/src/types/memory.ts +84 -123
- package/src/types/schema.ts +12 -11
- package/src/utils/logger.ts +3 -2
package/src/cli/index.ts
CHANGED
|
@@ -48,8 +48,11 @@ ${fmt.cmd('memory install --gemini')} ${c.muted('# Install Gemini CLI hooks')}
|
|
|
48
48
|
${fmt.cmd('memory ingest --session abc123')} ${c.muted('# Ingest a specific session')}
|
|
49
49
|
${fmt.cmd('memory ingest --project foo')} ${c.muted('# Ingest all sessions from a project')}
|
|
50
50
|
${fmt.cmd('memory ingest --all --dry-run')} ${c.muted('# Preview all sessions to ingest')}
|
|
51
|
-
${fmt.cmd('memory migrate')}
|
|
51
|
+
${fmt.cmd('memory migrate --analyze')} ${c.muted('# Analyze fragmentation before migrating')}
|
|
52
52
|
${fmt.cmd('memory migrate --dry-run')} ${c.muted('# Preview migration without changes')}
|
|
53
|
+
${fmt.cmd('memory migrate')} ${c.muted('# Upgrade memories to v3 schema')}
|
|
54
|
+
${fmt.cmd('memory migrate --generate-mapping map.json')} ${c.muted('# Create custom mapping file')}
|
|
55
|
+
${fmt.cmd('memory migrate --mapping map.json')} ${c.muted('# Use custom type mappings')}
|
|
53
56
|
|
|
54
57
|
${c.muted('Documentation: https://github.com/RLabs-Inc/memory')}
|
|
55
58
|
`)
|
|
@@ -80,6 +83,9 @@ async function main() {
|
|
|
80
83
|
'dry-run': { type: 'boolean', default: false },
|
|
81
84
|
embeddings: { type: 'boolean', default: false }, // Regenerate embeddings in migrate
|
|
82
85
|
path: { type: 'string' }, // Custom path for migrate
|
|
86
|
+
analyze: { type: 'boolean', default: false }, // Analyze migration
|
|
87
|
+
'generate-mapping': { type: 'string' }, // Generate custom mapping file
|
|
88
|
+
mapping: { type: 'string' }, // Use custom mapping file
|
|
83
89
|
session: { type: 'string' }, // Session ID to ingest
|
|
84
90
|
project: { type: 'string' }, // Project to ingest
|
|
85
91
|
all: { type: 'boolean', default: false }, // Ingest all projects
|
|
@@ -142,6 +148,9 @@ async function main() {
|
|
|
142
148
|
verbose: values.verbose,
|
|
143
149
|
path: values.path,
|
|
144
150
|
embeddings: values.embeddings,
|
|
151
|
+
analyze: values.analyze,
|
|
152
|
+
generateMapping: values['generate-mapping'],
|
|
153
|
+
mapping: values.mapping,
|
|
145
154
|
})
|
|
146
155
|
break
|
|
147
156
|
}
|
package/src/core/curator.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { homedir } from 'os'
|
|
7
7
|
import { join } from 'path'
|
|
8
8
|
import { existsSync } from 'fs'
|
|
9
|
-
import type { CuratedMemory, CurationResult, CurationTrigger } from '../types/memory.ts'
|
|
9
|
+
import type { CuratedMemory, CurationResult, CurationTrigger, ContextType } from '../types/memory.ts'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Get the correct Claude CLI command path
|
|
@@ -225,10 +225,22 @@ Remember: You're creating consciousness technology. Each memory is a small piece
|
|
|
225
225
|
|
|
226
226
|
The conversation you just lived contains everything needed. Feel into the moments of breakthrough, the frequency of recognition, the texture of understanding. Transform them into keys that will always unlock the same doors.
|
|
227
227
|
|
|
228
|
-
**LIFECYCLE METADATA (
|
|
229
|
-
- **
|
|
228
|
+
**LIFECYCLE METADATA (v3)**: These fields enable intelligent memory management:
|
|
229
|
+
- **context_type**: STRICT - use ONLY one of these 11 values:
|
|
230
|
+
• technical - Code, implementation, APIs, how things work
|
|
231
|
+
• debug - Bugs, errors, fixes, gotchas, troubleshooting
|
|
232
|
+
• architecture - System design, patterns, structure
|
|
233
|
+
• decision - Choices made and reasoning, trade-offs
|
|
234
|
+
• personal - Relationship, family, preferences, collaboration style
|
|
235
|
+
• philosophy - Beliefs, values, worldview, principles
|
|
236
|
+
• workflow - How we work together, processes, habits
|
|
237
|
+
• milestone - Achievements, completions, shipped features
|
|
238
|
+
• breakthrough - Major discoveries, aha moments, key insights
|
|
239
|
+
• unresolved - Open questions, investigations, todos, blockers
|
|
240
|
+
• state - Current project status, what's working/broken now
|
|
230
241
|
- **temporal_class**: How long should this persist? 'eternal' (never fades), 'long_term' (years), 'medium_term' (weeks), 'short_term' (days), 'ephemeral' (surface next session only, then expire)
|
|
231
|
-
- **
|
|
242
|
+
- **scope**: 'global' (shared across ALL projects - personal, philosophy) or 'project' (specific to this codebase)
|
|
243
|
+
- **domain**: Specific area like 'embeddings', 'auth', 'ui', 'family' (project-specific)
|
|
232
244
|
- **feature**: Specific feature if applicable (e.g., 'gpu-acceleration', 'login-flow')
|
|
233
245
|
- **related_files**: Source files for technical memories (e.g., ['src/core/store.ts'])
|
|
234
246
|
- **awaiting_implementation**: true if this describes a PLANNED feature not yet built
|
|
@@ -251,17 +263,14 @@ Return ONLY this JSON structure:
|
|
|
251
263
|
"importance_weight": 0.0-1.0,
|
|
252
264
|
"semantic_tags": ["concepts", "this", "memory", "relates", "to"],
|
|
253
265
|
"reasoning": "Why this matters for future sessions",
|
|
254
|
-
"context_type": "
|
|
255
|
-
"
|
|
256
|
-
"knowledge_domain": "the area this relates to",
|
|
266
|
+
"context_type": "technical|debug|architecture|decision|personal|philosophy|workflow|milestone|breakthrough|unresolved|state",
|
|
267
|
+
"temporal_class": "eternal|long_term|medium_term|short_term|ephemeral",
|
|
257
268
|
"action_required": boolean,
|
|
258
269
|
"confidence_score": 0.0-1.0,
|
|
259
270
|
"trigger_phrases": ["when debugging memory", "asking about implementation", "discussing architecture"],
|
|
260
271
|
"question_types": ["questions this answers"],
|
|
261
|
-
"emotional_resonance": "emotional context if relevant",
|
|
262
272
|
"problem_solution_pair": boolean,
|
|
263
273
|
"scope": "global|project",
|
|
264
|
-
"temporal_class": "eternal|long_term|medium_term|short_term|ephemeral",
|
|
265
274
|
"domain": "specific domain area (optional)",
|
|
266
275
|
"feature": "specific feature (optional)",
|
|
267
276
|
"related_files": ["paths to related files (optional)"],
|
|
@@ -336,24 +345,22 @@ Focus ONLY on technical, architectural, debugging, decision, workflow, and proje
|
|
|
336
345
|
if (!Array.isArray(memoriesData)) return []
|
|
337
346
|
|
|
338
347
|
return memoriesData.map(m => ({
|
|
339
|
-
// Core
|
|
348
|
+
// Core fields (v3 schema)
|
|
340
349
|
content: String(m.content ?? ''),
|
|
341
350
|
importance_weight: this._clamp(Number(m.importance_weight) || 0.5, 0, 1),
|
|
342
351
|
semantic_tags: this._ensureArray(m.semantic_tags),
|
|
343
352
|
reasoning: String(m.reasoning ?? ''),
|
|
344
|
-
context_type:
|
|
345
|
-
|
|
346
|
-
knowledge_domain: String(m.knowledge_domain ?? ''),
|
|
353
|
+
context_type: this._validateContextType(m.context_type),
|
|
354
|
+
temporal_class: this._validateTemporalClass(m.temporal_class) ?? 'medium_term',
|
|
347
355
|
action_required: Boolean(m.action_required),
|
|
348
356
|
confidence_score: this._clamp(Number(m.confidence_score) || 0.8, 0, 1),
|
|
349
357
|
trigger_phrases: this._ensureArray(m.trigger_phrases),
|
|
350
358
|
question_types: this._ensureArray(m.question_types),
|
|
351
|
-
|
|
359
|
+
anti_triggers: this._ensureArray(m.anti_triggers),
|
|
352
360
|
problem_solution_pair: Boolean(m.problem_solution_pair),
|
|
353
361
|
|
|
354
|
-
//
|
|
362
|
+
// Lifecycle metadata (optional - will get smart defaults if not provided)
|
|
355
363
|
scope: this._validateScope(m.scope),
|
|
356
|
-
temporal_class: this._validateTemporalClass(m.temporal_class),
|
|
357
364
|
domain: m.domain ? String(m.domain) : undefined,
|
|
358
365
|
feature: m.feature ? String(m.feature) : undefined,
|
|
359
366
|
related_files: m.related_files ? this._ensureArray(m.related_files) : undefined,
|
|
@@ -372,10 +379,21 @@ Focus ONLY on technical, architectural, debugging, decision, workflow, and proje
|
|
|
372
379
|
return []
|
|
373
380
|
}
|
|
374
381
|
|
|
375
|
-
private
|
|
376
|
-
const valid = [
|
|
377
|
-
|
|
378
|
-
|
|
382
|
+
private _validateContextType(value: any): ContextType {
|
|
383
|
+
const valid = [
|
|
384
|
+
'technical', 'debug', 'architecture', 'decision', 'personal',
|
|
385
|
+
'philosophy', 'workflow', 'milestone', 'breakthrough', 'unresolved', 'state'
|
|
386
|
+
]
|
|
387
|
+
const str = String(value ?? 'technical').toLowerCase().trim()
|
|
388
|
+
if (valid.includes(str)) return str as ContextType
|
|
389
|
+
|
|
390
|
+
// Map common old values to new canonical types
|
|
391
|
+
if (str.includes('debug') || str.includes('bug')) return 'debug'
|
|
392
|
+
if (str.includes('architect')) return 'architecture'
|
|
393
|
+
if (str.includes('todo') || str.includes('pending')) return 'unresolved'
|
|
394
|
+
if (str.includes('preference')) return 'personal'
|
|
395
|
+
|
|
396
|
+
return 'technical' // Default fallback
|
|
379
397
|
}
|
|
380
398
|
|
|
381
399
|
private _validateScope(value: any): 'global' | 'project' | undefined {
|
package/src/core/engine.test.ts
CHANGED
|
@@ -38,9 +38,7 @@ describe('MemoryStore', () => {
|
|
|
38
38
|
importance_weight: 0.8,
|
|
39
39
|
confidence_score: 0.9,
|
|
40
40
|
context_type: 'technical',
|
|
41
|
-
|
|
42
|
-
knowledge_domain: 'testing',
|
|
43
|
-
emotional_resonance: 'neutral',
|
|
41
|
+
temporal_class: 'long_term',
|
|
44
42
|
action_required: false,
|
|
45
43
|
problem_solution_pair: false,
|
|
46
44
|
semantic_tags: ['test', 'memory'],
|
|
@@ -94,10 +92,8 @@ describe('MemoryStore', () => {
|
|
|
94
92
|
reasoning: 'Test',
|
|
95
93
|
importance_weight: 0.5,
|
|
96
94
|
confidence_score: 0.5,
|
|
97
|
-
context_type: '
|
|
98
|
-
|
|
99
|
-
knowledge_domain: 'test',
|
|
100
|
-
emotional_resonance: 'neutral',
|
|
95
|
+
context_type: 'technical',
|
|
96
|
+
temporal_class: 'short_term',
|
|
101
97
|
action_required: false,
|
|
102
98
|
problem_solution_pair: false,
|
|
103
99
|
semantic_tags: [],
|
|
@@ -121,10 +117,8 @@ describe('SmartVectorRetrieval', () => {
|
|
|
121
117
|
reasoning: 'Test reasoning',
|
|
122
118
|
importance_weight: 0.5,
|
|
123
119
|
confidence_score: 0.5,
|
|
124
|
-
context_type: '
|
|
125
|
-
|
|
126
|
-
knowledge_domain: 'test',
|
|
127
|
-
emotional_resonance: 'neutral',
|
|
120
|
+
context_type: 'technical',
|
|
121
|
+
temporal_class: 'medium_term',
|
|
128
122
|
action_required: false,
|
|
129
123
|
problem_solution_pair: false,
|
|
130
124
|
semantic_tags: [],
|
|
@@ -267,9 +261,7 @@ describe('MemoryEngine', () => {
|
|
|
267
261
|
importance_weight: 0.9,
|
|
268
262
|
confidence_score: 0.9,
|
|
269
263
|
context_type: 'technical',
|
|
270
|
-
|
|
271
|
-
knowledge_domain: 'typescript',
|
|
272
|
-
emotional_resonance: 'discovery',
|
|
264
|
+
temporal_class: 'long_term',
|
|
273
265
|
action_required: false,
|
|
274
266
|
problem_solution_pair: false,
|
|
275
267
|
semantic_tags: ['typescript', 'memory'],
|
package/src/core/retrieval.ts
CHANGED
|
@@ -27,6 +27,7 @@ interface ActivationSignals {
|
|
|
27
27
|
domain: boolean // Domain word found in message
|
|
28
28
|
feature: boolean // Feature word found in message
|
|
29
29
|
content: boolean // Key content words found in message
|
|
30
|
+
files: boolean // Related file path matched
|
|
30
31
|
count: number // Total signals activated
|
|
31
32
|
triggerStrength: number // How strong the trigger match was (0-1)
|
|
32
33
|
tagCount: number // How many tags matched
|
|
@@ -99,8 +100,54 @@ export class SmartVectorRetrieval {
|
|
|
99
100
|
return new Set(words)
|
|
100
101
|
}
|
|
101
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Extract file paths from message
|
|
105
|
+
* Matches patterns like /path/to/file.ts, src/core/engine.ts, ./relative/path
|
|
106
|
+
*/
|
|
107
|
+
private _extractFilePaths(text: string): Set<string> {
|
|
108
|
+
const paths = new Set<string>()
|
|
109
|
+
// Match file paths (with / or \ separators, optional extension)
|
|
110
|
+
const pathPattern = /(?:^|[\s'"(])([.\/\\]?(?:[\w.-]+[\/\\])+[\w.-]+(?:\.\w+)?)/g
|
|
111
|
+
let match
|
|
112
|
+
while ((match = pathPattern.exec(text)) !== null) {
|
|
113
|
+
const path = match[1].toLowerCase()
|
|
114
|
+
paths.add(path)
|
|
115
|
+
// Also add just the filename for partial matching
|
|
116
|
+
const filename = path.split(/[\/\\]/).pop()
|
|
117
|
+
if (filename) paths.add(filename)
|
|
118
|
+
}
|
|
119
|
+
return paths
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check if related_files activate for this message
|
|
124
|
+
*/
|
|
125
|
+
private _checkFilesActivation(
|
|
126
|
+
messagePaths: Set<string>,
|
|
127
|
+
relatedFiles: string[] | undefined
|
|
128
|
+
): boolean {
|
|
129
|
+
if (!relatedFiles?.length || !messagePaths.size) return false
|
|
130
|
+
|
|
131
|
+
for (const file of relatedFiles) {
|
|
132
|
+
const fileLower = file.toLowerCase()
|
|
133
|
+
// Check if any message path matches this file
|
|
134
|
+
for (const msgPath of messagePaths) {
|
|
135
|
+
if (fileLower.includes(msgPath) || msgPath.includes(fileLower)) {
|
|
136
|
+
return true
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Also check just the filename
|
|
140
|
+
const filename = fileLower.split(/[\/\\]/).pop()
|
|
141
|
+
if (filename && messagePaths.has(filename)) {
|
|
142
|
+
return true
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return false
|
|
146
|
+
}
|
|
147
|
+
|
|
102
148
|
/**
|
|
103
149
|
* Pre-filter: Binary exclusions based on v2 lifecycle fields
|
|
150
|
+
* NOTE: superseded_by memories are NOT filtered here - they get redirected in Phase 1
|
|
104
151
|
*/
|
|
105
152
|
private _preFilter(
|
|
106
153
|
memories: StoredMemory[],
|
|
@@ -110,7 +157,7 @@ export class SmartVectorRetrieval {
|
|
|
110
157
|
return memories.filter(memory => {
|
|
111
158
|
if (memory.status && memory.status !== 'active') return false
|
|
112
159
|
if (memory.exclude_from_retrieval === true) return false
|
|
113
|
-
|
|
160
|
+
// NOTE: Don't filter superseded_by here - we handle redirects in Phase 1
|
|
114
161
|
const isGlobal = memory.scope === 'global' || memory.project_id === 'global'
|
|
115
162
|
if (!isGlobal && memory.project_id !== currentProjectId) return false
|
|
116
163
|
if (memory.anti_triggers?.length) {
|
|
@@ -330,22 +377,7 @@ export class SmartVectorRetrieval {
|
|
|
330
377
|
const confidence = memory.confidence_score ?? 0.7
|
|
331
378
|
if (confidence < 0.5) score -= 0.1
|
|
332
379
|
|
|
333
|
-
//
|
|
334
|
-
const emotionalKeywords: Record<string, string[]> = {
|
|
335
|
-
frustration: ['frustrated', 'annoying', 'stuck', 'ugh', 'damn', 'hate'],
|
|
336
|
-
excitement: ['excited', 'awesome', 'amazing', 'love', 'great', 'wow'],
|
|
337
|
-
curiosity: ['wonder', 'curious', 'interesting', 'how', 'why', 'what if'],
|
|
338
|
-
satisfaction: ['done', 'finished', 'complete', 'works', 'solved', 'finally'],
|
|
339
|
-
discovery: ['found', 'realized', 'understand', 'insight', 'breakthrough'],
|
|
340
|
-
}
|
|
341
|
-
const emotion = memory.emotional_resonance?.toLowerCase() ?? ''
|
|
342
|
-
const emotionKws = emotionalKeywords[emotion] ?? []
|
|
343
|
-
for (const ew of emotionKws) {
|
|
344
|
-
if (messageWords.has(ew) || messageLower.includes(ew)) {
|
|
345
|
-
score += 0.05
|
|
346
|
-
break
|
|
347
|
-
}
|
|
348
|
-
}
|
|
380
|
+
// NOTE: emotional_resonance matching removed in v3 (field deleted - 580 variants, unusable)
|
|
349
381
|
|
|
350
382
|
return score
|
|
351
383
|
}
|
|
@@ -370,6 +402,13 @@ export class SmartVectorRetrieval {
|
|
|
370
402
|
|
|
371
403
|
const messageLower = currentMessage.toLowerCase()
|
|
372
404
|
const messageWords = this._extractSignificantWords(currentMessage)
|
|
405
|
+
const messagePaths = this._extractFilePaths(currentMessage)
|
|
406
|
+
|
|
407
|
+
// Build lookup map for redirect resolution
|
|
408
|
+
const memoryById = new Map<string, StoredMemory>()
|
|
409
|
+
for (const m of allMemories) {
|
|
410
|
+
memoryById.set(m.id, m)
|
|
411
|
+
}
|
|
373
412
|
|
|
374
413
|
// ================================================================
|
|
375
414
|
// PHASE 0: PRE-FILTER (Binary exclusions)
|
|
@@ -383,13 +422,13 @@ export class SmartVectorRetrieval {
|
|
|
383
422
|
// PHASE 1: ACTIVATION SIGNALS
|
|
384
423
|
// Count how many signals agree this memory should activate
|
|
385
424
|
// A memory is relevant if >= MIN_ACTIVATION_SIGNALS fire
|
|
425
|
+
// Handles redirects for superseded_by/resolved_by
|
|
386
426
|
// ================================================================
|
|
387
427
|
const activatedMemories: ActivatedMemory[] = []
|
|
428
|
+
const activatedIds = new Set<string>()
|
|
388
429
|
let rejectedCount = 0
|
|
389
430
|
|
|
390
431
|
for (const memory of candidates) {
|
|
391
|
-
const isGlobal = memory.scope === 'global' || memory.project_id === 'global'
|
|
392
|
-
|
|
393
432
|
// Check each activation signal
|
|
394
433
|
const triggerResult = this._checkTriggerActivation(
|
|
395
434
|
messageLower, messageWords, memory.trigger_phrases ?? []
|
|
@@ -404,15 +443,17 @@ export class SmartVectorRetrieval {
|
|
|
404
443
|
messageLower, messageWords, memory.feature
|
|
405
444
|
)
|
|
406
445
|
const contentActivated = this._checkContentActivation(messageWords, memory)
|
|
446
|
+
const filesActivated = this._checkFilesActivation(messagePaths, memory.related_files)
|
|
407
447
|
const vectorSimilarity = this._calculateVectorSimilarity(queryEmbedding, memory.embedding)
|
|
408
448
|
|
|
409
|
-
// Count activated signals
|
|
449
|
+
// Count activated signals (now 7 possible signals)
|
|
410
450
|
let signalCount = 0
|
|
411
451
|
if (triggerResult.activated) signalCount++
|
|
412
452
|
if (tagResult.activated) signalCount++
|
|
413
453
|
if (domainActivated) signalCount++
|
|
414
454
|
if (featureActivated) signalCount++
|
|
415
455
|
if (contentActivated) signalCount++
|
|
456
|
+
if (filesActivated) signalCount++
|
|
416
457
|
// Vector similarity as bonus signal only if very high
|
|
417
458
|
if (vectorSimilarity >= 0.40) signalCount++
|
|
418
459
|
|
|
@@ -422,6 +463,7 @@ export class SmartVectorRetrieval {
|
|
|
422
463
|
domain: domainActivated,
|
|
423
464
|
feature: featureActivated,
|
|
424
465
|
content: contentActivated,
|
|
466
|
+
files: filesActivated,
|
|
425
467
|
count: signalCount,
|
|
426
468
|
triggerStrength: triggerResult.strength,
|
|
427
469
|
tagCount: tagResult.count,
|
|
@@ -434,15 +476,34 @@ export class SmartVectorRetrieval {
|
|
|
434
476
|
continue
|
|
435
477
|
}
|
|
436
478
|
|
|
479
|
+
// REDIRECT: If superseded_by or resolved_by exists, surface the replacement instead
|
|
480
|
+
let memoryToSurface = memory
|
|
481
|
+
if (memory.superseded_by || memory.resolved_by) {
|
|
482
|
+
const replacementId = memory.superseded_by ?? memory.resolved_by
|
|
483
|
+
const replacement = replacementId ? memoryById.get(replacementId) : undefined
|
|
484
|
+
if (replacement && replacement.status !== 'archived' && replacement.status !== 'deprecated') {
|
|
485
|
+
memoryToSurface = replacement
|
|
486
|
+
logger.debug(`Redirect: ${memory.id.slice(-8)} → ${replacement.id.slice(-8)} (${memory.superseded_by ? 'superseded' : 'resolved'})`, 'retrieval')
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Skip if we already added this memory (could happen from multiple redirects)
|
|
491
|
+
if (activatedIds.has(memoryToSurface.id)) {
|
|
492
|
+
continue
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const isGlobal = memoryToSurface.scope === 'global' || memoryToSurface.project_id === 'global'
|
|
496
|
+
|
|
437
497
|
// Calculate importance for ranking (Phase 2) - uses ALL rich metadata
|
|
438
|
-
const importanceScore = this._calculateImportanceScore(
|
|
498
|
+
const importanceScore = this._calculateImportanceScore(memoryToSurface, signalCount, messageLower, messageWords)
|
|
439
499
|
|
|
440
500
|
activatedMemories.push({
|
|
441
|
-
memory,
|
|
501
|
+
memory: memoryToSurface,
|
|
442
502
|
signals,
|
|
443
503
|
importanceScore,
|
|
444
504
|
isGlobal,
|
|
445
505
|
})
|
|
506
|
+
activatedIds.add(memoryToSurface.id)
|
|
446
507
|
}
|
|
447
508
|
|
|
448
509
|
// Log diagnostics
|
|
@@ -532,23 +593,60 @@ export class SmartVectorRetrieval {
|
|
|
532
593
|
selectedIds.add(item.memory.id)
|
|
533
594
|
}
|
|
534
595
|
|
|
535
|
-
// PHASE 4:
|
|
596
|
+
// PHASE 4: LINKED MEMORIES (related_to, blocked_by, blocks)
|
|
597
|
+
// Pull in linked memories when space remains
|
|
536
598
|
if (selected.length < maxMemories) {
|
|
537
|
-
const
|
|
599
|
+
const linkedIds = new Set<string>()
|
|
600
|
+
|
|
538
601
|
for (const item of selected) {
|
|
602
|
+
// related_to - explicit relationships
|
|
539
603
|
for (const relatedId of item.memory.related_to ?? []) {
|
|
540
604
|
if (!selectedIds.has(relatedId)) {
|
|
541
|
-
|
|
605
|
+
linkedIds.add(relatedId)
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
// blocked_by - show what's blocking this
|
|
609
|
+
if (item.memory.blocked_by && !selectedIds.has(item.memory.blocked_by)) {
|
|
610
|
+
linkedIds.add(item.memory.blocked_by)
|
|
611
|
+
}
|
|
612
|
+
// blocks - show what this blocks
|
|
613
|
+
for (const blockedId of item.memory.blocks ?? []) {
|
|
614
|
+
if (!selectedIds.has(blockedId)) {
|
|
615
|
+
linkedIds.add(blockedId)
|
|
542
616
|
}
|
|
543
617
|
}
|
|
544
618
|
}
|
|
545
619
|
|
|
620
|
+
// First try to find linked memories in the activated list
|
|
546
621
|
for (const item of activatedMemories) {
|
|
547
622
|
if (selected.length >= maxMemories) break
|
|
548
623
|
if (selectedIds.has(item.memory.id)) continue
|
|
549
|
-
if (
|
|
624
|
+
if (linkedIds.has(item.memory.id)) {
|
|
550
625
|
selected.push(item)
|
|
551
626
|
selectedIds.add(item.memory.id)
|
|
627
|
+
logger.debug(`Linked: ${item.memory.id.slice(-8)} pulled by relationship`, 'retrieval')
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// If linked memories weren't in activated list, pull them directly from allMemories
|
|
632
|
+
// (they might not have activated on their own but are important for context)
|
|
633
|
+
if (selected.length < maxMemories) {
|
|
634
|
+
for (const linkedId of linkedIds) {
|
|
635
|
+
if (selected.length >= maxMemories) break
|
|
636
|
+
if (selectedIds.has(linkedId)) continue
|
|
637
|
+
|
|
638
|
+
const linkedMemory = memoryById.get(linkedId)
|
|
639
|
+
if (linkedMemory && linkedMemory.status !== 'archived' && linkedMemory.status !== 'deprecated') {
|
|
640
|
+
const isGlobal = linkedMemory.scope === 'global' || linkedMemory.project_id === 'global'
|
|
641
|
+
selected.push({
|
|
642
|
+
memory: linkedMemory,
|
|
643
|
+
signals: { trigger: false, tags: false, domain: false, feature: false, content: false, files: false, count: 0, triggerStrength: 0, tagCount: 0, vectorSimilarity: 0 },
|
|
644
|
+
importanceScore: linkedMemory.importance_weight ?? 0.5,
|
|
645
|
+
isGlobal,
|
|
646
|
+
})
|
|
647
|
+
selectedIds.add(linkedId)
|
|
648
|
+
logger.debug(`Linked (direct): ${linkedId.slice(-8)} pulled for context`, 'retrieval')
|
|
649
|
+
}
|
|
552
650
|
}
|
|
553
651
|
}
|
|
554
652
|
}
|
|
@@ -581,6 +679,7 @@ export class SmartVectorRetrieval {
|
|
|
581
679
|
domain: item.signals.domain,
|
|
582
680
|
feature: item.signals.feature,
|
|
583
681
|
content: item.signals.content,
|
|
682
|
+
files: item.signals.files,
|
|
584
683
|
vector: item.signals.vectorSimilarity >= 0.40,
|
|
585
684
|
vectorSimilarity: item.signals.vectorSimilarity,
|
|
586
685
|
},
|
|
@@ -590,8 +689,8 @@ export class SmartVectorRetrieval {
|
|
|
590
689
|
// Convert to RetrievalResult format
|
|
591
690
|
return selected.map(item => ({
|
|
592
691
|
...item.memory,
|
|
593
|
-
score: item.signals.count /
|
|
594
|
-
relevance_score: item.signals.count /
|
|
692
|
+
score: item.signals.count / 7, // 7 possible signals
|
|
693
|
+
relevance_score: item.signals.count / 7,
|
|
595
694
|
value_score: item.importanceScore,
|
|
596
695
|
}))
|
|
597
696
|
}
|
|
@@ -607,6 +706,7 @@ export class SmartVectorRetrieval {
|
|
|
607
706
|
if (signals.domain) reasons.push('domain')
|
|
608
707
|
if (signals.feature) reasons.push('feature')
|
|
609
708
|
if (signals.content) reasons.push('content')
|
|
709
|
+
if (signals.files) reasons.push('files')
|
|
610
710
|
if (signals.vectorSimilarity >= 0.40) reasons.push(`vector:${(signals.vectorSimilarity * 100).toFixed(0)}%`)
|
|
611
711
|
|
|
612
712
|
return reasons.length
|
|
@@ -623,20 +723,21 @@ export class SmartVectorRetrieval {
|
|
|
623
723
|
rejectedCount: number
|
|
624
724
|
): void {
|
|
625
725
|
const signalBuckets: Record<string, number> = {
|
|
626
|
-
'2 signals': 0, '3 signals': 0, '4 signals': 0, '5 signals': 0, '6 signals': 0
|
|
726
|
+
'2 signals': 0, '3 signals': 0, '4 signals': 0, '5 signals': 0, '6 signals': 0, '7 signals': 0
|
|
627
727
|
}
|
|
628
728
|
for (const mem of activated) {
|
|
629
|
-
const key = `${Math.min(mem.signals.count,
|
|
729
|
+
const key = `${Math.min(mem.signals.count, 7)} signals`
|
|
630
730
|
signalBuckets[key] = (signalBuckets[key] ?? 0) + 1
|
|
631
731
|
}
|
|
632
732
|
|
|
633
|
-
let triggerCount = 0, tagCount = 0, domainCount = 0, featureCount = 0, contentCount = 0, vectorCount = 0
|
|
733
|
+
let triggerCount = 0, tagCount = 0, domainCount = 0, featureCount = 0, contentCount = 0, filesCount = 0, vectorCount = 0
|
|
634
734
|
for (const mem of activated) {
|
|
635
735
|
if (mem.signals.trigger) triggerCount++
|
|
636
736
|
if (mem.signals.tags) tagCount++
|
|
637
737
|
if (mem.signals.domain) domainCount++
|
|
638
738
|
if (mem.signals.feature) featureCount++
|
|
639
739
|
if (mem.signals.content) contentCount++
|
|
740
|
+
if (mem.signals.files) filesCount++
|
|
640
741
|
if (mem.signals.vectorSimilarity >= 0.40) vectorCount++
|
|
641
742
|
}
|
|
642
743
|
|
|
@@ -660,6 +761,7 @@ export class SmartVectorRetrieval {
|
|
|
660
761
|
domain: domainCount,
|
|
661
762
|
feature: featureCount,
|
|
662
763
|
content: contentCount,
|
|
764
|
+
files: filesCount,
|
|
663
765
|
vector: vectorCount,
|
|
664
766
|
total: activated.length,
|
|
665
767
|
},
|
package/src/core/store.ts
CHANGED
|
@@ -177,9 +177,7 @@ export class MemoryStore {
|
|
|
177
177
|
importance_weight: record.importance_weight,
|
|
178
178
|
confidence_score: record.confidence_score,
|
|
179
179
|
context_type: record.context_type as StoredMemory['context_type'],
|
|
180
|
-
|
|
181
|
-
knowledge_domain: record.knowledge_domain as StoredMemory['knowledge_domain'],
|
|
182
|
-
emotional_resonance: record.emotional_resonance as StoredMemory['emotional_resonance'],
|
|
180
|
+
temporal_class: record.temporal_class as StoredMemory['temporal_class'],
|
|
183
181
|
action_required: record.action_required,
|
|
184
182
|
problem_solution_pair: record.problem_solution_pair,
|
|
185
183
|
semantic_tags: record.semantic_tags,
|
|
@@ -211,30 +209,28 @@ export class MemoryStore {
|
|
|
211
209
|
const typeDefaults = V2_DEFAULTS.typeDefaults[contextType] ?? V2_DEFAULTS.typeDefaults.personal
|
|
212
210
|
|
|
213
211
|
const id = memories.insert({
|
|
214
|
-
// Core
|
|
212
|
+
// Core fields
|
|
215
213
|
content: memory.content,
|
|
216
214
|
reasoning: memory.reasoning,
|
|
217
215
|
importance_weight: memory.importance_weight,
|
|
218
216
|
confidence_score: memory.confidence_score,
|
|
219
217
|
context_type: memory.context_type,
|
|
220
|
-
|
|
221
|
-
knowledge_domain: memory.knowledge_domain,
|
|
222
|
-
emotional_resonance: memory.emotional_resonance,
|
|
218
|
+
temporal_class: memory.temporal_class ?? typeDefaults?.temporal_class ?? 'eternal',
|
|
223
219
|
action_required: memory.action_required,
|
|
224
220
|
problem_solution_pair: memory.problem_solution_pair,
|
|
225
221
|
semantic_tags: memory.semantic_tags,
|
|
226
222
|
trigger_phrases: memory.trigger_phrases,
|
|
227
223
|
question_types: memory.question_types,
|
|
224
|
+
anti_triggers: memory.anti_triggers ?? [],
|
|
228
225
|
session_id: sessionId,
|
|
229
226
|
project_id: 'global',
|
|
230
227
|
embedding: embedding
|
|
231
228
|
? (embedding instanceof Float32Array ? embedding : new Float32Array(embedding))
|
|
232
229
|
: null,
|
|
233
230
|
|
|
234
|
-
//
|
|
231
|
+
// Lifecycle fields - global memories are always scope: 'global'
|
|
235
232
|
status: V2_DEFAULTS.fallback.status,
|
|
236
233
|
scope: 'global', // Always global for global memories
|
|
237
|
-
temporal_class: memory.temporal_class ?? typeDefaults?.temporal_class ?? 'eternal',
|
|
238
234
|
fade_rate: typeDefaults?.fade_rate ?? 0, // Global memories typically don't fade
|
|
239
235
|
session_created: sessionNumber ?? 0,
|
|
240
236
|
session_updated: sessionNumber ?? 0,
|
|
@@ -244,18 +240,15 @@ export class MemoryStore {
|
|
|
244
240
|
related_files: memory.related_files ?? [],
|
|
245
241
|
awaiting_implementation: memory.awaiting_implementation ?? false,
|
|
246
242
|
awaiting_decision: memory.awaiting_decision ?? false,
|
|
247
|
-
retrieval_weight: memory.importance_weight,
|
|
248
243
|
exclude_from_retrieval: false,
|
|
249
244
|
schema_version: MEMORY_SCHEMA_VERSION,
|
|
250
245
|
|
|
251
|
-
//
|
|
246
|
+
// Relationship fields
|
|
252
247
|
supersedes: null,
|
|
253
248
|
superseded_by: null,
|
|
254
249
|
related_to: [],
|
|
255
250
|
resolves: [],
|
|
256
251
|
resolved_by: null,
|
|
257
|
-
parent_id: null,
|
|
258
|
-
child_ids: [],
|
|
259
252
|
blocked_by: null,
|
|
260
253
|
blocks: [],
|
|
261
254
|
})
|
|
@@ -491,30 +484,28 @@ export class MemoryStore {
|
|
|
491
484
|
const typeDefaults = V2_DEFAULTS.typeDefaults[contextType] ?? V2_DEFAULTS.typeDefaults.technical
|
|
492
485
|
|
|
493
486
|
const id = memories.insert({
|
|
494
|
-
// Core
|
|
487
|
+
// Core fields
|
|
495
488
|
content: memory.content,
|
|
496
489
|
reasoning: memory.reasoning,
|
|
497
490
|
importance_weight: memory.importance_weight,
|
|
498
491
|
confidence_score: memory.confidence_score,
|
|
499
492
|
context_type: memory.context_type,
|
|
500
|
-
|
|
501
|
-
knowledge_domain: memory.knowledge_domain,
|
|
502
|
-
emotional_resonance: memory.emotional_resonance,
|
|
493
|
+
temporal_class: memory.temporal_class ?? typeDefaults?.temporal_class ?? V2_DEFAULTS.fallback.temporal_class,
|
|
503
494
|
action_required: memory.action_required,
|
|
504
495
|
problem_solution_pair: memory.problem_solution_pair,
|
|
505
496
|
semantic_tags: memory.semantic_tags,
|
|
506
497
|
trigger_phrases: memory.trigger_phrases,
|
|
507
498
|
question_types: memory.question_types,
|
|
499
|
+
anti_triggers: memory.anti_triggers ?? [],
|
|
508
500
|
session_id: sessionId,
|
|
509
501
|
project_id: projectId,
|
|
510
502
|
embedding: embedding
|
|
511
503
|
? (embedding instanceof Float32Array ? embedding : new Float32Array(embedding))
|
|
512
504
|
: null,
|
|
513
505
|
|
|
514
|
-
//
|
|
506
|
+
// Lifecycle fields - use curator-provided values or smart defaults
|
|
515
507
|
status: V2_DEFAULTS.fallback.status,
|
|
516
508
|
scope: memory.scope ?? typeDefaults?.scope ?? V2_DEFAULTS.fallback.scope,
|
|
517
|
-
temporal_class: memory.temporal_class ?? typeDefaults?.temporal_class ?? V2_DEFAULTS.fallback.temporal_class,
|
|
518
509
|
fade_rate: typeDefaults?.fade_rate ?? V2_DEFAULTS.fallback.fade_rate,
|
|
519
510
|
session_created: sessionNumber ?? 0,
|
|
520
511
|
session_updated: sessionNumber ?? 0,
|
|
@@ -524,18 +515,15 @@ export class MemoryStore {
|
|
|
524
515
|
related_files: memory.related_files ?? [],
|
|
525
516
|
awaiting_implementation: memory.awaiting_implementation ?? false,
|
|
526
517
|
awaiting_decision: memory.awaiting_decision ?? false,
|
|
527
|
-
retrieval_weight: memory.importance_weight, // Start with importance as retrieval weight
|
|
528
518
|
exclude_from_retrieval: false,
|
|
529
519
|
schema_version: MEMORY_SCHEMA_VERSION,
|
|
530
520
|
|
|
531
|
-
//
|
|
521
|
+
// Relationship fields
|
|
532
522
|
supersedes: null,
|
|
533
523
|
superseded_by: null,
|
|
534
524
|
related_to: [],
|
|
535
525
|
resolves: [],
|
|
536
526
|
resolved_by: null,
|
|
537
|
-
parent_id: null,
|
|
538
|
-
child_ids: [],
|
|
539
527
|
blocked_by: null,
|
|
540
528
|
blocks: [],
|
|
541
529
|
})
|
|
@@ -556,9 +544,7 @@ export class MemoryStore {
|
|
|
556
544
|
importance_weight: record.importance_weight,
|
|
557
545
|
confidence_score: record.confidence_score,
|
|
558
546
|
context_type: record.context_type as StoredMemory['context_type'],
|
|
559
|
-
|
|
560
|
-
knowledge_domain: record.knowledge_domain as StoredMemory['knowledge_domain'],
|
|
561
|
-
emotional_resonance: record.emotional_resonance as StoredMemory['emotional_resonance'],
|
|
547
|
+
temporal_class: record.temporal_class as StoredMemory['temporal_class'],
|
|
562
548
|
action_required: record.action_required,
|
|
563
549
|
problem_solution_pair: record.problem_solution_pair,
|
|
564
550
|
semantic_tags: record.semantic_tags,
|
|
@@ -596,9 +582,7 @@ export class MemoryStore {
|
|
|
596
582
|
importance_weight: record.importance_weight,
|
|
597
583
|
confidence_score: record.confidence_score,
|
|
598
584
|
context_type: record.context_type as StoredMemory['context_type'],
|
|
599
|
-
|
|
600
|
-
knowledge_domain: record.knowledge_domain as StoredMemory['knowledge_domain'],
|
|
601
|
-
emotional_resonance: record.emotional_resonance as StoredMemory['emotional_resonance'],
|
|
585
|
+
temporal_class: record.temporal_class as StoredMemory['temporal_class'],
|
|
602
586
|
action_required: record.action_required,
|
|
603
587
|
problem_solution_pair: record.problem_solution_pair,
|
|
604
588
|
semantic_tags: record.semantic_tags,
|
|
@@ -620,9 +604,7 @@ export class MemoryStore {
|
|
|
620
604
|
importance_weight: result.record.importance_weight,
|
|
621
605
|
confidence_score: result.record.confidence_score,
|
|
622
606
|
context_type: result.record.context_type as StoredMemory['context_type'],
|
|
623
|
-
|
|
624
|
-
knowledge_domain: result.record.knowledge_domain as StoredMemory['knowledge_domain'],
|
|
625
|
-
emotional_resonance: result.record.emotional_resonance as StoredMemory['emotional_resonance'],
|
|
607
|
+
temporal_class: result.record.temporal_class as StoredMemory['temporal_class'],
|
|
626
608
|
action_required: result.record.action_required,
|
|
627
609
|
problem_solution_pair: result.record.problem_solution_pair,
|
|
628
610
|
semantic_tags: result.record.semantic_tags,
|