Skip to content

Replace sqlite-lembed local embedder - #62

Merged
bernoussama merged 3 commits into
masterfrom
codex/replace-sqlite-lembed
Jun 21, 2026
Merged

Replace sqlite-lembed local embedder#62
bernoussama merged 3 commits into
masterfrom
codex/replace-sqlite-lembed

Conversation

@bernoussama

@bernoussama bernoussama commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • replace sqlite-lembed with node-llama-cpp for local GGUF embeddings
  • keep sqlite-vec for local vector storage/search
  • make local embedder calls async and encode vectors as explicit float32 SQLite blobs
  • run local Docker e2e against Node 22 and Node 24

Verification

  • pnpm --filter @clankeroverflow/cli run test
  • pnpm --filter @clankeroverflow/cli run check-types
  • pnpm run test:e2e:local
  • pnpm run format:check
  • pnpm run lint
  • pnpm --filter @clankeroverflow/cli run build

Summary by CodeRabbit

  • New Features

    • E2E local mode tests now run against multiple Node versions (22 and 24) by default.
  • Documentation

    • Updated guidance for Private Local Mode testing workflow.
  • Refactor

    • Embedder implementation updated to use node-llama-cpp. The local status and local doctor output now reflects the updated embedder name visible to users.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@bernoussama, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 48 minutes and 5 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e11d2c03-2d2d-46a9-b433-52d1b1003f94

📥 Commits

Reviewing files that changed from the base of the PR and between 88b907c and e48b723.

📒 Files selected for processing (5)
  • packages/cli/.claude-plugin/plugin.json
  • packages/cli/.codex-plugin/plugin.json
  • packages/cli/openclaw.plugin.json
  • packages/cli/package.json
  • scripts/test-cli-local-e2e.ts

Walkthrough

Replaces the sqlite-lembed optional dependency with node-llama-cpp (v3.18.1) for local semantic embeddings. The Embedder.embed interface becomes async, createSqliteEmbedder is removed in favor of createLocalEmbedder, user-facing labels are renamed, and the e2e Docker infrastructure is extended to build and run against both Node 22 and Node 24.

Changes

sqlite-lembed → node-llama-cpp Embedder Replacement

Layer / File(s) Summary
Dependency swap and core embedder implementation
packages/cli/package.json, pnpm-workspace.yaml, packages/cli/src/mcp/local-semantic.ts
Removes sqlite-lembed and adds node-llama-cpp@3.18.1 as an optional dependency with allowBuilds entry. Updates LOCAL_EMBEDDER_ID, renames sqlite-vec tracking WeakSet, removes createSqliteEmbedder, and exports checkLocalEmbedderAvailable, floatVectorToBuffer, and createLocalEmbedder. Updates getLocalSemanticStatus to use checkLocalEmbedderAvailable.
Backend async Embedder interface and factory wiring
packages/cli/src/mcp/local-backend.ts
Replaces createSqliteEmbedder import with createLocalEmbedder, changes Embedder.embed return type to Promise<Buffer>, adds await to both embed call sites in searchSemantic and embedSolution, and wires resolveEmbedder to createLocalEmbedder.
User-facing label and MCP status text updates
packages/cli/src/index.ts, packages/cli/src/mcp/server.ts
Renames sqlite-lembed to node-llama-cpp in formatLocalStatus, formatDoctor checks, and the clanker_status MCP tool available/error output.
Unit tests for async embedder and floatVectorToBuffer
packages/cli/src/mcp/local-backend.test.ts, packages/cli/src/package.test.ts
Updates vector() helper to use floatVectorToBuffer, makes all mock embed methods async, adds tests for LOCAL_EMBEDDER_ID, blob correctness, and dimension mismatch error. Updates package metadata assertions to expect node-llama-cpp and absent sqlite-lembed.
Multi-Node Docker e2e infrastructure
packages/cli/e2e/Dockerfile.local-mode, scripts/test-cli-local-e2e.ts
Parameterizes Dockerfile.local-mode with ARG NODE_IMAGE. Rewrites the e2e script to derive a NODE_IMAGES list (Node 22 and 24 by default), introduces imageName(), creates a shared MODEL_VOLUME, and iterates over images building and running each sequentially.
E2e test flow and README
packages/cli/e2e/local-mode.mjs, README.md
Adds node-llama-cpp to the native dependency import check, inserts a pre-embedding phase with local status --json assertions, verifies keyword search before embedding, runs local embed with output assertions, and updates README to state the check runs Node 22 and Node 24 by default.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hop hop, the old sqlite chains are gone,
A llamacpp engine now carries on!
Float vectors buffered with precision bright,
Node 22 and 24 both run right.
The rabbit cheers — embeddings take flight! 🦙✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 title directly describes the main change: replacing sqlite-lembed with node-llama-cpp as the local embedder library.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/replace-sqlite-lembed

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 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: 2

🧹 Nitpick comments (1)
packages/cli/e2e/Dockerfile.local-mode (1)

1-2: ⚡ Quick win

Drop root privileges for the runtime container.

Line 2 inherits the default root user. Add a non-root USER before CMD to reduce blast radius if dependency scripts or test payloads are compromised.

Suggested patch
 RUN pnpm --filter `@clankeroverflow/cli` run build
 
+RUN chown -R node:node /workspace
+USER node
+
 CMD ["node", "packages/cli/e2e/local-mode.mjs"]
🤖 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 `@packages/cli/e2e/Dockerfile.local-mode` around lines 1 - 2, The Dockerfile
currently runs as the default root user, which poses a security risk. Add a USER
directive before the CMD instruction in the Dockerfile to specify a non-root
user for the runtime container. You can either create a new unprivileged user
(with appropriate RUN mkdir and useradd commands) or use an existing non-root
user that may be available in the base node image. This will ensure the
container runs with reduced privileges and limits the blast radius if the
runtime environment is compromised.

Source: Linters/SAST tools

🤖 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 `@packages/cli/src/mcp/local-backend.ts`:
- Around line 260-264: The resolveEmbedder() method has a race condition where
concurrent calls can both pass the initial check and trigger
createLocalEmbedder() separately, causing duplicate model loads. Fix this by
assigning the promise from createLocalEmbedder() to this.embedder immediately
before awaiting it, rather than assigning only after the await completes. This
way, subsequent concurrent calls will detect the in-progress promise and await
it instead of starting new embedder creation.

In `@scripts/test-cli-local-e2e.ts`:
- Around line 6-8: Add a guard check after the NODE_IMAGES initialization (after
the filter operation on lines 6-8) to ensure the array is not empty. If
NODE_IMAGES resolves to an empty array, the script should throw an error or exit
with a non-zero code to prevent false-green test runs. This will catch cases
where the CLANKER_LOCAL_E2E_NODE_IMAGES environment variable is blank, contains
only commas, or is otherwise invalid.

---

Nitpick comments:
In `@packages/cli/e2e/Dockerfile.local-mode`:
- Around line 1-2: The Dockerfile currently runs as the default root user, which
poses a security risk. Add a USER directive before the CMD instruction in the
Dockerfile to specify a non-root user for the runtime container. You can either
create a new unprivileged user (with appropriate RUN mkdir and useradd commands)
or use an existing non-root user that may be available in the base node image.
This will ensure the container runs with reduced privileges and limits the blast
radius if the runtime environment is compromised.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a75db137-31ef-4c30-9c95-7172f07653e1

📥 Commits

Reviewing files that changed from the base of the PR and between 3e789bd and 88b907c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • README.md
  • packages/cli/e2e/Dockerfile.local-mode
  • packages/cli/e2e/local-mode.mjs
  • packages/cli/package.json
  • packages/cli/src/index.ts
  • packages/cli/src/mcp/local-backend.test.ts
  • packages/cli/src/mcp/local-backend.ts
  • packages/cli/src/mcp/local-semantic.ts
  • packages/cli/src/mcp/server.ts
  • packages/cli/src/package.test.ts
  • pnpm-workspace.yaml
  • scripts/test-cli-local-e2e.ts

Comment on lines 260 to 264
private async resolveEmbedder() {
if (this.embedder) return this.embedder;
if (!this.semantic?.enabled) throw new LocalSemanticSearchNotConfiguredError();
this.embedder = await createSqliteEmbedder(this.db, this.semantic);
this.embedder = await createLocalEmbedder(this.semantic);
return this.embedder;

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 | 🟠 Major | ⚡ Quick win

Serialize embedder initialization to prevent duplicate GGUF model loads.

Line 263 assigns this.embedder only after await createLocalEmbedder(...) resolves. If two requests hit resolveEmbedder() concurrently on cold start, both can create separate embedder/model contexts. That can cause avoidable memory pressure and unstable startup under concurrent MCP traffic.

💡 Suggested fix
 export class LocalBackend implements SolutionBackend {
   private db: LocalDb;
   private semantic?: LocalSemanticConfig;
   private embedder?: Embedder;
+  private embedderPromise?: Promise<Embedder>;
@@
   private async resolveEmbedder() {
     if (this.embedder) return this.embedder;
     if (!this.semantic?.enabled) throw new LocalSemanticSearchNotConfiguredError();
-    this.embedder = await createLocalEmbedder(this.semantic);
-    return this.embedder;
+    if (!this.embedderPromise) {
+      this.embedderPromise = createLocalEmbedder(this.semantic)
+        .then((embedder) => {
+          this.embedder = embedder;
+          return embedder;
+        })
+        .catch((error) => {
+          this.embedderPromise = undefined;
+          throw error;
+        });
+    }
+    return this.embedderPromise;
   }
📝 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
private async resolveEmbedder() {
if (this.embedder) return this.embedder;
if (!this.semantic?.enabled) throw new LocalSemanticSearchNotConfiguredError();
this.embedder = await createSqliteEmbedder(this.db, this.semantic);
this.embedder = await createLocalEmbedder(this.semantic);
return this.embedder;
private async resolveEmbedder() {
if (this.embedder) return this.embedder;
if (!this.semantic?.enabled) throw new LocalSemanticSearchNotConfiguredError();
if (!this.embedderPromise) {
this.embedderPromise = createLocalEmbedder(this.semantic)
.then((embedder) => {
this.embedder = embedder;
return embedder;
})
.catch((error) => {
this.embedderPromise = undefined;
throw error;
});
}
return this.embedderPromise;
}
🤖 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 `@packages/cli/src/mcp/local-backend.ts` around lines 260 - 264, The
resolveEmbedder() method has a race condition where concurrent calls can both
pass the initial check and trigger createLocalEmbedder() separately, causing
duplicate model loads. Fix this by assigning the promise from
createLocalEmbedder() to this.embedder immediately before awaiting it, rather
than assigning only after the await completes. This way, subsequent concurrent
calls will detect the in-progress promise and await it instead of starting new
embedder creation.

Comment thread scripts/test-cli-local-e2e.ts
@bernoussama
bernoussama merged commit b9f0581 into master Jun 21, 2026
2 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jun 21, 2026
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