Blog: Building a Universal Memory Layer for AI Agents#4
Blog: Building a Universal Memory Layer for AI Agents#4
Conversation
📝 WalkthroughWalkthroughA new blog post MDX file has been added that comprehensively documents the design and implementation of a universal memory layer for AI agents, including memory types, system architecture, a Python prototype, and multi-agent considerations. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@website/src/content/blog/building-universal-memory-layer-for-ai-agents.mdx`:
- Around line 193-199: MemoryStore never exposes a write_procedural method even
though _init_tables creates the procedural table and ProceduralMemory dataclass
exists; add a write_procedural method to MemoryStore that mirrors the pattern of
write_episodic and write_semantic: accept a ProceduralMemory instance, open the
same DB connection/transaction used by the other writers, and INSERT or REPLACE
into the procedural table with columns (memory_id, condition_text, action_text,
success_count, failure_count), using the ProceduralMemory fields for values and
handling errors/logging the same way the existing write_episodic/write_semantic
methods do so callers can persist procedural memories.
- Line 305: Replace the incorrect author name "Butt" with the correct "Büttcher"
in the RRF citation strings (the sentence that currently reads "Cormack, Clarke,
and Butt (2009)" and the duplicate occurrence in the "Further Reading" section);
update both occurrences so the author list reads "Cormack, Clarke, and Büttcher"
to accurately reflect Gordon V. Cormack, Charles L. A. Clarke, and Stefan
Büttcher.
- Around line 219-234: The upsert in write_semantic is wrong because it
conflicts on memory_id (a new UUID each time) so INSERT never triggers DO
UPDATE; change the DB schema/constraint so semantic has a UNIQUE(subject,
predicate) and use ON CONFLICT(subject, predicate) DO UPDATE to update obj and
confidence for that pair, and when an update occurs also deduplicate the
auxiliary stores: remove the old row from memory_fts for that memory_id and
reinsert the new text, and replace the existing entry in self.vector_index (find
by memory_id and overwrite rather than append) so FTS and vector_index stay in
sync with the updated semantic row.
- Around line 316-320: The current embed function's use of Python's built-in
hash(text) is process-randomized so it's not actually deterministic across runs;
update embed(text: str) to compute a stable integer seed from a stable hash
(e.g., use hashlib.sha256(text.encode()) or zlib.crc32(text.encode()) and then
take that value mod 2**32) before seeding numpy, ensure you import the chosen
hashing module, and keep the downstream logic intact so embed still returns a
384-dim float32 vector but now reproducible across interpreter restarts.
- Line 130: Replace the deprecated default_factory(datetime.utcnow) used for the
dataclass field `timestamp` with a timezone-aware factory: import `timezone` and
change the default_factory for `timestamp` (the line defining timestamp:
datetime = field(default_factory=datetime.utcnow)) to use
`datetime.now(tz=timezone.utc)` so the field produces an aware UTC datetime
without triggering DeprecationWarning.
| content: str | ||
| context: dict # e.g., {"task": "refactor", "user": "alice"} | ||
| outcome: Optional[str] = None | ||
| timestamp: datetime = field(default_factory=datetime.utcnow) |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Python 3.12 datetime.utcnow deprecated DeprecationWarning replacement
💡 Result:
In Python 3.12+, datetime.datetime.utcnow() is deprecated because it returns a naive datetime (no timezone), which is often misinterpreted as local time. The recommended replacement is to create a timezone-aware UTC datetime instead. (docs.python.org)
from datetime import datetime, UTC
now_utc = datetime.now(tz=UTC) # recommended (tz is keyword-only)
# or (equivalent)
# from datetime import timezone
# now_utc = datetime.now(tz=timezone.utc)If you truly need the old naive “UTC clock reading” behavior, make it explicit (but prefer aware datetimes when possible):
naive_utc = datetime.now(tz=UTC).replace(tzinfo=None)Citations:
- 1: https://docs.python.org/3.15/library/datetime.html?utm_source=openai
- 2: https://docs.python.org/3.15/library/datetime.html?utm_source=openai
- 3: https://docs.python.org/3.15/library/datetime.html?utm_source=openai
datetime.utcnow() is deprecated since Python 3.12
The default factory datetime.utcnow was deprecated in Python 3.12. Readers following this code on modern Python will see a DeprecationWarning. The timezone-aware replacement is datetime.now(tz=timezone.utc) (note: tz is keyword-only).
🛠️ Proposed fix
-from datetime import datetime
+from datetime import datetime, timezone
...
- timestamp: datetime = field(default_factory=datetime.utcnow)
+ timestamp: datetime = field(default_factory=lambda: datetime.now(tz=timezone.utc))🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@website/src/content/blog/building-universal-memory-layer-for-ai-agents.mdx`
at line 130, Replace the deprecated default_factory(datetime.utcnow) used for
the dataclass field `timestamp` with a timezone-aware factory: import `timezone`
and change the default_factory for `timestamp` (the line defining timestamp:
datetime = field(default_factory=datetime.utcnow)) to use
`datetime.now(tz=timezone.utc)` so the field produces an aware UTC datetime
without triggering DeprecationWarning.
| CREATE TABLE IF NOT EXISTS procedural ( | ||
| memory_id TEXT PRIMARY KEY, | ||
| condition_text TEXT, | ||
| action_text TEXT, | ||
| success_count INTEGER, | ||
| failure_count INTEGER | ||
| ); |
There was a problem hiding this comment.
write_procedural is absent despite a complete ProceduralMemory schema and table
_init_tables creates the procedural table and Step 1 defines the ProceduralMemory dataclass, but MemoryStore never exposes a write_procedural method. Readers following the tutorial have no way to populate the third memory type, leaving the walkthrough incomplete for procedural memory.
Would you like me to draft a write_procedural method consistent with the existing write_episodic/write_semantic style?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@website/src/content/blog/building-universal-memory-layer-for-ai-agents.mdx`
around lines 193 - 199, MemoryStore never exposes a write_procedural method even
though _init_tables creates the procedural table and ProceduralMemory dataclass
exists; add a write_procedural method to MemoryStore that mirrors the pattern of
write_episodic and write_semantic: accept a ProceduralMemory instance, open the
same DB connection/transaction used by the other writers, and INSERT or REPLACE
into the procedural table with columns (memory_id, condition_text, action_text,
success_count, failure_count), using the ProceduralMemory fields for values and
handling errors/logging the same way the existing write_episodic/write_semantic
methods do so callers can persist procedural memories.
| def write_semantic(self, mem: "SemanticMemory", embedding: np.ndarray): | ||
| # Upsert: if same subject+predicate exists, update it | ||
| self.conn.execute( | ||
| """INSERT INTO semantic VALUES (?, ?, ?, ?, ?, ?) | ||
| ON CONFLICT(memory_id) DO UPDATE SET | ||
| obj=excluded.obj, confidence=excluded.confidence""", | ||
| (mem.memory_id, mem.subject, mem.predicate, | ||
| mem.obj, mem.confidence, mem.source) | ||
| ) | ||
| text = f"{mem.subject} {mem.predicate} {mem.obj}" | ||
| self.conn.execute( | ||
| "INSERT INTO memory_fts VALUES (?, ?)", | ||
| (mem.memory_id, text) | ||
| ) | ||
| self.conn.commit() | ||
| self.vector_index.append((mem.memory_id, embedding)) |
There was a problem hiding this comment.
write_semantic upsert logic is broken — the ON CONFLICT never fires
The comment says "Upsert: if same subject+predicate exists, update it", but the conflict clause is on memory_id. Since every SemanticMemory instance is constructed with a fresh UUID (field(default_factory=lambda: str(uuid.uuid4()))), the memory_id will never collide — so DO UPDATE is unreachable dead code. Every call always inserts a new row, allowing duplicate (subject, predicate) facts.
This also means:
- The FTS insert at lines 229-232 silently accumulates duplicate entries for the same fact.
self.vector_index.appendat line 234 keeps stale embeddings alongside new ones.
To implement the stated semantics, the semantic table needs a unique constraint on (subject, predicate) and the conflict must target that pair.
🐛 Proposed fix
CREATE TABLE IF NOT EXISTS semantic (
memory_id TEXT PRIMARY KEY,
subject TEXT,
predicate TEXT,
obj TEXT,
confidence REAL,
- source TEXT
+ source TEXT,
+ UNIQUE(subject, predicate)
); def write_semantic(self, mem: "SemanticMemory", embedding: np.ndarray):
# Upsert: if same subject+predicate exists, update it
self.conn.execute(
"""INSERT INTO semantic VALUES (?, ?, ?, ?, ?, ?)
- ON CONFLICT(memory_id) DO UPDATE SET
+ ON CONFLICT(subject, predicate) DO UPDATE SET
obj=excluded.obj, confidence=excluded.confidence""",You will also need to deduplicate the FTS and vector index on update — delete-then-reinsert for FTS, and replace the existing entry in self.vector_index.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@website/src/content/blog/building-universal-memory-layer-for-ai-agents.mdx`
around lines 219 - 234, The upsert in write_semantic is wrong because it
conflicts on memory_id (a new UUID each time) so INSERT never triggers DO
UPDATE; change the DB schema/constraint so semantic has a UNIQUE(subject,
predicate) and use ON CONFLICT(subject, predicate) DO UPDATE to update obj and
confidence for that pair, and when an update occurs also deduplicate the
auxiliary stores: remove the old row from memory_fts for that memory_id and
reinsert the new text, and replace the existing entry in self.vector_index (find
by memory_id and overwrite rather than append) so FTS and vector_index stay in
sync with the updated semantic row.
| return ranked[:top_n] | ||
| ``` | ||
|
|
||
| The RRF approach is attractive because it does not require normalizing scores across different retrieval methods. Each method produces a ranked list, and RRF combines the ranks. This was first described by Cormack, Clarke, and Butt (2009) and remains widely used in production search systems. |
There was a problem hiding this comment.
Incorrect author name for the RRF paper — "Butt" should be "Büttcher"
The third author of the 2009 RRF paper is Stefan Büttcher, not "Butt". The full author list is Gordon V. Cormack, Charles L. A. Clarke, and Stefan Büttcher. The same error appears in the "Further Reading" section at line 479.
✏️ Proposed fix
-The RRF approach is attractive because it does not require normalizing scores across different retrieval methods. Each method produces a ranked list, and RRF combines the ranks. This was first described by Cormack, Clarke, and Butt (2009) and remains widely used in production search systems.
+The RRF approach is attractive because it does not require normalizing scores across different retrieval methods. Each method produces a ranked list, and RRF combines the ranks. This was first described by Cormack, Clarke, and Büttcher (2009) and remains widely used in production search systems.Apply the same correction at line 479:
-- [Reciprocal Rank Fusion](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf) by Cormack, Clarke, and Butt (2009)
+- [Reciprocal Rank Fusion](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf) by Cormack, Clarke, and Büttcher (2009)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| The RRF approach is attractive because it does not require normalizing scores across different retrieval methods. Each method produces a ranked list, and RRF combines the ranks. This was first described by Cormack, Clarke, and Butt (2009) and remains widely used in production search systems. | |
| The RRF approach is attractive because it does not require normalizing scores across different retrieval methods. Each method produces a ranked list, and RRF combines the ranks. This was first described by Cormack, Clarke, and Büttcher (2009) and remains widely used in production search systems. |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@website/src/content/blog/building-universal-memory-layer-for-ai-agents.mdx`
at line 305, Replace the incorrect author name "Butt" with the correct
"Büttcher" in the RRF citation strings (the sentence that currently reads
"Cormack, Clarke, and Butt (2009)" and the duplicate occurrence in the "Further
Reading" section); update both occurrences so the author list reads "Cormack,
Clarke, and Büttcher" to accurately reflect Gordon V. Cormack, Charles L. A.
Clarke, and Stefan Büttcher.
| # Simulated embedding function (replace with a real model in production) | ||
| def embed(text: str) -> np.ndarray: | ||
| """Produce a deterministic pseudo-embedding for demonstration.""" | ||
| np.random.seed(hash(text) % 2**32) | ||
| return np.random.randn(384).astype(np.float32) |
There was a problem hiding this comment.
The pseudo-embedding is not reproducible across runs — misleading "deterministic" comment
Python's hash() is randomized per process by default (via PYTHONHASHSEED), so hash(text) % 2**32 will differ on every fresh Python invocation. Readers who run this code twice will get different "embeddings" for the same text and therefore different retrieval rankings, contradicting the inline comment "deterministic pseudo-embedding."
🛠️ Proposed fix — use a hash that is stable across runs
+import hashlib
+
def embed(text: str) -> np.ndarray:
- """Produce a deterministic pseudo-embedding for demonstration."""
- np.random.seed(hash(text) % 2**32)
+ """Produce a stable pseudo-embedding for demonstration (not for production)."""
+ seed = int(hashlib.md5(text.encode()).hexdigest(), 16) % 2**32
+ rng = np.random.default_rng(seed)
- return np.random.randn(384).astype(np.float32)
+ return rng.standard_normal(384).astype(np.float32)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Simulated embedding function (replace with a real model in production) | |
| def embed(text: str) -> np.ndarray: | |
| """Produce a deterministic pseudo-embedding for demonstration.""" | |
| np.random.seed(hash(text) % 2**32) | |
| return np.random.randn(384).astype(np.float32) | |
| import hashlib | |
| # Simulated embedding function (replace with a real model in production) | |
| def embed(text: str) -> np.ndarray: | |
| """Produce a stable pseudo-embedding for demonstration (not for production).""" | |
| seed = int(hashlib.md5(text.encode()).hexdigest(), 16) % 2**32 | |
| rng = np.random.default_rng(seed) | |
| return rng.standard_normal(384).astype(np.float32) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@website/src/content/blog/building-universal-memory-layer-for-ai-agents.mdx`
around lines 316 - 320, The current embed function's use of Python's built-in
hash(text) is process-randomized so it's not actually deterministic across runs;
update embed(text: str) to compute a stable integer seed from a stable hash
(e.g., use hashlib.sha256(text.encode()) or zlib.crc32(text.encode()) and then
take that value mod 2**32) before seeding numpy, ensure you import the chosen
hashing module, and keep the downstream logic intact so embed still returns a
384-dim float32 vector but now reproducible across interpreter restarts.
New Blog Post
Topic: Building a Universal Memory Layer for AI Agents
File:
building-universal-memory-layer-for-ai-agents.mdxAuto-generated by SLM Marketing Engine.
Auto-merges in 24h if no review comments.
Summary by CodeRabbit