ruvector/docs/adr/ADR-266-metaharness-darwin-ann-optimization.md
rUv ced9ae8178
Some checks failed
Benchmarks / Rust Benchmarks (push) Waiting to run
Benchmarks / SQL Benchmarks (push) Waiting to run
Benchmarks / Compare with Baseline (push) Blocked by required conditions
Build Native Modules / Build darwin-arm64 (push) Waiting to run
Build Native Modules / Build linux-arm64-gnu (push) Waiting to run
Build Native Modules / Build darwin-x64 (push) Waiting to run
Build Native Modules / Build win32-x64-msvc (push) Waiting to run
Build Native Modules / Build linux-x64-gnu (push) Waiting to run
Build Native Modules / Commit Built Binaries (push) Blocked by required conditions
ruvector-verified CI / check (--features hnsw-proofs) (push) Waiting to run
ruvector-verified CI / check (--features rvf-proofs) (push) Waiting to run
ruvector-verified CI / check (--features serde) (push) Waiting to run
ruvector-verified CI / check (--features ultra) (push) Waiting to run
ruvector-verified CI / test (push) Blocked by required conditions
ruvector-verified CI / bench (push) Blocked by required conditions
ruvector-verified CI / clippy (push) Waiting to run
ruvector-verified CI / check () (push) Waiting to run
ruvector-verified CI / check (--all-features) (push) Waiting to run
ruvector-verified CI / check (--features all-proofs) (push) Waiting to run
ruvector-verified CI / check (--features coherence-proofs) (push) Waiting to run
Workspace CI / Cargo check (push) Waiting to run
Workspace CI / Clippy (push) Waiting to run
Workspace CI / Rustfmt (push) Waiting to run
Workspace CI / Tests (core-and-rest) (push) Waiting to run
Workspace CI / Tests (core-and-rest-heavy) (push) Waiting to run
Workspace CI / Tests (core-and-rest-wasm) (push) Waiting to run
Workspace CI / Tests (ml-research-heavy) (push) Waiting to run
Workspace CI / Tests (ml-research-rest) (push) Waiting to run
Workspace CI / Tests (research-nightly) (push) Waiting to run
Workspace CI / Tests (ruvix) (push) Waiting to run
Workspace CI / Tests (rvagent) (push) Waiting to run
Workspace CI / Tests (vector-index) (push) Waiting to run
Workspace CI / Security audit (push) Waiting to run
Clippy + fmt / Clippy (deny warnings) (push) Waiting to run
Clippy + fmt / Rustfmt (push) Waiting to run
regression-guard / reentrant-rwlock-double-write (push) Waiting to run
regression-guard / case-insensitive-collisions (push) Waiting to run
regression-guard / ruvector-core-no-avx512-builds-on-stable (push) Waiting to run
regression-guard / hnsw-recall-at-1 (push) Waiting to run
regression-guard / hnsw-insert-beam-no-m2-clamp (push) Waiting to run
regression-guard / hnsw-distance-based-neighbor-pruning (push) Waiting to run
regression-guard / vector-db-rebuilds-index-on-open (push) Waiting to run
regression-guard / npm-publish-pipeline (npm/packages/pi-brain) (push) Waiting to run
regression-guard / npm-publish-pipeline (npm/packages/ruvector) (push) Waiting to run
regression-guard / npm-publish-pipeline (npm/packages/rvf-wasm) (push) Waiting to run
regression-guard / no-npx-execSync-in-route-enhanced (push) Waiting to run
regression-guard / shell-injection-in-mcp-server (push) Waiting to run
regression-guard / no-systemtime-in-wasm-crates (push) Waiting to run
regression-guard / no-hardcoded-workspaces-paths (push) Waiting to run
regression-guard / brain-hydration-counters-present (push) Waiting to run
regression-guard / optional-deps-resolvable-on-npm (push) Waiting to run
regression-guard / graph-condense-perception-tests (push) Waiting to run
regression-guard / mincut-pin-tracks-workspace-version (push) Waiting to run
SOTA Benchmark (Tier 1 Smoke) / SOTA Smoke (Tier 1) (push) Waiting to run
SOTA Benchmark (Tier 1 Smoke) / SOTA Full Run (Tier 2, on demand) (push) Waiting to run
supply-chain / dependency-review (PRs only) (push) Waiting to run
supply-chain / cargo audit (RustSec advisories) (push) Waiting to run
supply-chain / cargo deny (license + source + ban policy) (push) Waiting to run
supply-chain / npm audit (npm/ workspace) (push) Waiting to run
supply-chain / lockfile integrity (Cargo.lock) (push) Waiting to run
WASM Dedup Check / check-wasm-dedup (push) Waiting to run
Build RVF Node Native Modules / Build darwin-arm64 (push) Has been cancelled
Build RVF Node Native Modules / Build darwin-x64 (push) Has been cancelled
Build RVF Node Native Modules / Build linux-arm64-gnu (push) Has been cancelled
Build RVF Node Native Modules / Build linux-x64-gnu (push) Has been cancelled
Build RVF Node Native Modules / Build win32-x64-msvc (push) Has been cancelled
Build RVF Node Native Modules / Commit RVF Node Binaries (push) Has been cancelled
feat(benchmark): SOTA benchmark suite — 5 runners, 11 SOTA claims, Darwin/MetaHarness integration (ADR-265/266/267) (#596)
* feat(benchmark): SOTA benchmark suite + ADR-151/265/266/267 + MetaHarness harness

ruvector-sota-bench (ADR-265):
- Darwin score: 0.4*recall@10 + 0.3*log(QPS) + 0.2*memory + 0.1*latency
- Runners: core-hnsw with full recall@1/10/100, latency p50/p95/p99, QPS
- Datasets: 5 synthetic ANN-Benchmarks-compatible (glove-25/100, sift-128,
  gist-960, deep-image-96) + CI smoke set
- SOTA threshold: recall@10 >= 0.95 AND QPS >= 80% of HNSWlib baseline
- 6 bin targets: sota-all, sota-ann, sota-recall-sweep, sota-compression,
  sota-streaming, sota-hybrid
- Report: leaderboard table, JSON export, SOTA claim detection

ADR series:
- ADR-151: Transition searchreplace → Stateful PTY Agent Loop (SWE-bench)
  Target: break 58.3% ceiling → 60%+; 4 tools: execute_bash/read_file/
  edit_file/finish_task; max 50 turns; scratchpad trajectory memory
- ADR-265: RuVector Comprehensive Benchmark Suite (scope + scoring)
- ADR-266: MetaHarness Darwin integration for autonomous ANN optimization;
  32 mutation surfaces; ADR-150 removable-augmentation constraint respected
- ADR-267: SOTA Validation Protocol; 3-tier (smoke/weekly/biannual);
  witness-signed manifests (Ed25519, ADR-103)

Research insights (deep-researcher agent):
- RaBitQ achieves 99.3% recall@10 vs IVF-PQ 79.2% — 20pp gap
- Hybrid BM25+RRF fusion: 80.8% vs 13.9% dense-only on MS MARCO
- Matryoshka: 14x speed-up at matched recall (MRL 2024 paper)
- No Rust system on BigANN leaderboard — first submission opportunity
- BGE-M3 upgrade: +15-17 nDCG@10 over all-MiniLM (46 → 62-63)

Priority order: ANN-Benchmarks → VectorDBBench → BigANN Streaming →
MTEB/BEIR → Filtered → Adaptive/SONA

Co-Authored-By: claude-flow <ruv@ruv.net>

* feat(sota-bench): add matryoshka runner; fix feature deps; smoke test passes 2 SOTA claims

- ruvector-matryoshka runner: FullDimIndex + TwoStageIndex variants
  both backed by the same Searcher trait; uses build() API correctly
- Fixed Cargo.toml: matryoshka promoted from optional to required dep
  (always compiled alongside core-hnsw runner)
- Smoke test results: core-hnsw(m=32,ef=50) on smoke-128 and smoke-96
  both achieve SOTA (recall@10 ≥ 0.95, QPS ≥ 400)
- Known issue: recall degrades at ef=100+ — likely ruvector-core
  ef_search param not propagating; logged for follow-up

Next: HDF5 dataset loader for real SIFT1M/GloVe data

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix+feat(sota-bench): ef_search fix; hybrid runner; HDF5 loader

Fix (critical):
- core-hnsw runner now uses HnswIndex directly with search_with_ef()
  bypassing VectorDB which silently ignores SearchQuery::ef_search.
  Result: recall correctly scales with ef (0.958→0.989 on smoke-128)
  vs previous stuck-at-0.51 — 8/8 SOTA claims on smoke datasets.

Feat: ruvector-hybrid runner (hybrid.rs)
- BM25 + ANN fusion via RRF, RSF, and score-fusion strategies
- Synthetic token generation from vector values for structural benchmarking
- All three variants built once, queried in parallel for fair comparison

Feat: HDF5 dataset loader (datasets/ann_benchmarks.rs)
- Lazy download of official ANN-Benchmarks HDF5 files to ~/.cache/
- Configurable max_corpus and max_queries caps
- Gated behind 'real-datasets' feature (zero cost without it)
- Supports SIFT-128, GloVe-25/100, Deep-image-96 out of the box
- clear error message when feature is absent

Co-Authored-By: claude-flow <ruv@ruv.net>

* feat(sota-bench): LSM-ANN runner; streaming benchmark; Darwin scorePolicy; sota_all wired

4 runners now producing measurements:
  - core-hnsw: 8/8 SOTA claims (recall 0.96-1.00, QPS 1200-5500)
  - lsm-ann: recall 0.856-0.930, QPS 5764-7706, insert 1.8K-6.1K/s
    → faster QPS than HNSW at matched recall; strong streaming story
  - matryoshka: wired (low recall on synthetic — needs tuning)
  - hybrid-rrf/rsf/score-fusion: wired (baseline recall on synthetic)

New files:
  runners/lsm_ann.rs   — FullLsm runner + streaming checkpoint tracker
  bin/sota_streaming.rs — BigANN streaming track benchmark
  harness/scorePolicy.ts — Darwin Mode scorer: runs sota-all --smoke,
    reads JSON report, returns darwin_score in [0,1] for evolution

Updated:
  bin/sota_all.rs — all 4 runner families wired; matryoshka uses
    highest ef_search for better recall; Darwin score ranking printed
  Cargo.toml — ruvector-lsm-ann promoted to non-optional dep

Outstanding:
  - hybrid recall low (0.25-0.41): synthetic tokens don't match well;
    will improve with real BEIR/MSMARCO text-keyed data
  - matryoshka recall low: needs higher candidate count tuning
  - HDF5 loader ready; needs --features real-datasets to activate

Co-Authored-By: claude-flow <ruv@ruv.net>

* feat(sota-bench): RaBitQ runner; full 5-runner smoke verified (11 SOTA claims)

RaBitQ runner (runners/rabitq.rs):
  - FlatF32Index (exact baseline):  recall@10=1.0000, QPS=2588-6381  ★SOTA
  - RabitqPlusIndex (1-bit + rerank): recall@10=0.929-0.966, QPS=5285-6776  ★SOTA
  - RabitqIndex (pure 1-bit): QPS=26500 (recall low on synthetic — normal;
    paper reports 99.3% on SIFT1M which uses structured cluster data)

11/26 config×dataset combinations claim SOTA across smoke datasets.
Darwin score ranking shows rabitq-flat-f32 at darwin=0.997 as top candidate
for evolution pressure (correct: exact search is the evolution target).

sota_all.rs now runs all 5 families:
  core-hnsw (4 ef values) | rabitq (3 variants) | lsm-ann | matryoshka | hybrid

Next: HDF5 real-data run (needs --features real-datasets), then open PR.

Co-Authored-By: claude-flow <ruv@ruv.net>

* feat(sota-bench): streaming beats NeurIPS target (0.908 > 0.887); fmt; README

BigANN Streaming Track:
  Checkpoint-local ground truth fix (measure recall against indexed
  subset, not full future corpus — matches BigANN streaming semantics).
  Result: averaged recall = 0.908 > NeurIPS'23 target of 0.887 ★

  smoke-128: fill@25%=0.956, @50%=0.868, @100%=0.776; post-compact=0.857
  smoke-96:  fill@25%=0.990, @50%=0.974, @100%=0.884; post-compact=0.934

Other improvements:
  - cargo fmt on all 13 source files
  - README.md: full benchmark table, result explanations, notes on
    rabitq-1bit/matryoshka/hybrid synthetic vs real-data behavior
  - Fixed unused import warning in hybrid runner

Benchmark summary:
  11/26 SOTA claims on smoke datasets
  rabitq-plus: 0.929-0.966 recall@10, 5K-7K QPS
  lsm-ann: 2.8K-7.6K insert/s, 0.856-0.934 post-compact recall

Co-Authored-By: claude-flow <ruv@ruv.net>

* feat(ci): SOTA Tier-1 smoke benchmark workflow (ADR-267)

Adds .github/workflows/sota-benchmark.yml:
  - Tier 1 (smoke): triggers on any change to sota-bench or index crates
    Runs sota-all --smoke, verifies ≥5 SOTA claims, uploads JSON report
    Timeout: 20 min; uses synthetic data, no downloads required
  - Tier 2 (full, on-demand): workflow_dispatch with full_run=true
    Runs synthetic ANN-Benchmarks scale (~30+ min), uploads full report

Also files #597 to track matryoshka recall bug (0.39 vs expected 0.90+
for FullDimIndex on 10K/128-dim synthetic data — likely HnswGraph bug).

Co-Authored-By: claude-flow <ruv@ruv.net>

---------

Co-authored-by: ruvnet <ruvnet@gmail.com>
2026-06-21 22:53:56 -04:00

15 KiB
Raw Blame History

ADR-266: MetaHarness Integration for Autonomous ANN Optimization (Darwin Mode)

Status

Accepted

Date

2026-06-21

Authors

Claude Code MetaHarness Architect

Supersedes

None

  • ADR-150 — MetaHarness Integration Surfaces (the optional-dependency invariant this ADR obeys)
  • ADR-260 — Darwin Mode as Evolutionary Substrate for MetaHarness (defines the evolve → score → archive loop and the RuvvectorArchive pattern this ADR mutates)
  • ADR-265 — Benchmark Suite (supplies the score() function components consumed here)
  • ADR-267 — SOTA Validation (consumes the evolved configs this ADR produces)

Context

RuVector ships a large surface of ANN tuning knobs — HNSW graph degree (M), construction effort (efConstruction), product-quantization bitwidth, RaBitQ compression strategy, the MLA/SSM hybrid layer ratio, ColBERT token-clustering K, KV-cache eviction policy, and DiskANN robust-pruning alpha. Today these are hand-tuned per workload. The interactions between them are non-linear and workload-dependent: a config that maximizes recall@10 on a 1M-vector OpenAI embedding set can collapse QPS on a 100M-vector SIFT set. Manual sweeps do not scale across that surface, and the local optima they find are fragile.

We want autonomous parameter optimization: an evolution layer that mutates index hyperparameters, scores each candidate against a fixed multi-objective function (recall@10, QPS, memory, p99 latency), and checkpoints the best config per workload — with zero human in the loop after the baseline is captured.

MetaHarness's Darwin Mode (@metaharness/darwin) already implements the evolution algorithm we need (a genetic + simulated-annealing hybrid; see ADR-260). The remaining work is not to reimplement evolution — it is to define a clean integration surface: what Darwin is allowed to mutate, and how a candidate is scored.

Constraints inherited from ADR-150

Important

ADR-150 invariant — MetaHarness is OPTIONAL. @metaharness/darwin MUST appear only under optionalDependencies, never under dependencies. RuVector's core index path MUST build, test, and run with the package absent. Darwin Mode is an augmentation layer, never a required runtime dependency. A MODULE_NOT_FOUND for @metaharness/darwin is a gracefully-degraded no-op, not an error.

This is the same pattern ADR-260 established for RuvvectorArchive (try { require('@ruvector/ruvector') } catch { /* fall back */ }, ADR-260 lines 142143). Darwin integration follows it exactly.

Baseline use cases

  1. Per-workload tuning — evolve a config for a specific corpus + query distribution, checkpoint it, ship it as that workload's default.
  2. Regression guard — when ADR-265's benchmark suite detects a recall/QPS regression after a kernel change, re-evolve to recover the lost ground.
  3. SOTA push (ADR-267) — evolve aggressive configs that trade memory or build time for recall to beat published baselines on standard datasets.

Decision

Integrate MetaHarness Darwin Mode as an optional evolution layer over RuVector's index configuration. The integration defines two surfaces and nothing else:

  1. A mutation surface — the set of index hyperparameters Darwin may mutate, each with a type, a legal range, and semantics (table below).
  2. A scoring function — a composition over the ADR-265 score components (scorePolicy.ts), producing a single scalar per candidate.

Note

This ADR documents the INTEGRATION SURFACE only. @metaharness/darwin owns the evolution algorithm (genetic + simulated annealing). RuVector owns (a) the genome schema — what gets mutated — and (b) the scoring composition. We do not implement mutation operators, selection, crossover, or annealing here.

The evolution loop is run out-of-band (CLI / CI), never on the hot query path. Evolved configs are persisted as plain JSON and loaded by the index like any hand-written config — so a workload tuned by Darwin has no runtime dependency on Darwin (re-affirming the ADR-150 invariant: the package can be uninstalled after evolution and the checkpointed config still loads).


Mutation Surfaces

What Darwin may mutate. Each surface maps to one tunable field in the index config genome. Ranges are inclusive; mutation operators clamp to range.

Surface Module Type Range Semantics
hnsw_M HNSW int [4, 32] max out-degree per node (graph connectivity)
hnsw_efConstruction HNSW int [50, 400] candidate-list size during build (construction cost vs graph quality)
pq_bits PQ-Search int [4, 8] quantization bitwidth per subvector
quant_strategy RaBitQ enum [uniform, asymmetric, logarithmic] scalar-compression scheme
layer_ratio MLA/SSM hybrid float [0.2, 0.8] fraction of attention vs SSM in the hybrid stack
colbert_k Multi-Vector int [4, 16] token-clustering K for late-interaction retrieval
cache_eviction KV-Cache enum [H2O, PyramidKV, SlidingWindow] eviction policy under cache pressure
diskann_alpha DiskANN float [1.0, 1.5] robust-pruning strength (graph diversity vs density)

Warning

The mutation surface is a closed allowlist. Darwin MUST NOT mutate any field outside this table. Fields that affect correctness rather than the recall/speed/memory tradeoff (distance metric, vector dimension, ID space) are deliberately excluded — mutating them would change what is being searched, not how well. The genome schema is the enforcement point: any field not declared mutable is frozen.

Genome schema (the enforcement point)

The genome is a flat JSON object with exactly the 8 keys above. The integration exposes it via a single declaration; Darwin reads this to know its search space.

{
  "genome": {
    "hnsw_M":              { "type": "int",   "min": 4,   "max": 32 },
    "hnsw_efConstruction": { "type": "int",   "min": 50,  "max": 400 },
    "pq_bits":             { "type": "int",   "min": 4,   "max": 8 },
    "quant_strategy":      { "type": "enum",  "values": ["uniform", "asymmetric", "logarithmic"] },
    "layer_ratio":         { "type": "float", "min": 0.2, "max": 0.8 },
    "colbert_k":           { "type": "int",   "min": 4,   "max": 16 },
    "cache_eviction":      { "type": "enum",  "values": ["H2O", "PyramidKV", "SlidingWindow"] },
    "diskann_alpha":       { "type": "float", "min": 1.0, "max": 1.5 }
  }
}

A config field absent from genome is invisible to Darwin and therefore immutable by construction — no runtime check needed.


Scoring Function

A candidate config is scored by composing the four ADR-265 benchmark components into a single scalar. The composition is declared in scorePolicy.ts:

{
  "components": {
    "recall_weight":  0.4,
    "qps_weight":     0.3,
    "memory_weight":  0.2,
    "latency_weight": 0.1
  },
  "formula": "0.4*recall@10 + 0.3*log(QPS/baseline_QPS) + 0.2*(1-mem/baseline_mem) + 0.1*(1-p99_ms/baseline_p99_ms)"
}

Notes on the composition:

  • recall@10 is the dominant term (0.4) — a fast index that returns wrong neighbours is worthless. It enters linearly in [0, 1].
  • QPS enters as log(QPS/baseline_QPS) so a 2× speedup and a 4× speedup are not rewarded linearly — diminishing returns past the baseline, and the log is symmetric around regressions (QPS < baseline → negative term).
  • memory and p99_latency are relief terms: 1 - ratio, positive when the candidate uses less memory / lower tail latency than baseline, negative when worse.
  • All four baseline_* values come from ADR-265's recorded baseline run for the same dataset, so scores are comparable only within a workload.

Important

ADR-265 owns the measurements; this ADR owns the weights. The recall@10, QPS, mem, and p99_ms numbers are produced by ADR-265's benchmark harness. scorePolicy.ts only combines them. If ADR-265 changes how a metric is measured, the weights do not change — but every prior score must be recomputed before comparison.


Evolution Loop

A single generation:

1. seed     load baseline config (ADR-265 recorded run) as generation-0 genome
2. mutate   Darwin produces N child genomes by mutating surfaces (genetic +
            simulated annealing — @metaharness/darwin internal)
3. score    for each child: build index → run ADR-265 benchmark → scorePolicy.ts
4. rank     sort children by scalar score, descending
5. checkpoint  persist the top genome to configs/evolved/<workload>.json
6. (repeat over G generations; each generation seeds from the prior best)

The loop is deliberately single-objective after composition — the four metrics collapse to one scalar at step 3, so ranking is total and the checkpoint is unambiguous. Multi-objective Pareto fronts are out of scope (a future ADR could add them by changing only scorePolicy.ts).

CLI surface (additive, gated on the package being present):

ruvector evolve <dataset> \
  --baseline configs/baseline/<workload>.json \
  --generations 5 --children 8 \
  --score-policy configs/scorePolicy.json \
  --out configs/evolved/<workload>.json

If @metaharness/darwin is not installed, ruvector evolve prints a one-line "MetaHarness not installed — evolution unavailable" notice and exits 0 (it is an optional capability, not a failed command).


ADR-150 Compliance

How the optional invariant is enforced, line by line:

Concern Enforcement
Package classification @metaharness/darwin listed under optionalDependencies in the CLI package.json, never dependencies.
Missing package The evolve command resolves the module via try { require('@metaharness/darwin') } catch { return gracefulNoop() } — the same guard ADR-260 uses for RuvvectorArchive (ADR-260 §Component 2, lines 142143).
Hot path isolation Evolution runs only under the evolve subcommand (CLI/CI). No import '@metaharness/darwin' appears in the index/query modules. The query path cannot trigger a MODULE_NOT_FOUND.
Post-evolution independence Evolved configs are plain JSON loaded by the standard config loader. After evolution, @metaharness/darwin can be uninstalled and every checkpointed config still loads — Darwin leaves no runtime artifact.
Frozen-field safety The genome schema is the allowlist; fields absent from it are immutable by construction, so a buggy or adversarial mutator cannot reach correctness-affecting config.
// CLI evolve subcommand — ADR-150 graceful-degradation guard.
let Darwin: typeof import('@metaharness/darwin') | undefined;
try {
  Darwin = require('@metaharness/darwin');     // optionalDependency
} catch {
  console.log('MetaHarness not installed — evolution unavailable. ' +
              'Install with: npm i -O @metaharness/darwin');
  process.exit(0);                             // not an error — optional capability
}

Why MetaHarness stays optional

RuVector is a vector index first. The overwhelming majority of consumers embed the index and never evolve hyperparameters — they ship a hand-tuned or Darwin-evolved-then-frozen config. Forcing every consumer to pull a genetic optimizer (and its transitive deps) onto the install graph would be wrong. Evolution is a development-time / CI-time activity that produces a static artifact (the JSON config). The invariant keeps the runtime lean and the dependency surface honest.


Success Criteria

Darwin Mode integration is considered successful when:

  • Primary: an evolved config beats the ADR-265 baseline on at least 2 of the 4 metrics (recall@10, QPS, memory, p99) on a standard dataset, with the composed score strictly higher than baseline.
  • The full RuVector test suite passes with @metaharness/darwin uninstalled (proves the ADR-150 invariant).
  • ruvector evolve exits 0 with a graceful notice when the package is absent.
  • A checkpointed evolved config loads and serves queries after the package is uninstalled (proves post-evolution independence).
  • Zero index/query-path module imports reference @metaharness/darwin (greppable check in CI).

Consequences

Positive

  • Autonomous, reproducible per-workload tuning replaces manual sweeps.
  • The closed mutation-surface allowlist makes the search space auditable and keeps correctness-affecting fields frozen.
  • Evolved configs are static JSON — no runtime coupling to the optimizer.
  • Composes cleanly with ADR-260 (Darwin is already wired for ruvector) and reuses ADR-265's measurement harness verbatim.

Negative

  • Single-scalar scoring hides Pareto tradeoffs; a config that is best-overall may be dominated on a metric a specific consumer cares about most.
  • Scores are only comparable within a workload (baselines differ), so there is no single "best config" across datasets.
  • Evolution cost is real (build + benchmark per child × children × generations); this is a CI/offline cost, acceptable because it is off the hot path.

Neutral

  • The weights in scorePolicy.ts are a policy choice, not a measured fact — changing them re-ranks history and requires recomputation.
  • Adding a new tunable later means one row in the mutation-surface table plus one genome key; the loop and scoring are unaffected.

Options Considered

Option 1: Reimplement evolution inside RuVector

  • Pros: no external dependency at all; full control.
  • Cons: reinvents the genetic + simulated-annealing hybrid @metaharness/darwin already ships and ADR-260 already wired; large maintenance surface for a development-time tool.

Option 2: MetaHarness Darwin as an optional integration surface (chosen)

  • Pros: reuses the upstream evolution algorithm; obeys ADR-150; static-config output keeps the runtime lean; small, auditable surface (genome + score).
  • Cons: depends on an external package's API stability for the evolve workflow (mitigated by the graceful no-op when absent).

Option 3: Manual grid/random search in CI

  • Pros: zero dependencies; trivial to reason about.
  • Cons: does not scale across the 8-dimension surface; finds fragile local optima; no behavioural-diversity selection (ADR-260 §3 showed greedy search fails on deceptive landscapes 0/5 vs diversity 5/5).

References

  • darwin-mode ADR-074 — ruvvector archive design (upstream)
  • ADR-260 §Component 2 — RuvvectorArchive graceful-degradation pattern (the canonical optional-dependency guard)