Memory backends
Five backends, all behind the same Memory protocol. The memory=
URL resolver picks one for you; the underlying classes are exported
for direct construction.
Feature matrix
| Backend | Persistence | Recall | Hybrid recall_scored | user_id | Async network | Fact store |
|---|---|---|---|---|---|---|
InMemoryMemory | none | cosine over Python list | native BM25 | yes | n/a | InMemoryFactStore |
VectorMemory | none | cosine over Python list | native BM25 + cosine + RRF | yes | n/a | (no facts) |
SqliteMemory | single file | cosine; full table scan | shim (neutral scores) | yes | no | SqliteFactStore |
ChromaMemory | on-disk or hosted | Chroma’s HNSW | shim (neutral scores) | yes | depends on backend | ChromaFactStore |
PostgresMemory | Postgres + pgvector | pgvector ANN | shim (neutral scores) | yes | yes (asyncpg) | PostgresFactStore |
RedisMemory | Redis (optional RediSearch) | RediSearch HNSW (optional) | shim (neutral scores) | yes | yes (redis.asyncio) | RedisFactStore |
Native = the backend computes BM25 + vector cosine + RRF
internally and populates EpisodeMatch.bm25_score /
vector_score. Shim = recall_scored wraps the existing
recall() results with a neutral score=1.0 via
default_recall_scored. Native plumbing for shim backends can land
later as additive changes. See Hybrid recall.
InMemoryMemory. Default
Zero deps, zero setup. Lost on process exit.
from loomflow import Agent, InMemoryMemory
memory = InMemoryMemory()
agent = Agent("...", memory=memory)
# Or just by URL:
agent = Agent("...", memory="inmemory")Bounded by default to 100k users with a 24h idle TTL. See Bounded in-process state.
SqliteMemory. Persistent, no server
Single file, no infrastructure. Great for prototypes, single-instance bots, and demos.
agent = Agent("...", memory="sqlite:./bot.db")
# Or explicit:
from loomflow.memory.sqlite import SqliteMemory
memory = await SqliteMemory.connect("./bot.db", with_facts=True)
agent = Agent("...", memory=memory)Uses cosine similarity over an in-memory vector cache backed by SQLite storage. Recall is fast up to ~50K episodes; for larger working sets move to Postgres or Chroma.
ChromaMemory. Ephemeral or persistent
Lazy chromadb import. Set path= to persist on disk; omit for
ephemeral.
agent = Agent("...", memory="chroma") # ephemeral
agent = Agent("...", memory="chroma:./chroma-db") # persistent
# Explicit:
from loomflow.memory.chroma import ChromaMemory
from loomflow.memory.embedder import OpenAIEmbedder
memory = ChromaMemory.local(
"./chroma-db",
with_facts=True,
embedder=OpenAIEmbedder(),
)Install: pip install 'loomflow[chroma]'.
PostgresMemory. Production durable
Postgres + pgvector. Async via asyncpg. Schema migrations are
idempotent, init_schema() runs once on first connect and is safe
to re-run.
agent = Agent("...", memory="postgres://user:pw@host/db")
# Explicit:
from loomflow.memory.embedder import OpenAIEmbedder
from loomflow.memory.postgres import PostgresMemory
memory = await PostgresMemory.connect(
dsn="postgres://user:pw@host/db",
embedder=OpenAIEmbedder("text-embedding-3-small"),
with_facts=True,
)Install: pip install 'loomflow[postgres]'.
The schema lives in three tables, episodes, memory_blocks, and
facts (when with_facts=True). All carry a user_id column with a
default for the anonymous bucket and indexes for the multi-tenant
queries.
RedisMemory. Fast multi-instance
Redis with optional RediSearch HNSW vector index. Async via
redis.asyncio.
agent = Agent("...", memory="redis://localhost:6379/0")
# Explicit:
from loomflow.memory.embedder import OpenAIEmbedder
from loomflow.memory.redis import RedisMemory
memory = await RedisMemory.connect(
url="redis://localhost:6379/0",
embedder=OpenAIEmbedder(),
with_facts=True,
use_redisearch=True, # opt-in HNSW
)Install: pip install 'loomflow[redis]'.
Without RediSearch the backend falls back to brute-force cosine; with it, vector recall scales to millions of chunks with sub-10ms latency.
LazyMemory. What memory="postgres://..." actually returns
Agent(...) is synchronous, but PostgresMemory.connect() and
RedisMemory.connect() are async. The resolver returns a
LazyMemory proxy that opens the connection on the first agent.run:
agent = Agent("...", memory="postgres://...") # no connection yet
await agent.run("hi", user_id="alice") # connection opens hereLazyMemory forwards every call to the inner Memory once
materialized, so it’s transparent.
Pick a backend by your durability + scale story. Single instance, ephemeral data → InMemory. Single instance, durable → SQLite. Multi-instance or large vector recall → Postgres / Redis. Want hosted vector ANN with metadata filters → Chroma. The agent behaviour is identical across all five.