From 423cb00dbf4570b1071106e2e38d23290db009a0 Mon Sep 17 00:00:00 2001 From: alvinfei Date: Tue, 27 Oct 2026 23:33:19 +0800 Subject: [PATCH] fix(memhub): home memory count matches the memory page The home card counted chat memories only (list_memories without a scene), so it showed 55 while the memory page showed 94. It now reads memory_stats total (chat + working + coding experiences, facts not counted) and takes the newest memory from list_memories(scene="all"). Co-Authored-By: Claude Opus 5.5 (1M context) --- memhub/apps/core/src/one_memory/api.py | 16 ++++++- .../tests/test_memory_library_all_scenes.py | 47 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/memhub/apps/core/src/one_memory/api.py b/memhub/apps/core/src/one_memory/api.py index c4fe66e..97338be 100644 --- a/memhub/apps/core/src/one_memory/api.py +++ b/memhub/apps/core/src/one_memory/api.py @@ -2824,11 +2824,24 @@ def create_app( dependencies=protected, ) async def sdk_home_summary() -> SdkHomeSummary: + """The home card: the memory page's count and its newest memory. + + The count is `memory_stats` total (chat + working + coding + experiences, facts not counted), the same set the memory page lists + with scene="all". Without a scene `list_memories` counts chat only, + which showed 55 on the home card against 94 on the memory page. + """ try: client = await _sdk_client_handle(app).client() + stats = await asyncio.to_thread( + client.memory_stats, + user_id="local", + statuses=["active"], + ) raw = await asyncio.to_thread( client.list_memories, user_id="local", + scene="all", statuses=["active"], limit=1, offset=0, @@ -2838,8 +2851,9 @@ def create_app( _log_skipped_sdk_memories(skipped) except Exception as error: raise _sdk_http_error(error, "hy_memory_list_failed") from None + total = stats.get("total") if isinstance(stats, dict) else None return SdkHomeSummary( - memory_count=page.total, + memory_count=total if isinstance(total, int) and total >= 0 else page.total, latest=page.memories[0] if page.memories else None, observed_at=datetime.now(UTC), ) diff --git a/memhub/apps/core/tests/test_memory_library_all_scenes.py b/memhub/apps/core/tests/test_memory_library_all_scenes.py index cddc26c..439aa18 100644 --- a/memhub/apps/core/tests/test_memory_library_all_scenes.py +++ b/memhub/apps/core/tests/test_memory_library_all_scenes.py @@ -90,6 +90,7 @@ class FakeSdk: self.productivity = {} self.search_calls = [] self.list_calls = [] + self.stats_calls = [] self.get_calls = [] self.update_calls = [] self.delete_calls = [] @@ -227,6 +228,22 @@ class FakeSdk: "elapsed_ms": 1.0, } + def memory_stats(self, *, user_id, statuses=None): + self.stats_calls.append({"user_id": user_id, "statuses": statuses}) + experiences = [ + variant for (kind, variant, _id) in self.productivity if kind == "experience" + ] + facts = [variant for (kind, variant, _id) in self.productivity if kind == "fact"] + scenes = {"chat": {"count": len(self.chat)}} + for variant in ("coding", "working"): + scenes[variant] = {"count": experiences.count(variant), "facts": facts.count(variant)} + return { + "total": len(self.chat) + len(experiences), + "scenes": scenes, + "request_id": "stats", + "elapsed_ms": 1.0, + } + def get(self, memory_id, kind=None, variant=None): self.get_calls.append((memory_id, kind, variant)) match = self._matches(memory_id, kind, variant) @@ -647,3 +664,33 @@ def test_scene_filter_narrows_search_and_binds_the_window(tmp_path, monkeypatch) assert len(sdk.search_calls) == 2 run_with_sdk(tmp_path, monkeypatch, scenario) + + +@pytest.mark.fresh_sdk +def test_home_summary_counts_what_the_memory_page_lists(tmp_path, monkeypatch): + """Home showed 55 (chat only) while the memory page showed 94.""" + + async def scenario(app, sdk, client): + sdk.add_chat(chat_row("chat-1", "cursor chat")) + sdk.add_chat(chat_row("chat-2", "codebuddy chat", source="codebuddy")) + sdk.add_productivity("experience", experience_row("exp-w", "task w", "solution w")) + sdk.add_productivity( + "experience", experience_row("exp-c", "task c", "solution c", variant="coding") + ) + # Facts are not listed on the memory page, so they are not counted. + sdk.add_productivity("fact", fact_row("fact-1", "The pytest suite runs in CI")) + + response = await client.get("/api/v2/home-summary") + assert response.status_code == 200 + body = response.json() + library = (await client.get("/api/v2/memory-library")).json() + assert body["memory_count"] == library["total"] == 4 + assert sdk.stats_calls == [{"user_id": "local", "statuses": ["active"]}] + # The newest memory across scenes, not the newest chat memory. + home_list = sdk.list_calls[0] + assert home_list["scene"] == "all" + assert (home_list["offset"], home_list["limit"], home_list["order"]) == (0, 1, "desc") + assert body["latest"]["memory_id"] == "exp-w" + assert body["latest"]["scene"] == "working" + + run_with_sdk(tmp_path, monkeypatch, scenario)