Skip to main content
Glama

search_short_term_memories

Find relevant recent conversation memories by searching against current message context. Retrieve top matches, related items, and random flashbacks from short-term memory storage.

Instructions

Search and retrieve relevant short-term memories based on recent conversation context. Returns top relevant, next relevant, and random flashback memories.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
recentMessagesYes
conversation_idYes
roleWeightsNo

Implementation Reference

  • MCP tool handler: Calls ShortTermMemoryManager.searchRelevantMemories, formats results (topRelevant, nextRelevant, randomFlashback), saves memories, handles errors.
    handler: async (args) => { try { const results = await memoryManager.searchRelevantMemories( args.recentMessages, args.conversation_id, { roleWeights: args.roleWeights } ); // Format memories for output const formatMemory = (item) => ({ text: item.memory.text, conversation_id: item.memory.conversation_id, timestamp: item.memory.time_stamp.toISOString(), score: item.memory.score, relevance: item.relevance, keywords: item.memory.keywords.slice(0, 10).map(kw => kw.word), modalities: item.memory.modalities || [], attachments: item.memory.attachments || [] }); await storageManager.saveShortTermMemories(memoryManager.getMemories()); return { topRelevant: results.topRelevant.map(formatMemory), nextRelevant: results.nextRelevant.map(formatMemory), randomFlashback: results.randomFlashback.map(formatMemory), totalSearched: memoryManager.getMemories().length }; } catch (error) { return { error: error.message }; } }
  • Zod input schema for the tool: recentMessages array, conversation_id, optional roleWeights.
    inputSchema: z.object({ recentMessages: z.array(z.object({ role: z.enum(['user', 'assistant', 'system']), content: z.string() })).describe('Recent messages to search against'), conversation_id: z.string().describe('Current conversation ID'), roleWeights: z.object({ user: z.number().optional(), assistant: z.number().optional(), system: z.number().optional() }).optional() }),
  • Core logic: Extracts keywords/modalities, computes relevance (keywords, time decay, score, vector sim), selects filtered/sorted top/next/random memories with deduplication and score reinforcement.
    async searchRelevantMemories(recentMessages, conversationId, options = {}) { const currentTimeStamp = Date.now(); const queryModalities = normalizeModalities(options.queryModalities ?? options.attachments ?? []); // 提取当前对话的关键词 const messageKeywords = await this.extractMessageKeywords(recentMessages, options.roleWeights); const modalityKeywords = collectModalityKeywords(queryModalities); const currentKeywords = [...messageKeywords, ...modalityKeywords]; const searchOptions = { ...options, queryModalities }; if ('attachments' in searchOptions) { delete searchOptions.attachments; } // 计算所有记忆的相关性 const scoredMemories = this.memories .map((mem, index) => ({ memory: mem, relevance: this.calculateRelevance(mem, currentKeywords, currentTimeStamp, searchOptions), index })) .filter(item => item.relevance >= RELEVANCE_THRESHOLD) .sort((a, b) => b.relevance - a.relevance); // 选择相关和次相关记忆 const selectedIndices = new Set(); const finalTopRelevant = []; const finalNextRelevant = []; const allSelectedRelevantMemories = []; for (const candidateMemory of scoredMemories) { if (finalTopRelevant.length >= MAX_TOP_RELEVANT && finalNextRelevant.length >= MAX_NEXT_RELEVANT) { break; } const isFromSameConversation = candidateMemory.memory.conversation_id === conversationId; const timeDiffSinceMemory = currentTimeStamp - candidateMemory.memory.time_stamp.getTime(); // 跳过同对话的近期记忆 if (isFromSameConversation && timeDiffSinceMemory < MIN_TIME_DIFFERENCE_SAME_CONVERSATION_MS) { continue; } // 检查是否与已选记忆时间过近 let isTooCloseToSelected = false; for (const selectedMem of allSelectedRelevantMemories) { if (Math.abs(candidateMemory.memory.time_stamp.getTime() - selectedMem.memory.time_stamp.getTime()) < MIN_TIME_DIFFERENCE_ANY_MS) { isTooCloseToSelected = true; break; } } if (isTooCloseToSelected) continue; // 分配到 Top 或 Next if (finalTopRelevant.length < MAX_TOP_RELEVANT) { finalTopRelevant.push(candidateMemory); allSelectedRelevantMemories.push(candidateMemory); selectedIndices.add(candidateMemory.index); } else if (finalNextRelevant.length < MAX_NEXT_RELEVANT) { finalNextRelevant.push(candidateMemory); allSelectedRelevantMemories.push(candidateMemory); selectedIndices.add(candidateMemory.index); } } // 随机闪回选择 const finalRandomFlashback = []; let availableForRandomPool = this.memories .map((mem, index) => ({ memory: mem, index })) .filter(item => !selectedIndices.has(item.index)); for (let i = 0; i < MAX_RANDOM_FLASHBACK && availableForRandomPool.length; i++) { const currentCandidates = availableForRandomPool.filter(candidate => { const isFromSameConversation = candidate.memory.conversation_id === conversationId; const timeDiffSinceMemory = currentTimeStamp - candidate.memory.time_stamp.getTime(); if (isFromSameConversation && timeDiffSinceMemory < MIN_TIME_DIFFERENCE_SAME_CONVERSATION_MS) { return false; } const allPreviouslySelected = [...allSelectedRelevantMemories, ...finalRandomFlashback]; for (const selectedItem of allPreviouslySelected) { if (Math.abs(candidate.memory.time_stamp.getTime() - selectedItem.memory.time_stamp.getTime()) < MIN_TIME_DIFFERENCE_ANY_MS) { return false; } } return true; }); if (!currentCandidates.length) break; // 计算权重 const weights = currentCandidates.map(item => { const ageFactor = Math.max(0, 1 - (currentTimeStamp - item.memory.time_stamp.getTime()) / MEMORY_TTL_MS); const cappedScore = Math.max(0, Math.min(item.memory.score, MAX_SCORE_FOR_RANDOM_WEIGHT)); const normalizedScoreFactor = MAX_SCORE_FOR_RANDOM_WEIGHT > 0 ? cappedScore / MAX_SCORE_FOR_RANDOM_WEIGHT : 0; const weight = BASE_RANDOM_WEIGHT + ageFactor * RANDOM_WEIGHT_RECENCY_FACTOR + normalizedScoreFactor * RANDOM_WEIGHT_SCORE_FACTOR; return Math.max(0, weight); }); const selectedRandomItem = this.selectOneWeightedRandom(currentCandidates, weights); if (selectedRandomItem) { selectedRandomItem.relevance = this.calculateRelevance(selectedRandomItem.memory, currentKeywords, currentTimeStamp, searchOptions); finalRandomFlashback.push(selectedRandomItem); availableForRandomPool = availableForRandomPool.filter(item => item.index !== selectedRandomItem.index); } else { break; } } // 强化被激活的记忆 finalTopRelevant.forEach(item => { const memoryToUpdate = this.memories[item.index]; if (memoryToUpdate) { memoryToUpdate.score = Math.min(memoryToUpdate.score + SCORE_INCREMENT_TOP, 100); } }); finalNextRelevant.forEach(item => { const memoryToUpdate = this.memories[item.index]; if (memoryToUpdate) { memoryToUpdate.score = Math.min(memoryToUpdate.score + SCORE_INCREMENT_NEXT, 100); } }); return { topRelevant: finalTopRelevant, nextRelevant: finalNextRelevant, randomFlashback: finalRandomFlashback }; }
  • src/index.js:284-289 (registration)
    Dynamic tool recreation and handler invocation for short-term tools (including search_short_term_memories) per conversation during MCP call_tool request handling.
    if (toolScope === 'short-term' || toolName.includes('short_term')) { manager = await getShortTermManager(conversationId); storage = getStorageManager(conversationId); const tools = createShortTermTools(manager, storage, queryCache); const tool = tools.find(t => t.name === toolName); result = await withTimeout(tool.handler(validatedArgs), timeout, `Tool ${toolName} timeout`);

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/win10ogod/memory-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server