diff --git a/docs/adr/ADR-305-mincut-partitioned-memory-consolidation.md b/docs/adr/ADR-305-mincut-partitioned-memory-consolidation.md new file mode 100644 index 000000000..eee0859ba --- /dev/null +++ b/docs/adr/ADR-305-mincut-partitioned-memory-consolidation.md @@ -0,0 +1,256 @@ +# ADR-305: Mincut-Partitioned Agent-Memory Consolidation + +## Status + +**Proposed, hypothesis REJECTED by measurement.** Experimental crate +(`ruvector-partition-memory`), not wired into any production compaction +path. Retained for its evidence, its two documented `ruvector-mincut` +defects, and its from-scratch correct min-cut implementation +(`mincut_exact.rs`), which is a candidate for reuse in future partitioning +work regardless of this ADR's own outcome. + +## Context + +Nightly 2026-06-14 (`crates/ruvector-agent-memory`) introduced +`CoherencePolicy`: a global top-score compaction rule scoring every stored +memory by `α·recency + β·frequency + γ·coherence(context)` and keeping the +top `target_size`. It measured 100% recall after 50% compaction on its +test corpus and remains the best-performing policy in the ecosystem. + +`CoherencePolicy` is, by construction, a single global ranking scored +against one context window (in production, the agent's most recent working +context). That is also its structural risk: a memory topic unrelated to +the current context competes on the same scale as everything else, so a +minority topic can be evicted **in full** during a single consolidation +event, at a compaction ratio the aggregate recall number reports as +favorable. Nightly 2026-06-14 did not measure this — it reports mean +recall and LRU/LFU comparisons, not worst-topic behavior. + +`ruvector-mincut` provides a subpolynomial dynamic minimum-cut engine +(Jin–Sun–Thorup) and graph-partitioning utilities +(`GraphPartitioner`, `RuVectorGraphAnalyzer`) that had not previously been +applied to agent memory. This ADR's premise: partitioning the memory +similarity graph before applying a retention budget, with a +per-partition floor, should protect a topic from being evicted in full +even when it loses on the global score — because that topic's competition +for its floor allocation is only the rest of its own partition, not the +whole corpus. + +## Hypothesis + +```text +Given a 4,000-memory corpus with 6 semantic clusters of unequal size +(1400/1000/720/600/200/80 — the two smallest are 5% and 2% of the corpus), +scored against a recency-biased context drawn from the largest cluster +(the realistic "what the agent was just working on" scenario), + +when a partition-aware retention policy (floor + proportional budget per +graph partition) is used instead of CoherencePolicy's global top-score +ranking, at 50% compaction, + +then the best candidate's worst-cluster recall@10 should exceed the +baseline's by >= 15 percentage points, + +subject to: no candidate's overall recall@10 regressing more than 5pp +below baseline; partition+retention wall time staying under 30s per +candidate at this n; and the partition witness chain verifying. +``` + +Declared before the accepted run (see the research doc's Pass 2/3 and +calibration section); not modified afterward. + +## Decision + +Implement two partitioning strategies and compare both against the +`CoherencePolicy` baseline, using **the same scorer** in every retention +step so the only independent variable is budget allocation, not scoring: + +- **Candidate A — `MincutFixedK`**: wraps the existing + `ruvector_mincut::GraphPartitioner` (unweighted, edge-count recursive + bisection, fixed `K`). +- **Candidate B — `MincutAdaptive`**: a new adaptive-depth recursive + bisection that stops splitting a component once its cut is dense + relative to its internal edge weight (no caller-chosen `K`). +- **Retention**: floor + largest-remainder proportional budget per + partition (`retention.rs`), each partition ranked internally by + `ruvector_agent_memory::CoherencePolicy` — reused as a library + dependency, not re-implemented. + +## Evidence + +### A defect discovered before the hypothesis could be tested + +`DynamicMinCut::partition()` (and the `GraphPartitioner` / +`RuVectorGraphAnalyzer` path built on it) was found, during this +candidate's own development, to return vertex splits **inconsistent with +its own `min_cut_value()`**, and nondeterministically so: + +- 6-vertex repro (two triangles joined by one weak `0.05`-weight bridge; + true min cut is uniquely `{0,1,2}` vs `{3,4,5}` at value `0.05`): of + three runs, two returned the correct split, one returned a degenerate + `{single vertex}` vs `{rest}` split — while `min_cut_value()` reported + `0.05` correctly on **every** run. +- 100-vertex version (two 50-cliques, one `0.01`-weight bridge): every run + returned the degenerate split; `min_cut_value()` still correctly + reported `0.01`. +- `GraphPartitioner` was separately found to (a) drop vertices outright at + n=100 (returned partitions covering only 50 of 100 vertices) and (b) + fabricate vertex ids that were never in the input graph at all, when the + id space is non-contiguous. +- `GraphPartitioner` was also measured to be severely slow: **8.4s at + n=500**, and **did not finish in 5m42s at n=4000** (killed). + +Full repro commands are in the research doc. This crate works around the +correctness defects with a from-scratch, tested Stoer–Wagner +implementation (`mincut_exact.rs`) used as the sole source of partition +vertex sets; `ruvector_mincut`'s `min_cut_value()` is still queried as an +independent cross-check (its *value* output, as opposed to its +*partition*, was never observed wrong). It works around the performance +defect by scale-gating candidate A (`fixed_k_max_n`, default 600) rather +than hanging the benchmark or silently omitting the comparison. + +### The accepted hypothesis run (n=4000, `coherence_ratio=0.35`, `floor_min=3`) + +```text +variant overall_recall worst_cluster_recall coverage +GlobalTopScore 0.4193 0.1520 1.000 +MincutAdaptive 0.4873 0.1520 1.000 + +per_cluster_recall GlobalTopScore = [0.996, 0.152, 0.216, 0.316, 0.396, 0.440] +per_cluster_recall MincutAdaptive = [0.792, 0.380, 0.504, 0.152, 0.556, 0.540] + +worst_cluster_gain_pp = -0.00 (threshold: +15.00) +ACCEPTANCE_RESULT: REJECT +``` + +Overall recall improved (+6.8pp) and 4 of 6 clusters gained materially +(+15 to +23pp each), but the specific cluster that was *worst* under the +baseline (cluster 3, 600 members / 15% of the corpus) is **also** worst +under `MincutAdaptive`, at the identical value — because the partitioner +left cluster 3 merged with the 1400-member majority cluster (the 2000-size +partition in `sizes=[200, 2000, 1000, 720, 80]`), so its retention budget +was decided by the same global-style competition the hypothesis set out +to avoid. The `coherence_ratio=0.35` stopping rule, calibrated before this +run against the corpus's true global min cut (see the research doc), does +correctly find and isolate the genuinely weak seams — but cluster 0/3's +separation was not one of them at this threshold. + +At n=500, both candidates were run (`fixed_k_max_n=600` admits n=500): +`MincutFixedK` reached `worst_cluster_gain_pp=8.00`, `MincutAdaptive` +reached a *worse* worst-cluster recall than baseline (`0.0` vs `0.10`, +because the true 10-member minority cluster is below `min_cluster_size` +(20) and can never be isolated on its own). Both REJECT. + +A bounded, pre-declared-fitness sweep of `floor_min` over `{1,3,8,15}` at +n=4000, holding the same partition fixed, left `worst_cluster_recall` +essentially flat (`0.152`/`0.148` across all four values) — confirming +the bottleneck is the **partition step**, not the **retention-budget +step**: no floor value can protect a cluster the partitioner never +separated from the majority in the first place. + +## Consequences + +- **Do not promote** `MincutAdaptive`/`MincutFixedK` retention to + production. The pre-registered hypothesis (worst-cluster recall + protection) is rejected by direct measurement. +- `mincut_exact.rs`'s correct, tested Stoer–Wagner implementation is a + reusable asset independent of this ADR's outcome — any future graph-cut + work in this ecosystem needing a trustworthy partition should use it, or + a fixed `ruvector-mincut`, in preference to `DynamicMinCut::partition()` + as it stands today. +- The `ruvector-mincut` defects (partition/value inconsistency, + nondeterminism, vertex loss/fabrication, severe `GraphPartitioner` + latency) should be filed and fixed upstream in that crate; they affect + every existing consumer of `DynamicMinCut::partition()` / + `GraphPartitioner`, not just this experiment. +- A follow-up hypothesis worth testing (not implemented here): a + **per-branch, not global**, stopping criterion — e.g. always attempt at + least one more level of recursion on the largest remaining partition + before accepting `coherence_ratio`'s verdict, or size-weight the + threshold — might separate cluster 0/3 where the flat threshold did + not. This is a new hypothesis, not a retroactive change to the one + tested above. + +## Alternatives + +- **Ship `CoherencePolicy` unchanged.** Current state; the measured + overall-recall improvement here (+6.8pp) does not offset a rejected + primary hypothesis and a partitioner with two unresolved upstream + correctness defects and a severe latency defect. +- **Global top-score with a per-cluster-label floor** (using a cheap + clustering method like k-means on embeddings instead of graph min-cut) + was considered but not implemented; it would sidestep `ruvector-mincut` + entirely and is a reasonable next candidate. + +## Implementation plan + +Not applicable — hypothesis rejected; no production migration. + +## API shape + +`ruvector-partition-memory` (experimental, workspace member, not +re-exported by any production crate): `corpus`, `graph`, `mincut_exact`, +`partition`, `retention`, `metrics`, `witness`, `search` modules; see +`src/lib.rs` for the full surface. + +## Feature flags + +None; the crate is not on any production feature-gated path. + +## Benchmark evidence + +`docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/evidence/` +— raw, unedited command output: `bench_n4000.txt`, `bench_n500_with_fixedk.txt`, +`darwin_sweep.txt`, `calibration.txt`. + +## Security + +No new attack surface: the crate is a standalone research binary/library +operating on synthetic data, not wired into any request path. The witness +chain (`witness.rs`) is a correctness/audit mechanism, not an access +control mechanism, and makes no such claim. + +## Governance + +None of this crate's code should be treated as validated production +guidance for `ruvector-mincut` usage beyond the specific defects +documented above; those defects should be independently verified by +whoever owns that crate before any fix lands. + +## Failure modes + +- `DynamicMinCut::partition()` / `GraphPartitioner` defects: see Evidence. +- `AdaptiveConfig::min_cluster_size` (default 20) structurally prevents + isolating any true topic smaller than that absolute count — observed + directly at n=500 (10-member cluster, worst_cluster_recall=0.0). +- A coarse, single-threshold stopping rule can leave two clusters merged + even when one is a minority worth protecting, if their graph-structural + separation is weaker than the threshold demands elsewhere in the same + corpus (observed at n=4000, clusters 0/3). + +## Migration + +None. + +## Rollback + +None — nothing shipped to a production path. + +## Rejection criteria + +Met: worst-cluster recall gain (0.00pp, both n=4000 and n=500) fell short +of the pre-declared 15pp threshold in every configuration tested, +including a bounded post-hoc sweep of the one parameter (`floor_min`) +that could plausibly have rescued it without changing the hypothesis +itself. + +## Open questions + +- Would a per-branch/size-weighted stopping criterion (see Consequences) + cross the threshold? Untested — a genuinely new hypothesis for a future + nightly, not this one. +- Do the two `ruvector-mincut` defects reproduce on that crate's own + existing test suite, or does no existing test exercise + `DynamicMinCut::partition()` / `GraphPartitioner::partition()`'s output + against ground truth? Not investigated here; worth checking before + filing upstream. diff --git a/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/README.md b/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/README.md new file mode 100644 index 000000000..b204f00cd --- /dev/null +++ b/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/README.md @@ -0,0 +1,585 @@ +# Mincut-Partitioned Agent-Memory Consolidation + +**Date**: 2026-08-17 +**Crate**: `ruvector-partition-memory` (`crates/ruvector-partition-memory`) +**Status**: PoC complete — **hypothesis REJECTED by measurement**, plus two documented defects discovered in `ruvector-mincut` +**ADR**: [ADR-305](../../../adr/ADR-305-mincut-partitioned-memory-consolidation.md) + +--- + +## Summary of Outcome + +The hypothesis — that partitioning the agent-memory similarity graph +before applying a retention budget protects a minority topic from being +evicted in full by a global top-score compactor — is **rejected** on the +pre-declared metric (worst-cluster recall@10 gain ≥ 15pp) at every scale +tested: + +| Run | Best candidate | Worst-cluster gain | Threshold | Verdict | +|---|---|---|---|---| +| n=4000, coherence_ratio=0.35 | MincutAdaptive | **0.00pp** | 15pp | REJECT | +| n=500, coherence_ratio=0.35 | MincutFixedK | 8.00pp | 15pp | REJECT | +| n=4000, floor_min sweep {1,3,8,15} | (all) | 0.00pp (flat) | 15pp | REJECT | + +The mechanism is not worthless — 4 of 6 clusters gained 15–23pp recall +each and overall recall improved +6.8pp at n=4000 — but the specific +cluster the hypothesis exists to protect (the one a global score would +otherwise starve) was, in the accepted run, left merged with the majority +cluster by the partitioner, so it received no protection at all. A bounded +sweep of the retention floor confirmed this is a **partitioning** +shortfall, not a **retention-budget** shortfall: no floor value moved the +worst-cluster number. + +Along the way, developing this candidate against `ruvector-mincut` +surfaced two independent, reproducible defects in that crate (not +previously known to this nightly process — see below), which this run +worked around rather than silently absorbed. + +--- + +## Abstract + +`ruvector-agent-memory` (nightly 2026-06-14) scores every stored memory +against a global importance formula — `α·recency + β·frequency + +γ·coherence(context)` — and keeps the top-N at compaction time. It +measured excellent aggregate recall, but a global ranking is, by +construction, blind to topic diversity: a memory topic the agent is not +currently working on competes on the same scale as everything else, and +can be evicted **in full**. + +This nightly asks whether partitioning the memory similarity graph first +— using `ruvector-mincut`, previously unused for agent memory — and +retaining a guaranteed floor per partition, fixes that. It does not, at +least not with the threshold-based partitioner tested here; the write-up +below explains why, with per-cluster evidence. + +--- + +## Hypothesis + +```text +Given a 4,000-memory corpus with 6 semantic clusters of unequal size +(1400/1000/720/600/200/80 — two minorities at 5% and 2% of the corpus), +scored against a recency-biased context drawn from the largest cluster, + +when a partition-aware retention policy (floor + proportional budget per +graph partition) replaces CoherencePolicy's global top-score ranking, +at 50% compaction, + +then the best candidate's worst-cluster recall@10 should exceed the +baseline's by >= 15 percentage points, + +subject to: no candidate's overall recall@10 regressing more than 5pp +below baseline; partition+retention wall time under 30s per candidate at +this n; and the partition witness chain verifying. +``` + +This threshold, and the corpus/graph calibration below, were fixed +**before** the accepted run in `evidence/bench_n4000.txt`. They were not +adjusted afterward. + +--- + +## Why This Matters for RuVector + +RuVector is a Rust-native substrate for agent memory, not just a vector +store. Long-running agents accumulate memories across many unrelated +topics; a compaction policy that silently loses whole topics degrades +retrieval quality in a way aggregate recall numbers hide. This nightly +connects: + +| Component | Role | +|---|---| +| `ruvector-agent-memory` | Reused directly as a library dependency — the baseline scorer, and the within-partition scorer for both candidates. Not re-implemented. | +| `ruvector-mincut` | Source of the graph-partitioning primitives this crate builds on (`GraphPartitioner`) and cross-checks against (`DynamicMinCut::min_cut_value()`). | +| `ruvector-retrieval-receipt` (2026-08-13) | Precedent this crate follows for `witness.rs`'s SHA-256 hash-chain design — tamper-evident commitments over a decision, not a signature over correctness. | +| ruFlo | A real production path for this class of policy (if a future variant is accepted) would run as a scheduled memory-consolidation workflow, not inline on the write path. | +| MCP | A future accepted policy's natural interface is a narrow `memory_consolidate(target_pct)` tool, mirroring 2026-06-14's suggested `memory_compact`. | + +--- + +## Architecture + +```mermaid +flowchart TD + A[Memory corpus
4000 records, 6 clusters] --> B[k-NN similarity graph
graph.rs, k=10, cosine weights] + B --> C1[GlobalTopScore baseline
ruvector_agent_memory::CoherencePolicy] + B --> C2[MincutFixedK candidate A
ruvector_mincut::GraphPartitioner] + B --> C3[MincutAdaptive candidate B
mincut_exact.rs Stoer-Wagner] + C3 --> W[PartitionWitnessChain
witness.rs — SHA-256 hash chain] + C1 --> R1[retain_global_top_score] + C2 --> R2[retain_partitioned
floor + proportional budget] + C3 --> R2 + R1 --> M[metrics.rs
overall + per-cluster + worst-cluster recall@10] + R2 --> M + M --> ACC[Pre-declared acceptance gate
main.rs] +``` + +`mincut_exact.rs` exists because `ruvector_mincut::DynamicMinCut::partition()` +was found, during development, to disagree with its own `min_cut_value()` +— see **Defects Discovered** below. Candidate B's splits are materialized +by a from-scratch, tested Stoer–Wagner implementation instead; +`ruvector_mincut`'s value is still queried as an independent cross-check +and logged. + +--- + +## Implementation + +Three variants, one shared scorer: + +- **`GlobalTopScore`** (baseline): `ruvector_agent_memory::CoherencePolicy::default()` + applied to the whole corpus. +- **`MincutFixedK`** (candidate A): `ruvector_mincut::GraphPartitioner` + (existing tool, unweighted edge-count recursive bisection to a + caller-chosen `K`), then `retain_partitioned`. +- **`MincutAdaptive`** (candidate B): a new recursive bisection + (`partition.rs::recurse`) using `mincut_exact::global_min_cut` at each + level, stopping once a component's cut is dense relative to its + internal edge weight (`coherence_ratio`, calibrated below), then + `retain_partitioned`. + +`retain_partitioned` (`retention.rs`) allocates the retention budget +per-partition via a floor (`floor_min`, default 3) plus largest-remainder +proportional split of the remainder, then ranks each partition internally +with the same `CoherencePolicy` the baseline uses — isolating the +independent variable to *budget allocation*, not *scoring*. + +The corpus (`corpus.rs`) is a deterministic, seeded synthetic generator: +6 clusters on the unit sphere (rejection-sampled to cosine separation +≤ 0.35), Gaussian noise (`noise_std`), decoupled recency/frequency +signals, and a recency-biased "focus cluster" standing in for what the +agent was just working on — the realistic scenario in which +`CoherencePolicy`'s context window is biased away from other topics. +Ground truth is brute-force top-k cosine search against the full, +uncompacted corpus, computed once at generation time. + +### Calibration (before the accepted run) + +At the originally-planned `noise_std=0.35`, the corpus's true global min +cut degenerately isolated a single outlier vertex +(`normalized_cut≈0.76` — no real topic boundary was the graph's weakest +seam). At `noise_std=0.25`, the min cut cleanly isolated one whole +semantic cluster (`normalized_cut≈0.07`), confirming a graph structure +the hypothesis could actually be tested against. `noise_std=0.25` and +`coherence_ratio=0.35` were fixed from this calibration pass, before the +accepted run — see `evidence/calibration.txt`. + +--- + +## Defects Discovered in `ruvector-mincut` + +Two independent, reproducible issues, found while building candidate B, +neither previously known to this nightly process: + +### 1. `DynamicMinCut::partition()` is inconsistent with its own `min_cut_value()`, and nondeterministic + +Minimal repro: two triangles `{0,1,2}` and `{3,4,5}`, joined by one +`weight=0.05` bridge edge. The true global minimum cut is unique — value +`0.05`, split `{0,1,2}`/`{3,4,5}` (isolating any single triangle vertex +costs ≥ `1.0`). + +```rust +let mincut = MinCutBuilder::new().exact().with_edges(edges).build().unwrap(); +mincut.min_cut_value() // always 0.05, every run — correct +mincut.partition() // sometimes {0,1,2}/{3,4,5} (correct), + // sometimes {single vertex}/{rest} (wrong: that + // split's actual crossing weight is >= 1.0, not 0.05) +``` + +Of three runs: two returned the correct split, one returned the +degenerate split — same code, same input, different process invocations. +At 100 vertices (two 50-cliques, one `0.01` bridge), **every** run +returned the degenerate split, while `min_cut_value()` still correctly +reported `0.01` every time. `cut_edges()` (derived from `.partition()`) +was cross-checked to independently confirm the mismatch: for the +degenerate split, summed crossing-edge weight was `2.0`, not the reported +`0.05`. + +### 2. `GraphPartitioner` / `RuVectorGraphAnalyzer`: vertex loss, vertex fabrication, and severe latency + +- At n=100 (two 50-cliques + weak bridge), `GraphPartitioner::partition()` + returned partitions covering only 50 of the 100 input vertices. +- With a non-contiguous vertex-id space (`{1,2,3,11,12,13}`), + `RuVectorGraphAnalyzer::partition()` returned a side containing ids + (`4,5,6,7,8,9,10`) that were never in the input graph. +- **Latency**: `GraphPartitioner::partition()` (K=10) measured **8.4s at + n=500**, and had not finished after **5m42s at n=4000** (process + killed). This crate's own `mincut_exact::global_min_cut` measured + **167ms at n=500** and **~11.1s for the full adaptive recursion at + n=4000** — the same order of magnitude for *one* global min cut, + suggesting `GraphPartitioner`'s recursive re-wrapping (`RuVectorGraphAnalyzer::new` + per subgraph, itself built on the fully-dynamic `MinCutWrapper`) pays a + large, likely superlinear, overhead for what is fundamentally a + one-shot static computation at each level. + +**Workaround used in this crate**: `mincut_exact.rs` — a from-scratch, +tested, deterministic weighted Stoer–Wagner implementation — is the sole +source of partition vertex sets for candidate B. +`ruvector_mincut::DynamicMinCut::min_cut_value()` is still called as an +independent cross-check (`partition.rs`), logged via a `debug_assert!` on +disagreement; it was never observed wrong in this crate's testing, only +its *partition* output was. `fixed_k_partition` (candidate A) filters +`GraphPartitioner`'s output against the known-valid vertex set and +appends any uncovered vertex as a fallback group, so it cannot silently +drop or fabricate a memory — and is scale-gated (`fixed_k_max_n`, default +600) so a benchmark run cannot hang on it. + +**Not filed upstream as part of this nightly** (no `ruvector-mincut` +maintainer sign-off in scope here) — recorded as an open question in +ADR-305 for whoever owns that crate to verify and file. + +--- + +## Benchmark Methodology + +- Release build (`cargo build --release`), `rustc 1.94.1`, `cargo 1.94.1`. +- Hardware: x86-64, 4 logical CPUs, 15GiB RAM, Linux 6.18.5. +- Deterministic seed (`seed=42`) for corpus generation; ground truth + computed once per corpus via brute-force cosine search, not resampled + per variant. +- 150 out-of-sample queries (25 per cluster), recall@10 against the full + uncompacted corpus. +- Single run per configuration (no repeated-trial variance reporting — + see Limitations). +- Exact commands and raw, unedited output: `evidence/*.txt`. + +```bash +cargo run --release -p ruvector-partition-memory --bin benchmark -- 4000 3 0.35 10 600 +cargo run --release -p ruvector-partition-memory --bin benchmark -- 500 3 0.35 10 600 +cargo run --release -p ruvector-partition-memory --example darwin_sweep +cargo run --release -p ruvector-partition-memory --example calibrate +``` + +## Benchmark Results + +### n=4000 (accepted run) + +```text +variant retained overall_recall worst_cluster_recall coverage partition_us retention_us +GlobalTopScore 2000 0.4193 0.1520 1.000 0 12948 +MincutAdaptive 2000 0.4873 0.1520 1.000 11164029 13482 + +per_cluster_recall GlobalTopScore = [0.996, 0.152, 0.216, 0.316, 0.396, 0.440] +per_cluster_recall MincutAdaptive = [0.792, 0.380, 0.504, 0.152, 0.556, 0.540] + +MincutFixedK: SKIPPED (n=4000 exceeds fixed_k_max_n=600; see Defects Discovered) +MincutAdaptive partitions: 5, sizes=[200, 2000, 1000, 720, 80] + ^^^^ cluster0(1400)+cluster3(600) stayed merged +worst_cluster_gain_pp = -0.00 (threshold 15.00) ACCEPTANCE_RESULT: REJECT +``` + +Full raw output: `evidence/bench_n4000.txt`. + +### n=500 (both candidates) + +```text +variant overall_recall worst_cluster_recall coverage +GlobalTopScore 0.3440 0.1000 1.000 +MincutFixedK 0.4467 0.1800 1.000 +MincutAdaptive 0.3500 0.0000 0.833 <- 10-member cluster below min_cluster_size(20) + +worst_cluster_gain_pp (best=MincutFixedK) = 8.00 (threshold 15.00) ACCEPTANCE_RESULT: REJECT +``` + +Full raw output: `evidence/bench_n500_with_fixedk.txt`. + +### Bounded Darwin-style sweep (n=4000, partition fixed, `floor_min` varied) + +```text +floor_min=1 overall_recall=0.4880 worst_cluster_recall=0.1520 fitness=0.4224 +floor_min=3 overall_recall=0.4873 worst_cluster_recall=0.1520 fitness=0.4222 +floor_min=8 overall_recall=0.5000 worst_cluster_recall=0.1480 fitness=0.4260 +floor_min=15 overall_recall=0.5067 worst_cluster_recall=0.1480 fitness=0.4260 + +winner: floor_min=15 DARWIN_RESULT: PROMOTE (composite fitness only — see Darwin section) +``` + +`worst_cluster_recall` is flat (within noise) across every `floor_min` +tested — direct evidence the shortfall is structural (partitioning), not +a retention-budget tuning problem. Full raw output: +`evidence/darwin_sweep.txt`. + +--- + +## Memory Math + +At n=4000, d=64: corpus embeddings are `4000 × 64 × 4 bytes ≈ 1.0MB`. +The k-NN graph (k=10, deduplicated undirected) holds ~31,000 edges; +stored as `(u64, u64, f64)` triples, `~744KB`. `mincut_exact`'s working +set during a single `global_min_cut` call is `O(V)` `HashMap`s of degree +`~2k`; peak additional memory is a small multiple of the edge list, not +separately measured in this run (see Limitations). + +## Performance Math + +`MincutAdaptive`'s ~11.1s at n=4000 is dominated by the top-level +`global_min_cut` call over the full ~4000-vertex, ~31000-edge graph +(subsequent recursion levels operate on rapidly shrinking subgraphs). +This is consistent with the `O(V·E·log V)`-ish binary-heap Stoer–Wagner +formulation used here (not the theoretically tighter but more complex +`O(VE + V² log V)` Nagamochi–Ibaraki-style variant) — acceptable for a +one-time nightly consolidation event, not for an inline write-path +operation at this scale without further optimization. + +## Failure Modes + +- Partitioner leaves the true worst cluster merged with the majority + (this run's actual failure mode — see per-cluster evidence above). +- `min_cluster_size` floor structurally prevents isolating any topic + smaller than that absolute count (n=500 run). +- `ruvector-mincut` defects (see above) — worked around, not fixed. + +## Rejected Alternatives + +- **K-means-based partitioning** instead of graph min-cut: not + implemented; a reasonable next candidate that sidesteps + `ruvector-mincut` entirely (see ADR-305 Alternatives). +- **Forcing `GraphPartitioner` to be candidate A at full scale**: rejected + after direct measurement (5m42s, unfinished) — reported honestly as a + scale-gated skip rather than silently hidden or waited out indefinitely. + +--- + +## Security + +No new attack surface. This crate is a standalone research binary/library +over synthetic data; nothing in it is wired into a request-serving path. +`witness.rs` (SHA-256 hash chain over partition decisions) is a +tamper-evidence mechanism for *auditing a partition decision after the +fact* — it proves a step's recorded cut value and vertex-set hashes were +not edited post-hoc — it is **not** a correctness proof of the underlying +min cut and makes no access-control claim, matching the threat-model +framing `ruvector-retrieval-receipt` (2026-08-13) established for reads. + +## Governance + +Hypothesis rejected; no promotion, no production migration, no rollback +needed. The two `ruvector-mincut` defects are recorded as an open +question in ADR-305, not filed upstream from within this nightly run — +that requires the owning maintainer's verification. + +## MCP Implications + +None planned — the underlying policy is rejected. Had it been accepted, +the natural interface would mirror the 2026-06-14 nightly's suggested +`memory_compact(context, target_pct)` tool, narrowly scoped, read/write +on the agent's own memory store only. + +## WASM / Edge Implications + +Not evaluated. `mincut_exact.rs` has zero non-`ruvector_mincut` type +dependencies beyond `std` collections and would very likely compile to +WASM (no unsafe, no platform-specific code) if this policy is revisited, +but binary-size and edge-memory impact were not measured in this run — +no deployment claim is made. + +## RVF Implications + +A future accepted consolidation policy's output (retained memory ids + +partition witness chain) is a natural fit for an RVF portable snapshot: +the witness chain already produces the kind of signed-lineage evidence +RVF snapshots want. Not implemented — analysis only, per the mandatory +(implementation optional) requirement for RVF fit. + +## RVM Implications + +No RVM fit identified: this policy does not need isolated execution, +capability boundaries, or proof-gated mutation beyond what its own +witness chain already provides for its one internal decision (the +partition). Not forced. + +## ruFlo Implications + +If a future variant of this hypothesis is accepted, ruFlo's natural role +is a scheduled memory-consolidation workflow (analogous to the "memory +maintenance" workflow class in the harness's own role list) — triggered +on a cadence or storage-pressure signal, not run inline on the write +path, given the measured ~11s latency at n=4000. + +--- + +## Practical Applications + +1. **Long-running coding agents** — memory: prior debugging sessions + across unrelated modules; problem: a burst of work on module A can + starve retained memory of module B at consolidation time; RuVector + capability: (if a future variant is accepted) partition-aware + retention; ecosystem integration: ruFlo scheduled consolidation; + business value: fewer "the agent forgot X" regressions; main risk: + this run shows the naive version does not reliably deliver that + protection; time horizon: near-term, pending a revised hypothesis. +2. **Customer-support agent memory** — user: support bot; problem: a busy + week on one product line can evict memory of a rarely-escalated + product line; capability: same as above; risk: same; horizon: near-term. +3. **Multi-project assistant memory** — user: an assistant used across + several unrelated user projects; problem: intense work on project A + crowds out project B's memory; horizon: near-term. +4. **Scientific literature agents** — user: research assistant tracking + several research threads; problem: an active thread's queries bias + consolidation away from a dormant-but-still-relevant thread; horizon: + medium-term. +5. **Enterprise Graph RAG** — user: internal knowledge agent; problem: + department-specific knowledge clusters compete unevenly for retention + budget; horizon: medium-term. +6. **Robotics/edge agent memory** — user: an embedded agent with a hard + memory cap; problem: same starvation risk, higher stakes given no + "just don't compact" fallback; horizon: long-term, pending edge + feasibility work not done here. +7. **Security/anomaly-memory agents** — user: a SOC assistant; problem: + a high-volume alert category can crowd out memory of a rare-but-severe + category; horizon: medium-term. +8. **Local-first personal assistants** — user: a device-resident + assistant; problem: identical starvation risk under a tight local + memory budget; horizon: long-term. + +## Long Horizon Applications + +1. **Self-healing graph memory** — thesis: agent memory graphs that + detect and repair their own topic-starvation without a human noticing; + requires: a stopping criterion that reliably finds every weak seam, not + just some of them (this run's central gap); RuVector role: the + substrate the repair loop runs against; why this experiment matters: + it is the first measured evidence of *where* a naive version of this + idea fails; primary uncertainty: whether any single global threshold + can ever reliably separate every minority topic, or whether a + per-branch/adaptive criterion is required; falsification: repeat this + benchmark with a per-branch stopping rule and measure worst-cluster + gain again. +2. **Synthetic nervous systems for agent fleets** — thesis: fleets of + agents sharing a partitioned memory substrate, each fleet member + effectively "owning" a partition; requires: partition stability under + concurrent writes, not evaluated here; RuVector role: shared substrate; + uncertainty: whether partition boundaries stay stable as memory grows; + falsification: a delete/insert-churn variant of this benchmark. +3. **Agent operating systems** — thesis: memory partitioning as a kernel + primitive analogous to process isolation; requires: much stronger + correctness guarantees than this run's underlying library currently + provides (see Defects Discovered); uncertainty: whether the two + documented `ruvector-mincut` defects are fixable without an API + change; falsification: the fix either lands and this crate's + `mincut_exact.rs` workaround becomes redundant, or it doesn't. +4. **Swarm memory** — thesis: partition-aware consolidation as the memory + layer for multi-agent swarms; requires: partitioning at swarm scale + (this run only reached n=4000 at ~11s per full run); uncertainty: + scaling behavior beyond n=4000, not measured; falsification: repeat at + n=40,000 and check wall time stays sub-linear-ish. +5. **Dynamic world models** — thesis: topic partitions as a proxy for + distinct "world model" facets an agent maintains; requires: partition + labels that are stable and interpretable over time, not evaluated; + uncertainty: whether graph min-cut partitions correspond to anything a + human would call a coherent "facet"; falsification: qualitative review + of partition contents against human-labeled topics. +6. **Proof-gated autonomous infrastructure** — thesis: the witness chain + here generalizes to a general "prove this maintenance decision wasn't + silently gamed" primitive for autonomous infra; requires: extending + `witness.rs`'s pattern beyond partition decisions; uncertainty: + whether the pattern holds up under adversarial (not just accidental) + tampering; falsification: an explicit red-team pass against the + witness chain, not performed in this run. +7. **RVM coherence domains** — thesis: partitions as RVM coherence-domain + boundaries; requires: the RVM fit analysis above to change from "not + identified" to "identified," which would need a concrete isolation + requirement this policy does not currently have; uncertainty: high; + falsification: N/A until a concrete requirement exists. +8. **Robotics memory** — thesis: partition-aware retention for + resource-constrained robot memory; requires: the edge/WASM + measurements this run explicitly did not make; uncertainty: whether + `mincut_exact.rs`'s ~11s at n=4000 is remotely feasible on embedded + hardware; falsification: run `mincut_exact` benchmarks on target + hardware. + +--- + +## Competitor Comparison + +Not materially applicable — no public vector database documents a +graph-partition-aware memory *compaction* policy comparable to this +experiment's scope (agent-memory lifecycle management, not ANN indexing). +`documented_external_capability`: none found for this specific mechanism +in Milvus/Qdrant/Weaviate/Pinecone/LanceDB/FAISS/pgvector/Chroma/Vespa. +`directly_measured_capability`: N/A (nothing external to measure against). +`unknown`: whether any of these systems' internal (undocumented) +compaction logic does something structurally similar. + +--- + +## Evolution Results (Darwin) + +- **Executed**: yes, bounded (generations=1, candidates_per_generation=4, + matching the harness's default budget), over `floor_min ∈ {1,3,8,15}`, + partition held fixed (only retention depends on `floor_min`). +- **Fitness** (declared before running): `0.5·worst_cluster_recall + + 0.3·overall_recall + 0.2·correctness`. +- **Winner**: `floor_min=15`, `fitness=0.4260` vs parent + (`floor_min=3`) `fitness=0.4222` — `DARWIN_RESULT: PROMOTE` **on this + composite fitness metric only**. `worst_cluster_recall` itself did not + improve (0.148 vs 0.152 — marginally *worse*); the promotion is driven + by `floor_min=15`'s better overall recall. This is reported precisely + so it is not mistaken for the primary ACCEPTANCE_RESULT, which remains + REJECT. +- **Parent retained**: yes — this Darwin promotion is not wired into + `main.rs`'s defaults; ADR-305 does not recommend shipping it. + +## Witness Evidence + +`MincutAdaptive`'s partition witness chain: 9 split steps at n=4000, +`chain_verify=true`, head +`a15b77949d3d26928fc84cd89b0dcb749c4b16359b3caa08320967a8bffa8469` +(`evidence/bench_n4000.txt`). `witness.rs` unit tests additionally verify +the chain detects post-hoc tampering of a recorded step +(`chain_breaks_when_a_field_is_edited_after_the_fact`). + +## Production Path + +None — hypothesis rejected. See ADR-305 Consequences for the specific +follow-up direction (per-branch stopping criterion) that would need to be +tested as a new hypothesis before any production consideration. + +## Falsification Criteria + +Met, per the pre-declared acceptance gate: worst-cluster recall gain did +not reach +15pp in any tested configuration, including a bounded sweep of +the one parameter most likely to rescue it. + +## Limitations + +- **Single run per configuration** — no repeated-trial variance reporting + (Step 13's "prefer multiple repetitions" was not followed here, given + ~11s per n=4000 run and the time budget for one nightly cycle). The + measured numbers should be read as point estimates, not + variance-characterized results. +- **One corpus generator, one seed family** — results are specific to + this synthetic corpus's cluster-separation and noise characteristics; + not validated against a real agent-memory trace. +- **`ruvector-mincut` defects not filed upstream** from within this run — + recorded as an open question, not resolved. +- **No WASM/edge measurement**, despite the mandatory-analysis + requirement being satisfied by the qualitative section above. +- **`mincut_exact.rs` is not asymptotically optimal** Stoer–Wagner + (a Nagamochi–Ibaraki-style formulation would be faster); it was + sufficient for this run's n=4000 but was not tuned for larger scale. + +## Next Research + +1. Test a per-branch/size-weighted adaptive stopping criterion against + the same corpus and acceptance gate, as a genuinely new hypothesis. +2. Test a k-means-based (non-graph) partition baseline, sidestepping + `ruvector-mincut` entirely, as a cheaper alternative worth comparing. +3. Verify the two `ruvector-mincut` defects against that crate's own test + suite and, if confirmed absent from existing coverage, file them + upstream with the repros in this doc. +4. Repeat this benchmark with repeated trials and variance reporting if + a revised hypothesis clears the first-pass bar above. + +## References + +- Nightly 2026-06-14, `crates/ruvector-agent-memory` — `CoherencePolicy`, + reused directly here. +- Nightly 2026-08-13, `crates/ruvector-retrieval-receipt`, ADR-304 — + witness-chain design precedent for `witness.rs`. +- Jin, Sun, Thorup, "Fully Dynamic Exact Minimum Cut in Subpolynomial + Time" (SODA 2024) — the algorithm `ruvector-mincut`'s `witness` module + cites; not itself re-verified in this run. +- Stoer, Wagner, "A Simple Min-Cut Algorithm" (1997) — the algorithm + implemented from scratch in `mincut_exact.rs`. diff --git a/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/evidence/bench_n4000.txt b/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/evidence/bench_n4000.txt new file mode 100644 index 000000000..c7b2cb2e9 --- /dev/null +++ b/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/evidence/bench_n4000.txt @@ -0,0 +1,18 @@ +=== ruvector-partition-memory nightly benchmark === +n=4000 dims=64 cluster_sizes=[1400, 1000, 720, 600, 200, 80] focus_cluster=0 target_size=2000 (50% retention) queries=150 +params: floor_min=3 coherence_ratio=0.35 fixed_k=10 fixed_k_max_n=600 +corpus_gen_us=119649 knn_graph_us=2691799 knn_edges=31061 + +MincutFixedK: SKIPPED (n=4000 exceeds fixed_k_max_n=600; ruvector_mincut::GraphPartitioner measured at 8.4s for n=500 and did not finish in 5m42s for n=4000 during this nightly's development — see the research doc for the repro) +MincutAdaptive partitions: 5 (coherence_ratio=0.35) sizes=[200, 2000, 1000, 720, 80] +MincutAdaptive witness: 9 split steps, chain_verify=true, head=a15b77949d3d26928fc84cd89b0dcb749c4b16359b3caa08320967a8bffa8469 + +variant retained overall_recall worst_cluster_recall coverage partition_us retention_us +GlobalTopScore retained=2000 overall_recall=0.4193 worst_cluster_recall=0.1520 coverage=1.000 partition_us=0 retention_us=12948 +MincutAdaptive retained=2000 overall_recall=0.4873 worst_cluster_recall=0.1520 coverage=1.000 partition_us=11164029 retention_us=13482 + +per_cluster_recall GlobalTopScore = [0.996, 0.15200000000000005, 0.21599999999999997, 0.31600000000000006, 0.39599999999999996, 0.44000000000000006] +per_cluster_recall MincutAdaptive = [0.7919999999999999, 0.38, 0.5040000000000001, 0.15200000000000002, 0.5559999999999999, 0.54] + +acceptance: best_candidate=MincutAdaptive worst_cluster_gain_pp=-0.00 (threshold_pp=15.00) gain_ok=false overall_ok=true latency_ok=true witness_ok=true +ACCEPTANCE_RESULT: REJECT diff --git a/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/evidence/bench_n500_with_fixedk.txt b/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/evidence/bench_n500_with_fixedk.txt new file mode 100644 index 000000000..c76d484f1 --- /dev/null +++ b/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/evidence/bench_n500_with_fixedk.txt @@ -0,0 +1,20 @@ +=== ruvector-partition-memory nightly benchmark === +n=500 dims=64 cluster_sizes=[175, 125, 90, 75, 25, 10] focus_cluster=0 target_size=250 (50% retention) queries=150 +params: floor_min=3 coherence_ratio=0.35 fixed_k=10 fixed_k_max_n=600 +corpus_gen_us=12962 knn_graph_us=35113 knn_edges=3572 + +MincutFixedK partitions: 3 (K=10 requested) sizes=[1, 346, 153] +MincutAdaptive partitions: 4 (coherence_ratio=0.35) sizes=[90, 75, 125, 210] +MincutAdaptive witness: 7 split steps, chain_verify=true, head=11910bea6063621a4290554636dc218909bc4baa4812350f4ea4de56238a7a70 + +variant retained overall_recall worst_cluster_recall coverage partition_us retention_us +GlobalTopScore retained=250 overall_recall=0.3440 worst_cluster_recall=0.1000 coverage=1.000 partition_us=0 retention_us=1533 +MincutFixedK retained=250 overall_recall=0.4467 worst_cluster_recall=0.1800 coverage=1.000 partition_us=642943 retention_us=1681 +MincutAdaptive retained=250 overall_recall=0.3500 worst_cluster_recall=0.0000 coverage=0.833 partition_us=516739 retention_us=2514 + +per_cluster_recall GlobalTopScore = [0.988, 0.2, 0.188, 0.3, 0.2879999999999999, 0.10000000000000003] +per_cluster_recall MincutFixedK = [0.848, 0.18, 0.34, 0.4640000000000001, 0.5479999999999999, 0.2999999999999999] +per_cluster_recall MincutAdaptive = [0.6039999999999999, 0.49199999999999994, 0.4040000000000001, 0.46, 0.14000000000000004, 0.0] + +acceptance: best_candidate=MincutFixedK worst_cluster_gain_pp=8.00 (threshold_pp=15.00) gain_ok=false overall_ok=true latency_ok=true witness_ok=true +ACCEPTANCE_RESULT: REJECT diff --git a/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/evidence/calibration.txt b/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/evidence/calibration.txt new file mode 100644 index 000000000..575b8e43a --- /dev/null +++ b/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/evidence/calibration.txt @@ -0,0 +1,6 @@ +n=500 ratio=0.5 partitions=4 sizes=[90, 75, 125, 210] purities=["1.00", "1.00", "1.00", "0.83"] us=480432 +n=500 ratio=0.35 partitions=4 sizes=[90, 75, 125, 210] purities=["1.00", "1.00", "1.00", "0.83"] us=524855 +n=500 ratio=0.2 partitions=2 sizes=[90, 410] purities=["1.00", "0.43"] us=315318 +n=4000 ratio=0.5 partitions=5 sizes=[200, 2000, 1000, 720, 80] purities=["1.00", "0.70", "1.00", "1.00", "1.00"] us=10784652 +n=4000 ratio=0.35 partitions=5 sizes=[200, 2000, 1000, 720, 80] purities=["1.00", "0.70", "1.00", "1.00", "1.00"] us=11097579 +n=4000 ratio=0.2 partitions=4 sizes=[2200, 1000, 720, 80] purities=["0.64", "1.00", "1.00", "1.00"] us=6667169 diff --git a/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/evidence/darwin_sweep.txt b/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/evidence/darwin_sweep.txt new file mode 100644 index 000000000..338dbf0ac --- /dev/null +++ b/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/evidence/darwin_sweep.txt @@ -0,0 +1,8 @@ +parent partition (fixed for the whole sweep): 5 partitions, sizes=[200, 2000, 1000, 720, 80], correctness=1 +gen=1 candidates_per_generation=4 +floor_min=1 retained=2000 overall_recall=0.4880 worst_cluster_recall=0.1520 fitness=0.4224 +floor_min=3 retained=2000 overall_recall=0.4873 worst_cluster_recall=0.1520 fitness=0.4222 +floor_min=8 retained=2000 overall_recall=0.5000 worst_cluster_recall=0.1480 fitness=0.4240 +floor_min=15 retained=2000 overall_recall=0.5067 worst_cluster_recall=0.1480 fitness=0.4260 +winner: floor_min=15 fitness=0.4260 beats_parent(floor_min=3)=true +DARWIN_RESULT: PROMOTE diff --git a/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/gist.md b/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/gist.md new file mode 100644 index 000000000..b6e10812d --- /dev/null +++ b/docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/gist.md @@ -0,0 +1,123 @@ +# Partitioning agent memory before compaction: a negative result, and a bug it uncovered + +## Problem + +Agent memory systems that compact by a single global importance score +(recency + frequency + relevance to current context) can evict an entire +topic in one pass, even at a compaction ratio that looks fine on average. +A topic the agent isn't currently working on has no defense against a +score built for the topic it is working on. + +## Hypothesis + +Partition the memory similarity graph into topic clusters first, then +give each partition a guaranteed minimum retention share, so a topic only +competes with itself for its floor allocation instead of the whole +corpus. Tested on a synthetic 4,000-memory corpus with 6 unequal-size +semantic clusters (down to 2% of the corpus), against +`ruvector-agent-memory`'s existing `CoherencePolicy` baseline, at 50% +compaction. + +Pre-declared bar: the best partition-aware candidate's **worst-cluster** +recall@10 must beat the baseline's by ≥15 percentage points. + +## What happened + +It didn't clear the bar. Overall recall improved (+6.8pp) and 4 of 6 +clusters individually gained 15–23pp — the mechanism clearly does +something. But the specific cluster that was worst under the baseline was +*also* worst under the partitioned candidate, at the identical recall +value, because the partitioner left it merged with the majority cluster +instead of separating it out. A follow-up sweep of the retention floor +(1, 3, 8, 15) left the worst-cluster number flat across every value — +proof the gap is in *where the graph gets cut*, not *how the budget gets +split afterward*. + +```text +per_cluster_recall GlobalTopScore = [0.996, 0.152, 0.216, 0.316, 0.396, 0.440] +per_cluster_recall MincutAdaptive = [0.792, 0.380, 0.504, 0.152, 0.556, 0.540] + ^^^^^ improved a lot ^^^^^ untouched +``` + +## The bug along the way + +Before any of the above could be measured, `ruvector_mincut::DynamicMinCut::partition()` +turned out to be untrustworthy. Minimal repro: two triangles joined by a +single weak-weight bridge edge — a graph whose true minimum cut is +unique and easy to verify by hand. + +```rust +let mincut = MinCutBuilder::new().exact().with_edges(edges).build().unwrap(); +mincut.min_cut_value() // 0.05, every single run — correct +mincut.partition() // sometimes the correct split, sometimes a + // degenerate "isolate one vertex" split whose + // actual crossing weight is 40x the reported value +``` + +At 100 vertices the degenerate split happened on every run, not just +some. `GraphPartitioner` (built on the same machinery) separately dropped +vertices outright, fabricated vertex ids for non-contiguous id spaces, +and took 8.4 seconds to partition 500 vertices — with no sign of +finishing at 4,000 after nearly six minutes. + +None of that is this crate's algorithm — it's the *value* computation +that was correct, only the *partition materialization* that wasn't. The +workaround was a from-scratch, tested, deterministic weighted +Stoer–Wagner implementation (`mincut_exact.rs`, ~250 lines, zero +non-`ruvector_mincut`-type dependencies), used as the sole source of +partition vertex sets, with `ruvector_mincut`'s value still queried +purely as an independent cross-check. + +## Why report a rejected hypothesis + +Because the measurement is real and the mechanism partially works. A +future variant with a smarter stopping rule — one that doesn't let a +single global threshold decide every split — is a legitimate next +experiment, and now has a concrete, per-cluster reason to exist instead +of a hunch. And because the correctness bug this candidate ran into would +have silently produced wrong partitions for anyone else building on +`GraphPartitioner` or `DynamicMinCut::partition()` today, whether or not +this particular hypothesis had panned out. + +## Limitations + +Single run per configuration, one synthetic corpus, one seed family — no +variance characterization. The `ruvector-mincut` defects are documented +with repros but not filed upstream from within this run; that needs the +owning maintainer's independent verification. + +## Production relevance + +None yet — this is a rejected hypothesis. If a per-branch stopping +criterion clears the bar in a follow-up run, the natural production path +is a scheduled ruFlo memory-consolidation workflow, not an inline +write-path operation (the measured ~11s partitioning time at n=4000 rules +that out regardless). + +## RuVector ecosystem implications + +`mincut_exact.rs` is a reusable, correctness-tested min-cut +implementation independent of this ADR's own rejected hypothesis — a +better foundation for any future RuVector graph-partitioning work than +`DynamicMinCut::partition()` as it stands today. + +## Future direction + +Test a per-branch/size-weighted stopping criterion (attempt at least one +more split on the largest remaining partition before accepting a global +threshold's verdict) against the same corpus and the same 15pp bar, as a +new, separately pre-declared hypothesis. + +## References + +- Nightly 2026-06-14, `ruvector-agent-memory` — `CoherencePolicy`, the + baseline this experiment measured against and reused as a dependency. +- Nightly 2026-08-13, `ruvector-retrieval-receipt` — witness-chain design + precedent. +- Stoer & Wagner, "A Simple Min-Cut Algorithm" (1997). +- Jin, Sun & Thorup, "Fully Dynamic Exact Minimum Cut in Subpolynomial + Time" (SODA 2024) — the algorithm `ruvector-mincut` implements. + +Full write-up, ADR, and raw benchmark output: +`docs/research/nightly/2026-08-17_mincut-partitioned-memory-consolidation/` +in [ruvnet/ruvector](https://github.com/ruvnet/ruvector).