Memory search uses LIKE queries and gets quadratically slower
CrewAI's default RAG storage uses substring LIKE queries against SQLite, which are full table scans. As memory grows, every agent turn pays the full cost of the scan.
The symptom
You start a CrewAI project. Memory is snappy. Over the next few weeks, you accumulate a few thousand memory entries from real runs, and suddenly every agent turn feels like it takes a beat longer than it used to. You check metrics; retrieval latency has doubled. A month later, it’s quadrupled. You didn’t change any code.
Why this happens
CrewAI’s default RAG storage backend uses SQLite, and its text search is implemented with LIKE '%needle%'. SQLite cannot index LIKE patterns that start with a wildcard. There is no tree walk, no inverted index, no clever trick — every query is a full scan of the memory table. As the table grows, every scan gets linearly more expensive. Combined with the fact that agents often issue several memory queries per turn, the effect compounds.
For a table with 5,000 rows and a few hundred bytes of content per row, each scan takes on the order of 1–3 milliseconds. Multiply that by a dozen queries per task, a dozen tasks per run, and a dozen runs per hour, and you’re spending real wall-clock time on something that should have been a hashmap lookup.
The second problem is worse. LIKE is a substring match, not a relevance ranking. You might have ten entries that all contain the word “revenue”; LIKE returns them in insertion order, not in order of how useful they are to the current query. The agent then has to do its own filtering, which costs tokens.
Why this persists upstream
FTS5 exists, SQLite ships with it, and it has existed since 2015. The reason CrewAI hasn’t switched is not controversial — it’s that FTS5 requires schema changes (a virtual table, triggers to keep it in sync) and replacing default methods on the storage classes. It’s a real migration, not a one-line change, and it carries compatibility risk for anyone who has customized the storage layer. The cost of shipping it upstream is non-trivial; the cost of not shipping it is absorbed by individual teams as “CrewAI got slow”.
How Fast-CrewAI addresses it
Fast-CrewAI’s memory patch replaces the RAGStorage, ShortTermMemory, LongTermMemory, EntityMemory, and LTMSQLiteStorage classes. On first use, it creates an FTS5 virtual table alongside your existing memory table and installs triggers so every insert, update, and delete in the source table propagates to the index. Existing data is backfilled during initialization.
Queries that used to do SELECT ... WHERE content LIKE '%query%' become SELECT ... FROM memory_fts WHERE memory_fts MATCH 'query' ORDER BY bm25(memory_fts) LIMIT N. The results come back ranked by BM25 relevance — the same ranking Elasticsearch is famous for — running in-process in SQLite.
In our benchmarks, this is 11.2× faster on identical workloads (10,206 ops/s vs 913 ops/s) and uses 31% less memory. It’s also more accurate, because BM25 is a real ranking function and LIKE is a substring match.
Workaround you can ship today
If you don’t want to adopt Fast-CrewAI yet, you can do the migration by hand in an afternoon:
- Create an FTS5 virtual table:
CREATE VIRTUAL TABLE memory_fts USING fts5(content, content='memory', content_rowid='id'); - Backfill it:
INSERT INTO memory_fts(memory_fts) VALUES('rebuild'); - Add triggers to keep it in sync with the source table on
INSERT,UPDATE, andDELETE. - Replace your
LIKEqueries withMATCHand order bybm25(memory_fts).
It’s a finite amount of work and it goes away the moment CrewAI or Fast-CrewAI ships it upstream.
When it matters
This issue is invisible at prototype scale and crushing at production scale. If your crew has been running for more than a few weeks and retrieval latency is climbing, this is almost certainly the cause. Book an audit if you want us to confirm it on your workload.