Context compaction is a common failure point for most OpenClaw memory systems. When the context window fills, OpenClaw compacts the session to make room for new messages. Any memory operations that were not flushed to durable storage can be lost during this transition.
The default memory-core backend uses a per-agent SQLite database powered by sqlite-vec. This architecture is lightweight, easy to set up, and works well for individual developers running a single agent on a local machine. It becomes fragile when you run multiple agents or when a file-based state needs to be shared or managed outside the runtime.
To address these limitations, this tutorial demonstrates how to build a production-ready OpenClaw memory plugin powered by Actian VectorAI DB. Rather than storing embeddings and memory records inside local SQLite files, the plugin externalizes memory into a dedicated vector database designed for high availability, durability, and independent scaling.
By moving memory management outside the runtime, agents gain access to a centralized knowledge layer that teams can share across processes, hosts, and deployment environments.
How OpenClaw Memory Works by Default
OpenClaw uses a default memory plugin called memory-core. It indexes MEMORY.md into chunks and stores them in a per-agent SQLite database located at ~/.openclaw/memory/<agentId>.sqlite. Inside this file are two key components: an FTS5 (Full-Text Search version 5) index for keyword search and a vec0 vector index powered by the sqlite-vec extension for semantic search.
The important design point is that the Markdown files in the agent workspace are the source of truth. Files like MEMORY.md and memory/YYYY-MM-DD.md hold the actual durable content. The SQLite database does not store primary memory. It only builds a derived, regenerable index over those files. This distinction matters because this tutorial replaces the indexing layer, not the underlying memory files.
In practice, this design introduces some failure modes that push teams toward external memory backends.
Failure modes in the default memory-core backend
Context compaction events can orphan in-flight memory operations. Even though recent fixes in the May 2026 release improve how OpenClaw closes local embedding providers when active-memory searches time out, the underlying issue remains tied to lifecycle boundaries during compaction.
One of the failure modes is that multiple agent instances cannot reliably share a single <agentId>.sqlite file. This creates contention in distributed setups where agents need shared knowledge, leading to inconsistent memory state across instances.
Another failure mode is that sqlite-vec extension must exist in the runtime environment. When it is missing, OpenClaw falls back to in-process JavaScript cosine similarity, which does not scale for production workloads or high-throughput memory search.
Context compaction lifecycle
Context compaction runs when the context window fills and OpenClaw removes older messages to free space. Before compaction begins, OpenClaw triggers an automatic memory flush, but this only captures memories the agent has explicitly written. Any in-flight operations or pending writes that have not reached the memory files are not persisted.
After compaction, the agent resumes with a compressed version of its previous conversation. At that point, memory recall depends on what has already been written to the memory files and indexed for retrieval. Any information that was still in-flight and had not been persisted before compaction cannot be recalled during that session. The issue is not that durable memory disappears, but that memory that was never successfully persisted cannot be recovered.
Because the SQLite database lives inside the agent process, the vector index is tightly coupled to the runtime. When the process restarts or compaction resets the context, the index does not exist independently. Moving the memory layer to VectorAI DB decouples storage from the agent lifecycle, so the memory system survives compaction and continues to serve queries across sessions.
The Plugin Slot System
OpenClaw uses a named slot system to control extensibility points inside the agent runtime. The memory system is one of these slots, exposed as plugins.slots.memory. This slot determines which backend handles all memory-related tool calls.
The slot system separates memory behavior from memory storage. That separation allows you to move from a local SQLite-based index to an external vector database without modifying agent logic or tool definitions. In practice, this means one config change followed by a restart is enough to replace the entire memory backend.
OpenClaw enforces a single active memory plugin at a time. The architecture does not support multiple concurrent memory backends in the same slot. Issue #60572 on GitHub proposes splitting memory into sub-slots such as memory.recall, memory.store, and memory.compaction, but this has not merged as of May 2026. As a result, every implementation must fully replace the existing memory provider rather than extend it.

Figure 1: Plugin slot architecture
Before writing the VectorAI DB plugin, it helps to study existing implementations of this pattern. The noncelogic/openclaw-memory-lancedb and lancedb/openclaw-lancedb-demo repositories show how external vector stores integrate into the slot system. The serenichron/openclaw-memory-mem0 plugin is the cleanest reference. It demonstrates the minimal structure required to implement a working memory backend and serves as the template for the VectorAI DB integration in this tutorial.
Setting Up VectorAI DB
Before writing the plugin, get VectorAI DB running locally and verify vector search works end-to-end. To execute the commands in this section, install the following:
- NPM
- - Docker
- - Ollama
- - OpenClaw CLI
- - Together AI API key
In your project directory, create a docker-compose.yml:
services:
vectorai:
image: actian/vectorai:latest
platform: linux/amd64 # If you are using a macOs device, else comment this line.
container_name: vectorai_db
ports:
- "6573:6573"
- "6574:6574"
volumes:
- ./data:/var/lib/actian-vectorai
environment:
- VECTORAI_LOG_LEVEL=info
restart: unless-stopped
Start the VectorAI DB server by:
docker-compose up -d
Ensure Ollama is running and the model is ready:
ollama pull nomic-embed-text
ollama pull llama3.2:3b
ollama serve
Create a package.json file:
{
"name": "vectorai-memory",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"openclaw": {
"extensions": [
"./dist/index.js"
]
},
"scripts": {
"build": "esbuild index.ts --bundle --platform=node --format=esm --external:openclaw --external:@actian/vectorai-client --external:@grpc/grpc-js --external:typebox --outfile=dist/index.js",
"build:check": "tsc --noEmit"
},
"dependencies": {
"@actian/vectorai-client": "^1.0.2",
"@sinclair/typebox": "^0.34.49",
"openclaw": "^2026.5.28",
"typebox": "^1.1.39"
},
"devDependencies": {
"@types/node": "^22.0.0",
"esbuild": "^0.28.0",
"typescript": "^5.0.0"
}
}
Create a tsconfig.json file:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": [
"index.ts"
]
}
Run the command:
npm install
This installs all the dependencies.
Create a collection the plugin will use. In your project directory, create a file create-collection.ts:
import { VectorAIClient } from "@actian/vectorai-client";
const client = new VectorAIClient("localhost:6574", {
restUrl: "http://localhost:6573",
});
// dim=768 matches nomic-embed-text output dimensions.
await client.collections.create("openclaw-memory", {
dimension: 768,
distanceMetric: "COSINE",
});
// Smoke test: insert one vector and query it back.
await client.points.upsert("openclaw-memory", [
{
id: 1,
vector: new Array(768).fill(0.01),
payload: { text: "smoke test", source: "MEMORY.md", line: 1 },
},
]);
const results = await client.points.search(
"openclaw-memory",
new Array(768).fill(0.01),
{ limit: 1 }
);
console.log("Collection ready. Top result:", results[0].payload.text);
Verify the connection by running the command:
npx tsx create-collection.ts
You will see an output as shown:

Figure 2: Verify connection
Building the Plugin
The OpenClaw memory slot expects a plugin that implements the memory tools and lifecycle hooks used by the agent runtime. The plugin below replaces the default SQLite/sqlite-vec backend with VectorAI DB.
Create a file index.ts:
import { defineToolPlugin } from "openclaw/plugin-sdk/tool-plugin";
import { Type } from "typebox";
import { VectorAIClient, hasId } from "@actian/vectorai-client";
const DEFAULT_GRPC = "localhost:6574";
const DEFAULT_REST = "http://localhost:6573";
const DEFAULT_OLLAMA = "http://localhost:11434";
const DEFAULT_COLLECTION = "openclaw-memory";
const EMBED_MODEL = "nomic-embed-text";
const DEFAULT_LIMIT = 5;
const EMBED_DIM = 768;
function hashId(input: string): number {
return Math.abs(
input.split("").reduce((acc, ch) => (Math.imul(31, acc) + ch.charCodeAt(0)) | 0, 0)
);
}
async function embed(text: string, ollamaHost: string): Promise<number[]> {
const res = await fetch(`${ollamaHost}/api/embeddings`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model: EMBED_MODEL, prompt: text }),
});
if (!res.ok) throw new Error(`Ollama embed failed: ${res.status} ${res.statusText}`);
return (await res.json()).embedding as number[];
}
export default defineToolPlugin({
id: "vectorai-memory",
name: "VectorAI Memory",
description: "Routes OpenClaw memory tools to Actian VectorAI DB via Ollama embeddings.",
configSchema: Type.Object({
grpcAddr: Type.Optional(
Type.String({ description: "VectorAI gRPC address (default: localhost:6574)" })
),
restUrl: Type.Optional(
Type.String({ description: "VectorAI REST URL (default: http://localhost:6573)" })
),
ollamaHost: Type.Optional(
Type.String({ description: "Ollama host for embeddings (default: http://localhost:11434)" })
),
collection: Type.Optional(
Type.String({ description: "VectorAI collection name (default: openclaw-memory)" })
),
}),
tools: (tool) => [
tool({
name: "vectorai_store",
label: "Store in VectorAI",
description: "Embed and store a text chunk in VectorAI DB. Returns the assigned numeric ID.",
parameters: Type.Object({
text: Type.String({ description: "The text content to embed and store." }),
source: Type.String({ description: "The source file or identifier this text came from." }),
line: Type.Optional(Type.Number({ description: "Line number within the source file." })),
}),
async execute({ text, source, line }, config, ctx) {
ctx?.signal?.throwIfAborted();
const grpcAddr = config?.grpcAddr ?? DEFAULT_GRPC;
const restUrl = config?.restUrl ?? DEFAULT_REST;
const ollamaHost = config?.ollamaHost ?? DEFAULT_OLLAMA;
const collection = config?.collection ?? DEFAULT_COLLECTION;
const client = new VectorAIClient(grpcAddr, { restUrl });
const vector = await embed(text, ollamaHost);
const id = hashId(`${source}:${line ?? 0}:${text.slice(0, 40)}`);
await client.points.upsert(collection, [
{ id, vector, payload: { text, source, line: line ?? 0 } },
]);
return JSON.stringify({ stored: true, id, collection });
},
}),
tool({
name: "vectorai_search",
label: "Search VectorAI",
description: "Semantic search over stored memories. Returns ranked results with scores.",
parameters: Type.Object({
query: Type.String({ description: "Natural language query to find semantically similar stored entries." }),
limit: Type.Optional(
Type.Number({
description: `Max results to return (default: ${DEFAULT_LIMIT}, max: 20).`,
minimum: 1,
maximum: 20,
})
),
}),
async execute({ query, limit }, config, ctx) {
ctx?.signal?.throwIfAborted();
const grpcAddr = config?.grpcAddr ?? DEFAULT_GRPC;
const restUrl = config?.restUrl ?? DEFAULT_REST;
const ollamaHost = config?.ollamaHost ?? DEFAULT_OLLAMA;
const collection = config?.collection ?? DEFAULT_COLLECTION;
const client = new VectorAIClient(grpcAddr, { restUrl });
const vector = await embed(query, ollamaHost);
const hits = await client.points.search(collection, vector, {
limit: limit ?? DEFAULT_LIMIT,
});
return JSON.stringify(
hits.map((h: any) => ({
id: h.id,
score: h.score,
text: h.payload?.text ?? null,
source: h.payload?.source ?? null,
line: h.payload?.line ?? null,
}))
);
},
}),
tool({
name: "vectorai_recall",
label: "Recall from VectorAI",
description: "Retrieve a specific memory by its numeric ID.",
parameters: Type.Object({
id: Type.Number({ description: "The numeric ID of the memory point to retrieve." }),
}),
async execute({ id }, config, ctx) {
ctx?.signal?.throwIfAborted();
const grpcAddr = config?.grpcAddr ?? DEFAULT_GRPC;
const restUrl = config?.restUrl ?? DEFAULT_REST;
const collection = config?.collection ?? DEFAULT_COLLECTION;
const client = new VectorAIClient(grpcAddr, { restUrl });
const hits = await client.points.search(
collection,
new Array(EMBED_DIM).fill(0),
{ limit: 1, filter: hasId([id]) }
);
const h = hits[0];
return JSON.stringify(
h?.payload
? { id: h.id, text: h.payload.text, source: h.payload.source, line: h.payload.line }
: null
);
},
}),
tool({
name: "vectorai_forget",
label: "Forget from VectorAI",
description: "Permanently delete a stored memory by its numeric ID.",
parameters: Type.Object({
id: Type.Number({ description: "The numeric ID of the memory point to delete." }),
}),
async execute({ id }, config, ctx) {
ctx?.signal?.throwIfAborted();
const grpcAddr = config?.grpcAddr ?? DEFAULT_GRPC;
const restUrl = config?.restUrl ?? DEFAULT_REST;
const collection = config?.collection ?? DEFAULT_COLLECTION;
const client = new VectorAIClient(grpcAddr, { restUrl });
await client.points.delete(collection, { ids: [id] });
return JSON.stringify({ deleted: true, id, collection });
},
}),
],
});
Run the command:
npm run build
This creates the file dist/index.js.
The plugin uses OpenClaw's plugin SDK and exports a standard plugin entry.
The defineToolPlugin() function is the plugin entry point. OpenClaw calls it during startup and exposes the runtime API used to register tools and lifecycle hooks.
The plugin creates a VectorAI DB client and an embedding helper that generates vectors using Ollama's nomic-embed-text model. Every memory operation uses the same embedding pipeline, which keeps retrieval and storage consistent.
Tool implementations
The plugin registers four tools that mirror OpenClaw's memory operations.
vectorai_store embeds a text chunk and stores it together with metadata such as the source file path and line number.
vectorai_search embeds the query, performs a vector search against the openclaw-memory collection, and returns the most relevant memories ranked by semantic similarity.
vectorai_recall retrieves a specific memory by ID.
Each stored record contains metadata. This metadata makes it possible to trace search results back to the original memory file.
vectorai_forget removes a stored memory from the VectorAI DB collection.
At this point, OpenClaw can already store, search, recall, and delete memories through VectorAI DB instead of SQLite/sqlite-vec.
Configure OpenClaw
Create an openclaw.plugin.json file that registers the plugin:
{
"id": "vectorai-memory",
"name": "VectorAI memory",
"version": "1.0.0",
"description": "Store and retrieve data in Actian VectorAI DB using semantic vector search.",
"activation": {
"onStartup": true
},
"contracts": {
"tools": [
"vectorai_store",
"vectorai_search",
"vectorai_recall",
"vectorai_forget"
]
},
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"grpcAddr": {
"type": "string"
},
"restUrl": {
"type": "string"
},
"ollamaHost": {
"type": "string"
},
"collection": {
"type": "string"
}
}
}
}
This single configuration change swaps the memory backend from SQLite/sqlite-vec to VectorAI DB. The agent behavior and prompts stay the same but the memory layer changes.
Installing and activating the plugin
At this point, the plugin is complete. The final step is swapping OpenClaw from the default memory-core backend to the new VectorAI DB backend.
So far, your directory structure should look like this:
.
├── create-collection.ts
├── data
├── dist
│ └── index.js
├── docker-compose.yml
├── index.ts
├── openclaw.plugin.json
├── tsconfig.json
├── package-lock.json
├── package.json
Install the plugin from your local directory:
openclaw plugins install .
You see an output shown:

Figure 3: OpenClaw plugin installation
Configure your OpenClaw agent to use the Together AI API Llama-3.3-70B-Instruct-Turbo chat model by default. Replace the placeholder with your API key.
openclaw onboard --non-interactive --accept-risk --mode local --auth-choice together-api-key --together-api-key "$TOGETHER_API_KEY"
openclaw config set agents.defaults.model.primary "together/meta-llama/Llama-3.3-70B-Instruct-Turbo"
openclaw config set plugins.allow '["vectorai-memory", "together"]'
Restart the OpenClaw gateway:
openclaw gateway restart
Test connection
Check the agent your OpenClaw is using by:
openclaw agents list
You should see an output shown below:
In this case, OpenClaw uses the default agent main.
To verify the plugin works, store a test memory in VectorAI DB using the plugin:
openclaw agent --agent main --message '/tool vectorai_store {"text":"Rome is the capital of Italy","source":"test","line":1}'
You should see:

Figure 5: Storing to VectorAI DB
To retrieve it, ask:
openclaw agent --agent main --message '/tool vectorai_search {"query":"what is the capital of Italy?"}'
We get the following results:

Figure 6: Agent retrieving from VectorAI DB
Why On-Premises Memory Matters for Production Agents
For many teams, cloud-hosted memory services are a practical choice. They simplify deployment and reduce operational overhead. However, they are not always an option.
Production agents often run in environments where data cannot leave the network boundary. Financial institutions, healthcare organizations, government agencies, and industrial environments frequently restrict cloud egress or require local data residency. In these cases, the memory layer must run alongside the application infrastructure.
VectorAI DB allows teams to keep semantic search and durable memory inside their own environment. Memory remains available across agent restarts, context compaction events, and multiple agent instances without depending on an external service.
Wrapping Up
OpenClaw's default SQLite/sqlite-vec memory backend works well for a single agent running on a single machine. As soon as you need shared memory, durable storage across context compaction events, or independent management of the vector store, an external backend becomes a better fit.
In this tutorial, you built a custom OpenClaw plugin that routes memory operations to VectorAI DB. The swap required only a plugin installation, a configuration change, and a gateway restart. The agent behavior stayed the same, but the memory layer became persistent, shareable, and independent of the agent runtime.
Get started with Actian VectorAI DB Community Edition by signing up today. Check the documentation for deployment and usage instructions, and participate in the Discord community for support and discussions.

Top comments (0)