@rlabs-inc/memory 0.4.3 → 0.4.5

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 CHANGED
@@ -63,7 +63,7 @@ A memory is relevant if **multiple signals agree** it should activate. Not coinc
63
63
 
64
64
  **Phase 0 - Pre-Filter**: Exclude inactive, superseded, or wrong-scope memories
65
65
 
66
- **Phase 1 - Activation Signals** (6 binary signals, need ≥2 to proceed)
66
+ **Phase 1 - Activation Signals** (7 binary signals, need ≥2 to proceed)
67
67
 
68
68
  | Signal | Description |
69
69
  |--------|-------------|
@@ -72,6 +72,7 @@ A memory is relevant if **multiple signals agree** it should activate. Not coinc
72
72
  | Domain | Domain word found in message |
73
73
  | Feature | Feature word found in message |
74
74
  | Content | 3+ significant content words overlap |
75
+ | Files | Related file path matched in message |
75
76
  | Vector | Semantic similarity ≥ 40% |
76
77
 
77
78
  **Phase 2 - Importance Ranking** (among relevant memories)
@@ -88,6 +89,11 @@ A memory is relevant if **multiple signals agree** it should activate. Not coinc
88
89
 
89
90
  **Selection**: Sort by signal count (DESC) → importance score (DESC). Max 2 global memories (tech prioritized), project memories fill remaining slots.
90
91
 
92
+ **Phase 3 - Redirects & Relationships**:
93
+ - If memory has `superseded_by` or `resolved_by`, surface the replacement instead
94
+ - Pull related memories via `blocked_by` and `blocks` fields
95
+ - Include `related_to` linked memories if space remains
96
+
91
97
  ### Global vs Project Memories
92
98
 
93
99
  Memories are stored in two scopes:
@@ -97,6 +103,36 @@ Memories are stored in two scopes:
97
103
 
98
104
  Global memories are retrieved alongside project memories, with a maximum of 2 globals per retrieval (technical types prioritized).
99
105
 
106
+ ### Two-Tier Memory Structure
107
+
108
+ Memories are stored with a **headline + content** structure for context efficiency:
109
+
110
+ | Part | Purpose | Usage |
111
+ |------|---------|-------|
112
+ | **Headline** | 1-2 line summary, ALWAYS shown | Quick recognition |
113
+ | **Content** | Full structured template | Expanded on demand |
114
+
115
+ **Headline examples:**
116
+ ```
117
+ Bad: "Debug session about CLI errors"
118
+ Good: "CLI returns error object when context full - check response.type before JSON parsing"
119
+ ```
120
+
121
+ **Type-specific content templates:**
122
+
123
+ | Type | Template |
124
+ |------|----------|
125
+ | technical | WHAT / WHERE / HOW / WHY / GOTCHA |
126
+ | debug | SYMPTOM / CAUSE / FIX / PREVENT |
127
+ | architecture | PATTERN / COMPONENTS / WHY / REJECTED |
128
+ | decision | DECISION / OPTIONS / REASONING / REVISIT_WHEN |
129
+ | personal | FACT / CONTEXT / AFFECTS |
130
+ | breakthrough | INSIGHT / BEFORE / AFTER / IMPLICATIONS |
131
+ | unresolved | QUESTION / CONTEXT / BLOCKERS / OPTIONS |
132
+ | state | WORKING / BROKEN / NEXT / BLOCKED_BY |
133
+
134
+ **Expansion:** Use `GET /memory/expand?ids=abc123` to fetch full content on demand.
135
+
100
136
  ### Smart Curation
101
137
  At session end (or before context compaction), the same Claude instance reviews the conversation and extracts memories. No API key needed—uses Claude Code's `--resume` flag.
102
138
 
@@ -141,24 +177,21 @@ First message of each session receives temporal context:
141
177
  ```
142
178
 
143
179
  ### Emoji Memory Types
144
- Compact visual representation for efficient parsing:
180
+ 11 canonical types with compact visual representation:
145
181
 
146
182
  | Emoji | Type | Meaning |
147
183
  |-------|------|---------|
148
- | 💡 | breakthrough | Insight, discovery |
149
- | ⚖️ | decision | Choice made |
150
- | 💜 | personal | Relationship, friendship |
151
- | 🔧 | technical | Technical knowledge |
152
- | 📍 | technical_state | Current state |
153
- | | unresolved | Open question |
154
- | ⚙️ | preference | User preference |
155
- | 🔄 | workflow | How work flows |
156
- | 🏗️ | architectural | System design |
157
- | 🐛 | debugging | Debug insight |
158
- | 🌀 | philosophy | Deeper thinking |
159
- | 🎯 | todo | Action needed |
160
- | ✅ | problem_solution | Problem→Solution pair |
161
- | 🏆 | milestone | Achievement |
184
+ | 🔧 | technical | Code, implementation, APIs |
185
+ | 🐛 | debug | Bugs, errors, fixes, gotchas |
186
+ | 🏗️ | architecture | System design, patterns |
187
+ | ⚖️ | decision | Choices made, trade-offs |
188
+ | 💜 | personal | Relationship, collaboration |
189
+ | 🌀 | philosophy | Beliefs, values, principles |
190
+ | 🔄 | workflow | How we work together |
191
+ | 🏆 | milestone | Achievements, shipped features |
192
+ | 💡 | breakthrough | Key insights, discoveries |
193
+ | | unresolved | Open questions, todos |
194
+ | 📍 | state | Current project status |
162
195
 
163
196
  ## Architecture
164
197
 
@@ -209,19 +242,21 @@ Memories are stored as human-readable markdown with YAML frontmatter:
209
242
 
210
243
  ```markdown
211
244
  ---
212
- # Core fields (v1)
245
+ # Schema version
246
+ schema_version: 4
247
+
248
+ # Two-tier content (v4)
249
+ headline: "CLI returns error object when context full - check response.type"
250
+ # (content is in markdown body below)
251
+
252
+ # Core metadata
213
253
  importance_weight: 0.9
214
254
  confidence_score: 0.85
215
- context_type: technical
216
- temporal_relevance: persistent
255
+ context_type: technical # one of 11 canonical types
217
256
  semantic_tags: [embeddings, vectors, memory-system]
218
257
  trigger_phrases: [working with embeddings, vector search]
219
- question_types: [how, what]
220
- knowledge_domain: architecture
221
- emotional_resonance: discovery
222
258
 
223
- # Lifecycle fields (v2)
224
- schema_version: 2
259
+ # Classification
225
260
  status: active # active, pending, superseded, deprecated, archived
226
261
  scope: project # global or project
227
262
  temporal_class: long_term # eternal, long_term, medium_term, short_term, ephemeral
@@ -233,13 +268,17 @@ feature: vector-search
233
268
  related_to: [memory-xyz, memory-abc]
234
269
  supersedes: memory-old-id
235
270
  superseded_by: null
271
+ blocked_by: null
272
+ blocks: []
236
273
 
237
274
  # Embedding (384 dimensions)
238
275
  embedding: [0.023, -0.041, 0.087, ...]
239
276
  ---
240
277
 
241
- Embeddings are 384-dimensional vectors generated by all-MiniLM-L6-v2.
242
- The model loads at server startup (~80MB) and generates embeddings in ~5ms.
278
+ WHAT: Embeddings are 384-dimensional vectors generated by all-MiniLM-L6-v2.
279
+ WHERE: src/core/embeddings.ts
280
+ HOW: Model loads at server startup (~80MB) and generates embeddings in ~5ms.
281
+ WHY: Local embeddings avoid API costs and latency.
243
282
  ```
244
283
 
245
284
  Benefits:
@@ -266,8 +305,16 @@ memory doctor --verbose # Detailed diagnostics
266
305
  memory stats # Show memory statistics
267
306
  memory stats --project x # Project-specific stats
268
307
 
269
- memory migrate # Upgrade memories to latest schema (v1 → v2)
308
+ memory migrate # Upgrade memories to latest schema
270
309
  memory migrate --dry-run # Preview changes without applying
310
+ memory migrate --analyze # Show fragmentation analysis
311
+ memory migrate --embeddings # Regenerate null embeddings
312
+
313
+ memory ingest # Batch ingest historical sessions
314
+ memory ingest --session <id> # Ingest specific session
315
+ memory ingest --project <name> # Ingest all sessions from project
316
+ memory ingest --all # Ingest all projects
317
+ memory ingest --dry-run # Preview what would be ingested
271
318
  ```
272
319
 
273
320
  ## Environment Variables
@@ -336,6 +383,32 @@ This isn't just about remembering facts. It's about preserving:
336
383
 
337
384
  ## Changelog
338
385
 
386
+ ### v0.4.4
387
+ - **Docs**: Comprehensive README update with accurate v0.4.x documentation
388
+ - **Fix**: CLI `--version` now reads from package.json instead of hardcoded value
389
+
390
+ ### v0.4.3
391
+ - **Fix**: Minor patch release
392
+
393
+ ### v0.4.2
394
+ - **Feature**: Two-tier memory structure with headline + content separation
395
+ - **Feature**: Type-specific content templates (11 canonical types)
396
+ - **Feature**: `/memory/expand` endpoint for on-demand content expansion
397
+ - **Improvement**: Headlines enable fast recognition without full content parsing
398
+
399
+ ### v0.4.1
400
+ - **Feature**: V3 schema with 11 canonical context types (consolidated from 170+ variants)
401
+ - **Feature**: 7th activation signal: `files` for related_files path matching
402
+ - **Feature**: Redirect logic for `superseded_by` and `resolved_by` fields
403
+ - **Feature**: Blocking relationships via `blocked_by` and `blocks` fields
404
+ - **Improvement**: Replaced `temporal_relevance` with `temporal_class` + `fade_rate`
405
+ - **Cleanup**: Removed 11 dead metadata fields (emotional_resonance, knowledge_domain, etc.)
406
+
407
+ ### v0.4.0
408
+ - **Feature**: Schema versioning infrastructure for safe migrations
409
+ - **Feature**: Enhanced `migrate` command with `--analyze` and `--embeddings` flags
410
+ - **Feature**: Custom context_type mapping support for migrations
411
+
339
412
  ### v0.3.11
340
413
  - **Feature**: Agent SDK integration for curator and manager - no API key needed, uses Claude Code OAuth
341
414
  - **Feature**: `memory ingest --session <id>` to ingest a single session (useful when automatic curation fails)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rlabs-inc/memory",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "AI Memory System - Consciousness continuity through intelligent memory curation and retrieval",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -306,6 +306,8 @@ export async function ingest(options: IngestOptions) {
306
306
  // Accumulate all memories from this session for manager
307
307
  const sessionMemories: CuratedMemory[] = []
308
308
  let sessionSummary = ''
309
+ let interactionTone = ''
310
+ let projectSnapshot: CurationResult['project_snapshot'] = undefined
309
311
 
310
312
  const spinner = new Spinner()
311
313
 
@@ -330,16 +332,36 @@ export async function ingest(options: IngestOptions) {
330
332
  totalMemories++
331
333
  }
332
334
 
333
- // Keep the most recent session summary
335
+ // Keep the most recent session summary, tone, and snapshot
334
336
  if (result.session_summary) {
335
337
  sessionSummary = result.session_summary
336
338
  }
339
+ if (result.interaction_tone) {
340
+ interactionTone = result.interaction_tone
341
+ }
342
+ if (result.project_snapshot) {
343
+ projectSnapshot = result.project_snapshot
344
+ }
337
345
  } catch (error: any) {
338
346
  failedSegments++
339
347
  spinner.stop(` ${style('red', '✗')} Segment ${segment.segmentIndex + 1}/${segment.totalSegments}: ${error.message}`)
340
348
  }
341
349
  }
342
350
 
351
+ // Store session summary and project snapshot
352
+ if (sessionSummary) {
353
+ await store.storeSessionSummary(project.folderId, session.id, sessionSummary, interactionTone)
354
+ if (options.verbose) {
355
+ console.log(` ${style('dim', `Summary stored: ${sessionSummary.slice(0, 60)}...`)}`)
356
+ }
357
+ }
358
+ if (projectSnapshot) {
359
+ await store.storeProjectSnapshot(project.folderId, session.id, projectSnapshot)
360
+ if (options.verbose) {
361
+ console.log(` ${style('dim', `Snapshot stored: phase=${projectSnapshot.current_phase || 'none'}`)}`)
362
+ }
363
+ }
364
+
343
365
  // Run manager if we have memories and manager is enabled
344
366
  if (sessionMemories.length > 0 && managerEnabled) {
345
367
  try {
@@ -350,7 +372,8 @@ export async function ingest(options: IngestOptions) {
350
372
  const curationResult: CurationResult = {
351
373
  memories: sessionMemories,
352
374
  session_summary: sessionSummary,
353
- project_snapshot: undefined,
375
+ interaction_tone: interactionTone,
376
+ project_snapshot: projectSnapshot,
354
377
  }
355
378
 
356
379
  const managerResult = await manager.manageWithSDK(
package/src/cli/index.ts CHANGED
@@ -5,8 +5,9 @@
5
5
 
6
6
  import { parseArgs } from 'util'
7
7
  import { c, symbols, fmt } from './colors.ts'
8
+ import packageJson from '../../package.json'
8
9
 
9
- const VERSION = '0.1.0'
10
+ const VERSION = packageJson.version
10
11
 
11
12
  /**
12
13
  * Show help message
@@ -7,6 +7,7 @@ import { homedir } from 'os'
7
7
  import { join } from 'path'
8
8
  import { existsSync } from 'fs'
9
9
  import type { CuratedMemory, CurationResult, CurationTrigger, ContextType } from '../types/memory.ts'
10
+ import { logger } from '../utils/logger.ts'
10
11
 
11
12
  /**
12
13
  * Get the correct Claude CLI command path
@@ -396,13 +397,14 @@ Focus ONLY on technical, architectural, debugging, decision, workflow, and proje
396
397
  // Try to extract JSON from response (same regex as Python)
397
398
  const jsonMatch = responseJson.match(/\{[\s\S]*\}/)?.[0]
398
399
  if (!jsonMatch) {
400
+ logger.debug('parseCurationResponse: No JSON object found in response', 'curator')
399
401
  throw new Error('No JSON object found in response')
400
402
  }
401
403
 
402
404
  // Simple parse - match Python's approach
403
405
  const data = JSON.parse(jsonMatch)
404
406
 
405
- return {
407
+ const result: CurationResult = {
406
408
  session_summary: data.session_summary ?? '',
407
409
  interaction_tone: data.interaction_tone,
408
410
  project_snapshot: data.project_snapshot ? {
@@ -417,7 +419,13 @@ Focus ONLY on technical, architectural, debugging, decision, workflow, and proje
417
419
  } : undefined,
418
420
  memories: this._parseMemories(data.memories ?? []),
419
421
  }
420
- } catch {
422
+
423
+ // Log what we extracted in verbose mode
424
+ logger.debug(`Curator parsed: ${result.memories.length} memories, summary: ${result.session_summary ? 'yes' : 'no'}, snapshot: ${result.project_snapshot ? 'yes' : 'no'}`, 'curator')
425
+
426
+ return result
427
+ } catch (error: any) {
428
+ logger.debug(`parseCurationResponse error: ${error.message}`, 'curator')
421
429
  return {
422
430
  session_summary: '',
423
431
  memories: [],
@@ -550,9 +558,17 @@ This session has ended. Please curate the memories from this conversation accord
550
558
  }
551
559
 
552
560
  if (!resultText) {
561
+ logger.debug('Curator SDK: No result text returned from Agent SDK', 'curator')
553
562
  return { session_summary: '', memories: [] }
554
563
  }
555
564
 
565
+ // Log raw response in verbose mode
566
+ logger.debug(`Curator SDK raw response (${resultText.length} chars):`, 'curator')
567
+ if (logger.isVerbose()) {
568
+ const preview = resultText.length > 3000 ? resultText.slice(0, 3000) + '...[truncated]' : resultText
569
+ console.log(preview)
570
+ }
571
+
556
572
  return this.parseCurationResponse(resultText)
557
573
  }
558
574
 
@@ -704,9 +720,21 @@ This session has ended. Please curate the memories from this conversation accord
704
720
  const exitCode = await proc.exited
705
721
 
706
722
  if (exitCode !== 0) {
723
+ logger.debug(`Curator CLI exited with code ${exitCode}`, 'curator')
724
+ if (stderr) {
725
+ logger.debug(`Curator stderr: ${stderr}`, 'curator')
726
+ }
707
727
  return { session_summary: '', memories: [] }
708
728
  }
709
729
 
730
+ // Log raw response in verbose mode
731
+ logger.debug(`Curator CLI raw stdout (${stdout.length} chars):`, 'curator')
732
+ if (logger.isVerbose()) {
733
+ // Show first 2000 chars to avoid flooding console
734
+ const preview = stdout.length > 2000 ? stdout.slice(0, 2000) + '...[truncated]' : stdout
735
+ console.log(preview)
736
+ }
737
+
710
738
  // Extract JSON from CLI output
711
739
  try {
712
740
  // First, parse the CLI JSON wrapper
@@ -718,6 +746,7 @@ This session has ended. Please curate the memories from this conversation accord
718
746
  // New format: array of events, find the one with type="result"
719
747
  resultObj = cliOutput.find((item: any) => item.type === 'result')
720
748
  if (!resultObj) {
749
+ logger.debug('Curator: No result object found in CLI output array', 'curator')
721
750
  return { session_summary: '', memories: [] }
722
751
  }
723
752
  } else {
@@ -727,6 +756,7 @@ This session has ended. Please curate the memories from this conversation accord
727
756
 
728
757
  // Check for error response FIRST (like Python does)
729
758
  if (resultObj.type === 'error' || resultObj.is_error === true) {
759
+ logger.debug(`Curator: Error response from CLI: ${JSON.stringify(resultObj).slice(0, 500)}`, 'curator')
730
760
  return { session_summary: '', memories: [] }
731
761
  }
732
762
 
@@ -735,9 +765,17 @@ This session has ended. Please curate the memories from this conversation accord
735
765
  if (typeof resultObj.result === 'string') {
736
766
  aiResponse = resultObj.result
737
767
  } else {
768
+ logger.debug(`Curator: result field is not a string: ${typeof resultObj.result}`, 'curator')
738
769
  return { session_summary: '', memories: [] }
739
770
  }
740
771
 
772
+ // Log the AI response in verbose mode
773
+ logger.debug(`Curator AI response (${aiResponse.length} chars):`, 'curator')
774
+ if (logger.isVerbose()) {
775
+ const preview = aiResponse.length > 3000 ? aiResponse.slice(0, 3000) + '...[truncated]' : aiResponse
776
+ console.log(preview)
777
+ }
778
+
741
779
  // Remove markdown code blocks if present (```json ... ```)
742
780
  const codeBlockMatch = aiResponse.match(/```(?:json)?\s*([\s\S]*?)```/)
743
781
  if (codeBlockMatch) {
@@ -747,10 +785,14 @@ This session has ended. Please curate the memories from this conversation accord
747
785
  // Now find the JSON object (same regex as Python)
748
786
  const jsonMatch = aiResponse.match(/\{[\s\S]*\}/)?.[0]
749
787
  if (jsonMatch) {
788
+ logger.debug(`Curator: Found JSON object (${jsonMatch.length} chars), parsing...`, 'curator')
750
789
  return this.parseCurationResponse(jsonMatch)
790
+ } else {
791
+ logger.debug('Curator: No JSON object found in AI response', 'curator')
751
792
  }
752
- } catch {
793
+ } catch (error: any) {
753
794
  // Parse error - return empty result
795
+ logger.debug(`Curator: Parse error: ${error.message}`, 'curator')
754
796
  }
755
797
 
756
798
  return { session_summary: '', memories: [] }