I built a local memory layer for AI agents: no vector DB, one SQLite file

rust dev.to

If you build AI agents, you know the feeling: the model itself has no memory. Every conversation starts from zero. To make an agent remember user preferences, pick up where a task left off, or reuse knowledge across sessions, you have to bolt on a memory system yourself.

I looked at the existing options, and none of them felt right for me:

  • Vector databases (Pinecone, Qdrant, Weaviate…) are powerful, but standing up a distributed service just for memory is a lot of weight for a small project or a solo dev.

  • Cloud memory services (Mem0 and similar) keep your data on someone else's servers and bill per use.

  • Rolling my own in-memory store loses everything on restart — and forget semantic search entirely.

What I actually wanted was pretty boring: a local memory layer that runs from one file and one command, works out of the box, and keeps the data fully in my hands. I couldn't find one I liked, so I wrote it. That's how yq-nova-agent started, open sourced on August 3rd: github.com/YQteam-dyq/yq-nova-agent

What it does

In one sentence: a single-file SQLite memory and state layer for agents, built around three operations — remember, recall, forget.

  • Things an agent learns persist across conversations and survive restarts.

  • You can recall semantically relevant information from past sessions using natural language.

  • It tracks entities and their relationships, so you get lightweight graph reasoning.

  • Stale or low-importance memories are cleaned up automatically, so the store doesn't grow forever.

No external service to deploy. The only runtime dependency is one SQLite file.

Design decisions worth talking about

Three operations, one mental model

The API is deliberately small. HTTP, the Rust SDK, and the CLI all expose the same semantics, so there's no conceptual overhead:

# Store a fact with tags and an importance score
yq-nova remember "User is a Rust developer who prefers lightweight tools" --tag user-profile --importance 0.9

# Recall with natural language
yq-nova recall "user's technical background" --top-k 5

# Check what's in the store
yq-nova stats
Enter fullscreen mode Exit fullscreen mode

Don't want an HTTP server? The CLI works standalone. Building a Rust app? Pull in yq-nova-core as a library and call it in-process.

Hybrid retrieval, not just vectors

An early version that only did vector similarity missed too much: exact keyword matches and graph relationships between entities don't show up in pure semantic search. So recall fuses three signals with RRF (Reciprocal Rank Fusion):

  • Semantic search (embedding similarity)

  • Keyword search (SQLite FTS5 full-text index)

  • Graph signals (entity-relation relatedness)

# Hybrid mode with graph enhancement
yq-nova recall "storage solutions related to SQLite" --mode hybrid --graph
Enter fullscreen mode Exit fullscreen mode

Embeddings are pluggable: OpenAI-compatible endpoints by default, plus a built-in mock provider so you can develop and test fully offline.

Entities and relations: memory with context

Isolated memory entries aren't enough — pieces of information relate to each other. The project keeps an entity-relation graph with recursive BFS traversal. Store "React is a UI library" and "Vue is a UI library", and a recall can walk the graph to find neighboring concepts. That's context pure vector search can't give you.

SQLite is underrated

A lot of people write SQLite off as a toy, but with WAL mode, composite indexes, and FTS5 it's genuinely enough for a single-node memory layer — and the operational cost is close to zero. Persistence, transactions, and schema migrations are all solved problems, so I didn't have to reinvent any of them. This is also what makes the "zero external dependencies" promise hold up.

What v0.2.0 added

v0.2.0 shipped on August 6th, and it closed most of the gap between "works on my machine" and "usable in production":

  • Embedded SDK mode (EmbeddedNova) — use it in-process without spinning up an HTTP server

  • Local ONNX inference via FastEmbed, so you don't need an OpenAI API key to get vectors

  • API token auth middleware on the HTTP server

  • SQLite vector index backed by sqlite-vec's HNSW

  • Integration tests plus Criterion benchmarks (KNN, graph traversal, embedding)

  • Docker image and docker-compose for one-command startup

  • A small Python client (yq_nova)

# Run the server in Docker, persisting data to /data
docker run -p 7999:7999 \
  -v yq-nova-data:/data \
  -e YQ_NOVA_EMBEDDING__DEFAULT_PROVIDER=mock \
  yq-nova serve
Enter fullscreen mode Exit fullscreen mode

Auth, OpenTelemetry tracing, and benchmarks are the "invisible" features — but they're exactly what you need before trusting something in production, so I prioritized them in v0.2.

The honest part

The project is new. It's been open source since August 3rd, and today it has zero stars and an issue tracker that only I talk to. I'm the only maintainer, and there are 18 commits so far. I'm not writing this to claim I built something impressive — I'm writing it because I hit a real problem (agent memory) that I think the "lightweight + local + single file" approach genuinely solves, and I want more people who feel the same pain to find it.

If you're building agents and have opinions about memory, or you try it and think something is designed wrong, please open an issue. What I'd most like to know:

How do you handle agent memory today, and what hurts the most about it?

Your answers will directly shape what I build next — v0.3 is in active development, and I'd rather prioritize based on real scenarios than my own guesses.

If the project is useful to you, or the direction sounds interesting, a star is the easiest way to help.


Repo: github.com/YQteam-dyq/yq-nova-agent

I'm YQteam-dyq on GitHub — happy to chat about agent memory, Rust, or anything in between.

Source: dev.to

arrow_back Back to Tutorials