semqlite — the real, unmodified SQLite CLI shell (vendored amalgamation,
built straight from the official sqlite/sqlite
mirror, with six custom SQL functions baked in for experimenting on Ragger's semantic
memory database. It assumes that for the most part embeddings are stored as a blob of the binary
vector. Other details of the vector encoding can be configured with a few commands.
vendor/sqlite/shell.c is upstream's own src/shell.c.in, run through their
own tool/mkshellc.tcl, byte-for-byte. Zero patches. Two things make that
possible:
#define main sqlite3_shell— documented directly in shell.c's own header comment aboveint main(): "other projects that use shell.c as a subroutine" rename it this way.src/main.cis a two-line wrapper that callssqlite3_shell(argc, argv).SQLITE_SHELL_EXTFUNCS— a documented extension hook shell.c already has for exactly this purpose: bolting custom SQL functions onto every connection the shell opens, without touching shell.c itself. Seeinclude/shell_extfuncs_hook.h.
Net effect: you get the full upstream CLI — .mode, .import, .dump,
.schema, readline history, everything — plus DMPHON(),
EMBEDDING_SIM()/EMBEDDING_DIST(), EMBED(), and STEM_PORTER()/
STEM_SNOWBALL() available on every database you open.
Double Metaphone phonetic encoding (Lawrence Philips, CUJ June 2000 — public algorithm, ported from Ragger's C++ implementation with zero deps).
- mode 0 (default): all phonetic codes for all words, space-separated (both primary and alternate when they differ)
- mode 1: first code per word (primary — always present)
- mode 2: last code per word (alternate if one exists, else primary — always present, never NULL for alphabetic input)
SELECT DMPHON('Don''t panic'); -- 'TNT PNK'
SELECT DMPHON('Schmidt', 1); -- 'XMT'
SELECT DMPHON('Schmidt', 2); -- 'SMT'
SELECT DMPHON('hello', 2); -- 'HL' (only one code exists, so mode 2 == mode 1)Stems a single English word using the classic 1980 Porter algorithm (ported from Ragger's C++ implementation, zero deps). English only, no configuration.
SELECT STEM_PORTER('running'); -- 'run'
SELECT STEM_PORTER('flies'); -- 'fli'
SELECT STEM_PORTER('RUNNING'); -- 'run' (case-insensitive)Stems a single UTF-8 word using the Snowball
stemming algorithm family (vendored libstemmer_c). Unlike STEM_PORTER,
this supports many languages — one stemming algorithm per language, picked
by name at call time.
language accepts either a canonical long name (english, french,
german, russian, ...) or a short code (en, fr, de, ru, ...),
case-insensitively. The full list of supported languages: arabic (ar),
armenian (hy), basque (eu), catalan (ca), danish (da), dutch (nl),
english (en), finnish (fi), french (fr), german (de), greek (el),
hindi (hi), hungarian (hu), indonesian (id), irish (ga), italian
(it), lithuanian (lt), nepali (ne), norwegian (no), porter
(long name only — the original 1980 Porter algorithm as implemented by
Snowball itself, distinct from STEM_PORTER() above), portuguese (pt),
romanian (ro), russian (ru), serbian (sr), spanish (es), swedish
(sv), tamil (ta), turkish (tr), yiddish (yi).
If language is omitted, STEM_SNOWBALL uses the snowball_language
semqlite_config setting, which itself defaults to English (en) if
never set:
SELECT SEMQLITE_SET('snowball_language', 'en'); -- set the default (optional; 'en' is already the default)
SELECT STEM_SNOWBALL('running'); -- 'run' (default English)
SELECT STEM_SNOWBALL('chevaux', 'french'); -- 'cheval' (long name)
SELECT STEM_SNOWBALL('chevaux', 'fr'); -- 'cheval' (short code, same result)
SELECT STEM_SNOWBALL('laufen', 'de'); -- 'lauf'An unrecognised language name degrades gracefully to a lowercased
passthrough of the input rather than an error — check
snowball_language_supported()-style validation up front if you need to
detect that case.
Note: STEM_SNOWBALL with the 1-argument form is not marked
SQLITE_DETERMINISTIC because its result depends on the mutable
snowball_language setting; the 2-argument form (explicit language) is
deterministic. STEM_PORTER is always deterministic.
Cosine similarity / cosine distance between two embedding BLOBs.
The storage dtype and byte layout are controlled by semqlite_config settings:
| key | values | default | purpose |
|---|---|---|---|
embedding_vector_type |
f32, f16, bf16, int8 |
f16 |
How the payload bytes are interpreted |
embedding_offset |
integer (bytes) | 0 |
Bytes to skip at the start of each blob before the vector payload — set this to match any header your application prepends |
EMBEDDING_SIM→[-1, 1], 1.0 = identical directionEMBEDDING_DIST→[0, 2], 0.0 = identical direction (1 - cosine)
int8 payloads: N bytes of quantized data, optionally followed by a 2-byte IEEE f16 dequantization scale (scale = max|x| / 127). If the scale suffix is absent, a fallback of 1/127 is used (correct for unit-norm vectors).
Note: these functions are not marked SQLITE_DETERMINISTIC because
their behavior depends on the mutable semqlite_config settings. Same blob
inputs with a different embedding_vector_type produce different results.
-- Configure for Ragger's default: f16 blobs with 1-byte version prefix
SELECT SEMQLITE_SET('embedding_vector_type', 'f16');
SELECT SEMQLITE_SET('embedding_offset', '1');
-- Most semantically similar summaries to summary_id=2
SELECT s2.summary_id, EMBEDDING_SIM(s1.embedding, s2.embedding) AS sim
FROM summaries s1, summaries s2
WHERE s1.summary_id = 2 AND s2.summary_id != 2
ORDER BY sim DESC LIMIT 10;
--Most semantically similar to text entry. EMBED() called only once.
WITH q(vec) AS (SELECT EMBED('input text for testing'))
SELECT document_id, sim, text FROM (
SELECT d.document_id, d.text,
EMBEDDING_SIM(q.vec, d.embedding) AS sim
FROM documents d, q
)
WHERE sim > 0.7
ORDER BY sim DESC LIMIT 10;Generates a sentence embedding via a GGUF model, loaded through libllama
(MacPorts llama.cpp package). Mean-pooled, L2-normalized, returned as a
BLOB — same shape as embeddings already stored in Ragger's DB.
SQLite extensions can't actually define new PRAGMAs (the syntax is
hardcoded into the core parser, not an extension point like functions/vtabs
are), so the closest equivalent — settings that persist in the database file
so you never re-type them — is a semqlite_config table plus two helper
functions:
SELECT SEMQLITE_SET('embedding_model', '/path/to/all-MiniLM-L12-v2'); -- or,
SELECT SEMQLITE_SET('embedding_model', (SELECT CONCAT('~/.ragger/models/', value)
FROM settings WHERE key = 'embedding_model'));
SELECT SEMQLITE_SET('embedding_dims', '384'); -- optional, 1..4096; omit/0 = model's native dim
SELECT SEMQLITE_SET('embedding_vector_type', 'f16'); -- "f16" (default), "f32", "bf16", or "int8"
SELECT SEMQLITE_SET('embedding_offset', '0'); -- bytes of zero-filled header to prepend (default 0)
SELECT SEMQLITE_SET('embedding_skip_renorm', '0'); -- set to 1 to skip L2 normalization (testing)
SELECT SEMQLITE_GET('embedding_model'); -- read back current setting
SELECT EMBED('some text to embed'); -- BLOB, per the settings aboveSettings are set once per database and persist across semqlite restarts
(they live in semqlite_config, auto-created on first use). The model itself
is loaded lazily on first EMBED() call and cached for the process
lifetime, keyed by path — calling SEMQLITE_SET('embedding_model', ...) with
a different path swaps the cached model on the next call.
embedding_vector_type controls the on-disk blob format:
- f32 — 4 bytes/dim, lossless
- f16 — 2 bytes/dim, IEEE half precision (default, matches Ragger)
- bf16 — 2 bytes/dim, bfloat16 (f32's exponent range, less mantissa)
- int8 — 1 byte/dim + 2-byte f16 scale suffix (symmetric per-vector quantization, scale = max|x| / 127)
embedding_offset reserves N zero-filled bytes at the start of the blob,
allowing an external tool to write a header there. EMBEDDING_SIM/DIST
skip the same offset when decoding. Set both to the same value.
embedding_skip_renorm disables the L2 normalization step in EMBED() —
useful for testing whether normalization affects retrieval quality on your
data. Default is off (normalize).
embedding_dims smaller than the model's native output truncates
(Matryoshka-style slicing — not re-trained for it, just a slice, fine for
experimentation); larger zero-pads.
Any GGUF text-embedding model works with the llama backend (mean/CLS/last
pooling all handled via llama_context_params.pooling_type = LLAMA_POOLING_TYPE_MEAN). The onnx backend (default) uses the same
tokenize → ONNX inference → mean-pool → L2-normalize pipeline as Ragger.
-- Sanity check: related text scores higher than unrelated text.
-- Absolute numbers depend on which model/backend produced the vectors —
-- not comparable across different models, only useful as a
-- related-vs-unrelated ordering check on a given model.
SELECT EMBEDDING_SIM(EMBED('cats are great pets'), EMBED('dogs are wonderful companions'));
SELECT EMBEDDING_SIM(EMBED('cats are great pets'), EMBED('quantum chromodynamics'));Measured examples (yours will differ if you use a different model):
| Backend | Model | related sim | unrelated sim |
|---|---|---|---|
llama |
nomic-embed-text-v1.5 (768-dim) | ~0.49 | ~0.01 |
onnx |
all-MiniLM-L6-v2 (384-dim) | ~0.94 | ~0.88 |
Copy-paste this block to configure semqlite for Ragger's memories.db.
It reads all embedding settings directly from Ragger's settings table — no
hardcoded values to keep in sync:
.open ~/.ragger/memories.db
-- Load embedding config from Ragger's settings table.
-- vector_type: f16 (default), f32, bf16, or int8
-- model: resolved model directory name
-- dimensions: 384 for all-MiniLM-L12-v2, etc.
-- offset: default 0; int8 scale is a 2-byte f16 suffix
-- after the payload, matching SemanticSQLite's convention.
SELECT SEMQLITE_SET('embedding_vector_type',
(SELECT value FROM settings WHERE key = 'vector_type'));
SELECT SEMQLITE_SET('embedding_dims',
(SELECT value FROM settings WHERE key = 'dimensions'));
SELECT SEMQLITE_SET('embedding_model',
'~/.ragger/models/' || (SELECT value FROM settings WHERE key = 'embedding_model'));
SELECT SEMQLITE_SET('embedding_offset', '1');After this, EMBEDDING_SIM(), EMBEDDING_DIST(), and EMBED() all work
against Ragger's live data with no further configuration.
| Requirement | Needed for | Notes |
|---|---|---|
| CMake ≥ 3.24 | everything | |
| C11 + C++20 compiler | everything | GCC 14 / Clang tested |
git |
everything | CMake clones the dependencies below (pinned) at configure time |
Rust toolchain (cargo) |
-DSEMQLITE_ONNX=ON (default) |
tokenizers-cpp's HuggingFace backend is a Rust crate built via cargo build, invoked automatically by CMake. Not optional unless you build with -DSEMQLITE_ONNX=OFF. See install command below. |
libllama (llama.cpp) |
embedding_embedder='llama' |
MacPorts on macOS, build-from-source on Linux — see below. Not needed for the ONNX backend, but the CMake always looks for it (both backends can coexist in one binary). |
| Internet access (first build only) | everything | Dependencies are fetched from GitHub at configure time and cached under build/_deps/: c_lib (unless a sibling ../c_lib exists), plus with -DSEMQLITE_ONNX=ON (default) tokenizers-cpp (git, pinned commit) and ONNX Runtime (release archive, SHA-256 verified). Versions are pinned in cmake/pins.cmake. |
libedit (optional) |
interactive shell usage | Gives the shell arrow-key/history line editing. Without it, arrow keys print raw escape sequences and there's no command history — the shell still works, it's just unpleasant to use interactively. macOS (MacPorts): sudo port install libedit. Debian/Linux: sudo apt install libedit-dev. Auto-detected; silently skipped if not found. |
If a requirement is missing, CMake usually fails at build time with an
opaque error rather than a clean message at configure time (e.g. missing
cargo shows up later as no such file or directory from a custom build
command) — check this table first if a build fails partway through.
git clone <this-repo-url> SemanticSQLite
cd SemanticSQLite
# nothing else to fetch by hand — CMake downloads the pinned dependenciesInstall Rust (required for the default ONNX backend):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"If cargo gets installed after an earlier failed configure, run
rm -rf build before reconfiguring — CMake's find_program() caches a
"not found" result and won't recheck on a stale cache.
Install libllama (for the llama embedding backend — see
embedding_embedder above):
# Build from source
git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp
cmake -B build -DBUILD_SHARED_LIBS=ON -DGGML_VULKAN=ON # or -DGGML_CUDA=ON
cmake --build build -j$(nproc)
sudo cmake --install build
sudo ldconfigThe ONNX embedding backend (default) needs no separate install — ONNX
Runtime is auto-fetched at configure time for your platform (macOS
arm64, Linux x86_64/aarch64), and tokenizers-cpp is cloned at the commit
pinned in cmake/pins.cmake. Disable it with --no-onnx (build script) or
-DSEMQLITE_ONNX=OFF (manual cmake) if you only want the llama backend.
c_lib provides the SQLite library itself
(diskerror_sqlite3, foreign keys on by default) plus the shared C++ utilities.
With a sibling checkout at ../c_lib, its prebuilt libraries in ../c_lib/build
are linked (../c_lib/build_libs.sh runs incrementally before each build);
otherwise c_lib is fetched from GitHub and built as a subproject.
cmake/pins.cmake records the exact commit of each fetched dependency, so any
tag or fresh clone builds the same code. While developing, configure with
-DDEPS_LATEST=ON (or put it in your local CMakeUserPresets.json; scripts/build.sh
passes it on) to track each upstream's newest commit instead; the commits used land in
build/deps-resolved.cmake. Before tagging a release, build and test that way, then run
scripts/pin-deps.sh and commit cmake/pins.cmake. cmake --build build --target update_deps
re-fetches the newest upstream on demand. To use a local checkout of one dependency
(offline builds), pass -DFETCHCONTENT_SOURCE_DIR_TOKENIZERS_CPP=/path. ONNX Runtime is
pinned by version (ORT_VERSION) and archive checksum, and is bumped by hand.
./scripts/build.sh # check deps, configure, build
sudo cmake --install build # installs semqlite to /usr/local/binCheck out c_lib next to this repo
(../c_lib); it is detected automatically. Cleaning this project never
recompiles c_lib; to rebuild it from scratch, run ../c_lib/build_libs.sh --clean.
Manual cmake (without the build script):
cmake --preset dev # or: cmake -B build
cmake --build build -j8
sudo cmake --install buildProduces semqlite — drop-in sqlite3 replacement. Verified on macOS
(Apple Silicon) and Debian 13 (x86_64), both -DSEMQLITE_ONNX=ON and OFF.
semqlite ~/.ragger/memories.db # interactive shell
semqlite ~/.ragger/memories.db "SELECT ..." # one-shot queryshell.c here and sqlite3.c/sqlite3.h in c_lib/vendor/sqlite/ were
generated from sqlite/sqlite at the commit recorded in
vendor/sqlite/UPSTREAM_COMMIT.txt (same file in c_lib). Refresh both together
and re-apply the shell.c patch in vendor/sqlite/PATCHES.md:
git clone https://github.com/sqlite/sqlite
cd /path/to/sqlite-clone && git pull
mkdir build-amal && cd build-amal
../configure --disable-tcl
make sqlite3.c shell.c sqlite3.h
cp sqlite3.c sqlite3.h /path/to/c_lib/vendor/sqlite/
cp shell.c /path/to/SemanticSQLite/vendor/sqlite/SQLite itself is public domain. DMPHON/Double Metaphone is a published,
widely-implemented public algorithm. STEM_PORTER is the classic public-domain
1980 Porter algorithm. STEM_SNOWBALL links the vendored Snowball
libstemmer_c (BSD-3-Clause, snowballstem.org).
EMBED() links libllama (MIT license). This project's own code (the
EMBEDDING_SIM/EMBEDDING_DIST/EMBED implementations, the shell-integration
glue) is released with no restrictions — do whatever you want with it.