mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-08-27 09:32:06 +00:00
* feat(mragent): MRAgent graph memory over RuVector with Darwin optimization
Add ADR-269 and a runnable reference implementation of MRAgent ("Memory is
Reconstructed, Not Retrieved") on RuVector, optimized by Meta-Harness Darwin
Mode under the "freeze the model, evolve the harness" invariant.
- Frozen model: deterministic Cue-Tag-Content memory substrate mirroring
RuVector hybrid (RRF) search + bounded-depth Cypher traversal semantics
(examples/mragent/agent/memory.mjs)
- Evolved harness: 10-gene reconstruction genome (cueK, efSearch, hybridAlpha,
fusion, traversalDepth, tagFanout, pruneThreshold, maxContent, rerank,
promptStrategy) in DARWIN_MUTABLE_BLOCK regions (agent/harness.mjs)
- Darwin evolution loop with mapLimit/paretoFront and ADR-150 graceful fallback
when @metaharness/darwin is absent (optimize.mjs)
- scorePolicy.ts fitness mirroring ADR-266; benchmark + probe + 7 deterministic
acceptance gates
- eval corpus with chained multi-hop "bridge" tasks so traversal depth, fan-out
and pruning are genuinely load-bearing
Runs with zero optional deps: baseline 83.3% -> evolved 100% accuracy, faster
and ~33% smaller context. Darwin discovers traversalDepth=3 (LINKED_TO*1..3).
Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_017MDmEV4svuFxuDBGg8zek2
* feat(mragent): self-reconstructing graph memory, beyond SOTA (ADR-270)
Extend the MRAgent harness past the paper into calibrated, adaptive,
self-reorganizing memory, co-evolved by Darwin. Also fixes the corpus being
silently excluded by the root .gitignore data/ rule (the example was missing
its eval set).
Beyond-SOTA mechanisms (each a tunable gene Darwin evolves):
- Adaptive depth (haltConfidence): halt traversal once evidence is decisive
- Abstention + risk-adjusted utility (abstainThreshold): refuse on weak
evidence instead of hallucinating; graded on calibrated utility, not raw acc
- Consolidation/replay (agent/consolidate.mjs): store reorganizes its own
topology, laying Cue->shortcut->Content edges (RuVector self-learning GNN)
Substrate upgrades:
- Concept layer (agent/concepts.mjs): dense (concept) vs sparse (token) signals
genuinely decoupled, so hybridAlpha/fusion become load-bearing
- Hardened 24-task corpus, 6 classes (semantic/lexical/hybrid/bridge/
distractor/unanswerable) synthesized from structured signal specs
- All 12 genes proven load-bearing (some via epistatic interaction)
- Memetic optimizer: GA (mapLimit/paretoFront) + multi-start coordinate-descent
polish that reliably finds the narrow calibration optimum
Measured (deterministic, zero optional deps): baseline acc 81% / risk 0.708 /
halluc 0.13 -> evolved 100% / risk 1.000 / halluc 0.00; consolidation -25%
hops at 100% accuracy. 11 acceptance gates pass. ADR-150 compliant.
Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_017MDmEV4svuFxuDBGg8zek2
* feat(mragent): generalization protocol (train/test/CV) + overfit fixes
Add a held-out evaluation regime that proves the evolved harness GENERALIZES
rather than memorizing the eval set, and fix the overfitting it surfaced.
Protocol:
- Scale corpus to 60 tasks via a deterministic generator (tools/genCorpus.mjs,
npm run gen-corpus), 10 per class, difficulty-varied (1-hop AND 2-hop bridges,
1-3 ranking-distractors) so train constrains every gene
- Optimizer evolves on a class-stratified TRAIN split, selects via 3-fold
cross-validation with a variance penalty (mean - 0.5*range), and reports a
held-out TEST split it never saw
- Generalization gate = does evolution improve the unseen split
Overfit fixes uncovered by held-out eval:
- Abstention confidence now derives from the answer's RAW relevance, not its
decay^depth path score, so deep-but-relevant bridge answers aren't mistaken
for weak ones (b-test confidence 0.39 -> 0.79); abstention generalizes across
depths. Adaptive-depth halt uses the same raw-relevance signal.
- Larger difficulty-varied corpus + CV variance penalty stop the optimizer
shaving under-constrained genes (maxContent->1) to train-fragile settings
Result (held-out test, reproducible): baseline ~30% acc / risk 0.25 / halluc
0.17 -> evolved ~65% / 0.81 / 0.04 (+35pt acc, +0.56 risk). Honest ceiling
(~80%) documented: synthetic embedding noise + one global hybridAlpha can't
serve both dense- and sparse-keyed queries. 12 acceptance gates pass.
Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_017MDmEV4svuFxuDBGg8zek2
* feat(mragent): GPU LLM write-layer for the Darwin optimizer (local RTX 5080)
Adds the directed-proposal layer the GA lacks (ADR-260 'real Darwin write-layer
proposes leaps from failure traces'): agent/llmMutator.mjs shows a local,
GPU-served code model (qwen2.5-coder via an OpenAI-compatible endpoint) the
current genome + its failing cases and asks for improved genomes. Every proposal
is clamped to the declared gene bounds (coerceGenome) before entering the
population, so untrusted LLM output can only ever be a safe genome — never an
unsafe gene. Wired into optimize.mjs every 3rd generation; folded into the
archive so GPU candidates compete in polish + acceptance.
Fully opt-in + gracefully degrading (ADR-150): MRAGENT_LLM=off or no reachable
endpoint => identical deterministic GA+coordinate-descent run as before. Auto-
detects http://localhost:11434/v1 (ollama) by default; MRAGENT_LLM_URL/MODEL
override.
Measured (RTX 5080, qwen2.5-coder:7b): 8 genomes proposed across gens, bounds-
safe; the deterministic polish still wins on this small synthetic corpus (the
GA+grid already enumerates the optimum), so the write-layer is a no-regression
enhancement that matters on larger corpora the grid can't cover. 14/14 tests
pass (2 new coerceGenome safety tests).
Co-Authored-By: claude-flow <ruv@ruv.net>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: ruvnet <ruvnet@gmail.com>
63 lines
3.4 KiB
JavaScript
63 lines
3.4 KiB
JavaScript
// MRAgent benchmark (v2): baseline vs Darwin-evolved harness over the frozen
|
|
// Cue-Tag-Content corpus, plus the consolidation (replay) pass. Reports the three
|
|
// beyond-SOTA dimensions: helpfulness (accuracy), calibration (risk + halluc), and
|
|
// reconstruction cost (latency/hops/context). Picks up the evolved genome from
|
|
// optimize.report.json if present.
|
|
//
|
|
// Run: npm run benchmark
|
|
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { MemoryStore, baselineGenome, evaluate } from "./agent/harness.mjs";
|
|
import { consolidate } from "./agent/consolidate.mjs";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const corpus = JSON.parse(fs.readFileSync(path.join(__dirname, "data", "eval-set.json"), "utf8"));
|
|
const tasks = corpus.tasks;
|
|
|
|
const baseline = baselineGenome();
|
|
// Evolved genome: from a prior `npm run optimize`, else a calibrated reference.
|
|
let evolved = { ...baseline, fusion: "linear", traversalDepth: 3, abstainThreshold: 0.4, haltConfidence: 0.5, maxContent: 6, tagFanout: 3 };
|
|
const reportPath = path.join(__dirname, "optimize.report.json");
|
|
if (fs.existsSync(reportPath)) {
|
|
try {
|
|
const rep = JSON.parse(fs.readFileSync(reportPath, "utf8"));
|
|
if (rep?.evolved?.genome) evolved = rep.evolved.genome;
|
|
} catch { /* keep reference */ }
|
|
}
|
|
|
|
const base = evaluate(baseline, new MemoryStore(tasks), tasks);
|
|
const evoStore = new MemoryStore(tasks);
|
|
const evo = evaluate(evolved, evoStore, tasks);
|
|
|
|
// Consolidation pass (self-reorganizing memory) on the evolved harness.
|
|
const evoPre = evaluate(evolved, evoStore, tasks);
|
|
const cons = consolidate(evoStore, tasks, evolved);
|
|
const evoPost = evaluate(evolved, evoStore, tasks);
|
|
|
|
console.log("== MRAgent benchmark (v2 — beyond MRAgent) ==");
|
|
console.log(`corpus: ${tasks.length} Cue-Tag-Content tasks (semantic/lexical/hybrid/bridge/distractor/unanswerable)\n`);
|
|
console.log("config accuracy risk halluc latency hops context");
|
|
const row = (name, m) =>
|
|
console.log(`${name.padEnd(17)} ${(m.accuracy * 100).toFixed(1).padStart(5)}% ${m.riskScore.toFixed(3)} ${m.hallucinationRate.toFixed(2)} ${m.avgLatencyMs.toFixed(2).padStart(5)} ${m.avgHops.toFixed(2)} ${m.avgContext.toFixed(1)}`);
|
|
row("baseline", base);
|
|
row("evolved", evo);
|
|
row("evolved+replay", evoPost);
|
|
|
|
const dAcc = (evo.accuracy - base.accuracy) * 100;
|
|
const dRisk = evo.riskScore - base.riskScore;
|
|
const dHops = ((evoPre.avgHops - evoPost.avgHops) / Math.max(evoPre.avgHops, 1e-9)) * 100;
|
|
console.log(`\nevolved vs baseline: accuracy ${dAcc >= 0 ? "+" : ""}${dAcc.toFixed(1)}pt · risk ${dRisk >= 0 ? "+" : ""}${dRisk.toFixed(3)} · hallucination ${base.hallucinationRate.toFixed(2)} → ${evo.hallucinationRate.toFixed(2)}`);
|
|
console.log(`consolidation: ${cons.consolidated} shortcuts → ${dHops.toFixed(1)}% fewer hops at ${(evoPost.accuracy * 100).toFixed(1)}% accuracy`);
|
|
|
|
const report = {
|
|
frozenModel: "RuVector Cue-Tag-Content graph (frozen)",
|
|
corpusSize: tasks.length,
|
|
baseline: { genome: baseline, metrics: base },
|
|
evolved: { genome: evolved, metrics: evo },
|
|
consolidated: { shortcuts: cons.consolidated, metrics: evoPost },
|
|
deltas: { accuracyPoints: dAcc, riskDelta: dRisk, hopsReductionPct: dHops },
|
|
};
|
|
fs.writeFileSync(path.join(__dirname, "benchmark.report.json"), JSON.stringify(report, null, 2));
|
|
console.log(`\nreport -> ${path.join(__dirname, "benchmark.report.json")}`);
|