Skip to content

Commit 03bada8

Browse files
committed
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
1 parent 7a4b4b3 commit 03bada8

1 file changed

Lines changed: 32 additions & 16 deletions

File tree

src/hawk/memory_tools.py

Lines changed: 32 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,25 @@
1213

1314
from __future__ import annotations
1415

15-
from typing import Any
16+
from typing import Any, Protocol, Sequence, runtime_checkable
1617

1718
from .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+
2035
class 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

Comments
 (0)