Memory in CrewAI
Memory lets a crew remember across runs. In CrewAI 1.15 it is one "unified" memory: the model decides what to store, gives it a scope and an importance, and recalls it into later prompts. A new process remembered Ravi’s bakery chatbot - but memory also stored the agent’s own advice as fact, took 45 seconds a run, and with memory=True and no key it failed silently.
What you will be able to do
- Explain what memory adds to a crew, and what it costs
- Configure memory for a local setup: llm, embedder, storage
- Store and recall memories directly with remember() and recall()
- Read how memories are scored and scoped
- Give a crew memory across runs, and see what it puts in the prompt
- Avoid the silent failure of memory=True
The idea, in plain English
Without memory, every kickoff() starts from nothing - like LangGraph without a checkpointer (Lesson 3.6). Memory lets a crew remember facts from earlier runs: who the user is, what they are building, what was decided.
The outline of older tutorials talks about short-term, long-term and entity memory. CrewAI 1.15 replaced them with one unified Memory: after a task, the model pulls out facts and stores them; before a task, relevant facts are added to the prompt.
Memory needs an embedder (text to vectors). Our Ollama would not make embeddings with llama3, so - as in Lesson 2.15 - we used a small local embedder that needs no download. CrewAI 1.15.27, lancedb 0.30.0, llama3 on Ollama.
Worked example: A course assistant that hears "I am Ravi, building a bakery chatbot" in one run - and uses it in the next run, in a new process. Plus the memory API on its own.
1 - Run 1 stores facts
After the task, llama3 extracts memory statements and analyses each one (scope, importance, categories). 9 requests, 43.8 s.
Two separate processes, one memory folder. Real runs, requests recorded.
Words you will see in this lesson
Memory words.
MemoryFacts a crew keeps between runs.EmbedderTurns text into a list of numbers (a vector), so similar texts can be found.Vector storeA database of vectors. CrewAI uses LanceDB, in a folder on disk.remember / recallStore a fact / find facts related to a question.ScopeA folder-like path for memories, like /AI/industry/retail. Chosen by the model.ImportanceA number from 0 to 1 the model gives each memory.An everyday example: a shop assistant’s notebook
- A good shop assistant writes notes about regular customers: "Ravi - new to cooking, likes simple recipes". (Storing memories.)
- Next time Ravi comes in, they read the notes before helping. (Recall into the prompt.)
- But if they also write down their own guesses as facts, the notebook slowly fills with things nobody said. (The agent’s advice stored as memory.)
- And writing notes after every conversation takes time. (Memory’s extra model calls.)
Step 1 - set memory up for a local machine
Memory has three parts, and their defaults all need OpenAI:
- llm - the model that decides what to store. Default: gpt-5.4-mini. We use llama3.
- embedder - text to vectors. Default: OpenAI. The Ollama embedder needs pip install ollama - and an embedding model.
- storage - where vectors are kept. Default: LanceDB. We give it a folder: storage="./mem_db".
- Our Ollama 0.34.1 refused embeddings with llama3: "This server does not support embeddings." A real setup pulls an embedding model (for example ollama pull nomic-embed-text).
- To avoid a download we passed a function: a tiny embedder that counts words, like the fake embeddings of Lesson 2.15. It finds memories that share words - it does not understand meaning.
"""A tiny local embedder: counts words into 256 buckets (the 'fake embeddings' idea from Lesson 2.15).
Good enough to find memories that share words - not a real semantic model."""
import hashlib, math, re
STOP = {"the", "a", "an", "is", "are", "and", "or", "of", "to", "in", "on", "for", "with", "what", "how", "i", "he", "his", "s"}
def embed(texts: list[str]) -> list[list[float]]:
vectors = []
for text in texts:
v = [0.0] * 256
for word in re.findall(r"[a-z0-9]+", text.lower()):
if word in STOP:
continue
word = word[:-1] if word.endswith("s") and len(word) > 3 else word # bakery/bakeries, answer/answers
v[int(hashlib.md5(word.encode()).hexdigest(), 16) % 256] += 1.0
norm = math.sqrt(sum(x * x for x in v)) or 1.0
vectors.append([x / norm for x in v])
return vectorsembedder={"provider": "ollama", "config": {"model_name": "llama3"}}
RuntimeError: Memory requires an embedder for vector search but initialization failed: The ollama python package is not installed. Please install it with `pip install ollama`
after pip install ollama
ollama._types.ResponseError: This server does not support embeddings. Start it with `--embeddings` (status code: 501)Example 1 - remember and recall
Use the Memory object on its own first:
- remember(text) stores a fact. llama3 analyses it first - so 3 facts took 18.2 s.
- drain_writes() waits until everything is saved.
- recall(question) returns matches with a score. With our word embedder this took 0.0 s.
import time
from crewai import LLM
from crewai.memory.unified_memory import Memory
from simple_embedder import embed
memory = Memory(
llm=LLM(model="ollama/llama3", base_url="http://localhost:11434", temperature=0), # analyses what to store
embedder=embed, # turns text into vectors (local)
storage="./mem_db", # a folder on disk
)
start = time.perf_counter()
memory.remember("Ravi is a beginner. He prefers short answers with one everyday example.")
memory.remember("Ravi's project is a support chatbot for a bakery in Hyderabad.")
memory.remember("The course uses llama3 through Ollama on Ravi's laptop.")
memory.drain_writes() # wait until everything is saved
print(f"saved 3 memories in {time.perf_counter() - start:.1f} s")
for question in ["How should I answer Ravi?", "What is Ravi building?", "What is the capital of France?"]:
start = time.perf_counter()
matches = memory.recall(question)
print(f"--- {question} ({time.perf_counter() - start:.1f} s)")
for m in matches[:3]:
print(f" score {m.score:.2f} {m.record.content}")
memory.close()saved 3 memories in 18.2 s
--- How should I answer Ravi? (0.0 s)
score 0.66 Ravi's project is a support chatbot for a bakery in Hyderabad.
score 0.66 The course uses llama3 through Ollama on Ravi's laptop.
--- What is Ravi building? (0.0 s)
score 0.67 Ravi's project is a support chatbot for a bakery in Hyderabad.
score 0.66 The course uses llama3 through Ollama on Ravi's laptop.
--- What is the capital of France? (0.0 s)
score 0.63 The course uses llama3 through Ollama on Ravi's laptop.
score 0.63 Ravi's project is a support chatbot for a bakery in Hyderabad.Reading Example 1: scope, importance and scores
Two surprises. We listed what was stored to understand them:
- llama3 chose a scope for each memory: the bakery fact went to /AI/industry/retail. You did not decide this - the model did.
- Each memory got an importance: 0.8 for the bakery and llama3 facts, but only 0.5 - the default - for "Ravi is a beginner", the fact that answers "How should I answer Ravi?". That memory was not returned for it.
- The score is 0.5 x meaning + 0.3 x recency + 0.2 x importance (from CrewAI’s source). Fresh, "important" memories score well even with no match.
- That is why "What is the capital of France?" still scored 0.63. A score is not a yes/no - set a minimum, or check the results.
- [/AI] importance=0.8 categories=['everyday_example']
The course uses llama3 through Ollama on Ravi's laptop.
- [/AI/industry/retail] importance=0.8 categories=['everyday_example', 'industry']
Ravi's project is a support chatbot for a bakery in Hyderabad.
- [/] importance=0.5 categories=['beginner', 'short_answers', 'everyday_example']
Ravi is a beginner. He prefers short answers with one everyday example.
/ (3 records)
/AI (2 records)
/AI/industry (1 records)
/AI/industry/retail (1 records)Example 2 - a crew that remembers across runs
Now give a crew the memory. Run 1 and run 2 are separate processes; only the memory folder connects them:
- Crew(..., memory=memory) - the Memory object from Step 1, with storage="./crew_mem".
- Run 1: "Hi, I am Ravi. I am new to Python and I am building a support chatbot for my bakery in Hyderabad."
- Run 2: "What should I build first for my project?" - the word "bakery" is not in the question.
- We recorded every request, and ran the same run-2 question once without memory to compare.
import sys, time
from crewai import LLM, Agent, Crew, Task
from crewai.memory.unified_memory import Memory
from simple_embedder import embed
base = "http://127.0.0.1:11500" # through the spy, to see the prompts
memory = Memory(llm=LLM(model="ollama/llama3", base_url=base, temperature=0),
embedder=embed, storage="./crew_mem")
helper = Agent(role="Course assistant", goal="Help each student with their own project",
backstory="You give short, practical advice.",
llm=LLM(model="ollama/llama3", base_url=base, temperature=0))
task = Task(description="The student says: {message}", expected_output="A short, helpful reply.", agent=helper)
crew = Crew(agents=[helper], tasks=[task], memory=memory)
start = time.perf_counter()
result = crew.kickoff(inputs={"message": sys.argv[1]})
memory.drain_writes()
print("--- reply:", result.raw.strip())
print(f"--- {time.perf_counter() - start:.1f} s")=== RUN 1
--- reply: Since you're new to Python, I recommend starting with a simple chatbot framework like Rasa or Dialogflow. For your bakery chatbot, you can use intents to handle customer inquiries and provide relevant information. You can also use Python libraries like NLTK and spaCy for natural language processing.
--- 43.8 s
=== RUN 2 (new process)
--- reply: Since you're building a support chatbot for your bakery, I recommend starting with the core functionality of handling customer inquiries and providing relevant information. This will lay the foundation for your chatbot and allow you to focus on adding more features later.
--- 47.3 s
=== the same run-2 question, WITHOUT memory
--- reply: I'm excited to help you with your project!
--- 9.9 sWhat memory added to the prompt
In run 2, CrewAI put this under the task, before llama3 answered:
- "# Memories from past conversations" with five memories - including "Ravi is building a support chatbot for his bakery in Hyderabad". That is how run 2 knew.
- But three of the five are the agent’s OWN advice from run 1 ("Rasa or Dialogflow are simple chatbot frameworks for beginners", "NLTK and spaCy..."). Memory stored them as facts. Run 1’s advice becomes run 2’s "knowledge".
- All five scored 0.63 - the scores did not separate useful from not useful.
- Memory also gave the agent two tools, search_memory and save_to_memory - so again a failing native tool request (400) and the text fallback from Lesson 6.7.
# Memories from past conversations:
Relevant memories:
- (score=0.63) NLTK and spaCy are Python libraries for natural language processing.
categories: Python, Natural Language Processing, Library
entities: ['NLTK', 'spaCy', 'Python']
dates: []
topics: ['natural language processing', 'language processing', 'Python libraries']
- (score=0.63) Ravi is new to Python.
categories: Programming, Learning
entities: ['Ravi', 'Python']
dates: []
topics: ['Python', 'Learning']
- (score=0.63) Intents can be used to handle customer inquiries and provide relevant information in a chatbot.
categories: AI, Chatbots, Customer Service
entities: ['intents', 'customer inquiries', 'chatbot']
dates: []
topics: ['AI', 'Chatbots', 'Customer Service']
- (score=0.63) Rasa or Dialogflow are simple chatbot frameworks for beginners.
categories: Technology, Artificial Intelligence, Chatbots
entities: ['Rasa', 'Dialogflow']
dates: []
topics: ['chatbot frameworks', 'beginners']
- (score=0.63) Ravi is building a support chatbot for his bakery in Hyderabad.
categories: Business, Industry, Restaurant, Bakery
entities: ['Ravi', 'Hyderabad', 'bakery']
dates: []
topics: ['support chatbot', 'bakery business']What memory costs
Count the requests:
- Run 1: 9 requests - 3 for the assistant (one a failed native tool call), 1 to extract memory statements, 5 to analyse them.
- Run 2: 7 requests.
- 43.8 s and 47.3 s per run, against 9.9 s without memory - about 4 to 5 times slower.
- Every memory is extra text in every later prompt - more tokens, every time (Lesson 5.6).
memory=True fails silently
The shortcut Crew(..., memory=True) uses the defaults - OpenAI for analysis and embeddings. We had no OpenAI key:
- No error. The crew answered "Hello!" as usual.
- The only sign was an internal warning: "Ending event 'memory_save_failed' emitted with empty scope stack."
- Nothing was remembered - and nothing told us clearly.
- Always configure Memory explicitly, and check with list_records() that something was saved.
[CrewAIEventsBus] Warning: Ending event 'memory_save_failed' emitted with empty
scope stack. Missing starting event?
result: Hello!Watch out: Memory stores what users say - names, projects, maybe private details - in a folder on disk, and sends it into later prompts. Decide what may be stored, how long, and how a user can be forgotten (memory.forget, memory.reset).
When memory is worth it
- Worth it: an assistant that talks to the same user many times and should remember their project and preferences.
- Not worth it: one-off jobs (write this paragraph) - you pay 4-5x the time for nothing.
- Often simpler: store the few facts you need yourself (a user profile in a database) and pass them through inputs - you control exactly what is remembered.
- In LangGraph, the same need is solved with checkpoints for one conversation (Lesson 3.6) and a separate store for long-term facts.
The recipe
- 1. Memory(llm=your local model, embedder=a real embedding model, storage="./folder").
- 2. Crew(..., memory=memory) - never just memory=True without a key.
- 3. Check with memory.list_records() what was stored - and remove what should not be there.
- 4. Read the prompt once to see what memory adds.
- 5. Measure the extra time and tokens, and decide if the use case needs it.
Memory at a glance
CreateAll three set explicitly.
Memory(llm=llm, embedder=embed, storage="./mem_db")
Use in a crewAcross runs.
Crew(..., memory=memory)
Store / findDirectly.
memory.remember("..."); memory.recall("question")Wait for savesBefore exit.
memory.drain_writes()
InspectWhat is stored.
memory.list_records(); memory.tree()
ForgetEverything or a scope.
memory.reset() / memory.reset(scope="/AI")
Try it yourself
The code does not change. Swap the content string and the program does something else entirely.
“Run crew_memory.py a third time: "Which framework did you suggest to me?" What does it remember?”
“After run 1, remove the agent’s advice from memory and keep only facts the student said.”
“If you can, run ollama pull nomic-embed-text and use the Ollama embedder. Do the scores separate better?”
“Store {"name": "Ravi", "project": "bakery chatbot"} in a JSON file and pass it through inputs instead of memory. Compare the time.”
What usually goes wrong
Nothing was saved, with only an internal warning.
✗ Crew(..., memory=True)✓ Crew(..., memory=Memory(llm=..., embedder=..., storage="./mem"))The agent’s own advice was stored as fact and fed into later answers.
An unrelated question scored 0.63 - recency and importance count too.
4-5x slower for nothing. Use it only when the same user comes back.
Key points
- CrewAI 1.15 has one unified Memory: the model extracts, scopes and rates facts after a task, and recalls them into later prompts.
- Its defaults need OpenAI; configure llm, embedder and storage yourself - our Ollama needed an embedding model, so we used a tiny local embedder.
- A new process remembered Ravi’s bakery chatbot; without memory the answer was generic.
- Memory also stored the agent’s own advice as fact, and every run took 4-5x longer (43.8 s vs 9.9 s).
- memory=True with no key failed silently - check list_records().
Quick check before you move on
Quiz
- 1.
Why was "Ravi is a beginner" not returned for "How should I answer Ravi?"
- 2.
What wrong kind of memory did run 1 store?
- 3.
Why did run 1 make 9 requests?
- 4.
When is a simple profile passed through inputs better than memory?
Interview questions
How does CrewAI memory work in recent versions?
A unified memory uses an LLM to extract and analyse facts after tasks (scope, categories, importance), stores them with embeddings in a vector store, and recalls relevant memories into later prompts by a composite of semantic similarity, recency and importance; agents also get memory tools.
What are the risks of automatic agent memory?
Storing hallucinated or agent-generated content as fact, privacy and retention issues, extra latency and tokens, and silent failures if the embedder or LLM is misconfigured. Inspect, curate and limit what is stored.
Comments
Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.
Loading comments...