Why SQLite for cwkPippa
One user. One Mac. Tens of thousands of messages, growing slowly. Postgres would be overkill. The whole DB is a single file under , backed up by Time Machine like any other file. Zero admin.
aiosqlite wraps SQLite in an async API. Same SQL, async/await ergonomics. Fits right in with FastAPI.
Schema is plain SQL, no ORM
cwkPippa doesn't use SQLAlchemy. The schema is a few CREATE TABLE statements in backend/store/conversations.py. Migrations are ALTER TABLE in a function called init_db that's idempotent — adding a column? Use PRAGMA table_info to check first, only add if missing.
The mirror principle
SQLite is the convenience mirror for the JSONL ground truth. If it ever drifts (corruption, schema change gone wrong, partial writes), the recovery is purge the rows for that conversation and replay the JSONL. Never patch SQLite directly. You'll learn this the hard way in the Truth track.
? placeholders. SQL injection isn't the only reason — it's also how you get the right SQLite type coercion. Strings and bytes don't auto-convert in SQLite the way you'd expect.