Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,10 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/lobster/**"
"extensions: llama-cpp":
- changed-files:
- any-glob-to-any-file:
- "extensions/llama-cpp/**"
"extensions: memory-core":
- changed-files:
- any-glob-to-any-file:
Expand Down
10 changes: 10 additions & 0 deletions config/knip.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,16 @@ const config = {
entry: ["index.js!", "scripts/postinstall.js!"],
project: ["index.js!", "scripts/**/*.js!"],
},
[`${BUNDLED_PLUGIN_ROOT_DIR}/llama-cpp`]: {
entry: bundledPluginEntries,
project: ["index.ts!", "src/**/*.{js,mjs,ts}!"],
ignoreDependencies: [
// The provider resolves node-llama-cpp from its own package at runtime
// so local embeddings use the plugin-owned native dependency.
"node-llama-cpp",
...bundledPluginIgnoredRuntimeDependencies,
],
},
[`${BUNDLED_PLUGIN_ROOT_DIR}/*`]: {
// Bundled plugins often load their public surface via string specifiers in
// `index.ts` contracts, so Knip needs these convention-based entry files.
Expand Down
11 changes: 7 additions & 4 deletions docs/concepts/memory-builtin.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,12 @@ To set a provider explicitly:

Without an embedding provider, only keyword search is available.

To force the built-in local embedding provider, install the optional
`node-llama-cpp` runtime package next to OpenClaw, then point `local.modelPath`
at a GGUF file:
To force local GGUF embeddings, install the official llama.cpp provider plugin,
then point `local.modelPath` at a GGUF file:

```bash
openclaw plugins install @openclaw/llama-cpp-provider
```

```json5
{
Expand All @@ -67,7 +70,7 @@ at a GGUF file:
| DeepInfra | `deepinfra` | Default: `BAAI/bge-m3` |
| Gemini | `gemini` | Supports multimodal (image + audio) |
| GitHub Copilot | `github-copilot` | Uses Copilot subscription |
| Local | `local` | Optional `node-llama-cpp` runtime |
| Local | `local` | `@openclaw/llama-cpp-provider` |
| Mistral | `mistral` | |
| Ollama | `ollama` | Local/self-hosted |
| OpenAI | `openai` | Default: `text-embedding-3-small` |
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/memory-qmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ binary, and can index content beyond your workspace memory files.
- **Reranking and query expansion** for better recall.
- **Index extra directories** -- project docs, team notes, anything on disk.
- **Index session transcripts** -- recall earlier conversations.
- **Fully local** -- runs with the optional node-llama-cpp runtime package and
- **Fully local** -- runs with the official llama.cpp provider plugin and
auto-downloads GGUF models.
- **Automatic fallback** -- if QMD is unavailable, OpenClaw falls back to the
builtin engine seamlessly.
Expand Down
3 changes: 2 additions & 1 deletion docs/concepts/memory-search.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ For multi-endpoint setups with memory-specific providers, `provider` can also
be a custom `models.providers.<id>` entry, such as `ollama-5080`, when that
provider sets `api: "ollama"` or another memory embedding adapter owner.

For local embeddings with no API key, set `provider: "local"`. Source checkouts
For local embeddings with no API key, install
`@openclaw/llama-cpp-provider` and set `provider: "local"`. Source checkouts
may still require native build approval: `pnpm approve-builds` then
`pnpm rebuild node-llama-cpp`.

Expand Down
1 change: 1 addition & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -1241,6 +1241,7 @@
"plugins/admin-http-rpc",
"plugins/voice-call",
"plugins/memory-wiki",
"plugins/llama-cpp",
"plugins/memory-lancedb",
"plugins/oc-path",
"plugins/zalouser"
Expand Down
58 changes: 58 additions & 0 deletions docs/plugins/llama-cpp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
summary: "Install the official llama.cpp provider for local GGUF memory embeddings"
read_when:
- You want memory search embeddings from a local GGUF model
- You are configuring memorySearch.provider = "local"
- You need the OpenClaw plugin that owns the node-llama-cpp runtime
title: "llama.cpp Provider"
sidebarTitle: "llama.cpp Provider"
---

`llama-cpp` is the official external provider plugin for local GGUF embeddings.
It owns the `node-llama-cpp` runtime dependency used by
`memorySearch.provider: "local"`.

Install it before using local memory embeddings:

```bash
openclaw plugins install @openclaw/llama-cpp-provider
```

The main `openclaw` npm package does not include `node-llama-cpp`. Keeping the
native dependency in this plugin prevents normal OpenClaw npm updates from
deleting a manually installed runtime inside the OpenClaw package directory.

## Configuration

Set the memory search provider to `local`:

```json5
{
agents: {
defaults: {
memorySearch: {
provider: "local",
local: {
modelPath: "hf:ggml-org/embeddinggemma-300m-qat-q8_0-GGUF/embeddinggemma-300m-qat-Q8_0.gguf",
},
},
},
},
}
```

The default model is `embeddinggemma-300m-qat-Q8_0.gguf`. You can also point
`local.modelPath` at a local `.gguf` file.

## Native Runtime

Use Node 24 for the smoothest native install path. Source checkouts using pnpm
may need to approve and rebuild the native dependency:

```bash
pnpm approve-builds
pnpm rebuild node-llama-cpp
```

For lower-friction local embeddings, use a local service provider such as
Ollama or LM Studio instead.
6 changes: 4 additions & 2 deletions docs/plugins/plugin-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Each entry lists the package, distribution route, and description.

- **[mattermost](/plugins/reference/mattermost)** (`@openclaw/mattermost`) - included in OpenClaw. Adds the Mattermost channel surface for sending and receiving OpenClaw messages.

- **[memory-core](/plugins/reference/memory-core)** (`@openclaw/memory-core`) - included in OpenClaw. Adds memory embedding provider support. Adds agent-callable tools.
- **[memory-core](/plugins/reference/memory-core)** (`@openclaw/memory-core`) - included in OpenClaw. Adds file-backed memory search tools.

- **[memory-wiki](/plugins/reference/memory-wiki)** (`@openclaw/memory-wiki`) - included in OpenClaw. Persistent wiki compiler and Obsidian-friendly knowledge vault for OpenClaw.

Expand Down Expand Up @@ -235,7 +235,7 @@ Each entry lists the package, distribution route, and description.

## Official external packages

34 plugins
35 plugins

- **[acpx](/plugins/reference/acpx)** (`@openclaw/acpx`) - npm; ClawHub. OpenClaw ACP runtime backend with plugin-owned session and transport management.

Expand Down Expand Up @@ -267,6 +267,8 @@ Each entry lists the package, distribution route, and description.

- **[googlechat](/plugins/reference/googlechat)** (`@openclaw/googlechat`) - npm; ClawHub. OpenClaw Google Chat channel plugin for spaces and direct messages.

- **[llama-cpp](/plugins/reference/llama-cpp)** (`@openclaw/llama-cpp-provider`) - npm; ClawHub. OpenClaw llama.cpp embedding provider plugin.

- **[line](/plugins/reference/line)** (`@openclaw/line`) - npm; ClawHub. OpenClaw LINE channel plugin for LINE Bot API chats.

- **[lobster](/plugins/reference/lobster)** (`@openclaw/lobster`) - npm; ClawHub. Lobster workflow tool plugin for typed pipelines and resumable approvals.
Expand Down
2 changes: 1 addition & 1 deletion docs/plugins/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ This page is generated from `extensions/*/package.json` and
pnpm plugins:inventory:gen
```

Use [Plugin inventory](/plugins/plugin-inventory) to browse all 126
Use [Plugin inventory](/plugins/plugin-inventory) to browse all 127
generated plugin reference pages by distribution, package, and description.
23 changes: 23 additions & 0 deletions docs/plugins/reference/llama-cpp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
summary: "OpenClaw llama.cpp embedding provider plugin."
read_when:
- You are installing, configuring, or auditing the llama-cpp plugin
title: "llama-cpp plugin"
---

# llama-cpp plugin

OpenClaw llama.cpp embedding provider plugin.

## Distribution

- Package: `@openclaw/llama-cpp-provider`
- Install route: npm; ClawHub

## Surface

contracts: embeddingProviders

## Related docs

- [llama.cpp Provider](/plugins/llama-cpp)
6 changes: 3 additions & 3 deletions docs/plugins/reference/memory-core.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
summary: "Adds memory embedding provider support. Adds agent-callable tools."
summary: "Adds file-backed memory search tools."
read_when:
- You are installing, configuring, or auditing the memory-core plugin
title: "Memory Core plugin"
---

# Memory Core plugin

Adds memory embedding provider support. Adds agent-callable tools.
Adds file-backed memory search tools.

## Distribution

Expand All @@ -16,4 +16,4 @@ Adds memory embedding provider support. Adds agent-callable tools.

## Surface

contracts: memoryEmbeddingProviders, tools
contracts: tools
3 changes: 2 additions & 1 deletion docs/reference/memory-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,13 +274,14 @@ Use `provider: "openai-compatible"` for a generic OpenAI-compatible
```

</Accordion>
<Accordion title="Local (GGUF + node-llama-cpp)">
<Accordion title="Local (GGUF + llama.cpp)">
| Key | Type | Default | Description |
| --------------------- | ------------------ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `local.modelPath` | `string` | auto-downloaded | Path to GGUF model file |
| `local.modelCacheDir` | `string` | node-llama-cpp default | Cache dir for downloaded models |
| `local.contextSize` | `number \| "auto"` | `4096` | Context window size for the embedding context. 4096 covers typical chunks (128–512 tokens) while bounding non-weight VRAM. Lower to 1024–2048 on constrained hosts. `"auto"` uses the model's trained maximum — not recommended for 8B+ models (Qwen3-Embedding-8B: 40 960 tokens → ~32 GB VRAM vs ~8.8 GB at 4096). |

Install the official llama.cpp provider first: `openclaw plugins install @openclaw/llama-cpp-provider`.
Default model: `embeddinggemma-300m-qat-Q8_0.gguf` (~0.6 GB, auto-downloaded). Source checkouts still require native build approval: `pnpm approve-builds` then `pnpm rebuild node-llama-cpp`.

Use the standalone CLI to verify the same provider path the Gateway uses:
Expand Down
118 changes: 118 additions & 0 deletions extensions/llama-cpp/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {
createPluginRegistryFixture,
registerVirtualTestPlugin,
} from "openclaw/plugin-sdk/plugin-test-contracts";
import {
clearEmbeddingProviders,
clearMemoryEmbeddingProviders,
getRegisteredEmbeddingProvider,
} from "openclaw/plugin-sdk/plugin-test-runtime";
import { afterEach, describe, expect, it, vi } from "vitest";

const memoryHostEmbeddingMocks = vi.hoisted(() => ({
createLocalEmbeddingProvider: vi.fn(),
}));

vi.mock("openclaw/plugin-sdk/memory-core-host-engine-embeddings", () => ({
createLocalEmbeddingProvider: memoryHostEmbeddingMocks.createLocalEmbeddingProvider,
}));

import llamaCppPlugin from "./index.js";
import {
DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
createLlamaCppEmbeddingProvider,
formatLlamaCppSetupError,
} from "./src/embedding-provider.js";

afterEach(() => {
clearEmbeddingProviders();
clearMemoryEmbeddingProviders();
memoryHostEmbeddingMocks.createLocalEmbeddingProvider.mockReset();
});

describe("llama.cpp provider plugin", () => {
it("registers the local embedding provider through the generic SDK contract", () => {
const { config, registry } = createPluginRegistryFixture();

registerVirtualTestPlugin({
registry,
config,
id: "llama-cpp",
name: "llama.cpp Provider",
contracts: {
embeddingProviders: ["local"],
},
register: llamaCppPlugin.register,
});

const provider = getRegisteredEmbeddingProvider("local");
expect(provider?.ownerPluginId).toBe("llama-cpp");
expect(provider?.adapter).toMatchObject({
id: "local",
defaultModel: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
transport: "local",
});
});

it("adapts the worker-backed local embedding provider", async () => {
const close = vi.fn();
memoryHostEmbeddingMocks.createLocalEmbeddingProvider.mockResolvedValue({
id: "local",
model: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
maxInputTokens: 2048,
embedQuery: vi.fn(async () => [0.6, 0.8]),
embedBatchInputs: vi.fn(async () => [[0.3, 0.4]]),
embedBatch: vi.fn(async () => [[1, 0]]),
close,
});
const abortController = new AbortController();

const provider = await createLlamaCppEmbeddingProvider(
{
config: {},
provider: "local",
model: "text-embedding-3-small",
},
{ nodeLlamaCppImportUrl: "file:///plugin/node-llama-cpp.js" },
);

await expect(provider.embed("hello")).resolves.toEqual([0.6, 0.8]);
await expect(
provider.embedBatch([{ text: "doc" }], { signal: abortController.signal }),
).resolves.toEqual([[0.3, 0.4]]);
await provider.close?.();

expect(provider.model).toBe(DEFAULT_LLAMA_CPP_EMBEDDING_MODEL);
expect(provider.maxInputTokens).toBe(2048);
expect(close).toHaveBeenCalledTimes(1);
expect(memoryHostEmbeddingMocks.createLocalEmbeddingProvider).toHaveBeenCalledWith(
{
config: {},
provider: "local",
fallback: "none",
model: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
local: {
modelPath: DEFAULT_LLAMA_CPP_EMBEDDING_MODEL,
},
},
{
nodeLlamaCppImportUrl: "file:///plugin/node-llama-cpp.js",
},
);
const workerProvider =
await memoryHostEmbeddingMocks.createLocalEmbeddingProvider.mock.results[0].value;
expect(workerProvider.embedBatchInputs).toHaveBeenCalledWith([{ text: "doc" }], {
signal: abortController.signal,
});
});

it("formats missing runtime errors with the plugin install command", () => {
const err = Object.assign(new Error("Cannot find package 'node-llama-cpp'"), {
code: "ERR_MODULE_NOT_FOUND",
});

expect(formatLlamaCppSetupError(err)).toContain(
"openclaw plugins install @openclaw/llama-cpp-provider",
);
});
});
11 changes: 11 additions & 0 deletions extensions/llama-cpp/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { llamaCppEmbeddingProviderAdapter } from "./src/embedding-provider.js";

export default definePluginEntry({
id: "llama-cpp",
name: "llama.cpp Provider",
description: "Local GGUF embeddings through node-llama-cpp",
register(api) {
api.registerEmbeddingProvider(llamaCppEmbeddingProviderAdapter);
},
});
Loading
Loading