dream(memory): #3231 wire embedding near-dup detection into MemoryConsolidator.dedup() (evaluated, ACCEPT) (#3232)

* dream(memory): wire embedding near-duplicate detection into MemoryConsolidator.dedup()

MemoryConsolidator.dedup() only ever deduplicated entries via byte-exact
SHA-256 content hashing, even though every entry already carries a
computed .embedding and the adapter's HNSWIndex (already held in scope
for removal bookkeeping) is incrementally kept in sync. Paraphrases and
reformattings of the same memory were never caught.

Adds a second pass: for hash-pass survivors with an embedding, query the
already-populated HNSWIndex for cosine-similarity neighbors above a
configurable similarityThreshold (default 0.95, matching the unwired
domain-layer consolidator's own constant), and merge using the same
keeper-selection strategies (now factored into shared selectKeeper/
mergeGroup helpers). Guarded to cosine-metric indexes only; disabled via
similarityThreshold >= 1.

Evaluation: 471/472 passing in @claude-flow/memory (1 pre-existing,
unrelated, environmental failure); baseline (git-stash-isolated source)
fails exactly the 2 new discriminating tests and passes the rest,
confirming the fix is additive and non-regressive. tsc --noEmit clean.

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

* dream(memory): fix-forward — converge near-dup pass to a fixed point in one dedup() call

Independent adversarial critique (STEP 10) reproduced a real, bounded gap
in the prior commit: a near-duplicate cluster larger than
NEAR_DUP_SEARCH_K (8) split into multiple leftover sub-group survivors
within a single dedup() call, because each round's `consumed` bookkeeping
permanently excluded a group's keeper from further matching even though
it was still fully present in the index. 15 pairwise-identical-embedding
entries collapsed to 2 survivors (merged: 13) instead of 1 (merged: 14).

It self-healed across repeated runAll() calls (background timer /
nightlyLearner), so this was never a permanent-data-loss bug, but a
single manual dedup() call under-converged relative to its documented
"collapse duplicates" contract.

Fix: loop pass 2 to a fixed point (re-scan until a full round produces
zero merges) instead of a single scan. Always terminates — entries
strictly decrease each round that merges anything. `groups` now counts
merge operations across all rounds, which can exceed the number of
underlying duplicate clusters when one needed more than one round;
documented in the method's doc comment.

Added a discriminating regression test reproducing the critic's exact
15-entry scenario; confirmed it fails against the pre-fix single-round
code (merged: 13) and passes against this fix (merged: 14, single
survivor). Full package suite: 472/473 passing (1 pre-existing,
unrelated, environmental failure, unchanged from before this commit).
tsc --noEmit clean.

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

* dream(memory): 2026-09-08 research gist — embedding near-dup consolidation

Full SOTA report: 5-role parallel research fan-out, ledger check with
GitHub-verified fates for the last 14 rows (3 merged since 09-03, one PR
now crossing the 14-day stale threshold for the first time this cycle),
competitor comparison (CrewAI/Mem0/Zep/LangMem/Qdrant/SemDedup), plugins
and automation scan findings, adversarial critique with a real bug found
and fixed, and witness stamp.

No gist-creation MCP tool available in this session (GitHub MCP tools
cover issues/PRs/repos, not gists) — committing to docs/dream-cycle/
instead, matching every dream-cycle night since 2026-08-14.

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

* dream(memory): #3231 update LEDGER.md for 2026-09-08

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

* dream(memory): #3231 empirical receipts for dedup() near-dup pass (response to review)

Response to PR #3232's INCONCLUSIVE review, which asked for a threshold
contract rather than a smoke fixture: a contamination-checked embedding
corpus with pinned model, precision/recall including hard negatives and
cross-namespace cases, baseline vs. candidate latency/memory at realistic
cardinality, and deterministic replay + unchanged exact-dedup behavior.

Real pinned-model corpus: attempted directly against both ONNX backends
this monorepo ships. Both fail for reasons unrelated to this candidate,
reproduced on an unmodified checkout before writing anything:
- @huggingface/transformers@4.2.0: transformers.node.mjs does
  `import { Tensor } from "onnxruntime-common"` against a CJS module — a
  real ESM/CJS interop break under this sandbox's Node version.
- @xenova/transformers@2.17.0: pulls in sharp@0.32.6, whose native
  binding (sharp-linux-x64.node) isn't present for this platform — the
  same failure independently reproduced moments later by this package's
  own nightlyLearner test, which falls back to mock embeddings.
Neither is fixable within this PR's scope without touching unrelated,
pre-existing native/module-resolution infrastructure. Documented in
detail at the top of the new test file rather than silently worked
around or omitted.

What's delivered instead, in
v3/@claude-flow/memory/src/consolidator-embedding-benchmark.test.ts:
- A deterministic synthetic corpus where pairwise cosine similarity is
  constructed EXACTLY via vectorAtSimilarity() (Gram-Schmidt against a
  random orthogonal vector), not measured after the fact — a rigorous
  instrument for threshold mechanics specifically, disclosed as not
  validating real-world semantic accuracy.
- Precision/recall/false-merge-rate across a 5-point threshold sweep,
  every point at a deliberate margin from every corpus similarity value
  after an exact-boundary collision (sweep value == corpus value) proved
  float32-jitter-sensitive while writing this — documented as a finding,
  not silently patched around.
- Namespace isolation: proved cross-namespace near-dup merging matches
  cross-namespace hash-exact merging exactly (both pre-existing,
  unscoped-by-namespace behavior — this candidate doesn't change it).
- Determinism: 3 independent runs, compared by deterministic entry KEY
  (not raw id, which is randomly generated per store() and was a second
  bug this file's own first draft had to fix).
- Latency/memory at N=5000 (matching this repo's own HNSW-benchmark
  convention): initially measured 9.8s for the candidate pass, which
  traced to an unset `ef` search parameter defaulting to efConstruction
  (200) — every dedup() search traversed a 200-candidate list for a
  duplicate-detection task that only needs very-close neighbors. Fixed
  by passing an explicit NEAR_DUP_SEARCH_EF=32 in consolidator.ts,
  re-measured at 3.7s (~2.7x), precision/recall unchanged (still exact).
  Real numbers logged in the test output either way, not asserted away.

Full package suite: 478/479 passing (472 pre-existing + 7 new; same 1
pre-existing unrelated environmental failure as every prior commit on
this branch). tsc --noEmit clean.

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

* dream(memory): #3231 add new benchmark file to CI test-ratchet baseline

CI on a2e99cf failed with "1 unexpected failing file(s): +
v3/@claude-flow/memory/src/consolidator-embedding-benchmark.test.ts".

Root-caused via the vitest JSON report artifact (test-results-ubuntu-latest,
downloaded and parsed directly, not guessed): a suite-level collection
failure, "Cannot find package '@claude-flow/security' imported from
'.../v3/@claude-flow/memory/src/agentdb-retrieval-guard.ts'" — the same
unbuilt-sibling-package gap already accepted in scripts/ci-test-baseline.txt
for 9 other @claude-flow/memory test files, including consolidator.test.ts
itself (line 95). Every file in this package fails identically in this CI
checkout state; my new file just wasn't in the baseline yet because it's
new. Not a regression this PR introduced — confirmed by the failure being
a pre-import-time module-resolution error, not a test assertion in the
new file's own logic (which passes locally in a checkout with @claude-flow/
memory's dependencies actually built: see the 6/6 local run in the prior
commit's message).

Fix: add the new file to the baseline, alongside its 9 memory-package
siblings already there, matching this repo's own established convention
for this exact gap rather than working around it some other way.

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

* dream(memory): #3231 baseline 2 more federation/gateway plugin test files

CI on 20afdc8 failed (run 34905889339) after main was rebased in — 3
unexpected failing files, all pre-existing, unrelated to this PR's diff:

  plugins/ruflo-chatgpt-federation/test/oauth.test.mjs
    Cannot find package 'jose'
  plugins/ruflo-chatgpt-federation/test/publisher.test.mjs
    Cannot find package 'nostr-tools/pure'
  plugins/ruflo-x-gateway/test/oauth.test.mjs
    Cannot find package 'jose'

Verified via the vitest.json artifact (downloaded and parsed, not
guessed): all three are plugin-local-dependency collection failures,
identical in kind to the already-baselined sibling
plugins/ruflo-x-gateway/test/gateway.test.mjs ("node:test suite with its
own deps... root vitest cannot resolve them"). Confirmed these are new
on main itself (main's own ci-test-baseline.txt at the rebase point does
not list them either) and unrelated to v3/@claude-flow/memory — nothing
this PR touches.

Re-verified locally: re-ran the full @claude-flow/memory suite against
the rebased main (496/497 passing, same 1 pre-existing unrelated
failure as always) and recomputed the ratchet's unexpected-set directly
against the CI run's vitest.json artifact — 0 unexpected after this
change.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
rUvandClaude authored and GitHub committed 2026-09-14 23:13:14 +00:00
1 parent 755b6720c5
commit e036e35dd7
6 files changed
+855 -41

No files matched your search

+1
View File
@@ -188,3 +188,4 @@ as a first-class, high-priority candidate, not a background note.
| 2026-09-09 | swarm | AgentPool.performHealthChecks() stamped lastHeartbeat=now on every "still healthy" tick, and updateAgentHeartbeat() (the only other legitimate way to advance it) has zero callers anywhere in v3/ — so the check's own tick was the sole thing keeping every pooled agent's heartbeat fresh, meaning timeSinceLastActivity could never exceed one interval and replaceUnhealthyAgent() could never fire for any agent, in any pool, ever; removed the self-stamp | #3242 | #3243 | yes | ACCEPT-scoped | 223/223 tests (+3 new), baseline-fails-2-of-3/candidate-passes-3-of-3 via stash isolation; adversarial critic confirmed independently but disclosed @claude-flow/swarm has no reachable path through the shipped @claude-flow/cli today (blast radius is the swarm package's own UnifiedSwarmCoordinator, not an active CLI hot path) | 008c96a0... | 08-24..09-02 backlog unchanged (see note above); #3169/#3184/#3221 merged since 09-05 |
| 2026-09-01 | security | `maxToolCallsPerTurn` was session-lifetime cumulative, never reset (disclosed bug in #3139); wall-clock sliding window fix, new `turnWindowMs` policy field (follow-up to #3139, stacked PR) | #3151 | #3152 | yes | ACCEPT | 30/30 new-suite tests, 52/56 broader MCP sweep (4 pre-existing unbuilt-sibling-package env failures, unrelated); independent critic CONFIRMED-WITH-CAVEATS, 2 minor findings fixed pre-commit | 7d93a482... | rebased onto main (post #3086/#3094/#3103/#3110/#3119/#3123/#3130/#3139 merges), awaiting fresh CI as of 2026-09-14 |
| 2026-09-02 | intelligence | `LearningBridge.consolidate()` reward-blind fix (hardcoded `1.0` → real insight confidence via `getByKey`; round-1 fix had a wrong id/key lookup — `entryId` is the caller-assigned `key`, not the backend's internal `entry.id` — caught by adversarial critic, corrected round 2) | #3159 | #3160 | yes | ACCEPT | 56/56→62/62 tests (candidate); 464/465 full package (1 pre-existing environmental failure); independent critic round 2 CONFIRMED | bead6402... | rebased onto main (post #3086/#3094/#3103/#3110/#3119/#3123/#3130/#3139/#3152 merges), awaiting fresh CI as of 2026-09-14 |
| 2026-09-08 | memory | `MemoryConsolidator.dedup()` deduplicated via byte-exact SHA-256 hash only, though every entry already carries a computed `.embedding` and `dedup()` already holds a handle to the adapter's incrementally-synced `HNSWIndex` (used only for `removePoint` bookkeeping, never queried for similarity) — added a second embedding-cosine near-duplicate pass (threshold 0.95, zero extra backend round-trips); independent adversarial critique found and this session fixed forward a real convergence bug (a duplicate cluster larger than the 8-entry search window split into multiple survivors in one call, self-healing across repeated `runAll()` but under-converging on a single call) | #3231 | #3232 | yes | ACCEPT | 472/473 full-suite green (1 pre-existing unrelated failure, unchanged); baseline-fails/candidate-passes via stash isolation for both the original fix and the convergence fix-forward; `tsc` clean | f5eaa461... | rebased onto main (post #3086/#3094/#3103/#3110/#3119/#3123/#3130/#3139/#3152/#3160 merges), awaiting fresh CI as of 2026-09-14 |
+124
View File
@@ -0,0 +1,124 @@
# Memory SOTA Report — 2026-09-08
TL;DR: Tonight's memory deep-dive confirmed and closed a gap flagged (but not selected) by last night's memory-scan finding: `MemoryConsolidator.dedup()` (`v3/@claude-flow/memory/src/consolidator.ts`) only ever collapsed duplicates via byte-exact SHA-256 content hashing, even though every stored entry already carries a computed `.embedding` and the method already holds a handle to the adapter's incrementally-synced `HNSWIndex` — used only for point-removal bookkeeping, never queried for similarity. This is the same "signal already computed, live path discards it for a worse proxy" bug class as three recently-fixed/merged findings (2026-09-03 MMR/#3169, 2026-08-25 PQ-dispatch/#3094, 2026-08-28 HybridBackend-weights/#3119). Five parallel research roles converged independently on the same candidate and patch shape. An orphaned second implementation, `MemoryDomainService.consolidate()` (~700 LOC, zero tests, unreachable from the package's public exports), already does real embedding-cosine grouping but was confirmed NOT directly reusable (it's bound to an injected `IMemoryRepository` requiring a real per-entry `searchByVector()` round-trip; the in-scope `HNSWIndex` handle is strictly cheaper). Fixed by adding an embedding near-duplicate pass directly in `consolidator.ts`, reusing the already-loaded embedding and index with zero extra backend round-trips. An independent adversarial critic then found and this session fixed a real, bounded convergence bug in the first cut of the patch (duplicate clusters wider than the 8-entry search window split into multiple survivors within one call) — documented in full below, including the reproduction and the fix.
## What's New in 2026
| Finding | Source | Confidence |
|---|---|---|
| SemDedup: embed → cluster → intra-cluster cosine-threshold removal, up to ~50% dedup with minimal quality loss | [NVIDIA NeMo Curator SemDedup docs](https://docs.nvidia.com/nemo/curator/curate-text/process-data/deduplication/semdedup) (implements arXiv:2303.09540) | A — reproducible open-source pipeline |
| CrewAI's native `Memory` class: explicit 0.85 cosine-similarity gate → LLM adjudicates keep/update/delete/insert | [CrewAI docs v1.15.2](https://docs.crewai.com/v1.15.2/en/concepts/memory) | B — official vendor docs |
| Mem0: embed-retrieve-similar → single LLM call decides ADD/UPDATE/DELETE/NOOP; LOCOMO 26% relative LLM-judge improvement over OpenAI Memory | arXiv:2504.19413 (ECAI 2025) | B — published, vendor-run benchmark |
| Zep/Graphiti: bi-temporal knowledge graph, entity resolution at write-time, contradictions close a validity window rather than delete; DMR 94.8% vs MemGPT 93.4% | arXiv:2501.13956 | B — published, vendor-run benchmark |
| TiMem Temporal Memory Tree: hierarchical "semantic-guided consolidation... without fine-tuning"; LoCoMo 75.30% with 52.2% reduction in recalled memory length | arXiv:2601.02845 (Jan 2026) | B — single arXiv preprint, concrete numbers, not independently reproduced |
| Qdrant maintainer, on record: no out-of-the-box memory-dedup feature is planned — "each dataset require[s] individual calibration" | [Qdrant GH discussion #3268](https://github.com/orgs/qdrant/discussions/3268) | B — primary-source maintainer statement |
| Mandol: agglomerative clustering + LLM cluster-synthesis for conversational agent memory, benchmarked against Mem0/Zep | arXiv:2606.29778 (Jun 2026) | C — single paper, not yet reproduced |
| Hindsight practitioner blog: "Most agent memory systems are explicit about retrieval and silent about consolidation policy. That silence is where production failures live." Four-lever framework (Importance/Merge/Decay/Eviction) | [hindsight.vectorize.io, 2026-05-21](https://hindsight.vectorize.io/blog/2026/05/21/agent-memory-consolidation) | C — vendor blog, self-reported numbers |
| Mem0's Apr-2026 rework reportedly drops UPDATE/DELETE for single-pass ADD-only accumulation — a direction *opposite* embedding-based consolidation, an open debate not settled practice | [mem0.ai blog, "State of AI Agent Memory 2026"](https://mem0.ai/blog/state-of-ai-agent-memory-2026) | C — vendor blog, unverified |
Dominant 2025-2026 direction (SemDedup, TiMem, Zep, CrewAI, Mem0) is cluster/embed-then-threshold dedup — exactly what the orphaned `MemoryDomainService` already does and what tonight's fix adds to the wired path. This independently corroborates last night's diagnosis rather than pointing Ruflo toward a different approach.
## Ruflo Current Capability
`MemoryConsolidator.dedup()` (`v3/@claude-flow/memory/src/consolidator.ts:140-210` pre-fix) buckets every entry by `sha256(content)` and merges byte-exact matches only — structurally blind to paraphrases/reformattings. It's invoked via `runAll()` (sweep → dedup → compact), reached by `UnifiedMemoryService`'s background timer (opt-in, `config.consolidator.autoRun`) and by the AgentDB `nightlyLearner` controller.
At the call site, `entries: Map<string, MemoryEntry>` is already fully resident with each entry's `.embedding?: Float32Array` (`types.ts:66`) inline, and `index: HNSWIndex` is already an in-scope, incrementally-synced handle exposing `.search(query, k, ef?)` — confirmed via direct read of `agentdb-adapter.ts` (index kept in sync on every write/delete) and `hnsw-index.ts` (`search()` is pure in-process graph traversal, no I/O). Fetching a similarity signal here costs zero extra backend round-trips.
Ruflo also ships a second, orphaned consolidation implementation: `MemoryDomainService.consolidate()` (`domain/services/memory-domain-service.ts:135-331`) does real embedding-cosine grouping (thresholds 0.95 dedupe / 0.8 merge) via `repository.searchByVector()` — but `repository: IMemoryRepository` is an injected interface requiring a genuine async round-trip *per entry* for a real backend, and the concrete backing repository (`HybridMemoryRepository`) does its own brute-force O(n) linear scan, not even HNSW-backed. The whole domain+application DDD chain dead-ends at its own barrel exports — confirmed via repo-wide import grep — and is explicitly excluded from the package's public surface (`index.test.ts:43` asserts `HybridMemoryRepository` must not be exported), consistent with a deliberate ADR-125-style quarantine rather than pure oversight. Zero test files exist for any file in this chain. **Conclusion, independently reached by two research roles**: wiring in the full domain-service architecture is not the smallest fix and was not selected; the in-scope `HNSWIndex` + already-resident `.embedding` is a strictly cheaper, more direct fix.
One more live gap surfaced while verifying the above (not previously documented): `dedup()`'s per-entry removal loop deletes IDs from `namespaceIndex`/`tagIndex` but — unlike `sweepExpired()`, which explicitly prunes now-empty `Set`s — never removes an emptied `Set` from its parent `Map`, so these maps grow unboundedly across many dedup cycles in a long-lived process. Not fixed tonight (separate, smaller, lower-value finding — see Additional Candidates below).
## Competitor Comparison
| System | Mechanism | Evidence Grade |
|---|---|---|
| LangGraph / LangMem SDK | LLM-based background consolidation (`BackgroundMemoryManager`), parallel LLM tool calls decide create/update/delete; no fixed numeric threshold | B |
| AutoGen | No native consolidation; delegates entirely to plugged-in Mem0/Zep | B |
| CrewAI | Embedding-cosine gate (0.85) → LLM adjudicates keep/update/delete/insert | B |
| Mem0 | Embed-retrieve-similar → LLM decides ADD/UPDATE/DELETE/NOOP | B |
| Zep / Graphiti | Bi-temporal knowledge-graph entity resolution + validity-window supersession | B |
| Qdrant / Weaviate / Milvus / LanceDB | **None shipped, deliberately** — maintainer on record: no out-of-the-box solution, "each dataset requires individual calibration" | B (deliberate-avoidance, not oversight) |
| NVIDIA NeMo Curator SemDedup | Embed + cluster dedup for *training* corpora — never adapted to live agent memory in anything found tonight (a genuine, undisputed gap) | B for SemDedup itself; the "agent-memory gap" is a negative result across the search |
Ruflo's hash-exact-only baseline sat below every framework-layer competitor here and at parity with the vector-DB layer's default of doing nothing. Tonight's fix — an explicit, documented, conservative cosine threshold (0.95) feeding the existing keeper-selection logic — moves Ruflo onto the same mechanism class as CrewAI's 0.85 gate, the most reproducible and simplest-to-benchmark option in this table (versus Mem0/Zep/LangMem's non-deterministic LLM-mediated judging, or Graphiti's much heavier graph-structural machinery). It does not reach Mem0/Zep's *contradiction resolution* (rewriting superseded content, not just dropping a near-duplicate) — a legitimate next rung, out of scope tonight.
## Hypothesis
> Given `MemoryConsolidator.dedup()` operating on entries that already carry a resident `.embedding: Float32Array` and an already-populated, incrementally-synced `HNSWIndex` handle it holds but never queries for similarity, when a near-duplicate detection pass is added that queries `index.search()` for each hash-pass-surviving entry with an embedding and merges any group whose cosine similarity is at or above a configurable `similarityThreshold` (default 0.95, matching the orphaned domain-service's own constant) using the existing keeper-selection strategies, then `dedup()` should catch semantic near-duplicates (paraphrases/reformattings) that byte-exact SHA-256 hashing structurally cannot, relative to baseline (hash-exact-only, near-duplicates never merged), subject to: (1) all pre-existing hash-exact-dedup tests and behavior remain unchanged; (2) the near-dup pass is strictly additive — no survivor is dropped without an above-threshold match, and hash-exact duplicates are still merged even without embeddings; (3) entries without `.embedding`, or an index configured with a non-cosine metric, fall back to exactly today's hash-only behavior; (4) a single `dedup()` call fully converges a duplicate cluster of any size to one survivor, not just clusters within the search-neighborhood width; (5) $0 evaluation cost, fully deterministic test-only evaluation, zero LLM calls.
Frozen before evaluation began. Condition (4) was added mid-session after the adversarial critic (STEP 10) reproduced a violation of it in the first cut of the implementation — see Evaluation below for why this is a legitimate hypothesis refinement (a genuinely new, previously-unstated condition the critique surfaced) rather than a post-hoc loosening: the fix was applied to satisfy the *stronger* condition, not to relax the hypothesis to match a weaker result.
## Benchmarks / Evaluation
**evaluated: accepted (ACCEPT).** Real evaluator: Vitest 4.1.8, deterministic, zero LLM calls, $0 cost.
**Candidate diff** (2 commits on `dream/2026-09-08-memory`): `v3/@claude-flow/memory/src/consolidator.ts` — `selectKeeper`/`mergeGroup` helpers factored out of the original hash-pass logic and reused by a new embedding near-duplicate pass, looped to a fixed point (see Adversarial Critique below for why). New `ConsolidatorOptions.similarityThreshold` (default 0.95; `>= 1` disables the pass entirely). `v3/@claude-flow/memory/src/consolidator.test.ts` — 7 new tests.
**Baseline vs. candidate, isolated via `git stash` of just the source file (test file kept):**
- First cut (single-round near-dup pass): baseline fails exactly the 2 tests requiring real cosine-merge behavior (`expected 0 to be 1`); candidate passes all 15. 13/15 pass on baseline (all pre-existing + 4 new guard/fallback tests that hold under both old and new code).
- After the convergence fix: re-ran the same isolation against the *first-cut* candidate specifically for the new 15-entry-cluster regression test — it fails (`expected 13 to be 14`, i.e. 2 leftover survivors instead of 1), reproducing the critic's exact finding; the fixed-point-loop version passes (16/16).
**Full `@claude-flow/memory` package suite**: **472/473 passing** (466 pre-existing + 7 new; 1 pre-existing, unrelated, environmental failure — `auto-memory-bridge.test.ts`, a chmod-based read-only-file test that can't enforce permissions in this root-owned sandbox, confirmed identical with and without this diff and matching the architecture-reviewer's independently-measured pre-change baseline of 465/466). `tsc --noEmit`: zero errors.
**Adversarial critique (STEP 10, independent subagent, no authoring context)** — verdict **CONFIRMED-WITH-CAVEATS**, full 14-point checklist run and answered with evidence (re-derived from the diff, not trusted from any prior claim):
- No test weakened (diff purely additive on the test file — confirmed via direct diff of the pre-existing 9 tests, byte-identical).
- No gold/expected data touched; no cherry-picking (ran the full suite itself: 471/472 on the first cut, matching what's claimed above pre-convergence-fix).
- No cost/latency/I/O regression — read `hnsw-index.ts`'s `search()`/`removePoint()` directly and confirmed pure in-memory graph traversal.
- Only one production call site (`controller-registry.ts:1021`, `nightlyLearner`) plus `MemoryService.getConsolidator()` — confirmed via repo-wide grep. **Disclosed, not hidden**: `getConsolidator()` does not override `similarityThreshold`, so the near-dup pass is **on by default at 0.95 for every existing `MemoryService` consumer**, not opt-in — a real behavior change for current users of `runAll()`/the background auto-run timer, not just new callers. Judged acceptable given the conservative default and the precedent set by 2026-09-03's MMR fix (also enabled by default), but explicitly flagged here rather than left implicit.
- No undocumented caching, no threshold gaming, no leaked expected answers, no security-boundary crossing (pure `Map`/`HNSWIndex` operations).
- Dimension-agnostic by inspection (no literal dimension anywhere in the new code path).
- **Found a real bug** (the convergence gap described above) — reported with a precise reproduction (15 pairwise-identical-embedding entries, `merged: 13` instead of 14, i.e. 2 survivors instead of 1) and fixed in this session per the report, with a new discriminating regression test added and confirmed to fail pre-fix / pass post-fix.
## Darwin Results
Real interface confirmed tonight (`ruvector harness darwin --help`): `darwin <config> --execute`, JSON-genome-driven, requires an explicit `--execute` flag to authorize candidate execution. **Skipped, deliberately, with a reason distinct from most recent nights' skips**: unlike the last several accepted candidates (binary invariant/wiring fixes with no continuous parameter), tonight's `similarityThreshold` *is* a genuine continuous tunable with a real quality/false-positive-rate tradeoff — in principle Darwin-eligible. It was not run tonight because doing so honestly would require a labeled near-duplicate benchmark corpus (true-duplicate vs. true-distinct pairs with ground truth) that does not exist in this repo, and building one is scoped out tonight per STEP 0.6's budget protection (research depth over new evaluation infrastructure). The shipped default (0.95) is not arbitrary — it matches the orphaned domain-service's own already-chosen constant and CrewAI's closely-adjacent 0.85 gate — but a future `memory` DEEP night building a small labeled near-dup corpus and running bounded Darwin evolution over this exact parameter is a concrete, well-scoped follow-up (see Recommended Next Steps).
## Flywheel Evidence
Real interface confirmed tonight (`ruvector harness flywheel --help`): `flywheel verify <bundle>` / `flywheel gate <evidence>`. No signed `@metaharness/flywheel` bundle exists for this candidate — the schema targets retrieval-policy candidates evaluated against an LLM-task corpus via paired bootstrap statistics; this is a deterministic code-correctness fix with no such corpus, consistent with every accepted dream-cycle night since 2026-08-18. Evidence retained as: 7 new deterministic tests (including the critic-driven convergence regression) + this gist + the linked issue + the two stash-isolated baseline-fails/candidate-passes comparisons (first-cut and convergence-fix) + the independent adversarial critique.
Classified: **OBSERVATION** (byte-exact-hash-only dedup, `HNSWIndex` handle held but never queried for similarity — confirmed via direct read + repo-wide grep by two independent research roles) / **MEASUREMENT** (baseline fails exactly the discriminating tests both before and after the convergence fix; full-suite pass count 472/473, unchanged failure identity from the pre-existing baseline) / **INFERENCE** (the orphaned `MemoryDomainService` is not directly reusable — its `IMemoryRepository` abstraction requires real per-entry round-trips; the in-scope `HNSWIndex` is strictly cheaper) / **DECISION** (ship the minimal in-file wiring fix, not the domain-service swap; fix the convergence bug forward rather than merely disclosing it, since a working fix was cheap and available) / **REJECTION** (Darwin evolution of `similarityThreshold`, deferred — no labeled corpus exists; not attempted with a fabricated one).
## Reward Hack Check
No standalone generic reward-hack CLI reachable this session (`weight-eft` resolves to a bin name but is a LoRA-distillation tool per its own doctor description, not a diff/benchmark scanner — consistent with every recent night's note). Manual checklist, independently re-verified by the adversarial critic with fresh eyes: no test weakened (purely additive, confirmed via diff); no gold-label leakage (no LLM/gold-answer path in this code); no cherry-picking (full package suite reported both ways, honestly, including the one pre-existing unrelated failure); no seed manipulation (fully deterministic PRNG-seeded embeddings throughout); zero cost (no model calls anywhere); no undocumented caching. The one caveat disclosed rather than hidden: the near-dup pass is on-by-default for existing `MemoryService` consumers (see Evaluation above) — a real behavior change, not a hidden side effect, but worth a reviewer's explicit attention.
## Security Review
Not the primary focus, but directly relevant to data-retention correctness: this pass *deletes* entries judged near-duplicate, so an adversary who could influence embedding generation to force a coincidental high-cosine collision with a legitimate memory could, in principle, cause that memory to be silently dropped during consolidation. Mitigations already in place: the default threshold (0.95) is conservative and requires near-exact vector alignment, not merely "related" content; the pass only runs during consolidation (background timer or `nightlyLearner`), not on the synchronous write/store path, so it isn't reachable from a single malicious write in real time; and it operates purely on already-trusted, already-embedded internal state — no new network, filesystem, or credential surface is introduced (confirmed line-by-line by the adversarial critic). No fix required tonight; flagging the threshold-as-attack-surface framing for future `security`-DEEP consideration if Ruflo's embedding-generation path itself is ever exposed to less-trusted input.
## Scan Findings: plugins
Re-verified last night's finding (`PluginIntegrityVerifier` unwired, registry `checksum` fields placeholder, `requireVerification` enforced at the registry-listing level only) — **confirmed still accurate**, with new concrete artifacts found tonight: (1) `commands/plugins.ts`'s `--verify` flag (`default: true`, advertised to users) is completely dead — the captured `verify` variable is never read again, so `--verify` and `--no-verify` are byte-identical no-ops; (2) six more `PluginStoreConfig` fields (`requireSecurityAudit`, `minTrustLevel`, `trustedAuthors`, `blockedPlugins`, `requirePermissionPrompt`, `allowedPermissions`) are declared, defaulted, and never read anywhere; (3) `trust-anchors.json` ships a placeholder all-zero public key, so even wiring the verifier in today would validate nothing real. Competitor check: npm's own provenance/Sigstore attestation and VS Code Marketplace's install-time repository-signature verification both show install-time artifact verification is the industry norm for a plugin marketplace shaped like Ruflo's (Grade A sources, both). **Explicit novelty verdict: this is a refinement of a direction already surfaced 3+ times in the ledger** (#2254 2026-06-01, #2630 2026-07-11, last night 2026-09-07) — not filed as a new top-level issue; recorded here as supporting evidence for a future `security`-DEEP night, per STEP 1.1.
## Scan Findings: automation
`dream-cycle-backlog-guard.yml` (referenced in the ledger's own notes as addressing the dream-cycle-PR-backlog visibility gap) **does not exist on `main`** — it exists only inside still-open, still-draft PR #3205 (created 2026-09-05, zero comments, untouched since creation — 3 days stale as of tonight). Read directly from the PR diff: it is a **dashboard, not a fix** — a daily cron job that lists aging `dream/*` PRs into a GitHub Step Summary with a `::warning::` annotation past a configurable age threshold; its own header comment says so explicitly ("does not fix the append mechanism... adds the missing visibility"). No other open PR or issue addresses the underlying structural problem (ledger rows only reach `main` via PR merge). Two smaller, new findings in the hooks/automation surface: `commands/hooks.ts`'s `teammate-idle` handler collapses every exception (not just "no MCP server") into a bare success response, discarding the real error; `hooks/src/workers/session-hook.ts`'s per-worker session-start scan loop reports failure but discards the actual error message. Neither implemented tonight (low severity, narrow blast radius, and this cycle's budget was allocated to the memory DEEP surface per STEP 0.6). Primary-source check on evidence-durability patterns for scheduled pipelines found no A-grade source solving this exact case; the closest real precedent is the general "decouple durable-write from review-gate" pattern (transactional-outbox-style), which would mean giving STEP 25 a durability path independent of PR-merge — a concrete design direction for a future `automation` DEEP night, not something implemented anywhere in this repo yet.
## Additional candidates surfaced but not selected tonight (STEP 3.2)
Scored under `0.25·Ruflo_fit + 0.20·testability + 0.20·measurability + 0.15·production_value + 0.10·novelty + 0.10·reviewability`; tonight's selected candidate scored 4.80 (deep-researcher rubric) / independently corroborated by the architecture-reviewer's separate patch-plan analysis:
1. **Prefer `accessCount`/`lastAccessedAt` over pure recency in keeper-selection** (score 4.20) — both fields are already resident on every entry and unused by `dedup()`'s keeper-selection logic; a frequently-accessed-but-slightly-older duplicate can currently lose to a barely-newer, never-accessed one. Same call site, ~10-15 lines. Changes default merge semantics, so deliberately not bundled with tonight's already-two-commit candidate.
2. **Fix `dedup()`'s missing empty-`Set` cleanup for `namespaceIndex`/`tagIndex`** (score 3.95) — `sweepExpired()` already prunes empty `Set`s; `dedup()` doesn't, an unbounded-growth gap in long-lived processes. Trivial (2-4 lines), but a separate conceptual change from tonight's candidate.
3. **Skip redundant per-entry `HNSWIndex.removePoint()` calls when `compactHnsw()` immediately rebuilds the index anyway** (score 3.75) — `runAll()` always follows sweep+dedup with a full rebuild from `entries`, making per-entry removal bookkeeping during those phases provably wasted (and occasionally O(n) per call via `removePoint()`'s entry-point-rescan branch). Needs a synthetic adversarial fixture to demonstrate cleanly; deferred.
4. **Delete or explicitly quarantine the orphaned DDD memory layer** (`MemoryDomainService`/`MemoryApplicationService`/`HybridMemoryRepository`, ~700 LOC, zero tests) (score 2.65) — dead code, but `index.test.ts:43`'s explicit "must not be exported" assertion suggests deliberate quarantine rather than pure oversight; deletion vs. eventual wiring is a design decision, not a bug fix, and needs an ADR rather than a silent removal.
5. **`QLearningRouter`-adjacent findings, `SONAManager`/`PersistentSonaCoordinator` orphans** — carried over from last night's `intelligence` surface, not re-scored tonight (different DEEP surface); still open per that night's own recommendation.
## SOTA Proof & Witness
| Field | Value |
|---|---|
| Session commit | `a295c68703158377a6ce827738bf8f13b94bd695` |
| Report SHA-256 (pre-witness content) | `24117f596be1304943be8705b08d1d157bf5904919df355774a027b523ff6909` |
| Witness stamp | `f5eaa46114b6b67e8dcf01e38d73c61aabb9400f8fe6b662cc191b1dc9787827` |
Verifier procedure: fetch `docs/dream-cycle/dream-gist-2026-09-08.md` from this branch, strip the witness table's filled values back to `PENDING`, SHA-256 the result, concatenate with the session commit above, SHA-256 again — result must equal the witness stamp.
## Recommended Next Steps
1. **Merge the linked draft PR** (human review required) — two commits: the initial wiring fix and a fix-forward addressing a real convergence bug an independent adversarial critic found and this session fixed before shipping, with a discriminating regression test proving both the original fix and the fix-forward.
2. **Build a labeled near-duplicate benchmark corpus** (true-duplicate vs. true-distinct embedding pairs with ground truth) so a future night can run real bounded Darwin evolution over `similarityThreshold` instead of shipping a precedent-matched-but-unvalidated default.
3. **Decide the fate of the orphaned DDD memory layer** (`MemoryDomainService` et al., candidate 4 above) — an explicit ADR (keep-quarantined vs. delete vs. eventually-wire-in) would resolve ~700 LOC of ambiguous status that two research roles both had to independently re-derive tonight.
4. **Security-DEEP follow-up on plugin supply-chain integrity** — now surfaced a 4th time (2026-06-01, 2026-07-11, 2026-09-07, tonight) with concrete new artifacts (dead `--verify` flag, placeholder trust anchor, 6 more dead config fields); per STEP 1.1, this direction should be either picked up as a small, reviewable wiring fix on a future `security` night, or explicitly deprioritized with a stated reason, rather than surfaced a 5th time with no decision.
5. **Merge backlog remains real**: as of tonight, `#3086/#3094/#3103/#3110/#3119/#3123/#3130` (2026-08-24 through 08-30) are still open/draft — `#3086` (2026-08-24) is now 15 days old with no meaningful follow-up beyond its own author comments, crossing this pipeline's own STALE threshold (>14 days, no follow-up) for the first time this cycle. `#3139/#3152/#3160` (08-31 through 09-02) are 6-8 days old, not yet stale. Three PRs have merged since 09-03 (`#3169`, `#3184`, `#3221`), showing the review process does work when engaged — the backlog-guard dashboard (PR #3205, still unmerged itself) would make this visible automatically but doesn't yet.