<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Statewave</title>
    <description>The latest articles on DEV Community by Statewave (@statewave).</description>
    <link>https://dev.to/statewave</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4060888%2Fc6466dda-faf5-49f0-aa7e-c09af71c3a5a.png</url>
      <title>DEV Community: Statewave</title>
      <link>https://dev.to/statewave</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/statewave"/>
    <language>en</language>
    <item>
      <title>8 Open Source Tools for Building AI Agents with Memory</title>
      <dc:creator>Statewave</dc:creator>
      <pubDate>Thu, 03 Sep 2026 14:09:06 +0000</pubDate>
      <link>https://dev.to/statewave/8-open-source-tools-for-building-ai-agents-with-memory-4i47</link>
      <guid>https://dev.to/statewave/8-open-source-tools-for-building-ai-agents-with-memory-4i47</guid>
      <description>&lt;p&gt;An agent that remembers needs four things: somewhere to put raw events, something that turns them into facts, a way to retrieve those facts under a token budget, and a framework to run the loop. No single tool does all four well, which is why this list is eight tools and not one.&lt;/p&gt;

&lt;p&gt;We build &lt;a href="https://github.com/smaramwbc/statewave" rel="noopener noreferrer"&gt;Statewave&lt;/a&gt;, an open-source memory runtime, so treat the entry about us accordingly. Everything below is Apache-2.0, MIT, or the PostgreSQL License, verified from the LICENSE file rather than the badge, checked on 1 September 2026. MCP is mid-transition from MIT to Apache-2.0.&lt;/p&gt;

&lt;p&gt;Grouping below is by what each tool owns, because the most common mistake in this category is picking two tools that own the same layer and none that own the missing one.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The memory layer&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Statewave&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flw2nx4c17xmhmm15d4mv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flw2nx4c17xmhmm15d4mv.png" alt="Statewave" width="799" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A memory runtime that sits behind your agents over HTTP. You send raw events as episodes; it compiles them into typed memories with confidence scores and validity windows, and you request a ranked, token-bounded bundle when you need context.&lt;/p&gt;

&lt;p&gt;from statewave import StatewaveClient  &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;with StatewaveClient("&lt;a href="http://localhost:8100%22" rel="noopener noreferrer"&gt;http://localhost:8100"&lt;/a&gt;) as sw:     sw.create_episode(subject_id="user-42", source="chat", type="message",                       payload={"text": "Alice asked about pricing tiers"})   sw.compile_memories("user-42")    print(sw.get_context("user-42", task="answer pricing", max_tokens=1000).assembled_context)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What it owns: storage, consolidation, ranked retrieval, conflict resolution, provenance. What it deliberately does not own: your agent loop. Storage is Postgres with pgvector and no separate vector service. &lt;strong&gt;npx &lt;a class="mentioned-user" href="https://dev.to/statewavedev"&gt;@statewavedev&lt;/a&gt;/statewave&lt;/strong&gt; boots the API, admin console, and Postgres locally. The server defaults to demo mode with stub hash-based embeddings and the heuristic compiler, which means no real semantic search but a working loop.&lt;/p&gt;

&lt;p&gt;Determinism is the property we care most about. The same subject, task, and token budget return the same bytes, which is what makes retrieval regression-testable.&lt;/p&gt;

&lt;p&gt;Where it costs you: cross-subject retrieval is two calls, and the default compiler is heuristic regex. Switch to &lt;strong&gt;STATEWAVE_COMPILER_TYPE=llm&lt;/strong&gt; for better extraction from messy conversation, or keep the heuristic compiler deliberately if no customer text may leave your network.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Mem0&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flgk770j85xavzl98sap9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flgk770j85xavzl98sap9.png" alt="Mem0" width="799" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mem0ai/mem0" rel="noopener noreferrer"&gt;Mem0&lt;/a&gt; is the shortest path from nothing to an assistant that remembers a user. Its API is four calls wide: add, search, update, delete. Apache-2.0 for the library, with a managed platform sold separately.&lt;/p&gt;

&lt;p&gt;Pick it when time to first working result matters more than being able to explain a specific retrieval later. Composition of its vector store, graph layer, and reranker is not broken down in public docs, so auditability means reading source.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Graphiti&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv1e5iv7ijsqge5hy4ra5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv1e5iv7ijsqge5hy4ra5.png" alt="Graphiti" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/getzep/graphiti" rel="noopener noreferrer"&gt;Graphiti&lt;/a&gt; is a temporal knowledge graph that tracks when a fact became valid and when it stopped being true. That is a real answer to stale memory, which most fact stores handle by overwriting and hoping.&lt;/p&gt;

&lt;p&gt;A note that saves evaluation time: many lists point to &lt;a href="https://github.com/getzep/zep" rel="noopener noreferrer"&gt;getzep/zep&lt;/a&gt; for this project. That repo’s README describes itself as examples for the managed Zep Cloud rather than the product, checked 1 September 2026. Graphiti is the self-hostable piece.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Cognee&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5oaf1spw91s3tnpefevk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5oaf1spw91s3tnpefevk.png" alt="Cognee" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/topoteretes/cognee" rel="noopener noreferrer"&gt;Cognee&lt;/a&gt; builds a self-hosted knowledge graph with ontology grounding, combining embeddings with graph reasoning. Apache-2.0.&lt;/p&gt;

&lt;p&gt;Reach for it when the relationships between entities carry the meaning, not just the facts about each one. It is more machinery than a preference store needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The orchestration layer&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. LangGraph&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjp2nha9i1rpl377evea4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjp2nha9i1rpl377evea4.png" alt="LangGraph" width="799" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/langchain-ai/langgraph" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt; models agents as state machines with explicit nodes and edges, which makes multi-step flows debuggable in a way that a while-loop over tool calls is not. MIT.&lt;/p&gt;

&lt;p&gt;Its checkpointer handles per-thread state well. Worth being precise about the boundary: thread-level persistence is not the same as cross-session, cross-agent memory, and conflating them is how teams end up with an agent that remembers a conversation but not a customer.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;6. Letta&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1yzdan7qy4sst6vlpyod.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1yzdan7qy4sst6vlpyod.png" alt="Letta" width="799" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/letta-ai/letta" rel="noopener noreferrer"&gt;Letta&lt;/a&gt; carries the MemGPT lineage and is a stateful agent runtime rather than a memory layer. It owns the reasoning loop, tool calls, and context management. Apache-2.0.&lt;/p&gt;

&lt;p&gt;Check what you are adopting before you commit to it. &lt;strong&gt;letta-ai/letta&lt;/strong&gt; now describes itself as a landing page for the project, with the retired Letta V1 server preserved on an &lt;strong&gt;archive&lt;/strong&gt; branch and marked unsupported and not for production use. The current path is the hosted platform and the Letta Agent SDK, so Letta is no longer the self-hostable “one decision instead of four” it used to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The protocol and storage layers&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;7. Model Context Protocol&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqx5iop6d3os7v35b0xv8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqx5iop6d3os7v35b0xv8.jpg" alt="Model Context Protocol" width="512" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; is the piece people skip, then rebuild badly. It is an open protocol for exposing tools and data to LLM clients, built on JSON-RPC.&lt;/p&gt;

&lt;p&gt;Why it belongs on a memory list: if four agents in three frameworks need the same memory, a protocol endpoint is what stops you writing three adapters. We expose Statewave over MCP for exactly this, so a Claude custom connector and a Python agent hit one memory service without either knowing about the other.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;8. pgvector&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F79vo79vxi62alrp69dq2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F79vo79vxi62alrp69dq2.jpg" alt="pgvector" width="512" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;pgvector&lt;/a&gt; adds vector types and distance operators to Postgres. Not glamorous, and it removes an entire moving part from your architecture.&lt;/p&gt;

&lt;p&gt;One detail worth knowing: use an HNSW index rather than IVFFlat for anything that grows. IVFFlat recall depends on lists and probes matching your row count, so a corpus that outgrows its tuning quietly returns worse neighbors. We migrated for that reason and wrote up the details in the Postgres post.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to assemble these&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Stacks that work: pair one tool per layer:&lt;/p&gt;

&lt;p&gt;●      &lt;strong&gt;Fast prototype:&lt;/strong&gt; LangGraph plus Mem0. Two decisions, working today.&lt;br&gt;&lt;br&gt;
●      &lt;strong&gt;Self-hosted with audit requirements:&lt;/strong&gt; &lt;a href="https://www.statewave.ai/blog/self-hosting-ai-memory" rel="noopener noreferrer"&gt;your framework plus Statewave on Postgres and pgvector, exposed over MCP&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
●      &lt;strong&gt;Relationship-heavy domains:&lt;/strong&gt; Graphiti or Cognee for memory, LangGraph for orchestration.&lt;/p&gt;

&lt;p&gt;A common anti-pattern is picking Letta and Mem0 together, or LangGraph checkpointers and expecting cross-session memory. Both are two tools fighting over one layer while a different layer stays empty.&lt;/p&gt;

&lt;p&gt;If you want the working code rather than the list, we keep three runnable demos: &lt;a href="https://github.com/smaramwbc/statewave-multi-agent-memory" rel="noopener noreferrer"&gt;multi-agent memory&lt;/a&gt; with conflicting sources and automatic supersession, &lt;a href="https://github.com/smaramwbc/statewave-multi-agent-shared-context" rel="noopener noreferrer"&gt;multi-agent shared context&lt;/a&gt; where a planner and coder stop contradicting each other, and a &lt;a href="https://github.com/smaramwbc/statewave-personal-assistant" rel="noopener noreferrer"&gt;personal assistant&lt;/a&gt; that boots in five minutes without an LLM key.&lt;/p&gt;

&lt;p&gt;No neutral, third-party benchmark compares these on the same task with the same corpus. Ours covers a subset and we ran it, which is exactly why it is not the one to settle your decision. Run the eval on your own data before committing to any of them.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>opensource</category>
      <category>tools</category>
    </item>
    <item>
      <title>7 Postgres Tools Every AI Engineer Should Know</title>
      <dc:creator>Statewave</dc:creator>
      <pubDate>Thu, 03 Sep 2026 13:59:42 +0000</pubDate>
      <link>https://dev.to/statewave/7-postgres-tools-every-ai-engineer-should-know-2noj</link>
      <guid>https://dev.to/statewave/7-postgres-tools-every-ai-engineer-should-know-2noj</guid>
      <description>&lt;p&gt;Most AI stacks add a vector database on day one and a second datastore to keep in sync forever after. A short list of Postgres extensions removes that decision for a large class of workloads.&lt;/p&gt;

&lt;p&gt;We build &lt;a href="https://github.com/smaramwbc/statewave" rel="noopener noreferrer"&gt;Statewave&lt;/a&gt;, an open-source memory runtime for AI agents that runs on Postgres and nothing else. No separate vector service. That constraint forced us to learn which extensions genuinely carry AI workloads and which are resume padding. Below are seven, ordered by how often they earn their install.&lt;/p&gt;

&lt;p&gt;Our own dependency list is one extension long, which is the first useful signal in this post: &lt;strong&gt;vector is the only one we require. Two entries below are not extensions at all, full-text search ships in Postgres core, and PgBouncer is a connection pooler, but both earn their place on an AI workload, so they are here.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;1. pgvector&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;pgvector&lt;/a&gt; is what makes the rest of the argument possible. It adds a vector column type plus distance operators for cosine, L2, and inner product.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CREATE EXTENSION IF NOT EXISTS vector; ALTER TABLE memories ALTER COLUMN embedding TYPE vector(1536);&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Index choice is the part worth internalizing, because we shipped the wrong one first. Our initial migration built an IVFFlat index:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CREATE INDEX ON memories USING ivfflat (embedding vector_cosine_ops);&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/pgvector/pgvector#ivfflat" rel="noopener noreferrer"&gt;IVFFlat&lt;/a&gt; partitions vectors into lists and probes a subset at query time. It builds fast and uses little memory. Recall, though, depends on lists and probes being tuned against your actual row count, and a corpus that grows past what you tuned for silently returns worse neighbors. Nothing errors. Results just get quietly less relevant.&lt;/p&gt;

&lt;p&gt;We moved to HNSW:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CREATE INDEX ix_memories_embedding   ON memories USING hnsw (embedding vector_cosine_ops);&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/pgvector/pgvector#hnsw" rel="noopener noreferrer"&gt;HNSW&lt;/a&gt; builds a navigable graph. Better recall at the same latency, stable as the corpus grows, and no list-count tuning. Cost is real: build time and memory. On a small corpus the migration finishes in under a second, but HNSW build is the slow step at scale, so our migration sets statement_timeout = '20min' to survive large tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use IVFFlat&lt;/strong&gt; when your corpus is static and you need a fast build. &lt;strong&gt;Use HNSW&lt;/strong&gt; for anything that grows. If you inherited an IVFFlat index and nobody has retuned lists since the table doubled, that is worth checking today.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;2. Built-in tsvector and GIN (no extension required)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Semantic search alone fails on exact tokens. A user asking for error code SW-4021 needs lexical matching, and embeddings will happily return five semantically adjacent errors instead.&lt;/p&gt;

&lt;p&gt;Postgres ships &lt;a href="https://www.postgresql.org/docs/current/textsearch.html" rel="noopener noreferrer"&gt;full-text search&lt;/a&gt; in core. A generated column plus a GIN index gives you lexical retrieval with zero dependencies:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ALTER TABLE memories   ADD COLUMN content_tsvector tsvector   GENERATED ALWAYS AS (to_tsvector('english', content)) STORED; CREATE INDEX ix_memories_content_tsvector   ON memories USING gin (content_tsvector);&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;GENERATED ALWAYS AS ... STORED means Postgres maintains the column on every write. No trigger to forget, no backfill job to schedule.&lt;/p&gt;

&lt;p&gt;We run this alongside vector search and fuse the two, which is what "hybrid retrieval" means in practice. Including it here because half the teams that install a vector database do so to solve a problem that lexical search solves better.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;3. pgcrypto&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Small but load-bearing. &lt;a href="https://www.postgresql.org/docs/current/pgcrypto.html" rel="noopener noreferrer"&gt;pgcrypto&lt;/a&gt; supplies gen_random_uuid() for primary keys generated server-side, so you are not round-tripping to the application for an ID.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CREATE EXTENSION IF NOT EXISTS pgcrypto;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On Postgres 13 and newer, gen_random_uuid() is in core and you may not need the extension at all. Check your version before adding it. Its digest and encryption functions matter separately if you are storing anything sensitive in a memory layer, which for agent memory you usually are.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;4. pg_trgm&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/pgtrgm.html" rel="noopener noreferrer"&gt;pg_trgm&lt;/a&gt; does trigram matching for fuzzy string comparison. Entity resolution is the AI-specific use: deciding that "Acme Corp", "ACME Corporation", and "acme corp." are the same subject before you write three separate memory rows about them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE INDEX ON subjects USING gin (name gin_trgm_ops); SELECT name, similarity(name, 'acme corp') AS score FROM subjects WHERE name % 'acme corp' ORDER BY score DESC;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Embeddings are the wrong tool here. Two spellings of the same company name are lexically close and semantically identical, so cosine similarity gives you no separation between the right match and every other company in your table.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;5. pg_stat_statements&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/pgstatstatements.html" rel="noopener noreferrer"&gt;pg_stat_statements&lt;/a&gt; tells you why your agent feels slow.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CREATE EXTENSION IF NOT EXISTS pg_stat_statements; SELECT query, calls, mean_exec_time, total_exec_time FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;AI workloads have a specific failure shape: one vector query looks fine at 40ms, then you discover the agent issues it eleven times per turn. Mean latency stays healthy while total time balloons. total_exec_time catches that; a p99 dashboard does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;6. pg_cron&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/citusdata/pg_cron" rel="noopener noreferrer"&gt;pg_cron&lt;/a&gt; covers the background work agent memory needs. Compiling raw events into typed facts, expiring memories past their validity window, and recomputing scores are all jobs that should not run on the request path.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CREATE EXTENSION IF NOT EXISTS pg_cron; SELECT cron.schedule('expire-memories', '0 * * * *',   $$UPDATE memories SET status='expired'    WHERE valid_until &amp;lt; now() AND status='active'$$);&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We run compilation as durable jobs in the application layer rather than in pg_cron, because our jobs call an LLM and need retry semantics Postgres should not own. Honest rule: pg_cron is right for deterministic SQL maintenance and wrong for anything that makes a network call.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;7. PgBouncer (a pooler, not an extension)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Included deliberately, because the thing that breaks first when you scale an AI service is not the vector index. It is connection count.&lt;/p&gt;

&lt;p&gt;Each API replica holds a pool. Ten replicas at fifteen connections each is 150 logical connections crowding max_connections, and Postgres connection overhead is not free. Raising max_connections postpones the problem and makes it worse.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.pgbouncer.org/" rel="noopener noreferrer"&gt;PgBouncer&lt;/a&gt; in &lt;strong&gt;transaction mode&lt;/strong&gt; decouples logical client connections from physical backends: a pool of 30 to 60 backend connections comfortably serves 5 to 15 replicas. Transaction mode is the right setting when sessions are short. If you use prepared statements, PgBouncer 1.21 and later support them in transaction mode via &lt;strong&gt;max_prepared_statements&lt;/strong&gt;; on older versions, avoid holding them across transactions.&lt;/p&gt;

&lt;p&gt;One thing to know before you switch: &lt;strong&gt;pg_stat_activity&lt;/strong&gt; then shows &lt;strong&gt;PgBouncer's&lt;/strong&gt; identity rather than your replicas. Use &lt;strong&gt;SHOW POOLS&lt;/strong&gt; and &lt;strong&gt;SHOW CLIENTS&lt;/strong&gt; for the per-replica picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What we would actually install&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For a new AI service on Postgres, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;vector&lt;/strong&gt; with an HNSW index. Non-negotiable.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in tsvector plus GIN.&lt;/strong&gt; Free, and it fixes the exact-token failures embeddings cannot.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pg_stat_statements.&lt;/strong&gt; Install before you need it, because the query pattern you need to diagnose is one you have to catch in the act.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pg_trgm&lt;/strong&gt; once you have real user-entered entity names.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PgBouncer&lt;/strong&gt; at the point where replica count times pool size approaches &lt;strong&gt;max_connections.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;pgcrypto&lt;/strong&gt; and &lt;strong&gt;pg_cron&lt;/strong&gt; are situational. Check your Postgres version for the first and your job semantics for the second.&lt;/p&gt;

&lt;p&gt;Broader point: &lt;a href="https://www.statewave.ai/blog/self-hosting-ai-memory" rel="noopener noreferrer"&gt;a memory layer for AI agents can run on Postgres with one extension and no separate vector service&lt;/a&gt;. We wrote up the &lt;a href="https://www.statewave.ai/blog/self-hosted-memory-postgres-pgvector" rel="noopener noreferrer"&gt;storage decisions in more detail&lt;/a&gt;, and the IVFFlat-to-HNSW migration is readable in the repo linked at the top if you would rather check it than take our word for it.&lt;/p&gt;

&lt;p&gt;What did we miss? If you are running something in production that earned its place on an AI workload, name it below.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>postgres</category>
      <category>sql</category>
    </item>
    <item>
      <title>6 Ways to Add Observability to Your AI Agent Pipeline</title>
      <dc:creator>Statewave</dc:creator>
      <pubDate>Thu, 03 Sep 2026 13:57:44 +0000</pubDate>
      <link>https://dev.to/statewave/6-ways-to-add-observability-to-your-ai-agent-pipeline-36hn</link>
      <guid>https://dev.to/statewave/6-ways-to-add-observability-to-your-ai-agent-pipeline-36hn</guid>
      <description>&lt;p&gt;When an agent gives a wrong answer, the prompt log tells you what it was asked and what it said. It does not tell you which stored facts reached the prompt, which were filtered out, or which were stale. That gap is where agent debugging actually lives.&lt;/p&gt;

&lt;p&gt;We build &lt;a href="https://github.com/smaramwbc/statewave" rel="noopener noreferrer"&gt;Statewave&lt;/a&gt;, a memory runtime for AI agents, so most of what follows comes from instrumenting the retrieval side of that problem. Six layers, ordered from the one everyone already has to the one almost nobody does.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;1. Request tracing across the hop boundary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Start here because it is cheap and everything else attaches to it. An agent turn is rarely one process: the framework calls a model, the model requests a tool, the tool calls a memory service, the memory service queries Postgres.&lt;/p&gt;

&lt;p&gt;Propagate a request ID across every hop and log it at each. Without it you have four logs and no way to join them, which turns a five-minute investigation into an afternoon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt; is the standard worth adopting rather than inventing a correlation header. The span boundaries that matter for agents are: turn, model call, tool call, retrieval.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;2. Deterministic retrieval, so runs are comparable&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This one is a design decision rather than a tool, and it gates the usefulness of everything after it.&lt;/p&gt;

&lt;p&gt;If your memory layer searches at query time and samples differently per call, two identical requests return different context. You cannot compare a good run against a bad one because the inputs were never the same. Non-determinism does not just make debugging harder; it makes A/B comparison meaningless.&lt;/p&gt;

&lt;p&gt;Our approach is to move the expensive work off the query path: episodes compile into typed memories once per subject change, then &lt;a href="https://www.statewave.ai/blog/ai-agent-memory-vs-rag" rel="noopener noreferrer"&gt;context assembly ranks those compiled memories&lt;/a&gt; by kind priority, recency, task relevance, temporal validity, and semantic similarity. Same inputs, same bytes.&lt;/p&gt;

&lt;p&gt;Whatever memory layer you use, find out whether it guarantees this. If it does not, your first observability investment is making retrieval reproducible, not adding another dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;3. Retrieval receipts&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is the layer that answers "why did the agent say that?"&lt;/p&gt;

&lt;p&gt;A receipt is an immutable record of one context assembly: which memories and episodes were selected, the content hash of the assembled bundle, and the policy snapshot in force at the time. Ours are ULID-keyed, which gives chronological sorting at the database level without a separate index.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;{   "receipt_id": "01J8XKQ2M7...",   "subject_id": "user-42",   "selected_entries": ["mem_881", "mem_902", "ep_1204"],   "context_hash": "sha256:9f2c...",   "canonicalization_version": 3,   "policy_snapshot": {"bundle_hash": "sha256:41ab..."} }&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One detail worth stealing: the &lt;strong&gt;canonicalization_version&lt;/strong&gt; field. Hashing an assembled context is only useful if you can tell later whether a hash mismatch means the content changed or the hashing routine changed. Version the canonicalization and historical hashes stay verifiable.&lt;/p&gt;

&lt;p&gt;Prompt logs cannot do this job. A prompt log shows the final string; a receipt shows the decisions that produced it, including which memories were considered and rejected.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;4. Replay, with honest semantics&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Receipts let you re-run an assembly. What replay means, precisely, matters more than having it.&lt;/p&gt;

&lt;p&gt;Ours re-runs against &lt;strong&gt;current&lt;/strong&gt; memories using the &lt;strong&gt;original&lt;/strong&gt; policy bundle captured in the receipt. That is deliberately not byte-for-byte historical reproduction, and the difference is worth stating plainly:&lt;/p&gt;

&lt;p&gt;●      Memories may have been added, tombstoned, or superseded since. Those appear in the diff as added or removed entries.&lt;br&gt;&lt;br&gt;
●      New episodes ingested since will show up in scope.&lt;br&gt;&lt;br&gt;
●      Scoring code runs at whatever version is deployed now, so a changed heuristic shows up as a context hash change.&lt;/p&gt;

&lt;p&gt;True point-in-time reproduction needs memory snapshots, which we have not built. If you are designing this yourself, decide which semantic you need before building, because retrofitting snapshots is much harder than including them.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;5. Health scoring on the subject, not just the service&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Service health tells you Postgres is up. It does not tell you that one customer's memory has quietly degraded.&lt;/p&gt;

&lt;p&gt;Subject-level health scoring is the agent-specific version of an SLO. Ours computes a deterministic 0 to 100 score from signals already in the data: unresolved sessions, repeated issue patterns, urgency markers, idle open issues, and SLA breaches. Every factor returns its own contribution, so the score is explainable rather than a number nobody trusts.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;# every penalty is named and capped, so a score decomposes _UNRESOLVED_ISSUE_PENALTY = 15   # per open session, capped at 45 _REPEATED_ISSUE_PENALTY   = 20   # 2+ sessions sharing a pattern _ESCALATION_PENALTY     = 10   # per episode with urgency markers, capped at 20 _IDLE_OPEN_PENALTY        = 15   # open issue, no activity in 7+ days _SLA_BREACH_PENALTY       = 10   # per breaching session, capped at 20&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No ML, no stored state, computed on demand. Determinism is the constraint that keeps it useful: the same data always produces the same score, so a change in the number always means a change in the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;6. Alert on state transitions, not thresholds&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Threshold alerts on a score that moves every request produce noise until someone mutes the channel.&lt;/p&gt;

&lt;p&gt;Alert on transitions between named states instead. Ours emits &lt;strong&gt;subject.health_degraded&lt;/strong&gt; when a subject moves healthy to watch, watch to at_risk, or healthy to at_risk, and &lt;strong&gt;subject.health_improved&lt;/strong&gt; on the way back. Unchanged states emit nothing, because the deduplication compares against the last cached state.&lt;/p&gt;

&lt;p&gt;That single rule, transitions rather than levels, is the difference between an alert channel people read and one they filter.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Where to start&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you have none of this today, the order that pays fastest:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Request IDs across hops.&lt;/strong&gt; One afternoon, and it makes every later layer joinable.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make retrieval deterministic.&lt;/strong&gt; Nothing downstream is trustworthy without it.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Receipts on retrieval.&lt;/strong&gt; The single highest-value artifact for debugging agents, and the one most stacks lack.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transition-based alerts.&lt;/strong&gt; Cheap once you have a state model.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Replay and subject health are worth building when you have compliance requirements or a support workflow, and skippable if you do not.&lt;/p&gt;

&lt;p&gt;Our implementations of the above are Apache-2.0 and readable in the &lt;a href="https://github.com/smaramwbc/statewave" rel="noopener noreferrer"&gt;repo&lt;/a&gt; if you want the receipt schema or the health scoring rather than the summary. The &lt;a href="https://www.statewave.ai/blog/agent-memory-provenance-audit-trails" rel="noopener noreferrer"&gt;provenance and audit trail write-up&lt;/a&gt; covers the reasoning behind layer three in more depth.&lt;/p&gt;

&lt;p&gt;What does your agent observability stack look like? Particularly curious whether anyone has solved true point-in-time replay without snapshotting the whole store.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>debugging</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Best Open-Source AI Agent Memory Tools (Ranked by What They Guarantee)</title>
      <dc:creator>Statewave</dc:creator>
      <pubDate>Mon, 17 Aug 2026 06:37:25 +0000</pubDate>
      <link>https://dev.to/statewave/best-open-source-ai-agent-memory-tools-ranked-by-what-they-guarantee-451g</link>
      <guid>https://dev.to/statewave/best-open-source-ai-agent-memory-tools-ranked-by-what-they-guarantee-451g</guid>
      <description>&lt;p&gt;The best open-source AI agent memory tool is the one that guarantees the properties your system depends on: a self-hostable runtime, deterministic retrieval, provenance back to source events, and a permissive license. Ranked on those four, Statewave leads, ahead of Cognee, Mem0, Graphiti, and Letta.&lt;/p&gt;

&lt;p&gt;We checked the LICENSE file and README of every project below, reading the repository rather than the landing page. That produced the finding this post is built on: &lt;strong&gt;two of the four alternatives most often listed as self-hostable memory servers do not ship that from the repo people link to.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post ranks by capability, not by adoption. Adoption tells you what other teams picked last year. Capability tells you what will hold when your agent is in production.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: we build Statewave. Every claim below about another project is quoted from or linked to that project's public repository, so you can check each one yourself.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What makes an agent memory tool "best"?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Four properties separate a memory runtime from a fact store with a vector index bolted on. Every ranking below is scored against these.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-hostable as the actual product.&lt;/strong&gt; Not a client library that calls someone else's cloud. When data cannot leave your infrastructure, this is the first filter, and it eliminates more tools than teams expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deterministic retrieval.&lt;/strong&gt; The same subject, task, and token budget returns the same context. Without this, you cannot regression-test memory, and you cannot explain why two identical requests produced different answers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provenance.&lt;/strong&gt; Every compiled fact traces back to the raw event that produced it. When someone asks why the agent said something about their account, that has to be a query rather than an investigation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Permissive license.&lt;/strong&gt; Apache-2.0 or MIT, with no source-disclosure obligation and no seat-count trap. All five here pass this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Which open-source memory tools are worth shortlisting?&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Self-hosted product&lt;/th&gt;
&lt;th&gt;Deterministic retrieval&lt;/th&gt;
&lt;th&gt;Provenance&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Statewave&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes, single service&lt;/td&gt;
&lt;td&gt;Yes, documented contract&lt;/td&gt;
&lt;td&gt;Yes, per memory&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cognee&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Not advertised&lt;/td&gt;
&lt;td&gt;Graph lineage&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mem0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Library and self-hosted server; managed platform separate&lt;/td&gt;
&lt;td&gt;Not advertised&lt;/td&gt;
&lt;td&gt;Not surfaced in public docs&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Graphiti&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes, library plus a REST/MCP server&lt;/td&gt;
&lt;td&gt;Not advertised&lt;/td&gt;
&lt;td&gt;Edge validity dates&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Letta&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent runtime, not a memory layer&lt;/td&gt;
&lt;td&gt;Not advertised&lt;/td&gt;
&lt;td&gt;Not surfaced in public docs&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Statewave&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fitv5j5efc3sfiy3nlf8s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fitv5j5efc3sfiy3nlf8s.png" alt="Statewave" width="799" height="312"&gt;&lt;/a&gt;&lt;br&gt;
Statewave is a memory runtime rather than a memory library. It records raw events as append-only episodes, compiles them into typed memories with confidence scores and validity windows, then assembles a ranked, token-bounded context bundle on demand. Start the server first with npx &lt;a class="mentioned-user" href="https://dev.to/statewavedev"&gt;@statewavedev&lt;/a&gt;/statewave, then talk to it from the Python SDK (pip install statewave), which is a client rather than the runtime:&lt;/p&gt;

&lt;p&gt;from statewave import StatewaveClient&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;with StatewaveClient("&lt;a href="http://localhost:8100%22" rel="noopener noreferrer"&gt;http://localhost:8100"&lt;/a&gt;) as sw:    sw.create_episode(subject_id="user-42", source="chat", type="message",                      payload={"text": "Alice asked about pricing tiers"})    sw.compile_memories("user-42")    print(sw.get_context("user-42", task="answer pricing", max_tokens=1000).assembled_context)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It leads on all four criteria for one architectural reason: the expensive work happens at compile time, not query time. The same (subject_id, task, max_tokens) returns the same bytes against the same compiled state, which is what makes retrieval regression-testable. Every memory returns source_episode_ids, so provenance is a field rather than a feature request. Storage is Postgres with pgvector and nothing else, so self-hosting is one command rather than an infrastructure project: npx &lt;a class="mentioned-user" href="https://dev.to/statewavedev"&gt;@statewavedev&lt;/a&gt;/statewave, or git clone plus docker compose up -d if you want the compose file in your own tree.&lt;/p&gt;

&lt;p&gt;Where it costs you: compiled bundles are denser than plain fact-store lookups, so a single-hop query spends more tokens than it would against a flat key-value store. For workloads that are all single-hop and cost-sensitive, that trade is not worth it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams that must self-host and must be able to prove why a fact surfaced. Read the &lt;a href="https://www.statewave.ai/blog/agent-memory-provenance-audit-trails" rel="noopener noreferrer"&gt;ranked retrieval and provenance model&lt;/a&gt; for how the audit chain works.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Cognee&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2comicq9nzswdkzbwoug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2comicq9nzswdkzbwoug.png" alt="Cognee" width="800" height="317"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/topoteretes/cognee" rel="noopener noreferrer"&gt;Cognee&lt;/a&gt; builds a self-hosted knowledge graph with ontology grounding, combining vector embeddings with graph reasoning. It ranks second because it genuinely self-hosts and the graph gives you real lineage between entities.&lt;/p&gt;

&lt;p&gt;It ranks below Statewave on determinism, which is not advertised as a contract, and on retrieval shape: graph traversal answers "how is Alice connected to Acme" better than "what is the ranked context for Alice under a 1,000-token budget."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fits&lt;/strong&gt; domains where the relationships between entities are the product.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Mem0&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiffw28m3eai6821o77xi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiffw28m3eai6821o77xi.png" alt="Mem0" width="799" height="303"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/mem0ai/mem0" rel="noopener noreferrer"&gt;Mem0&lt;/a&gt; offers the shortest path from zero to an assistant that remembers a user, with an &lt;strong&gt;add / search / update / delete&lt;/strong&gt; surface that is four calls wide. The open-source library is Apache-2.0.&lt;/p&gt;

&lt;p&gt;It ranks third on our criteria rather than on quality, and not because it is library-only, which it no longer is: the repository ships a self-hosted Docker server with a dashboard, per-user API keys and a request audit log alongside the pip-installable library. The split that remains is between that open-source stack and the managed platform, and the README is explicit that its published scores "reflect Mem0's managed platform, which includes proprietary optimizations not available in the open-source SDK." The load-bearing gap for our criteria is retrieval composition: the public documentation still does not break down how the vector store, graph layer and reranker compose at retrieval time. If you need to explain a specific retrieval to an auditor, you will be reading source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fits&lt;/strong&gt; consumer-facing personalization where speed to first result matters more than explainability.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Graphiti&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqyedmugyumz45rjygff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqyedmugyumz45rjygff.png" alt="Graphiti" width="800" height="320"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/getzep/graphiti" rel="noopener noreferrer"&gt;Graphiti&lt;/a&gt; is a temporal context graph engine that tracks when facts became valid and invalid, which is a real answer to the stale-memory problem.&lt;/p&gt;

&lt;p&gt;The ranking note here is the one that surprised us. The repository most lists point to for this project is &lt;a href="https://github.com/getzep/zep" rel="noopener noreferrer"&gt;getzep/zep&lt;/a&gt;, and its own README states it is &lt;strong&gt;not&lt;/strong&gt; the product: it holds example code and integrations for Zep Cloud, which is managed. Graphiti is the self-hostable open-source component, and it is more than a library: the repository ships a FastAPI REST service under server/ and an MCP server under mcp_server/, with a prebuilt zepai/graphiti image published on each graphiti-core release. A team whose requirement is "runs in our VPC" does not have to write that service. What it still writes is the retrieval contract, because what Graphiti returns is graph queries rather than a ranked, token-bounded, deterministic bundle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fits&lt;/strong&gt; teams that want temporal graph semantics and are willing to build the retrieval contract themselves.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. Letta&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F64zcnzo4kn0z4tsgjvd9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F64zcnzo4kn0z4tsgjvd9.png" alt="Letta" width="799" height="316"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/letta-ai/letta" rel="noopener noreferrer"&gt;Letta&lt;/a&gt; carries the MemGPT lineage and is the most capable system here at the thing it actually does, which is running agents. It ranks last only because it is answering a different question: it is an agent runtime that owns the reasoning loop, the tool calls and the context management.&lt;/p&gt;

&lt;p&gt;The second repository note: that README states the repo is the legacy server and active development has moved to the Letta Agent repo. Adopting Letta means replacing your orchestration, not adding memory to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fits&lt;/strong&gt; teams with no agent stack yet who want the whole thing in one decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Which one should you pick?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Pick by the constraint that is load-bearing for you, in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data cannot leave your infrastructure.&lt;/strong&gt; Statewave, Cognee, or Graphiti. Mem0's open-source stack qualifies; its managed platform does not.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need to explain retrievals after the fact.&lt;/strong&gt; Statewave. Provenance and deterministic ranking are the two properties that make this answerable.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relationships between entities are the product.&lt;/strong&gt; Cognee or Graphiti.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You have no agent framework yet.&lt;/strong&gt; Letta.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want something working this afternoon.&lt;/strong&gt; Mem0.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One honest note on all of the above, including ours: no neutral, third-party benchmark covers all five on the same task with the same corpus. Every comparison in this category, this one included, is vendor-shaped. Run the eval on your own data before committing.&lt;/p&gt;

&lt;p&gt;When the first two constraints are the binding ones, &lt;a href="https://github.com/smaramwbc/statewave" rel="noopener noreferrer"&gt;Statewave is Apache-2.0 and boots with one command&lt;/a&gt;: &lt;strong&gt;npx &lt;a class="mentioned-user" href="https://dev.to/statewavedev"&gt;@statewavedev&lt;/a&gt;/statewave.&lt;/strong&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Best Memory Layer for Multi-Agent AI Systems With Shared Context</title>
      <dc:creator>Statewave</dc:creator>
      <pubDate>Mon, 17 Aug 2026 06:30:06 +0000</pubDate>
      <link>https://dev.to/statewave/best-memory-layer-for-multi-agent-ai-systems-with-shared-context-2900</link>
      <guid>https://dev.to/statewave/best-memory-layer-for-multi-agent-ai-systems-with-shared-context-2900</guid>
      <description>&lt;p&gt;The best memory layer for a multi-agent system is the one that keys memory to the subject rather than the agent, returns the same context to concurrent callers, enforces per-caller policy below the agents, and records which memories reached which agent. Scored on those four, Statewave leads, ahead of Mem0, LangMem, Graphiti and Letta.&lt;/p&gt;

&lt;p&gt;We went looking for one specific primitive in each of these five projects, reading their repositories and public docs rather than their landing pages. The finding: &lt;strong&gt;we could not locate a documented per-caller policy primitive in any of the four alternatives.&lt;/strong&gt; Every one of them can share memory between agents. We found no way in their public docs to say "the billing agent sees this memory and the FAQ agent does not" inside the memory layer itself.&lt;/p&gt;

&lt;p&gt;That gap is the whole subject of this post, because it is the requirement that only appears once you have more than one agent.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: we build Statewave. Every claim below about another project comes from that project's public repository or documentation, so you can check each one yourself.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why does multi-agent memory need different properties?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Single-agent memory has one reader. Multi-agent memory has several readers with different privileges, arriving concurrently, contributing facts that the others will act on. Three failures follow, and none are fixed by a bigger context window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-agent amnesia.&lt;/strong&gt; A researcher agent gathers requirements over six turns. A coder agent picks up the task with none of it, and the user re-explains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval drift between callers.&lt;/strong&gt; Two agents ask the same question about the same customer in the same minute and get different context, so they reach different conclusions from identical underlying facts. Now you are debugging a disagreement neither agent caused.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Uniform visibility.&lt;/strong&gt; Every agent that can read the pool can read all of it, including the contractor's coding agent and the public-facing FAQ bot.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What are the four requirements?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Memory keyed to the subject.&lt;/strong&gt; The unit of memory should be the entity it concerns, a customer or an account or a repository, not the agent that recorded it. Key memory by agent, and sharing becomes copying, and copies drift. Key it by session and it dies with the session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Determinism across concurrent callers.&lt;/strong&gt; The same subject, task and token budget returns the same bytes to every agent that asks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-caller policy.&lt;/strong&gt; A rule layer below the agents that decides, per request, whether a memory is passed, redacted, or denied based on who is asking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-agent audit.&lt;/strong&gt; A record of which memories entered which agent's context, under which rules, at what time. A chat transcript is not this artifact.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Which memory layer handles shared context best?&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Subject-keyed&lt;/th&gt;
&lt;th&gt;Deterministic&lt;/th&gt;
&lt;th&gt;Per-caller policy&lt;/th&gt;
&lt;th&gt;Cross-agent audit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Statewave&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes, subjects are the primitive&lt;/td&gt;
&lt;td&gt;Yes, documented contract&lt;/td&gt;
&lt;td&gt;Yes, labels plus policy bundles&lt;/td&gt;
&lt;td&gt;Yes, state-assembly receipts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mem0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes, user and agent scoping&lt;/td&gt;
&lt;td&gt;Not advertised&lt;/td&gt;
&lt;td&gt;Not found in public docs&lt;/td&gt;
&lt;td&gt;Request-level audit log, not per-memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LangMem&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes, store namespaces&lt;/td&gt;
&lt;td&gt;Not advertised&lt;/td&gt;
&lt;td&gt;Not found in public docs&lt;/td&gt;
&lt;td&gt;Not surfaced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Graphiti&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Graph-wide, not subject-scoped&lt;/td&gt;
&lt;td&gt;Not advertised&lt;/td&gt;
&lt;td&gt;Not found in public docs&lt;/td&gt;
&lt;td&gt;Edge validity only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Letta&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Per-agent by design&lt;/td&gt;
&lt;td&gt;Not advertised&lt;/td&gt;
&lt;td&gt;Not found in public docs&lt;/td&gt;
&lt;td&gt;Not surfaced&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Statewave&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fumoy8c6h3kmh0hubmlzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fumoy8c6h3kmh0hubmlzi.png" alt="Statewave" width="799" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Statewave organizes everything around &lt;strong&gt;subjects&lt;/strong&gt;, the entity memory is about. Four agents reading &lt;strong&gt;customer-8821&lt;/strong&gt; see one pool of episodes and compiled facts, so sharing is the default rather than a protocol you write.&lt;/p&gt;

&lt;p&gt;Determinism comes from moving the work off the query path. Episodes compile into typed memories once per subject change, and context assembly ranks those compiled memories by kind priority, recency, task relevance, temporal validity, and semantic similarity. Same inputs, same bundle, whichever agent asks.&lt;/p&gt;

&lt;p&gt;Per-caller policy is the piece that separates it here. Memories carry sensitivity labels such as &lt;strong&gt;pii, financial,&lt;/strong&gt; and &lt;strong&gt;secret&lt;/strong&gt;. Every context request carries a caller identity. A declarative YAML policy bundle, content-hashed and immutable, decides per request whether each memory is denied, redacted, or passed. Policies run in &lt;strong&gt;log_only&lt;/strong&gt; mode first, recording what they would have filtered, before anyone enables enforcement.&lt;/p&gt;

&lt;p&gt;Cross-agent audit is the state-assembly receipt: an immutable record of which memories and episodes went into one specific bundle, hashed, with the policy snapshot attached.&lt;/p&gt;

&lt;p&gt;Where it costs you: cross-subject retrieval is not first-class. An agent needing facts about both Alice and Acme makes two calls. Subject granularity is a real design decision and getting it wrong means re-modelling later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fits&lt;/strong&gt; systems where agents have different privileges or the data is sensitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Mem0&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fddyzcdxdhopmf6wavrab.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fddyzcdxdhopmf6wavrab.png" alt="Mem0" width="799" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mem0ai/mem0" rel="noopener noreferrer"&gt;Mem0&lt;/a&gt; supports user, agent and session scoping, so several agents can read one user's memory pool. It ranks second because that sharing works and the API is small enough to wire up quickly.&lt;/p&gt;

&lt;p&gt;It ranks below Statewave on the second half of the list. Determinism is not advertised, and the composition of the vector store, graph layer and reranker is not broken down publicly, so two concurrent callers have no documented guarantee of identical context. We found no per-caller policy primitive: the self-hosted server issues per-user API keys, which authenticate a caller rather than decide which memories that caller may read, and Entity-Scoped Memory, which partitions the pool by user, agent, app or run, is marked platform-only. On audit it is closer than the rest of the field: the self-hosted stack keeps a request audit log. That is a record of calls, not a record of which memories entered which agent's context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fits&lt;/strong&gt; multi-agent systems where every agent is trusted equally.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. LangMem&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi2qfe5wwt0sbyrbs6ivu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi2qfe5wwt0sbyrbs6ivu.png" alt="LangMem" width="799" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/langchain-ai/langmem" rel="noopener noreferrer"&gt;LangMem&lt;/a&gt; gives LangGraph applications a store with namespaces, which is a clean sharing boundary when every agent lives inside LangGraph.&lt;/p&gt;

&lt;p&gt;The constraint is the boundary itself. A LangGraph support agent, an OpenAI Agents SDK sales copilot and a custom Express service should share one memory surface, and the shared store reaches only the first. The package splits in two here, and the distinction is worth naming: LangMem's core primitives are storage-agnostic and travel into any application, while the shared memory that agents actually read is LangGraph's own long-term memory store. The portable half travels; the shared surface does not. Ranking, budgeting and provenance on top of the store are code you write.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fits&lt;/strong&gt; teams whose agents all run inside one framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Graphiti&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzd0x0prvfyyfyojnuskv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzd0x0prvfyyfyojnuskv.png" alt="LangMem" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/getzep/graphiti" rel="noopener noreferrer"&gt;Graphiti&lt;/a&gt; tracks when facts became valid and invalid across a temporal context graph, which handles the stale-fact problem well when several agents write to one graph.&lt;/p&gt;

&lt;p&gt;It ranks fourth for multi-agent work because the graph is global rather than subject-scoped, so isolating one entity's context is a traversal you design. Per-caller filtering is application code above Graphiti, whether you embed the package or run its REST service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fits&lt;/strong&gt; shared-context systems where entity relationships carry the meaning.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. Letta&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz4ys3juxpn0ym8i8nvf6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz4ys3juxpn0ym8i8nvf6.png" alt="Letta" width="799" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/letta-ai/letta" rel="noopener noreferrer"&gt;Letta&lt;/a&gt; is a stateful agent runtime with the MemGPT lineage, and it is strong at what it does. For this scorecard it ranks last because its memory model is per-agent by design: memory belongs to an agent rather than to an entity several agents share.&lt;/p&gt;

&lt;p&gt;Multi-agent sharing in that model means passing state between runtimes rather than reading one pool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fits&lt;/strong&gt; teams that want a complete agent runtime and whose agents do not need a common memory surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How do you choose?&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Do your agents have different privileges?&lt;/strong&gt; If yes, per-caller policy is the binding requirement and it narrows the field to one.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do agents run in more than one framework?&lt;/strong&gt; If yes, the memory layer has to be an HTTP service, which rules out framework-native stores.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Will anyone ask which agent saw what?&lt;/strong&gt; If yes, you need receipts, not logs.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Are your agents symmetric and your data non-sensitive?&lt;/strong&gt; Then requirements three and four do not bind, and Mem0 or LangMem is the faster path.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Being straight about our own gaps: tenant isolation is query-scoped at the application layer rather than Postgres row-level security, rate limiting is keyed per IP, and there is no admin-action identity yet, so label promotions record when but not who. One more, since it touches the audit argument above: receipt replay re-runs the original retrieval against today's memories using the original policy bundle, which is not byte-for-byte historical reproduction. Any of those may disqualify the approach for your compliance posture.&lt;/p&gt;

&lt;p&gt;No neutral, third-party benchmark covers all five on the same multi-agent task, ours included. The scorecard above is a documentation review, and the honest way to use it is as a list of questions to ask each vendor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/smaramwbc/statewave" rel="noopener noreferrer"&gt;Statewave is Apache-2.0 and self-hosted on Postgres&lt;/a&gt;. Many agents point at one memory service over HTTP or MCP, and your orchestration stays where you built it.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
