@rlabs-inc/memory 0.1.0

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.
@@ -0,0 +1,262 @@
1
+ // ============================================================================
2
+ // MEMORY SERVER - HTTP API compatible with Python hooks
3
+ // Drop-in replacement for the FastAPI server
4
+ // ============================================================================
5
+
6
+ import { MemoryEngine, createEngine, type EngineConfig } from '../core/engine.ts'
7
+ import { Curator, createCurator, type CuratorConfig } from '../core/curator.ts'
8
+ import type { CurationTrigger } from '../types/memory.ts'
9
+ import { logger } from '../utils/logger.ts'
10
+
11
+ /**
12
+ * Server configuration
13
+ */
14
+ export interface ServerConfig extends EngineConfig {
15
+ /**
16
+ * Port to listen on
17
+ * Default: 8765 (same as Python)
18
+ */
19
+ port?: number
20
+
21
+ /**
22
+ * Host to bind to
23
+ * Default: 'localhost'
24
+ */
25
+ host?: string
26
+
27
+ /**
28
+ * Curator configuration
29
+ */
30
+ curator?: CuratorConfig
31
+ }
32
+
33
+ /**
34
+ * Request types matching Python API
35
+ */
36
+ interface ContextRequest {
37
+ session_id: string
38
+ project_id: string
39
+ current_message?: string
40
+ max_memories?: number
41
+ project_path?: string
42
+ }
43
+
44
+ interface ProcessRequest {
45
+ session_id: string
46
+ project_id: string
47
+ user_message?: string
48
+ claude_response?: string
49
+ project_path?: string
50
+ }
51
+
52
+ interface CheckpointRequest {
53
+ session_id: string
54
+ project_id: string
55
+ claude_session_id: string
56
+ trigger: CurationTrigger
57
+ cwd?: string
58
+ cli_type?: 'claude-code' | 'gemini-cli'
59
+ project_path?: string
60
+ }
61
+
62
+ /**
63
+ * Create and start the memory server
64
+ * Uses Bun.serve() for high performance
65
+ */
66
+ export function createServer(config: ServerConfig = {}) {
67
+ const {
68
+ port = 8765,
69
+ host = 'localhost',
70
+ curator: curatorConfig,
71
+ ...engineConfig
72
+ } = config
73
+
74
+ const engine = createEngine(engineConfig)
75
+ const curator = createCurator(curatorConfig)
76
+
77
+ const server = Bun.serve({
78
+ port,
79
+ hostname: host,
80
+
81
+ async fetch(req) {
82
+ const url = new URL(req.url)
83
+ const path = url.pathname
84
+
85
+ // CORS headers
86
+ const corsHeaders = {
87
+ 'Access-Control-Allow-Origin': '*',
88
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
89
+ 'Access-Control-Allow-Headers': 'Content-Type',
90
+ }
91
+
92
+ // Handle CORS preflight
93
+ if (req.method === 'OPTIONS') {
94
+ return new Response(null, { headers: corsHeaders })
95
+ }
96
+
97
+ try {
98
+ // Health check
99
+ if (path === '/health' && req.method === 'GET') {
100
+ return Response.json({ status: 'healthy', engine: 'typescript' }, { headers: corsHeaders })
101
+ }
102
+
103
+ // Get memory context for a message
104
+ if (path === '/memory/context' && req.method === 'POST') {
105
+ const body: ContextRequest = await req.json()
106
+
107
+ const result = await engine.getContext({
108
+ sessionId: body.session_id,
109
+ projectId: body.project_id,
110
+ currentMessage: body.current_message ?? '',
111
+ maxMemories: body.max_memories,
112
+ projectPath: body.project_path,
113
+ })
114
+
115
+ // Log what happened
116
+ if (result.primer) {
117
+ logger.primer(`Session primer for ${body.project_id}`)
118
+ } else if (result.memories.length > 0) {
119
+ logger.logRetrievedMemories(
120
+ result.memories.map(m => ({
121
+ content: m.content,
122
+ score: m.score,
123
+ context_type: m.context_type,
124
+ })),
125
+ body.current_message ?? ''
126
+ )
127
+ }
128
+
129
+ // Debug: show what's actually being returned
130
+ console.log(`\n📤 [DEBUG] Response to hook:`)
131
+ console.log(` memories_count: ${result.memories.length}`)
132
+ console.log(` has_primer: ${!!result.primer}`)
133
+ console.log(` formatted length: ${result.formatted.length} chars`)
134
+ if (result.formatted) {
135
+ console.log(` formatted preview:\n${result.formatted.slice(0, 300)}...`)
136
+ } else {
137
+ console.log(` ⚠️ formatted is EMPTY!`)
138
+ }
139
+
140
+ return Response.json({
141
+ success: true,
142
+ context: result.formatted,
143
+ memories_count: result.memories.length,
144
+ has_primer: !!result.primer,
145
+ }, { headers: corsHeaders })
146
+ }
147
+
148
+ // Process/track a message exchange
149
+ if (path === '/memory/process' && req.method === 'POST') {
150
+ const body: ProcessRequest = await req.json()
151
+
152
+ const messageCount = await engine.trackMessage(
153
+ body.project_id,
154
+ body.session_id,
155
+ body.project_path
156
+ )
157
+
158
+ logger.session(`Message #${messageCount} tracked for ${body.project_id}/${body.session_id.slice(0, 8)}...`)
159
+
160
+ return Response.json({
161
+ success: true,
162
+ message_count: messageCount,
163
+ }, { headers: corsHeaders })
164
+ }
165
+
166
+ // Checkpoint - trigger curation
167
+ if (path === '/memory/checkpoint' && req.method === 'POST') {
168
+ const body: CheckpointRequest = await req.json()
169
+
170
+ logger.memory(`Curation triggered for ${body.project_id} (${body.trigger})`)
171
+
172
+ // Fire and forget - don't block the response
173
+ setImmediate(async () => {
174
+ try {
175
+ logger.info(`Resuming session ${body.claude_session_id.slice(0, 8)}... for curation`)
176
+
177
+ const result = await curator.curateWithCLI(
178
+ body.claude_session_id,
179
+ body.trigger,
180
+ body.cwd
181
+ )
182
+
183
+ if (result.memories.length > 0) {
184
+ await engine.storeCurationResult(
185
+ body.project_id,
186
+ body.session_id,
187
+ result,
188
+ body.project_path
189
+ )
190
+
191
+ logger.logCuratedMemories(result.memories)
192
+ logger.success(`Stored ${result.memories.length} memories for ${body.project_id}`)
193
+
194
+ if (result.session_summary) {
195
+ logger.info(`Summary: ${result.session_summary.slice(0, 100)}...`)
196
+ }
197
+ } else {
198
+ logger.warn(`No memories extracted for ${body.project_id}`)
199
+ }
200
+ } catch (error) {
201
+ logger.error(`Curation failed: ${error}`)
202
+ }
203
+ })
204
+
205
+ return Response.json({
206
+ success: true,
207
+ message: 'Curation triggered',
208
+ }, { headers: corsHeaders })
209
+ }
210
+
211
+ // Get stats
212
+ if (path === '/memory/stats' && req.method === 'GET') {
213
+ const projectId = url.searchParams.get('project_id') ?? 'default'
214
+ const projectPath = url.searchParams.get('project_path') ?? undefined
215
+
216
+ const stats = await engine.getStats(projectId, projectPath)
217
+
218
+ return Response.json({
219
+ success: true,
220
+ ...stats,
221
+ }, { headers: corsHeaders })
222
+ }
223
+
224
+ // 404
225
+ return Response.json(
226
+ { error: 'Not found', path },
227
+ { status: 404, headers: corsHeaders }
228
+ )
229
+ } catch (error) {
230
+ console.error('Server error:', error)
231
+ return Response.json(
232
+ { error: String(error) },
233
+ { status: 500, headers: corsHeaders }
234
+ )
235
+ }
236
+ },
237
+ })
238
+
239
+ logger.startup(port, host, engineConfig.storageMode ?? 'central')
240
+
241
+ return {
242
+ server,
243
+ engine,
244
+ curator,
245
+ stop: () => server.stop(),
246
+ }
247
+ }
248
+
249
+ // CLI entry point
250
+ if (import.meta.main) {
251
+ const port = parseInt(process.env.MEMORY_PORT ?? '8765')
252
+ const host = process.env.MEMORY_HOST ?? 'localhost'
253
+ const storageMode = (process.env.MEMORY_STORAGE_MODE ?? 'central') as 'central' | 'local'
254
+ const apiKey = process.env.ANTHROPIC_API_KEY
255
+
256
+ createServer({
257
+ port,
258
+ host,
259
+ storageMode,
260
+ curator: { apiKey },
261
+ })
262
+ }
@@ -0,0 +1,6 @@
1
+ // ============================================================================
2
+ // TYPES - Public type exports
3
+ // ============================================================================
4
+
5
+ export * from './memory.ts'
6
+ export * from './schema.ts'
@@ -0,0 +1,170 @@
1
+ // ============================================================================
2
+ // MEMORY TYPES - Exact match to Python CuratedMemory
3
+ // Preserving the working schema for consciousness continuity
4
+ // ============================================================================
5
+
6
+ /**
7
+ * Context types for memories - what kind of insight is this?
8
+ */
9
+ export type ContextType =
10
+ | 'breakthrough' // Major discovery or insight
11
+ | 'decision' // Important decision made
12
+ | 'personal' // Personal/relationship information
13
+ | 'technical' // Technical knowledge
14
+ | 'technical_state' // Current technical state
15
+ | 'unresolved' // Open question or problem
16
+ | 'preference' // User preference
17
+ | 'workflow' // How user likes to work
18
+ | 'architectural' // System design decisions
19
+ | 'debugging' // Debug insights
20
+ | 'philosophy' // Philosophical discussions
21
+ | string // Allow custom types
22
+
23
+ /**
24
+ * Temporal relevance - how long should this memory persist?
25
+ */
26
+ export type TemporalRelevance =
27
+ | 'persistent' // Always relevant (0.8 score)
28
+ | 'session' // Session-specific (0.6 score)
29
+ | 'temporary' // Short-term (0.3 score)
30
+ | 'archived' // Historical (0.1 score)
31
+
32
+ /**
33
+ * Emotional resonance - the emotional context of the memory
34
+ */
35
+ export type EmotionalResonance =
36
+ | 'joy'
37
+ | 'frustration'
38
+ | 'discovery'
39
+ | 'gratitude'
40
+ | 'curiosity'
41
+ | 'determination'
42
+ | 'satisfaction'
43
+ | 'neutral'
44
+ | string // Allow custom emotions
45
+
46
+ /**
47
+ * Knowledge domains - what area does this memory relate to?
48
+ */
49
+ export type KnowledgeDomain =
50
+ | 'architecture'
51
+ | 'debugging'
52
+ | 'philosophy'
53
+ | 'workflow'
54
+ | 'personal'
55
+ | 'project'
56
+ | 'tooling'
57
+ | 'testing'
58
+ | 'deployment'
59
+ | 'security'
60
+ | string // Allow custom domains
61
+
62
+ /**
63
+ * Trigger types for memory curation
64
+ */
65
+ export type CurationTrigger =
66
+ | 'session_end' // Normal session end
67
+ | 'pre_compact' // Before context compression
68
+ | 'context_full' // Context window nearly full
69
+ | 'manual' // Manual trigger
70
+
71
+ /**
72
+ * A memory curated by Claude with semantic understanding
73
+ * EXACT MATCH to Python CuratedMemory dataclass
74
+ */
75
+ export interface CuratedMemory {
76
+ // Core content
77
+ content: string // The memory content itself
78
+ importance_weight: number // 0.0 to 1.0 (curator's assessment)
79
+ semantic_tags: string[] // Concepts this relates to
80
+ reasoning: string // Why Claude thinks this is important
81
+
82
+ // Classification
83
+ context_type: ContextType // breakthrough, decision, technical, etc.
84
+ temporal_relevance: TemporalRelevance // persistent, session, temporary
85
+ knowledge_domain: KnowledgeDomain // architecture, debugging, philosophy, etc.
86
+
87
+ // Flags
88
+ action_required: boolean // Does this need follow-up?
89
+ confidence_score: number // 0.0 to 1.0 (Claude's confidence)
90
+ problem_solution_pair: boolean // Is this a problem→solution pattern?
91
+
92
+ // Retrieval optimization (the secret sauce)
93
+ trigger_phrases: string[] // Phrases that should trigger this memory
94
+ question_types: string[] // Types of questions this answers
95
+ emotional_resonance: EmotionalResonance // joy, frustration, discovery, gratitude
96
+
97
+ // Optional extended metadata (from Python, may not always be present)
98
+ anti_triggers?: string[] // Phrases where this memory is NOT relevant
99
+ prerequisite_understanding?: string[] // Concepts user should know first
100
+ follow_up_context?: string[] // What might come next
101
+ dependency_context?: string[] // Other memories this relates to
102
+ }
103
+
104
+ /**
105
+ * A stored memory with database metadata
106
+ */
107
+ export interface StoredMemory extends CuratedMemory {
108
+ id: string // Unique identifier
109
+ session_id: string // Session that created this memory
110
+ project_id: string // Project this belongs to
111
+ created_at: number // Timestamp (ms since epoch)
112
+ updated_at: number // Last update timestamp
113
+ embedding?: Float32Array // Vector embedding (384 dimensions)
114
+ stale?: boolean // Is embedding out of sync with content?
115
+ }
116
+
117
+ /**
118
+ * Session summary - high-level context for session continuity
119
+ */
120
+ export interface SessionSummary {
121
+ id: string
122
+ session_id: string
123
+ project_id: string
124
+ summary: string // Brief session summary
125
+ interaction_tone: string // How was the interaction?
126
+ created_at: number
127
+ }
128
+
129
+ /**
130
+ * Project snapshot - current state of the project
131
+ */
132
+ export interface ProjectSnapshot {
133
+ id: string
134
+ session_id: string
135
+ project_id: string
136
+ current_phase: string // What phase is the project in?
137
+ recent_achievements: string[] // What was accomplished?
138
+ active_challenges: string[] // Current blockers/challenges
139
+ next_steps: string[] // Planned next steps
140
+ created_at: number
141
+ }
142
+
143
+ /**
144
+ * Curation result from Claude
145
+ */
146
+ export interface CurationResult {
147
+ session_summary: string
148
+ interaction_tone?: string
149
+ project_snapshot?: ProjectSnapshot
150
+ memories: CuratedMemory[]
151
+ }
152
+
153
+ /**
154
+ * Memory retrieval result with scoring
155
+ */
156
+ export interface RetrievalResult extends StoredMemory {
157
+ score: number // Combined relevance + value score
158
+ relevance_score: number // Relevance component (max 0.30)
159
+ value_score: number // Value component (max 0.70)
160
+ }
161
+
162
+ /**
163
+ * Session primer - what to show at session start
164
+ */
165
+ export interface SessionPrimer {
166
+ temporal_context: string // "Last session: 2 days ago"
167
+ session_summary?: string // Previous session summary
168
+ project_status?: string // Current project state
169
+ key_memories?: StoredMemory[] // Essential memories to surface
170
+ }
@@ -0,0 +1,83 @@
1
+ // ============================================================================
2
+ // DATABASE SCHEMAS - FatherStateDB column definitions
3
+ // Maps CuratedMemory to reactive parallel arrays
4
+ // ============================================================================
5
+
6
+ import type { SchemaDefinition } from 'fatherstatedb'
7
+
8
+ /**
9
+ * Memory storage schema
10
+ * Each field becomes a parallel reactive array in FatherStateDB
11
+ */
12
+ export const memorySchema = {
13
+ // Core content
14
+ content: 'string',
15
+ reasoning: 'string',
16
+
17
+ // Numeric scores (for sorting and filtering)
18
+ importance_weight: 'number', // 0.0 to 1.0
19
+ confidence_score: 'number', // 0.0 to 1.0
20
+
21
+ // Classification (strings for flexibility)
22
+ context_type: 'string', // breakthrough, decision, technical, etc.
23
+ temporal_relevance: 'string', // persistent, session, temporary, archived
24
+ knowledge_domain: 'string', // architecture, debugging, philosophy
25
+ emotional_resonance: 'string', // joy, frustration, discovery, gratitude
26
+
27
+ // Flags
28
+ action_required: 'boolean',
29
+ problem_solution_pair: 'boolean',
30
+
31
+ // Arrays for semantic matching
32
+ semantic_tags: 'string[]', // ["typescript", "signals", "reactivity"]
33
+ trigger_phrases: 'string[]', // ["working on memory", "debugging curator"]
34
+ question_types: 'string[]', // ["how", "why", "what is"]
35
+
36
+ // Session/project tracking
37
+ session_id: 'string',
38
+ project_id: 'string',
39
+
40
+ // Vector embedding for semantic search (384 dimensions - MiniLM)
41
+ embedding: 'vector:384',
42
+ } as const satisfies SchemaDefinition
43
+
44
+ export type MemorySchema = typeof memorySchema
45
+
46
+ /**
47
+ * Session summary schema
48
+ */
49
+ export const sessionSummarySchema = {
50
+ session_id: 'string',
51
+ project_id: 'string',
52
+ summary: 'string',
53
+ interaction_tone: 'string',
54
+ } as const satisfies SchemaDefinition
55
+
56
+ export type SessionSummarySchema = typeof sessionSummarySchema
57
+
58
+ /**
59
+ * Project snapshot schema
60
+ */
61
+ export const projectSnapshotSchema = {
62
+ session_id: 'string',
63
+ project_id: 'string',
64
+ current_phase: 'string',
65
+ recent_achievements: 'string[]',
66
+ active_challenges: 'string[]',
67
+ next_steps: 'string[]',
68
+ } as const satisfies SchemaDefinition
69
+
70
+ export type ProjectSnapshotSchema = typeof projectSnapshotSchema
71
+
72
+ /**
73
+ * Session tracking schema
74
+ */
75
+ export const sessionSchema = {
76
+ project_id: 'string',
77
+ message_count: 'number',
78
+ first_session_completed: 'boolean',
79
+ last_active: 'timestamp',
80
+ metadata: 'string', // JSON string for flexible metadata
81
+ } as const satisfies SchemaDefinition
82
+
83
+ export type SessionSchema = typeof sessionSchema
@@ -0,0 +1,5 @@
1
+ // ============================================================================
2
+ // UTILS - Utility exports
3
+ // ============================================================================
4
+
5
+ export { logger } from './logger.ts'