What the build taught.
Engineering observations that emerged from building the Lucid Decision System. Not best-practices posts — specific things that were wrong, then fixed.
Local embeddings are sufficient for personal-scale semantic retrieval
nomic-embed-text via Ollama produces 768-dim vectors that perform meaningfully at personal decision volumes. OpenAI API adds cost and data-egress risk with no measurable quality gain at this scale. The assumption that "local models are a compromise" did not survive contact with the actual retrieval results.
Pin your qdrant-client version
qdrant-client 1.16 silently removed the search() method and replaced it with query_points(). There was no deprecation warning in the version that was pinned. Lesson: vector DB client libraries are not stable across minor versions. Always pin, always test the upgrade path explicitly.
Sequential phases create clean dependency chains
The Learning Engine (Phase 2) and Memory Layer (Phase 3) were designed as separate phases rather than merged. The payoff: the assumption_history schema from Phase 2 became a first-class ranking input in the Phase 3 Retrieval Broker — with zero rework. The phases were more tightly coupled than the spec made explicit. Separation made that coupling visible before it became a constraint.
Celery embed dispatch must be wrapped in try/except at the call site
The Celery embed_object.delay() call runs after the Postgres commit — after L1 is already written. If Redis is down, the unguarded Celery call crashes the entire API endpoint with a RuntimeError. L2 (Qdrant) is a derivative index, not the primary store. A Redis outage must never fail a decision write. The fix: wrap every embed dispatch in try/except with a pass. Embedding is best-effort.
DQS needs an upper-bound clamp, not just zero-division guards
The Decision Quality Score formula has three terms summing to 1.0. The uncertainties_covered term divides covered by total_unexpected. If a user enters covered > total_unexpected (a data-entry artifact, not a meaningful claim), the formula produces DQS > 1.0, which violates the ecs_profiles.epistemic_score check constraint. The fix: cap uncertainties_covered at total_unexpected before dividing, then clamp the final result to min(1.0, total). Both guards are necessary.
Notes are added as they emerge from active engineering work — not on a schedule. These five are extracted directly from the Lucid Decision System build log. More will appear as Phase 4 and beyond surface new observations.