Add Memory capability with pluggable storage backends#179
Add Memory capability with pluggable storage backends#179
Conversation
Implements a Memory capability (AbstractCapability subclass) for persistent key-value memory across agent sessions, addressing #30. - MemoryStore protocol with InMemoryStore (dict-based, for testing) and FileStore (JSON file on disk, for persistence) backends - Five tools via get_toolset(): save_memory, recall_memory, search_memories, list_memories, delete_memory - Dynamic instructions via get_instructions() that inject stored memories into the system prompt at run start - Substring-based search across keys, content, and tags - Spec serialization support (Memory.from_spec with backend="memory"|"file") - 48 tests covering all code paths, passing lint, format, and typecheck Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Address audit findings from PR review: - Better search: word-boundary matching with relevance scoring (count of matching words across key/content/tags, sorted by score descending). Underscores and hyphens treated as word separators. - Memory scoping: `scope: str = 'global'` field on MemoryEntry, with optional `scope` parameter on `search_memories` and `list_memories` tools and `list_all`/`search` store methods. - TTL/expiration: `expires_at: str | None = None` on MemoryEntry with `is_expired()` method. Stores filter out expired entries automatically. `save_memory` tool accepts optional `ttl_minutes` parameter. - Dedup warning: when saving a memory whose key is very similar to an existing key (same 10-char prefix, Levenshtein distance <= 2), log a warning via the `pydantic_harness.memory` logger. Tests: 48 -> 99, all passing with 100% coverage. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…Any types Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
… and FileStore Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
… backend Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…L, and conformance Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- personal_assistant.py: FileStore persistence, preferences, instructions injection - study_coach.py: TTL/spaced repetition, tags, search - coding_assistant.py: procedural memory, rules, search, delete All examples assert on memory state and are instrumented with logfire spans. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
| def get(self, key: str) -> MemoryEntry | None: | ||
| """Retrieve a memory entry by key.""" | ||
| return self._entries.get(key) |
There was a problem hiding this comment.
🚩 Expired entries are never cleaned up from storage
The store-level get() method (_BaseDictStore.get at line 186) returns entries regardless of expiration status. Filtering is only done in list_all, search, and the recall_memory tool. This means expired entries accumulate indefinitely in both InMemoryStore (memory leak) and FileStore (disk bloat). For short-lived processes this is fine, but long-running agents with TTL-based entries will see unbounded growth. A periodic or lazy cleanup strategy (e.g., purging expired entries on list_all/search or on a timer) would be worth considering.
Was this helpful? React with 👍 or 👎 to provide feedback.
Audit vs prior art: MemoryWorth adding now:
Follow-up opportunities:
|
Claude here: We reviewed this PR and pushed several improvements. Here's what changed: Code Quality (7 commits)Type Safety
Code Deduplication
Robustness
Style
Tests (48 → 119)Added edge case tests for:
Examples (3 scripts)All instrumented with Logfire, assert on memory state:
All 3 ran successfully against |
Summary
Implements a
Memorycapability (AbstractCapabilitysubclass) for persistent key-value memory across agent sessions.MemoryStoreprotocol with two backends:InMemoryStore(dict-based, for testing) andFileStore(JSON file on disk, for persistence)get_toolset():save_memory,recall_memory,search_memories,list_memories,delete_memoryget_instructions()that inject stored memories into the system prompt at run startfrom_spec(backend="memory"|"file")Closes #30
Test plan
ruff checkandruff formatpasspyrightstrict mode passes with 0 errors🤖 Generated with Claude Code