A tiny, dependency-free, local-only RAG (retrieval-augmented generation) setup: plain markdown files as the source of truth, SQLite FTS5 as the search index, and a local Ollama model to actually answer questions using that context. No cloud API, no vector database, no framework.
Built for a case where all three of those constraints mattered at once: a
resource-constrained OpenBSD box where pip chokes on Rust-extension
wheels (ruling out most "real" RAG toolkits), no desire to depend on an
internet connection or a paid API for something that's meant to keep
running indefinitely, and a genuine need to keep the actual facts in a
format a human can read and diff in git - not locked inside a database or
a vector store's binary format.
fatos/*.md (source of truth, git-versioned)
│
│ sync.py (rebuild-anytime, disposable index)
▼
memoria.db (SQLite + FTS5 full-text search)
│
│ ask.py (search → build prompt → send to local Ollama)
▼
an answer, grounded in whichever fatos/*.md files actually matched
The database is never the source of truth - it's a cache. Delete it and
sync.py rebuilds it from the markdown in a few milliseconds. That split
matters: it means the actual knowledge stays diffable, greppable, and
mergeable in git, while still getting real full-text search instead of
"grep and hope."
# one fact per file, frontmatter for metadata
cat > fatos/some-fact.md <<'EOF'
---
author: alice
date: 2026-01-15
tags: [infra]
---
Whatever you want the agent to remember, in plain prose.
EOF
python3 sync.py # rebuild the search index
python3 ask.py "some question" # search + ask a local Ollama model$ python3 ask.py "when does the backup run and how long is it kept?"
[1 relevant fact(s) found: example-backup-schedule.md]
The nightly backup runs at 02:20 and keeps 14 daily snapshots plus 6
monthly ones...
Configuration is two environment variables, both optional:
OLLAMA_HOST default: http://localhost:11434
OLLAMA_MODEL default: llama3.2
For a real production system with millions of documents, you'd want actual
vector search, chunking strategies, reranking, all of it. This isn't that.
It's for the much more common smaller case - a few dozen to a few hundred
short, hand-written facts - where FTS5's keyword search is entirely
sufficient, and the overhead (and dependency surface) of a full vector-DB
stack buys you nothing. sync.py is 80 lines; ask.py is about the same.
Reading both end to end takes less time than reading a framework's
quickstart guide.