Skip to content

Commit d0174e0

Browse files
authored
refactor(memory): use typing.Protocol for MemoryBackend interface (#19)
* refactor(memory): use typing.Protocol for MemoryBackend interface - Replace duck-typing hasattr checks with structural Protocol - MemoryBackend is runtime_checkable for isinstance validation - Aligns with hawk-eco architecture: SDKs use structural interfaces * fix(memory): move Sequence to TYPE_CHECKING block, use hasattr for backend detection - Import Sequence under TYPE_CHECKING to satisfy ruff UP035/TC003 - Use hasattr instead of isinstance(Protocol) for backend detection since runtime_checkable checks class attrs, not instance attrs, breaking MagicMock-based tests on Python 3.12+ * style: ruff format memory_tools.py
1 parent 7a4b4b3 commit d0174e0

1 file changed

Lines changed: 35 additions & 16 deletions

File tree

src/hawk/memory_tools.py

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""Memory-as-voluntary-tools for agent-driven memory management.
22
33
Lets 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
67
Usage:
78
from hawk.memory_tools import MemoryTools
@@ -12,11 +13,28 @@
1213

1314
from __future__ import annotations
1415

15-
from typing import Any
16+
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
17+
18+
if TYPE_CHECKING:
19+
from collections.abc import Sequence
1620

1721
from .tools import Tool
1822

1923

24+
@runtime_checkable
25+
class MemoryBackend(Protocol):
26+
"""Structural interface for persistent memory backends.
27+
28+
Any object with ``remember`` and ``recall`` methods satisfying these
29+
signatures is accepted — no import or inheritance required.
30+
"""
31+
32+
def remember(self, content: str, *, session_id: str | None = None) -> Any: ...
33+
def recall(
34+
self, query: str, *, limit: int = 5, session_id: str | None = None
35+
) -> Sequence[str]: ...
36+
37+
2038
class MemoryTools:
2139
"""Provides record/retrieve memory operations as agent tools.
2240
@@ -30,6 +48,9 @@ def __init__(self, client: Any, *, session_id: str | None = None) -> None:
3048
self._client = client
3149
self._session_id = session_id
3250
self._local_memories: list[dict[str, str]] = []
51+
self._backend: MemoryBackend | None = (
52+
client if hasattr(client, "remember") and hasattr(client, "recall") else None
53+
)
3354

3455
def record_memory(
3556
self, content: str, category: str = "general", importance: str = "normal"
@@ -42,34 +63,32 @@ def record_memory(
4263
}
4364
self._local_memories.append(memory)
4465

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)
66+
if self._backend is not None:
67+
try:
68+
self._backend.remember(content, session_id=self._session_id)
4969
return f"Recorded to persistent memory: '{content[:100]}...'"
50-
except Exception as exc:
51-
import logging
70+
except Exception as exc:
71+
import logging
5272

53-
logging.getLogger(__name__).debug("Failed to persist memory via yaad: %s", exc)
73+
logging.getLogger(__name__).debug("Failed to persist memory: %s", exc)
5474

5575
return f"Recorded to session memory: '{content[:100]}...'"
5676

5777
def retrieve_memories(self, query: str, limit: int = 5) -> str:
5878
"""Retrieve relevant memories for the current context."""
5979
results = []
6080

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)
81+
if self._backend is not None:
82+
try:
83+
recalled = self._backend.recall(query, limit=limit, session_id=self._session_id)
6584
if recalled:
6685
return f"Recalled {len(recalled)} memories:\n" + "\n".join(
6786
f"- {m}" for m in recalled
6887
)
69-
except Exception as exc:
70-
import logging
88+
except Exception as exc:
89+
import logging
7190

72-
logging.getLogger(__name__).debug("Failed to recall memories via yaad: %s", exc)
91+
logging.getLogger(__name__).debug("Failed to recall memories: %s", exc)
7392

7493
# Fallback to local fuzzy match
7594
query_lower = query.lower()

0 commit comments

Comments
 (0)