recall
Retrieve memories by topic, with automatic reinforcement of strong matches and pruning of irrelevant ones. Leave query empty to get all active memories ranked by relevance.
Instructions
Search memories by topic. Auto-reinforces strong matches. Auto-prunes dead memories. Leave query empty to get all active memories ranked by relevance.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | Search query. Leave empty for all. | |
| limit | No | Max results. Default: 10. | |
| min_relevance | No | Min relevance 0-1. Default: 0.05. |
Implementation Reference
- index.js:249-322 (handler)The handleRecall function is the core handler for the 'recall' tool. It loads memories, auto-prunes dead ones (relevance < 0.01), scores each memory by a combination of relevance decay and query similarity (using bigram overlap), filters/sorts by final_score, and auto-reinforces the top match if match_score > 0.25. Returns query info, results list, counts, and a message.
function handleRecall(args) { const { query = '', limit = 10, min_relevance = 0.05 } = args; let memories = loadMemories(); const now = Date.now(); let pruned = 0; // Auto-prune dead memories on every recall const before = memories.length; memories = memories.filter(m => computeRelevance(m, now).relevance >= 0.01); pruned = before - memories.length; if (pruned > 0) saveMemories(memories); // Score all memories let scored = memories.map(m => { const rel = computeRelevance(m, now); let match_score = 0; if (query) { // Use similarity function instead of basic substring match_score = similarity(query, m.content); // Also check tags const tagText = (m.tags || []).join(' '); if (tagText) { match_score = Math.max(match_score, similarity(query, tagText) * 0.8); } } else { match_score = 1.0; // No query = return all } const final_score = rel.relevance * (0.3 + 0.7 * match_score); return { _memory: m, // internal ref for auto-reinforce id: m.id, content: m.content, category: m.category, tags: m.tags, mention_count: m.mention_count, relevance: rel.relevance, match_score: Math.round(match_score * 100) / 100, final_score: Math.round(final_score * 1000) / 1000, age_days: rel.age_days, status: rel.status, decay: rel.decay, }; }); // Filter and sort scored = scored .filter(s => s.final_score >= min_relevance) .sort((a, b) => b.final_score - a.final_score) .slice(0, limit); // Auto-reinforce: if query matched well enough to be the top result, it's a mention if (query && scored.length > 0 && scored[0].match_score > 0.25) { reinforceMemory(scored[0]._memory); saveMemories(memories); scored[0].mention_count = scored[0]._memory.mention_count; } // Strip internal refs from output const results = scored.map(({ _memory, ...rest }) => rest); return { query: query || '(all)', results, total_memories: memories.length, returned: results.length, auto_pruned: pruned, message: results.length === 0 ? 'No relevant memories found.' : `${results.length} memories found.${pruned > 0 ? ` ${pruned} dead memories pruned.` : ''}` }; } - index.js:432-443 (schema)Tool definition schema for 'recall' in getToolDefinitions(). Name 'recall', description explaining auto-reinforce/auto-prune behavior, and inputSchema with optional query (string), limit (number, default 10), and min_relevance (number, default 0.05).
{ name: 'recall', description: 'Search memories by topic. Auto-reinforces strong matches. Auto-prunes dead memories. Leave query empty to get all active memories ranked by relevance.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query. Leave empty for all.' }, limit: { type: 'number', description: 'Max results. Default: 10.' }, min_relevance: { type: 'number', description: 'Min relevance 0-1. Default: 0.05.' } } } }, - index.js:511-511 (registration)The 'recall' case in the tools/call switch statement inside handleRequest. Dispatches to handleRecall(args) when the tool name matches 'recall'.
case 'recall': result = handleRecall(args); break; - index.js:159-167 (helper)The reinforceMemory helper function, shared by remember and recall. Increments mention_count, updates last_reinforced timestamp, and upgrades category from 'question' to 'interest' if mention_count >= 5.
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:78-98 (helper)The similarity function (tokenize + bigram-based overlap) used by handleRecall to match queries against memory content and tags.
function tokenize(text) { return text.toLowerCase().replace(/[^a-z0-9 ]/g, '').split(/\s+/).filter(w => w.length > 1); } function similarity(a, b) { const wordsA = tokenize(a); const wordsB = tokenize(b); if (wordsA.length === 0 || wordsB.length === 0) return 0; // Build bigram sets (unigrams + bigrams) const setA = new Set(wordsA); const setB = new Set(wordsB); for (let i = 0; i < wordsA.length - 1; i++) setA.add(wordsA[i] + ' ' + wordsA[i + 1]); for (let i = 0; i < wordsB.length - 1; i++) setB.add(wordsB[i] + ' ' + wordsB[i + 1]); let overlap = 0; for (const gram of setA) { if (setB.has(gram)) overlap++; } return overlap / Math.max(setA.size, setB.size); }