Search Manager service specification
Search Manager service specification
Responsibility
Dispatch search queries to all registered plugins with the search capability, collect results with per-plugin timeouts, merge/deduplicate, and emit results progressively to the frontend via Tauri events.
Ownership boundary
The Search Manager holds an Arc<PluginRuntime> and calls into it directly. It does not read or write plugin stdio. It does not manage plugin processes. It does not parse JSON-RPC responses.
- Plugin Runtime owns: process lifecycle, stdin/stdout I/O, JSON-RPC serialization/deserialization
- Search Manager owns: dispatch logic, timing, result merging and ranking
Forward path
impl SearchManager {
/// Start a search. Returns a query_id for correlation.
pub async fn start_search(&self, query: String) -> Result<String, SearchError>;
/// Cancel an in-flight search.
pub fn cancel_search(&self) -> Result<(), SearchError>;
/// Update search configuration at runtime.
pub fn set_search_config(&self, config: SearchConfig) -> Result<(), SearchError>;
}
Internal flow:
- Generate
query_id(UUIDv4) - For each plugin with
searchcapability, callruntime.search(plugin_id, query) - Await each result concurrently (PluginRuntime enforces timeouts internally)
- On each result: emit
search_resultevent - On timeout: emit
search_errorevent (PluginRuntime has already killed the plugin) - When all done: emit
search_doneevent
SearchConfig
pub struct SearchConfig {
pub max_results_per_plugin: usize, // 50
pub dedup_tolerance: f32, // 0.0–1.0, normalized title+artist similarity
pub ranking_weights: RankingWeights,
}
pub struct RankingWeights {
pub relevance: f32, // plugin-reported relevance score
pub popularity: f32, // if plugin provides it
pub source: f32, // per-plugin trust weight, user-configurable
}
Default values:
max_results_per_plugin: 50dedup_tolerance: 1.0 (exact match only)ranking_weights:{ relevance: 1.0, popularity: 0.5, source: 1.0 }
Timeouts are owned by PluginRuntime (see plugin-runtime.md). The Search Manager never
configures or overrides per-method timeouts. For explicit user-initiated cancellation,
cancel_search calls runtime.cancel(plugin_id) for each plugin with in-flight
requests.
Deduplication algorithm
Exact match on normalized title::artist string:
- Lowercase both
titleandartist_name - Strip punctuation characters (
.,!?;:'"()[]{}) - Collapse whitespace runs to single spaces
- Concatenate:
normalized_title::normalized_artist - If two results from different plugins produce the same key, they’re duplicates
- Keep the one from the higher-weighted source plugin (per
ranking_weights.source)
No Levenshtein or fuzzy matching in v1. Exact normalized match catches the 90% case (same song, different capitalization/punctuation) with zero false positives.
SearchQuery
pub struct SearchQuery {
pub query: String,
pub limit: usize, // max results from this plugin
}
References
- ADR-007 (search manager — Rust backend with Tauri events)
- FR-CORE-4 (parallel search manager)