CLAUDE.md updates: - new src/second_brain/embeddings/ entry in the project layout - setup section now lists the postgres-superuser bootstrap (CREATE SCHEMA AUTHORIZATION lovebug) and the alembic step - env var table now covers SECOND_BRAIN_DATABASE_URL, OLLAMA_URL, EMBEDDING_MODEL, and the pool sizing knobs - conventions section gained five new gotchas (the lovebug CREATE gap, public.embeddings ownership, best-effort embedding, etc.) postgres-migration-planning.md flipped from "planning questions" to a "what shipped" runbook — locked decisions table, operator setup steps, known gotchas, and the next-iteration backlog.
5.3 KiB
5.3 KiB
Postgres + pgvector migration — implementation notes
Status: shipped (2026-05-24). Originally a planning checklist; rewritten to capture the implemented design and the decisions that landed. Kept in the repo so the next iteration has the "why" without grepping commits.
What shipped
- Backing store switched from SQLite to the existing petalbrain Postgres
instance (pgvector/pg17), connecting over the homelab docker network at
homelab-postgres:5432. - Relational tables (
sources,extractions,wiki_pages) live in a newsecond_brainschema owned by thelovebugrole — no dedicated service role. - Embeddings reuse the shared
public.embeddingstable (HNSW vector index already in place). Rows are keyed by(source_schema='second_brain', source_table='extractions', source_id, embedding_model='nomic-embed-text'). For this round we embed extraction summaries only (not transcript chunks, not key_points / claims). - Embedding pipeline mirrors
vault_mcp.core.embeddings: sharedembedding_chunkingfor 512/64-token chunking → Ollamanomic-embed-text(768-d,num_ctx=8192) → delete-before-insert upsert with vector text literals (%s::vector). - Graceful degradation is the rule: missing DB URL, Ollama down, or any
DB error logs a warning and returns
None. The relational row and the file-based vault stay canonical. - Alembic stands up the
second_brainschema;public.embeddingsis managed out-of-band and intentionally outside the migration tree. - File-based wiki compiler is unchanged. No dual-write to
petalbrain.wiki. - No SQLite import — we started clean on Postgres.
Locked decisions (from the planning round)
| Question | Decision |
|---|---|
| DB instance | Existing petalbrain (homelab-postgres) |
| Schema | New second_brain schema (owned by lovebug) |
| Connection | Containerised: homelab-postgres:5432 (NOT host 127.0.0.1:5433) |
| Pooling | psycopg_pool.ConnectionPool min=1/max=10 — no PgBouncer in front |
| Driver | psycopg v3, SQLAlchemy uses postgresql+psycopg:// |
| PK style | Int autoincrement (SERIAL) — kept |
| Timestamps | Naive UTC timestamp without time zone — kept |
| Embedding scope | Extraction summaries only (Phase 1) |
| Embedding model | nomic-embed-text (768-d), Ollama via OLLAMA_URL |
| Migration tool | Alembic, second-brain owns its own tree |
public.embeddings |
NOT managed by our Alembic — shared with vault-mcp / ob1 |
| File-based wiki | Kept intact; no dual-write into petalbrain.wiki |
| Old SQLite data | Not imported — clean start |
Setup (operator runbook)
# 1. One-time, as postgres superuser, bootstrap the schema. lovebug doesn't
# have CREATE on petalbrain, so this can't be done from the app side.
docker exec homelab-postgres psql -U postgres -d petalbrain -c \
"CREATE SCHEMA second_brain AUTHORIZATION lovebug;
GRANT ALL PRIVILEGES ON SCHEMA second_brain TO lovebug;"
# 2. Point the project at the DB (env wins over settings.toml).
export SECOND_BRAIN_DATABASE_URL=\
"postgresql+psycopg://lovebug:<pw>@homelab-postgres:5432/petalbrain"
# 3. Install deps and run the migration.
uv sync
uv run alembic upgrade head
The lovebug password lives in /opt/backups/postgres-consolidation/credentials.env
(0600, host-only).
Key files (post-migration)
src/second_brain/models.py— schema-qualified ORM,__table_args__→second_brain; native Postgres enum types are scoped to the schema too.src/second_brain/database.py— psycopg-v3 engine + URL resolution (constructor arg →SECOND_BRAIN_DATABASE_URL→HERBYLAB_DATABASE_URL→[database].url).src/second_brain/embeddings/__init__.py— near-verbatim port ofvault_mcp/core/embeddings.pyadapted to our key tuple.alembic.ini+alembic/env.py— schema-pinned Alembic; the env.py guardsCREATE SCHEMAbehindpg_namespacebecauselovebuglacksCREATEon the database.alembic/versions/4a1e2f6c9d10_v1_second_brain_pipeline_tables.py— v1 migration: enum types +sources+extractions+wiki_pages.tests/test_smoke_embedding.py— end-to-end smoke test exercising the full Postgres + Ollama path. Auto-skips when either is unreachable.
Known gotchas
CREATE SCHEMA IF NOT EXISTSerrors aslovebugeven when the schema exists.pg_namespacelookup inalembic/env.pyis the workaround.+psycopgURL prefix. SQLAlchemy needs it; raw psycopg doesn't.database.pyadds it if missing; the embeddings module strips it.- Pool conflict.
database.pyopens an SQLAlchemy pool;embeddings/__init__.pyopens an independent psycopg pool. Both share the same DSN; both are small (min=1) so they don't strain the cluster. anthropic-skills:zero-checkneedspytest-cov. Added to the new[dependency-groups].devblock souv syncresolves it.
Next iterations (open, not blocked)
- Embed transcript chunks (or at least key_points / claims), not just summaries.
- Promote vector retrieval into the web UI (semantic search across extractions).
- Add a periodic backfill script that re-embeds extractions whose
embedding_modelno longer matches the current default. - Decide on the production vault path (
/opt/projects/second-brain-vault/?).