limitless_search_conversations_about
Search all historical conversations from Limitless Pendant recordings using natural language queries, filtering by time, speaker, and content type to find specific discussions.
Instructions
Advanced search across ALL lifelogs (not just recent) with intelligent context, relevance scoring, and comprehensive filtering options. Perfect for finding historical discussions.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| search_term | Yes | Text to search for across ALL lifelogs (not just recent ones). | |
| time_expression | No | Natural time range like 'this week', 'past month', 'today' to limit search scope. | |
| speaker_name | No | Filter results to specific speaker/participant. | |
| content_types | No | Filter by content node types like ['heading1', 'heading2', 'blockquote']. | |
| include_context | No | Include surrounding context for better understanding. | |
| max_results | No | Maximum number of results to return. | |
| timezone | No | IANA timezone for time calculations. |
Implementation Reference
- src/server.ts:758-794 (registration)Tool registration for 'limitless_search_conversations_about', including thin wrapper handler that delegates to the main implementation in AdvancedSearch.searchConversationsAbout// Advanced Search Tool server.tool("limitless_search_conversations_about", "Advanced search across ALL lifelogs (not just recent) with intelligent context, relevance scoring, and comprehensive filtering options. Perfect for finding historical discussions.", AdvancedSearchArgsSchema, async (args, _extra) => { try { let timeRange = undefined; if (args.time_expression) { const parser = new NaturalTimeParser({ timezone: args.timezone }); timeRange = parser.parseTimeExpression(args.time_expression); } const searchOptions: SearchOptions = { includeContext: args.include_context, maxResults: args.max_results, searchInSpeaker: args.speaker_name, searchInContentType: args.content_types, timeRange }; const results = await AdvancedSearch.searchConversationsAbout( limitlessApiKey, args.search_term, searchOptions ); const resultText = results.length === 0 ? `No conversations found about "${args.search_term}".` : `Found ${results.length} relevant conversation(s) about "${args.search_term}":\n\n${JSON.stringify(results, null, 2)}`; return { content: [{ type: "text", text: resultText }] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error searching conversations: ${errorMessage}` }], isError: true }; } } );
- src/search-and-analytics.ts:33-110 (handler)Core handler function implementing the search logic: fetches lifelogs across time range, identifies relevant content nodes containing the search term (with speaker/content type filters), computes relevance scores, extracts surrounding context, generates summaries, and returns top scored results.static async searchConversationsAbout( apiKey: string, searchTerm: string, options: SearchOptions = {} ): Promise<TopicSearchResult[]> { const { includeContext = true, contextLines = 2, maxResults = 20, minRelevanceScore = 0.3, searchInSpeaker, searchInContentType, timeRange } = options; // Fetch all relevant lifelogs const lifelogParams: any = {}; if (timeRange) { lifelogParams.start = timeRange.start; lifelogParams.end = timeRange.end; lifelogParams.timezone = timeRange.timezone; } // Use a large limit to search through more history lifelogParams.limit = 1000; lifelogParams.includeMarkdown = true; lifelogParams.includeHeadings = true; const allLifelogs = await getLifelogs(apiKey, lifelogParams); const results: TopicSearchResult[] = []; const searchTermLower = searchTerm.toLowerCase(); for (const lifelog of allLifelogs) { const matchingNodes = this.findRelevantNodes( lifelog, searchTermLower, searchInSpeaker, searchInContentType ); if (matchingNodes.length === 0) continue; // Calculate relevance score const relevanceScore = this.calculateRelevanceScore(matchingNodes, searchTermLower); if (relevanceScore < minRelevanceScore) continue; // Get context if requested let contextBefore: LifelogContentNode[] = []; let contextAfter: LifelogContentNode[] = []; if (includeContext && lifelog.contents) { const { before, after } = this.extractContext( lifelog.contents, matchingNodes, contextLines ); contextBefore = before; contextAfter = after; } results.push({ lifelog, relevantNodes: matchingNodes, contextBefore, contextAfter, relevanceScore, summary: this.generateSearchSummary(lifelog, matchingNodes, searchTerm) }); } // Sort by relevance and return top results return results .sort((a, b) => b.relevanceScore - a.relevanceScore) .slice(0, maxResults); }
- src/server.ts:237-245 (schema)Zod schema defining the input arguments for the tool registration.const AdvancedSearchArgsSchema = { search_term: z.string().describe("Text to search for across ALL lifelogs (not just recent ones)."), time_expression: z.string().optional().describe("Natural time range like 'this week', 'past month', 'today' to limit search scope."), speaker_name: z.string().optional().describe("Filter results to specific speaker/participant."), content_types: z.array(z.string()).optional().describe("Filter by content node types like ['heading1', 'heading2', 'blockquote']."), include_context: z.boolean().optional().default(true).describe("Include surrounding context for better understanding."), max_results: z.number().optional().default(20).describe("Maximum number of results to return."), timezone: z.string().optional().describe("IANA timezone for time calculations."), };
- src/search-and-analytics.ts:18-26 (schema)TypeScript interface defining options passed to the search handler, mapping from tool args.export interface SearchOptions { includeContext?: boolean; contextLines?: number; maxResults?: number; minRelevanceScore?: number; searchInSpeaker?: string; searchInContentType?: string[]; timeRange?: TimeRange; }
- src/search-and-analytics.ts:112-136 (helper)Helper method to find content nodes in a lifelog matching the search term and filters.private static findRelevantNodes( lifelog: Lifelog, searchTermLower: string, searchInSpeaker?: string, searchInContentType?: string[] ): LifelogContentNode[] { if (!lifelog.contents) return []; return lifelog.contents.filter(node => { // Content match const hasContentMatch = node.content?.toLowerCase().includes(searchTermLower); // Speaker filter if (searchInSpeaker && node.speakerName !== searchInSpeaker) { return false; } // Content type filter if (searchInContentType && !searchInContentType.includes(node.type)) { return false; } return hasContentMatch; }); }