Skip to content

Blog: Building a Universal Memory Layer for AI Agents#4

Open
varun369 wants to merge 1 commit intomainfrom
blog/2026-02-18-building-universal-memory-layer-for-ai-agents
Open

Blog: Building a Universal Memory Layer for AI Agents#4
varun369 wants to merge 1 commit intomainfrom
blog/2026-02-18-building-universal-memory-layer-for-ai-agents

Conversation

@varun369
Copy link
Owner

@varun369 varun369 commented Feb 18, 2026

New Blog Post

Topic: Building a Universal Memory Layer for AI Agents
File: building-universal-memory-layer-for-ai-agents.mdx


Auto-generated by SLM Marketing Engine.

Auto-merges in 24h if no review comments.

Summary by CodeRabbit

  • Documentation
    • Added comprehensive blog post on designing and implementing memory layers for AI agents, covering memory types (episodic, semantic, procedural), hybrid retrieval architectures, practical Python examples, multi-agent coordination patterns, and production considerations including storage backends and memory decay strategies.

@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

📝 Walkthrough

Walkthrough

A 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

Cohort / File(s) Summary
Blog Post: Universal Memory Layer for AI Agents
website/src/content/blog/building-universal-memory-layer-for-ai-agents.mdx
New blog post covering episodic, semantic, and procedural memory types; SQLite-backed storage with vector indexing; hybrid retrieval combining BM25 and cosine similarity via reciprocal rank fusion; runnable Python prototype; and multi-agent trust-weighting considerations.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A memory most fine, in layers divine,
Vector and keyword in perfect align!
Episodic, semantic, procedural dance—
This agent will learn at first glance.
Through SQLite's halls, the wisdom now calls! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a blog post about building a universal memory layer for AI agents, which matches the file addition and content focus.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch blog/2026-02-18-building-universal-memory-layer-for-ai-agents

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 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)

(docs.python.org)

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)

(docs.python.org)

Citations:


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.

Comment on lines +193 to +199
CREATE TABLE IF NOT EXISTS procedural (
memory_id TEXT PRIMARY KEY,
condition_text TEXT,
action_text TEXT,
success_count INTEGER,
failure_count INTEGER
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +219 to +234
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))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.append at 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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Comment on lines +316 to +320
# 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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
# 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant