← Back to Agentic AI map
Lesson 6.8 · CrewAI

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.

crewai

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.

workflowRemembering across runsstep 1 / 2

1 - Run 1 stores facts

After the task, llama3 extracts memory statements and analyses each one (scope, importance, categories). 9 requests, 43.8 s.

stored
"Ravi is new to Python", "...bakery..."
also stored
its own advice (Rasa, NLTK)
time
43.8 s

Two separate processes, one memory folder. Real runs, requests recorded.

Words you will see in this lesson

Memory words.

Small dictionary
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.
simple_embedder.py - a tiny local embedder
"""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 vectors
Output - the errors on the way
embedder={"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.
mem1.py
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()
Output (status boxes removed)
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.
Output - memory.list_records() and memory.tree()
- [/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.
crew_memory.py
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")
Output - two runs, then the same question without memory
=== 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 s

What 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.
Run 2 - the memory part of the prompt (exactly as sent)
# 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.
Output - Crew(..., memory=True), no OpenAI key
[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

Create

All three set explicitly.

Memory(llm=llm, embedder=embed, storage="./mem_db")
Use in a crew

Across runs.

Crew(..., memory=memory)
Store / find

Directly.

memory.remember("..."); memory.recall("question")
Wait for saves

Before exit.

memory.drain_writes()
Inspect

What is stored.

memory.list_records(); memory.tree()
Forget

Everything 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 3

“Run crew_memory.py a third time: "Which framework did you suggest to me?" What does it remember?”

Clean memory

“After run 1, remove the agent’s advice from memory and keep only facts the student said.”

A real embedder

“If you can, run ollama pull nomic-embed-text and use the Ollama embedder. Do the scores separate better?”

Do it yourself

“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

memory=True without a key

Nothing was saved, with only an internal warning.

✗ Crew(..., memory=True)
✓ Crew(..., memory=Memory(llm=..., embedder=..., storage="./mem"))
Not reading what is stored

The agent’s own advice was stored as fact and fed into later answers.

Trusting the score

An unrelated question scored 0.63 - recency and importance count too.

Memory for one-off jobs

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

What are the three parts of a Memory?
An llm (decides what to store), an embedder (text to vectors) and storage (a folder with the vector database).
How is a memory’s score made?
0.5 x meaning + 0.3 x recency + 0.2 x importance.
How did run 2 know about the bakery?
CrewAI added "# Memories from past conversations" with the stored facts to the prompt.
What happened with memory=True and no OpenAI key?
No error; nothing was saved; only an internal "memory_save_failed" warning.

Quiz

  1. 1.

    Why was "Ravi is a beginner" not returned for "How should I answer Ravi?"

  2. 2.

    What wrong kind of memory did run 1 store?

  3. 3.

    Why did run 1 make 9 requests?

  4. 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...