Read more about: #agents#claude-code#llms#infrastructure#open-source

Why I Chose FTS Over Vector Search for Claude Code Memory

cli · claude-code · llms · infrastructure

Claude Code stores everything locally. Every command, every output, every conversation - it’s all in ~/.claude/projects/ as JSONL files. The data’s just sitting there.

I wanted to search it. The obvious choice was vector search. I went with SQLite FTS instead.

cc-dejavu

The problem with CLAUDE.md

You could document useful commands in CLAUDE.md. I tried this. Across a few projects, it doesn’t scale.

Maintaining command references becomes a chore. Static docs go stale. You forget to update them. The curation effort compounds with every new project.

Better approach: let actual usage be the documentation. Memory that grows from real work, not manual upkeep.

Why start with bash commands

Claude Code’s conversation history includes everything - tool calls, outputs, free-form chat. I started with bash commands specifically.

Commands are structured. Predictable vocabulary: binaries, flags, paths. When an LLM has to guess search terms, constrained vocabulary means better guesses. Searching for “docker” or “pytest” is more reliable than searching for “that thing we discussed about deployment.”

The case against vectors

Vector search sounds right for semantic retrieval. But it forces architectural constraints I didn’t want.

What vectors needWhat that costs
Embedding pipelineLatency on every insert
Vector storeAnother dependency to manage
RerankerBecause similarity alone isn’t enough
DeduplicationBecause everything is “similar”

You lose frequency awareness. A command you ran once three months ago scores the same as one you use daily. You inevitably bolt on post-processing to fix this.

Here’s the thing: there’s already an LLM in front of this database. It understands meaning. It can translate intent into keywords. Why add a second semantic layer?

BM25 + frecency

SQLite FTS with BM25 handles relevance in one system. Add frecency (frequency + recency) and frequently-used commands surface naturally.

No pipelines. No rerankers. No redundant semantics. One system doing one job.

The tradeoff

FTS has a limitation. The LLM doesn’t know what keywords exist in the index. It has to guess search terms based on user intent.

This works better than expected. Bash commands have predictable vocabulary. And when guesses miss, you iterate. Still faster than maintaining embedding pipelines.

The punchline

Sometimes the simplest architecture wins. When there’s already an LLM interpreting queries, you don’t need a second semantic system between it and your data. BM25 is boring. Boring works.

Try it

The tool is called deja. Install with:

curl -fsSL https://raw.githubusercontent.com/Michaelliv/cc-dejavu/main/install.sh | bash

Or with Bun: bun add -g cc-dejavu

Then search your Claude Code history:

deja search docker
deja list --here

Run deja onboard to teach Claude how to search its own history.