Skip to content
openclawOS

How openclawOS uses sqlite-vec for agent memory

An honest look at how openclawOS stores Pi's memory: sessions in normal SQLite tables, vector recall via sqlite-vec, no Pinecone or Weaviate required.

Dipankar Sarkar 9 min read
  • memory
  • sqlite-vec
  • vectors
  • deep dive

Long-term memory is what separates “a chatbot” from “an agent that remembers you”. openclawOS stores it in two places: structured facts in SQLite tables, and semantic chunks in a vector index. Both live in the same SQLite database, thanks to sqlite-vec.

This is how it works and the trade-offs we picked.

Why SQLite

openclawOS’s choice of “everything in one SQLite file” is intentional. It means:

  • Zero ops. Backup is cp openclaw.db openclaw.db.bak.
  • Atomic transactions across structured + vector data.
  • No external service to monitor, restart, version-pin.
  • Runs everywhere. Pi 4? Yes. Mac mini? Yes. $5 VPS? Yes.

For personal/small-team scale (low millions of vectors max), this is plenty. If you ever outgrow it, the storage layer is abstracted — swap for Postgres + pgvector with one config change.

What gets stored

Structured facts

A simple key-value table per identity:

CREATE TABLE identity_facts (
  identity_id TEXT,
  key TEXT,
  value JSON,
  source TEXT,        -- which session emitted it
  created_at INTEGER,
  PRIMARY KEY (identity_id, key)
);

Pi writes via a memory.set_fact tool. Common entries: “user’s spouse name”, “user’s city”, “user’s vegetarian preference”, “user’s GitHub handle”.

The model decides what’s worth remembering — it’s not implicit on every message.

Semantic chunks

The harder, more powerful store. Pi writes via a memory.note tool:

memory.note({
  identity: "dipankar",
  text: "User prefers Postgres over MySQL for new projects. Reason: better JSON support, less foot-gun-y."
})

That call:

  1. Embeds the text using the configured embedding model (default: OpenAI text-embedding-3-small, cheap).
  2. Stores the chunk + embedding + metadata in the memory_chunks table via sqlite-vec.
  3. Returns a chunk ID for later reference.

At recall time:

  1. Pi calls memory.recall(query: string, k: int).
  2. openclawOS embeds the query, does a vector ANN search in sqlite-vec.
  3. Top-k chunks come back, with their relevance scores and metadata.

The sqlite-vec setup

sqlite-vec is a SQLite extension that adds vector columns and ANN queries. It’s loaded once at Gateway start:

db.loadExtension("sqlite-vec");
db.exec(`
  CREATE VIRTUAL TABLE memory_chunks USING vec0(
    embedding float[1536],
    identity TEXT,
    text TEXT,
    created_at INTEGER
  );
`);

Queries are SQL — sqlite-vec adds a MATCH operator:

db.prepare(`
  SELECT text, distance
  FROM memory_chunks
  WHERE embedding MATCH ?
  AND identity = ?
  ORDER BY distance ASC
  LIMIT ?
`).all(queryEmbedding, identityId, k);

It’s fast (sub-10ms for 100k chunks on a laptop). It’s atomic with the rest of your data. It’s one file.

Identity scoping

Memory is namespaced by identity. A query from identity A cannot recall chunks written by identity B unless an explicit cross-identity rule is configured (e.g., a household “shared memory” identity).

This prevents the worst-case privacy failure: your bot accidentally recalling someone else’s preferences in your conversation.

Compaction interaction

Long sessions get compacted (older turns summarised to save tokens). Compacted summaries can optionally be written to long-term memory as chunks, so the agent retains gist beyond the session window.

This is opt-in per binding. Default is on for personal use, off for high-traffic public bots (where the noise-to-signal ratio of compacted summaries is poor).

Embedding model

The default is OpenAI’s text-embedding-3-small. It’s cheap (~$0.02 per million tokens) and good enough for personal-scale recall.

For full-air-gapped setups, openclawOS supports local embedding via an Ollama endpoint or sentence-transformers. Quality is slightly lower; latency is much lower.

Backup and migration

The whole database is one file. To backup: copy it. To migrate to a new machine: rsync the file. To start fresh: delete it.

For automated backups, run litestream against the SQLite file — point-in-time recovery, continuous backup to S3 or a remote SQLite. We recommend it for any non-toy deployment.

When you outgrow this

If you ever need to scale past a few million vectors or want shared multi-region memory, swap the storage layer for Postgres + pgvector. The interface is the same; the config changes one line:

memory:
  driver: postgres
  url: postgres://...

Most people never need this.

The point

Vector search is often presented as something that requires a whole separate service. For agent memory at personal/team scale, it doesn’t. A single SQLite file with sqlite-vec gives you everything you need, with zero ops and atomic transactions across all your data.

It’s one of those choices that looked unfashionable two years ago and now looks obvious.

Frequently asked

Because for personal and small-team use, sqlite-vec is enough. It's a single file, zero ops, and runs in the same process. We avoid an external service unless we need to.

Run your own gateway.

Free, MIT, no signup. Pi is waiting.