Skip to content

fix(search): run embedding pass in background so server starts immediately#218

Merged
aliasunder merged 14 commits into
phase-2from
phase-2-background-embedding
Jun 29, 2026
Merged

fix(search): run embedding pass in background so server starts immediately#218
aliasunder merged 14 commits into
phase-2from
phase-2-background-embedding

Conversation

@aliasunder

@aliasunder aliasunder commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • Server blocked for ~5 min on first startup while embedding every note — FTS was ready but the server didn't accept requests until Pass 3 finished
  • rebuildFromVault now returns after FTS indexing (Passes 1+2) and fires embedding (Pass 3) in the background
  • Return type changes from number to { count, embedding } — server ignores the promise, tests can await it

Test plan

  • 1131 tests pass
  • TypeScript compiles clean
  • Embedding test awaits the background promise to verify embeddings complete
  • Deploy and verify server responds to health check within seconds of startup (not minutes)

🤖 Generated with Claude Code

https://claude.ai/code/session_01JPDgHMKBvJ51V2YrrMYZET

Summary by CodeRabbit

  • New Features
    • Added EMBEDDING_ENABLED feature flag (default: true) to disable the embedding pipeline and run search using FTS5 only.
    • Added MEMORY_ENABLED feature flag (default: true) to the local/remote compose setups.
  • Bug Fixes
    • Improved incremental indexing so unchanged content isn’t re-embedded and rebuilds preserve existing vector data.
    • Fixed missed re-embeds when an earlier embedding attempt fails, ensuring subsequent updates still proceed.
  • Documentation / Tests
    • Updated indexing documentation and environment-variable references; expanded search/indexing test coverage.

…ately

rebuildFromVault now returns after FTS indexing (Passes 1+2) and fires
the embedding pass (Pass 3) in the background. The server accepts
requests immediately — search works with FTS-only until vectors are
ready. Previously the server blocked for ~5 minutes on first startup
while embedding every note.

Return type changes from number to { count, embedding } so callers
can optionally await the embedding promise (tests do, server doesn't).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JPDgHMKBvJ51V2YrrMYZET
Comment thread src/vault-mcp/search/search-index.ts
Comment thread src/vault-mcp/search/search-index.ts
Comment thread src/vault-mcp/search/search-index.ts
Comment thread src/vault-mcp/search/search-index.ts
aliasunder and others added 2 commits June 29, 2026 15:29
…bed chain error propagation

EMBEDDING_ENABLED was documented in .env.example files but never passed
through any docker-compose environment section — a user setting it to
false would have it silently ignored. Added to all 5 compose files plus
synced CLI templates.

The file-watcher's promise chain for per-path embed serialization used
.then() directly, which propagates a rejected previous embed and skips
the current one. Added .catch(() => {}) before .then() so a transient
failure can't cascade.

Also added EMBEDDING_ENABLED to the README Configuration table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…fication comments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Refactors rebuildFromVault to return a background embedding promise while preserving vector tables on rebuild, updates watcher/server embedding calls to the new logger-passing API, expands embedding and chunking tests, and adds EMBEDDING_ENABLED documentation and compose defaults.

Changes

Embedding Pipeline Resilience and rebuildFromVault Refactor

Layer / File(s) Summary
rebuildFromVault and embedding flow
src/vault-mcp/search/search-index.ts
rebuildFromVault now accepts { vaultPath } plus logger, returns { count, embedding }, preserves vector tables during rebuild, removes vectors/chunks for deleted notes, and runs per-note embedding in a background promise with isolated failures.
Watcher, embedder, and server wiring
src/vault-mcp/search/file-watcher.ts, src/vault-mcp/search/embedder.ts, src/vault-mcp/server.ts
The watcher no longer blocks later embeds after a rejected prior embed, embedder model-load warnings use describeError, and startup rebuilds pass { vaultPath } with logger.
Embedding flags and documentation
README.md, ARCHITECTURE.md, .devin/wiki.json, server.json, docker-compose.yml, docker-compose.local.yml, deploy/*/docker-compose.yml, cli/templates/*/docker-compose.yml, AGENTS.md
Adds EMBEDDING_ENABLED across docs and compose templates, keeps MEMORY_ENABLED, and updates architecture/configuration text for the embedding-enabled indexing flow.
Comma-separated folder parsing
src/vault-mcp/config.ts
Replaces the reduce-based comma parser with split(","), trim(), and empty-value filtering.
Search tests, chunker tests, and chunker comments
src/vault-mcp/search/__tests__/search-index.test.ts, src/vault-mcp/search/__tests__/embedder.test.ts, src/vault-mcp/search/__tests__/chunker.test.ts, src/vault-mcp/search/chunker.ts
Updates rebuildFromVault test call sites to the new return shape, adds embedding-failure and retry coverage, checks the 500-token chunking threshold, and adds inline chunking comments.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • aliasunder/vault-cortex#89: Shares the same cli/templates/*/docker-compose.yml environment wiring pattern for the service container.
  • aliasunder/vault-cortex#171: Also updates src/vault-mcp/search/embedder.ts-style logging to use centralized error-description helpers.
  • aliasunder/vault-cortex#217: Directly precedes this embedding/indexing refactor in search-index.ts, file-watcher.ts, and embedder.ts.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: moving the embedding pass to the background so startup is no longer blocked.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch phase-2-background-embedding

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

aliasunder and others added 4 commits June 29, 2026 15:36
Stop wiping note_chunks and note_vectors on rebuild. Content-hash
gating in embedAndStoreChunks already skips unchanged chunks, so
only new/modified notes re-embed. Deleted notes are cleaned up by
diffing note_chunks paths against the current vault file list.

Reduces startup embedding time from ~26 min (full re-embed on
Lightsail) to near-zero when vault content hasn't changed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JPDgHMKBvJ51V2YrrMYZET
…dary

- embedder: pipeline load failure + retry (error path, pipelineLoading reset)
- chunker: exact 500-token threshold boundary (confirms < not <=)
- search-index: embedNote error propagation, rebuild resilience on per-note failure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The embedding pipeline paragraph claimed "Full rebuilds re-embed
everything (the rebuild clears vector tables first)" — both halves
are wrong. Vector tables persist across rebuilds (content-hash gating
applies to full rebuilds too, not just incremental updates), and only
new/modified chunks re-embed. Updated to match the actual code path
in rebuildFromVault Pass 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…memory retention

- rebuildFromVault now follows the (params, logger) two-arg convention
- Add MEMORY_ENABLED to all 6 docker-compose files (pre-existing gap)
- Extract notesForEmbedding before background pass so the full
  noteContents array can be garbage-collected during embedding
- Remove unused module-level logger import (all callers pass logger)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JPDgHMKBvJ51V2YrrMYZET
@aliasunder

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 4

🧹 Nitpick comments (1)
src/vault-mcp/search/file-watcher.ts (1)

65-73: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer an async wrapper over the .catch().then() chain.

This preserves the sequencing, but an async wrapper is easier to reason about and matches the repo’s preferred style.

♻️ Proposed refactor
       const previousEmbed = pendingEmbeds.get(relativePath) ?? Promise.resolve()
-      const currentEmbed = previousEmbed
-        .catch(() => {})
-        .then(() =>
-          search.embedNote(
-            { notePath: relativePath, rawContent: content },
-            logger,
-          ),
-        )
+      const currentEmbed = (async (): Promise<void> => {
+        try {
+          await previousEmbed
+        } catch {
+          // Let a later change retry after a transient embed failure.
+        }
+        await search.embedNote(
+          { notePath: relativePath, rawContent: content },
+          logger,
+        )
+      })()

As per coding guidelines, "Prefer async/await over .then()/.catch() for promise handling."

🤖 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/vault-mcp/search/file-watcher.ts` around lines 65 - 73, Refactor the
promise chaining in file-watcher’s embedding flow to use an async wrapper
instead of the current `.catch().then()` sequence. Update the logic around
`pendingEmbeds`, `previousEmbed`, and `currentEmbed` in the file-watcher handler
so it preserves the same sequencing while using `async/await` for readability
and consistency with the repo style.

Source: Coding guidelines

🤖 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 `@ARCHITECTURE.md`:
- Line 266: Update the embedding pipeline documentation to explicitly state that
Pass 3 runs asynchronously in the background after `rebuildFromVault` completes
Passes 1+2. In the `ARCHITECTURE.md` embedding section, clarify that startup
returns before embeddings finish, and that `createEmbedder(logger)` continues
processing chunks and vector updates after the rebuild contract completes.
Reference `rebuildFromVault`, Pass 3, and `createEmbedder(logger)` so the
background behavior is unmistakable.

In `@docker-compose.yml`:
- Around line 61-62: The Compose environment entries for EMBEDDING_ENABLED and
MEMORY_ENABLED are using empty-string expansion, which can override the boolean
defaults expected by config.ts. Update the Docker Compose manifest(s) so these
variables resolve to explicit boolean defaults instead of an empty string,
matching the .default("true").asBool() parsing used in the configuration loader.
Apply the same fix to the other Compose manifests that use this pattern, and
keep the env var names aligned with the config symbols EMBEDDING_ENABLED and
MEMORY_ENABLED.

In `@src/vault-mcp/search/__tests__/chunker.test.ts`:
- Around line 31-39: The threshold test in chunker.test.ts is too loose because
chunkNoteContent is only checked with toBeGreaterThan(1), so it won’t fail if
the splitter behavior regresses. Update the exact-threshold case in the
chunkNoteContent spec to assert the deterministic full chunk output for the
500-token fixture, using the existing generateTokens helper and the
chunkNoteContent function, so the test verifies the precise number and shape of
chunks instead of just “more than one.”

In `@src/vault-mcp/search/search-index.ts`:
- Around line 920-988: Pass 3 is writing stale embedding data after startup and
can overwrite newer watcher-driven state. Update the background embedding flow
in search-index.ts so each note is validated before committing writes, or route
the pass through the same per-path serialization used by live embed/remove
operations. Use the existing symbols notesForEmbedding, embedAndStoreChunks, and
the startup embeddingPromise loop to re-check that the note still exists and its
content matches before storing chunks/vectors, and skip or retry any path that
is no longer fresh.

---

Nitpick comments:
In `@src/vault-mcp/search/file-watcher.ts`:
- Around line 65-73: Refactor the promise chaining in file-watcher’s embedding
flow to use an async wrapper instead of the current `.catch().then()` sequence.
Update the logic around `pendingEmbeds`, `previousEmbed`, and `currentEmbed` in
the file-watcher handler so it preserves the same sequencing while using
`async/await` for readability and consistency with the repo style.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 71942494-f85c-46da-8942-3b662d39082d

📥 Commits

Reviewing files that changed from the base of the PR and between 2fcb2c0 and 8acded8.

📒 Files selected for processing (16)
  • ARCHITECTURE.md
  • README.md
  • cli/templates/local/docker-compose.yml
  • cli/templates/remote/docker-compose.yml
  • deploy/local/docker-compose.yml
  • deploy/remote/docker-compose.yml
  • docker-compose.local.yml
  • docker-compose.yml
  • src/vault-mcp/search/__tests__/chunker.test.ts
  • src/vault-mcp/search/__tests__/embedder.test.ts
  • src/vault-mcp/search/__tests__/search-index.test.ts
  • src/vault-mcp/search/chunker.ts
  • src/vault-mcp/search/embedder.ts
  • src/vault-mcp/search/file-watcher.ts
  • src/vault-mcp/search/search-index.ts
  • src/vault-mcp/server.ts

Comment thread ARCHITECTURE.md
Comment thread docker-compose.yml Outdated
Comment thread src/vault-mcp/search/__tests__/chunker.test.ts
Comment thread src/vault-mcp/search/search-index.ts
aliasunder and others added 7 commits June 29, 2026 16:17
… extraction

- docker-compose: use :-true defaults (empty string crashes env-var)
- file-watcher: log previous embed failure instead of silent catch
- search-index: extract multi-clause if to named hasDeletedNotes boolean
- search-index: add let-justification comment for embedding counters
- ARCHITECTURE.md: document that Pass 3 runs in the background
- AGENTS.md: add conditional-extraction convention

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JPDgHMKBvJ51V2YrrMYZET
…for embedding pipeline

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ssertion

Coverage gap: toFloat32Array error path was untested — pipeline returning
non-Float32Array data now has a regression test (mutation-verified).
Assertion quality: embedBatch slice test collapsed from two index-based
assertions into a single toEqual on the full array.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pass 3 (background embedding) consumes a startup snapshot while the
file watcher runs concurrently. Without a guard, Pass 3 could overwrite
fresh embeddings from the watcher with stale snapshot content, or
recreate vectors for notes deleted after the snapshot. Check the notes
table mtime before each embed — skip when the note was removed or
updated by the watcher since the snapshot was taken.

Also fixes the file-watcher error message from "failed to index file"
to "failed to process file change" — when embedding fails, FTS indexing
has already succeeded, so the old message was misleading.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The pipeline type is a 24-member union that vi.fn() can't satisfy.
Cast through unknown with a comment explaining why — test-only mock,
production code has the runtime toFloat32Array guard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JPDgHMKBvJ51V2YrrMYZET
The as cast on selectNoteMtimeStmt.get() follows the pre-existing
pattern used by all 9 other .get() calls in this file — better-sqlite3
returns unknown and the codebase casts to the expected row shape.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JPDgHMKBvJ51V2YrrMYZET
@aliasunder

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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.

🧹 Nitpick comments (1)
src/vault-mcp/search/__tests__/embedder.test.ts (1)

73-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the double cast from the mocked pipeline.

as unknown as Awaited<ReturnType<typeof mockedPipeline>> sidesteps the strict typing this test file is supposed to preserve and can hide a broken mock shape. Please replace it with a typed test helper/factory instead of forcing the value through unknown. As per coding guidelines, src/**/*.{ts,tsx} should "avoid as casts".

🤖 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/vault-mcp/search/__tests__/embedder.test.ts` around lines 73 - 76, The
mocked pipeline setup in the embedder test is bypassing type safety by using a
double cast through unknown, which should be removed. Update the test around
mockedPipeline and badPipeline to use a properly typed helper/factory that
returns the expected mocked pipeline shape instead of forcing it with as unknown
as Awaited<ReturnType<typeof mockedPipeline>>. Keep the test strictly typed so
the mock matches the real pipeline contract without any casts.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@src/vault-mcp/search/__tests__/embedder.test.ts`:
- Around line 73-76: The mocked pipeline setup in the embedder test is bypassing
type safety by using a double cast through unknown, which should be removed.
Update the test around mockedPipeline and badPipeline to use a properly typed
helper/factory that returns the expected mocked pipeline shape instead of
forcing it with as unknown as Awaited<ReturnType<typeof mockedPipeline>>. Keep
the test strictly typed so the mock matches the real pipeline contract without
any casts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0fad7cf3-dbe8-4906-9bf0-181f19c669ad

📥 Commits

Reviewing files that changed from the base of the PR and between 8acded8 and 9922d0a.

📒 Files selected for processing (14)
  • .devin/wiki.json
  • AGENTS.md
  • ARCHITECTURE.md
  • cli/templates/local/docker-compose.yml
  • cli/templates/remote/docker-compose.yml
  • deploy/local/docker-compose.yml
  • deploy/remote/docker-compose.yml
  • docker-compose.local.yml
  • docker-compose.yml
  • server.json
  • src/vault-mcp/config.ts
  • src/vault-mcp/search/__tests__/embedder.test.ts
  • src/vault-mcp/search/file-watcher.ts
  • src/vault-mcp/search/search-index.ts
✅ Files skipped from review due to trivial changes (5)
  • AGENTS.md
  • src/vault-mcp/config.ts
  • docker-compose.local.yml
  • ARCHITECTURE.md
  • .devin/wiki.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/vault-mcp/search/file-watcher.ts
  • src/vault-mcp/search/search-index.ts

@aliasunder
aliasunder merged commit bfc0b02 into phase-2 Jun 29, 2026
12 checks passed
@aliasunder
aliasunder deleted the phase-2-background-embedding branch June 29, 2026 21:26
aliasunder added a commit that referenced this pull request Jun 29, 2026
…ately (#218)

- **Server blocked for ~5 min on first startup** while embedding every
note — FTS was ready but the server didn't accept requests until Pass 3
finished
- `rebuildFromVault` now returns after FTS indexing (Passes 1+2) and
fires embedding (Pass 3) in the background
- Return type changes from `number` to `{ count, embedding }` — server
ignores the promise, tests can await it

- [x] 1131 tests pass
- [x] TypeScript compiles clean
- [x] Embedding test awaits the background promise to verify embeddings
complete
- [ ] Deploy and verify server responds to health check within seconds
of startup (not minutes)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01JPDgHMKBvJ51V2YrrMYZET

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

* **New Features**
* Added `EMBEDDING_ENABLED` feature flag (default: `true`) to disable
the embedding pipeline and run search using FTS5 only.
* Added `MEMORY_ENABLED` feature flag (default: `true`) to the
local/remote compose setups.
* **Bug Fixes**
* Improved incremental indexing so unchanged content isn’t re-embedded
and rebuilds preserve existing vector data.
* Fixed missed re-embeds when an earlier embedding attempt fails,
ensuring subsequent updates still proceed.
* **Documentation / Tests**
* Updated indexing documentation and environment-variable references;
expanded search/indexing test coverage.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
aliasunder added a commit that referenced this pull request Jun 30, 2026
…ately (#218)

- **Server blocked for ~5 min on first startup** while embedding every
note — FTS was ready but the server didn't accept requests until Pass 3
finished
- `rebuildFromVault` now returns after FTS indexing (Passes 1+2) and
fires embedding (Pass 3) in the background
- Return type changes from `number` to `{ count, embedding }` — server
ignores the promise, tests can await it

- [x] 1131 tests pass
- [x] TypeScript compiles clean
- [x] Embedding test awaits the background promise to verify embeddings
complete
- [ ] Deploy and verify server responds to health check within seconds
of startup (not minutes)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01JPDgHMKBvJ51V2YrrMYZET

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

* **New Features**
* Added `EMBEDDING_ENABLED` feature flag (default: `true`) to disable
the embedding pipeline and run search using FTS5 only.
* Added `MEMORY_ENABLED` feature flag (default: `true`) to the
local/remote compose setups.
* **Bug Fixes**
* Improved incremental indexing so unchanged content isn’t re-embedded
and rebuilds preserve existing vector data.
* Fixed missed re-embeds when an earlier embedding attempt fails,
ensuring subsequent updates still proceed.
* **Documentation / Tests**
* Updated indexing documentation and environment-variable references;
expanded search/indexing test coverage.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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