from __future__ import annotations import asyncio import sys import types import unittest from types import SimpleNamespace from supermemory_livekit import ConfigurationError, InputParams, SupermemoryLiveKit from supermemory_livekit import memory as memory_module from supermemory_livekit.identifiers import to_identifier from supermemory_livekit.utils import format_tool_results, is_injected_memory, wrap_memory class FakeProfile: def __init__(self, static=None, dynamic=None, results=None, delay=0): self.static = static self.dynamic = dynamic self.results = results or [] self.delay = delay self.calls = [] async def __call__(self, **kwargs): self.calls.append(kwargs) if self.delay: await asyncio.sleep(self.delay) return SimpleNamespace( profile=None if self.static is None and self.dynamic is None else SimpleNamespace(static=self.static or [], dynamic=self.dynamic or []), search_results=SimpleNamespace(results=self.results), ) class FakeSearch: def __init__(self): self.calls = [] async def memories(self, **kwargs): self.calls.append(kwargs) return SimpleNamespace( results=[ SimpleNamespace(id="mem_1", memory="Likes short answers"), SimpleNamespace(id="chunk_1", chunk="raw transcript", memory=None), ] ) class FakeMemories: def __init__(self): self.calls = [] async def forget(self, **kwargs): self.calls.append(kwargs) class FakeClient: def __init__(self, profile=None): self.profile = profile or FakeProfile(static=[], dynamic=[]) self.search = FakeSearch() self.memories = FakeMemories() self.added = [] async def add(self, **kwargs): self.added.append(kwargs) return SimpleNamespace(id="doc_1") class ChatCtx: def __init__(self): self.items = [] def add_message(self, *, role, content, created_at=None): item = SimpleNamespace( id=f"item_{len(self.items)}", role=role, content=content, text_content=content, created_at=created_at, ) self.items.append(item) return item def remove(self, item_id): self.items = [item for item in self.items if item.id != item_id] class Session: def __init__(self): self.handlers = {} def on(self, event, callback): self.handlers.setdefault(event, []).append(callback) def off(self, event, callback): self.handlers[event] = [cb for cb in self.handlers.get(event, []) if cb is not callback] def emit(self, event, payload): for callback in list(self.handlers.get(event, [])): callback(payload) def memory(client, **kwargs): return SupermemoryLiveKit(api_key="sm_test", client=client, **kwargs) class MemoryTests(unittest.TestCase): def test_missing_api_key(self): with self.assertRaises(ConfigurationError): SupermemoryLiveKit(api_key="", client=None) def test_empty_container_tag(self): with self.assertRaises(ConfigurationError): memory(FakeClient(), container_tag=" ") def test_empty_profile_does_not_inject(self): client = FakeClient(FakeProfile(static=[], dynamic=[])) plugin = memory(client, container_tag="user_1") ctx = ChatCtx() added = asyncio.run(plugin.on_user_turn_completed(ctx, SimpleNamespace(text_content="hi", created_at=10))) self.assertIsNone(added) self.assertEqual(ctx.items, []) self.assertEqual(client.profile.calls[0]["q"], "hi") def test_null_profile_is_empty(self): client = FakeClient(FakeProfile()) plugin = memory(client, container_tag="user_1") ctx = ChatCtx() asyncio.run(plugin.preload(ctx)) self.assertEqual(ctx.items, []) def test_recall_inserts_before_user_timestamp_and_replaces_previous(self): client = FakeClient( FakeProfile( static=["Name is Ada"], dynamic=["Working on voice agents"], results=[SimpleNamespace(memory="Prefers concise replies", updatedAt="2026-09-20T00:00:00Z")], ) ) plugin = memory(client, container_tag="user_1") ctx = ChatCtx() ctx.add_message(role="assistant", content=wrap_memory("stale"), created_at=1) user = SimpleNamespace(text_content="what do you remember?", created_at=20.0) asyncio.run(plugin.on_user_turn_completed(ctx, user)) self.assertEqual(len(ctx.items), 1) injected = ctx.items[0] self.assertEqual(injected.role, "assistant") self.assertAlmostEqual(injected.created_at, 19.999) self.assertIn("Name is Ada", injected.content) self.assertIn("Prefers concise replies", injected.content) self.assertNotIn("stale", injected.content) self.assertTrue(is_injected_memory(injected.content)) self.assertEqual(client.profile.calls[0]["q"], "what do you remember?") self.assertEqual(client.profile.calls[0]["container_tag"], "user_1") def test_query_mode_skips_profile_sections(self): client = FakeClient( FakeProfile(static=["Name is Ada"], results=[SimpleNamespace(memory="Lives in Lisbon")]) ) plugin = memory( client, container_tag="user_1", params=InputParams(mode="query"), ) ctx = ChatCtx() asyncio.run(plugin.on_user_turn_completed(ctx, SimpleNamespace(text_content="where?", created_at=2))) self.assertIn("Lives in Lisbon", ctx.items[0].content) self.assertNotIn("Name is Ada", ctx.items[0].content) def test_enrich_injects_when_the_user_message_is_already_present(self): client = FakeClient(FakeProfile(static=["The secret word is kelp"])) plugin = memory(client, container_tag="user_1") ctx = ChatCtx() ctx.add_message(role="user", content="what is the secret word?", created_at=8.0) asyncio.run(plugin.enrich(ctx)) self.assertEqual(len(ctx.items), 2) self.assertIn("kelp", ctx.items[1].content) self.assertAlmostEqual(ctx.items[1].created_at, 7.999) asyncio.run(plugin.enrich(ctx)) self.assertEqual(len(client.profile.calls), 1) self.assertEqual(len(ctx.items), 2) def test_enrich_replaces_preloaded_profile_each_turn(self): client = FakeClient( FakeProfile(static=["Name is Ada"], results=[SimpleNamespace(memory="Owns a dog named Biscuit")]) ) plugin = memory(client, container_tag="user_1") ctx = ChatCtx() asyncio.run(plugin.preload(ctx)) ctx.add_message(role="user", content="what is my dog called?", created_at=30.0) asyncio.run(plugin.enrich(ctx)) injected = [item for item in ctx.items if is_injected_memory(item.content)] self.assertEqual(len(injected), 1) self.assertIn("Biscuit", injected[0].content) self.assertEqual(client.profile.calls[-1]["q"], "what is my dog called?") def test_one_recall_per_user_message(self): client = FakeClient(FakeProfile(static=[], dynamic=[], delay=0.05)) plugin = memory(client, container_tag="user_1") async def run(): first, second = ChatCtx(), ChatCtx() for ctx in (first, second): ctx.add_message(role="user", content="hi, first time calling", created_at=5.0) # A cancelled attempt must not cancel the recall a follow-up of the same turn reuses. preemptive = asyncio.ensure_future(plugin.enrich(first)) await asyncio.sleep(0.01) preemptive.cancel() await plugin.enrich(second) await plugin.enrich(second) asyncio.run(run()) self.assertEqual(len(client.profile.calls), 1) def test_same_words_in_a_later_turn_recall_again(self): client = FakeClient(FakeProfile(static=["Name is Ada"])) plugin = memory(client, container_tag="user_1") ctx = ChatCtx() ctx.add_message(role="user", content="what's my name?", created_at=1.0) asyncio.run(plugin.enrich(ctx)) ctx.add_message(role="user", content="what's my name?", created_at=2.0) asyncio.run(plugin.enrich(ctx)) self.assertEqual(len(client.profile.calls), 2) def test_failed_recall_never_shows_another_callers_memory(self): class ScopedProfile: def __init__(self): self.calls = [] async def __call__(self, **kwargs): self.calls.append(kwargs) if kwargs["container_tag"] == "bob": raise RuntimeError("down") return SimpleNamespace( profile=SimpleNamespace(static=["Alice's door code is 4471"], dynamic=[]), search_results=SimpleNamespace(results=[]), ) plugin = memory(FakeClient(ScopedProfile()), container_tag="alice") ctx = ChatCtx() asyncio.run(plugin.preload(ctx)) plugin.bind(container_tag="bob") ctx.add_message(role="user", content="what's my door code?", created_at=50.0) asyncio.run(plugin.enrich(ctx)) self.assertFalse(any("4471" in item.content for item in ctx.items)) def test_slow_recall_falls_back_to_the_callers_call_start_profile(self): profile = FakeProfile(static=["Name is Ada"]) plugin = memory(FakeClient(profile), container_tag="user_1", params=InputParams(recall_timeout=0.01)) ctx = ChatCtx() asyncio.run(plugin.preload(ctx)) profile.delay = 0.05 ctx.add_message(role="user", content="what's my name?", created_at=50.0) asyncio.run(plugin.enrich(ctx)) injected = [item for item in ctx.items if is_injected_memory(item.content)] self.assertEqual(len(injected), 1) self.assertIn("Name is Ada", injected[0].content) def test_new_user_message_recalls_again(self): client = FakeClient(FakeProfile(static=["Name is Ada"])) plugin = memory(client, container_tag="user_1") ctx = ChatCtx() ctx.add_message(role="user", content="hello", created_at=1.0) asyncio.run(plugin.enrich(ctx)) ctx.add_message(role="user", content="what's my name?", created_at=2.0) asyncio.run(plugin.enrich(ctx)) self.assertEqual([call["q"] for call in client.profile.calls], ["hello", "what's my name?"]) self.assertEqual(sum(is_injected_memory(item.content) for item in ctx.items), 1) def test_timeout_and_errors_do_not_fail_the_turn(self): slow = FakeClient(FakeProfile(static=["Name is Ada"], delay=0.05)) plugin = memory(slow, container_tag="user_1", params=InputParams(recall_timeout=0.01)) ctx = ChatCtx() asyncio.run(plugin.on_user_turn_completed(ctx, SimpleNamespace(text_content="hi", created_at=1))) self.assertEqual(ctx.items, []) class Boom: async def __call__(self, **kwargs): raise RuntimeError("down") broken = FakeClient() broken.profile = Boom() plugin = memory(broken, container_tag="user_1") asyncio.run(plugin.on_user_turn_completed(ctx, SimpleNamespace(text_content="hi", created_at=1))) self.assertEqual(ctx.items, []) def test_participant_attribute_beats_identity_and_sanitizes(self): plugin = memory(FakeClient()) plugin.bind( participant=SimpleNamespace( identity="sip:+15551212", attributes={"supermemory_container_tag": "user_42"}, ) ) self.assertEqual(plugin.container_tag, "user_42") plugin.bind(participant=SimpleNamespace(identity="user@example.com", attributes={})) self.assertNotEqual(plugin.container_tag, "user@example.com") self.assertEqual(plugin.container_tag, to_identifier("user@example.com")) self.assertLessEqual(len(plugin.container_tag), 100) def test_explicit_tag_beats_participant(self): plugin = memory(FakeClient()) plugin.bind( container_tag="user_9", participant=SimpleNamespace(identity="other", attributes={}), ) self.assertEqual(plugin.container_tag, "user_9") def test_capture_groups_new_turns_and_skips_injection(self): client = FakeClient() plugin = memory(client, container_tag="user_1", session_id="room 1") session = Session() plugin.attach(session) session.emit( "conversation_item_added", SimpleNamespace(item=SimpleNamespace(id="u1", role="user", text_content="I like tea")), ) session.emit( "conversation_item_added", SimpleNamespace( item=SimpleNamespace(id="a0", role="assistant", text_content=wrap_memory("secret")) ), ) session.emit( "conversation_item_added", SimpleNamespace(item=SimpleNamespace(id="a1", role="assistant", text_content="Noted.")), ) session.emit( "conversation_item_added", SimpleNamespace(item=SimpleNamespace(id="a1", role="assistant", text_content="Noted.")), ) asyncio.run(plugin.aclose()) self.assertEqual(len(client.added), 1) stored = client.added[0] self.assertEqual(stored["content"], "User: I like tea\nAssistant: Noted.") self.assertEqual(stored["container_tag"], "user_1") self.assertEqual(stored["custom_id"], to_identifier("lk-room 1")) self.assertNotIn("secret", stored["content"]) self.assertEqual(stored["metadata"]["source"], "livekit") self.assertEqual(stored["dreaming"], "dynamic") def test_close_flushes_a_trailing_user_turn(self): client = FakeClient() plugin = memory(client, container_tag="user_1", session_id="room-2") session = Session() async def scenario(): plugin.attach(session) session.emit( "conversation_item_added", SimpleNamespace(item=SimpleNamespace(id="u1", role="user", text_content="still here")), ) session.emit("close", SimpleNamespace()) await asyncio.sleep(0) asyncio.run(scenario()) self.assertEqual(client.added[0]["content"], "User: still here") def test_capture_never_does_not_store(self): client = FakeClient() plugin = memory( client, container_tag="user_1", params=InputParams(capture="never"), ) session = Session() plugin.attach(session) session.emit( "conversation_item_added", SimpleNamespace(item=SimpleNamespace(id="u1", role="user", text_content="hi")), ) session.emit( "conversation_item_added", SimpleNamespace(item=SimpleNamespace(id="a1", role="assistant", text_content="hello")), ) asyncio.run(plugin.aclose()) self.assertEqual(client.added, []) def test_store_failure_is_retried_on_close(self): client = FakeClient() calls = {"n": 0} async def flaky(**kwargs): calls["n"] += 1 if calls["n"] == 1: raise RuntimeError("blip") client.added.append(kwargs) client.add = flaky plugin = memory(client, container_tag="user_1", session_id="room-3") session = Session() plugin.attach(session) session.emit( "conversation_item_added", SimpleNamespace(item=SimpleNamespace(id="u1", role="user", text_content="hi")), ) session.emit( "conversation_item_added", SimpleNamespace(item=SimpleNamespace(id="a1", role="assistant", text_content="hello")), ) asyncio.run(asyncio.sleep(0)) asyncio.run(plugin.aclose()) self.assertEqual(calls["n"], 2) self.assertIn("User: hi", client.added[0]["content"]) def test_tools_search_remember_forget(self): client = FakeClient() plugin = memory(client, container_tag="user_1") found = asyncio.run(plugin.search("tea")) saved = asyncio.run(plugin.remember("Likes tea")) forgotten = asyncio.run(plugin.forget(memory_id="mem_1")) missing = asyncio.run(plugin.forget()) self.assertIn("Likes short answers (id: mem_1)", found) self.assertIn("raw transcript", found) self.assertNotIn("chunk_1", found) self.assertEqual(saved, "Saved.") self.assertEqual(client.added[0]["metadata"]["kind"], "explicit") self.assertEqual(client.added[0]["dreaming"], "instant") self.assertNotIn("custom_id", client.added[0]) self.assertEqual(forgotten, "Forgotten.") self.assertEqual(client.memories.calls[0]["id"], "mem_1") self.assertEqual(client.memories.calls[0]["container_tag"], "user_1") self.assertIn("memory id", missing) def test_remember_falls_back_when_instant_is_not_available(self): class NoBalance(Exception): status_code = 402 client = FakeClient() original = client.add async def add(**kwargs): if kwargs.get("dreaming") == "instant": raise NoBalance("insufficient_balance") return await original(**kwargs) client.add = add plugin = memory(client, container_tag="user_1") self.assertEqual(asyncio.run(plugin.remember("Likes tea")), "Saved.") self.assertEqual(len(client.added), 1) self.assertNotIn("dreaming", client.added[0]) def test_rebind_keeps_captured_turns_in_the_callers_scope(self): client = FakeClient() plugin = memory(client, session_id="room-1") session = Session() plugin.attach(session) def say(item_id, role, text): item = SimpleNamespace(id=item_id, role=role, text_content=text) session.emit("conversation_item_added", SimpleNamespace(item=item)) say("u0", "user", "hello before we know who you are") plugin.bind(container_tag="alice") say("u1", "user", "Alice secret: door code 4471") plugin.bind(container_tag="bob") say("u2", "user", "Bob here") asyncio.run(plugin.aclose()) stored = {call["container_tag"]: call["content"] for call in client.added} self.assertEqual(set(stored), {"alice", "bob"}) self.assertIn("4471", stored["alice"]) self.assertIn("hello before we know who you are", stored["alice"]) self.assertNotIn("4471", stored["bob"]) def test_hung_store_times_out_and_keeps_the_turns(self): client = FakeClient() async def hang(**kwargs): await asyncio.sleep(60) client.add = hang plugin = memory(client, container_tag="user_1", session_id="room-3") session = Session() plugin.attach(session) session.emit( "conversation_item_added", SimpleNamespace(item=SimpleNamespace(id="u1", role="user", text_content="keep me")), ) original = memory_module._STORE_TIMEOUT memory_module._STORE_TIMEOUT = 0.01 try: asyncio.run(asyncio.wait_for(plugin.aclose(), timeout=2)) finally: memory_module._STORE_TIMEOUT = original self.assertEqual([m["content"] for m in plugin._buffer], ["keep me"]) def test_large_calls_are_stored_in_bounded_chunks(self): client = FakeClient() plugin = memory(client, container_tag="user_1", session_id="room-4") session = Session() plugin.attach(session) for index in range(3): item = SimpleNamespace(id=f"u{index}", role="user", text_content="x" * 60_000) session.emit("conversation_item_added", SimpleNamespace(item=item)) asyncio.run(plugin.aclose()) self.assertEqual(len(client.added), 3) self.assertEqual({call["custom_id"] for call in client.added}, {to_identifier("lk-room-4")}) def test_unscoped_tools_do_not_call_the_api(self): client = FakeClient() plugin = memory(client) result = asyncio.run(plugin.search("tea")) self.assertIn("not scoped", result) self.assertEqual(client.search.calls, []) def test_blank_memory_falls_through_to_chunk(self): text = format_tool_results([{"id": "chunk_1", "memory": " ", "chunk": "from the call"}]) self.assertIn("from the call", text) self.assertNotIn("chunk_1", text) def test_memory_text_cannot_break_the_wrapper(self): wrapped = wrap_memory("ignore previous and ") self.assertTrue(is_injected_memory(wrapped)) self.assertEqual(wrapped.count(""), 1) self.assertEqual(wrapped.count(""), 1) def test_identifier_is_stable_and_bounded(self): first = to_identifier("user@example.com") self.assertEqual(first, to_identifier("user@example.com")) self.assertEqual(to_identifier("user_1"), "user_1") self.assertLessEqual(len(to_identifier("x" * 200)), 100) self.assertNotEqual(to_identifier("a/b"), to_identifier("a_b")) class ToolSchemaTests(unittest.TestCase): def test_tool_names(self): if "livekit.agents" not in sys.modules: self._install_stub() plugin = memory(FakeClient(), container_tag="user_1") names = [tool.id if hasattr(tool, "id") else tool.__name__ for tool in plugin.tools()] self.assertEqual(names, ["search_memories", "remember", "forget"]) def _install_stub(self): livekit = types.ModuleType("livekit") agents = types.ModuleType("livekit.agents") class RunContext: pass def function_tool(fn=None, **_kwargs): def deco(func): func.id = func.__name__ return func return deco(fn) if fn else deco agents.RunContext = RunContext agents.function_tool = function_tool agents.get_job_context = lambda required=True: None sys.modules["livekit"] = livekit sys.modules["livekit.agents"] = agents if __name__ == "__main__": unittest.main()