How SearchOrchestrator Coordinates Multi-Strategy Search in Claude-Mem

SearchOrchestrator acts as a decision-tree router that normalizes input parameters and automatically selects between SQLite, Chroma, or Hybrid search strategies based on query presence and vector store availability.

Claude-Mem is an open-source memory system that enables persistent context across sessions. At the heart of its retrieval pipeline sits the SearchOrchestrator, which intelligently coordinates three distinct search strategies to handle everything from exact filter queries to semantic vector searches.

Search Strategy Architecture

The system implements three independent strategies located in src/services/worker/search/strategies/:

  • SQLiteSearchStrategy (SQLiteSearchStrategy.ts): Executes filter-only queries directly against the SQLite database. Serves as the universal fallback when vector search is unavailable or fails.
  • ChromaSearchStrategy (ChromaSearchStrategy.ts): Performs semantic vector search via ChromaDB, then hydrates result IDs from SQLite.
  • HybridSearchStrategy (HybridSearchStrategy.ts): Combines semantic relevance from Chroma with SQLite filters for concept-based retrieval.

The SearchOrchestrator (src/services/worker/search/SearchOrchestrator.ts) exposes a unified interface while internally routing requests to the appropriate implementation.

How SearchOrchestrator Routes Queries

The orchestrator implements a decision-tree routing logic that evaluates request parameters and system state to select the optimal strategy.

Parameter Normalization

Before routing, the normalizeParams method (lines 38-82 in SearchOrchestrator.ts) converts URL-friendly inputs into a uniform NormalizedParams object. This handles comma-separated lists, date ranges (dateStart/dateEnd), and type conversions, ensuring downstream strategies receive consistent inputs regardless of the API surface.

Strategy Selection Logic

The search method implements the following routing tree:

  1. No query string provided → Route to SQLiteSearchStrategy for filter-only retrieval (lines 84-88).
  2. Query string present and Chroma available → Route to ChromaSearchStrategy for semantic search (lines 90-101).
  3. Chroma fails or returns empty → Fallback to SQLiteSearchStrategy (lines 100-107).
  4. No Chroma instance (e.g., fresh install) → Return empty result with usedChroma: false (lines 113-120).

This architecture ensures graceful degradation: semantic search enhances results when available, but the system remains functional with SQLite alone.

Higher-Level Search Helpers

Beyond the generic search method, the orchestrator provides specialized helpers that leverage HybridSearchStrategy when available:

  • findByConcept (lines 124-137): Searches for observations linked to specific concepts, preferring hybrid semantic+filter approach.
  • findByType (lines 139-152): Retrieves items by type classification with optional hybrid ranking.
  • findByFile (lines 154-165): Locates observations associated with specific source files.

Each helper first checks for this.hybridStrategy existence, falling back to SQLiteSearchStrategy if the hybrid implementation is not instantiated.

Result Processing and Formatting

The orchestrator owns post-processing utilities that transform raw database rows into user-facing output:

These components ensure consistent output formatting regardless of which search strategy generated the underlying data.

Code Examples

Basic Semantic Search with Automatic Fallback

import { SearchOrchestrator } from './src/services/worker/search/SearchOrchestrator.js';
import { SessionSearch } from './src/sqlite/SessionSearch.js';
import { SessionStore } from './src/sqlite/SessionStore.js';
import { ChromaSync } from './src/sync/ChromaSync.js';

const sessionSearch = new SessionSearch(/* … */);
const sessionStore = new SessionStore(/* … */);
const chromaSync = new ChromaSync(/* … */); // null if Chroma unavailable

const orchestrator = new SearchOrchestrator(sessionSearch, sessionStore, chromaSync);

const result = await orchestrator.search({
  query: 'how did I debug the login flow',
  searchType: 'all',
  limit: 20,
});

console.log(orchestrator.formatSearchResults(result.results, 'how did I debug the login flow'));

If chromaSync is null, the orchestrator automatically routes to SQLiteSearchStrategy.

Filter-Only Search (SQLite Only)

const filterResult = await orchestrator.search({
  dateStart: '2024-01-01',
  dateEnd: '2024-01-31',
  type: 'observations',
  concepts: 'authentication',
  limit: 50,
});

console.log(orchestrator.formatSearchResults(filterResult.results, ''));

Concept-Based Search Using Hybrid Strategy

const conceptResult = await orchestrator.findByConcept('react-hooks', {
  limit: 10,
  project: 'frontend-app',
});

console.log(orchestrator.formatSearchResults(conceptResult.results, 'react hooks'));

Building a Timeline Around Results

const timeline = orchestrator.getTimeline(timelineData, anchorId, anchorEpoch, 5, 5);
console.log(orchestrator.formatTimeline(timeline, anchorId, { query: 'error handling' }));

Summary

  • SearchOrchestrator serves as the central router in src/services/worker/search/SearchOrchestrator.ts, coordinating three distinct search implementations.
  • The normalizeParams method standardizes inputs before routing, ensuring consistency across strategies.
  • Strategy selection follows a decision tree: no query → SQLite; query + Chroma → Chroma; Chroma failure → SQLite fallback.
  • Higher-level helpers (findByConcept, findByType, findByFile) prefer Hybrid search when available, falling back to SQLite.
  • Post-processing via ResultFormatter and TimelineBuilder provides consistent output formatting regardless of the underlying strategy.

Frequently Asked Questions

What happens if ChromaDB is not installed or unavailable?

If the ChromaSync instance passed to the orchestrator is null or undefined, the search method detects this condition (lines 113-120) and returns an empty result object with usedChroma: false. For queries without a free-text query parameter, the system continues to function normally using SQLiteSearchStrategy for filter-based retrieval.

How does SearchOrchestrator handle date range filtering?

The orchestrator's normalizeParams method (lines 38-82) processes dateStart and dateEnd parameters, converting them from URL-friendly string formats into standardized Date objects or timestamps. These normalized parameters are then passed to the selected search strategy, which applies them as SQL WHERE clauses in SQLiteSearchStrategy or as metadata filters in ChromaSearchStrategy.

Can I use SearchOrchestrator without the HybridSearchStrategy?

Yes. The HybridSearchStrategy is optional. The orchestrator checks for its existence in the higher-level helper methods (findByConcept, findByType, findByFile). If this.hybridStrategy is undefined, these methods automatically fall back to SQLiteSearchStrategy. The core search method functions entirely with just SQLiteSearchStrategy and optionally ChromaSearchStrategy.

What is the difference between the search method and findByConcept?

The search method is the low-level entry point that routes requests to SQLiteSearchStrategy or ChromaSearchStrategy based on the presence of a query parameter. The findByConcept method is a higher-level helper specifically optimized for concept-based retrieval. It attempts to use HybridSearchStrategy (which combines semantic search with concept filtering) first, falling back to SQLiteSearchStrategy if hybrid search is unavailable.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →