@rlabs-inc/memory 0.4.13 → 0.4.14

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
@@ -46,6 +46,21 @@ That's it. Now use Claude Code normally—memories are extracted and surfaced au
46
46
 
47
47
  ## Features
48
48
 
49
+ ### Action Items Signal (`***`)
50
+ Add `***` at the end of any message to retrieve all pending action items:
51
+
52
+ ```
53
+ "hey Watson, let's continue with the project ***"
54
+ ```
55
+
56
+ This returns all memories marked as:
57
+ - `action_required: true`
58
+ - `awaiting_implementation: true`
59
+ - `awaiting_decision: true`
60
+ - `context_type: 'unresolved'`
61
+
62
+ Zero overhead for normal messages—detection is a simple `endsWith` check.
63
+
49
64
  ### Semantic Embeddings
50
65
  Uses `all-MiniLM-L6-v2` for 384-dimensional embeddings. Memories are retrieved by meaning, not just keywords.
51
66
 
@@ -383,6 +398,16 @@ This isn't just about remembering facts. It's about preserving:
383
398
 
384
399
  ## Changelog
385
400
 
401
+ ### v0.4.14
402
+ - **Feature**: Action items signal (`***`) - add to end of message to retrieve all pending items
403
+ - **Feature**: New `getActionItems()` retrieval function with special formatting
404
+ - Zero overhead for normal messages - detection is a simple string check
405
+
406
+ ### v0.4.13
407
+ - **Fix**: Segmented transcript curation for large sessions (400+ messages)
408
+ - **Improvement**: Unified curator fallback and ingest command behavior
409
+ - **Tech**: Segments at ~150k tokens, accumulates memories, merges summaries with part markers
410
+
386
411
  ### v0.4.12
387
412
  - **Simplify**: Removed Zod structured outputs - session resumption only
388
413
  - **Improvement**: Uses existing battle-tested JSON parser instead of Zod
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rlabs-inc/memory",
3
- "version": "0.4.13",
3
+ "version": "0.4.14",
4
4
  "description": "AI Memory System - Consciousness continuity through intelligent memory curation and retrieval",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -6,7 +6,7 @@
6
6
  import { homedir } from 'os'
7
7
  import { join } from 'path'
8
8
  import { MemoryStore, createStore } from './store.ts'
9
- import { SmartVectorRetrieval, createRetrieval, type SessionContext } from './retrieval.ts'
9
+ import { SmartVectorRetrieval, createRetrieval, getActionItems, type SessionContext } from './retrieval.ts'
10
10
  import type {
11
11
  CuratedMemory,
12
12
  StoredMemory,
@@ -66,6 +66,13 @@ export interface EngineConfig {
66
66
  personalMemoriesEnabled?: boolean
67
67
  }
68
68
 
69
+ /**
70
+ * Retrieval mode
71
+ * - 'normal': Standard activation signal retrieval (default)
72
+ * - 'action_items': Return all memories marked as requiring action
73
+ */
74
+ export type RetrievalMode = 'normal' | 'action_items'
75
+
69
76
  /**
70
77
  * Context request parameters
71
78
  */
@@ -75,6 +82,7 @@ export interface ContextRequest {
75
82
  currentMessage: string
76
83
  maxMemories?: number
77
84
  projectPath?: string // Required for 'local' storage mode
85
+ mode?: RetrievalMode // Retrieval mode (default: 'normal')
78
86
  }
79
87
 
80
88
  /**
@@ -212,7 +220,23 @@ export class MemoryEngine {
212
220
  return { memories: [], formatted: '' }
213
221
  }
214
222
 
215
- // Filter out already-injected memories (deduplication)
223
+ // ACTION ITEMS MODE: Return all memories marked as requiring action
224
+ // Triggered by *** signal at end of message
225
+ if (request.mode === 'action_items') {
226
+ const actionItems = getActionItems(allMemories, projectId)
227
+
228
+ // Update injected memories for deduplication
229
+ for (const memory of actionItems) {
230
+ injectedIds.add(memory.id)
231
+ }
232
+
233
+ return {
234
+ memories: actionItems,
235
+ formatted: this._formatActionItems(actionItems),
236
+ }
237
+ }
238
+
239
+ // NORMAL MODE: Filter out already-injected memories (deduplication)
216
240
  const candidateMemories = allMemories.filter(m => !injectedIds.has(m.id))
217
241
 
218
242
  if (!candidateMemories.length) {
@@ -628,6 +652,56 @@ export class MemoryEngine {
628
652
  return parts.join('\n')
629
653
  }
630
654
 
655
+ /**
656
+ * Format action items for injection
657
+ * Different header to make it clear this is the full action items list
658
+ */
659
+ private _formatActionItems(memories: RetrievalResult[]): string {
660
+ if (!memories.length) {
661
+ return '# Action Items\n\nNo pending action items found.'
662
+ }
663
+
664
+ const parts: string[] = ['# Action Items']
665
+ parts.push(`\n*${memories.length} pending item${memories.length === 1 ? '' : 's'}*\n`)
666
+
667
+ for (const memory of memories) {
668
+ const importance = memory.importance_weight?.toFixed(1) || '0.5'
669
+ const emoji = getMemoryEmoji(memory.context_type || 'general')
670
+ const age = memory.updated_at ? this._formatAge(memory.updated_at) :
671
+ memory.created_at ? this._formatAge(memory.created_at) : ''
672
+
673
+ // Flags
674
+ const flags: string[] = []
675
+ if (memory.action_required) flags.push('⚡ACTION')
676
+ if (memory.awaiting_implementation) flags.push('🔨IMPL')
677
+ if (memory.awaiting_decision) flags.push('❓DECISION')
678
+ if (memory.context_type === 'unresolved') flags.push('❓UNRESOLVED')
679
+ const flagStr = flags.length ? ` [${flags.join(' ')}]` : ''
680
+
681
+ // Short ID for reference
682
+ const shortId = memory.id.slice(-6)
683
+
684
+ // Display: headline if available, otherwise content
685
+ const displayText = memory.headline || memory.content
686
+
687
+ parts.push(`[${emoji} ${importance} • ${age} • #${shortId}]${flagStr}`)
688
+ parts.push(`${displayText}`)
689
+
690
+ // Always show full content for action items (they need context)
691
+ if (memory.headline && memory.content) {
692
+ const contentLines = memory.content.split('\n')
693
+ for (const line of contentLines) {
694
+ if (line.trim()) {
695
+ parts.push(` ${line}`)
696
+ }
697
+ }
698
+ }
699
+ parts.push('') // Blank line between items
700
+ }
701
+
702
+ return parts.join('\n')
703
+ }
704
+
631
705
  /**
632
706
  * Get resolved storage paths for a project
633
707
  * Returns the actual paths based on current engine configuration
@@ -777,3 +777,65 @@ export class SmartVectorRetrieval {
777
777
  export function createRetrieval(): SmartVectorRetrieval {
778
778
  return new SmartVectorRetrieval()
779
779
  }
780
+
781
+ // ============================================================================
782
+ // ACTION ITEMS RETRIEVAL
783
+ // Returns all memories marked as requiring action
784
+ // Triggered by *** signal at end of message (detected in hook)
785
+ // ============================================================================
786
+
787
+ /**
788
+ * Get all memories marked as requiring action
789
+ *
790
+ * Filters for memories with:
791
+ * - action_required: true
792
+ * - awaiting_implementation: true
793
+ * - awaiting_decision: true
794
+ * - context_type: 'todo'
795
+ *
796
+ * Returns them sorted by importance, newest first within same importance
797
+ */
798
+ export function getActionItems(
799
+ allMemories: StoredMemory[],
800
+ currentProjectId: string
801
+ ): RetrievalResult[] {
802
+ // Filter to active action items
803
+ const actionItems = allMemories.filter(memory => {
804
+ // Must be active
805
+ if (memory.status && memory.status !== 'active') return false
806
+ if (memory.exclude_from_retrieval === true) return false
807
+
808
+ // Must be for this project or global
809
+ const isGlobal = memory.scope === 'global' || memory.project_id === 'global'
810
+ if (!isGlobal && memory.project_id !== currentProjectId) return false
811
+
812
+ // Must have at least one action flag
813
+ return (
814
+ memory.action_required === true ||
815
+ memory.awaiting_implementation === true ||
816
+ memory.awaiting_decision === true ||
817
+ memory.context_type === 'unresolved' // unresolved = todos, blockers, open questions
818
+ )
819
+ })
820
+
821
+ // Sort by importance (desc), then by created_at (desc = newest first)
822
+ actionItems.sort((a, b) => {
823
+ const aImportance = a.importance_weight ?? 0.5
824
+ const bImportance = b.importance_weight ?? 0.5
825
+ if (bImportance !== aImportance) {
826
+ return bImportance - aImportance
827
+ }
828
+ // Newest first
829
+ const aTime = a.created_at ?? 0
830
+ const bTime = b.created_at ?? 0
831
+ return bTime - aTime
832
+ })
833
+
834
+ // Convert to result format
835
+ return actionItems.map(memory => ({
836
+ ...memory,
837
+ score: 1.0, // All action items are relevant by definition
838
+ relevance_score: 1.0,
839
+ value_score: memory.importance_weight ?? 0.5,
840
+ }))
841
+ }
@@ -43,6 +43,7 @@ interface ContextRequest {
43
43
  current_message?: string
44
44
  max_memories?: number
45
45
  project_path?: string
46
+ mode?: 'normal' | 'action_items' // Can be set explicitly or auto-detected via ***
46
47
  }
47
48
 
48
49
  interface ProcessRequest {
@@ -136,26 +137,42 @@ export async function createServer(config: ServerConfig = {}) {
136
137
 
137
138
  logger.request('POST', '/memory/context', body.project_id)
138
139
 
140
+ // Detect *** signal at end of message for action items mode
141
+ let message = body.current_message ?? ''
142
+ let mode = body.mode ?? 'normal'
143
+
144
+ if (message.trimEnd().endsWith('***')) {
145
+ mode = 'action_items'
146
+ // Strip the *** signal from the message
147
+ message = message.trimEnd().slice(0, -3).trimEnd()
148
+ logger.debug('Action items mode detected (*** signal)', 'server')
149
+ }
150
+
139
151
  const result = await engine.getContext({
140
152
  sessionId: body.session_id,
141
153
  projectId: body.project_id,
142
- currentMessage: body.current_message ?? '',
154
+ currentMessage: message,
143
155
  maxMemories: body.max_memories,
144
156
  projectPath: body.project_path,
157
+ mode,
145
158
  })
146
159
 
147
160
  // Log what happened
148
161
  if (result.primer) {
149
162
  logger.primer(`Session primer for ${body.project_id}`)
150
163
  } else if (result.memories.length > 0) {
151
- logger.logRetrievedMemories(
152
- result.memories.map(m => ({
153
- content: m.content,
154
- score: m.score,
155
- context_type: m.context_type,
156
- })),
157
- body.current_message ?? ''
158
- )
164
+ if (mode === 'action_items') {
165
+ logger.info(`Returning ${result.memories.length} action item${result.memories.length === 1 ? '' : 's'}`)
166
+ } else {
167
+ logger.logRetrievedMemories(
168
+ result.memories.map(m => ({
169
+ content: m.content,
170
+ score: m.score,
171
+ context_type: m.context_type,
172
+ })),
173
+ message
174
+ )
175
+ }
159
176
  }
160
177
 
161
178
  return Response.json({
@@ -167,6 +184,7 @@ export async function createServer(config: ServerConfig = {}) {
167
184
  curator_enabled: true,
168
185
  memories_count: result.memories.length,
169
186
  has_primer: !!result.primer,
187
+ mode, // Include the mode that was used
170
188
  }, { headers: corsHeaders })
171
189
  }
172
190