Skip to content

feat: zero-download semantic fallback — algorithmic embeddings + code-aware FTS (#169, Phase B) - #181

Merged
cdeust merged 3 commits into
mainfrom
feat/semantic-fallback-169b
Jul 24, 2026
Merged

cdeust merged 3 commits into
mainfrom
feat/semantic-fallback-169b

Conversation

@cdeust

@cdeust cdeust commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Phase B — full #169 on the merged #173 seams

Reimplements the zero-download semantic fallback on the provider / lifecycle /
factory seams from PR #175, with no monolith and the root cause fixed.

Closes #169

1. Fallback provider, selected by the factory per ModelState

  • embedding_fallback_provider.AlgorithmicEmbeddingProvider — the SECOND
    EmbeddingProvider (the neural EmbeddingEngine is the first). Deterministic,
    download-free, same EMBEDDING_DIM; delegates to shared.algorithmic_embedding
    (TF + Random Indexing + within-document co-occurrence bridging; sources in the
    module docstring).
  • embedding_factory.use_fallback(state) is the total state→provider map:
    LOADED → neural; PACKAGE_ABSENT, MODEL_FILES_ABSENT, LOAD_RAISED,
    UNINITIALIZED → fallback. No state maps to nothing (test asserts totality).
  • The lifecycle no longer blocks or crashes: MODEL_FILES_ABSENT / LOAD_RAISED
    serve fallback this session and fetch in the background
    (embedding_downloads.py, honouring CORTEX_EMBEDDING_ZERO_DOWNLOAD), upgrading
    next session.

2. Root-cause fix (the PR #172 empty-stamp bug)

SqliteMemoryStore stamps embedding_model unconditionally on write, outside
the _has_vec branch — so a store built without sqlite-vec still records
'fallback', never ''. Regression test: sqlite-vec absent + sentence-transformers
absent → stamp 'fallback'.

3. Loud engagement + telemetry

One WARNING when the fallback engages; get_telemetry surfaces embedding_mode
(neural / fallback / unknown) via factory.current_embedding_mode.

4. Code-aware FTS on all recall paths

camelCase/snake_case sub-tokens at index time (augment_content) and query
time
— recall WRRF _signal_fts, entity MATCH, and
recall_helpers.compute_vector_fts. memories_fts converted external→self-content
with a one-shot rebuild+reindex migration. Test: a lowercase query recalls a
camelCase symbol memory through the recall_helpers path.

5. Scheduled upgrade

consolidation/embedding_upgrade.run_embedding_upgrade_cycle re-embeds
fallback-stamped memories with the neural encoder and restamps them 'neural' once
the model is available; wired into consolidate's compress phase, count in the
response. Test: fallback memories + model available + cycle → restamped neural.

6 & 8. Genuine absence + mixed-store invariant

  • Subprocess test: sentence_transformers genuinely absent (import hook raises,
    not monkeypatched) + CORTEX_EMBEDDING_ZERO_DOWNLOAD → remember + recall
    round-trip with zero network, stamp 'fallback', PACKAGE_ABSENT.
  • Mixed store: neural and fallback vectors never cross-rank (query-space vec
    filter) — a neural vector identical to a fallback query is excluded.

Benchmark — LongMemEval-S, SQLite three-way (n=50, this code)

mode MRR Recall@10
(a) no-vector baseline 0.275 46.0%
(b) algorithmic fallback 0.378 66.0%
(c) sentence-transformers 0.609 94.0%

Fallback vs no-vector: ΔMRR +0.102, ΔRecall@10 +20.0 pp → fallback beats the
no-vector baseline (adoption criterion met). Results + MANIFEST under
benchmarks/results/semantic-fallback-169/.

Tests / gates

  • New: test_semantic_fallback_169.py (13 tests: provider/selection/stamp/mixed
    store/upgrade/subprocess/migration), test_algorithmic_embedding.py,
    test_code_tokenize.py. Existing embedding suite (56) passes unchanged.
  • ruff check . + ruff format --check clean. Full-suite tally reported on the PR
    thread once the run completes.
  • Vector-path tests carry skipif(not sqlite-vec); they run on the [sqlite]
    CI legs (test-sqlite / test-windows) and skip on the no-[sqlite] matrix.

🤖 Generated with Claude Code

cdeust and others added 3 commits July 24, 2026 23:49
…#169)

Phase B — reimplements #169 on the merged provider/lifecycle/factory seams.

1. Second provider: AlgorithmicEmbeddingProvider (embedding_fallback_provider.py)
   implements EmbeddingProvider via shared.algorithmic_embedding (TF + Random
   Indexing + co-occurrence bridging). The factory selects per ModelState —
   embedding_factory.use_fallback maps every non-LOADED state (PACKAGE_ABSENT,
   MODEL_FILES_ABSENT, LOAD_RAISED, UNINITIALIZED) to the fallback and LOADED to
   neural; the mapping is total (no state maps to nothing). The lifecycle no
   longer blocks or crashes: MODEL_FILES_ABSENT and LOAD_RAISED serve fallback
   this session and fetch in the background (embedding_downloads.py), upgrading
   next session.

2. Root-cause fix: SqliteMemoryStore stamps embedding_model UNCONDITIONALLY on
   write (outside the _has_vec branch) — 'fallback' even with sqlite-vec absent,
   never ''.

3. Loud: one WARNING when the fallback engages + get_telemetry embedding_mode.

4. Code-aware FTS on all recall paths: camelCase/snake_case sub-tokens at index
   time (augment_content) and query time (recall WRRF _signal_fts, entity MATCH,
   and recall_helpers.compute_vector_fts). memories_fts converted to
   self-content with a one-shot rebuild+reindex migration.

5. Scheduled upgrade: consolidation/embedding_upgrade.run_embedding_upgrade_cycle
   re-embeds fallback-stamped memories with neural once available and restamps
   them, wired into consolidate's compress phase; count in the response.

6/8. Mixed store never cross-ranks (query-space vec filter); genuine
   sentence-transformers absence + empty HF_HOME covered by a subprocess test.

Co-Authored-By: Claude Fable 5 <[email protected]>
SQLite-specific harness (reuses the production harness's dataset + scoring) that
compares no-vector / algorithmic fallback / sentence-transformers, since the PG
harness has no such toggle. n=50 on this PR's code (commit 8347944):
no-vector MRR 0.275 / R@10 46% → fallback 0.378 / 66% (+0.102 MRR, +20pp) →
neural 0.609 / 94%. Fallback beats the no-vector baseline — adoption criterion
met. Results + MANIFEST under benchmarks/results/semantic-fallback-169/.

Co-Authored-By: Claude Fable 5 <[email protected]>
…additions

The unconditional embedding_model stamp, select_fallback_embeddings,
reembed_memory, and the FTS augment/migration shifted the SQLite heat writers
(447/477/541 → 493/523/587). Pins updated per the test's own instructions; no
new heat writer introduced.

Co-Authored-By: Claude Fable 5 <[email protected]>
@cdeust
cdeust merged commit 0db6b2e into main Jul 24, 2026
14 checks passed
@cdeust
cdeust deleted the feat/semantic-fallback-169b branch July 24, 2026 22:54
github-actions Bot pushed a commit that referenced this pull request Nov 10, 2026
…et (ADR-1129) (#965)

A pull request reviewed by the workflow that preceded the cap and the
verdict line had its budget used by reviews that posted no verdict: the
review was skipped on every new head and the merge was held for ever
(ai-architect-mcp-spec #182, #184, #181; ai-architect-mcp-codebase #484,
#481, #478, #476 on 2026-11-10). A run now counts only if it has the
Claude review budget job.


Claude-Session: https://claude.ai/code/session_018kyZYsWnKSjpX4oNV19PCF

Signed-off-by: Claude <[email protected]>
Co-authored-by: Claude <[email protected]>
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