remember
Automatically categorize and store user preferences, facts, and corrections. Deduplicates and applies time-based expiry to optimize memory relevance.
Instructions
IMPORTANT: Call this whenever the user reveals a preference, fact about themselves, correction, or recurring interest. Auto-categorizes if no category given. Auto-deduplicates similar content. Categories: one-time (7d), question (14d), interest (60d), preference (180d), correction (365d), fact (365d), context (30d).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| content | Yes | What to remember. Keep concise — under 20 words. | |
| category | No | Optional. Auto-detected if omitted. | |
| tags | No | Optional comma-separated tags. |
Implementation Reference
- index.js:173-247 (handler)Main handler for the 'remember' tool. Takes content (required), optional category and tags. Auto-deduplicates by semantic similarity: supersedes contradictory preferences, reinforces similar memories, or creates a new memory.
function handleRemember(args) { const { content, category = null, tags = '' } = args; if (!content) return { error: 'Missing "content"' }; const memories = loadMemories(); // Semantic dedup: find most similar existing memory let bestMatch = null; let bestScore = 0; for (const m of memories) { const score = similarity(content, m.content); if (score > bestScore) { bestScore = score; bestMatch = m; } } // Conflict detection FIRST: preferences that overlap but differ = supersede // "prefers dark mode" and "prefers light mode" are similar but contradictory const inferredCat = category && CATEGORY_CONFIG[category] ? category : autoCategory(content); if (inferredCat === 'preference' && bestMatch && bestScore > 0.25 && bestMatch.category === 'preference' && content.toLowerCase() !== bestMatch.content.toLowerCase()) { const old = bestMatch.content; bestMatch.content = content; bestMatch.last_reinforced = Date.now(); bestMatch.mention_count += 1; saveMemories(memories); const rel = computeRelevance(bestMatch); return { action: 'superseded', id: bestMatch.id, old_content: old, new_content: content, relevance: rel.relevance, message: `Preference updated: "${old}" → "${content}"` }; } // High similarity (>0.5) = reinforce existing (not a preference conflict) if (bestMatch && bestScore > 0.5) { reinforceMemory(bestMatch); // Keep the longer/newer content if (content.length > bestMatch.content.length) { bestMatch.content = content; } saveMemories(memories); const rel = computeRelevance(bestMatch); return { action: 'reinforced_existing', id: bestMatch.id, mention_count: bestMatch.mention_count, category: bestMatch.category, relevance: rel.relevance, status: rel.status, similarity: Math.round(bestScore * 100) + '%', message: `Reinforced (mention #${bestMatch.mention_count}, ${Math.round(bestScore * 100)}% similar).${bestMatch.mention_count >= 5 && bestMatch.category === 'interest' ? ' Upgraded to interest.' : ''}` }; } // New memory const memory = createMemory(content, category, tags); memories.push(memory); saveMemories(memories); const rel = computeRelevance(memory); return { action: 'created', id: memory.id, category: memory.category, auto_categorized: !category, relevance: rel.relevance, decay_halflife: `${memory.decay_halflife_days} days`, message: `Stored as "${memory.category}"${!category ? ' (auto)' : ''}. Fades 50% in ${memory.decay_halflife_days}d.` }; } - index.js:419-431 (schema)Input schema definition for the 'remember' tool, registered via getToolDefinitions(). Requires 'content', optional 'category' (enum from CATEGORY_CONFIG) and 'tags'.
{ name: 'remember', description: 'IMPORTANT: Call this whenever the user reveals a preference, fact about themselves, correction, or recurring interest. Auto-categorizes if no category given. Auto-deduplicates similar content. Categories: one-time (7d), question (14d), interest (60d), preference (180d), correction (365d), fact (365d), context (30d).', inputSchema: { type: 'object', properties: { content: { type: 'string', description: 'What to remember. Keep concise — under 20 words.' }, category: { type: 'string', enum: Object.keys(CATEGORY_CONFIG), description: 'Optional. Auto-detected if omitted.' }, tags: { type: 'string', description: 'Optional comma-separated tags.' } }, required: ['content'] } }, - index.js:509-523 (registration)Tool dispatch inside handleRequest(): routes the 'remember' tool name to handleRemember().
switch (name) { case 'remember': result = handleRemember(args); break; case 'recall': result = handleRecall(args); break; case 'forget': result = handleForget(args); break; case 'inspect': result = handleInspect(); break; // Backwards compat: old tools still work case 'reinforce': result = handleRemember({ content: args.content || args.id, category: null }); break; case 'prune': result = handleRecall({ query: '', limit: 0 }); break; case 'stats': result = handleInspect(); break; default: result = { error: `Unknown tool: ${name}` }; } } catch (e) { result = { error: e.message }; } return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }}; - index.js:159-167 (helper)Reinforcement helper called by handleRemember() when a similar memory is found. Increments mention_count and upgrades 'question' to 'interest' after 5 mentions.
function reinforceMemory(memory) { memory.mention_count += 1; memory.last_reinforced = Date.now(); if (memory.mention_count >= 5 && memory.category === 'question') { memory.category = 'interest'; memory.base_weight = CATEGORY_CONFIG['interest'].base_weight; memory.decay_halflife_days = CATEGORY_CONFIG['interest'].decay_halflife_days; } } - index.js:104-120 (helper)Helper called by handleRemember() when creating a new memory. Auto-categorizes content, generates a SHA256-based ID, and sets decay parameters based on category.
function createMemory(content, category = null, tags = []) { const now = Date.now(); const cat = category && CATEGORY_CONFIG[category] ? category : autoCategory(content); const config = CATEGORY_CONFIG[cat]; return { id: createHash('sha256').update(content + now.toString()).digest('hex').slice(0, 12), content, category: cat, tags: typeof tags === 'string' ? tags.split(',').map(t => t.trim()).filter(Boolean) : tags, created_at: now, last_reinforced: now, mention_count: 1, base_weight: config.base_weight, decay_halflife_days: config.decay_halflife_days, }; }