Skip to content

feat(install): SQLite-first plugin install — full hook experience, zero system PostgreSQL - #160

Merged
cdeust merged 5 commits into
mainfrom
feat/sqlite-first-install
Jul 22, 2026
Merged

feat(install): SQLite-first plugin install — full hook experience, zero system PostgreSQL#160
cdeust merged 5 commits into
mainfrom
feat/sqlite-first-install

Conversation

@cdeust

@cdeust cdeust commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

The plugin install now delivers the complete experience (all 9 hooks + memory) with no system-level PostgreSQL: default backend is SQLite, provisioned by setup.py with pip deps only — no brew/apt, no pgvector, no eager model download. Postgres becomes the documented opt-in upgrade (install-plugin.sh --postgres).

  • Never downgrades an existing install: three detectors (env DATABASE_URL, prior backend marker, reachable local cortex DB at 127.0.0.1:5432) force the full PostgreSQL path — verified live against a real local Postgres.
  • Backend choice persists to ~/.claude/methodology/backend.json (name only, never a URL) and resolves at runtime via new mcp_server/infrastructure/backend_marker.py; any operator env var beats the marker.
  • Model download was already lazy in the engine — the install-time pre-cache is now skipped on the default path; first encode() prints size + cache dir + "then runs fully offline".
  • Found and fixed a real gap: 5 of 9 hooks spoke raw psycopg despite the "SQLite fallback" claim. The two user-visible ones are ported to the store abstraction: session_start (full banner incl. receipts; previously printed brew-install instructions on SQLite) and auto_recall (FTS5 recall with stopword-stripped OR terms). doctor/check_setup//preflight are backend-aware.
  • README discloses honestly what SQLite mode lacks (in-process fusion instead of PL/pgSQL + HNSW; three PG-only hook enrichments degrade to no-ops).

Test plan

  • 744 passed (scripts/infrastructure/architecture/invariants/doctor) + 108 (hooks incl. live PG e2e) + 10 (check_setup); new: 26 backend-marker + 11 sqlite-hook-path tests
  • ruff check + ruff format --check clean
  • Install script exercised across 8 sandbox scenarios (fresh sqlite, marker re-run, --postgres, env-URL, CORTEX_BACKEND, marker protection, bad arg, live local-PG detection)
  • CI green

🤖 Generated with Claude Code

cdeust and others added 5 commits July 22, 2026 17:56
New mcp_server/infrastructure/backend_marker.py: the installer persists
its provisioned backend to ~/.claude/methodology/backend.json (backend
NAME only, never a URL) and scripts/launcher.py — the single entry point
for the MCP server and every hook — resolves it into
CORTEX_MEMORY_STORE_BACKEND at launch. Precedence guarantees an
operator's explicit configuration (CORTEX_MEMORY_STORE_BACKEND,
CORTEX_BACKEND, DATABASE_URL, CORTEX_MEMORY_DATABASE_URL) always wins,
so an existing PostgreSQL install can never be silently downgraded to
SQLite by a plugin update. The launcher also stops injecting the
PostgreSQL DATABASE_URL default when the resolved backend is sqlite,
and normalizes the plugin manifest's now-empty user_config URL to
absent. Engine backend selection (memory_store) is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…opt-in

The plugin postInstall now defaults to the zero-config SQLite path on
every OS: Python deps + verification only — no brew/apt PostgreSQL, no
pgvector, no eager embedding-model download (the model fetches lazily on
first encode; embedding_engine's download log now states the ~100 MB
one-time size and cache location, figure per PRIVACY.md / the pre-cache
step it replaces). install-plugin.sh gains a --postgres flag (and
CORTEX_BACKEND=postgres env) that runs the previous full path, and an
existing PostgreSQL install is auto-detected (env URL, prior marker, or
a reachable local cortex database at 127.0.0.1:5432) and kept — never
downgraded. The provisioned backend is persisted to the backend marker
via Path.home() inside python (Windows Git Bash $HOME is POSIX-style and
native python.exe would misresolve it). plugin.json's postInstall
message now matches reality and user_config.database_url defaults to
empty (set it only to opt into PostgreSQL). scripts/setup.py's SQLite
mode is promoted from CI-testing convenience to the supported plugin
default and skips the eager model pre-cache; the PostgreSQL path keeps
it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On the zero-config SQLite default the two context-injecting hooks
previously spoke raw psycopg SQL and degraded to nothing (session_start
even printed brew-install-PostgreSQL instructions). Both now branch on
the resolved backend (backend_marker.effective_backend) and go through
the store abstraction: session_start builds the same banner (checkpoint
+ anchors + hot memories + injection receipt via
emit_injection_receipt) from SqliteMemoryStore; auto_recall runs an
FTS5 recall with the PG contract (heat floor, protected-first,
benchmarks excluded, receipts). The FTS query is built from
stopword-stripped OR'd terms because FTS5's unicode61 tokenizer has no
stemming/stopwords — MATCHing the raw prompt ANDs every token and
almost never hits (mirrors plainto_tsquery's stopword strip; terms are
double-quoted so user text cannot inject FTS5 operators; term count
bounded by the existing query[:200] cap). PG-only banner extras (team
decisions, pending wiki curation, grooming staleness) are skipped on
SQLite — disclosed in README.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
doctor previously required PG driver / DATABASE_URL / connection /
extensions unconditionally, so every zero-config SQLite install failed
four checks and check_setup / /preflight reported a healthy install as
broken. active_checks() now resolves the backend exactly like the
launcher and hooks do (env var, then the installer's backend marker)
and swaps the four PG checks for a single SQLite store-open check
(SqliteMemoryStore's constructor runs DDL + migrations, so a successful
open proves the storage path). check_setup consumes active_checks()
instead of CHECKS; the PostgreSQL list is unchanged and remains the
fallback on any resolution failure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
README: the Claude Code plugin section is promoted out of the collapsed
details block and rewritten — zero-config SQLite path first (deps only,
lazy ~100 MB one-time model download, all hooks registered), the
--postgres upgrade second with the one-line honest claim of what it
adds (connection-pooled concurrency, server-side PL/pgSQL WRRF fusion,
pgvector HNSW ANN at very large scale — figures already asserted in
Under the Hood), plus an explicit disclosure of what SQLite mode does
NOT do (in-process fusion without HNSW; team-decision injection,
preemptive context, and pipeline heat-bumps degrade to silent no-ops).
PRIVACY.md/CLAUDE.md/module-inventory stop asserting the plugin path is
PostgreSQL. cortex-setup-project Phase 1 verifies via memory_stats
first so a healthy SQLite install never triggers a PostgreSQL install.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cdeust
cdeust merged commit 70b80a6 into main Jul 22, 2026
14 checks passed
@cdeust
cdeust deleted the feat/sqlite-first-install branch July 22, 2026 16:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant