`HybridQueryWorker` orchestrates parallel retrieval from pgvector and OpenSearch, applies RRF fusion, and returns ranked results with score breakdown and latency metrics. It replaces `QueryWorker` as the primary query engine behind `GET /memory/query`.
## Design
**Execution flow:**
1. Call `QueryOptimizer::optimize_query()` → get `QueryContext` with `SearchStrategy`.
2. Generate embedding via `EmbeddingsClient::embed()`.
- **LexicalFirst:** OpenSearch top-200 → extract IDs → pgvector `WHERE id IN (...)` top-10.
- **SemanticOnly:** pgvector top-50 (fallback if OpenSearch unavailable).
- **LexicalOnly:** OpenSearch top-50 (fallback if embedding model unavailable).
4. Build response with score breakdown.
**Critical: use actual VectorStore API.**
The existing `VectorStore` exposes `search_l1(project, &embedding, limit)` and `search_l2(project, &embedding)`. The worker must call these — not invented methods.
For the cascading strategy (`LexicalFirst`), a new `search_l1_by_ids(project, &embedding, limit, &[chunk_id])` method is needed on `VectorStore`. This is a filtered pgvector query:
10. Write integration tests with mock VectorStore + mock OpenSearch.
## Acceptance
1. Hybrid query returns results from **both** engines — check `semantic_rank` and `lexical_rank` are both `Some` for documents appearing in both lists.
2. Cascading query's pgvector call receives only IDs from the OpenSearch narrowing step — verify with query log or mock.
3. If `OpenSearchClient` is `None`, strategy automatically falls back to `SemanticOnly`.
4. If embedding generation fails, strategy falls back to `LexicalOnly` (if OpenSearch available) or returns error.
5.`metrics.total_time_ms` is populated and < 500ms for test fixtures.
6.`metrics.semantic_time_ms` and `lexical_time_ms` are roughly equal (parallel execution, not serial).
7. Results are sorted by `final_score` descending.
8.`final_count` ≤ `RRFConfig.final_k`.
## Verify
```bash
cargo test -p mem-cli hybrid_query -- --nocapture
```
**False pass:** Worker always returns semantic-only results even when strategy is `Hybrid`. Check by asserting `lexical_rank.is_some()` on at least one result when OpenSearch is configured. Another false pass: serial execution disguised as parallel — assert `max(semantic_time_ms, lexical_time_ms) ≈ total - fusion_time_ms`, not `sum`.
## Artifacts
-`crates/mem-cli/src/hybrid_query_worker.rs` (rewrite from current stub)