I went looking for a simple answer: what should I actually use for memory in OpenClaw if I care about privacy, reliability, and not turning my notes into cloud exhaust?
I expected a boring answer.
Instead I found a tiny argument with huge implications.
In this r/openclaw thread, someone asked a very normal question: “I use the built-in openclaw memory_wiki. Is there evidence than any other method is superior these days?”
That sounds casual. It isn’t.
Once your agent starts remembering work docs, identity notes, client quirks, operating preferences, and half-finished ideas, memory stops being a feature. It becomes infrastructure.
And if you get it wrong, you don’t just get worse answers. You get privacy leaks, prompt bloat, and weird cross-agent behavior that’s hard to debug.
My conclusion after digging through OpenClaw setups, Ollama embeddings, SQLite FTS5, and a few “real memory” systems:
For most OpenClaw users, the best memory stack is SQLite FTS5 + local Ollama embeddings.
Not pure vectors.
Not blind trust in a built-in wiki.
Not a giant autonomous-memory framework unless you truly need one.
Just a local, inspectable retrieval stack that does keyword search well and adds semantic recall when needed.
Why this matters more than people admit
A lot of people talk about memory like it’s a convenience feature.
It isn’t.
Memory is how you avoid dumping 3,000 tokens of junk into GPT-5, Claude, or Qwen every time your agent needs 3 relevant facts.
That’s the practical side of context optimization.
Good memory means:
- less irrelevant context
- fewer wasted tokens
- better answers
- lower latency
- fewer “why did it bring that up?” moments
If you run agents in OpenClaw, n8n, Make, Zapier, OpenClaw-connected Discord bots, or custom automations, this matters a lot. Bad memory design quietly becomes bad cost design.
That’s especially true if you’re still on per-token billing somewhere upstream. Retrieval mistakes become prompt inflation. Prompt inflation becomes surprise spend.
The built-in OpenClaw option is tempting
If you want the lowest-friction setup, memory_wiki is the obvious first stop.
And to be fair, that’s a reasonable default. If you’re already inside OpenClaw, using the built-in memory layer is the path of least resistance.
My issue isn’t that memory_wiki is bad.
My issue is that I couldn’t find enough first-party detail about how it behaves internally to trust it with my real notes without extra testing.
When memory is holding things like:
- personal notes
- work procedures
- client-specific instructions
- identity files
- long-running automation state
…I want answers to boring questions:
- What exactly gets indexed?
- How is retrieval ranked?
- How isolated are multiple agents?
- What happens in multi-user setups?
- Can I inspect and debug the retrieval path?
Those questions matter because of one phrase I found in another OpenClaw discussion:
knowledge bleed
That’s the real risk.
Not “cloud vs local.”
Not “vectors vs graphs.”
Not “old school search vs AI-native search.”
The real question is: when your OpenClaw agent remembers something, can you see it, control it, and keep it from leaking into the wrong conversation?
That pushed me toward the boring option.
The boring winner: SQLite FTS5 is still absurdly good
The most convincing answer I found in the OpenClaw memory discussion pointed to a custom stack built on:
- SQLite FTS5
better-sqlite3- Porter stemming
- markdown files like
MEMORY.md,IDENTITY.md,SOUL.md,AGENTS.md
That immediately felt more trustworthy than a black-box memory layer.
Why?
Because I can picture it.
Files on disk. A local database. Search I can inspect. Queries I can test. Results I can explain.
That matters.
What SQLite FTS5 gives you
SQLite FTS5 still covers a huge percentage of real memory use cases:
- stemming via Porter tokenizer
- BM25-style ranking
- prefix search
- proximity search with
NEAR - snippet generation
- local-only storage
- zero extra infrastructure
For a lot of “agent memory” problems, you do not need a vector database first.
You need:
- clean source files
- searchable text
- explainable ranking
- namespace separation
That gets you surprisingly far.
Minimal schema
Here’s the smallest useful starting point:
CREATE VIRTUAL TABLE docs USING fts5(
path,
namespace,
content,
tokenize = 'porter'
);
A dead-simple insert flow might look like this:
INSERT INTO docs(path, namespace, content)
VALUES ('memory/client-a/IDENTITY.md', 'client-a', 'Client A prefers CSV exports on Fridays.');
And a basic search:
SELECT path, snippet(docs, 2, '[', ']', '...', 12) AS snippet
FROM docs
WHERE docs MATCH 'csv friday'
ORDER BY bm25(docs)
LIMIT 5;
That’s already better than a lot of “AI memory” setups because it’s inspectable.
If retrieval is weird, you can debug it.
That alone is a huge operational advantage.
But pure keyword search is not enough
SQLite FTS5 is the baseline, not the whole answer.
Once your notes get messy, wording changes, or the user asks semantically related questions without using the same terms, keyword search starts missing things.
That’s where embeddings help.
My opinion: yes, you probably want embeddings — but only as a second retrieval lane, not the whole memory system.
Ollama makes local embeddings the obvious move
The reason this stack is practical now is Ollama.
You can run embeddings locally over HTTP, no hosted embedding API required.
That means:
- no sending personal notes to a third party
- no separate billing for embedding calls
- no extra cloud dependency for retrieval
- easy local development
A few useful embedding models available in Ollama:
-
mxbai-embed-large— 334M nomic-embed-textall-minilm
Example:
ollama pull mxbai-embed-large
Then generate embeddings locally:
curl http://localhost:11434/api/embed -d '{
"model": "mxbai-embed-large",
"input": "Client A prefers CSV exports on Fridays"
}'
That’s it. Local semantic retrieval is no longer exotic.
The right design is hybrid retrieval
This is the part people keep getting wrong.
Embeddings-only memory is not the best default anymore.
Pure vector similarity is great at fuzzy recall, but it’s weak at exact terms, identifiers, names, file-level provenance, and explainability.
That’s why the best practical setup is hybrid:
- FTS5 for exact terms, filenames, acronyms, and strong keyword relevance
- embeddings for semantic recall
- optional reranking or merge logic on top
A simple retrieval strategy can be:
- run an FTS5 search
- run a vector similarity search
- merge top candidates
- dedupe by file/chunk
- pass only the best few results into the model
That gives you much better recall without turning retrieval into magic.
Pseudocode for a hybrid retrieval pass
const keywordHits = searchFts5(query, { namespace: 'client-a', limit: 10 })
const vectorHits = searchVectors(queryEmbedding, { namespace: 'client-a', limit: 10 })
const merged = mergeAndScore(keywordHits, vectorHits)
.filter(hit => hit.score > 0.4)
.slice(0, 5)
return merged
That’s the stack I’d actually trust.
A practical local architecture
If I were setting this up for OpenClaw today, I’d do something like this:
markdown files
-> ingestion script
-> SQLite FTS5 index
-> local Ollama embeddings
-> vector store or embedding table
-> hybrid retrieval API
-> OpenClaw memory tool
Suggested file layout
memory/
personal/
IDENTITY.md
MEMORY.md
PREFERENCES.md
client-a/
IDENTITY.md
SOP.md
PROJECTS.md
client-b/
IDENTITY.md
NOTES.md
That structure is boring on purpose.
Boring is good here.
Example Node.js ingestion script
Here’s a stripped-down example using better-sqlite3.
import Database from 'better-sqlite3'
import fs from 'node:fs'
import path from 'node:path'
const db = new Database('memory.db')
db.exec(`
CREATE VIRTUAL TABLE IF NOT EXISTS docs USING fts5(
path,
namespace,
content,
tokenize = 'porter'
);
`)
function indexFile(filePath, namespace) {
const content = fs.readFileSync(filePath, 'utf8')
db.prepare('DELETE FROM docs WHERE path = ?').run(filePath)
db.prepare(`
INSERT INTO docs(path, namespace, content)
VALUES (?, ?, ?)
`).run(filePath, namespace, content)
}
function walk(dir, namespace) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const fullPath = path.join(dir, entry.name)
if (entry.isDirectory()) walk(fullPath, namespace)
else if (entry.name.endsWith('.md')) indexFile(fullPath, namespace)
}
}
walk('./memory/client-a', 'client-a')
walk('./memory/client-b', 'client-b')
console.log('Indexed markdown files into FTS5')
And a search function:
function search(namespace, query) {
return db.prepare(`
SELECT
path,
snippet(docs, 2, '[', ']', '...', 16) AS snippet,
bm25(docs) AS rank
FROM docs
WHERE namespace = ? AND docs MATCH ?
ORDER BY rank
LIMIT 5
`).all(namespace, query)
}
console.log(search('client-a', 'CSV Friday exports'))
That’s already enough to power useful memory retrieval in an agent workflow.
What about Letta or OpenMemory?
This is where the answer depends on what you mean by “memory.”
Letta
Letta is not just retrieval.
It treats memory as part of the runtime for a long-lived agent. Core memory blocks stay in context. Older messages persist in storage. Agents can update memory through tools.
That’s a much stronger model than plain RAG.
If you need:
- persistent agent state
- self-editing memory
- multi-agent memory blocks
- long-running autonomous workflows
…Letta is worth serious attention.
But it’s also a bigger architectural commitment.
If your real requirement is “make OpenClaw remember notes locally, accurately, and safely,” Letta may be more system than you need.
OpenMemory
OpenMemory is interesting if you care about portability and moving memory across tools.
That’s a real advantage.
But for a straightforward OpenClaw deployment, it’s still another layer to run, reason about, and secure.
If your priority is operational calm, every extra layer should justify itself.
For me, most setups don’t need that extra complexity on day one.
My ranking after digging through the options
| Option | My take |
|---|---|
OpenClaw memory_wiki
|
Best if you want the easiest built-in path and your needs are simple. Lowest friction, but I’d test boundaries hard before trusting it with sensitive or multi-tenant memory. |
| SQLite FTS5 + Ollama embeddings | Best overall for most OpenClaw users. Local, inspectable, hybrid-friendly, cheap to run, and easy to debug. |
| Letta / OpenMemory-style layer | Best if memory is part of your agent architecture, not just retrieval. More powerful, but more operational and conceptual overhead. |
The part everyone skips: preventing knowledge bleed
This is the section that matters most.
The scariest failure mode is not bad ranking.
It’s cross-agent contamination.
If you run multiple agents for different users, Discord chats, clients, or internal teams, you need isolation rules from day one.
My minimum rules
- separate indexes per tenant or agent role
- separate identity files like
IDENTITY.md - separate embedding collections by default
- log retrieval hits
- log source paths for every memory result
- keep memory writes narrow
- do not allow broad self-editing unless you actually need it
A simple namespace rule saves you from a lot of pain:
function getNamespace({ clientId, agentRole }) {
return `${clientId}:${agentRole}`
}
And every retrieval call should require one:
const namespace = getNamespace({ clientId: 'client-a', agentRole: 'support-bot' })
const hits = search(namespace, userQuery)
If namespace is optional, someone will forget it.
If someone forgets it, eventually memory leaks.
Why this also matters for token costs
This isn’t just a privacy and reliability issue.
It’s a cost issue too.
Bad memory means bad retrieval.
Bad retrieval means bloated prompts.
Bloated prompts mean higher spend and slower agents.
That’s one reason I care about this topic in the first place.
If you’re running agents all day, retrieval quality directly affects how much context you send upstream. And if you’re paying per token, every sloppy memory decision compounds.
That’s also why flat-rate AI infrastructure is appealing for automation-heavy teams. If your agents are constantly retrieving, summarizing, rewriting, and looping through tool calls, predictable compute matters a lot more than people admit.
Standard Compute is interesting here because it gives you an OpenAI-compatible API with flat monthly pricing instead of per-token billing. If you’re wiring OpenClaw, n8n, Make, Zapier, or custom agents into heavy workflows, that changes the economics of experimentation. You can spend your time fixing retrieval quality instead of watching token meters.
That doesn’t replace good memory design.
It just means your memory mistakes don’t instantly become billing anxiety.
What I’d actually run
If you forced me to pick one OpenClaw memory stack today for real notes, work docs, and long-running automations, I’d use:
- markdown source files
- SQLite FTS5 for keyword retrieval
- Ollama embeddings for semantic recall
- strict namespace separation per agent or tenant
- optional lightweight fact graph only where relationships matter repeatedly
That last part is optional, but useful.
If the same entities keep showing up across tasks, a tiny fact graph can outperform “just search harder.”
Examples:
- Sam is the billing approver
- Sam hates Tuesday deploys
- Project Atlas is blocked on legal review
- Client A wants CSV, Client B wants JSON
Those relationship-heavy facts can be modeled explicitly instead of hoping vector similarity rediscovers them every time.
But I would not start there.
I’d start with the simplest stack that is:
- local
- inspectable
- testable
- easy to namespace
- easy to repair
That stack is SQLite FTS5 + local Ollama embeddings.
Final answer
If you use OpenClaw and you’re asking “what should I use for memory?”, my answer is pretty clear now.
Use memory_wiki if you want the easiest built-in path.
Use Letta if you want a real stateful-agent runtime.
But if you want the first setup I’d trust with actual personal notes, work memory, and long-running automations, the winner is:
SQLite FTS5 + local Ollama embeddings
It’s not flashy.
It won’t win demo day.
It’s not the most tweetable architecture.
But it’s the one I’d trust.
And for memory, trust matters a lot more than novelty.
Top comments (0)