Memory
Memory lets an agent remember things across conversations: what a
user told it last week, their preferences, facts about them. Use it
whenever the same users come back to your agent more than once. Pass
memory= to Agent, pass user_id= to agent.run(), and every run
reads from and writes to that user’s memory.
Memory holds three kinds of state: episodes (past conversation
turns), working blocks (small per-user scratch data that is always
injected), and Facts (structured (subject, predicate, object)
triples extracted from chats). One Agent with one Memory
partitions automatically by user_id, so you don’t need any
namespace plumbing in your app.
from loomflow import Agent
# In-memory (default; lost on restart)
agent = Agent("...", model="claude-opus-4-7", memory="inmemory")
# Single-file SQLite (persistent, no server)
agent = Agent("...", model="claude-opus-4-7", memory="sqlite:./bot.db")
# Chroma (ephemeral / persistent)
agent = Agent("...", model="claude-opus-4-7", memory="chroma")
agent = Agent("...", model="claude-opus-4-7", memory="chroma:./chroma-db")
# Postgres + pgvector
agent = Agent("...", model="claude-opus-4-7", memory="postgres://user:pw@localhost/loomflow")
# Redis (with optional RediSearch HNSW vector index)
agent = Agent("...", model="claude-opus-4-7", memory="redis://localhost:6379/0")Those are five of the six backends. The sixth, VectorMemory
(in-process, with embedding recall), has no URL form; construct it
directly and pass the instance. See Backends.
Pass user_id= to agent.run and the framework partitions every
recall and write automatically:
await agent.run("Hi, I'm Alice and I live in Tokyo.", user_id="alice")
await agent.run("Hi, I'm Bob.", user_id="bob")
# Alice's lives_in fact never surfaces in Bob's recall.user_id must be a string, or None for the anonymous bucket;
anything else raises TypeError, so pass numeric ids through str().
What ships by default
- Auto fact extraction. Every
agent.run()against a real model pulls structured(subject, predicate, object)claims into the bi-temporal fact store. It is on by default for Anthropic, OpenAI and LiteLLM models. - Auto-attached fact store. The resolver wires a fact store for
every backend automatically (pass
with_facts=Falseto skip it). - Auto-picked embedder.
OpenAIEmbedder("text-embedding-3-small")ifOPENAI_API_KEYis set,HashEmbedder()otherwise. SetLOOMFLOW_EMBEDDER(for exampleLOOMFLOW_EMBEDDER=hash) to force a choice. user_idpartition. Every backend honours the multi-tenant contract, so one shared file or pool serves any number of users.- Lazy connect for async backends. Postgres and Redis URLs return
a
LazyMemoryproxy; the connection opens on the firstagent.run, soAgent(...)stays synchronous. A Postgres URL also creates its tables then.
Read more
Embedder protocol for custom backends.Hybrid recallrecall_scored(): BM25 + vector + RRF with a score breakdown, on every backend.Bi-temporal factsHow supersession works. Querying historical state at a past valid_at.Fact graphrecall_graph(): multi-hop traversal over facts with point-in-time replay and explicit entity merging.Token-budgeted injectionTuning(memory_token_budget=). Cap the seed memory block; rank by relevance × recency decay. Working blocks stay pinned.GDPR opsprofile() / forget() / export(). Right-to-be-forgotten and DSAR on every backend.Three ways to construct memory
1. URL string: the resolver picks defaults
agent = Agent("...", model="claude-opus-4-7", memory="postgres://localhost/loomflow")2. Dict form for tweaks
agent = Agent("...", model="claude-opus-4-7", memory={
"backend": "chroma",
"path": "./chroma-db",
"namespace": "tenant_a",
"embedder": "openai-large",
"with_facts": True,
})3. Explicit instance for full control
from loomflow.memory.chroma import ChromaMemory
from loomflow.memory.embedder import OpenAIEmbedder
memory = ChromaMemory.local(
"./chroma-db", with_facts=True, embedder=OpenAIEmbedder()
)
agent = Agent("...", model="claude-opus-4-7", memory=memory)Tools see the active scope automatically
Inside any tool, get_run_context() returns the live RunContext:
from loomflow import get_run_context, tool
@tool
async def my_orders() -> list[dict]:
ctx = get_run_context()
return await db.query("orders", user_id=ctx.user_id)You don’t need to thread user_id through every tool signature.
Memory vs Vector stores. Memory is for conversational state:
episodes, working blocks, and Facts built up over time. VectorStore
(see RAG) is for document corpora: what your agent
retrieves from but doesn’t build up. They are different
primitives with different backends.