Skip to content

research: DynamicMinCut direct backend fixes mincut-gated-forgetting's performance rejection (ADR-346) - #993

Draft
ruvnet wants to merge 3 commits into
mainfrom
claude/focused-darwin-2caikw
Draft

ruvnet wants to merge 3 commits into
mainfrom
claude/focused-darwin-2caikw

Conversation

@ruvnet

@ruvnet ruvnet commented Sep 17, 2026

Copy link
Copy Markdown
Owner

Hypothesis

Follow-up to ADR-345 (nightly 2026-09-05), which rejected MincutGatedForgetting (a structural, min-cut-derived eviction signal for agent-memory compaction) on two axes: a 1,800-2,700x compaction slowdown, and a 0.0pp bridge-survival improvement. Its own "Next Research" item 1 named ruvector_mincut::DynamicMinCut, used directly instead of the RuVectorGraphAnalyzer convenience wrapper, as the specific untried fix for the performance axis.

This PR implements and tests exactly that, reusing ADR-345's corpus, hypothesis text, and acceptance thresholds unmodified (no goalpost moves):

Given the same 84-memory synthetic corpus and MincutGatedForgetting policies,
when boundary detection uses MincutBackend::Direct (DynamicMinCut via MinCutBuilder)
instead of MincutBackend::Wrapper (RuVectorGraphAnalyzer, unchanged),
then compaction wall-clock stays under 100x baseline's (the gate Wrapper failed by ~16-27x),
subject to: bridge-survival gap and Recall@10 delta reproducing Wrapper's own numbers exactly.

Architecture

Added MincutBackend::{Wrapper, Direct} to the existing feature-gated graph_forget module. Both backends build the identical k-NN graph through one shared helper, so the only controlled variable is which ruvector-mincut API computes the partition. Wrapper remains the default; no existing caller's behavior changes.

Files Changed

  • crates/ruvector-agent-memory/src/graph_forget.rs: MincutBackend enum, with_backend(), shared graph builder, partition_via_wrapper/partition_via_direct.
  • crates/ruvector-agent-memory/src/lib.rs: re-export MincutBackend.
  • crates/ruvector-agent-memory/examples/: 4 new examples (mincut_direct_backend_bench, mincut_direct_scaling_probe, mincut_direct_determinism_probe, mincut_direct_scale_effectiveness_probe), registered in Cargo.toml under the existing mincut-forget feature.
  • docs/adr/ADR-346-mincut-direct-backend-for-gated-forgetting.md, docs/adr/INDEX.md (regenerated via scripts/adr-index.mjs).
  • docs/research/nightly/2026-09-17-mincut-direct-backend/{README.md,gist.md}.

Benchmark Command & Real Results

cargo run --release -p ruvector-agent-memory --example mincut_direct_backend_bench --features mincut-forget
Policy                           Bridge Surv.    Recall@10  Compaction (us)
----------------------------------------------------------------------------
CoherencePolicy (baseline)              66.7%       100.0%               74
Soft-Wrapper (2026-09-05)               66.7%       100.0%           117589
Hard-Wrapper (2026-09-05)               66.7%       100.0%           116171
Soft-Direct (this run)                  66.7%       100.0%             2081
Hard-Direct (this run)                  66.7%       100.0%             2112

Acceptance test (thresholds unmodified from the 2026-09-05 run)
  Soft-Wrapper gap=+0.0pp[FAIL] recall_delta=0.00pp[PASS] slowdown=1589.0x[FAIL]
  Hard-Wrapper gap=+0.0pp[FAIL] recall_delta=0.00pp[PASS] slowdown=1569.9x[FAIL]
  Soft-Direct  gap=+0.0pp[FAIL] recall_delta=0.00pp[PASS] slowdown=28.1x[PASS]
  Hard-Direct  gap=+0.0pp[FAIL] recall_delta=0.00pp[PASS] slowdown=28.5x[PASS]

Direct vs Wrapper speedup: Soft 56.5x, Hard 55.0x

Scaling probe (ring k-NN, k=8) vs. ADR-345's own table on the same topology/sizes: n=50 76.8ms→0.708ms (~108x), n=100 481.3ms→1.895ms (~254x), n=200 2,712.9ms→5.466ms (~496x), n=400 11,415.0ms→18.620ms (~613x). Determinism probe (fixed 19-vertex graph, 30 trials): Wrapper gave 15/30 empty/unusable results at 841ms/call; Direct gave 0/30 empty results (1 distinct min-cut value across all 30) at 0.119ms/call.

An exploratory, explicitly non-gating follow-up (mincut_direct_scale_effectiveness_probe) then asks whether the 0.0pp effectiveness gap was just a small-corpus artifact, now that Direct is cheap enough to test at scale: across 84→1,344 memories (16x), the gap stays at 0.0pp (briefly -6.2pp at n=336). Not a sampling artifact.

Acceptance Result

ACCEPT for this PR's registered performance sub-hypothesis (Direct clears the 100x slowdown gate at 28.1-28.5x, vs. Wrapper's 1,569-1,589x; effectiveness numbers are bit-identical between backends as required). REJECT unchanged for the overall MincutGatedForgetting production hypothesis — the bridge-survival gap is still 0.0pp against the required ≥15pp, now confirmed across a 16x corpus-size range, strengthening ADR-345's "global min-cut isolates an outlier, not the intended bridge" explanation.

Darwin Result

No automated Darwin/MetaHarness tooling exists in this repository (verified: npx ruvector harness doctor/status --json — no such executable resolvable; npx metaharness --help resolves to an unrelated project-scaffolding generator). Framed manually: one generation, two candidates (Wrapper parent, Direct challenger), ADR-345's unmodified acceptance thresholds as the fitness function, one promotion (Direct merged as an opt-in addition; Wrapper retained as the unchanged default; the underlying policy itself stays unpromoted).

Flywheel Result

This PR is a Flywheel-style follow-up: it reads ADR-345's raw numbers and named root causes, executes its "Next Research" item 1 without re-deriving anything, and records new evidence (the scale-effectiveness sweep) that materially strengthens one of ADR-345's still-open questions, so a future run doesn't have to re-ask "is the zero effect just a small-sample artifact?" from scratch.

Security Review

No new cryptographic primitive. MincutBackend::Direct's disconnected-graph-to-empty-partition mapping is a documented correctness/parity choice, not a security control. witnessed_compaction (ADR-345's eviction-witness mechanism) is untouched. No new external dependency — ruvector-mincut was already an optional dependency under the same mincut-forget feature.

Main Limitations

  • Single run per configuration; no repeated-run variance characterization (same limitation ADR-345 flagged).
  • ClusterHierarchy::boundary_size, ADR-345's other named alternative, does not exist under that name in this codebase (verified by source inspection) and was not tested; DynamicMinCut was used instead.
  • ruvector-mincut's MinCutWrapper non-determinism is not fixed at its source, only routed around for this integration.
  • The effectiveness root cause remains open; a local min-cut per candidate (via the existing, unused ruvector-mincut::localkcut module) is the next thing to try, not attempted here.

Production Recommendation

Merge MincutBackend::Direct as the additive, opt-in improvement it is — it is strictly better than Wrapper on every measured axis (speed, determinism) with no behavior change for existing callers. Do not promote MincutGatedForgetting itself to a recommended or default compaction policy; the effectiveness gate is unmet independent of backend or corpus size.

Research Document, ADR, and Gist

  • Full report: docs/research/nightly/2026-09-17-mincut-direct-backend/README.md
  • ADR: docs/adr/ADR-346-mincut-direct-backend-for-gated-forgetting.md
  • Gist: docs/research/nightly/2026-09-17-mincut-direct-backend/gist.md

Test Plan

  • cargo build --release -p ruvector-agent-memory --features mincut-forget (clean)
  • cargo test -p ruvector-agent-memory --features mincut-forget (all tests pass, including existing graph_forget unit tests unaffected by the new default-Wrapper backend)
  • cargo fmt -p ruvector-agent-memory -- --check (clean)
  • cargo clippy -p ruvector-agent-memory --features mincut-forget --all-targets (no new warnings)
  • node scripts/adr-index.mjs --check (no duplicate ADR numbers)
  • All four new example benchmarks run in release mode; raw output captured verbatim in the research report

🤖 Generated with claude-flow

https://claude.ai/code/session_01554na6bnFsvgTnQ4mBnX8y


Generated by Claude Code

claude and others added 3 commits September 17, 2026 07:37
…cut-gated forgetting

ADR-345's nightly run rejected MincutGatedForgetting partly on a
1,800-2,700x compaction slowdown caused by RuVectorGraphAnalyzer's
per-call full edge-replay into MinCutWrapper's bounded-range instances.
Its own "next research" item 1 named ruvector_mincut::DynamicMinCut as
the untried, lower-level alternative.

Add MincutBackend::{Wrapper, Direct} with a shared k-NN graph builder so
both backends see byte-identical topology; Direct builds a DynamicMinCut
via MinCutBuilder (one exact solve) instead of replaying edges into the
wrapper. Wrapper remains the default; no existing caller's behavior
changes.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01554na6bnFsvgTnQ4mBnX8y
Records the decision, measured evidence, and rejection criteria for
MincutBackend::Direct (performance sub-hypothesis accepted; overall
MincutGatedForgetting production use remains rejected per ADR-345).
Regenerates docs/adr/INDEX.md via scripts/adr-index.mjs.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01554na6bnFsvgTnQ4mBnX8y
…ct backend

Full research report (hypothesis, capability-discovery table, benchmark
methodology, raw benchmark output for the scaling/determinism/main/
scale-effectiveness probes, promotion decision, and next-research items)
plus a standalone gist article, per the nightly research process.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01554na6bnFsvgTnQ4mBnX8y

ruvnet commented Sep 17, 2026

Copy link
Copy Markdown
Owner Author

Tests (ml-research-rest) failed on this PR's head commit (7849990), but the failing test is ruvector-domain-expansion::tool_orchestration::tests::test_difficulty_affects_error_scenarios — a crate this PR does not touch (the diff is scoped to ruvector-agent-memory's graph_forget module and its examples/Cargo.toml, plus docs).

Reading that test: generate_tasks(20, 0.9) uses unseeded rand::thread_rng(), and only one of its three task categories (gen_error_recovery, chosen with independent probability 0.3 per task) ever produces a non-empty error_scenarios list — the other two categories always return Vec::new() regardless of difficulty. The test asserts at least one of 20 generated tasks has non-empty error_scenarios, which fails by chance with probability (0.7)^20 ≈ 0.08% (roughly 1 in 1,250 runs) independent of any code correctness issue. This is a pre-existing flaky test unrelated to this PR's change, not a regression it introduced.

I attempted the one permitted re-run to confirm, but the workflow run was still in progress at the time (rerun-failed-jobs returned "This workflow is already running"). I'll re-run just this job once the run finishes.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants