Skip to main content
Glama

search_short_term_memories

Retrieve relevant short-term memories from recent conversations to maintain context and continuity in ongoing discussions.

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
recentMessagesYesRecent messages to search against
conversation_idYesCurrent conversation ID
roleWeightsNo

Implementation Reference

  • The async handler function that executes the tool logic. It invokes ShortTermMemoryManager.searchRelevantMemories with the provided arguments, formats the results into topRelevant, nextRelevant, and randomFlashback arrays, saves the memories, and 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 schema defining the input parameters for the tool: recentMessages (array of role/content), conversation_id (string), and optional roleWeights (object).
    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()
    }),
  • src/index.js:153-154 (registration)
    Registers all short-term tools, including search_short_term_memories, by calling createShortTermTools and iterating with registerTool (default managers). Note: tools are dynamically recreated per-conversation during execution.
    const shortTermTools = createShortTermTools(defaultShortTermManager, defaultStorageManager);
    shortTermTools.forEach(tool => registerTool(tool, 'short-term'));
  • Core search logic in ShortTermMemoryManager: extracts keywords from recentMessages and modalities, calculates relevance scores (keywords match + time decay + memory score + vector similarity), filters recent/same-convo duplicates, selects top relevant (max 2), next (max 1), random flashbacks (max 2) with recency/score weights, updates scores on activation.
    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
      };
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden. It mentions the return structure ('top relevant, next relevant, and random flashback memories') which is helpful, but doesn't disclose behavioral traits like whether this is a read-only operation, performance characteristics, error conditions, or how relevance is determined. The description adds some value but leaves significant gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is efficiently structured in two sentences that cover purpose and return values. It's appropriately sized without unnecessary elaboration, though it could be slightly more specific about the search mechanism.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a search tool with 3 parameters, no annotations, and no output schema, the description provides basic purpose and return structure but lacks details about behavioral traits, parameter interactions, and error handling. The return format description is helpful but incomplete without an output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 67% (2 of 3 parameters have descriptions). The description doesn't add any parameter-specific information beyond what's in the schema. It mentions the search context but doesn't explain how parameters like 'roleWeights' affect the search. Baseline 3 is appropriate given moderate schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('search and retrieve') and resource ('short-term memories'), and specifies the context ('based on recent conversation context'). It distinguishes from some siblings like 'search_long_term_memories' by specifying the memory type, but doesn't differentiate from 'search_memories' which could be ambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage context ('based on recent conversation context') but doesn't explicitly state when to use this tool versus alternatives like 'search_long_term_memories' or 'search_memories'. No guidance on prerequisites or exclusions is provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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