AI Blog

LanceDB vs Chroma vs sqlite-vec vs FAISS: Four Shapes for a Local Agent Knowledge Base

Before you pick a local vector store, notice that Claude Code, Cursor and Codex deleted theirs — the leading coding agents retrieve with grep, not embeddings. If your corpus still needs an index, these four are not competing products but four different architectures: a search library with no storage, a SQLite extension, an embedded engine with a write-ahead log, and a columnar format on disk.

By Agentic AI Wiki 23 min read

The most instructive fact about local vector stores in 2026 is that the highest-volume retrieval systems in production deleted theirs: Claude Code shipped with a local vector database, removed it, and retrieves with grep. If your corpus still needs an index — and prose corpora do — then these four are not four competing products but four different architectures, and picking the wrong shape costs more than picking the wrong brand.

At a glance

All four run inside your process on hardware you own. That is where the similarity ends: one is a library with no storage at all, one is a file format, one is an extension to a database you probably already ship, and one is an engine with a write-ahead log.

ProjectSinceWhat it actually isWhere the data lives
FAISS 2017 A similarity-search library — C++ with Python bindings, CPU and GPU. Nowhere. You choose and manage storage yourself.
Chroma 2022 An embedded vector database with a Rust core since 1.0. A persistent directory the engine owns.
LanceDB 2023 A retrieval engine over the Lance columnar format. Lance files on local disk or object storage.
sqlite-vec 2024 A pure-C SQLite extension adding vec0 virtual tables. Inside the SQLite file you already have.

Licensing is not a differentiator here — FAISS is MIT, the other three are Apache 2.0, and all four are genuinely self-hostable with no phone-home. Popularity is worth one glance and no more, because it measures three different things at once: FAISS has eight years of research users, sqlite-vec is two years old and still pre-1.0.

GitHub stars — FAISS, Chroma, LanceDB, sqlite-vec Horizontal bar chart of GitHub stars in thousands, snapshot 26 July 2026: FAISS leads at 40.6k, Chroma 28.9k, LanceDB 11.0k, sqlite-vec 7.9k. FAISS is a search library rather than a database, and sqlite-vec is the youngest project of the four. GitHub stars (thousands, 26 Jul 2026) 0 10k 20k 30k 40k 50k stars FAISS library, since 2017 40.6k Chroma embedded engine, 2022 28.9k LanceDB columnar format, 2023 11.0k sqlite-vec SQLite extension, 2024 7.9k
Age and audience, not quality — FAISS has been the reference ANN library since 2017, and sqlite-vec has existed for two years.
Local vector store capability matrix Heatmap comparing LanceDB, Chroma, sqlite-vec and FAISS across five axes: built-in storage, ANN index, metadata filtering, hybrid or full-text search, and handling corpora larger than RAM. Strength runs from light neutral (weak) through soft accent (medium) to solid accent (strong). What each store gives you out of the box Storage built in ANN index Metadata filtering Hybrid / full-text Larger than RAM LanceDB Lance format IVF-PQ SQL predicates FTS + rerank Disk-native Chroma Persistent dir HNSW where filters Substring only Segments sqlite-vec SQLite file Brute force Typed columns Join FTS5 Paged scan FAISS None Flat/IVF/HNSW/PQ ID masks only None On-disk IVF Weak Medium Strong
Read the weak cells as design decisions, not gaps: FAISS omits storage on purpose, and sqlite-vec omits ANN indexing to stay a small C file with no dependencies.

The question before the question

Before comparing stores, it is worth knowing that the best-resourced agent teams looked at this pipeline and walked away from it. Anthropic's Claude Code shipped an early version backed by a local vector database and replaced the whole thing — embedding model, index, chunking heuristics — with live ripgrep calls. Cursor, Codex, and Cline all lean on grep-shaped, scriptable retrieval for code. The reasoning is structural: exact match is correct when you are looking for PaymentRetryPolicy, an index over files being edited is stale between the write and the re-index, and a search the agent can narrow into another search is a plan rather than a single shot.

The best measurement so far is a May 2026 arXiv paper from PwC, Is Grep All You Need? How Agent Harnesses Reshape Agentic Search. Across 116 LongMemEval questions and several harness/model pairs, lexical search beat vector retrieval uniformly when results were injected inline into the context — 93.1% against 83.6% on one Claude Opus configuration, with the narrowest margin at 76.7% against 75.0%. But when the same results were written to files for the agent to read instead of being spliced into the prompt, the ordering reversed on half the configurations tested.

That reversal is the finding worth carrying. Retrieval quality is not a property of the retriever alone; the harness and the delivery mechanism move the number as much as the algorithm does. So the honest framing for the rest of this piece is narrow: you want an index when the corpus is large, prose-heavy, and queried by meaning rather than by string — support histories, contracts, research, policy — and you want grep when the corpus is code-shaped and on local disk. Most real systems end up with both.

FAISS — deep dive

FAISS architecture FAISS is a search library loaded into your own process: it holds an index of vectors and returns integer IDs with distances. Persistence, metadata, filtering and the join back to documents are all code you write yourself, outside the library. Your process — everything here is your code FAISS — a search library C++ with Python bindings · CPU or GPU · no server, no file format Index structures Flat (exact) · IVF · HNSW · PQ (compression) choose the recall × latency × memory point yourself Search returns (id, distance) integer IDs only — the library never saw your text filtering is an ID bitmask you build and pass in Everything a database would have given you Persistence serialize the index to disk, reload on start, decide what happens on crash Metadata store a separate table mapping id → text, source, tenant, timestamp — kept in sync by you Collections & lifecycle adds, deletes, rebuilds, versioning — no concept of a “collection” exists in the library The trade You get the fastest and most tunable ANN implementation available, including GPU search over billions of vectors. You also get to write, test and operate the three boxes on the right — which is most of what a vector database is.
FAISS returns integer IDs and distances. Everything that makes those IDs useful is code you write.

How it stores

It does not. This is the single most important thing to understand about FAISS and the reason half of all comparisons involving it are miscategorised. There is no file format, no collection concept, no metadata, no persistence layer — you build an index in memory, and serializing it, reloading it, and deciding what happens after a crash are your problems. The library never sees your text; it sees float arrays and hands back integers.

How it searches

Extremely well, and with more control than anything else on this list. Flat gives exact brute-force search; IVF partitions the space into clusters and probes a few; HNSW builds a navigable graph; PQ compresses vectors into codes so billions of them fit in memory. You compose these — IVF4096,PQ64 is a real index description — and you tune the recall-latency-memory triangle by hand. GPU search is native, which no database on this list matches.

What it makes hard

Filtering, which is the query shape production actually has. "Top-k where tenant_id = 7 and lang = 'en'" is not something FAISS models; the escape hatch is an ID bitmask you construct from a separate metadata table, which means an indirection per candidate and a synchronisation problem between two stores that must never disagree. Deletes are similarly manual. If your answer to "where does the metadata live?" is "in Postgres, and I'll join," you have just described most of a vector database, and you should ask whether you wanted one.

sqlite-vec — deep dive

sqlite-vec architecture sqlite-vec is a C extension that adds vec0 virtual tables to SQLite. Vectors, typed metadata columns, partition keys and auxiliary payload columns live inside one ordinary SQLite file, queried with SQL alongside FTS5 full-text tables and your existing application rows. One ordinary SQLite file pure-C extension, zero dependencies · runs wherever SQLite runs, including WASM vec0 virtual table CREATE VIRTUAL TABLE chunks USING vec0(...) embedding float, int8, bit metadata cols filterable, ≤16 partition keys shard by tenant +aux columns payload only Brute-force KNN every vector scanned, exact recall partition keys cut the scan set fine to ~10⁵ · slow far beyond Your existing tables documents, users, permissions + FTS5 for BM25 keyword search joined in the same statement One SQL query, one transaction SELECT … WHERE tenant = ? AND embedding MATCH ? ORDER BY distance LIMIT k Agent / app No server the database is a file you can copy or ship Any language Python, Node, Go, Rust, Ruby, browser WASM Backups are cp whatever you already do with a SQLite file Still pre-1.0 expect breaking changes
The whole knowledge base is a file you can copy, ship inside an app, or open in the browser.

How it stores

As rows, in the SQLite file you already have. CREATE VIRTUAL TABLE … USING vec0(…) gives you a table holding float, int8, or binary vectors alongside up to sixteen typed metadata columns you can filter on, up to four partition keys that physically shard the index, and auxiliary columns that carry payload without being indexed. It is pure C with no dependencies, which is why it runs on Linux, macOS, Windows, a Raspberry Pi, and in the browser via WASM — the only option here that ships inside a client application.

How it searches

By scanning. The default is brute-force KNN over every vector in the partition, which sounds like a flaw and is mostly a feature: recall is exact, there is no index to build or tune, and freshly inserted rows are immediately searchable with no compaction lag. Partition keys are the scaling lever — if every query is scoped to one tenant, you are scanning that tenant's vectors, not the corpus. The documented advice is to keep hundreds of vectors per partition value rather than a handful, since over-sharding costs more than it saves.

What it makes hard

Growth. Linear scan is fine into the low hundreds of thousands of vectors and painful at ten million — benchmarks that put it two or three orders of magnitude behind ANN indexes are measuring exactly this, and they are fair. The project is also still explicitly pre-1.0 with breaking changes expected, which is an acceptable risk for a file you control and a poor one for a schema you cannot easily migrate. What you get in exchange is the best hybrid-retrieval story here for free: FTS5 is already in SQLite, so BM25 plus vector plus a join against your permissions table is one statement.

Chroma — deep dive

Chroma architecture Chroma runs in-process against a persistent directory. Writes append to an immutable write-ahead log; background compaction materializes the log into HNSW segments; reads query the segments. Since version 1.0 the core is Rust, with native bindings for Python, JavaScript, Ruby and Swift. Your code one import, one path collection.add(...) collection.query(...) Python · JS · Ruby · Swift Rust core (since 1.0) — lock-free, multithreaded log-structured: writes go to a log, reads go to segments, compaction bridges them 1 · Write-ahead log immutable, append-only writes acknowledge here ingest never blocks 2 · Compaction background, asynchronous log becomes segments so there is a brief lag 3 · HNSW segments graph index, in memory queries read here SPANN is distributed-only Query: vector search + metadata where-filters + substring document match no BM25 ranking — if you want true hybrid retrieval you add a keyword index yourself filters are applied against the segment metadata, not a separate store A persistent directory on your disk no server process in embedded mode the same API also talks to a self-hosted server or Chroma Cloud The trade Shortest distance from nothing to working retrieval. The memory-resident HNSW is also the ceiling: comfortable to roughly a million vectors, then memory pressure decides for you.
Writes and reads are decoupled by design — which is why ingest is fast and freshly written rows are briefly not yet searchable.

How it stores

In a persistent directory it manages, using a log-structured design that the 1.0 Rust rewrite made explicit. Writes append to an immutable write-ahead log and acknowledge immediately; a background compaction process materializes the log into segments; reads query the segments. The rewrite brought a claimed 3–5× improvement in both writes and queries over the previous Python implementation, plus native bindings for JavaScript, Ruby, and Swift and a WASM build for the browser.

How it searches

HNSW — a navigable small-world graph, held in memory, which is the standard choice for good reason: excellent recall at low latency when the index fits. Metadata where filters are applied in the same query path against segment metadata rather than a separate store, and there is substring matching on document text. There is no BM25 ranking, so "hybrid search" in a Chroma stack means you brought your own keyword index. Note also that SPANN, the disk-friendly index in Chroma's distributed offering, is not available in the single-node embedded mode this article is about.

What it makes hard

Scaling past memory, and knowing when you have. HNSW is memory-resident, so the practical ceiling on one machine is roughly a million vectors before pressure starts showing in latency, and the answer past that is distributed Chroma — which is a service to operate, not a directory. The compaction lag is the other sharp edge: a row you just wrote is durable but not necessarily indexed yet, which is invisible in a notebook and confusing in an agent loop that writes and immediately searches.

LanceDB — deep dive

LanceDB architecture LanceDB stores tables in the Lance columnar format on local disk or object storage, queried in-process over memory-mapped files. IVF-PQ vector indexes, full-text and scalar indexes sit beside the data, every write creates a new version, and Arrow-compatible tools read the same files with no copy. In-process engine Rust core, no server Python · TypeScript · Rust · Java vector search + full-text search + SQL filters = hybrid query with reranking, one call Lance columnar format — on disk or object storage memory-mapped, read column-by-column; the corpus does not have to fit in RAM Data columns text, vectors, images, audio, arbitrary blobs Indexes beside data IVF-PQ vector index + full-text + scalar Versioned every write is a new version you can read back Filters and vectors resolve against the same files no second metadata store to keep in sync — and no sync job to get wrong Zero-copy to the data stack Apache Arrow · Pandas · Polars · DuckDB read the same files directly so evaluating your retrieval set is a dataframe query, not an export The trade Scales past RAM on one machine and keeps data, metadata and index in one place — at the cost of a smaller community than the server databases, and IVF-PQ's approximation, which loses ground under tight metadata filters.
Data, metadata, and indexes are the same files — so there is no second store to keep in sync, and no sync job to get wrong.

How it stores

In the Lance columnar format, built on Apache Arrow, on local disk or object storage. This is the architectural bet that distinguishes it: the engine reads columns from memory-mapped files rather than holding an index in RAM, so a corpus larger than memory is the normal case rather than the failure case. Every write produces a new version you can read back, which makes "what did retrieval look like before I re-chunked everything?" an answerable question. Columns can hold text, vectors, images, audio, or arbitrary blobs, which is why the project markets itself around multimodal data.

How it searches

IVF-PQ by default — inverted-file partitioning with product quantization, an approximate index that compresses vectors and scales to very large collections on modest hardware. Alongside the vector index sit a full-text index and scalar indexes, and the query API composes vector search, full-text search, SQL-style filters, and reranking into a single hybrid call. That is the most complete out-of-the-box retrieval story of the four, and it matters more than raw QPS: hybrid plus reranking is the single largest quality lever in most stacks.

What it makes hard

Two things, both real. IVF-PQ is approximate and quantized, so recall is a tuning parameter rather than a given, and independent benchmarks show throughput dipping under tight metadata filters — the post-filtering behaviour that catches most partitioned ANN indexes. And the community is smaller than Chroma's or Qdrant's: fewer tutorials, fewer answered questions, and multi-process concurrent access has documented limitations. Buying a disk-native format means buying its ecosystem too.

Cross-cutting comparison

How much of the database you write yourself

How much of the knowledge base you write yourself Four-column comparison of the code you must supply around each store: FAISS leaves you persistence, metadata and lifecycle; sqlite-vec gives you SQL storage but you own the scan budget; Chroma supplies storage, index and filters but no keyword ranking; LanceDB supplies storage, indexes, full-text and hybrid retrieval in one call. How much of the knowledge base you still have to write FAISS Persistence, metadata, collections, deletes — all yours. You get an index, not a database. In exchange: the most tunable ANN there is, and GPU search. sqlite-vec Storage, metadata and joins come free with SQLite; BM25 is one FTS5 table away. You own the scan cost: no ANN index, so growth is your problem. Chroma Storage, HNSW index and metadata filters are supplied. Almost nothing to assemble. You add the keyword half yourself — there is no BM25 ranking. LanceDB Storage, vector index, full-text index and reranking arrive as one hybrid query. You own the format choice and a smaller community of answers.
The weak spots differ in kind: FAISS omits the database, Chroma omits the keyword half, sqlite-vec omits the index.

The four occupy a clean gradient. FAISS hands you an index and leaves persistence, metadata, collections, and deletes entirely to you — perhaps four hundred lines of glue that you will then own forever. sqlite-vec inherits storage, transactions, joins, and BM25 from SQLite for free, so the surrounding code nearly vanishes, but hands you the scan budget in exchange. Chroma supplies storage, index, and filters with the shortest possible on-ramp and then quietly leaves the keyword half of hybrid retrieval as an exercise. LanceDB supplies the most in one call — vector, full-text, filters, reranking — and asks you to adopt a storage format and a smaller support community in return.

Where each one stops

Where each store stops being the right answer Four-column comparison of scale limits on one machine: sqlite-vec's brute-force scan fades past a few hundred thousand vectors; Chroma's memory-resident HNSW is comfortable to about a million; FAISS scales to billions but only as an index you operate; LanceDB reads from disk so corpus size is bounded by storage rather than RAM. Comfortable working range on a single machine sqlite-vec ~100k Brute-force scan is exact and simple, then linear cost catches up. Partition keys extend this when queries are per-tenant. Chroma ~1M HNSW lives in memory, so RAM sets the limit and degrades past it. Beyond that the answer is distributed Chroma, which is a service. FAISS billions IVF-PQ compression and GPU search go as far as any library goes. The ceiling is operational, not algorithmic: you are running it all yourself. LanceDB disk-bound Reads from mapped files, so the corpus may exceed RAM by a wide margin. The cost is approximation: IVF-PQ recall dips under tight metadata filters.
Three of these ceilings are about memory. Only LanceDB's is about disk.

Scale limits here are set by where the index lives, not by how fast the code is. sqlite-vec's brute-force scan is linear, so its ceiling arrives earliest — comfortable into the low hundreds of thousands, extendable when partition keys mean each query touches one tenant's slice. Chroma's ceiling is RAM: HNSW is memory-resident and degrades once the machine is under pressure, which lands around a million vectors on typical hardware. FAISS technically goes furthest with quantization and GPU search, but its ceiling is operational rather than algorithmic — billions of vectors is a system you are now running. LanceDB is the only one whose limit is storage rather than memory, which is the whole point of the columnar bet, paid for with approximate recall.

Freshness, and the agent-loop consequence

The four behave differently in the seconds after a write, and agents notice this in a way batch pipelines do not. sqlite-vec has no index to update, so a row is searchable the instant the transaction commits — the strongest freshness guarantee of the four, and an underrated advantage when an agent writes a note and then searches for it. Chroma acknowledges the write to its log immediately but only makes it searchable after compaction, so a write-then-read loop can miss. FAISS requires you to add to the index explicitly and rebuild periodically, so freshness is whatever your code implements. LanceDB versions on every write and indexes incrementally, but newly added rows are searched by brute force until the index catches up, which is a graceful degradation rather than a miss.

When to pick which

Pick by where the data has to live and how much of it there is — not by a QPS chart from a benchmark that did not run your filters.

If this describes youPickBecause
Your corpus is code, config, or a docs tree on local diskNone of themGive the agent glob, grep, and read. Exact match, zero index maintenance, never stale.
Your app already ships a SQLite file, or runs on a device or in a browsersqlite-vecThe knowledge base becomes rows in a file you already back up, with FTS5 hybrid search and permission joins for free.
You want working retrieval this afternoon and will decide laterChromaShortest path from nothing to a working index, and the default in most framework tutorials, so examples match your code.
The corpus is bigger than RAM, or multimodal, or you want hybrid + reranking without assembling itLanceDBDisk-native columnar storage with vector, full-text, and scalar indexes over the same files, in one query.
Search itself is the hard problem — GPU, billions of vectors, custom index tuningFAISSNothing else gives you this much control over the recall–latency–memory triangle. Budget for the database you will write around it.
You already operate PostgrespgvectorA local knowledge base does not have to mean a new dependency. Vectors on the same rows, one transaction, one backup story.

One caveat worth stating plainly: whichever you choose, the store is rarely what limits answer quality. Parsing, chunking, and reranking are. A team that picks the "wrong" store and adds a local cross-encoder reranker will beat a team that picks the "right" one and retrieves dense-only top-k.

FAQ

Do I need a vector database at all if my agent can just grep?

Not for code, config, or logs — the leading coding agents removed theirs and got better results. You need an index when the corpus is prose-heavy and queried by meaning, because the user's words will never match the document's words. Most production systems run both and route between them.

Which of these four is fastest?

The question is under-specified in a way that makes benchmarks misleading. FAISS wins raw ANN throughput, especially on GPU. sqlite-vec is two to three orders of magnitude slower on large collections because brute force is linear, and exactly right on small ones because there is no index overhead. LanceDB is fast on large disk-resident corpora and dips under tight metadata filters. Chroma is fast until the index no longer fits in memory. Benchmark with your filters and your corpus size or do not benchmark at all.

Can I start with one and move to another later?

Yes, and it is cheaper than it sounds — but only because you keep the chunks and re-embed. Vectors do not transfer meaningfully between stores unless the embedding model is identical, and the metadata schema always needs reshaping. Keep your parsed, chunked corpus as the source of truth in a durable format, and treat the index as derived data you can rebuild. That single discipline makes every store decision reversible.

Is sqlite-vec's brute-force search a dealbreaker?

Only above your scale threshold. At fifty thousand chunks a full scan is milliseconds and gives exact recall with no tuning; at ten million it is unusable. Partition keys move the line considerably when queries are naturally scoped to one tenant or one project. Check the number of vectors you will actually have in a year, not the number in the benchmark.

What about pgvector, Qdrant, or Milvus Lite?

All reasonable, and pgvector is the honest default for any team already running Postgres — see our pgvector vs Pinecone vs Weaviate vs Qdrant comparison for the server-side market. This article is about the embedded case: no server process, data as files, retrieval in the same process as the agent.

Which embedding model should I pair with these?

Local, in almost every case — open-weight embedding models lead the public leaderboards outright. Qwen3-Embedding tops multilingual MTEB above the major hosted endpoints, BGE-M3 is the MIT-licensed multilingual workhorse producing dense, sparse, and multi-vector output in one pass, and EmbeddingGemma or nomic-embed-text fit in well under a gigabyte. Pin the version: changing embedding models invalidates every vector you have stored.

Further reading

On this wiki:

Project sources: