"""Behavior tests for the built-in memory → external provider bridge. The bridge lives behind the MemoryManager interface (``MemoryManager.notify_memory_tool_write``): the agent loop hands over the raw built-in memory tool result + args, and the manager decides whether/what to mirror to external providers. These tests drive that method with a fake external provider and assert which ``on_memory_write`` calls land. """ import json import pytest from agent.memory_manager import MemoryManager from agent.memory_provider import MemoryProvider class _RecordingProvider(MemoryProvider): """Minimal external provider that records on_memory_write calls.""" def __init__(self) -> None: self.calls = [] @property def name(self) -> str: return "recording" def is_available(self) -> bool: return True def initialize(self, session_id: str, **kwargs) -> None: pass def get_tool_schemas(self): return [] def shutdown(self) -> None: pass def on_memory_write(self, action, target, content, metadata=None): self.calls.append({ "action": action, "target": target, "content": content, "metadata": dict(metadata or {}), }) def _manager_with_provider(): mgr = MemoryManager() provider = _RecordingProvider() mgr.add_provider(provider) return mgr, provider def test_notifies_remove_with_old_text_after_success(): mgr, provider = _manager_with_provider() mgr.notify_memory_tool_write( json.dumps({"success": True}), {"action": "remove", "target": "memory", "old_text": "stale preference entry"}, ) assert provider.calls == [ { "action": "remove", "target": "memory", "content": "", "metadata": {"old_text": "stale preference entry"}, } ] def test_skips_failed_memory_write(): mgr, provider = _manager_with_provider() mgr.notify_memory_tool_write( json.dumps({"success": False, "error": "No entry matched"}), {"action": "remove", "target": "memory", "old_text": "stale preference entry"}, ) assert provider.calls == [] def test_skips_staged_memory_write(): mgr, provider = _manager_with_provider() mgr.notify_memory_tool_write( json.dumps({"success": True, "staged": True, "pending_id": "abc123"}), {"action": "remove", "target": "memory", "old_text": "stale preference entry"}, ) assert provider.calls == [] @pytest.mark.parametrize("tool_result", [None, [], object(), "not-json"]) def test_skips_unrecognized_tool_result_shape(tool_result): mgr, provider = _manager_with_provider() mgr.notify_memory_tool_write( tool_result, {"action": "add", "target": "memory", "content": "new fact"}, ) assert provider.calls == [] def test_preserves_old_text_for_replace_and_remove_batch(): mgr, provider = _manager_with_provider() mgr.notify_memory_tool_write( json.dumps({"success": True}), { "target": "user", "operations": [ {"action": "replace", "old_text": "old preference", "content": "updated"}, {"action": "remove", "old_text": "obsolete preference"}, {"action": "add", "content": "new fact"}, ], }, ) assert provider.calls == [ {"action": "replace", "target": "user", "content": "updated", "metadata": {"old_text": "old preference"}}, {"action": "remove", "target": "user", "content": "", "metadata": {"old_text": "obsolete preference"}}, {"action": "add", "target": "user", "content": "new fact", "metadata": {}}, ] def test_non_mutating_actions_are_not_mirrored(): mgr, provider = _manager_with_provider() mgr.notify_memory_tool_write( json.dumps({"success": True}), {"action": "read", "target": "memory"}, ) assert provider.calls == [] def test_build_metadata_callback_is_merged_per_op(): mgr, provider = _manager_with_provider() mgr.notify_memory_tool_write( json.dumps({"success": True}), {"action": "add", "target": "memory", "content": "fact"}, build_metadata=lambda: {"session_id": "s1", "tool_name": "memory"}, ) assert provider.calls == [ { "action": "add", "target": "memory", "content": "fact", "metadata": {"session_id": "s1", "tool_name": "memory"}, } ]