Skip to content

Fix: vouch index to repopulate embedding_index for semantic search - #120

Closed
james3773 wants to merge 1 commit into
vouchdev:mainfrom
james3773:fix/rebuild-index-populates-embedding-index
Closed

Fix: vouch index to repopulate embedding_index for semantic search#120
james3773 wants to merge 1 commit into
vouchdev:mainfrom
james3773:fix/rebuild-index-populates-embedding-index

Conversation

@james3773

@james3773 james3773 commented May 27, 2026

Copy link
Copy Markdown

Related issues

Closes #119

What changed

health._rebuild_embeddings() now delegates to backfill_embeddings(store, force=True) so vouch index republishes vectors into embedding_index with model metadata (same path as _embed_and_store), instead of the unused legacy embeddings table via index_embedding(). index_db.reset() also clears the legacy embeddings table. Added regression test test_rebuild_index_restores_semantic_embeddings.

Why

rebuild_index() calls reset(), which wipes embedding_index — the table search_semantic and embedding-backed kb.search read. The old _rebuild_embeddings() repopulated only the legacy embeddings table, so semantic search often returned nothing after vouch index and silently fell back to FTS5/substring. Anyone recovering state.db or running bulk reindex lost embedding retrieval without an obvious error.

What might break

  • File moves: none.
  • On-disk field shape changes: none (YAML artifacts unchanged).
  • Behaviour changes: yes, intentionally.
    • After vouch index with embeddings installed, semantic / embedding search should return hits again instead of empty results.
    • reset() now also deletes rows from the legacy embeddings table (orphaned vectors that search never used).
    • Reindex backfill covers the same artifact kinds as backfill_embeddings (claims, pages, sources, entities, relations, evidence), not only claims/pages/entities as before.

Not a breaking change for typical workflows. Existing .vouch/ directories need no migration; run vouch index once to rebuild correctly.

VEP

Not required — internal index rebuild fix; no new CLI/MCP/JSONL surface or schema contract.

Tests

  • make check passes locally (lint + mypy + pytest)
  • New / changed behaviour has a test (test_rebuild_index_restores_semantic_embeddings in tests/embeddings/test_migration.py)
  • CHANGELOG.md updated under ## [Unreleased]

Summary by CodeRabbit

  • Bug Fixes

    • Embeddings are now properly cleared when resetting the index, preventing stale cached vectors from persisting.
    • Session summaries are now correctly updated and retried, improving robustness when approval failures occur.
  • Tests

    • Added regression test for semantic embedding rebuild functionality.
    • Added test for session summary retry and update behavior.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR addresses two independent improvements: fixing embedding index reset/rebuild to correctly populate semantic search (issue #119), and making session crystallization idempotent to handle approval retries with persistent summary pages.

Changes

Embedding Index Repair (Issue #119)

Layer / File(s) Summary
Index reset and embedding backfill
src/vouch/index_db.py, src/vouch/health.py, tests/embeddings/test_migration.py
reset() now deletes stale embeddings table rows; _rebuild_embeddings() refactored to use backfill_embeddings() (standard artifact write path via put_embedding + set_embedding_meta) instead of direct encoding, with early-return guards for missing migration/embedder support; regression test confirms search_semantic returns hits immediately after rebuild.

Session Crystallization Idempotency

Layer / File(s) Summary
Page update storage method
src/vouch/storage.py
KBStore.update_page() added to overwrite existing page files with YAML frontmatter + body, trigger embedding/storage hooks, and raise ArtifactNotFoundError if page missing.
Session crystallization with idempotent summary page
src/vouch/sessions.py, tests/test_sessions.py
Imports updated to include ProposalKind; new helper _approved_artifact_ids_for_session() scans stored APPROVED proposals by session and kind (CLAIM/PAGE/ENTITY/RELATION) to extract payload ids; crystallize() now derives summary-page content from that stored list (not per-call approvals), conditionally writes summary page only when derived list non-empty, and updates existing pages idempotently; test validates partial approval retry produces consistent summary page with full artifact set.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant rebuild_index
  participant reset
  participant backfill_embeddings
  participant embedding_index
  Client->>rebuild_index: vouch index (rebuild_index)
  rebuild_index->>reset: reset(kb_dir)
  reset->>embedding_index: DELETE stale embeddings
  rebuild_index->>backfill_embeddings: backfill_embeddings(store, force=True)
  backfill_embeddings->>embedding_index: put_embedding + set_embedding_meta
  embedding_index-->>Client: semantic search now works
Loading
sequenceDiagram
  participant Client
  participant crystallize
  participant _approved_artifact_ids_for_session
  participant proposals
  participant update_page
  participant summary_page
  Client->>crystallize: crystallize(session_id)
  crystallize->>_approved_artifact_ids_for_session: scan session proposals
  _approved_artifact_ids_for_session->>proposals: filter by APPROVED status + kind
  proposals-->>_approved_artifact_ids_for_session: return approved ids
  crystallize->>crystallize: attempt approve() for pending
  crystallize->>update_page: update_page(summary) if ids non-empty
  update_page->>summary_page: overwrite with YAML + claims
  summary_page-->>Client: idempotent page id returned
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • vouchdev/vouch#36: Foundational embedding backend changes (new embeddings table, embedding indexing/search infrastructure) that the PR's _rebuild_embeddings refactor and backfill_embeddings migration depend on.
  • vouchdev/vouch#40: Semantic/embedding search implementation (search_semantic / embedding vector queries) whose correctness directly depends on the PR's embedding reset/rebuild fix to populate embedding_index correctly.

Poem

🐰 Embeddings rebound with a fresh new start,
No stale vectors lurking, playing their part;
Sessions persist through their crystalline dance,
Idempotent pages get their second chance!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and concisely describes the main change: fixing vouch index to properly repopulate embedding_index for semantic search functionality.
Linked Issues check ✅ Passed The PR comprehensively addresses all coding requirements from issue #119: (1) aligns _rebuild_embeddings() with backfill_embeddings() using put_embedding + metadata, (2) clears legacy embeddings table in reset(), and (3) includes regression test test_rebuild_index_restores_semantic_embeddings.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the embedding repopulation issue: health.py refactors embedding logic, index_db.py cleans legacy table, storage.py adds required update_page method, sessions.py updates summary page logic, and tests validate the fix.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/vouch/sessions.py`:
- Around line 110-116: Replace the get-then-put existence check with an
update-first flow: call store.update_page(page) and if it raises
ArtifactNotFoundError or a deserialization ValueError (from corrupted
frontmatter), fall back to store.put_page(page) so a malformed or missing page
is overwritten idempotently; remove the store.get_page(page.id) call and handle
both ArtifactNotFoundError and ValueError around store.update_page(page)
(referencing store.update_page, store.put_page, ArtifactNotFoundError, and
page.id).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 42476b52-d522-4115-97c2-59786a95ae48

📥 Commits

Reviewing files that changed from the base of the PR and between 3beb821 and 7b51afd.

📒 Files selected for processing (6)
  • src/vouch/health.py
  • src/vouch/index_db.py
  • src/vouch/sessions.py
  • src/vouch/storage.py
  • tests/embeddings/test_migration.py
  • tests/test_sessions.py

Comment thread src/vouch/sessions.py
Comment on lines +110 to 116
try:
store.get_page(page.id)
except ArtifactNotFoundError:
store.put_page(page)
else:
store.update_page(page)
with index_db.open_db(store.kb_dir) as conn:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use update-first flow to keep crystallize recoverable on malformed existing summary pages.

Line 111 calls store.get_page(page.id) as an existence check; that deserializes frontmatter and can raise ValueError for a corrupted file, aborting crystallize instead of overwriting it idempotently.

Suggested change
-        try:
-            store.get_page(page.id)
-        except ArtifactNotFoundError:
-            store.put_page(page)
-        else:
-            store.update_page(page)
+        try:
+            store.update_page(page)
+        except ArtifactNotFoundError:
+            store.put_page(page)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
store.get_page(page.id)
except ArtifactNotFoundError:
store.put_page(page)
else:
store.update_page(page)
with index_db.open_db(store.kb_dir) as conn:
try:
store.update_page(page)
except ArtifactNotFoundError:
store.put_page(page)
with index_db.open_db(store.kb_dir) as conn:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/sessions.py` around lines 110 - 116, Replace the get-then-put
existence check with an update-first flow: call store.update_page(page) and if
it raises ArtifactNotFoundError or a deserialization ValueError (from corrupted
frontmatter), fall back to store.put_page(page) so a malformed or missing page
is overwritten idempotently; remove the store.get_page(page.id) call and handle
both ArtifactNotFoundError and ValueError around store.update_page(page)
(referencing store.update_page, store.put_page, ArtifactNotFoundError, and
page.id).

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.

2 participants