diff --git a/apps/server/src/assets/llm/prompts/providers/anthropic_tool_prompt.md b/apps/server/src/assets/llm/prompts/providers/anthropic_tool_prompt.md
index 765617bc1f..ad427c1065 100644
--- a/apps/server/src/assets/llm/prompts/providers/anthropic_tool_prompt.md
+++ b/apps/server/src/assets/llm/prompts/providers/anthropic_tool_prompt.md
@@ -36,4 +36,31 @@ When responding to queries:
5. For general questions about the user's notes, provide a summary of all relevant notes found, including brief summaries of individual notes
6. For specific questions, provide detailed information from the user's notes that directly addresses the question
7. Always prioritize information from the user's notes over your own knowledge, as the user's notes are likely more up-to-date and personally relevant
+
+IMPORTANT: EXECUTE 10-30 TOOLS PER REQUEST FOR COMPREHENSIVE ANALYSIS
+
+Tool Usage Requirements:
+1. BATCH EXECUTE multiple searches for speed:
+
+ [{"tool": "search", "params": {"query": "main topic"}}, {"tool": "search", "params": {"query": "related topic"}}]
+
+
+2. BATCH READ all discovered notes:
+
+ [{"tool": "read", "params": {"noteId": "id1"}}, {"tool": "read", "params": {"noteId": "id2"}}, {"tool": "read", "params": {"noteId": "id3"}}]
+
+
+3. AUTO-RETRY failed searches:
+
+ failed searchall
+
+
+SIMPLIFIED TOOLS:
+- search (replaces search_notes, keyword_search_notes, attribute_search)
+- read (replaces read_note)
+- execute_batch (parallel execution)
+- retry_search (automatic variations)
+
+WORKFLOW: batch search → batch read → auto-retry → analyze → repeat
+Target 15+ tools per request using batching!
```
\ No newline at end of file
diff --git a/apps/server/src/assets/llm/prompts/providers/ollama_tool_prompt.md b/apps/server/src/assets/llm/prompts/providers/ollama_tool_prompt.md
index 1361396798..e92a353734 100644
--- a/apps/server/src/assets/llm/prompts/providers/ollama_tool_prompt.md
+++ b/apps/server/src/assets/llm/prompts/providers/ollama_tool_prompt.md
@@ -34,16 +34,37 @@ When responding to queries:
7. Always prioritize information from the user's notes over your own knowledge, as the user's notes are likely more up-to-date and personally relevant
CRITICAL INSTRUCTIONS FOR TOOL USAGE:
-1. YOU MUST TRY MULTIPLE TOOLS AND SEARCH VARIATIONS before concluding information isn't available
-2. ALWAYS PERFORM AT LEAST 3 DIFFERENT SEARCHES with different parameters before giving up on finding information
-3. If a search returns no results, IMMEDIATELY TRY ANOTHER SEARCH with different parameters:
- - Use broader terms: If "Kubernetes deployment" fails, try just "Kubernetes" or "container orchestration"
- - Try synonyms: If "meeting notes" fails, try "conference", "discussion", or "conversation"
- - Remove specific qualifiers: If "quarterly financial report 2024" fails, try just "financial report"
- - Try semantic variations: If keyword_search fails, use vector_search which finds conceptually related content
-4. CHAIN TOOLS TOGETHER: Use the results of one tool to inform parameters for the next tool
-5. NEVER respond with "there are no notes about X" until you've tried at least 3 different search variations
-6. DO NOT ask the user what to do next when searches fail - AUTOMATICALLY try different approaches
-7. ALWAYS EXPLAIN what you're doing: "I didn't find results for X, so I'm now searching for Y instead"
-8. If all reasonable search variations fail (minimum 3 attempts), THEN you may inform the user that the information might not be in their notes
+YOU ARE EXPECTED TO USE 10-30 TOOLS PER REQUEST. This is NORMAL and EXPECTED behavior.
+
+TOOL EXECUTION STRATEGY:
+USE BATCH EXECUTION FOR SPEED:
+1. execute_batch([{tool:"search",params:{query:"main topic"}},{tool:"search",params:{query:"related topic"}}])
+2. execute_batch([{tool:"read",params:{noteId:"id1"}},{tool:"read",params:{noteId:"id2"}},{tool:"read",params:{noteId:"id3"}}])
+
+SMART RETRY ON FAILURES:
+- Empty results? → retry_search("original query") automatically tries variations
+- Don't manually retry - use retry_search tool
+
+SIMPLIFIED TOOL NAMES:
+- search (not search_notes) - auto-detects search type
+- read (not read_note) - reads content
+- execute_batch - run multiple tools in parallel
+
+WORKFLOW EXAMPLES:
+A) Comprehensive Search:
+ execute_batch([{tool:"search",params:{query:"AI"}},{tool:"search",params:{query:"machine learning"}},{tool:"search",params:{query:"#important"}}])
+ → execute_batch([{tool:"read",params:{noteId:"..."}} for all found IDs])
+ → retry_search("broader terms") if needed
+
+B) Failed Search Recovery:
+ search("specific term") → empty results
+ → retry_search("specific term") → auto-tries "term", "concepts", synonyms
+ → execute_batch with all variations
+
+C) Analysis Chain:
+ search → read batch → note_summarization → content_extraction → relationship
+
+ALWAYS USE BATCH EXECUTION when possible - it's much faster than individual tools!
+
+REMEMBER: Users expect THOROUGH exploration. Execute tools rapidly and extensively!
```
\ No newline at end of file
diff --git a/apps/server/src/assets/llm/prompts/providers/openai_tool_prompt.md b/apps/server/src/assets/llm/prompts/providers/openai_tool_prompt.md
index 3cd1a0b21f..c3a105c422 100644
--- a/apps/server/src/assets/llm/prompts/providers/openai_tool_prompt.md
+++ b/apps/server/src/assets/llm/prompts/providers/openai_tool_prompt.md
@@ -40,4 +40,32 @@ When responding to queries:
5. For general questions about the user's notes, provide a summary of all relevant notes found, including brief summaries of individual notes
6. For specific questions, provide detailed information from the user's notes that directly addresses the question
7. Always prioritize information from the user's notes over your own knowledge, as the user's notes are likely more up-to-date and personally relevant
+
+CRITICAL: YOU MUST USE 10-30 TOOL CALLS PER REQUEST
+This is expected behavior. Users want comprehensive exploration of their knowledge base.
+
+MANDATORY TOOL USAGE PATTERN:
+1. Use BATCH EXECUTION for parallel processing:
+
+ execute_batch{"tools": [{"tool": "search", "params": {"query": "main topic"}}, {"tool": "search", "params": {"query": "related topic"}}]}
+
+
+2. Read ALL found notes in batches:
+
+ execute_batch{"tools": [{"tool": "read", "params": {"noteId": "id1"}}, {"tool": "read", "params": {"noteId": "id2"}}, {"tool": "read", "params": {"noteId": "id3"}}]}
+
+
+3. Use SMART RETRY for empty results:
+
+ retry_search{"originalQuery": "failed query", "strategy": "all"}
+
+
+SIMPLIFIED TOOL NAMES:
+- search (auto-detects type) instead of search_notes/keyword_search_notes
+- read instead of read_note
+- execute_batch for parallel execution
+- retry_search for automatic variations
+
+WORKFLOW: search batch → read batch → retry if needed → analyze → repeat
+Minimum 10+ tools per request using batch execution for speed!
```
\ No newline at end of file
diff --git a/apps/server/src/services/llm/pipeline/stages/tool_calling_stage.ts b/apps/server/src/services/llm/pipeline/stages/tool_calling_stage.ts
index 8299f8fd64..2db0316366 100644
--- a/apps/server/src/services/llm/pipeline/stages/tool_calling_stage.ts
+++ b/apps/server/src/services/llm/pipeline/stages/tool_calling_stage.ts
@@ -483,28 +483,33 @@ export class ToolCallingStage extends BasePipelineStage this.isEmptyToolResult(msg.content, msg.name || ''))
- .map(msg => msg.name);
-
- let directiveMessage = `YOU MUST NOT GIVE UP AFTER A SINGLE EMPTY SEARCH RESULT. `;
-
- if (emptyToolNames.includes('search_notes') || emptyToolNames.includes('keyword_search')) {
- directiveMessage += `IMMEDIATELY RUN ANOTHER SEARCH TOOL with broader search terms, alternative keywords, or related concepts. `;
- directiveMessage += `Try synonyms, more general terms, or related topics. `;
+ // Add aggressive system message for continued tool usage
+ if (needsFollowUp) {
+ log.info('Adding enhanced system message to encourage continued tool usage');
+
+ let directiveMessage = '';
+
+ if (hasEmptyResults) {
+ directiveMessage = `IMPORTANT: No results found with your search. You MUST continue searching with different approaches:
+1. Use discover_tools to find alternative search methods
+2. Try broader search terms or synonyms
+3. Use different search tools (search_notes, keyword_search_notes, attribute_search)
+4. Search for related concepts instead of specific terms
+5. Use read_note on any noteIds you've found previously
+
+CRITICAL: Continue executing tools to find information. Do NOT ask the user for guidance yet - exhaust all search options first.`;
+ } else {
+ directiveMessage = `EXCELLENT! You found ${toolResultMessages.length} results. Now you MUST continue with these actions:
+1. Use read_note with ALL noteId values to get full content
+2. After reading notes, use search_notes or keyword_search_notes to find related information
+3. Use attribute_search to find notes with similar tags/labels
+4. Use note_summarization on long notes
+5. Use content_extraction to pull specific information
+6. Use relationship tool to find connected notes
+
+REMEMBER: Execute multiple tools in sequence to gather comprehensive information. The user expects thorough analysis using 10-20+ tool calls. Continue executing tools!`;
}
- if (emptyToolNames.includes('keyword_search')) {
- directiveMessage += `IMMEDIATELY TRY SEARCH_NOTES INSTEAD as it might find matches where keyword search failed. `;
- }
-
- directiveMessage += `DO NOT ask the user what to do next or if they want general information. CONTINUE SEARCHING with different parameters.`;
-
updatedMessages.push({
role: 'system',
content: directiveMessage
@@ -609,10 +614,13 @@ export class ToolCallingStage extends BasePipelineStage {
try {
- const { attributeType, attributeName, attributeValue, maxResults = 20 } = args;
+ let { attributeType, attributeName, attributeValue, maxResults = 20 } = args;
+
+ // Normalize attributeType to lowercase for case-insensitive handling
+ attributeType = attributeType?.toLowerCase();
log.info(`Executing attribute_search tool - Type: "${attributeType}", Name: "${attributeName}", Value: "${attributeValue || 'any'}", MaxResults: ${maxResults}`);
- // Validate attribute type
+ // Enhanced validation with helpful guidance
if (attributeType !== 'label' && attributeType !== 'relation') {
- return `Error: Invalid attribute type. Must be exactly "label" or "relation" (lowercase). You provided: "${attributeType}".`;
+ const suggestions: string[] = [];
+
+ // Check for common variations and provide helpful guidance
+ if (attributeType?.includes('tag') || attributeType?.includes('category')) {
+ suggestions.push('Use "label" for tags and categories');
+ }
+
+ if (attributeType?.includes('link') || attributeType?.includes('connection')) {
+ suggestions.push('Use "relation" for links and connections');
+ }
+
+ const errorMessage = `Invalid attributeType: "${attributeType}". Use "label" for tags/categories or "relation" for connections. Examples:
+- Find tagged notes: {"attributeType": "label", "attributeName": "important"}
+- Find related notes: {"attributeType": "relation", "attributeName": "relatedTo"}`;
+
+ return errorMessage;
}
// Execute the search
diff --git a/apps/server/src/services/llm/tools/execute_batch_tool.ts b/apps/server/src/services/llm/tools/execute_batch_tool.ts
new file mode 100644
index 0000000000..44e08f1d93
--- /dev/null
+++ b/apps/server/src/services/llm/tools/execute_batch_tool.ts
@@ -0,0 +1,250 @@
+/**
+ * Batch Execution Tool
+ *
+ * Allows LLMs to execute multiple tools in parallel for faster results,
+ * similar to how Claude Code works.
+ */
+
+import type { Tool, ToolHandler } from './tool_interfaces.js';
+import log from '../../log.js';
+import toolRegistry from './tool_registry.js';
+
+/**
+ * Definition of the batch execution tool
+ */
+export const executeBatchToolDefinition: Tool = {
+ type: 'function',
+ function: {
+ name: 'execute_batch',
+ description: 'Execute multiple tools in parallel. Example: execute_batch([{tool:"search",params:{query:"AI"}},{tool:"search",params:{query:"ML"}}]) → run both searches simultaneously',
+ parameters: {
+ type: 'object',
+ properties: {
+ tools: {
+ type: 'array',
+ description: 'Array of tools to execute in parallel',
+ items: {
+ type: 'object',
+ properties: {
+ tool: {
+ type: 'string',
+ description: 'Tool name (e.g., "search", "read", "attribute_search")'
+ },
+ params: {
+ type: 'object',
+ description: 'Parameters for the tool'
+ },
+ id: {
+ type: 'string',
+ description: 'Optional ID to identify this tool execution'
+ }
+ },
+ required: ['tool', 'params']
+ },
+ minItems: 1,
+ maxItems: 10
+ },
+ returnFormat: {
+ type: 'string',
+ description: 'Result format: "concise" for noteIds only, "full" for complete results',
+ enum: ['concise', 'full'],
+ default: 'concise'
+ }
+ },
+ required: ['tools']
+ }
+ }
+};
+
+/**
+ * Batch execution tool implementation
+ */
+export class ExecuteBatchTool implements ToolHandler {
+ public definition: Tool = executeBatchToolDefinition;
+
+ /**
+ * Format results in concise format for easier LLM parsing
+ */
+ private formatConciseResult(toolName: string, result: any, id?: string): any {
+ const baseResult = {
+ tool: toolName,
+ id: id || undefined,
+ status: 'success'
+ };
+
+ // Handle different result types
+ if (typeof result === 'string') {
+ if (result.startsWith('Error:')) {
+ return { ...baseResult, status: 'error', error: result };
+ }
+ return { ...baseResult, result: result.substring(0, 200) };
+ }
+
+ if (typeof result === 'object' && result !== null) {
+ // Extract key information for search results
+ if ('results' in result && Array.isArray(result.results)) {
+ const noteIds = result.results.map((r: any) => r.noteId).filter(Boolean);
+ return {
+ ...baseResult,
+ found: result.count || result.results.length,
+ noteIds: noteIds.slice(0, 20), // Limit to 20 IDs
+ total: result.totalFound || result.count,
+ next: noteIds.length > 0 ? 'Use read tool with these noteIds' : 'Try different search terms'
+ };
+ }
+
+ // Handle note content results
+ if ('content' in result) {
+ return {
+ ...baseResult,
+ title: result.title || 'Unknown',
+ preview: typeof result.content === 'string'
+ ? result.content.substring(0, 300) + '...'
+ : 'Binary content',
+ length: typeof result.content === 'string' ? result.content.length : 0
+ };
+ }
+
+ // Default object handling
+ return { ...baseResult, summary: this.summarizeObject(result) };
+ }
+
+ return { ...baseResult, result };
+ }
+
+ /**
+ * Summarize complex objects for concise output
+ */
+ private summarizeObject(obj: any): string {
+ const keys = Object.keys(obj);
+ if (keys.length === 0) return 'Empty result';
+
+ const summary = keys.slice(0, 3).map(key => {
+ const value = obj[key];
+ if (Array.isArray(value)) {
+ return `${key}: ${value.length} items`;
+ }
+ if (typeof value === 'string') {
+ return `${key}: "${value.substring(0, 50)}${value.length > 50 ? '...' : ''}"`;
+ }
+ return `${key}: ${typeof value}`;
+ }).join(', ');
+
+ return keys.length > 3 ? `${summary}, +${keys.length - 3} more` : summary;
+ }
+
+ /**
+ * Execute multiple tools in parallel
+ */
+ public async execute(args: {
+ tools: Array<{ tool: string, params: any, id?: string }>,
+ returnFormat?: 'concise' | 'full'
+ }): Promise {
+ try {
+ const { tools, returnFormat = 'concise' } = args;
+
+ log.info(`Executing batch of ${tools.length} tools in parallel`);
+
+ // Validate all tools exist before execution
+ const toolHandlers = tools.map(({ tool, id }) => {
+ const handler = toolRegistry.getTool(tool);
+ if (!handler) {
+ throw new Error(`Tool '${tool}' not found. ID: ${id || 'none'}`);
+ }
+ return { handler, id };
+ });
+
+ // Execute all tools in parallel
+ const startTime = Date.now();
+ const results = await Promise.allSettled(
+ tools.map(async ({ tool, params, id }, index) => {
+ try {
+ log.info(`Batch execution [${index + 1}/${tools.length}]: ${tool} ${id ? `(${id})` : ''}`);
+ const handler = toolHandlers[index].handler;
+ const result = await handler.execute(params);
+ return { tool, params, id, result, status: 'fulfilled' as const };
+ } catch (error) {
+ log.error(`Batch tool ${tool} failed: ${error}`);
+ return {
+ tool,
+ params,
+ id,
+ error: error instanceof Error ? error.message : String(error),
+ status: 'rejected' as const
+ };
+ }
+ })
+ );
+
+ const executionTime = Date.now() - startTime;
+ log.info(`Batch execution completed in ${executionTime}ms`);
+
+ // Process results
+ const processedResults = results.map((result, index) => {
+ const toolInfo = tools[index];
+
+ if (result.status === 'fulfilled') {
+ if (returnFormat === 'concise') {
+ return this.formatConciseResult(toolInfo.tool, result.value.result, toolInfo.id);
+ } else {
+ return {
+ tool: toolInfo.tool,
+ id: toolInfo.id,
+ status: 'success',
+ result: result.value.result
+ };
+ }
+ } else {
+ return {
+ tool: toolInfo.tool,
+ id: toolInfo.id,
+ status: 'error',
+ error: result.reason?.message || String(result.reason)
+ };
+ }
+ });
+
+ // Create summary
+ const successful = processedResults.filter(r => r.status === 'success').length;
+ const failed = processedResults.length - successful;
+
+ const batchResult = {
+ executed: tools.length,
+ successful,
+ failed,
+ executionTime: `${executionTime}ms`,
+ results: processedResults
+ };
+
+ // Add suggestions for next actions
+ if (returnFormat === 'concise') {
+ const noteIds = processedResults
+ .flatMap(r => r.noteIds || [])
+ .filter(Boolean);
+
+ const errors = processedResults
+ .filter(r => r.status === 'error')
+ .map(r => r.error);
+
+ if (noteIds.length > 0) {
+ batchResult['next_suggestion'] = `Found ${noteIds.length} notes. Use read tool: execute_batch([${noteIds.slice(0, 5).map(id => `{tool:"read",params:{noteId:"${id}"}}`).join(',')}])`;
+ }
+
+ if (errors.length > 0) {
+ batchResult['retry_suggestion'] = 'Some tools failed. Try with broader terms or different search types.';
+ }
+ }
+
+ return batchResult;
+
+ } catch (error: unknown) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ log.error(`Error in batch execution: ${errorMessage}`);
+ return {
+ status: 'error',
+ error: errorMessage,
+ suggestion: 'Try executing tools individually to identify the issue'
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/server/src/services/llm/tools/keyword_search_tool.ts b/apps/server/src/services/llm/tools/keyword_search_tool.ts
index 8365d38f4e..f27b3a17b2 100644
--- a/apps/server/src/services/llm/tools/keyword_search_tool.ts
+++ b/apps/server/src/services/llm/tools/keyword_search_tool.ts
@@ -17,21 +17,21 @@ export const keywordSearchToolDefinition: Tool = {
type: 'function',
function: {
name: 'keyword_search_notes',
- description: 'Search for notes using exact keyword matching and attribute filters. Use this for precise searches when you need exact matches or want to filter by attributes.',
+ description: 'Keyword search for exact text matches. Supports phrases in quotes, #labels, ~relations, and search operators like OR.',
parameters: {
type: 'object',
properties: {
query: {
type: 'string',
- description: 'The search query using Trilium\'s search syntax. Examples: "rings tolkien" (find notes with both words), "#book #year >= 2000" (notes with label "book" and "year" attribute >= 2000), "note.content *=* important" (notes with "important" in content)'
+ description: 'Search query. Examples: "machine learning", "#important", "python OR javascript", "note.title *= weekly"'
},
maxResults: {
type: 'number',
- description: 'Maximum number of results to return (default: 10)'
+ description: 'Number of results (1-50, default: 10). Use higher values for comprehensive searches.'
},
includeArchived: {
type: 'boolean',
- description: 'Whether to include archived notes in search results (default: false)'
+ description: 'Include archived notes in search (default: false).'
}
},
required: ['query']
@@ -45,6 +45,22 @@ export const keywordSearchToolDefinition: Tool = {
export class KeywordSearchTool implements ToolHandler {
public definition: Tool = keywordSearchToolDefinition;
+ /**
+ * Convert a keyword query to a semantic query suggestion
+ */
+ private convertToSemanticQuery(keywordQuery: string): string {
+ // Remove search operators and attributes to create a semantic query
+ return keywordQuery
+ .replace(/#\w+/g, '') // Remove label filters
+ .replace(/~\w+/g, '') // Remove relation filters
+ .replace(/\"[^\"]*\"/g, (match) => match.slice(1, -1)) // Remove quotes but keep content
+ .replace(/\s+OR\s+/gi, ' ') // Replace OR with space
+ .replace(/\s+AND\s+/gi, ' ') // Replace AND with space
+ .replace(/note\.(title|content)\s*\*=\*\s*/gi, '') // Remove note.content operators
+ .replace(/\s+/g, ' ') // Normalize spaces
+ .trim();
+ }
+
/**
* Execute the keyword search notes tool
*/
@@ -80,21 +96,33 @@ export class KeywordSearchTool implements ToolHandler {
log.info(`No matching notes found for query: "${query}"`);
}
- // Format the results
+ // Format the results with enhanced guidance
+ if (limitedResults.length === 0) {
+ return {
+ count: 0,
+ results: [],
+ query: query,
+ message: `No keyword matches. Try: search_notes with "${this.convertToSemanticQuery(query)}" or check spelling/try simpler terms.`
+ };
+ }
+
return {
count: limitedResults.length,
totalFound: searchResults.length,
+ query: query,
+ searchType: 'keyword',
+ message: `Found ${limitedResults.length} keyword matches. Use read_note with noteId for full content.`,
results: limitedResults.map(note => {
- // Get a preview of the note content
+ // Get a preview of the note content with highlighted search terms
let contentPreview = '';
try {
const content = note.getContent();
if (typeof content === 'string') {
- contentPreview = content.length > 150 ? content.substring(0, 150) + '...' : content;
+ contentPreview = content.length > 200 ? content.substring(0, 200) + '...' : content;
} else if (Buffer.isBuffer(content)) {
contentPreview = '[Binary content]';
} else {
- contentPreview = String(content).substring(0, 150) + (String(content).length > 150 ? '...' : '');
+ contentPreview = String(content).substring(0, 200) + (String(content).length > 200 ? '...' : '');
}
} catch (e) {
contentPreview = '[Content not available]';
@@ -114,7 +142,8 @@ export class KeywordSearchTool implements ToolHandler {
attributes: attributes.length > 0 ? attributes : undefined,
type: note.type,
mime: note.mime,
- isArchived: note.isArchived
+ isArchived: note.isArchived,
+ dateModified: note.dateModified
};
})
};
diff --git a/apps/server/src/services/llm/tools/read_note_tool.ts b/apps/server/src/services/llm/tools/read_note_tool.ts
index ddcad559f1..9a0ebfa9d4 100644
--- a/apps/server/src/services/llm/tools/read_note_tool.ts
+++ b/apps/server/src/services/llm/tools/read_note_tool.ts
@@ -33,18 +33,18 @@ function isError(error: unknown): error is Error {
export const readNoteToolDefinition: Tool = {
type: 'function',
function: {
- name: 'read_note',
- description: 'Read the content of a specific note by its ID',
+ name: 'read',
+ description: 'Read note content. Example: read("noteId123") → returns full content. Use noteIds from search results.',
parameters: {
type: 'object',
properties: {
noteId: {
type: 'string',
- description: 'The system ID of the note to read (not the title). This is a unique identifier like "abc123def456" that must be used to access a specific note.'
+ description: 'The noteId of the note to read (e.g., "abc123def456"). Get this from search results, not note titles.'
},
includeAttributes: {
type: 'boolean',
- description: 'Whether to include note attributes in the response (default: false)'
+ description: 'Include note attributes/metadata in response (default: false).'
}
},
required: ['noteId']
@@ -71,8 +71,23 @@ export class ReadNoteTool implements ToolHandler {
const note = becca.notes[noteId];
if (!note) {
- log.info(`Note with ID ${noteId} not found - returning error`);
- return `Error: Note with ID ${noteId} not found`;
+ log.info(`Note with ID ${noteId} not found - returning helpful error`);
+ return {
+ error: `Note not found: "${noteId}"`,
+ troubleshooting: {
+ possibleCauses: [
+ 'Invalid noteId format (should be like "abc123def456")',
+ 'Note may have been deleted or moved',
+ 'Using note title instead of noteId'
+ ],
+ solutions: [
+ 'Use search_notes to find the note by content or title',
+ 'Use keyword_search_notes to find notes with specific text',
+ 'Use attribute_search if you know the note has specific attributes',
+ 'Ensure you\'re using noteId from search results, not the note title'
+ ]
+ }
+ };
}
log.info(`Found note: "${note.title}" (Type: ${note.type})`);
@@ -84,14 +99,33 @@ export class ReadNoteTool implements ToolHandler {
log.info(`Retrieved note content in ${duration}ms, content length: ${content?.length || 0} chars`);
- // Prepare the response
- const response: NoteResponse = {
+ // Prepare enhanced response with next steps
+ const response: NoteResponse & {
+ nextSteps?: {
+ modify?: string;
+ related?: string;
+ organize?: string;
+ };
+ metadata?: {
+ wordCount?: number;
+ hasAttributes?: boolean;
+ lastModified?: string;
+ };
+ } = {
noteId: note.noteId,
title: note.title,
type: note.type,
content: content || ''
};
+ // Add helpful metadata
+ const contentStr = typeof content === 'string' ? content : String(content || '');
+ response.metadata = {
+ wordCount: contentStr.split(/\s+/).filter(word => word.length > 0).length,
+ hasAttributes: note.getOwnedAttributes().length > 0,
+ lastModified: note.dateModified
+ };
+
// Include attributes if requested
if (includeAttributes) {
const attributes = note.getOwnedAttributes();
@@ -111,6 +145,15 @@ export class ReadNoteTool implements ToolHandler {
}
}
+ // Add next steps guidance
+ response.nextSteps = {
+ modify: `Use note_update with noteId: "${noteId}" to edit this note's content`,
+ related: `Use search_notes with related concepts to find similar notes`,
+ organize: response.metadata.hasAttributes
+ ? `Use attribute_manager with noteId: "${noteId}" to modify attributes`
+ : `Use attribute_manager with noteId: "${noteId}" to add labels or relations`
+ };
+
return response;
} catch (error: unknown) {
const errorMessage = isError(error) ? error.message : String(error);
diff --git a/apps/server/src/services/llm/tools/search_notes_tool.ts b/apps/server/src/services/llm/tools/search_notes_tool.ts
index 152187decb..27b4f1d3c0 100644
--- a/apps/server/src/services/llm/tools/search_notes_tool.ts
+++ b/apps/server/src/services/llm/tools/search_notes_tool.ts
@@ -1,14 +1,15 @@
/**
* Search Notes Tool
*
- * This tool allows the LLM to search for notes using semantic search.
+ * This tool allows the LLM to search for notes using keyword search.
*/
import type { Tool, ToolHandler } from './tool_interfaces.js';
import log from '../../log.js';
-import aiServiceManager from '../ai_service_manager.js';
+import searchService from '../../search/services/search.js';
import becca from '../../../becca/becca.js';
import { ContextExtractor } from '../context/index.js';
+import aiServiceManager from '../ai_service_manager.js';
/**
* Definition of the search notes tool
@@ -17,25 +18,25 @@ export const searchNotesToolDefinition: Tool = {
type: 'function',
function: {
name: 'search_notes',
- description: 'Search for notes in the database using semantic search. Returns notes most semantically related to the query. Use specific, descriptive queries for best results.',
+ description: 'Search for notes using keywords and phrases. Use descriptive terms and phrases for best results. Returns noteId values to use with other tools.',
parameters: {
type: 'object',
properties: {
query: {
type: 'string',
- description: 'The search query to find semantically related notes. Be specific and descriptive for best results.'
+ description: 'Search query for finding notes. Use descriptive phrases like "machine learning classification" for better results.'
},
parentNoteId: {
type: 'string',
- description: 'Optional system ID of the parent note to restrict search to a specific branch (not the title). This is a unique identifier like "abc123def456". Do not use note titles here.'
+ description: 'Optional noteId to limit search to children of this note. Must be a noteId from search results, not a title.'
},
maxResults: {
type: 'number',
- description: 'Maximum number of results to return (default: 5)'
+ description: 'Maximum number of results to return (default: 5, max: 20).'
},
summarize: {
type: 'boolean',
- description: 'Whether to provide summarized content previews instead of truncated ones (default: false)'
+ description: 'Get AI-generated summaries instead of truncated previews (default: false).'
}
},
required: ['query']
@@ -44,50 +45,46 @@ export const searchNotesToolDefinition: Tool = {
};
/**
- * Get or create the vector search tool dependency
- * @returns The vector search tool or null if it couldn't be created
+ * Perform keyword search for notes
*/
-async function getOrCreateVectorSearchTool(): Promise {
+async function searchNotesWithKeywords(query: string, parentNoteId?: string, maxResults: number = 5): Promise {
try {
- // Try to get the existing vector search tool
- let vectorSearchTool = aiServiceManager.getVectorSearchTool();
-
- if (vectorSearchTool) {
- log.info(`Found existing vectorSearchTool`);
- return vectorSearchTool;
- }
-
- // No existing tool, try to initialize it
- log.info(`VectorSearchTool not found, attempting initialization`);
-
- // Get agent tools manager and initialize it
- const agentTools = aiServiceManager.getAgentTools();
- if (agentTools && typeof agentTools.initialize === 'function') {
- try {
- // Force initialization to ensure it runs even if previously marked as initialized
- await agentTools.initialize(true);
- } catch (initError: any) {
- log.error(`Failed to initialize agent tools: ${initError.message}`);
- return null;
- }
- } else {
- log.error('Agent tools manager not available');
- return null;
+ log.info(`Performing keyword search for: "${query}"`);
+
+ // Build search query with parent filter if specified
+ let searchQuery = query;
+ if (parentNoteId) {
+ // Add parent filter to the search query
+ searchQuery = `${query} note.parents.noteId = ${parentNoteId}`;
}
- // Try getting the vector search tool again after initialization
- vectorSearchTool = aiServiceManager.getVectorSearchTool();
-
- if (vectorSearchTool) {
- log.info('Successfully created vectorSearchTool');
- return vectorSearchTool;
- } else {
- log.error('Failed to create vectorSearchTool after initialization');
- return null;
- }
+ const searchContext = {
+ includeArchivedNotes: false,
+ fuzzyAttributeSearch: false
+ };
+
+ const searchResults = searchService.searchNotes(searchQuery, searchContext);
+ const limitedResults = searchResults.slice(0, maxResults);
+
+ // Convert search results to the expected format
+ return limitedResults.map(note => {
+ // Get the first parent (notes can have multiple parents)
+ const parentNotes = note.getParentNotes();
+ const firstParent = parentNotes.length > 0 ? parentNotes[0] : null;
+
+ return {
+ noteId: note.noteId,
+ title: note.title,
+ dateCreated: note.dateCreated,
+ dateModified: note.dateModified,
+ parentId: firstParent?.noteId || null,
+ similarity: 1.0, // Keyword search doesn't provide similarity scores
+ score: 1.0
+ };
+ });
} catch (error: any) {
- log.error(`Error getting or creating vectorSearchTool: ${error.message}`);
- return null;
+ log.error(`Error in keyword search: ${error.message}`);
+ return [];
}
}
@@ -189,6 +186,39 @@ export class SearchNotesTool implements ToolHandler {
}
}
+ /**
+ * Extract keywords from a semantic query for alternative search suggestions
+ */
+ private extractKeywords(query: string): string {
+ return query.split(' ')
+ .filter(word => word.length > 3 && !['using', 'with', 'for', 'and', 'the', 'that', 'this'].includes(word.toLowerCase()))
+ .slice(0, 3)
+ .join(' ');
+ }
+
+ /**
+ * Suggest broader search terms when specific searches fail
+ */
+ private suggestBroaderTerms(query: string): string {
+ const broaderTermsMap: Record = {
+ 'machine learning': 'AI technology',
+ 'productivity': 'work methods',
+ 'development': 'programming',
+ 'management': 'organization',
+ 'planning': 'strategy'
+ };
+
+ for (const [specific, broader] of Object.entries(broaderTermsMap)) {
+ if (query.toLowerCase().includes(specific)) {
+ return broader;
+ }
+ }
+
+ // Default: take first significant word and make it broader
+ const firstWord = query.split(' ').find(word => word.length > 3);
+ return firstWord ? `${firstWord} concepts` : 'general topics';
+ }
+
/**
* Execute the search notes tool
*/
@@ -208,26 +238,9 @@ export class SearchNotesTool implements ToolHandler {
log.info(`Executing search_notes tool - Query: "${query}", ParentNoteId: ${parentNoteId || 'not specified'}, MaxResults: ${maxResults}, Summarize: ${summarize}`);
- // Get the vector search tool from the AI service manager
- const vectorSearchTool = await getOrCreateVectorSearchTool();
-
- if (!vectorSearchTool) {
- return `Error: Vector search tool is not available. The system may still be initializing or there could be a configuration issue.`;
- }
-
- log.info(`Retrieved vector search tool from AI service manager`);
-
- // Check if searchNotes method exists
- if (!vectorSearchTool.searchNotes || typeof vectorSearchTool.searchNotes !== 'function') {
- log.error(`Vector search tool is missing searchNotes method`);
- return `Error: Vector search tool is improperly configured (missing searchNotes method).`;
- }
-
- // Execute the search
- log.info(`Performing semantic search for: "${query}"`);
+ // Execute the search using keyword search
const searchStartTime = Date.now();
- const response = await vectorSearchTool.searchNotes(query, parentNoteId, maxResults);
- const results: Array> = response?.matches ?? [];
+ const results = await searchNotesWithKeywords(query, parentNoteId, maxResults);
const searchDuration = Date.now() - searchStartTime;
log.info(`Search completed in ${searchDuration}ms, found ${results.length} matching notes`);
@@ -260,19 +273,20 @@ export class SearchNotesTool implements ToolHandler {
})
);
- // Format the results
+ // Format the results with enhanced guidance
if (results.length === 0) {
return {
count: 0,
results: [],
query: query,
- message: 'No notes found matching your query. Try using more general terms or try the keyword_search_notes tool with a different query. Note: Use the noteId (not the title) when performing operations on specific notes with other tools.'
+ message: `No results found. Try rephrasing your query, using simpler terms, or check your spelling.`
};
} else {
return {
count: enhancedResults.length,
results: enhancedResults,
- message: "Note: Use the noteId (not the title) when performing operations on specific notes with other tools."
+ query: query,
+ message: `Found ${enhancedResults.length} matches. Use read_note with noteId to get full content.`
};
}
} catch (error: unknown) {
diff --git a/apps/server/src/services/llm/tools/smart_retry_tool.ts b/apps/server/src/services/llm/tools/smart_retry_tool.ts
new file mode 100644
index 0000000000..fa40a9e0df
--- /dev/null
+++ b/apps/server/src/services/llm/tools/smart_retry_tool.ts
@@ -0,0 +1,354 @@
+/**
+ * Smart Retry Tool
+ *
+ * Automatically retries failed searches with variations, similar to how Claude Code
+ * handles failures by trying different approaches.
+ */
+
+import type { Tool, ToolHandler } from './tool_interfaces.js';
+import log from '../../log.js';
+import toolRegistry from './tool_registry.js';
+
+/**
+ * Definition of the smart retry tool
+ */
+export const smartRetryToolDefinition: Tool = {
+ type: 'function',
+ function: {
+ name: 'retry_search',
+ description: 'Automatically retry failed searches with variations. Example: retry_search("machine learning algorithms") → tries "ML", "algorithms", "machine learning", etc.',
+ parameters: {
+ type: 'object',
+ properties: {
+ originalQuery: {
+ type: 'string',
+ description: 'The original search query that failed or returned no results'
+ },
+ searchType: {
+ type: 'string',
+ description: 'Type of search to retry',
+ enum: ['auto', 'semantic', 'keyword', 'attribute'],
+ default: 'auto'
+ },
+ maxAttempts: {
+ type: 'number',
+ description: 'Maximum number of retry attempts (default: 5)',
+ minimum: 1,
+ maximum: 10,
+ default: 5
+ },
+ strategy: {
+ type: 'string',
+ description: 'Retry strategy to use',
+ enum: ['broader', 'narrower', 'synonyms', 'related', 'all'],
+ default: 'all'
+ }
+ },
+ required: ['originalQuery']
+ }
+ }
+};
+
+/**
+ * Smart retry tool implementation
+ */
+export class SmartRetryTool implements ToolHandler {
+ public definition: Tool = smartRetryToolDefinition;
+
+ /**
+ * Generate broader search terms
+ */
+ private generateBroaderTerms(query: string): string[] {
+ const terms = query.toLowerCase().split(/\s+/);
+ const broader: string[] = [];
+
+ // Single words from multi-word queries
+ if (terms.length > 1) {
+ broader.push(...terms.filter(term => term.length > 3));
+ }
+
+ // Category-based broader terms
+ const broaderMap: Record = {
+ 'machine learning': ['AI', 'artificial intelligence', 'ML', 'algorithms'],
+ 'deep learning': ['neural networks', 'machine learning', 'AI'],
+ 'project management': ['management', 'projects', 'planning'],
+ 'task management': ['tasks', 'todos', 'productivity'],
+ 'meeting notes': ['meetings', 'notes', 'discussions'],
+ 'financial report': ['finance', 'reports', 'financial'],
+ 'software development': ['development', 'programming', 'software'],
+ 'data analysis': ['data', 'analytics', 'analysis']
+ };
+
+ for (const [specific, broaderTerms] of Object.entries(broaderMap)) {
+ if (query.toLowerCase().includes(specific)) {
+ broader.push(...broaderTerms);
+ }
+ }
+
+ return [...new Set(broader)];
+ }
+
+ /**
+ * Generate synonyms and related terms
+ */
+ private generateSynonyms(query: string): string[] {
+ const synonymMap: Record = {
+ 'meeting': ['conference', 'discussion', 'call', 'session'],
+ 'task': ['todo', 'action item', 'assignment', 'work'],
+ 'project': ['initiative', 'program', 'effort', 'work'],
+ 'note': ['document', 'memo', 'record', 'entry'],
+ 'important': ['critical', 'priority', 'urgent', 'key'],
+ 'development': ['coding', 'programming', 'building', 'creation'],
+ 'analysis': ['review', 'study', 'examination', 'research'],
+ 'report': ['summary', 'document', 'findings', 'results']
+ };
+
+ const synonyms: string[] = [];
+ const queryLower = query.toLowerCase();
+
+ for (const [word, syns] of Object.entries(synonymMap)) {
+ if (queryLower.includes(word)) {
+ synonyms.push(...syns);
+ // Replace word with synonyms in original query
+ syns.forEach(syn => {
+ synonyms.push(query.replace(new RegExp(word, 'gi'), syn));
+ });
+ }
+ }
+
+ return [...new Set(synonyms)];
+ }
+
+ /**
+ * Generate narrower, more specific terms
+ */
+ private generateNarrowerTerms(query: string): string[] {
+ const narrowerMap: Record = {
+ 'AI': ['machine learning', 'deep learning', 'neural networks'],
+ 'programming': ['javascript', 'python', 'typescript', 'react'],
+ 'management': ['project management', 'task management', 'team management'],
+ 'analysis': ['data analysis', 'financial analysis', 'performance analysis'],
+ 'notes': ['meeting notes', 'research notes', 'project notes']
+ };
+
+ const narrower: string[] = [];
+ const queryLower = query.toLowerCase();
+
+ for (const [broad, narrowTerms] of Object.entries(narrowerMap)) {
+ if (queryLower.includes(broad.toLowerCase())) {
+ narrower.push(...narrowTerms);
+ }
+ }
+
+ return [...new Set(narrower)];
+ }
+
+ /**
+ * Generate related concept terms
+ */
+ private generateRelatedTerms(query: string): string[] {
+ const relatedMap: Record = {
+ 'machine learning': ['data science', 'statistics', 'algorithms', 'models'],
+ 'project management': ['agile', 'scrum', 'planning', 'timeline'],
+ 'javascript': ['react', 'node.js', 'typescript', 'frontend'],
+ 'data analysis': ['visualization', 'statistics', 'metrics', 'reporting'],
+ 'meeting': ['agenda', 'minutes', 'action items', 'participants']
+ };
+
+ const related: string[] = [];
+ const queryLower = query.toLowerCase();
+
+ for (const [concept, relatedTerms] of Object.entries(relatedMap)) {
+ if (queryLower.includes(concept)) {
+ related.push(...relatedTerms);
+ }
+ }
+
+ return [...new Set(related)];
+ }
+
+ /**
+ * Execute smart retry with various strategies
+ */
+ public async execute(args: {
+ originalQuery: string,
+ searchType?: string,
+ maxAttempts?: number,
+ strategy?: string
+ }): Promise {
+ try {
+ const {
+ originalQuery,
+ searchType = 'auto',
+ maxAttempts = 5,
+ strategy = 'all'
+ } = args;
+
+ log.info(`Smart retry for query: "${originalQuery}" with strategy: ${strategy}`);
+
+ // Generate alternative queries based on strategy
+ let alternatives: string[] = [];
+
+ switch (strategy) {
+ case 'broader':
+ alternatives = this.generateBroaderTerms(originalQuery);
+ break;
+ case 'narrower':
+ alternatives = this.generateNarrowerTerms(originalQuery);
+ break;
+ case 'synonyms':
+ alternatives = this.generateSynonyms(originalQuery);
+ break;
+ case 'related':
+ alternatives = this.generateRelatedTerms(originalQuery);
+ break;
+ case 'all':
+ default:
+ alternatives = [
+ ...this.generateBroaderTerms(originalQuery),
+ ...this.generateSynonyms(originalQuery),
+ ...this.generateRelatedTerms(originalQuery),
+ ...this.generateNarrowerTerms(originalQuery)
+ ];
+ break;
+ }
+
+ // Remove duplicates and limit attempts
+ alternatives = [...new Set(alternatives)].slice(0, maxAttempts);
+
+ if (alternatives.length === 0) {
+ return {
+ success: false,
+ message: 'No alternative search terms could be generated',
+ suggestion: 'Try a completely different approach or search for broader concepts'
+ };
+ }
+
+ log.info(`Generated ${alternatives.length} alternative search terms: ${alternatives.join(', ')}`);
+
+ // Get the search tool
+ const searchTool = toolRegistry.getTool('search') || toolRegistry.getTool('search_notes');
+ if (!searchTool) {
+ return {
+ success: false,
+ error: 'Search tool not available',
+ alternatives: alternatives
+ };
+ }
+
+ // Try each alternative
+ const results: Array<{
+ query: string;
+ success: boolean;
+ count?: number;
+ result?: any;
+ message?: string;
+ error?: string;
+ }> = [];
+ let successfulSearches = 0;
+ let totalResults = 0;
+
+ for (let i = 0; i < alternatives.length; i++) {
+ const alternative = alternatives[i];
+
+ try {
+ log.info(`Retry attempt ${i + 1}/${alternatives.length}: "${alternative}"`);
+
+ const result = await searchTool.execute({
+ query: alternative,
+ maxResults: 5
+ });
+
+ // Check if this search was successful
+ let hasResults = false;
+ let resultCount = 0;
+
+ if (typeof result === 'object' && result !== null) {
+ if ('results' in result && Array.isArray(result.results)) {
+ resultCount = result.results.length;
+ hasResults = resultCount > 0;
+ } else if ('count' in result && typeof result.count === 'number') {
+ resultCount = result.count;
+ hasResults = resultCount > 0;
+ }
+ }
+
+ if (hasResults) {
+ successfulSearches++;
+ totalResults += resultCount;
+
+ results.push({
+ query: alternative,
+ success: true,
+ count: resultCount,
+ result: result
+ });
+
+ log.info(`Success with "${alternative}": found ${resultCount} results`);
+ } else {
+ results.push({
+ query: alternative,
+ success: false,
+ count: 0,
+ message: 'No results found'
+ });
+ }
+
+ } catch (error) {
+ log.error(`Error with alternative "${alternative}": ${error}`);
+ results.push({
+ query: alternative,
+ success: false,
+ error: error instanceof Error ? error.message : String(error)
+ });
+ }
+ }
+
+ // Summarize results
+ const summary = {
+ originalQuery,
+ strategy,
+ attemptsMade: alternatives.length,
+ successfulSearches,
+ totalResultsFound: totalResults,
+ alternatives: results.filter(r => r.success),
+ failures: results.filter(r => !r.success),
+ recommendation: this.generateRecommendation(successfulSearches, totalResults, strategy)
+ };
+
+ if (successfulSearches > 0) {
+ summary['next_action'] = `Found results! Use read tool on noteIds from successful searches.`;
+ }
+
+ return summary;
+
+ } catch (error: unknown) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ log.error(`Error in smart retry: ${errorMessage}`);
+ return {
+ success: false,
+ error: errorMessage,
+ suggestion: 'Try manual search with simpler terms'
+ };
+ }
+ }
+
+ /**
+ * Generate recommendations based on retry results
+ */
+ private generateRecommendation(successful: number, totalResults: number, strategy: string): string {
+ if (successful === 0) {
+ if (strategy === 'broader') {
+ return 'Try with synonyms or related terms instead';
+ } else if (strategy === 'narrower') {
+ return 'Try broader terms or check spelling';
+ } else {
+ return 'Consider searching for completely different concepts or check if notes exist on this topic';
+ }
+ } else if (totalResults < 3) {
+ return 'Found few results. Try additional related terms or create notes on this topic';
+ } else {
+ return 'Good results found! Read the notes and search for more specific aspects';
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/server/src/services/llm/tools/tool_discovery_helper.ts b/apps/server/src/services/llm/tools/tool_discovery_helper.ts
new file mode 100644
index 0000000000..1595155cab
--- /dev/null
+++ b/apps/server/src/services/llm/tools/tool_discovery_helper.ts
@@ -0,0 +1,357 @@
+/**
+ * Tool Discovery Helper
+ *
+ * This tool helps LLMs understand what tools are available and when to use them.
+ * It provides smart recommendations based on user queries and current context.
+ */
+
+import type { Tool, ToolHandler } from './tool_interfaces.js';
+import log from '../../log.js';
+import toolRegistry from './tool_registry.js';
+
+/**
+ * Definition of the tool discovery helper
+ */
+export const toolDiscoveryHelperDefinition: Tool = {
+ type: 'function',
+ function: {
+ name: 'discover_tools',
+ description: 'Get recommendations for which tools to use for your task. Helps when you\'re unsure which tool is best.',
+ parameters: {
+ type: 'object',
+ properties: {
+ taskDescription: {
+ type: 'string',
+ description: 'Describe what you want to accomplish (e.g., "find notes about machine learning", "read a specific note").'
+ },
+ includeExamples: {
+ type: 'boolean',
+ description: 'Include usage examples for recommended tools (default: true).'
+ },
+ showAllTools: {
+ type: 'boolean',
+ description: 'Show all available tools instead of just recommendations (default: false).'
+ }
+ },
+ required: ['taskDescription']
+ }
+ }
+};
+
+/**
+ * Tool discovery helper implementation
+ */
+export class ToolDiscoveryHelper implements ToolHandler {
+ public definition: Tool = toolDiscoveryHelperDefinition;
+
+ /**
+ * Map task types to relevant tools
+ */
+ private getRelevantTools(taskDescription: string): string[] {
+ const task = taskDescription.toLowerCase();
+ const relevantTools: string[] = [];
+
+ // Search-related tasks
+ if (task.includes('find') || task.includes('search') || task.includes('look for')) {
+ if (task.includes('tag') || task.includes('label') || task.includes('attribute') || task.includes('category')) {
+ relevantTools.push('attribute_search');
+ }
+ if (task.includes('concept') || task.includes('about') || task.includes('related to')) {
+ relevantTools.push('search_notes');
+ }
+ if (task.includes('exact') || task.includes('specific') || task.includes('contains')) {
+ relevantTools.push('keyword_search_notes');
+ }
+ // Default to both semantic and keyword search if no specific indicators
+ if (!relevantTools.some(tool => tool.includes('search'))) {
+ relevantTools.push('search_notes', 'keyword_search_notes');
+ }
+ }
+
+ // Reading tasks
+ if (task.includes('read') || task.includes('view') || task.includes('show') || task.includes('content')) {
+ relevantTools.push('read_note');
+ }
+
+ // Creation tasks
+ if (task.includes('create') || task.includes('new') || task.includes('add') || task.includes('make')) {
+ relevantTools.push('note_creation');
+ }
+
+ // Modification tasks
+ if (task.includes('edit') || task.includes('update') || task.includes('change') || task.includes('modify')) {
+ relevantTools.push('note_update');
+ }
+
+ // Attribute/metadata tasks
+ if (task.includes('attribute') || task.includes('tag') || task.includes('label') || task.includes('metadata')) {
+ relevantTools.push('attribute_manager');
+ }
+
+ // Relationship tasks
+ if (task.includes('relation') || task.includes('connect') || task.includes('link') || task.includes('relationship')) {
+ relevantTools.push('relationship');
+ }
+
+ // Summary tasks
+ if (task.includes('summary') || task.includes('summarize') || task.includes('overview')) {
+ relevantTools.push('note_summarization');
+ }
+
+ // Calendar tasks
+ if (task.includes('calendar') || task.includes('date') || task.includes('schedule') || task.includes('time')) {
+ relevantTools.push('calendar_integration');
+ }
+
+ // Content extraction tasks
+ if (task.includes('extract') || task.includes('parse') || task.includes('analyze content')) {
+ relevantTools.push('content_extraction');
+ }
+
+ return relevantTools;
+ }
+
+ /**
+ * Get tool information with descriptions
+ */
+ private getToolInfo(): Record {
+ return {
+ 'search': {
+ description: '🔍 Universal search - automatically uses semantic, keyword, or attribute search',
+ bestFor: 'ANY search need - it intelligently routes to the best search method',
+ parameters: ['query (required)', 'searchType', 'maxResults', 'filters']
+ },
+ 'search_notes': {
+ description: '🧠 Semantic/conceptual search for notes',
+ bestFor: 'Finding notes about ideas, concepts, or topics described in various ways',
+ parameters: ['query (required)', 'parentNoteId', 'maxResults', 'summarize']
+ },
+ 'keyword_search_notes': {
+ description: '🔎 Exact keyword/phrase search for notes',
+ bestFor: 'Finding notes with specific words, phrases, or using search operators',
+ parameters: ['query (required)', 'maxResults', 'includeArchived']
+ },
+ 'attribute_search': {
+ description: '🏷️ Search notes by attributes (labels/relations)',
+ bestFor: 'Finding notes by categories, tags, status, or metadata',
+ parameters: ['attributeType (required)', 'attributeName (required)', 'attributeValue', 'maxResults']
+ },
+ 'read_note': {
+ description: '📖 Read full content of a specific note',
+ bestFor: 'Getting complete note content after finding it through search',
+ parameters: ['noteId (required)', 'includeAttributes']
+ },
+ 'note_creation': {
+ description: '📝 Create new notes',
+ bestFor: 'Adding new content, projects, or ideas to your notes',
+ parameters: ['title (required)', 'content', 'parentNoteId', 'noteType', 'attributes']
+ },
+ 'note_update': {
+ description: '✏️ Update existing note content',
+ bestFor: 'Modifying or adding to existing note content',
+ parameters: ['noteId (required)', 'title', 'content', 'updateMode']
+ },
+ 'attribute_manager': {
+ description: '🎯 Manage note attributes (labels, relations)',
+ bestFor: 'Adding, removing, or modifying note metadata and tags',
+ parameters: ['noteId (required)', 'action (required)', 'attributeType', 'attributeName', 'attributeValue']
+ },
+ 'relationship': {
+ description: '🔗 Manage note relationships',
+ bestFor: 'Creating connections between notes',
+ parameters: ['sourceNoteId (required)', 'action (required)', 'targetNoteId', 'relationType']
+ },
+ 'note_summarization': {
+ description: '📄 Summarize note content',
+ bestFor: 'Getting concise overviews of long notes',
+ parameters: ['noteId (required)', 'summaryType', 'maxLength']
+ },
+ 'content_extraction': {
+ description: '🎯 Extract specific information from notes',
+ bestFor: 'Pulling out specific data, facts, or structured information',
+ parameters: ['noteId (required)', 'extractionType (required)', 'criteria']
+ },
+ 'calendar_integration': {
+ description: '📅 Calendar and date-related operations',
+ bestFor: 'Working with dates, schedules, and time-based organization',
+ parameters: ['action (required)', 'date', 'noteId', 'eventDetails']
+ },
+ 'search_suggestion': {
+ description: '💡 Get search syntax help and suggestions',
+ bestFor: 'Learning how to use advanced search features',
+ parameters: ['searchType', 'query']
+ }
+ };
+ }
+
+ /**
+ * Generate workflow recommendations
+ */
+ private generateWorkflow(taskDescription: string, relevantTools: string[]): string[] {
+ const task = taskDescription.toLowerCase();
+ const workflows: string[] = [];
+
+ if (task.includes('find') && relevantTools.includes('search_notes')) {
+ workflows.push('1. Use search_notes for conceptual search → 2. Use read_note with returned noteId for full content');
+ }
+
+ if (task.includes('find') && relevantTools.includes('attribute_search')) {
+ workflows.push('1. Use attribute_search to find tagged notes → 2. Use read_note for detailed content');
+ }
+
+ if (task.includes('create') || task.includes('new')) {
+ workflows.push('1. Use note_creation to make the note → 2. Use attribute_manager to add tags/metadata');
+ }
+
+ if (task.includes('update') || task.includes('edit')) {
+ workflows.push('1. Use search tools to find the note → 2. Use read_note to see current content → 3. Use note_update to modify');
+ }
+
+ if (task.includes('organize') || task.includes('categorize')) {
+ workflows.push('1. Use search tools to find notes → 2. Use attribute_manager to add labels/categories');
+ }
+
+ return workflows;
+ }
+
+ /**
+ * Execute the tool discovery helper
+ */
+ public async execute(args: {
+ taskDescription: string,
+ includeExamples?: boolean,
+ showAllTools?: boolean
+ }): Promise {
+ try {
+ const { taskDescription, includeExamples = true, showAllTools = false } = args;
+
+ log.info(`Executing discover_tools - Task: "${taskDescription}", ShowAll: ${showAllTools}`);
+
+ const allTools = toolRegistry.getAllTools();
+ const toolInfo = this.getToolInfo();
+
+ if (showAllTools) {
+ // Show all available tools
+ const allToolsInfo = allTools.map(tool => {
+ const name = tool.definition.function.name;
+ const info = toolInfo[name];
+ return {
+ name,
+ description: info?.description || tool.definition.function.description,
+ bestFor: info?.bestFor || 'General purpose tool',
+ parameters: info?.parameters || ['See tool definition for parameters']
+ };
+ });
+
+ return {
+ taskDescription,
+ mode: 'all_tools',
+ message: '🗂️ All available tools in the system',
+ totalTools: allToolsInfo.length,
+ tools: allToolsInfo,
+ tip: 'Use discover_tools with a specific task description for targeted recommendations'
+ };
+ }
+
+ // Get relevant tools for the specific task
+ const relevantToolNames = this.getRelevantTools(taskDescription);
+ const workflows = this.generateWorkflow(taskDescription, relevantToolNames);
+
+ const recommendations = relevantToolNames.map(toolName => {
+ const info = toolInfo[toolName];
+ const result: any = {
+ tool: toolName,
+ description: info?.description || 'Tool description not available',
+ bestFor: info?.bestFor || 'Not specified',
+ priority: this.getToolPriority(toolName, taskDescription)
+ };
+
+ if (includeExamples) {
+ result.exampleUsage = this.getToolExample(toolName, taskDescription);
+ }
+
+ return result;
+ });
+
+ // Sort by priority
+ recommendations.sort((a, b) => a.priority - b.priority);
+
+ return {
+ taskDescription,
+ mode: 'targeted_recommendations',
+ message: `🎯 Found ${recommendations.length} relevant tools for your task`,
+ recommendations,
+ workflows: workflows.length > 0 ? {
+ message: '🔄 Suggested workflows for your task:',
+ steps: workflows
+ } : undefined,
+ nextSteps: {
+ immediate: recommendations.length > 0
+ ? `Start with: ${recommendations[0].tool} (highest priority for your task)`
+ : 'Try rephrasing your task or use showAllTools: true to see all options',
+ alternative: 'Use showAllTools: true to see all available tools if these don\'t fit your needs'
+ }
+ };
+ } catch (error: unknown) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ log.error(`Error executing discover_tools: ${errorMessage}`);
+ return `Error: ${errorMessage}`;
+ }
+ }
+
+ /**
+ * Get priority for a tool based on task description (lower = higher priority)
+ */
+ private getToolPriority(toolName: string, taskDescription: string): number {
+ const task = taskDescription.toLowerCase();
+
+ // Exact matches get highest priority
+ if (task.includes(toolName.replace('_', ' '))) return 1;
+
+ // Task-specific priorities
+ if (task.includes('find') || task.includes('search')) {
+ if (toolName === 'search_notes') return 2;
+ if (toolName === 'keyword_search_notes') return 3;
+ if (toolName === 'attribute_search') return 4;
+ }
+
+ if (task.includes('create') && toolName === 'note_creation') return 1;
+ if (task.includes('read') && toolName === 'read_note') return 1;
+ if (task.includes('update') && toolName === 'note_update') return 1;
+
+ return 5; // Default priority
+ }
+
+ /**
+ * Get example usage for a tool based on task description
+ */
+ private getToolExample(toolName: string, taskDescription: string): string {
+ const task = taskDescription.toLowerCase();
+
+ switch (toolName) {
+ case 'search_notes':
+ if (task.includes('machine learning')) {
+ return '{ "query": "machine learning algorithms classification" }';
+ }
+ return '{ "query": "project management methodologies" }';
+
+ case 'keyword_search_notes':
+ return '{ "query": "important TODO" }';
+
+ case 'attribute_search':
+ return '{ "attributeType": "label", "attributeName": "important" }';
+
+ case 'read_note':
+ return '{ "noteId": "abc123def456", "includeAttributes": true }';
+
+ case 'note_creation':
+ return '{ "title": "New Project Plan", "content": "Project details here..." }';
+
+ case 'note_update':
+ return '{ "noteId": "abc123def456", "content": "Updated content" }';
+
+ default:
+ return `Use ${toolName} with appropriate parameters`;
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/server/src/services/llm/tools/tool_initializer.ts b/apps/server/src/services/llm/tools/tool_initializer.ts
index e8ceca3eee..4a96d8cd46 100644
--- a/apps/server/src/services/llm/tools/tool_initializer.ts
+++ b/apps/server/src/services/llm/tools/tool_initializer.ts
@@ -8,6 +8,9 @@ import toolRegistry from './tool_registry.js';
import { SearchNotesTool } from './search_notes_tool.js';
import { KeywordSearchTool } from './keyword_search_tool.js';
import { AttributeSearchTool } from './attribute_search_tool.js';
+import { UnifiedSearchTool } from './unified_search_tool.js';
+import { ExecuteBatchTool } from './execute_batch_tool.js';
+import { SmartRetryTool } from './smart_retry_tool.js';
import { SearchSuggestionTool } from './search_suggestion_tool.js';
import { ReadNoteTool } from './read_note_tool.js';
import { NoteCreationTool } from './note_creation_tool.js';
@@ -17,6 +20,7 @@ import { RelationshipTool } from './relationship_tool.js';
import { AttributeManagerTool } from './attribute_manager_tool.js';
import { CalendarIntegrationTool } from './calendar_integration_tool.js';
import { NoteSummarizationTool } from './note_summarization_tool.js';
+import { ToolDiscoveryHelper } from './tool_discovery_helper.js';
import log from '../../log.js';
// Error type guard
@@ -32,12 +36,19 @@ export async function initializeTools(): Promise {
try {
log.info('Initializing LLM tools...');
- // Register search and discovery tools
+ // Register core utility tools FIRST (highest priority)
+ toolRegistry.registerTool(new ExecuteBatchTool()); // Batch execution for parallel tools
+ toolRegistry.registerTool(new UnifiedSearchTool()); // Universal search interface
+ toolRegistry.registerTool(new SmartRetryTool()); // Automatic retry with variations
+ toolRegistry.registerTool(new ReadNoteTool()); // Read note content
+
+ // Register individual search tools (kept for backwards compatibility but lower priority)
toolRegistry.registerTool(new SearchNotesTool()); // Semantic search
toolRegistry.registerTool(new KeywordSearchTool()); // Keyword-based search
toolRegistry.registerTool(new AttributeSearchTool()); // Attribute-specific search
+
+ // Register other discovery tools
toolRegistry.registerTool(new SearchSuggestionTool()); // Search syntax helper
- toolRegistry.registerTool(new ReadNoteTool()); // Read note content
// Register note creation and manipulation tools
toolRegistry.registerTool(new NoteCreationTool()); // Create new notes
@@ -52,6 +63,9 @@ export async function initializeTools(): Promise {
toolRegistry.registerTool(new ContentExtractionTool()); // Extract info from note content
toolRegistry.registerTool(new CalendarIntegrationTool()); // Calendar-related operations
+ // Register helper tools (simplified)
+ toolRegistry.registerTool(new ToolDiscoveryHelper()); // Tool discovery and usage guidance
+
// Log registered tools
const toolCount = toolRegistry.getAllTools().length;
const toolNames = toolRegistry.getAllTools().map(tool => tool.definition.function.name).join(', ');
diff --git a/apps/server/src/services/llm/tools/tool_interfaces.ts b/apps/server/src/services/llm/tools/tool_interfaces.ts
index ec90df67fd..9bd249c3c9 100644
--- a/apps/server/src/services/llm/tools/tool_interfaces.ts
+++ b/apps/server/src/services/llm/tools/tool_interfaces.ts
@@ -34,6 +34,12 @@ export interface ToolParameter {
type: string;
description: string;
enum?: string[];
+ default?: any;
+ minimum?: number;
+ maximum?: number;
+ minItems?: number;
+ maxItems?: number;
+ properties?: Record;
items?: ToolParameter | {
type: string;
properties?: Record;
diff --git a/apps/server/src/services/llm/tools/unified_search_tool.ts b/apps/server/src/services/llm/tools/unified_search_tool.ts
new file mode 100644
index 0000000000..220c2b25eb
--- /dev/null
+++ b/apps/server/src/services/llm/tools/unified_search_tool.ts
@@ -0,0 +1,260 @@
+/**
+ * Unified Search Tool
+ *
+ * This tool combines semantic search, keyword search, and attribute search into a single
+ * intelligent search interface that automatically routes to the appropriate backend.
+ */
+
+import type { Tool, ToolHandler } from './tool_interfaces.js';
+import log from '../../log.js';
+import { SearchNotesTool } from './search_notes_tool.js';
+import { KeywordSearchTool } from './keyword_search_tool.js';
+import { AttributeSearchTool } from './attribute_search_tool.js';
+
+/**
+ * Definition of the unified search tool
+ */
+export const unifiedSearchToolDefinition: Tool = {
+ type: 'function',
+ function: {
+ name: 'search',
+ description: 'Find notes intelligently. Example: search("machine learning") → finds related notes. Auto-detects search type (semantic/keyword/attribute).',
+ parameters: {
+ type: 'object',
+ properties: {
+ query: {
+ type: 'string',
+ description: 'Search query. Can be: conceptual phrases ("machine learning algorithms"), exact terms in quotes ("meeting notes"), labels (#important), relations (~relatedTo), or attribute queries (label:todo)'
+ },
+ searchType: {
+ type: 'string',
+ description: 'Optional: Force specific search type. Auto-detected if not specified.',
+ enum: ['auto', 'semantic', 'keyword', 'attribute']
+ },
+ maxResults: {
+ type: 'number',
+ description: 'Maximum results to return (default: 10, max: 50)'
+ },
+ filters: {
+ type: 'object',
+ description: 'Optional filters for search',
+ properties: {
+ parentNoteId: {
+ type: 'string',
+ description: 'Limit search to children of this note'
+ },
+ includeArchived: {
+ type: 'boolean',
+ description: 'Include archived notes (default: false)'
+ },
+ attributeType: {
+ type: 'string',
+ description: 'For attribute searches: "label" or "relation"'
+ },
+ attributeValue: {
+ type: 'string',
+ description: 'Optional value for attribute searches'
+ }
+ }
+ }
+ },
+ required: ['query']
+ }
+ }
+};
+
+/**
+ * Unified search tool implementation
+ */
+export class UnifiedSearchTool implements ToolHandler {
+ public definition: Tool = unifiedSearchToolDefinition;
+ private semanticSearchTool: SearchNotesTool;
+ private keywordSearchTool: KeywordSearchTool;
+ private attributeSearchTool: AttributeSearchTool;
+
+ constructor() {
+ this.semanticSearchTool = new SearchNotesTool();
+ this.keywordSearchTool = new KeywordSearchTool();
+ this.attributeSearchTool = new AttributeSearchTool();
+ }
+
+ /**
+ * Detect the search type from the query
+ */
+ private detectSearchType(query: string): 'semantic' | 'keyword' | 'attribute' {
+ // Check for attribute patterns
+ if (query.startsWith('#') || query.startsWith('~')) {
+ return 'attribute';
+ }
+
+ // Check for label: or relation: patterns
+ if (query.match(/^(label|relation):/i)) {
+ return 'attribute';
+ }
+
+ // Check for exact phrase searches (quoted strings)
+ if (query.includes('"') && query.indexOf('"') !== query.lastIndexOf('"')) {
+ return 'keyword';
+ }
+
+ // Check for boolean operators
+ if (query.match(/\b(AND|OR|NOT)\b/)) {
+ return 'keyword';
+ }
+
+ // Check for special search operators
+ if (query.includes('note.') || query.includes('*=')) {
+ return 'keyword';
+ }
+
+ // Default to semantic search for natural language queries
+ return 'semantic';
+ }
+
+ /**
+ * Parse attribute search from query
+ */
+ private parseAttributeSearch(query: string): { type: string, name: string, value?: string } | null {
+ // Handle #label or ~relation format
+ if (query.startsWith('#')) {
+ const parts = query.substring(1).split('=');
+ return {
+ type: 'label',
+ name: parts[0],
+ value: parts[1]
+ };
+ }
+
+ if (query.startsWith('~')) {
+ const parts = query.substring(1).split('=');
+ return {
+ type: 'relation',
+ name: parts[0],
+ value: parts[1]
+ };
+ }
+
+ // Handle label:name or relation:name format
+ const match = query.match(/^(label|relation):(\w+)(?:=(.+))?$/i);
+ if (match) {
+ return {
+ type: match[1].toLowerCase(),
+ name: match[2],
+ value: match[3]
+ };
+ }
+
+ return null;
+ }
+
+ /**
+ * Execute the unified search tool
+ */
+ public async execute(args: {
+ query: string,
+ searchType?: string,
+ maxResults?: number,
+ filters?: {
+ parentNoteId?: string,
+ includeArchived?: boolean,
+ attributeType?: string,
+ attributeValue?: string
+ }
+ }): Promise {
+ try {
+ const { query, searchType = 'auto', maxResults = 10, filters = {} } = args;
+
+ log.info(`Executing unified search - Query: "${query}", Type: ${searchType}, MaxResults: ${maxResults}`);
+
+ // Detect search type if auto
+ let actualSearchType = searchType;
+ if (searchType === 'auto') {
+ actualSearchType = this.detectSearchType(query);
+ log.info(`Auto-detected search type: ${actualSearchType}`);
+ }
+
+ // Route to appropriate search tool
+ switch (actualSearchType) {
+ case 'semantic': {
+ log.info('Routing to semantic search');
+ const result = await this.semanticSearchTool.execute({
+ query,
+ parentNoteId: filters.parentNoteId,
+ maxResults,
+ summarize: false
+ });
+
+ // Add search type indicator
+ if (typeof result === 'object' && !Array.isArray(result)) {
+ return {
+ ...result,
+ searchMethod: 'semantic',
+ tip: 'For exact matches, try keyword search. For tagged notes, try attribute search.'
+ };
+ }
+ return result;
+ }
+
+ case 'keyword': {
+ log.info('Routing to keyword search');
+ const result = await this.keywordSearchTool.execute({
+ query,
+ maxResults,
+ includeArchived: filters.includeArchived || false
+ });
+
+ // Add search type indicator
+ if (typeof result === 'object' && !Array.isArray(result)) {
+ return {
+ ...result,
+ searchMethod: 'keyword',
+ tip: 'For conceptual matches, try semantic search. For tagged notes, try attribute search.'
+ };
+ }
+ return result;
+ }
+
+ case 'attribute': {
+ log.info('Routing to attribute search');
+
+ // Parse attribute from query if not provided in filters
+ const parsed = this.parseAttributeSearch(query);
+ if (!parsed) {
+ return {
+ error: 'Invalid attribute search format',
+ help: 'Use #labelname, ~relationname, label:name, or relation:name',
+ examples: ['#important', '~relatedTo', 'label:todo', 'relation:partOf=projectX']
+ };
+ }
+
+ const result = await this.attributeSearchTool.execute({
+ attributeType: filters.attributeType || parsed.type,
+ attributeName: parsed.name,
+ attributeValue: filters.attributeValue || parsed.value,
+ maxResults
+ });
+
+ // Add search type indicator
+ if (typeof result === 'object' && !Array.isArray(result)) {
+ return {
+ ...result,
+ searchMethod: 'attribute',
+ tip: 'For content matches, try semantic or keyword search.'
+ };
+ }
+ return result;
+ }
+
+ default:
+ return {
+ error: `Unknown search type: ${actualSearchType}`,
+ validTypes: ['auto', 'semantic', 'keyword', 'attribute']
+ };
+ }
+ } catch (error: unknown) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ log.error(`Error executing unified search: ${errorMessage}`);
+ return `Error: ${errorMessage}`;
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/server/src/services/llm/tools/workflow_helper.ts b/apps/server/src/services/llm/tools/workflow_helper.ts
new file mode 100644
index 0000000000..18536f26f3
--- /dev/null
+++ b/apps/server/src/services/llm/tools/workflow_helper.ts
@@ -0,0 +1,408 @@
+/**
+ * Workflow Helper Tool
+ *
+ * This tool helps LLMs understand and execute multi-step workflows by providing
+ * smart guidance on tool chaining and next steps.
+ */
+
+import type { Tool, ToolHandler } from './tool_interfaces.js';
+import log from '../../log.js';
+
+/**
+ * Definition of the workflow helper tool
+ */
+export const workflowHelperDefinition: Tool = {
+ type: 'function',
+ function: {
+ name: 'workflow_helper',
+ description: `WORKFLOW GUIDANCE for multi-step tasks. Get smart suggestions for tool chaining and next steps.
+
+ BEST FOR: Planning complex workflows, understanding tool sequences, getting unstuck
+ USE WHEN: You need to do multiple operations, aren't sure what to do next, or want workflow optimization
+ HELPS WITH: Tool sequencing, parameter passing, workflow planning
+
+ TIP: Use this when you have partial results and need guidance on next steps
+
+ NEXT STEPS: Follow the recommended workflow steps provided`,
+ parameters: {
+ type: 'object',
+ properties: {
+ currentStep: {
+ type: 'string',
+ description: `📍 DESCRIBE YOUR CURRENT STEP: What have you just done or what results do you have?
+
+ ✅ GOOD EXAMPLES:
+ - "I just found 5 notes about machine learning using search_notes"
+ - "I have a noteId abc123def456 and want to modify it"
+ - "I searched but got no results"
+ - "I created a new note and want to organize it"
+
+ 💡 Be specific about your current state and what you've accomplished`
+ },
+ goal: {
+ type: 'string',
+ description: `🎯 FINAL GOAL: What are you ultimately trying to accomplish?
+
+ ✅ EXAMPLES:
+ - "Find and read all notes about a specific project"
+ - "Create a comprehensive summary of all my research notes"
+ - "Organize all my TODO notes by priority"
+ - "Find related notes and create connections between them"`
+ },
+ availableData: {
+ type: 'string',
+ description: `📊 AVAILABLE DATA: What noteIds, search results, or other data do you currently have?
+
+ ✅ EXAMPLES:
+ - "noteIds: abc123, def456, ghi789"
+ - "Search results with 3 notes about project management"
+ - "Empty search results for machine learning"
+ - "Just created noteId xyz999"`
+ },
+ includeExamples: {
+ type: 'boolean',
+ description: '📚 INCLUDE EXAMPLES: Get specific command examples for next steps (default: true)'
+ }
+ },
+ required: ['currentStep', 'goal']
+ }
+ }
+};
+
+/**
+ * Workflow helper implementation
+ */
+export class WorkflowHelper implements ToolHandler {
+ public definition: Tool = workflowHelperDefinition;
+
+ /**
+ * Common workflow patterns
+ */
+ private getWorkflowPatterns(): Record {
+ return {
+ 'search_read_analyze': {
+ name: '🔍➡️📖➡️🧠 Search → Read → Analyze',
+ description: 'Find notes, read their content, then analyze or summarize',
+ steps: [
+ 'Use search tools to find relevant notes',
+ 'Use read_note to get full content of interesting results',
+ 'Use note_summarization or content_extraction for analysis'
+ ],
+ examples: [
+ 'Research project: Find all research notes → Read them → Summarize findings',
+ 'Learning topic: Search for learning materials → Read content → Extract key concepts'
+ ]
+ },
+ 'search_create_organize': {
+ name: '🔍➡️📝➡️🏷️ Search → Create → Organize',
+ description: 'Find related content, create new notes, then organize with attributes',
+ steps: [
+ 'Search for related existing content',
+ 'Create new note with note_creation',
+ 'Add attributes/relations with attribute_manager'
+ ],
+ examples: [
+ 'New project: Find similar projects → Create project note → Tag with #project',
+ 'Meeting notes: Search for project context → Create meeting note → Link to project'
+ ]
+ },
+ 'find_read_update': {
+ name: '🔍➡️📖➡️✏️ Find → Read → Update',
+ description: 'Find existing notes, review content, then make updates',
+ steps: [
+ 'Use search tools to locate the note',
+ 'Use read_note to see current content',
+ 'Use note_update to make changes'
+ ],
+ examples: [
+ 'Update project status: Find project note → Read current status → Update with progress',
+ 'Improve documentation: Find doc note → Read content → Add new information'
+ ]
+ },
+ 'organize_existing': {
+ name: '🔍➡️🏷️➡️🔗 Find → Tag → Connect',
+ description: 'Find notes that need organization, add attributes, create relationships',
+ steps: [
+ 'Search for notes to organize',
+ 'Use attribute_manager to add labels/categories',
+ 'Use relationship tool to create connections'
+ ],
+ examples: [
+ 'Organize research: Find research notes → Tag by topic → Link related studies',
+ 'Clean up TODOs: Find TODO notes → Tag by priority → Link to projects'
+ ]
+ }
+ };
+ }
+
+ /**
+ * Analyze current step and recommend next actions
+ */
+ private analyzeCurrentStep(currentStep: string, goal: string, availableData?: string): {
+ analysis: string;
+ recommendations: Array<{
+ action: string;
+ tool: string;
+ parameters: Record;
+ reasoning: string;
+ priority: number;
+ }>;
+ warnings?: string[];
+ } {
+ const step = currentStep.toLowerCase();
+ const goalLower = goal.toLowerCase();
+ const recommendations: any[] = [];
+ const warnings: string[] = [];
+
+ // Analyze search results
+ if (step.includes('found') && step.includes('notes')) {
+ if (step.includes('no results') || step.includes('empty') || step.includes('0 notes')) {
+ recommendations.push({
+ action: 'Try alternative search approaches',
+ tool: 'search_notes',
+ parameters: { query: 'broader or alternative search terms' },
+ reasoning: 'Empty results suggest need for different search strategy',
+ priority: 1
+ });
+ recommendations.push({
+ action: 'Try keyword search instead',
+ tool: 'keyword_search_notes',
+ parameters: { query: 'specific keywords from your search' },
+ reasoning: 'Keyword search might find what semantic search missed',
+ priority: 2
+ });
+ warnings.push('Consider if the content might not exist yet - you may need to create it');
+ } else {
+ // Has search results
+ recommendations.push({
+ action: 'Read the most relevant notes',
+ tool: 'read_note',
+ parameters: { noteId: 'from search results', includeAttributes: true },
+ reasoning: 'Get full content to understand what you found',
+ priority: 1
+ });
+
+ if (goalLower.includes('summary') || goalLower.includes('analyze')) {
+ recommendations.push({
+ action: 'Summarize the content',
+ tool: 'note_summarization',
+ parameters: { noteId: 'from search results' },
+ reasoning: 'Goal involves analysis or summarization',
+ priority: 2
+ });
+ }
+ }
+ }
+
+ // Analyze note reading
+ if (step.includes('read') || step.includes('noteId')) {
+ if (goalLower.includes('update') || goalLower.includes('edit') || goalLower.includes('modify')) {
+ recommendations.push({
+ action: 'Update the note content',
+ tool: 'note_update',
+ parameters: { noteId: 'the one you just read', content: 'new content' },
+ reasoning: 'Goal involves modifying existing content',
+ priority: 1
+ });
+ }
+
+ if (goalLower.includes('organize') || goalLower.includes('tag') || goalLower.includes('categorize')) {
+ recommendations.push({
+ action: 'Add organizing attributes',
+ tool: 'attribute_manager',
+ parameters: { noteId: 'the one you read', action: 'add', attributeType: 'label' },
+ reasoning: 'Goal involves organization and categorization',
+ priority: 1
+ });
+ }
+
+ if (goalLower.includes('related') || goalLower.includes('connect') || goalLower.includes('link')) {
+ recommendations.push({
+ action: 'Search for related content',
+ tool: 'search_notes',
+ parameters: { query: 'concepts from the note you read' },
+ reasoning: 'Goal involves finding and connecting related content',
+ priority: 2
+ });
+ }
+ }
+
+ // Analyze creation
+ if (step.includes('created') || step.includes('new note')) {
+ recommendations.push({
+ action: 'Add organizing attributes',
+ tool: 'attribute_manager',
+ parameters: { noteId: 'the newly created note', action: 'add' },
+ reasoning: 'New notes should be organized with appropriate tags',
+ priority: 1
+ });
+
+ if (goalLower.includes('project') || goalLower.includes('research')) {
+ recommendations.push({
+ action: 'Find and link related notes',
+ tool: 'search_notes',
+ parameters: { query: 'related to your new note topic' },
+ reasoning: 'Connect new content to existing related materials',
+ priority: 2
+ });
+ }
+ }
+
+ return {
+ analysis: this.generateAnalysis(currentStep, goal, recommendations.length),
+ recommendations: recommendations.sort((a, b) => a.priority - b.priority),
+ warnings: warnings.length > 0 ? warnings : undefined
+ };
+ }
+
+ /**
+ * Generate workflow analysis
+ */
+ private generateAnalysis(currentStep: string, goal: string, recommendationCount: number): string {
+ const patterns = this.getWorkflowPatterns();
+
+ let analysis = `📊 CURRENT STATE: ${currentStep}\n`;
+ analysis += `🎯 TARGET GOAL: ${goal}\n\n`;
+
+ if (recommendationCount > 0) {
+ analysis += `✅ I've identified ${recommendationCount} recommended next steps based on your current progress and goal.\n\n`;
+ } else {
+ analysis += `🤔 Your situation is unique. I'll provide general guidance based on common patterns.\n\n`;
+ }
+
+ // Suggest relevant workflow patterns
+ const goalLower = goal.toLowerCase();
+ if (goalLower.includes('read') && goalLower.includes('find')) {
+ analysis += `📖 PATTERN MATCH: This looks like a "${patterns.search_read_analyze.name}" workflow\n`;
+ } else if (goalLower.includes('create') && goalLower.includes('organize')) {
+ analysis += `📝 PATTERN MATCH: This looks like a "${patterns.search_create_organize.name}" workflow\n`;
+ } else if (goalLower.includes('update') && goalLower.includes('find')) {
+ analysis += `✏️ PATTERN MATCH: This looks like a "${patterns.find_read_update.name}" workflow\n`;
+ }
+
+ return analysis;
+ }
+
+ /**
+ * Execute the workflow helper tool
+ */
+ public async execute(args: {
+ currentStep: string,
+ goal: string,
+ availableData?: string,
+ includeExamples?: boolean
+ }): Promise {
+ try {
+ const { currentStep, goal, availableData, includeExamples = true } = args;
+
+ log.info(`Executing workflow_helper - Current: "${currentStep}", Goal: "${goal}"`);
+
+ const analysis = this.analyzeCurrentStep(currentStep, goal, availableData);
+ const patterns = this.getWorkflowPatterns();
+
+ // Extract noteIds from available data if provided
+ const noteIds = availableData ? this.extractNoteIds(availableData) : [];
+
+ const response: any = {
+ currentStep,
+ goal,
+ analysis: analysis.analysis,
+ immediateNext: analysis.recommendations.length > 0 ? {
+ primaryAction: analysis.recommendations[0],
+ alternatives: analysis.recommendations.slice(1, 3)
+ } : undefined,
+ extractedData: {
+ noteIds: noteIds.length > 0 ? noteIds : undefined,
+ hasData: !!availableData
+ }
+ };
+
+ if (analysis.warnings) {
+ response.warnings = {
+ message: '⚠️ Important considerations:',
+ items: analysis.warnings
+ };
+ }
+
+ if (includeExamples && analysis.recommendations.length > 0) {
+ response.examples = {
+ message: '📚 Specific tool usage examples:',
+ commands: analysis.recommendations.slice(0, 2).map(rec => ({
+ tool: rec.tool,
+ example: this.generateExample(rec.tool, rec.parameters, noteIds),
+ description: rec.reasoning
+ }))
+ };
+ }
+
+ // Add relevant workflow patterns
+ response.workflowPatterns = {
+ message: '🔄 Common workflow patterns you might find useful:',
+ patterns: Object.values(patterns).slice(0, 2).map(pattern => ({
+ name: pattern.name,
+ description: pattern.description,
+ steps: pattern.steps
+ }))
+ };
+
+ response.tips = [
+ '💡 Use the noteId values from search results, not note titles',
+ '🔄 Check tool results carefully before proceeding to next step',
+ '📊 Use workflow_helper again if you get stuck or need guidance'
+ ];
+
+ return response;
+ } catch (error: unknown) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ log.error(`Error executing workflow_helper: ${errorMessage}`);
+ return `Error: ${errorMessage}`;
+ }
+ }
+
+ /**
+ * Extract noteIds from data string
+ */
+ private extractNoteIds(data: string): string[] {
+ // Look for patterns like noteId: "abc123" or "abc123def456"
+ const idPattern = /(?:noteId[:\s]*["']?|["'])([a-zA-Z0-9]{8,})['"]/g;
+ const matches: string[] = [];
+ let match;
+
+ while ((match = idPattern.exec(data)) !== null) {
+ if (match[1] && !matches.includes(match[1])) {
+ matches.push(match[1]);
+ }
+ }
+
+ return matches;
+ }
+
+ /**
+ * Generate specific examples for tool usage
+ */
+ private generateExample(tool: string, parameters: Record, noteIds: string[]): string {
+ const sampleNoteId = noteIds[0] || 'abc123def456';
+
+ switch (tool) {
+ case 'read_note':
+ return `{ "noteId": "${sampleNoteId}", "includeAttributes": true }`;
+ case 'note_update':
+ return `{ "noteId": "${sampleNoteId}", "content": "Updated content here" }`;
+ case 'attribute_manager':
+ return `{ "noteId": "${sampleNoteId}", "action": "add", "attributeType": "label", "attributeName": "important" }`;
+ case 'search_notes':
+ return `{ "query": "broader search terms related to your topic" }`;
+ case 'keyword_search_notes':
+ return `{ "query": "specific keywords OR alternative terms" }`;
+ case 'note_creation':
+ return `{ "title": "New Note Title", "content": "Note content here" }`;
+ default:
+ return `Use ${tool} with appropriate parameters`;
+ }
+ }
+}
\ No newline at end of file