CrewAI Memory Backends — Long-Term Memory (StorageBackend)¶
CrewAI 1.10+ has one unified Memory engine: it extracts facts from task outputs, infers a hierarchical scope and categories with an LLM, consolidates near-duplicates, and ranks recall by a composite of semantic similarity, recency decay and importance. Underneath sits a pluggable StorageBackend protocol, and the built-in store is LanceDB on local disk.
The crewai-memory family provides StorageBackend implementations on managed cloud databases, each using its backend's native vector search. CrewAI's engine keeps doing all the thinking; the backend just becomes shared and durable.
Memory vs. Flow persistence
This is a different layer from the crewai-persistence-* packages. Flow persistence snapshots the whole state of one flow run for resume/fork; memory stores discrete facts for cross-run retrieval. CrewAI's docs keep them separate, and so do we.
Packages¶
| Package | Backend | Vector search via |
|---|---|---|
crewai-memory-core |
shared base — build your own with 5 primitives | portable cosine fallback |
crewai-memory-dynamodb |
Amazon DynamoDB | native SearchVectors |
crewai-memory-postgres |
PostgreSQL | pgvector (<=>, HNSW) |
crewai-memory-cosmosdb |
Azure Cosmos DB (NoSQL) | VectorDistance (diskANN) |
crewai-memory-firestore |
Google Firestore | find_nearest (vector index) |
Usage¶
from crewai import Crew
from crewai.memory import Memory
from crewai_memory_dynamodb import DynamoDBMemoryBackend # or PostgresMemoryBackend / CosmosDBMemoryBackend / FirestoreMemoryBackend
backend = DynamoDBMemoryBackend(table_name="crewai-memory", dimensions=3072) # match the Memory embedder
memory = Memory(storage=backend)
crew = Crew(agents=[...], tasks=[...], memory=memory)
memory.remember("The customer prefers email over phone", scope="/customers/acme", categories=["preference"])
matches = memory.recall("how should we contact acme?", scope="/customers")
Register once for every Crew(memory=True) instead:
from crewai.memory.storage.factory import set_memory_storage_factory
set_memory_storage_factory(lambda spec: DynamoDBMemoryBackend("crewai-memory", dimensions=3072))
Agents then recall before each task and remember extracted facts after each run automatically.
What a backend must do¶
The protocol has fifteen methods, but they collapse into four capabilities, all handled by crewai-memory-core over five primitives (_put, _get, _delete_ids, _scan, optional _vector_search):
| Capability | Methods | How the core does it |
|---|---|---|
| Filtered vector search | search, asearch |
provider's native ANN in the scope subtree, then categories / metadata_filter / min_score on the candidates |
| Point read/write | save, update, get_record, touch_records |
_put / _get |
| Filtered scan & delete | list_records, delete, count, reset |
_scan + Python filters (older_than, categories, metadata) |
| Scope introspection | get_scope_info, list_scopes, list_categories |
_scan + path arithmetic |
Scope prefixes are path-aware: /a matches /a and /a/..., never /ab.
Table PK = scope, SK = id, a by_id GSI, and a vector index on embedding with PK as an inline filter. DynamoDB allows only equality on a string search-schema attribute, so a subtree search runs one SearchVectors per concrete scope and merges by similarity (1 - distance). Vector index fixed at creation; boto3>=1.43.78.
One table with embedding vector(dims), HNSW cosine index, B-tree on scope. Search is a single ORDER BY embedding <=> query with scope = p OR scope LIKE 'p/%'. Needs the pgvector extension.
Container partitioned by /scope, vector embedding policy on /embedding + diskANN index. Search is one SQL query ordered by VectorDistance. Needs the EnableNoSQLVectorSearch account capability; policy fixed at container creation.
One collection, embedding stored as a Vector, composite index scope ASC + embedding (flat) created via the Admin API (asynchronous). A subtree search is two find_nearest calls (exact scope + scope/… range) merged by similarity; root searches use the scope >= "/" range so one index serves everything.
Testing¶
crewai_memory_core.contract is one importable suite that every provider runs against its real backend, including an end-to-end pass through CrewAI's Memory engine with FakeEmbedder and zero LLM calls (all record fields supplied → fast-path insert; consolidation_threshold=1.0; recall(depth="shallow")).
With on_prune¶
When flow state carries a messages list pruned by our crewai-persistence-* packages, the reducer's on_prune hook can feed the pruned turns straight into this memory:
from agentstate_reducer import Background
def remember(pruned, namespace):
text = "\n".join(m["content"] for m in pruned)
memory.remember_many(memory.extract_memories(text), scope=namespace) # two LLM calls → run off-path
reducer = MessageReducer(config=ReducerConfig(max_messages=20, on_prune=[Background(remember)]))
Here the namespace is a CrewAI scope path string (for example /flow/<id> or /user/kamal).