On this page

On this page

Plugin guides

Memory LanceDB

memory-lancedb is an official external plugin that stores long-term memory in LanceDB with vector search. It can auto-recall relevant memories before a model turn and auto-capture important facts after a response.

Use it for a local vector database, an OpenAI-compatible embedding endpoint, or a memory store outside the default built-in memory backend.

Installation

bash
openclaw plugins install @openclaw/memory-lancedb

The plugin is published to npm; it is not bundled into the OpenClaw runtime image. Installing it writes the plugin entry, enables it, and switches plugins.slots.memory to memory-lancedb. If another plugin currently owns the memory slot, that plugin is disabled with a warning.

Quick start

json5
{  plugins: {    slots: {      memory: "memory-lancedb",    },    entries: {      "memory-lancedb": {        enabled: true,        config: {          embedding: {            provider: "openai",            model: "text-embedding-3-small",          },          autoRecall: true,          autoCapture: false,        },      },    },  },}

Installation applies to a running Gateway automatically, and configuration changes apply with the default hybrid reload mode. If the Gateway is offline, start it after configuration. Check the application result and inspect the plugin's runtime registration:

bash
openclaw plugins inspect memory-lancedb --runtime --json

See Apply changes and inspect and Hot reload.

Embedding config

embedding is required and must include at least one field. provider defaults to openai; model defaults to text-embedding-3-small.

Field Type Notes
embedding.provider string Adapter id, e.g. openai, github-copilot, ollama. Default openai.
embedding.model string Default text-embedding-3-small.
embedding.apiKey string Optional; supports ${ENV_VAR} expansion and live credential rotation.
embedding.baseUrl string Optional; supports ${ENV_VAR} expansion and live endpoint rotation.
embedding.dimensions integer (>=1) Required for models not in the built-in table (see below).

Two request paths exist:

  • Provider adapter path (default): set embedding.provider and omit embedding.apiKey/embedding.baseUrl. The plugin resolves the provider's configured auth profile, environment variable, or models.providers.<provider>.apiKey through the same memory embedding adapters memory-core uses. This is the path for github-copilot, ollama, and any other bundled provider with embedding support.
  • Direct OpenAI-compatible client path: leave embedding.provider unset (or "openai") and set embedding.apiKey plus embedding.baseUrl. Use this for a raw OpenAI-compatible embeddings endpoint that has no bundled provider adapter.

embedding.apiKey and embedding.baseUrl are re-read from live plugin config for the next memory operation, as long as provider, model, and dimensions remain unchanged.

The OpenAI provider adapter can use a stored Codex OAuth profile when the account grants embedding access. The separate Sign in with ChatGPT token-sharing grant does not authorize embeddings. API-key auth remains available through a profile, OPENAI_API_KEY, or models.providers.openai.apiKey. Other embedding-capable providers include github-copilot and ollama.

json5
{  plugins: {    entries: {      "memory-lancedb": {        enabled: true,        config: {          embedding: {            provider: "github-copilot",            model: "text-embedding-3-small",          },        },      },    },  },}

Some OpenAI-compatible embedding endpoints reject the encoding_format parameter; others ignore it and always return number[]. memory-lancedb omits encoding_format on requests and accepts either float-array or base64-encoded float32 responses, so both response shapes work without config.

Dimensions

OpenClaw has a built-in dimension for text-embedding-3-small (1536) and text-embedding-3-large (3072) only. Any other model needs an explicit embedding.dimensions so LanceDB can create the vector column, for example ZhiPu embedding-3 at 2048 dimensions:

json5
{  plugins: {    entries: {      "memory-lancedb": {        enabled: true,        config: {          embedding: {            apiKey: "${ZHIPU_API_KEY}",            baseUrl: "https://open.bigmodel.cn/api/paas/v4",            model: "embedding-3",            dimensions: 2048,          },        },      },    },  },}

Ollama embeddings

Use the bundled Ollama provider adapter path (embedding.provider: "ollama"). It calls Ollama's native /api/embed endpoint and follows the same auth/base URL rules as the Ollama provider.

json5
{  plugins: {    slots: {      memory: "memory-lancedb",    },    entries: {      "memory-lancedb": {        enabled: true,        config: {          embedding: {            provider: "ollama",            baseUrl: "http://127.0.0.1:11434",            model: "mxbai-embed-large",            dimensions: 1024,          },          recallMaxChars: 400,          autoRecall: true,          autoCapture: false,        },      },    },  },}

mxbai-embed-large is not in the built-in dimension table, so dimensions is required. For small local embedding models, lower recallMaxChars if the local server returns context-length errors.

Recall and capture limits

Setting Default Range Applies to
recallMaxChars 1000 100-10000 Recall query length and each escaped model-visible recalled item.
captureMaxChars 500 100-10000 memory_store input limit and auto-capture eligibility.
customTriggers [] 0-50 items, each ≤100 chars Literal phrases that make auto-capture consider a message.

recallMaxChars bounds the before_prompt_build auto-recall query, the memory_recall tool, the memory_forget query path, and openclaw ltm search. Auto-recall embeds the current turn's prompt after removing media attachment notes and normalizing whitespace. The same limit bounds each recalled item after prompt escaping before that text reaches the model.

Automatic recall and memory_recall each have a 15-second budget shared by embedding-provider initialization, the embedding request, and database search. On the provider-adapter path, cold initialization does not give the embedding request a fresh budget: if initialization finishes after the deadline, that expired query is not sent. Shared provider initialization may still finish for later operations.

captureMaxChars gates whether a user message from the turn's agent_end event is short enough to be considered for auto-capture. memory_store rejects longer text before embedding or storage; the setting does not affect recall queries.

customTriggers adds literal auto-capture phrases without regex. Built-in triggers cover common English, Czech, Chinese, Japanese, and Korean memory phrases (remember, prefer, 记住, 覚えて, 기억해, and similar).

Auto-capture also rejects text that looks like envelope/transport metadata, prompt-injection payloads, or already-injected <relevant-memories> context, and caps at 3 captured memories per agent turn.

Completed message occurrences are not processed again while they remain in the conversation transcript, including after compaction. The last 60 completed text blocks also stay deduplicated after their messages leave the transcript. This history includes text that matched an existing memory and successful blocks from a partially failed message. A later message can still capture text that an earlier occurrence skipped because of the per-turn limit. Identical replacements without a distinct timestamp or retained context can be indistinguishable from an unchanged replay. Resetting or ending the conversation clears that progress. Overlapping completions in one conversation share capture progress; other conversations can proceed independently. On shutdown, the plugin stops new capture work and waits for pending captures before closing its storage.

Every memory is owned by one agent. Recall, duplicate detection, capture, listing, raw queries, and deletion all enforce that owner before returning or mutating rows. An agent with memory.search.enabled: false in its agents.entries.* entry, or one inheriting a disabled top-level search, also gets none of the memory_recall, memory_store, or memory_forget tools and does not participate in automatic recall or capture, even when the plugin-level autoRecall/autoCapture flags are on.

Incognito sessions skip automatic recall and capture. Their prompts are not sent to the embedding provider for automatic recall, and memory_store refuses to save them. Explicit tool calls still follow their normal data-handling rules.

Commands

memory-lancedb registers the ltm CLI namespace whenever it is installed (not only when it owns the active memory slot):

bash
openclaw ltm list [--agent <id>] [--limit <n>] [--order-by-created-at]openclaw ltm search <query> [--agent <id>] [--limit <n>]openclaw ltm stats [--agent <id>]openclaw ltm optimize

ltm stats gives its database read 60 seconds after plugin registration. It stops the isolated reader before reporting a timeout, without creating a memory table or changing existing memory data. A database with no memory table reports zero. Plugin discovery and source capture happen before this deadline starts.

Compact a local table

Stop every Gateway and command using the table, including other profiles that share its dbPath, then run:

bash
openclaw ltm optimize

The command compacts small fragments and prints before/after fragment and row counts. It operates on the entire shared table, across all agents, so it has no --agent option. All rows and existing version history are preserved. Retained versions still reference their old files, so fewer active fragments does not necessarily mean less disk usage.

Optimization requires exclusive offline custody. It refuses while the active state directory has a Gateway owner or another plugin handle has the local table open, including handles from other profiles. Normal shared readers and writers remain supported outside maintenance. Restart the Gateways after it completes.

Local filesystem paths and file: URLs share the same custody lock. Remote URI tables require distributed custody and are refused. Older plugin versions and external LanceDB scripts do not participate in the table lock; stop them before maintenance too. The command does not prune versions, delete memories, apply retention cleanup, or schedule background maintenance.

Query memories

ltm query runs a non-vector query directly against the LanceDB table:

bash
openclaw ltm query --agent research --cols id,text,createdAt --limit 20openclaw ltm query --filter "category = 'preference'" --order-by createdAt:desc
Flag Default Notes
--agent <id> configured default agent Selects the private agent namespace. Available on list, search, query, and stats.
--cols <columns> id,text,importance,category,createdAt Comma-separated column allowlist.
--filter <condition> none One comparison over an output column, such as category = 'preference' or importance >= 0.8. String values must be quoted.
--limit <n> 10 Positive integer.
--order-by <column>:<asc|desc> none Sorted in memory after the filter runs; the sort column is auto-added to the projection and stripped from output if it was not requested.

Agents get three tools from the active memory plugin:

  • memory_recall: vector search over stored memories.
  • memory_store: save a fact, preference, decision, or entity (rejects text that looks like a prompt-injection payload; skips exact duplicates after normalizing line endings, Unicode NFC, and surrounding whitespace, but stores semantically similar memories with different text).
  • memory_forget: delete by memoryId, or by query (auto-deletes a single match above 90% score, otherwise lists candidate IDs to disambiguate).

Storage

LanceDB data defaults to memory/lancedb under the active OpenClaw state directory: ~/.openclaw/memory/lancedb for the default profile. OPENCLAW_STATE_DIR or --profile <name> (~/.openclaw-<name>) move it with the state directory. See Paths and instances.

Earlier releases ignored the state directory, so named profiles and custom state directories shared ~/.openclaw/memory/lancedb. Those setups now start with their own store. Set dbPath: "~/.openclaw/memory/lancedb" to keep using the old shared store.

Override the path with dbPath:

json5
{  plugins: {    entries: {      "memory-lancedb": {        enabled: true,        config: {          dbPath: "/srv/openclaw/memory/lancedb",          embedding: {            apiKey: "${OPENAI_API_KEY}",            model: "text-embedding-3-small",          },        },      },    },  },}

The plugin keeps one LanceDB table and stores a normalized agent owner on each row. This is a storage boundary, not a post-search filter: agent ownership is applied before vector ranking and is included in list, query, count, and delete predicates. ltm query --filter accepts one validated comparison over the public output columns. The store builds that comparison separately from the mandatory owner predicate, so a filter cannot widen the query to another agent.

Databases created before per-agent ownership have no reliable row provenance. On upgrade, openclaw doctor --fix assigns those legacy rows once to the configured default agent. Runtime access is blocked until that migration has completed; other agents never inherit the old shared rows.

storageOptions accepts string key/value pairs for LanceDB storage backends (e.g. S3-compatible object storage) and supports ${ENV_VAR} expansion:

json5
{  plugins: {    entries: {      "memory-lancedb": {        enabled: true,        config: {          dbPath: "s3://memory-bucket/openclaw",          storageOptions: {            access_key: "${AWS_ACCESS_KEY_ID}",            secret_key: "${AWS_SECRET_ACCESS_KEY}",            endpoint: "${AWS_ENDPOINT_URL}",          },          embedding: {            apiKey: "${OPENAI_API_KEY}",            model: "text-embedding-3-small",          },        },      },    },  },}

Runtime dependencies and platform support

memory-lancedb bundles LanceDB's JavaScript. Its plugin package declares native @lancedb/lancedb-* packages as optional dependencies, so installation selects the matching binary for the host platform. Gateway startup does not repair plugin dependencies; if the native dependency is missing or fails to load, reinstall or update the plugin package and restart the Gateway.

@lancedb/lancedb does not publish a native build for darwin-x64 (Intel Mac). On that platform the plugin logs that LanceDB is unavailable at load time; use the default memory backend, run the Gateway on a supported platform/architecture, or disable memory-lancedb.

Troubleshooting

Embedding provider cleanup failed

The plugin waits for admitted embedding requests before closing each retired provider. If native cleanup fails, it reports the error instead of retrying it across plugin instances. Restart the Gateway to reclaim remaining native resources.

Input length exceeds the context length

The embedding model rejected the recall query:

text
memory-lancedb: recall failed: Error: 400 the input length exceeds the context length

Lower recallMaxChars; the new limit applies to the next memory operation:

json5
{  plugins: {    entries: {      "memory-lancedb": {        config: {          recallMaxChars: 400,        },      },    },  },}

For Ollama, also verify the embedding server is reachable from the Gateway host using its native embed endpoint:

bash
curl http://127.0.0.1:11434/api/embed \  -H "Content-Type: application/json" \  -d '{"model":"mxbai-embed-large","input":"hello"}'

Unsupported embedding model

Without embedding.dimensions, only the built-in OpenAI embedding dimensions are known (text-embedding-3-small, text-embedding-3-large). For any other model, set embedding.dimensions to the vector size that model reports.

Plugin loads but no memories appear

Confirm plugins.slots.memory points at memory-lancedb, then run:

bash
openclaw ltm statsopenclaw ltm search "recent preference"

If autoCapture is disabled, the plugin still recalls existing memories but does not store new ones automatically. Use the memory_store tool, or enable autoCapture.

Was this useful?