From 3fff847fd3d37420927620723392a11660ed2f71 Mon Sep 17 00:00:00 2001 From: zhanghui Date: Sat, 10 Oct 2026 16:48:08 +0800 Subject: [PATCH 1/3] Say when a saved memory becomes searchable `add_memory(wait=true)` replied "the memory is now searchable", but extraction finishing and the search index catching up are two steps. Against a self-hosted EverOS the index lagged the write by about half a second: a search_memory call issued right after the reply found nothing and the same query a second later found the memory. Say so in the reply, the docstring and the README instead of promising immediate hits. Co-Authored-By: Claude Fable 5.1 --- README.md | 5 ++++- src/everos_mcp/server.py | 7 +++++-- tests/test_everos_mcp.py | 6 +++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ae78147..2761560 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ about it. | Tool | Purpose | |---|---| | `search_memory` | Relevance search over stored memories (optionally with the user profile) | -| `add_memory` | Store a durable fact or exchange; saves in the background by default (`wait=true` to block until it is searchable) | +| `add_memory` | Store a durable fact or exchange; saves in the background by default (`wait=true` to block until extraction has finished) | | `get_profile` | The synthesized user profile (facts, traits, preferences) | | `list_memories` | Chronological, paginated browsing | | `forget_session` | Delete what this connection stored (memories + cases distilled from its trajectories); the profile and learned skills are kept | @@ -107,6 +107,9 @@ permission prompt; `forget_session` is marked destructive. - Profile updates and case/skill distillation run in an offline pipeline and land seconds to minutes after the write. +- Extraction finishing and the memory becoming searchable are two steps: the search index + catches up a moment after the write (about a second on a self-hosted server), and + `list_memories` shows new entries sooner than `search_memory`. - A background save that fails is reported on the next tool result, so the model never silently believes something was remembered. - Trajectories need more than three tool-call rounds to pass the distillation quality diff --git a/src/everos_mcp/server.py b/src/everos_mcp/server.py index 3c3034c..c784d9c 100644 --- a/src/everos_mcp/server.py +++ b/src/everos_mcp/server.py @@ -212,7 +212,8 @@ async def add_memory( By default it returns at once and extraction runs in the background; the memory is searchable a few seconds later, and a failure is reported on a later tool result. Set wait=true only when you must search for it - right away — it blocks until extraction finishes. + right away — it blocks until extraction finishes; the index catches up + within about a second after that. """ conv = _conversation(ctx) if not user_message and not assistant_message: @@ -242,7 +243,9 @@ async def add_memory( if wait or conv.ephemeral or conv.saturated: status = await conv.store(messages, s.session_id) if status == "extracted": - return conv.reply("Stored and extracted; the memory is now searchable.") + return conv.reply( + "Stored and extracted; searchable once the index catches up, usually within a second." + ) return conv.reply( f"Stored, but extraction did not run (status {status!r}). The memory is " "NOT searchable yet — do not tell the user it was remembered." diff --git a/tests/test_everos_mcp.py b/tests/test_everos_mcp.py index 44c0a7f..9c81771 100644 --- a/tests/test_everos_mcp.py +++ b/tests/test_everos_mcp.py @@ -242,7 +242,7 @@ def test_add_memory_always_flushes_the_open_tail(tool_client): } ) reply = run(server.add_memory(user_message="x", wait=True)) - assert "now searchable" in reply + assert "Stored and extracted" in reply assert paths(seen) == ["add", "flush"] @@ -747,7 +747,7 @@ async def scenario(): return saved, forgot saved, forgot = run(scenario()) - assert "now searchable" in saved # waited instead of backgrounding + assert "Stored and extracted" in saved # waited instead of backgrounding assert "Nothing to forget" in forgot assert [r.url.path.rsplit("/", 1)[-1] for r in seen] == ["add", "flush"] @@ -838,7 +838,7 @@ def test_saturated_conversation_saves_in_the_foreground(tool_client): for _ in range(16): conv._pending.add(object()) # stand-ins for running saves reply = run(server.add_memory(user_message="x")) - assert "now searchable" in reply + assert "Stored and extracted" in reply def test_notices_are_capped(): From 53b5b02c18914275e4b008ef232eff6a5be93155 Mon Sep 17 00:00:00 2001 From: zhanghui Date: Mon, 12 Oct 2026 10:48:20 +0800 Subject: [PATCH 2/3] Use one conservative statement for when a memory is searchable Review on #3: the docstring still told the model to use wait=true when it must search right away, and the reply promised "within a second" from a single measurement while search_memory says "the last minute". Making wait=true block until the index has caught up was considered and rejected: how long indexing takes depends on server load, so any bound would be a guess. Instead every place now says the same thing: memories saved in the last minute may not be indexed yet, and list_memories shows them sooner. Tests also assert the old "now searchable" wording is gone. Co-Authored-By: Claude Fable 5.1 --- README.md | 6 +++--- src/everos_mcp/server.py | 13 +++++++------ tests/test_everos_mcp.py | 3 +++ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2761560..c62271a 100644 --- a/README.md +++ b/README.md @@ -107,9 +107,9 @@ permission prompt; `forget_session` is marked destructive. - Profile updates and case/skill distillation run in an offline pipeline and land seconds to minutes after the write. -- Extraction finishing and the memory becoming searchable are two steps: the search index - catches up a moment after the write (about a second on a self-hosted server), and - `list_memories` shows new entries sooner than `search_memory`. +- Extraction finishing and the memory becoming searchable are two steps: memories saved in + the last minute may not be indexed yet, and `list_memories` shows new entries sooner than + `search_memory`. - A background save that fails is reported on the next tool result, so the model never silently believes something was remembered. - Trajectories need more than three tool-call rounds to pass the distillation quality diff --git a/src/everos_mcp/server.py b/src/everos_mcp/server.py index c784d9c..827c6b2 100644 --- a/src/everos_mcp/server.py +++ b/src/everos_mcp/server.py @@ -209,11 +209,11 @@ async def add_memory( reports in Chinese"), a habit, a decision, or a long-term goal. Pass what the user said as user_message and/or your reply as assistant_message. - By default it returns at once and extraction runs in the background; - the memory is searchable a few seconds later, and a failure is reported - on a later tool result. Set wait=true only when you must search for it - right away — it blocks until extraction finishes; the index catches up - within about a second after that. + By default it returns at once and extraction runs in the background, + and a failure is reported on a later tool result. Set wait=true to block + until extraction has finished. Either way the search index catches up + afterwards: memories saved in the last minute may not be indexed yet, and + list_memories shows them sooner than search_memory. """ conv = _conversation(ctx) if not user_message and not assistant_message: @@ -244,7 +244,8 @@ async def add_memory( status = await conv.store(messages, s.session_id) if status == "extracted": return conv.reply( - "Stored and extracted; searchable once the index catches up, usually within a second." + "Stored and extracted. Memories saved in the last minute may not be " + "indexed yet; list_memories shows them sooner." ) return conv.reply( f"Stored, but extraction did not run (status {status!r}). The memory is " diff --git a/tests/test_everos_mcp.py b/tests/test_everos_mcp.py index 9c81771..b6d4373 100644 --- a/tests/test_everos_mcp.py +++ b/tests/test_everos_mcp.py @@ -243,6 +243,7 @@ def test_add_memory_always_flushes_the_open_tail(tool_client): ) reply = run(server.add_memory(user_message="x", wait=True)) assert "Stored and extracted" in reply + assert "now searchable" not in reply assert paths(seen) == ["add", "flush"] @@ -748,6 +749,7 @@ async def scenario(): saved, forgot = run(scenario()) assert "Stored and extracted" in saved # waited instead of backgrounding + assert "now searchable" not in saved assert "Nothing to forget" in forgot assert [r.url.path.rsplit("/", 1)[-1] for r in seen] == ["add", "flush"] @@ -839,6 +841,7 @@ def test_saturated_conversation_saves_in_the_foreground(tool_client): conv._pending.add(object()) # stand-ins for running saves reply = run(server.add_memory(user_message="x")) assert "Stored and extracted" in reply + assert "now searchable" not in reply def test_notices_are_capped(): From 9677d0dc9ee11da7943937a952185561f681f928 Mon Sep 17 00:00:00 2001 From: Dani Date: Sun, 11 Oct 2026 23:02:07 -0400 Subject: [PATCH 3/3] Stop calling an extracted memory searchable in Conversation.store Co-Authored-By: Claude Opus 5.5 (1M context) --- src/everos_mcp/conversation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/everos_mcp/conversation.py b/src/everos_mcp/conversation.py index c787ec4..fd7f430 100644 --- a/src/everos_mcp/conversation.py +++ b/src/everos_mcp/conversation.py @@ -108,7 +108,8 @@ def reply(self, text: str) -> str: async def store(self, messages: list[dict[str, Any]], session_id: str) -> str | None: """Write messages, then make sure extraction ran. - Returns the final status; "extracted" means searchable.""" + Returns the final status; "extracted" means extraction finished, not that + the memory is searchable yet (the index catches up afterwards).""" async with self._lock: return await self._add_and_flush(messages, session_id)