Skip to content

memini-ai v0.7.7 Audit Report

Date: 2026-07-10 Auditor: boomerang-architect Scope: Correctness (Area A) + Documentation (Area B)

Executive Summary

v0.7.7's core correctness is solidauto_detect_model, has_dim_mismatch, BM25 guards, multi-model RRF, and model aliases all work as designed with no latent bugs found. The tests (test_manager_dim_checks.py) are comprehensive and correctly exercise the new behavior. However, the documentation has critical gaps: the README's "Enabling Multi-Model" example (lines 73-79) omits MEMINI_EMBEDDING_DIM=1024, which will cause a dim-mismatch degradation to text-only search in v0.7.7's lenient mode — a user following this example verbatim will get a silently degraded server. The .env.example is missing all 5 v0.7.7 env vars. The README architecture tree is missing 20+ source files. The CHANGELOG v0.7.6 entry inaccurately claims enabled_models was reduced to 1 entry when the code has 2. The upgrading-embeddings.md guide references a non-existent pip extra (sentence-transformers[gpu]). The migration script exists but at a different path than documented. These are all doc bugs, not code bugs — the code is correct, but users following the docs will hit avoidable problems.

Severity Legend

  • CRITICAL — blocks user, breaks correctness, security risk
  • HIGH — significant bug, confusing behavior
  • MEDIUM — suboptimal, future-proofing
  • LOW — nitpick, style

Findings (sorted by severity)

[CRITICAL] B1.1: README "Enabling Multi-Model" example omits MEMINI_EMBEDDING_DIM=1024

  • Location: README.md:73-79
  • Description: The example shows export MEMINI_MODEL_NAME=BAAI/bge-m3 + export MEMINI_ENABLE_RRF=true but does NOT include export MEMINI_EMBEDDING_DIM=1024. BGE-M3 produces 1024-dim vectors. The default embedding_dim is 384. In v0.7.7's lenient mode, this mismatch logs a warning and degrades to text-only search — vector search is silently disabled. A user following this example verbatim will get a degraded server with no vector search capability.
  • Reproduction: Set MEMINI_MODEL_NAME=BAAI/bge-m3, leave MEMINI_EMBEDDING_DIM unset (defaults to 384). Start server. Call get_statusembeddingDimMismatch: true. All query_memories calls fall back to TEXT_ONLY.
  • Proposed fix: Add export MEMINI_EMBEDDING_DIM=1024 to the example block. Also add a note that the dim must match the model.
  • v0.7.8 priority: Must-fix

[HIGH] B3.1: .env.example missing all v0.7.7 env vars

  • Location: .env.example (72 lines)
  • Description: The .env.example file documents v0.7.0 dual-model RRF vars (EMBEDDING_MODE, ELEVATE_ENABLED, RRF_K, AUTO_EXTRACT_LOG_DIR, AUTO_EXTRACT_INTERVAL_SECONDS) but is missing all 5 v0.7.7 env vars: MEMINI_STRICT_EMBEDDING_DIM, MEMINI_AUTO_DETECT_MODEL, MEMINI_MODEL_NAME, MEMINI_ENABLE_RRF, RRF_TOP_K_PER_MODEL, MEMINI_ENABLED_MODELS. Users who copy .env.example to .env will not discover these critical configuration options.
  • Reproduction: grep -c "MEMINI_STRICT_EMBEDDING_DIM\|MEMINI_AUTO_DETECT_MODEL\|MEMINI_MODEL_NAME\|MEMINI_ENABLE_RRF\|RRF_TOP_K_PER_MODEL\|MEMINI_ENABLED_MODELS" .env.example → 0 matches.
  • Proposed fix: Add a "v0.7.7+ Model Selection" section to .env.example documenting all 6 new env vars with defaults and descriptions.
  • v0.7.8 priority: Must-fix

[HIGH] B1.2: README env var table missing v0.7.7 vars

  • Location: README.md:122-130
  • Description: The "Core Settings" env var table lists MEMINI_EMBEDDING_DIM but does not list MEMINI_STRICT_EMBEDDING_DIM, MEMINI_AUTO_DETECT_MODEL, MEMINI_MODEL_NAME, MEMINI_ENABLE_RRF, RRF_TOP_K_PER_MODEL, or MEMINI_ENABLED_MODELS. These are all user-facing configuration options introduced in v0.7.5-v0.7.7.
  • Proposed fix: Add rows for all 6 vars to the Core Settings table.
  • v0.7.8 priority: Must-fix

[HIGH] B4.1: upgrading-embeddings.md references non-existent pip extra

  • Location: docs/upgrading-embeddings.md:48-51
  • Description: Step 2 says uv pip install sentence-transformers[gpu] / pip install sentence-transformers[gpu]. The pyproject.toml has no [gpu] optional-dependency group. The sentence-transformers package on PyPI does not ship a [gpu] extra either. This command will fail or silently install the base package without GPU acceleration. The correct approach is to ensure torch with CUDA support is installed (which is already a core dependency in pyproject.toml).
  • Reproduction: pip install sentence-transformers[gpu] → "WARNING: sentence-transformers does not provide the extra 'gpu'"
  • Proposed fix: Replace with pip install torch --index-url https://download.pytorch.org/whl/cu118 (or the appropriate CUDA version) and note that torch>=2.0 is already a core dependency.
  • v0.7.8 priority: Must-fix

[HIGH] B4.2: Migration script path in docs doesn't match actual location

  • Location: docs/upgrading-embeddings.md:56 and README.md:35
  • Description: Both docs reference archives/memini-embedding-migration-2026-07-10/migrate_minilm_to_bge_m3.py as if it's under memini-ai-dev/. The actual file is at /home/jcharles/Projects/MCP-Servers/archives/memini-embedding-migration-2026-07-10/migrate_minilm_to_bge_m3.py — one level up in the workspace root. Running the command from memini-ai-dev/ will fail with "No such file or directory."
  • Reproduction: cd memini-ai-dev && python archives/memini-embedding-migration-2026-07-10/migrate_minilm_to_bge_m3.py → FileNotFoundError.
  • Proposed fix: Either move the archives directory into memini-ai-dev/ or update the docs to use the absolute path or ../archives/....
  • v0.7.8 priority: Must-fix

[MEDIUM] B1.3: README architecture tree missing 20+ source files

  • Location: README.md:322-352
  • Description: The architecture tree shows 6 top-level modules (config.py, server.py, api/, memory/, postgres/, model/, indexer/, utils/) but is missing: llm/ (5 files), audit/ (2 files), graph.py, rate_limiter.py, thought_chains.py, dialectic.py, multi_peer.py, entity_extractor.py, knowledge_graph.py, decay.py, user_model.py, tiered_loader.py, precompress.py, extractor.py, trust_engine.py, main.py, __init__.py. Also missing from subdirs: memory/rrf.py, memory/merger.py, indexer/snapshot.py, indexer/pause_controller.py, indexer/constants.py, utils/sanitizer.py.
  • Proposed fix: Regenerate the tree from the actual file listing (53 source files in src/memini_ai/).
  • v0.7.8 priority: Should-fix

[MEDIUM] B1.4: README tool count says "35+" but actual count is 52

  • Location: README.md:207-208
  • Description: The heading says "MCP Tools (35+)" but server.py::_setup_tools registers 52 tools. The "35+" is technically correct (52 ≥ 35) but misleadingly low. The tool listing in the README (lines 212-252) only shows ~28 tools grouped by category, missing ~24 tools.
  • Proposed fix: Update to "MCP Tools (52)" and add the missing tools to the categorized listing.
  • v0.7.8 priority: Should-fix

[MEDIUM] B1.5: README Docker Compose uses pgvector/pgvector:pg16 but user runs timescaledb-ha:pg18

  • Location: README.md:287
  • Description: The Docker Compose example uses image: pgvector/pgvector:pg16. The user's actual deployment uses docker.io/timescale/timescaledb-ha:pg18 (per AGENTS.md Session 12/40 review notes). This is misleading for users who want to match the production setup. The pgvector/pgvector:pg16 image works fine for development but doesn't include TimescaleDB.
  • Proposed fix: Add a note that the example uses a minimal pgvector image for development; for production with TimescaleDB, use timescale/timescaledb-ha:pg18.
  • v0.7.8 priority: Should-fix

[MEDIUM] B2.1: CHANGELOG v0.7.6 inaccurately claims enabled_models reduced to 1 entry

  • Location: CHANGELOG.md:55
  • Description: The v0.7.6 CHANGELOG says: "enabled_models default in config.py reduced to ['BAAI/bge-m3'] (was ['BAAI/bge-m3', 'BAAI/bge-large-en-v1.5'])." The actual code in config.py:63-69 has ['all-MiniLM-L6-v2', 'BAAI/bge-m3'] — 2 entries, not 1. The CHANGELOG is wrong: it was reduced from 3 to 2 (removing BGE-Large), not from 2 to 1.
  • Proposed fix: Correct to: "reduced to ['all-MiniLM-L6-v2', 'BAAI/bge-m3'] (was ['all-MiniLM-L6-v2', 'BAAI/bge-m3', 'BAAI/bge-large-en-v1.5'])."
  • v0.7.8 priority: Should-fix

[MEDIUM] B5.1: AGENTS.md missing v0.7.7 review note

  • Location: AGENTS.md:83-98 (Review Notes section)
  • Description: The latest review note is for Session 40 (v0.7.6). There is no Session 41 or v0.7.7 entry. The env var table (lines 67-82) is also missing v0.7.7 vars (MEMINI_STRICT_EMBEDDING_DIM, MEMINI_AUTO_DETECT_MODEL, MEMINI_MODEL_NAME, MEMINI_ENABLE_RRF).
  • Proposed fix: Add a Session 41 review note documenting v0.7.7 changes. Add the 4 new env vars to the table.
  • v0.7.8 priority: Should-fix

[MEDIUM] B5.2: HANDOFF.md missing Session 41 entry

  • Location: HANDOFF.md (latest entry is Session 40)
  • Description: No Session 41 handoff entry exists. The file ends at Session 40 (v0.7.6). v0.7.7 was released without a handoff entry documenting the auto-detect, lenient dim-mismatch, and new env vars.
  • Proposed fix: Add a Session 41 entry documenting v0.7.7 changes, process state, and quick-resume commands.
  • v0.7.8 priority: Should-fix

[LOW] B1.6: README "Multi-Model RRF" section doesn't mention model_name-driven selection

  • Location: README.md:26-67
  • Description: The Multi-Model RRF section describes EMBEDDING_MODE dispatch (cpu/auto/gpu) but doesn't mention that model selection is now driven by MEMINI_MODEL_NAME (v0.7.5+). The table at line 30-33 correctly lists model names and aliases, but the mode dispatch table (lines 39-43) doesn't explain how MEMINI_MODEL_NAME interacts with EMBEDDING_MODE.
  • Proposed fix: Add a sentence explaining that MEMINI_MODEL_NAME selects which model is loaded, and EMBEDDING_MODE controls how writes/queries are dispatched.
  • v0.7.8 priority: Nice-to-have

[LOW] B4.3: Migration script uses deprecated get_sentence_embedding_dimension

  • Location: archives/memini-embedding-migration-2026-07-10/migrate_minilm_to_bge_m3.py:108
  • Description: Line 108 calls model.get_sentence_embedding_dimension() which is deprecated in sentence-transformers 3.x and emits a FutureWarning. The v0.7.7 codebase already migrated to get_embedding_dimension() (see test_manager_dim_checks.py::TestDeprecationFix). The migration script should use the new method name for consistency.
  • Proposed fix: Change get_sentence_embedding_dimension()get_embedding_dimension().
  • v0.7.8 priority: Nice-to-have

Area A: Correctness

A1: auto_detect_model (model/manager.py:313-344)

Verdict: CORRECT — no bugs found.

  • Fires at the right time: Called from server.py::_init_memory_system (line 210) after system.initialize() succeeds and before any MCP tools are served. This is the correct place — the DB is connected, the memory count is available, and no user operations have run yet.
  • Correctly detects "new deployment": Checks memory_count == 0 AND config.model_name == DEFAULT_MODEL_NAME ("all-MiniLM-L6-v2") AND config.auto_detect_model == True. All three conditions must be true. When they are, sets config.model_name = BGE_M3_MODEL_ID and config.embedding_dim = BGE_M3_DIM, then returns True.
  • Correctly does NOT fire for "existing user": When memory_count > 0, returns False immediately (line 328-330). The user's existing MiniLM setup is preserved.
  • Correctly does NOT fire when user explicitly set model: When config.model_name != DEFAULT_MODEL_NAME, returns False (line 325-327). The user's explicit choice is respected.
  • No race condition: auto_detect_model is a @classmethod that reads config and memory_count as arguments. It's called synchronously within _init_memory_system before the server starts accepting MCP requests. There is no concurrent access to the config during this window. The ModelManager singleton is not yet acquired at this point.
  • Re-initialization after override: When auto_detect_model returns True, server.py:211-218 closes the old MemorySystem, creates a new one, and re-initializes. This ensures the new model_name and embedding_dim are picked up by the fresh ModelManager singleton on first acquire().
  • Tests cover all 4 branches: test_empty_db_selects_bge_m3, test_populated_db_keeps_minilm, test_user_set_model_name_respected, test_auto_detect_disabled_returns_false — all passing.

A2: has_dim_mismatch + add_memory + query_memories dim-mismatch path

Verdict: CORRECT — no bugs found.

  • has_dim_mismatch is set correctly: In manager.py::_load_model (lines 224-260), when actual_dim != self._embedding_dim and strict_embedding_dim=False (default), self._has_dim_mismatch = True is set and a warning is logged. The model is kept loaded (so get_status can report metadata).
  • add_memory catches EmbeddingDimMismatchError: In system.py::add_memory (lines 176-195), generate_embedding is called in a try/except. When EmbeddingDimMismatchError is raised (from embeddings.py:74-80), the exception is caught, a warning is logged, and input.vector = None is set. The memory is then stored with a NULL embedding. The text content is fully preserved.
  • Text is preserved: The add_memory flow continues past the except block to line 198 (input.content_hash = hash_content(input.text)) and then to the mode dispatch. The memory is written to the DB with embedding=NULL but text intact.
  • query_memories forces TEXT_ONLY: In system.py::query_memories (lines 402-404), manager.has_dim_mismatch is checked and options.strategy = SearchStrategy.TEXT_ONLY is set. This overrides any caller-specified strategy, ensuring vector search (which would fail or return garbage) is never attempted.
  • Tests cover both paths: test_lenient_mismatch_does_not_raise verifies the model loads with _has_dim_mismatch=True. test_strict_mismatch_raises_runtime_error verifies the opt-in crash behavior.

A3: BM25 guards (memory/search.py)

Verdict: CORRECT — no bugs found.

  • _build_bm25_index (lines 48-83): Filters out empty/whitespace-only text before building the index (line 63). If no valid memories remain, sets _bm25_index = None and returns (lines 65-68). Additionally filters out empty token lists (lines 72-74) — handles the edge case of text that is all punctuation with no whitespace splits. If no valid token pairs remain, sets index to None (lines 76-79).
  • text_only_search (lines 265-334): Guards against empty/whitespace query at line 280-281 (returns []). Guards against unbuilt index at line 285-286 (returns []). Guards against all-punctuation query tokens at line 289-291 (returns []). All three guards are correct and prevent the ZeroDivisionError that would occur in BM25Okapi.get_scores() with empty input.
  • text_search_collection (lines 439-496): Same three guards: empty query (line 456-457), empty entries (line 462-463), empty-text entries filtered (line 466-467), empty token lists (line 471-473), empty query tokens (line 479-481). All correct.
  • No edge cases where real results are incorrectly dropped: The guards only filter out degenerate inputs (empty strings, all-whitespace, all-punctuation). Valid text with actual words always passes through. The _tokenize method (line 35-46) uses text.lower().split() which correctly handles mixed content.

A4: Multi-model RRF (memory/rrf.py + system.py)

Verdict: CORRECT — no bugs found.

  • COLUMN_TO_MODEL (rrf.py:123-126): Maps "embedding""sentence-transformers/all-MiniLM-L6-v2" and "embedding_bge_m3""BAAI/bge-m3". Both are full HuggingFace names that match ModelManager.MODEL_DIMS keys. This is correct — the v0.7.5 bug (short name 'all-MiniLM-L6-v2' not recognized by ModelManager) is fixed.
  • RRF fusion for memories in BOTH columns: In rrf_search (lines 135-250), each model's column is searched independently. Results are accumulated in all_results dict keyed by memory ID. The rrf_score is the sum of 1/(k+rank) contributions from each column. A memory appearing in both columns gets contributions from both — this is the intended boost behavior.
  • Memories in only one column: If a memory only has an embedding (MiniLM) but no embedding_bge_m3, it only gets a contribution from the MiniLM search. The BGE-M3 search won't find it (the SQL filters WHERE embedding_bge_m3 IS NOT NULL). This is correct — the memory participates with whatever vector spaces it has.
  • k=60 used consistently: rrf_search default k=60 (line 139). _query_dual_model_rrf uses self._resolved_rrf_k which defaults to get_config().rrf_k = 60. _rrf_fusion in search.py also defaults to k=60. All consistent.
  • MODEL_TO_DIM (rrf.py:129-132): Correctly maps MiniLM → 384, BGE-M3 → 1024. Mirrors model/manager.py::MODEL_DIMS.
  • _query_dual_model_rrf (system.py:436-517): Correctly over-fetches (fetch_k = max(options.top_k * 2, options.top_k + 5)) to give RRF enough material. Propagates threshold and exact_search to the 384-side (v0.7.3 fix). Falls back to 384-only if the DB lacks 1024 support. Re-hydrates from 384 entries (preferred) with 1024 fallback.

A5: Model name aliases (model/manager.py)

Verdict: CORRECT — no bugs found.

  • Alias map location: _MODEL_ALIASES dict at lines 67-74.
  • 'minilm' is a valid alias: Maps to MINILM_MODEL_ID = "sentence-transformers/all-MiniLM-L6-v2". Verified.
  • 'bge-m3' is a valid alias: Maps to BGE_M3_MODEL_ID = "BAAI/bge-m3". Verified.
  • 'all-MiniLM-L6-v2' is the canonical form: The full HF name "sentence-transformers/all-MiniLM-L6-v2" is the canonical form. The short name "all-MiniLM-L6-v2" is also accepted as an alias (line 68). Both resolve to the same canonical ID.
  • Aliases are case-sensitive: _normalize_model_name (line 77-86) does key = name.strip() but no .lower(). The dict keys are all lowercase. So "BGE-M3" would NOT match "bge-m3" — it would be passed through as an unknown custom model name. This is a potential footgun but not a bug (the behavior is consistent and documented).
  • Other aliases: "minilm-l6-v2" → MiniLM (line 70). The full HF names are also self-aliases (lines 72-73) so passing the canonical name works as a no-op normalization.
  • Unknown names pass through: _normalize_model_name returns the input unchanged if not found in _MODEL_ALIASES (line 86). This allows custom HuggingFace models. The _load_model method will attempt to load it via SentenceTransformer(model_id).

Area B: Documentation

B1: README.md accuracy

Claim Location Verdict Detail
'minilm' and 'bge-m3' are valid aliases Line 32-33 ✅ CORRECT Both in _MODEL_ALIASES
Example config: MEMINI_MODEL_NAME=BAAI/bge-m3 + MEMINI_ENABLE_RRF=true Lines 73-79 ❌ CRITICAL BUG Missing MEMINI_EMBEDDING_DIM=1024 — causes dim mismatch
Env var table lists core settings Lines 122-130 ❌ INCOMPLETE Missing 6 v0.7.7 vars
"MCP Tools (35+)" Line 207-208 ⚠️ OUTDATED Actual count is 52
Multi-Model RRF section Lines 26-67 ⚠️ PARTIAL Doesn't explain model_name vs embedding_mode interaction
Docker Compose pgvector/pgvector:pg16 Line 287 ⚠️ MISLEADING User runs timescaledb-ha:pg18
Architecture tree Lines 322-352 ❌ INCOMPLETE Missing 20+ source files
EMBEDDING_MODE table Lines 39-43 ✅ CORRECT Mode dispatch is accurate

B2: CHANGELOG.md

  • v0.7.7 entries: Well-formatted, accurate. The "Changed" and "Added" sections correctly describe auto-detect, lenient dim-mismatch, and new env vars.
  • v0.7.6 entry: One inaccuracy — claims enabled_models reduced to ['BAAI/bge-m3'] but code has ['all-MiniLM-L6-v2', 'BAAI/bge-m3'] (2 entries, not 1). See finding B2.1.
  • Format consistency: All entries follow the Keep a Changelog format with ## [version] - date headers and ### Changed/Added/Removed/Fixed subsections. Consistent across all versions.

B3: .env.example

  • Exists: Yes (72 lines).
  • Missing v0.7.7 vars: MEMINI_STRICT_EMBEDDING_DIM, MEMINI_AUTO_DETECT_MODEL, MEMINI_MODEL_NAME, MEMINI_ENABLE_RRF, RRF_TOP_K_PER_MODEL, MEMINI_ENABLED_MODELS. See finding B3.1.
  • Existing v0.7.0 vars: EMBEDDING_MODE, ELEVATE_ENABLED, RRF_K, AUTO_EXTRACT_LOG_DIR, AUTO_EXTRACT_INTERVAL_SECONDS — all present and correctly documented.

B4: docs/upgrading-embeddings.md

  • 4-step recipe accuracy:
  • Step 1 (backup): pg_dump command is correct for the user's PG version (PG16+). Uses -Fc (custom format) which is the recommended format for pg_restore.
  • Step 2 (install deps): sentence-transformers[gpu] is NOT a valid extra. See finding B4.1.
  • Step 3 (run migration): Script path is wrong relative to memini-ai-dev/. See finding B4.2. The script itself is correct and idempotent (verified by reading the source — it skips rows that already have embedding_bge_m3 set, uses transactions, writes a backup before any modifications).
  • Step 4 (update env vars): Correctly includes MEMINI_EMBEDDING_DIM=1024 alongside MEMINI_MODEL_NAME=BAAI/bge-m3. This is the correct config — unlike the README example which omits the dim.
  • Rollback instructions: Correct. Unsetting MEMINI_MODEL_NAME and setting MEMINI_EMBEDDING_DIM=384 restores MiniLM. The original 384-dim vectors are preserved.
  • FAQ: Accurate. The dim-mismatch FAQ correctly describes v0.7.7's lenient behavior.

B5: Other docs

  • AGENTS.md: Session 40 review note (line 85) reflects v0.7.6, not v0.7.7. Missing v0.7.7 entry. Env var table (lines 67-82) missing v0.7.7 vars. See finding B5.1.
  • HANDOFF.md: Latest entry is Session 40 (v0.7.6). No Session 41 entry for v0.7.7. See finding B5.2.
  • CONTEXT.md: Not audited (not in the file list provided, but likely needs a v0.7.7 section).

Recommendations for v0.7.8

# Fix Priority Effort Files
1 Add MEMINI_EMBEDDING_DIM=1024 to README "Enabling Multi-Model" example Must-fix 1 line README.md:77
2 Add all 6 v0.7.7 env vars to .env.example Must-fix ~15 lines .env.example
3 Add v0.7.7 env vars to README Core Settings table Must-fix ~6 rows README.md:122-130
4 Fix sentence-transformers[gpu] reference in upgrading-embeddings.md Must-fix 2 lines docs/upgrading-embeddings.md:48-51
5 Fix migration script path in docs (or move archives into memini-ai-dev/) Must-fix 2 lines or mv docs/upgrading-embeddings.md:56, README.md:35
6 Regenerate README architecture tree from actual file listing Should-fix ~30 lines README.md:322-352
7 Update README tool count to 52 and add missing tools Should-fix ~30 lines README.md:207-252
8 Fix CHANGELOG v0.7.6 enabled_models claim (2 entries, not 1) Should-fix 1 line CHANGELOG.md:55
9 Add v0.7.7 review note to AGENTS.md + update env var table Should-fix ~15 lines AGENTS.md
10 Add Session 41 handoff entry for v0.7.7 Should-fix ~30 lines HANDOFF.md
11 Add note about Docker image difference (pgvector vs timescaledb) Should-fix 2 lines README.md:287
12 Fix deprecated get_sentence_embedding_dimension in migration script Nice-to-have 1 line archives/.../migrate_minilm_to_bge_m3.py:108
13 Add model_name vs embedding_mode interaction explanation to README Nice-to-have ~3 lines README.md:39-43

Total estimated effort: ~2 hours for all Must-fix items, ~4 hours for all items.