feat(agent): HDF5Memory::search with source filters, re-ranking, confidence

`HDF5Memory::search(query_embedding, query_text, &SearchOptions)` is the
store's full search path. `SearchOptions::new(k)` is plain hybrid search
with the tuned default fusion; each further stage is opt-in:

- `with_sources([..])`: only records from these source channels. The
  filter applies before ranking, so a filtered search still returns up
  to k results, normalised over what it can return. The HNSW pool is
  over-fetched in proportion to what the filter removes, and the allowed
  records are scanned exactly whenever that costs fewer distance
  evaluations than the index would (~pool x M) — and as the fallback if
  the pool comes back short. Keyword matches are filtered too.
- `with_rerank(ReRankConfig)` re-ranks a max(3k, 10) candidate pool by
  relevance, recency, source authority and activation;
  `with_confidence(ConfidenceConfig)` drops low-confidence results;
  `at_time(now)` pins the recency clock.

These were reachable only through the OpenClaw backend, which is now
`search` with both on. Its Hebbian boost now goes to the k results it
returns rather than the whole 3k candidate pool. `hybrid_search` and
`hybrid_search_with` are wrappers and unchanged (tested bit for bit).

Measured on tank (search_harness --options-study --full, 3 runs): at
100K every filter — 50%, 10%, 1% of the store, and records far from the
query — returns the exact filtered top 10, and none is slower than an
unfiltered search (1%: 2.3 ms vs 4.6 ms). Re-rank + confidence costs
about 3%. A first version decided between index and exact scan by pool
size vs store size; it measured 0.976 recall at 12.3 ms on the
far-from-query filter, which is why the rule compares costs instead.

Tests: tests/search_options.rs (filter correctness and full pages via
both paths, far-from-query fallback, edge cases, equality with
hybrid_search_with, re-rank recency, confidence, boost scope).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-24 16:35:42 -05:00
co-authored by Claude Opus 5.5
parent d0db83812b
commit c470244a6f
10 changed files with 951 additions and 110 deletions
+43
View File
@@ -149,6 +149,49 @@ vectors, and recall is measured against brute-force ground truth rather than
against the f32 index, whose own approximation errors a re-scored search is
entitled to get right.
### Search options: source filters, re-ranking, confidence
Measured 2026-09-24 on tank (AMD Ryzen 7 7800X3D). `HDF5Memory::search`
with `SearchOptions`, clustered 384-dim data, k = 10, Hebbian boosting off.
Filters keep 50%, 10% or 1% of the store at random, or two whole clusters
chosen *away* from each query's own — the case an ANN index handles worst,
because nothing it finds near the query is allowed. Recall is vector-only
against an exact scan of the allowed records; latency is full hybrid search
(vector + BM25). 200 queries, medians of three runs (recall was identical in
every run).
```bash
cargo run --release -p clawhdf5-bench --bin search_harness -- --options-study --full
```
| N | options | filtered recall@10 | p50 ms | p99 ms |
|---:|---|---:|---:|---:|
| 10 000 | no filter | 1.0000 | 0.488 | 0.518 |
| 10 000 | random 50% | 1.0000 | 0.520 | 0.563 |
| 10 000 | random 10% | 1.0000 | 0.342 | 0.367 |
| 10 000 | random 1% | 1.0000 | 0.220 | 0.239 |
| 10 000 | 2 clusters away from the query | 1.0000 | 0.252 | 0.278 |
| 10 000 | re-rank | — | 0.562 | 0.674 |
| 10 000 | re-rank + confidence | — | 0.554 | 0.575 |
| 100 000 | no filter | 0.9995 | 4.600 | 5.245 |
| 100 000 | random 50% | 1.0000 | 4.770 | 5.614 |
| 100 000 | random 10% | 1.0000 | 3.244 | 4.008 |
| 100 000 | random 1% | 1.0000 | 2.288 | 2.943 |
| 100 000 | 2 clusters away from the query | 1.0000 | 2.298 | 3.048 |
| 100 000 | re-rank | — | 4.747 | 5.411 |
| 100 000 | re-rank + confidence | — | 4.748 | 5.526 |
A filtered search finds the exact filtered top 10 and is never slower than an
unfiltered one. The filter applies before ranking — over-fetching the index in
proportion to what the filter removes, and scanning the allowed records
exactly whenever that costs fewer distance evaluations than the index would
(roughly `pool × M`). The first version compared the over-fetch to the store
size instead, and that measured badly at 100K: the 1% filter took 5.9 ms at
recall 0.9965 and the away-from-query filter 12.3 ms at 0.976, both through an
index asked for ~16 000 candidates, where scanning the few hundred or thousand
allowed records is exact and cheap. Re-ranking a 3k candidate pool and
confidence rejection add about 3%.
### float16 embedding storage (`MemoryConfig::float16`)
Measured 2026-09-23 on tank (AMD Ryzen 7 7800X3D). The same clustered