-
Notifications
You must be signed in to change notification settings - Fork 170
feat: implement SQLitePersistence layer for local memory storage #1258
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,37 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import sqlite3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check failure on line 1 in memmachine/storage/sqlite/persistence.py
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class SQLitePersistence: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SQLite-based persistence layer for MemMachine. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Provides a lightweight local storage option for agent memory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def __init__(self, db_path="memory.db"): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check failure on line 9 in memmachine/storage/sqlite/persistence.py
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.conn = sqlite3.connect(db_path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.cursor = self.conn.cursor() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._init_db() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def close(self): | |
| """ | |
| Close the SQLite cursor and connection. | |
| Safe to call multiple times. | |
| """ | |
| if getattr(self, "cursor", None) is not None: | |
| try: | |
| self.cursor.close() | |
| except sqlite3.Error: | |
| pass | |
| self.cursor = None | |
| if getattr(self, "conn", None) is not None: | |
| try: | |
| self.conn.close() | |
| except sqlite3.Error: | |
| pass | |
| self.conn = None | |
| def __enter__(self): | |
| return self | |
| def __exit__(self, exc_type, exc_val, exc_tb): | |
| self.close() |
Check failure on line 14 in memmachine/storage/sqlite/persistence.py
GitHub Actions / ruff
ruff (ANN202)
memmachine/storage/sqlite/persistence.py:14:9: ANN202 Missing return type annotation for private function `_init_db`
help: Add return type annotation: `None`
Check failure on line 22 in memmachine/storage/sqlite/persistence.py
GitHub Actions / ruff
ruff (Q001)
memmachine/storage/sqlite/persistence.py:15:29: Q001 Single quote multiline found but double quotes preferred
help: Replace single multiline quotes with double quotes
Copilot
AI
Mar 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ruff is configured to require type annotations (ANN*) for function signatures in this repo. __init__, _init_db, save, and load currently have no parameter/return type hints, which will likely fail CI; please add annotations (including the shape of metadata and the load() return type).
Copilot
AI
Mar 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keeping a single cursor on self.cursor and reusing it across calls makes future concurrency/parallel usage harder and can complicate error handling (e.g., partial transactions on exceptions). Prefer using self.conn.execute(...) / creating a fresh cursor per operation and wrapping writes in a transaction context so failures roll back cleanly.
| self.cursor = self.conn.cursor() | |
| self._init_db() | |
| def _init_db(self): | |
| self.cursor.execute(''' | |
| CREATE TABLE IF NOT EXISTS memory ( | |
| id TEXT PRIMARY KEY, | |
| content TEXT, | |
| metadata TEXT, | |
| timestamp DATETIME DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| ''') | |
| self.conn.commit() | |
| def save(self, memory_id, content, metadata=None): | |
| self.cursor.execute( | |
| "INSERT OR REPLACE INTO memory (id, content, metadata) VALUES (?, ?, ?)", | |
| (memory_id, content, json.dumps(metadata)) | |
| ) | |
| self.conn.commit() | |
| def load(self, memory_id): | |
| self.cursor.execute("SELECT content, metadata FROM memory WHERE id = ?", (memory_id,)) | |
| row = self.cursor.fetchone() | |
| self._init_db() | |
| def _init_db(self): | |
| with self.conn: | |
| self.conn.execute(''' | |
| CREATE TABLE IF NOT EXISTS memory ( | |
| id TEXT PRIMARY KEY, | |
| content TEXT, | |
| metadata TEXT, | |
| timestamp DATETIME DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| ''') | |
| def save(self, memory_id, content, metadata=None): | |
| with self.conn: | |
| self.conn.execute( | |
| "INSERT OR REPLACE INTO memory (id, content, metadata) VALUES (?, ?, ?)", | |
| (memory_id, content, json.dumps(metadata)) | |
| ) | |
| def load(self, memory_id): | |
| cursor = self.conn.execute( | |
| "SELECT content, metadata FROM memory WHERE id = ?", | |
| (memory_id,) | |
| ) | |
| row = cursor.fetchone() | |
| cursor.close() |
Copilot
AI
Mar 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This introduces new persistence behavior (schema init + save/load semantics) but there are no accompanying unit tests. Consider adding a small pytest suite that uses a temp sqlite file (or :memory:) to verify round-trip of content/metadata and that INSERT OR REPLACE behaves as intended.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This module is missing a top-level module docstring, and the stdlib imports are not sorted (Ruff
D100/I001will likely fail CI with the repo’s current lint settings). Add a short module docstring and sort imports alphabetically.