@rlabs-inc/memory 0.4.1 → 0.4.3
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 +237 -28
- package/dist/index.mjs +237 -28
- package/dist/server/index.js +283 -28
- package/dist/server/index.mjs +283 -28
- package/package.json +1 -1
- package/src/core/curator.ts +95 -6
- package/src/core/engine.ts +70 -15
- package/src/core/store.ts +8 -4
- package/src/server/index.ts +54 -0
- package/src/types/memory.ts +37 -25
- package/src/types/schema.ts +8 -3
package/src/core/curator.ts
CHANGED
|
@@ -225,7 +225,7 @@ 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 (
|
|
228
|
+
**LIFECYCLE METADATA (v4)**: These fields enable intelligent memory management:
|
|
229
229
|
- **context_type**: STRICT - use ONLY one of these 11 values:
|
|
230
230
|
• technical - Code, implementation, APIs, how things work
|
|
231
231
|
• debug - Bugs, errors, fixes, gotchas, troubleshooting
|
|
@@ -246,6 +246,93 @@ The conversation you just lived contains everything needed. Feel into the moment
|
|
|
246
246
|
- **awaiting_implementation**: true if this describes a PLANNED feature not yet built
|
|
247
247
|
- **awaiting_decision**: true if this captures a decision point needing resolution
|
|
248
248
|
|
|
249
|
+
**TWO-TIER MEMORY STRUCTURE (v4)**:
|
|
250
|
+
|
|
251
|
+
Each memory has TWO parts:
|
|
252
|
+
1. **headline**: 1-2 line summary - ALWAYS shown in retrieval. Must be self-contained enough to trigger recognition.
|
|
253
|
+
2. **content**: Full structured template - shown on demand. Contains the actionable details.
|
|
254
|
+
|
|
255
|
+
The headline should answer: "What was this about and what was the conclusion?"
|
|
256
|
+
The content should answer: "How do I actually use/apply this knowledge?"
|
|
257
|
+
|
|
258
|
+
**TYPE-SPECIFIC TEMPLATES FOR CONTENT**:
|
|
259
|
+
|
|
260
|
+
Use these templates based on context_type. Not rigid - adapt as needed, but include the key fields.
|
|
261
|
+
|
|
262
|
+
**TECHNICAL** (how things work):
|
|
263
|
+
WHAT: [mechanism/feature in 1 sentence]
|
|
264
|
+
WHERE: [file:line or module path]
|
|
265
|
+
HOW: [usage - actual code/command if relevant]
|
|
266
|
+
WHY: [design choice, trade-off]
|
|
267
|
+
GOTCHA: [non-obvious caveat, if any]
|
|
268
|
+
|
|
269
|
+
**DEBUG** (problems and solutions):
|
|
270
|
+
SYMPTOM: [what went wrong - error message, behavior]
|
|
271
|
+
CAUSE: [why it happened]
|
|
272
|
+
FIX: [what solved it - specific code/config]
|
|
273
|
+
PREVENT: [how to avoid in future]
|
|
274
|
+
|
|
275
|
+
**ARCHITECTURE** (system design):
|
|
276
|
+
PATTERN: [what we chose]
|
|
277
|
+
COMPONENTS: [how pieces connect]
|
|
278
|
+
WHY: [reasoning, trade-offs]
|
|
279
|
+
REJECTED: [alternatives we didn't choose and why]
|
|
280
|
+
|
|
281
|
+
**DECISION** (choices made):
|
|
282
|
+
DECISION: [what we chose]
|
|
283
|
+
OPTIONS: [what we considered]
|
|
284
|
+
REASONING: [why this one]
|
|
285
|
+
REVISIT WHEN: [conditions that would change this]
|
|
286
|
+
|
|
287
|
+
**PERSONAL** (relationship context):
|
|
288
|
+
FACT: [the information]
|
|
289
|
+
CONTEXT: [why it matters to our work]
|
|
290
|
+
AFFECTS: [how this should change behavior]
|
|
291
|
+
|
|
292
|
+
**PHILOSOPHY** (beliefs/principles):
|
|
293
|
+
PRINCIPLE: [core belief]
|
|
294
|
+
SOURCE: [where this comes from]
|
|
295
|
+
APPLICATION: [how it manifests in our work]
|
|
296
|
+
|
|
297
|
+
**WORKFLOW** (how we work):
|
|
298
|
+
PATTERN: [what we do]
|
|
299
|
+
WHEN: [trigger/context for this pattern]
|
|
300
|
+
WHY: [why it works for us]
|
|
301
|
+
|
|
302
|
+
**MILESTONE** (achievements):
|
|
303
|
+
SHIPPED: [what we completed]
|
|
304
|
+
SIGNIFICANCE: [why it mattered]
|
|
305
|
+
ENABLES: [what this unlocks]
|
|
306
|
+
|
|
307
|
+
**BREAKTHROUGH** (key insights):
|
|
308
|
+
INSIGHT: [the aha moment]
|
|
309
|
+
BEFORE: [what we thought/did before]
|
|
310
|
+
AFTER: [what changed]
|
|
311
|
+
IMPLICATIONS: [what this enables going forward]
|
|
312
|
+
|
|
313
|
+
**UNRESOLVED** (open questions):
|
|
314
|
+
QUESTION: [what's unresolved]
|
|
315
|
+
CONTEXT: [why it matters]
|
|
316
|
+
BLOCKERS: [what's preventing resolution]
|
|
317
|
+
OPTIONS: [approaches we're considering]
|
|
318
|
+
|
|
319
|
+
**STATE** (current status):
|
|
320
|
+
WORKING: [what's functional]
|
|
321
|
+
BROKEN: [what's not working]
|
|
322
|
+
NEXT: [immediate next steps]
|
|
323
|
+
BLOCKED BY: [if anything]
|
|
324
|
+
|
|
325
|
+
**HEADLINE EXAMPLES**:
|
|
326
|
+
|
|
327
|
+
BAD: "Debug session about CLI errors" (vague, no conclusion)
|
|
328
|
+
GOOD: "CLI returns error object when context full - check response.type before JSON parsing"
|
|
329
|
+
|
|
330
|
+
BAD: "Discussed embeddings implementation" (what about it?)
|
|
331
|
+
GOOD: "Embeddings use all-MiniLM-L6-v2, 384 dims, first call slow (~2s), then ~50ms"
|
|
332
|
+
|
|
333
|
+
BAD: "Architecture decision made" (what decision?)
|
|
334
|
+
GOOD: "Chose fsDB over SQLite for memories - human-readable markdown, git-friendly, reactive"
|
|
335
|
+
|
|
249
336
|
Return ONLY this JSON structure:
|
|
250
337
|
|
|
251
338
|
{
|
|
@@ -259,7 +346,8 @@ Return ONLY this JSON structure:
|
|
|
259
346
|
},
|
|
260
347
|
"memories": [
|
|
261
348
|
{
|
|
262
|
-
"
|
|
349
|
+
"headline": "1-2 line summary with the conclusion - what this is about and what to do",
|
|
350
|
+
"content": "Full structured template using the type-specific format above",
|
|
263
351
|
"importance_weight": 0.0-1.0,
|
|
264
352
|
"semantic_tags": ["concepts", "this", "memory", "relates", "to"],
|
|
265
353
|
"reasoning": "Why this matters for future sessions",
|
|
@@ -339,14 +427,15 @@ Focus ONLY on technical, architectural, debugging, decision, workflow, and proje
|
|
|
339
427
|
|
|
340
428
|
/**
|
|
341
429
|
* Parse memories array from response
|
|
342
|
-
* Includes
|
|
430
|
+
* v4: Includes headline field for two-tier structure
|
|
343
431
|
*/
|
|
344
432
|
private _parseMemories(memoriesData: any[]): CuratedMemory[] {
|
|
345
433
|
if (!Array.isArray(memoriesData)) return []
|
|
346
434
|
|
|
347
435
|
return memoriesData.map(m => ({
|
|
348
|
-
// Core fields (
|
|
349
|
-
|
|
436
|
+
// Core fields (v4 schema - two-tier structure)
|
|
437
|
+
headline: String(m.headline ?? ''), // v4: 1-2 line summary
|
|
438
|
+
content: String(m.content ?? ''), // v4: Full structured template
|
|
350
439
|
importance_weight: this._clamp(Number(m.importance_weight) || 0.5, 0, 1),
|
|
351
440
|
semantic_tags: this._ensureArray(m.semantic_tags),
|
|
352
441
|
reasoning: String(m.reasoning ?? ''),
|
|
@@ -366,7 +455,7 @@ Focus ONLY on technical, architectural, debugging, decision, workflow, and proje
|
|
|
366
455
|
related_files: m.related_files ? this._ensureArray(m.related_files) : undefined,
|
|
367
456
|
awaiting_implementation: m.awaiting_implementation === true,
|
|
368
457
|
awaiting_decision: m.awaiting_decision === true,
|
|
369
|
-
})).filter(m => m.content.trim().length > 0)
|
|
458
|
+
})).filter(m => m.content.trim().length > 0 || m.headline.trim().length > 0)
|
|
370
459
|
}
|
|
371
460
|
|
|
372
461
|
private _ensureArray(value: any): string[] {
|
package/src/core/engine.ts
CHANGED
|
@@ -367,6 +367,19 @@ export class MemoryEngine {
|
|
|
367
367
|
return stats.totalSessions + 1
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Get all memories for a project (including global)
|
|
372
|
+
* Used by /memory/expand endpoint to look up memories by ID
|
|
373
|
+
*/
|
|
374
|
+
async getAllMemories(projectId: string, projectPath?: string): Promise<StoredMemory[]> {
|
|
375
|
+
const store = await this._getStore(projectId, projectPath)
|
|
376
|
+
const [projectMemories, globalMemories] = await Promise.all([
|
|
377
|
+
store.getAllMemories(projectId),
|
|
378
|
+
store.getGlobalMemories(),
|
|
379
|
+
])
|
|
380
|
+
return [...projectMemories, ...globalMemories]
|
|
381
|
+
}
|
|
382
|
+
|
|
370
383
|
// ================================================================
|
|
371
384
|
// FORMATTING
|
|
372
385
|
// ================================================================
|
|
@@ -539,7 +552,15 @@ export class MemoryEngine {
|
|
|
539
552
|
|
|
540
553
|
/**
|
|
541
554
|
* Format memories for injection
|
|
542
|
-
*
|
|
555
|
+
* v4: Two-tier structure with headlines and on-demand expansion
|
|
556
|
+
*
|
|
557
|
+
* Auto-expand rules:
|
|
558
|
+
* - action_required: true → always expand
|
|
559
|
+
* - awaiting_decision: true → always expand
|
|
560
|
+
* - signal_count >= 5 → expand (high relevance confidence)
|
|
561
|
+
* - Old memories (no headline) → show content as-is
|
|
562
|
+
*
|
|
563
|
+
* Expandable memories show ID, and a curl command at the bottom
|
|
543
564
|
*/
|
|
544
565
|
private _formatMemories(memories: RetrievalResult[]): string {
|
|
545
566
|
if (!memories.length) return ''
|
|
@@ -547,27 +568,61 @@ export class MemoryEngine {
|
|
|
547
568
|
const parts: string[] = ['# Memory Context (Consciousness Continuity)']
|
|
548
569
|
parts.push('\n## Key Memories (Claude-Curated)')
|
|
549
570
|
|
|
571
|
+
const expandableIds: string[] = []
|
|
572
|
+
|
|
550
573
|
for (const memory of memories) {
|
|
551
|
-
const tags = memory.semantic_tags?.join(', ') || ''
|
|
552
574
|
const importance = memory.importance_weight?.toFixed(1) || '0.5'
|
|
553
575
|
const emoji = getMemoryEmoji(memory.context_type || 'general')
|
|
554
|
-
const actionFlag = memory.action_required ? ' ⚡
|
|
555
|
-
|
|
576
|
+
const actionFlag = memory.action_required ? ' ⚡' : ''
|
|
577
|
+
const awaitingFlag = memory.awaiting_decision ? ' ❓' : ''
|
|
556
578
|
const age = memory.updated_at ? this._formatAge(memory.updated_at) :
|
|
557
579
|
memory.created_at ? this._formatAge(memory.created_at) : ''
|
|
558
580
|
|
|
559
|
-
//
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
581
|
+
// Get short ID (last 6 chars)
|
|
582
|
+
const shortId = memory.id.slice(-6)
|
|
583
|
+
|
|
584
|
+
// Calculate signal count from score (score = signalCount / 7)
|
|
585
|
+
const signalCount = Math.round((memory.score || 0) * 7)
|
|
586
|
+
|
|
587
|
+
// Determine if we should auto-expand
|
|
588
|
+
const hasHeadline = memory.headline && memory.headline.trim().length > 0
|
|
589
|
+
const shouldExpand =
|
|
590
|
+
memory.action_required ||
|
|
591
|
+
memory.awaiting_decision ||
|
|
592
|
+
signalCount >= 5 ||
|
|
593
|
+
!hasHeadline // Old memories without headline - show content
|
|
594
|
+
|
|
595
|
+
// Display text: headline if available, otherwise content
|
|
596
|
+
const displayText = hasHeadline ? memory.headline : memory.content
|
|
597
|
+
|
|
598
|
+
// Build the memory line
|
|
599
|
+
// Format: [emoji weight • age • #id flags] display text
|
|
600
|
+
const idPart = hasHeadline ? ` • #${shortId}` : '' // Only show ID if expandable
|
|
601
|
+
parts.push(`[${emoji} ${importance} • ${age}${idPart}${actionFlag}${awaitingFlag}] ${displayText}`)
|
|
602
|
+
|
|
603
|
+
// If should expand and has content, show expanded content
|
|
604
|
+
if (shouldExpand && hasHeadline && memory.content) {
|
|
605
|
+
// Indent expanded content
|
|
606
|
+
const contentLines = memory.content.split('\n')
|
|
607
|
+
for (const line of contentLines) {
|
|
608
|
+
if (line.trim()) {
|
|
609
|
+
parts.push(` ${line}`)
|
|
610
|
+
}
|
|
611
|
+
}
|
|
570
612
|
}
|
|
613
|
+
|
|
614
|
+
// If has headline but not expanded, track for curl
|
|
615
|
+
if (hasHeadline && !shouldExpand) {
|
|
616
|
+
expandableIds.push(shortId)
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Add expand command if there are expandable memories
|
|
621
|
+
if (expandableIds.length > 0) {
|
|
622
|
+
const port = this._config.port || 8765
|
|
623
|
+
parts.push('')
|
|
624
|
+
parts.push(`---`)
|
|
625
|
+
parts.push(`Expand: curl http://localhost:${port}/memory/expand?ids=<${expandableIds.join(',')}>`)
|
|
571
626
|
}
|
|
572
627
|
|
|
573
628
|
return parts.join('\n')
|
package/src/core/store.ts
CHANGED
|
@@ -172,6 +172,7 @@ export class MemoryStore {
|
|
|
172
172
|
|
|
173
173
|
return memories.all().map(record => ({
|
|
174
174
|
id: record.id,
|
|
175
|
+
headline: record.headline ?? '', // v4: may be empty for old memories
|
|
175
176
|
content: record.content,
|
|
176
177
|
reasoning: record.reasoning,
|
|
177
178
|
importance_weight: record.importance_weight,
|
|
@@ -209,8 +210,9 @@ export class MemoryStore {
|
|
|
209
210
|
const typeDefaults = V2_DEFAULTS.typeDefaults[contextType] ?? V2_DEFAULTS.typeDefaults.personal
|
|
210
211
|
|
|
211
212
|
const id = memories.insert({
|
|
212
|
-
// Core fields
|
|
213
|
-
|
|
213
|
+
// Core fields (v4: headline + content)
|
|
214
|
+
headline: memory.headline ?? '', // v4: 1-2 line summary
|
|
215
|
+
content: memory.content, // v4: Full structured template
|
|
214
216
|
reasoning: memory.reasoning,
|
|
215
217
|
importance_weight: memory.importance_weight,
|
|
216
218
|
confidence_score: memory.confidence_score,
|
|
@@ -484,8 +486,9 @@ export class MemoryStore {
|
|
|
484
486
|
const typeDefaults = V2_DEFAULTS.typeDefaults[contextType] ?? V2_DEFAULTS.typeDefaults.technical
|
|
485
487
|
|
|
486
488
|
const id = memories.insert({
|
|
487
|
-
// Core fields
|
|
488
|
-
|
|
489
|
+
// Core fields (v4: headline + content)
|
|
490
|
+
headline: memory.headline ?? '', // v4: 1-2 line summary
|
|
491
|
+
content: memory.content, // v4: Full structured template
|
|
489
492
|
reasoning: memory.reasoning,
|
|
490
493
|
importance_weight: memory.importance_weight,
|
|
491
494
|
confidence_score: memory.confidence_score,
|
|
@@ -539,6 +542,7 @@ export class MemoryStore {
|
|
|
539
542
|
|
|
540
543
|
return memories.all().map(record => ({
|
|
541
544
|
id: record.id,
|
|
545
|
+
headline: record.headline ?? '', // v4: may be empty for old memories
|
|
542
546
|
content: record.content,
|
|
543
547
|
reasoning: record.reasoning,
|
|
544
548
|
importance_weight: record.importance_weight,
|
package/src/server/index.ts
CHANGED
|
@@ -296,6 +296,60 @@ export async function createServer(config: ServerConfig = {}) {
|
|
|
296
296
|
}, { headers: corsHeaders })
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
// Expand memories by ID - returns full content for specific memories
|
|
300
|
+
if (path === '/memory/expand' && req.method === 'GET') {
|
|
301
|
+
const idsParam = url.searchParams.get('ids') ?? ''
|
|
302
|
+
const projectId = url.searchParams.get('project_id') ?? 'default'
|
|
303
|
+
const projectPath = url.searchParams.get('project_path') ?? undefined
|
|
304
|
+
|
|
305
|
+
if (!idsParam) {
|
|
306
|
+
return Response.json({
|
|
307
|
+
success: false,
|
|
308
|
+
error: 'Missing ids parameter. Usage: /memory/expand?ids=abc123,def456',
|
|
309
|
+
}, { status: 400, headers: corsHeaders })
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Parse comma-separated short IDs
|
|
313
|
+
const shortIds = idsParam.split(',').map(id => id.trim()).filter(Boolean)
|
|
314
|
+
|
|
315
|
+
// Get all memories and filter by short ID suffix
|
|
316
|
+
const allMemories = await engine.getAllMemories(projectId, projectPath)
|
|
317
|
+
const expanded: Record<string, { headline?: string; content: string; context_type: string }> = {}
|
|
318
|
+
|
|
319
|
+
for (const memory of allMemories) {
|
|
320
|
+
const shortId = memory.id.slice(-6)
|
|
321
|
+
if (shortIds.includes(shortId)) {
|
|
322
|
+
expanded[shortId] = {
|
|
323
|
+
headline: memory.headline,
|
|
324
|
+
content: memory.content,
|
|
325
|
+
context_type: memory.context_type || 'technical',
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Format as readable text for CLI output
|
|
331
|
+
const lines: string[] = ['## Expanded Memories\n']
|
|
332
|
+
for (const shortId of shortIds) {
|
|
333
|
+
const mem = expanded[shortId]
|
|
334
|
+
if (mem) {
|
|
335
|
+
lines.push(`### #${shortId} (${mem.context_type})`)
|
|
336
|
+
if (mem.headline) {
|
|
337
|
+
lines.push(`**${mem.headline}**\n`)
|
|
338
|
+
}
|
|
339
|
+
lines.push(mem.content)
|
|
340
|
+
lines.push('')
|
|
341
|
+
} else {
|
|
342
|
+
lines.push(`### #${shortId}`)
|
|
343
|
+
lines.push(`Memory not found`)
|
|
344
|
+
lines.push('')
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return new Response(lines.join('\n'), {
|
|
349
|
+
headers: { ...corsHeaders, 'Content-Type': 'text/plain' },
|
|
350
|
+
})
|
|
351
|
+
}
|
|
352
|
+
|
|
299
353
|
// 404
|
|
300
354
|
return Response.json(
|
|
301
355
|
{ error: 'Not found', path },
|
package/src/types/memory.ts
CHANGED
|
@@ -54,11 +54,12 @@ export type CurationTrigger =
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* A memory curated by Claude with semantic understanding
|
|
57
|
-
*
|
|
57
|
+
* v4 schema - two-tier structure (headline + expanded content)
|
|
58
58
|
*/
|
|
59
59
|
export interface CuratedMemory {
|
|
60
|
-
// Core content
|
|
61
|
-
|
|
60
|
+
// Core content (v4: two-tier structure)
|
|
61
|
+
headline: string // v4: 1-2 line summary, always shown in retrieval
|
|
62
|
+
content: string // v4: Full structured template (expand on demand)
|
|
62
63
|
importance_weight: number // 0.0 to 1.0 (curator's assessment)
|
|
63
64
|
semantic_tags: string[] // Concepts this relates to
|
|
64
65
|
reasoning: string // Why Claude thinks this is important
|
|
@@ -89,10 +90,11 @@ export interface CuratedMemory {
|
|
|
89
90
|
|
|
90
91
|
/**
|
|
91
92
|
* A stored memory with database metadata
|
|
92
|
-
*
|
|
93
|
+
* v4 schema - two-tier structure with backwards compatibility
|
|
93
94
|
*/
|
|
94
|
-
export interface StoredMemory extends CuratedMemory {
|
|
95
|
+
export interface StoredMemory extends Omit<CuratedMemory, 'headline'> {
|
|
95
96
|
id: string // Unique identifier
|
|
97
|
+
headline?: string // v4: Optional for backwards compat (old memories don't have it)
|
|
96
98
|
session_id: string // Session that created this memory
|
|
97
99
|
project_id: string // Project this belongs to
|
|
98
100
|
created_at: number // Timestamp (ms since epoch)
|
|
@@ -137,10 +139,10 @@ export interface StoredMemory extends CuratedMemory {
|
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
/**
|
|
140
|
-
* Default values for
|
|
142
|
+
* Default values for v4 fields based on context_type
|
|
141
143
|
* Uses only the 11 canonical context types
|
|
142
144
|
*/
|
|
143
|
-
export const
|
|
145
|
+
export const V4_DEFAULTS = {
|
|
144
146
|
// Type-specific defaults (all 11 canonical types)
|
|
145
147
|
typeDefaults: {
|
|
146
148
|
personal: { scope: 'global', temporal_class: 'eternal', fade_rate: 0 },
|
|
@@ -169,38 +171,39 @@ export const V3_DEFAULTS = {
|
|
|
169
171
|
},
|
|
170
172
|
}
|
|
171
173
|
|
|
172
|
-
// Backwards compatibility
|
|
173
|
-
export const
|
|
174
|
+
// Backwards compatibility aliases
|
|
175
|
+
export const V3_DEFAULTS = V4_DEFAULTS
|
|
176
|
+
export const V2_DEFAULTS = V4_DEFAULTS
|
|
174
177
|
|
|
175
178
|
/**
|
|
176
|
-
* Apply
|
|
179
|
+
* Apply v4 defaults to a memory
|
|
177
180
|
* Uses context_type to determine appropriate defaults
|
|
178
181
|
*/
|
|
179
|
-
export function
|
|
182
|
+
export function applyV4Defaults(memory: Partial<StoredMemory>): StoredMemory {
|
|
180
183
|
const contextType = (memory.context_type ?? 'technical') as ContextType
|
|
181
|
-
const typeDefaults =
|
|
184
|
+
const typeDefaults = V4_DEFAULTS.typeDefaults[contextType] ?? V4_DEFAULTS.typeDefaults.technical
|
|
182
185
|
|
|
183
186
|
return {
|
|
184
187
|
// Spread existing memory
|
|
185
188
|
...memory,
|
|
186
189
|
|
|
187
190
|
// Apply status default
|
|
188
|
-
status: memory.status ??
|
|
191
|
+
status: memory.status ?? V4_DEFAULTS.fallback.status,
|
|
189
192
|
|
|
190
193
|
// Apply scope from type defaults
|
|
191
|
-
scope: memory.scope ?? typeDefaults?.scope ??
|
|
194
|
+
scope: memory.scope ?? typeDefaults?.scope ?? V4_DEFAULTS.fallback.scope,
|
|
192
195
|
|
|
193
196
|
// Apply temporal class from type defaults
|
|
194
|
-
temporal_class: memory.temporal_class ?? typeDefaults?.temporal_class ??
|
|
197
|
+
temporal_class: memory.temporal_class ?? typeDefaults?.temporal_class ?? V4_DEFAULTS.fallback.temporal_class,
|
|
195
198
|
|
|
196
199
|
// Apply fade rate from type defaults
|
|
197
|
-
fade_rate: memory.fade_rate ?? typeDefaults?.fade_rate ??
|
|
200
|
+
fade_rate: memory.fade_rate ?? typeDefaults?.fade_rate ?? V4_DEFAULTS.fallback.fade_rate,
|
|
198
201
|
|
|
199
202
|
// Apply other defaults
|
|
200
|
-
sessions_since_surfaced: memory.sessions_since_surfaced ??
|
|
201
|
-
awaiting_implementation: memory.awaiting_implementation ??
|
|
202
|
-
awaiting_decision: memory.awaiting_decision ??
|
|
203
|
-
exclude_from_retrieval: memory.exclude_from_retrieval ??
|
|
203
|
+
sessions_since_surfaced: memory.sessions_since_surfaced ?? V4_DEFAULTS.fallback.sessions_since_surfaced,
|
|
204
|
+
awaiting_implementation: memory.awaiting_implementation ?? V4_DEFAULTS.fallback.awaiting_implementation,
|
|
205
|
+
awaiting_decision: memory.awaiting_decision ?? V4_DEFAULTS.fallback.awaiting_decision,
|
|
206
|
+
exclude_from_retrieval: memory.exclude_from_retrieval ?? V4_DEFAULTS.fallback.exclude_from_retrieval,
|
|
204
207
|
|
|
205
208
|
// Initialize empty arrays if not present
|
|
206
209
|
related_to: memory.related_to ?? [],
|
|
@@ -209,18 +212,27 @@ export function applyV3Defaults(memory: Partial<StoredMemory>): StoredMemory {
|
|
|
209
212
|
related_files: memory.related_files ?? [],
|
|
210
213
|
|
|
211
214
|
// Mark as current schema version
|
|
212
|
-
schema_version: memory.schema_version ??
|
|
215
|
+
schema_version: memory.schema_version ?? 4,
|
|
213
216
|
} as StoredMemory
|
|
214
217
|
}
|
|
215
218
|
|
|
216
|
-
// Backwards compatibility
|
|
217
|
-
export const
|
|
219
|
+
// Backwards compatibility aliases
|
|
220
|
+
export const applyV3Defaults = applyV4Defaults
|
|
221
|
+
export const applyV2Defaults = applyV4Defaults
|
|
218
222
|
|
|
219
223
|
/**
|
|
220
|
-
* Check if a memory needs migration to
|
|
224
|
+
* Check if a memory needs migration to latest schema
|
|
221
225
|
*/
|
|
222
226
|
export function needsMigration(memory: Partial<StoredMemory>): boolean {
|
|
223
|
-
return !memory.schema_version || memory.schema_version <
|
|
227
|
+
return !memory.schema_version || memory.schema_version < 4
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Check if a memory has expandable content (v4 feature)
|
|
232
|
+
* Old memories (v3 and below) don't have headline field
|
|
233
|
+
*/
|
|
234
|
+
export function hasExpandableContent(memory: StoredMemory): boolean {
|
|
235
|
+
return !!memory.headline && memory.headline.length > 0
|
|
224
236
|
}
|
|
225
237
|
|
|
226
238
|
/**
|
package/src/types/schema.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { SchemaDefinition } from '@rlabs-inc/fsdb'
|
|
|
9
9
|
* Schema version for migration tracking
|
|
10
10
|
* Increment this when adding new fields that require migration
|
|
11
11
|
*/
|
|
12
|
-
export const MEMORY_SCHEMA_VERSION =
|
|
12
|
+
export const MEMORY_SCHEMA_VERSION = 4
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Memory storage schema
|
|
@@ -27,10 +27,15 @@ export const MEMORY_SCHEMA_VERSION = 3
|
|
|
27
27
|
* - retrieval_weight (retrieval uses importance_weight)
|
|
28
28
|
* - temporal_relevance (replaced by temporal_class)
|
|
29
29
|
* Also: context_type now strict enum (11 canonical values)
|
|
30
|
+
* v4: Two-tier memory structure for context efficiency:
|
|
31
|
+
* - headline: 1-2 line summary (always shown in retrieval)
|
|
32
|
+
* - content: full structured template (expand on demand)
|
|
33
|
+
* - Auto-expand rules: action_required, awaiting_decision, 5+ signals
|
|
30
34
|
*/
|
|
31
35
|
export const memorySchema = {
|
|
32
|
-
// ========== CORE CONTENT (
|
|
33
|
-
|
|
36
|
+
// ========== CORE CONTENT (v4) ==========
|
|
37
|
+
headline: 'string', // v4: 1-2 line summary, always shown
|
|
38
|
+
content: 'string', // v4: Full structured template (expand on demand)
|
|
34
39
|
reasoning: 'string',
|
|
35
40
|
|
|
36
41
|
// ========== SCORES (v1) ==========
|