Conversation
RuVectorGraphAnalyzer::partition() returned an empty/degenerate result in ~50% of repeated calls against a byte-identical graph (first measured by the 2026-09-05 nightly run, root cause left unresolved). Two independent bugs: - DynamicGraph::vertices()/edges() iterated a DashMap directly. DashMap's default hasher is randomly seeded per instance, so iteration order isn't stable across process runs even for identical insertions. Downstream tie-breaking (BoundedInstance's seed selection and bitmask-to-vertex assignment) silently picked a different valid cut on every run. - WitnessHandle::materialize_partition() inferred the graph's vertex range from max(U) (the cut side's own membership) instead of the graph's real vertex count, so V \ U came back truncated or empty whenever U didn't happen to contain the graph's highest-numbered vertex — the common case. Fix: sort vertices()/edges() at the DynamicGraph read boundary, sort the HashSet-derived vertex lists BoundedInstance uses for tie-breaking, and have RuVectorGraphAnalyzer::partition() build both cut sides from the graph's real vertex list via witness.contains() instead of materialize_partition(). No public API signature changes. Verified against the exact reproduction script from the 2026-09-05 run: 0/60 empty results across two 30-trial runs (was 15/30, 50%), plus a new regression test asserting byte-identical partitions across repeated calls. Full ruvector-mincut suite: 644 passed, 0 failed, 5 pre-existing ignored. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01VFTtb2ZrTNkmpWVQfuiKkT
Records the decision, evidence, alternatives considered, and open questions for the ruvector-mincut determinism fix. Regenerated docs/adr/INDEX.md via scripts/adr-index.mjs. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01VFTtb2ZrTNkmpWVQfuiKkT
Full methodology, root-cause diagnostic narrative (including the intermediate finding that fixing only the DashMap-ordering bug made the symptom look worse before the second, independent witness-partition bug was found), benchmark results, limitations, and next research for the 2026-09-11 nightly run. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01VFTtb2ZrTNkmpWVQfuiKkT
Fixes the Rustfmt CI check on PR #979. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01VFTtb2ZrTNkmpWVQfuiKkT
|
CI status: The job ran 3547 tests across the workspace; exactly one failed: Every other check on this head is green: Rustfmt, Clippy, Cargo check, and I don't have a fix to port — I did not touch Generated by Claude Code |
|
Second failure, also not this PR's:
I don't have a fix to port (no open PR found addressing it, and bumping Generated by Claude Code |
|
Re-run results:
I've used this session's one re-run for this workflow run already Generated by Claude Code |
Summary
Nightly research run (autonomous). Root-causes and fixes the
non-determinism in
ruvector_mincut::RuVectorGraphAnalyzer::partition()that the 2026-09-05 nightly (
docs/research/nightly/2026-09-05-mincut-gated-forgetting,ADR-345)
measured (15/30 calls returning empty/degenerate on a byte-identical graph)
but did not locate.
Two independent bugs, both fixed, no public API signature changes:
DynamicGraph::vertices()/edges()iterated aDashMapdirectly.DashMap's default hasher is randomly seeded per instance, so itsiteration order is not stable across process runs even for identical
insertions — downstream tie-breaking (
BoundedInstance's seed selectionand bitmask-to-vertex assignment) silently picked a different valid cut
on every run.
WitnessHandle::materialize_partition()inferred the graph's vertexrange from
max(U)(the cut side's own membership) instead of thegraph's real vertex count, so the complement
V \ Ucame back truncatedor empty whenever
Udidn't happen to contain the graph'shighest-numbered vertex — the common case, not an edge case. This was
the direct, deterministic cause of most empty results; fixing bug 1
alone (see commit history) made the symptom measurably worse before
bug 2 was found, which is itself a notable finding about diagnosing
compound bugs from a single aggregate metric.
Fix: sort
vertices()/edges()at theDynamicGraphread boundary;sort the
HashSet-derived vertex listsBoundedInstanceuses fortie-breaking; have
RuVectorGraphAnalyzer::partition()build both cutsides from the graph's real vertex list via
witness.contains()instead ofmaterialize_partition().Eighteen crates depend on
ruvector-mincutdirectly (including WASM/Nodebindings,
ruvector-agent-memory,prime-radiant,cognitum-gate-kernel,mcp-brain-server); all get this correctness fix for free on next rebuildwith zero migration cost.
Explicitly out of scope: the separately-documented latency scaling
problem (77ms–11.4s across 50–400 vertices) is untouched — this is a
correctness fix, not a performance fix.
Evidence
Reused the prior nightly's own unmodified reproduction script
(
crates/ruvector-agent-memory/examples/mincut_determinism_probe.rs) foran honest before/after comparison:
New regression test (
crates/ruvector-mincut/tests/determinism_tests.rs)asserts a strictly stronger property — byte-identical partitions across 30
repeated calls, not just "non-empty":
Full pre-existing
ruvector-mincutsuite (lib + all 10 integration testfiles): 644 passed, 0 failed, 5 pre-existing ignored.
Downstream sanity check —
ruvector-agent-memory'sgraph_forgettests(the crate/feature that originally surfaced this bug,
mincut-forgetfeature): 3 passed, 0 failed.
cargo clippy --release -p ruvector-mincut --lib --tests -- -D warnings:clean (no new warnings).
Files changed
crates/ruvector-mincut/src/graph/mod.rs— sortvertices()/edges()crates/ruvector-mincut/src/instance/bounded.rs— sort seed/tie-breakvertex lists; deterministic witness seed
crates/ruvector-mincut/src/instance/witness.rs— doc comment only(documents
materialize_partition()'s scope limitation)crates/ruvector-mincut/src/integration/mod.rs—partition()usesgraph.vertices()+witness.contains()instead ofmaterialize_partition()crates/ruvector-mincut/tests/determinism_tests.rs— new regression testdocs/adr/ADR-346-deterministic-mincut-witness-partition.md— new ADRdocs/adr/INDEX.md— regenerated vianode scripts/adr-index.mjsdocs/research/nightly/2026-09-11-mincut-partition-determinism/README.md— full nightly research report
docs/research/nightly/2026-09-11-mincut-partition-determinism/gist.md— standalone technical write-up
Benchmark commands (reproducible)
cargo build --release -p ruvector-agent-memory \ --example mincut_determinism_probe --features mincut-forget TRIALS=30 ./target/release/examples/mincut_determinism_probe cargo test --release -p ruvector-mincut --lib --testsAcceptance result
ACCEPT — see ADR-346
and the nightly README
for the full evidence table, falsification criteria, and limitations
(only the
<20-vertex brute-force path was end-to-end re-benchmarked;concurrent-mutation and WASM/Node binding smoke tests are flagged as
follow-up, not done this run).
MetaHarness / Flywheel / Darwin capability discovery
Re-verified per the nightly process's own rule — unchanged from the prior
nightly:
npx metaharness --helpresolves to a generic project-scaffoldingCLI not wired into this repo;
npx ruvector harness doctor --jsonfails(no such CLI package installed). No Darwin evolutionary search was run —
this was a targeted root-cause diagnosis of an already-reported bug, not a
parameter search over a candidate population.
Security / governance
No security-relevant surface changed (no new I/O, no trust-boundary or
signing-path changes). No schema, wire-format, or persisted-data changes.
Limitations / follow-up (see ADR-346 "Open Questions")
search_for_cuts's seed-ordering fix (the>=20-vertexLocalKCutpath)was applied by the same reasoning but not independently re-benchmarked at
that size this session.
MincutGatedForgettingacceptancebenchmark (that rejection has a second, independent, unaddressed latency
cause).
🤖 Generated with claude-flow
https://claude.ai/code/session_01VFTtb2ZrTNkmpWVQfuiKkT
Generated by Claude Code