think_recall
Search through current session thoughts or past insights to find relevant decisions before tackling complex tasks.
Instructions
Search through thoughts or past insights.
Scopes:
session: Current session thoughts (default)
insights: Past successful solutions (cross-session)
Use before starting complex tasks to find relevant past decisions.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search query (fuzzy matching) | |
| scope | No | Where to search | session |
| searchIn | No | What to search (session only) | all |
| limit | No | Max results | |
| threshold | No | Match strictness (lower = stricter) |
Implementation Reference
- src/index.ts:300-358 (handler)The inline async handler function registered for the 'think_recall' MCP tool. It dispatches to session recall or insights recall based on scope, formats results with match counts and snippets, and handles errors.async (args) => { try { const scope = (args.scope as 'session' | 'insights') ?? 'session'; const query = args.query as string; const limit = (args.limit as number) ?? 3; if (scope === 'insights') { // Search past insights const result = await thinkingService.recallInsights(query, limit); if (result.matches.length === 0) { const patternsText = result.topPatterns.length > 0 ? `\n\nπ Patterns in ${result.totalInsights} insights:\n${result.topPatterns.map(p => ` β’ ${p.keyword}: ${p.count}`).join('\n')}` : ''; return { content: [{ type: 'text' as const, text: `π No insights for "${query}"${patternsText}` }] }; } const text = [ `π§ INSIGHTS for "${query}"`, `Found ${result.matches.length}/${result.totalInsights}`, '', ...result.matches.map((m, i) => [ `#${i + 1} (${Math.round((1 - m.relevance) * 100)}%)`, ` ${m.insight.summary}`, ` Keywords: ${m.insight.keywords.join(', ')}`, ].join('\n')), ].join('\n'); return { content: [{ type: 'text' as const, text }] }; } else { // Search current session const result = thinkingService.recallThought({ query, scope: 'current', searchIn: (args.searchIn as 'thoughts' | 'extensions' | 'alternatives' | 'all') ?? 'all', limit, threshold: (args.threshold as number) ?? 0.4, }); if (result.matches.length === 0) { return { content: [{ type: 'text' as const, text: `π No matches for "${query}" in ${result.totalSearched} items` }] }; } const text = [ `π RECALL "${query}"`, `Found ${result.matches.length}/${result.totalSearched}`, '', ...result.matches.map((m, i) => [ `#${i + 1} Thought #${m.thoughtNumber} (${Math.round((1 - m.relevance) * 100)}%)`, ` "${m.snippet}"`, ].join('\n')), ].join('\n'); return { content: [{ type: 'text' as const, text }] }; } } catch (error) { return { content: [{ type: 'text' as const, text: `Error: ${error instanceof Error ? error.message : 'Unknown'}` }], isError: true }; } }
- src/index.ts:291-297 (schema)Input schema (Zod object) for the think_recall tool, defining parameters like query, scope (session/insights), searchIn fields, limit, and threshold.const thinkRecallSchema = { query: z.string().min(2).describe('Search query (fuzzy matching)'), scope: z.enum(['session', 'insights']).optional().default('session').describe('Where to search'), searchIn: z.enum(['thoughts', 'extensions', 'alternatives', 'all']).optional().default('all').describe('What to search (session only)'), limit: z.number().int().min(1).max(10).optional().default(3).describe('Max results'), threshold: z.number().min(0).max(1).optional().default(0.4).describe('Match strictness (lower = stricter)'), };
- src/index.ts:299-299 (registration)The server.registerTool call that registers the 'think_recall' tool with its title, description, schema, and inline handler function.server.registerTool('think_recall', { title: 'Think Recall', description: THINK_RECALL_DESCRIPTION, inputSchema: thinkRecallSchema },
- RecallService.recallThought: Core fuzzy search logic for current session thoughts using Fuse.js. Builds searchable index from thoughts/extensions/alternatives/subSteps, performs search, extracts snippets, filters by scope.recallThought(input: RecallInput, thoughts: ThoughtRecord[]): RecallResult { const { query, scope = 'current', searchIn = 'all', limit = RECALL_DEFAULT_LIMIT, threshold = RECALL_DEFAULT_THRESHOLD, } = input; // Validate query if (!query || query.trim().length < 2) { return { matches: [], totalSearched: 0, query, searchParams: { scope, searchIn, threshold }, }; } // Rebuild index if dirty if (this.fuseIndexDirty || !this.fuseIndex) { this.rebuildFuseIndex(thoughts); } // Perform search (get more results than needed for filtering) const rawResults = this.fuseIndex?.search(query, { limit: limit * 5 }) ?? []; // Filter by threshold (Fuse returns score where lower = better match) const thresholdFiltered = rawResults.filter((r) => (r.score ?? 1) <= threshold); // Filter by searchIn parameter const filteredResults = thresholdFiltered.filter((r) => { if (searchIn === 'all') return true; if (searchIn === 'thoughts') return r.item.type === 'thought'; if (searchIn === 'extensions') return r.item.type === 'extension'; if (searchIn === 'alternatives') return r.item.type === 'alternative' || r.item.type === 'subStep'; return true; }); // Map to RecallMatch format const matches: RecallMatch[] = filteredResults.slice(0, limit).map((r) => ({ thoughtNumber: r.item.thoughtNumber, snippet: this.extractSnippet(r.item.content, query), thought: r.item.originalThought.length > 300 ? r.item.originalThought.substring(0, 300) + '...' : r.item.originalThought, confidence: r.item.confidence, relevance: r.score ?? 1, matchedIn: r.item.type, extensionType: r.item.extensionType as ExtensionType | undefined, sessionId: r.item.sessionId, })); // Log search console.error( `π Recall search: "${query}" β ${matches.length} matches (searched ${filteredResults.length} items)` ); return { matches, totalSearched: rawResults.length, query, searchParams: { scope, searchIn, threshold }, }; }
- InsightsService.search: Core search logic for past insights (cross-session winning paths). Uses Fuse.js on summaries, goals, keywords; provides top patterns and stats.async search(query: string, limit = 3): Promise<InsightsSearchResult> { if (!this.data) await this.load(); if (!this.fuseIndex || this.data!.winningPaths.length === 0) { return { matches: [], totalInsights: 0, topPatterns: [], }; } // Search using Fuse.js const results = this.fuseIndex.search(query, { limit }); const matches: InsightMatch[] = results.map(r => ({ insight: r.item, relevance: r.score ?? 1, })); // Get top patterns const topPatterns = Object.entries(this.data!.patterns) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([keyword, count]) => ({ keyword, count })); return { matches, totalInsights: this.data!.winningPaths.length, topPatterns, }; }