11"""Memory-as-voluntary-tools for agent-driven memory management.
22
33Lets agents strategically decide what to remember/recall rather than
4- auto-ingesting everything. Wraps yaad's memory API as tool functions.
4+ auto-ingesting everything. Wraps any MemoryBackend-compatible client
5+ as tool functions.
56
67Usage:
78 from hawk.memory_tools import MemoryTools
1213
1314from __future__ import annotations
1415
15- from typing import Any
16+ from typing import Any , Protocol , Sequence , runtime_checkable
1617
1718from .tools import Tool
1819
1920
21+ @runtime_checkable
22+ class MemoryBackend (Protocol ):
23+ """Structural interface for persistent memory backends.
24+
25+ Any object with ``remember`` and ``recall`` methods satisfying these
26+ signatures is accepted — no import or inheritance required.
27+ """
28+
29+ def remember (self , content : str , * , session_id : str | None = None ) -> Any : ...
30+ def recall (
31+ self , query : str , * , limit : int = 5 , session_id : str | None = None
32+ ) -> Sequence [str ]: ...
33+
34+
2035class MemoryTools :
2136 """Provides record/retrieve memory operations as agent tools.
2237
@@ -30,6 +45,9 @@ def __init__(self, client: Any, *, session_id: str | None = None) -> None:
3045 self ._client = client
3146 self ._session_id = session_id
3247 self ._local_memories : list [dict [str , str ]] = []
48+ self ._backend : MemoryBackend | None = (
49+ client if isinstance (client , MemoryBackend ) else None
50+ )
3351
3452 def record_memory (
3553 self , content : str , category : str = "general" , importance : str = "normal"
@@ -42,34 +60,32 @@ def record_memory(
4260 }
4361 self ._local_memories .append (memory )
4462
45- # If client supports yaad memory API, persist
46- try :
47- if hasattr (self ._client , "remember" ):
48- self ._client .remember (content , session_id = self ._session_id )
63+ if self ._backend is not None :
64+ try :
65+ self ._backend .remember (content , session_id = self ._session_id )
4966 return f"Recorded to persistent memory: '{ content [:100 ]} ...'"
50- except Exception as exc :
51- import logging
67+ except Exception as exc :
68+ import logging
5269
53- logging .getLogger (__name__ ).debug ("Failed to persist memory via yaad : %s" , exc )
70+ logging .getLogger (__name__ ).debug ("Failed to persist memory: %s" , exc )
5471
5572 return f"Recorded to session memory: '{ content [:100 ]} ...'"
5673
5774 def retrieve_memories (self , query : str , limit : int = 5 ) -> str :
5875 """Retrieve relevant memories for the current context."""
5976 results = []
6077
61- # Try yaad recall
62- try :
63- if hasattr (self ._client , "recall" ):
64- recalled = self ._client .recall (query , limit = limit , session_id = self ._session_id )
78+ if self ._backend is not None :
79+ try :
80+ recalled = self ._backend .recall (query , limit = limit , session_id = self ._session_id )
6581 if recalled :
6682 return f"Recalled { len (recalled )} memories:\n " + "\n " .join (
6783 f"- { m } " for m in recalled
6884 )
69- except Exception as exc :
70- import logging
85+ except Exception as exc :
86+ import logging
7187
72- logging .getLogger (__name__ ).debug ("Failed to recall memories via yaad : %s" , exc )
88+ logging .getLogger (__name__ ).debug ("Failed to recall memories: %s" , exc )
7389
7490 # Fallback to local fuzzy match
7591 query_lower = query .lower ()
0 commit comments