Skip to content

feat: add cluster engine for semantic message grouping - #1240

Merged
sscargal merged 13 commits into
mainfrom
cluster-engine
Apr 13, 2026
Merged

sscargal merged 13 commits into
mainfrom
cluster-engine

Conversation

@o-love

@o-love o-love commented Mar 18, 2026 •

Copy link
Copy Markdown
Contributor

Purpose of the change

Introduce the cluster engine — a self-contained module for grouping incoming messages into semantic clusters by similarity before ingestion.

Description

  • ClusterManager: assigns messages to clusters based on cosine similarity, creates new clusters when no match is found
  • ClusterSplitter: detects topic boundaries within clusters using reranker scores and splits them
  • ClusterStore abstraction with ClusterStoreSQLAlchemy and InMemoryClusterStore implementations
  • All new files — no modifications to existing code

Stack: PR 2/4 — main ← storage-interface-refactor ← cluster-engine ← cluster-integration ← eval-harness

Depends on #1239

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

  • Unit Test

  • test_cluster_manager.py — cluster assignment, creation, and lifecycle

  • test_cluster_splitter.py — boundary detection and split logic

  • test_cluster_state_storage.py — SQLAlchemy and in-memory store CRUD operations

Test Results: All cluster engine tests pass locally.

Checklist

  • My code follows the style guidelines of this project (See STYLE_GUIDE.md)
  • I have performed a self-review of my own code
  • I have commented my code
  • My changes generate no new warnings
  • I have added unit tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have checked my code and corrected any misspellings

Maintainer Checklist

  • Confirmed all checks passed
  • Contributor has signed the commit(s)
  • Reviewed the code
  • Run, Tested, and Verified the change(s) work as expected

Screenshots/Gifs

N/A

Further comments

This PR is entirely additive (10 new files, 2170 lines). The cluster engine is not yet wired into the ingestion pipeline — that happens in PR 3/4 (#1241).

@o-love
o-love force-pushed the cluster-engine branch 4 times, most recently from 6a73322 to 1a5e28b Compare March 18, 2026 20:43
@o-love
o-love requested a review from Copilot March 24, 2026 21:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “cluster engine” module to the server’s semantic memory subsystem, enabling grouping of incoming messages into semantic clusters (and optionally splitting clusters using reranker-guided boundary detection) prior to ingestion. This PR is fully additive and introduces both production code and unit tests for clustering, splitting, and state persistence.

Changes:

  • Introduce ClusterManager and supporting state/params models for similarity-based cluster assignment.
  • Add RerankerClusterSplitter (+ helpers) for heuristic gating + reranker-driven split decisions, including split replay via recorded decisions.
  • Add ClusterStateStorage abstraction with SQLAlchemy and in-memory implementations + storage round-trip tests.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/server/src/memmachine_server/semantic_memory/cluster_manager.py Core clustering state + assignment logic (cosine similarity + time-gap gating).
packages/server/src/memmachine_server/semantic_memory/cluster_splitter.py Split gating, reranker scoring, split application, and split replay bookkeeping.
packages/server/src/memmachine_server/semantic_memory/cluster_store/cluster_store.py Protocol defining cluster state persistence interface.
packages/server/src/memmachine_server/semantic_memory/cluster_store/cluster_store_sqlalchemy.py SQLAlchemy-backed persistence of cluster state across multiple tables.
packages/server/src/memmachine_server/semantic_memory/cluster_store/in_memory_cluster_store.py In-memory state persistence for tests/local usage.
packages/server/src/memmachine_server/semantic_memory/cluster_store/init.py Package marker/docstring.
packages/server/server_tests/memmachine_server/semantic_memory/test_cluster_manager.py Unit tests for cluster assignment and lifecycle basics.
packages/server/server_tests/memmachine_server/semantic_memory/test_cluster_splitter.py Unit tests for split gating, split mechanics, replay behavior, and error handling.
packages/server/server_tests/memmachine_server/semantic_memory/cluster_store/test_cluster_state_storage.py Storage round-trip tests for SQLAlchemy (sqlite/pg) and in-memory backends.
packages/server/server_tests/memmachine_server/semantic_memory/cluster_store/init.py Test package marker/docstring.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@o-love
o-love force-pushed the storage-interface-refactor branch from 7a90993 to 79f9f4e Compare March 24, 2026 22:39
o-love added 5 commits March 25, 2026 11:39
…ing)

Widen parameter types to Sequence/Mapping for covariance, narrow return
types where mutation is needed (MutableMapping). Convert async def methods
returning iterators to def returning AsyncIterator. Add delete_history_set
to the storage interface.
Adapt all callers of storage methods that now return AsyncIterator:
- SemanticService propagates AsyncIterator for search, get_set_features,
  list_set_id_starts_with
- SemanticSessionManager propagates AsyncIterator to the boundary
- MemMachine collects AsyncIterator into lists at the API boundary
- IngestionService collects internally where lists are needed
- Add merge_async_iterators utility for parallel iterator merging
- Update test files to collect from AsyncIterator
- Fix ruff import sorting in semantic_memory.py and test_background
- Fix ty invalid-assignment: use Sequence[SemanticFeature] for
  consolidation sections, convert to list at llm boundary
- Fix ty invalid-argument-type: revert Protocol widening in session
  manager where config_store hasn't been updated yet, convert at
  call sites instead
- Fix ruff formatting in test_semantic_ingestion.py
The router constructs response models with concrete list/dict fields
but the widened model types now expose Sequence/Mapping. Convert at
the serialization boundary.
@o-love
o-love force-pushed the storage-interface-refactor branch from 79f9f4e to c8848ca Compare March 25, 2026 18:40
@sscargal sscargal added this to the v0.3.4 milestone Apr 6, 2026
@sscargal
sscargal requested review from edwinyyyu and sscargal April 6, 2026 21:37
o-love added 3 commits April 8, 2026 11:25
Introduce ClusterManager, ClusterSplitter, and ClusterStore abstraction
with SQLAlchemy and in-memory implementations. Clusters group incoming
messages by semantic similarity before ingestion.
Base automatically changed from storage-interface-refactor to main April 9, 2026 21:47

@sscargal sscargal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@sscargal
sscargal merged commit 0ea3eab into main Apr 13, 2026
43 checks passed
@sscargal
sscargal deleted the cluster-engine branch April 13, 2026 20:11
edwinyyyu pushed a commit to edwinyyyu/MemMachine that referenced this pull request Apr 20, 2026
* refactor: modernize storage interface types (list→Sequence, dict→Mapping)

Widen parameter types to Sequence/Mapping for covariance, narrow return
types where mutation is needed (MutableMapping). Convert async def methods
returning iterators to def returning AsyncIterator. Add delete_history_set
to the storage interface.

* refactor: propagate AsyncIterator through semantic memory module

Adapt all callers of storage methods that now return AsyncIterator:
- SemanticService propagates AsyncIterator for search, get_set_features,
  list_set_id_starts_with
- SemanticSessionManager propagates AsyncIterator to the boundary
- MemMachine collects AsyncIterator into lists at the API boundary
- IngestionService collects internally where lists are needed
- Add merge_async_iterators utility for parallel iterator merging
- Update test files to collect from AsyncIterator

* fix: resolve ruff and ty lint errors in storage interface refactor

- Fix ruff import sorting in semantic_memory.py and test_background
- Fix ty invalid-assignment: use Sequence[SemanticFeature] for
  consolidation sections, convert to list at llm boundary
- Fix ty invalid-argument-type: revert Protocol widening in session
  manager where config_store hasn't been updated yet, convert at
  call sites instead
- Fix ruff formatting in test_semantic_ingestion.py

* fix: convert Sequence/Mapping to list/dict at API response boundary

The router constructs response models with concrete list/dict fields
but the widened model types now expose Sequence/Mapping. Convert at
the serialization boundary.

* fix: ruff format router.py ternary expressions

* Update packages/server/src/memmachine_server/semantic_memory/semantic_memory.py

Co-authored-by: Copilot <[email protected]>
Signed-off-by: Shu Wang <[email protected]>

* Update packages/server/src/memmachine_server/common/utils.py

Co-authored-by: Copilot <[email protected]>
Signed-off-by: Shu Wang <[email protected]>

* Fix logger.debug formatting for add_messages method

Signed-off-by: Shu Wang <[email protected]>

* fix: ruff format long line in merge_async_iterators

* Fix semantic ingestion type-check test

* feat: add cluster engine for semantic message grouping

Introduce ClusterManager, ClusterSplitter, and ClusterStore abstraction
with SQLAlchemy and in-memory implementations. Clusters group incoming
messages by semantic similarity before ingestion.

---------

Signed-off-by: Shu Wang <[email protected]>
Co-authored-by: Shu Wang <[email protected]>
Co-authored-by: Copilot <[email protected]>
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.

5 participants