@xdarkicex/openclaw-memory-libravdb 1.6.23 → 1.6.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,6 +10,9 @@ export interface LibravDBClientOptions {
10
10
  tlsMode?: "auto" | "tls" | "insecure";
11
11
  tlsClientCertPath?: string;
12
12
  tlsClientKeyPath?: string;
13
+ /** Stable tenant key for multi-agent DB routing. Attached as the
14
+ * `libravdb-tenant-key` gRPC metadata header on every call. */
15
+ tenantKey?: string;
13
16
  }
14
17
  export declare function resolveClientEndpoint(configuredEndpoint?: string): string;
15
18
  export declare function isLegacyJsonRpcHealthResponse(payload: string): boolean;
@@ -19,6 +19,8 @@ export function resolveClientEndpoint(configuredEndpoint) {
19
19
  path.join(os.homedir(), ".libravdbd", "run"),
20
20
  "/opt/homebrew/var/libravdbd/run",
21
21
  "/usr/local/var/libravdbd/run",
22
+ "/var/run/libravdbd",
23
+ "/run/libravdbd",
22
24
  ];
23
25
  for (const dir of candidateDirs) {
24
26
  const fullPath = path.join(dir, sockName);
@@ -178,6 +180,15 @@ export class LibravDBClient {
178
180
  bootstrap: () => self.bootstrapHandshake(),
179
181
  rpcMutex,
180
182
  });
183
+ const interceptors = [];
184
+ if (options.tenantKey) {
185
+ const tenantKey = options.tenantKey;
186
+ interceptors.push((next) => async (req) => {
187
+ req.header.set("libravdb-tenant-key", tenantKey);
188
+ return next(req);
189
+ });
190
+ }
191
+ interceptors.push(authInterceptor);
181
192
  const transport = createGrpcTransport({
182
193
  baseUrl: targetUrl,
183
194
  httpVersion: "2",
@@ -190,7 +201,7 @@ export class LibravDBClient {
190
201
  ...(isInsecure ? { rejectUnauthorized: false } : {}),
191
202
  },
192
203
  defaultTimeoutMs: options.timeoutMs ?? 30000,
193
- interceptors: [authInterceptor],
204
+ interceptors,
194
205
  });
195
206
  this.client = createPromiseClient(LibravDB, transport);
196
207
  }
@@ -0,0 +1,46 @@
1
+ export interface TurnEntry {
2
+ index: number;
3
+ role: string;
4
+ contentHash: string;
5
+ turnHash: string;
6
+ ingestedAt: number;
7
+ }
8
+ export interface TurnManifest {
9
+ sessionId: string;
10
+ version: number;
11
+ turns: TurnEntry[];
12
+ tailHash: string;
13
+ }
14
+ export interface KernelCompatibleMessage {
15
+ role: string;
16
+ content: string;
17
+ id?: string;
18
+ }
19
+ export declare class TurnManifestStore {
20
+ private manifestDir;
21
+ constructor();
22
+ private getManifestPath;
23
+ hashString(data: string): string;
24
+ createEmpty(sessionId: string): TurnManifest;
25
+ load(sessionId: string, logger?: {
26
+ warn?: (msg: string) => void;
27
+ error?: (msg: string, e: unknown) => void;
28
+ }): TurnManifest;
29
+ save(manifest: TurnManifest): void;
30
+ verifyChain(manifest: TurnManifest): boolean;
31
+ /**
32
+ * Finds the overlap point between incoming messages and our stored history.
33
+ * Returns the index into incomingMessages where new (un-ACKed) messages begin.
34
+ * Returns 0 if no overlap (full re-sync).
35
+ */
36
+ findOverlapIndex(manifest: TurnManifest, incomingMessages: KernelCompatibleMessage[]): number;
37
+ appendACKedMessages(manifest: TurnManifest, newMessages: KernelCompatibleMessage[], startingIndex: number): TurnManifest;
38
+ /**
39
+ * Determines the absolute starting index for a set of new messages.
40
+ * If we have stored turns, the next message's index is last_turn.index + 1.
41
+ * If the manifest is empty, we infer from OpenClaw's prePromptMessageCount signal
42
+ * (caller must provide this as a hint when available).
43
+ */
44
+ deriveStartingIndex(manifest: TurnManifest, prePromptMessageCountHint?: number): number;
45
+ }
46
+ export declare const manifestStore: TurnManifestStore;
@@ -0,0 +1,127 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as crypto from "crypto";
4
+ import * as os from "os";
5
+ export class TurnManifestStore {
6
+ manifestDir;
7
+ constructor() {
8
+ this.manifestDir = path.join(os.homedir(), ".openclaw", "libravdb-manifests");
9
+ if (!fs.existsSync(this.manifestDir)) {
10
+ fs.mkdirSync(this.manifestDir, { recursive: true });
11
+ }
12
+ }
13
+ getManifestPath(sessionId) {
14
+ const safe = sessionId.replace(/[^A-Za-z0-9._-]/g, "_");
15
+ return path.join(this.manifestDir, `${safe}.manifest.json`);
16
+ }
17
+ hashString(data) {
18
+ return crypto.createHash("sha256").update(data).digest("hex");
19
+ }
20
+ createEmpty(sessionId) {
21
+ return {
22
+ sessionId,
23
+ version: 0,
24
+ turns: [],
25
+ tailHash: "0000000000000000000000000000000000000000000000000000000000000000",
26
+ };
27
+ }
28
+ load(sessionId, logger) {
29
+ const filePath = this.getManifestPath(sessionId);
30
+ if (!fs.existsSync(filePath)) {
31
+ return this.createEmpty(sessionId);
32
+ }
33
+ try {
34
+ const raw = fs.readFileSync(filePath, "utf8");
35
+ const manifest = JSON.parse(raw);
36
+ if (!this.verifyChain(manifest)) {
37
+ logger?.warn?.(`[LibraVDB] Manifest chain broken for session ${sessionId}. Forcing re-sync.`);
38
+ return this.createEmpty(sessionId);
39
+ }
40
+ return manifest;
41
+ }
42
+ catch (e) {
43
+ logger?.error?.(`[LibraVDB] Failed to read manifest for ${sessionId}:`, e);
44
+ return this.createEmpty(sessionId);
45
+ }
46
+ }
47
+ save(manifest) {
48
+ const filePath = this.getManifestPath(manifest.sessionId);
49
+ const tempPath = `${filePath}.${process.pid}.tmp`;
50
+ fs.writeFileSync(tempPath, JSON.stringify(manifest, null, 2), "utf8");
51
+ fs.renameSync(tempPath, filePath);
52
+ }
53
+ verifyChain(manifest) {
54
+ let currentHash = "0000000000000000000000000000000000000000000000000000000000000000";
55
+ for (const turn of manifest.turns) {
56
+ const expectedHash = this.hashString(`${turn.index}${turn.role}${turn.contentHash}${currentHash}`);
57
+ if (turn.turnHash !== expectedHash) {
58
+ return false;
59
+ }
60
+ currentHash = expectedHash;
61
+ }
62
+ return manifest.tailHash === currentHash;
63
+ }
64
+ /**
65
+ * Finds the overlap point between incoming messages and our stored history.
66
+ * Returns the index into incomingMessages where new (un-ACKed) messages begin.
67
+ * Returns 0 if no overlap (full re-sync).
68
+ */
69
+ findOverlapIndex(manifest, incomingMessages) {
70
+ if (manifest.turns.length === 0) {
71
+ return 0;
72
+ }
73
+ // Build a map of contentHash → index in our manifest
74
+ const known = new Map();
75
+ for (const turn of manifest.turns) {
76
+ known.set(turn.contentHash, turn.index);
77
+ }
78
+ // Scan incoming messages from newest to oldest to find the last match
79
+ for (let i = incomingMessages.length - 1; i >= 0; i--) {
80
+ const contentHash = this.hashString(incomingMessages[i].content);
81
+ if (known.has(contentHash)) {
82
+ return i + 1; // everything at and after this index is new
83
+ }
84
+ }
85
+ // No overlap found — OpenClaw trimmed too much or session diverged
86
+ return 0;
87
+ }
88
+ appendACKedMessages(manifest, newMessages, startingIndex) {
89
+ let currentHash = manifest.tailHash;
90
+ const newTurns = [];
91
+ for (let i = 0; i < newMessages.length; i++) {
92
+ const msg = newMessages[i];
93
+ const absoluteIndex = startingIndex + i;
94
+ const contentHash = this.hashString(msg.content);
95
+ currentHash = this.hashString(`${absoluteIndex}${msg.role}${contentHash}${currentHash}`);
96
+ newTurns.push({
97
+ index: absoluteIndex,
98
+ role: msg.role,
99
+ contentHash,
100
+ turnHash: currentHash,
101
+ ingestedAt: Date.now(),
102
+ });
103
+ }
104
+ return {
105
+ sessionId: manifest.sessionId,
106
+ version: manifest.version + 1,
107
+ turns: [...manifest.turns, ...newTurns],
108
+ tailHash: currentHash,
109
+ };
110
+ }
111
+ /**
112
+ * Determines the absolute starting index for a set of new messages.
113
+ * If we have stored turns, the next message's index is last_turn.index + 1.
114
+ * If the manifest is empty, we infer from OpenClaw's prePromptMessageCount signal
115
+ * (caller must provide this as a hint when available).
116
+ */
117
+ deriveStartingIndex(manifest, prePromptMessageCountHint) {
118
+ if (manifest.turns.length > 0) {
119
+ return manifest.turns[manifest.turns.length - 1].index + 1;
120
+ }
121
+ // Empty manifest — use OpenClaw's signal if provided, else assume 0
122
+ return typeof prePromptMessageCountHint === "number" && prePromptMessageCountHint >= 0
123
+ ? prePromptMessageCountHint
124
+ : 0;
125
+ }
126
+ }
127
+ export const manifestStore = new TurnManifestStore();
@@ -147,6 +147,14 @@ class DirectoryMarkdownSourceAdapter {
147
147
  if (!this.started || this.stopping) {
148
148
  return;
149
149
  }
150
+ // Cancel any pending scheduled scans so they don't race with this refresh
151
+ for (const state of this.states.values()) {
152
+ if (state.scanState.timer) {
153
+ clearTimeout(state.scanState.timer);
154
+ state.scanState.timer = null;
155
+ }
156
+ state.scanState.dirty = false;
157
+ }
150
158
  for (const root of this.roots) {
151
159
  await this.scanRoot(root);
152
160
  }
@@ -291,11 +299,11 @@ class DirectoryMarkdownSourceAdapter {
291
299
  continue;
292
300
  }
293
301
  stats.filesIncluded++;
294
- currentFiles.add(child);
295
302
  const stat = await this.safeStatWithCtime(child);
296
303
  if (!stat) {
297
304
  continue;
298
305
  }
306
+ currentFiles.add(child);
299
307
  candidates.push({ path: child, size: stat.size, mtimeMs: stat.mtimeMs, ctimeMs: stat.ctimeMs, ordinal: candidates.length });
300
308
  }
301
309
  }
@@ -310,7 +318,10 @@ class DirectoryMarkdownSourceAdapter {
310
318
  this.lastRetryAfterMs = 0;
311
319
  }
312
320
  else {
321
+ // Resume cursor target was deleted — clear it and resume normal processing
313
322
  rootState.scanState.resumeFromPath = null;
323
+ this.lastAcceptMore = true;
324
+ this.lastRetryAfterMs = 0;
314
325
  }
315
326
  }
316
327
  for (const candidate of sorted) {
@@ -439,6 +450,8 @@ class DirectoryMarkdownSourceAdapter {
439
450
  async syncMarkdownFile(rootState, filePath, initialStat) {
440
451
  const sourceDoc = filePath;
441
452
  const relativePath = toPosixPath(path.relative(rootState.root, filePath));
453
+ // Re-check existence when initialStat is provided — the file may have been
454
+ // deleted between the scan pass and the sync pass.
442
455
  const stat = initialStat ?? (await this.safeStatWithCtime(filePath));
443
456
  if (!stat) {
444
457
  await this.deleteSourceDocument(sourceDoc);
@@ -74,18 +74,22 @@ function createMemorySearchManager(getClient, cfg, defaults, initialStatus) {
74
74
  const filteredResults = minScore === undefined
75
75
  ? result.results
76
76
  : result.results.filter((item) => item.score >= minScore);
77
- const legacyResults = filteredResults.map((item) => ({
78
- ...item,
79
- content: item.text,
80
- }));
77
+ const legacyResults = filteredResults.map((item) => {
78
+ const meta = parseMetadataJson(item);
79
+ return {
80
+ ...item,
81
+ content: item.text || (typeof meta.text === "string" ? meta.text : ""),
82
+ };
83
+ });
81
84
  if (legacyCall) {
82
85
  return { results: legacyResults };
83
86
  }
84
87
  const memoryResults = filteredResults.map((item) => {
85
88
  const meta = parseMetadataJson(item);
86
89
  const collection = typeof meta.collection === "string" ? meta.collection : "memory";
90
+ const effectiveText = item.text || (typeof meta.text === "string" ? meta.text : "") || "";
87
91
  const relPath = encodeSearchResultPath(collection, item.id);
88
- returnedSearchPaths.set(relPath, item.text);
92
+ returnedSearchPaths.set(relPath, effectiveText);
89
93
  return toMemorySearchResult(item);
90
94
  });
91
95
  return memoryResults;
@@ -194,12 +198,13 @@ function parseMetadataJson(item) {
194
198
  function toMemorySearchResult(item) {
195
199
  const meta = parseMetadataJson(item);
196
200
  const collection = typeof meta.collection === "string" ? meta.collection : "memory";
201
+ const effectiveText = item.text || (typeof meta.text === "string" ? meta.text : "") || "";
197
202
  return {
198
203
  path: encodeSearchResultPath(collection, item.id),
199
204
  startLine: 1,
200
- endLine: Math.max(1, item.text.split("\n").length),
205
+ endLine: Math.max(1, effectiveText.split("\n").length),
201
206
  score: item.score,
202
- snippet: item.text,
207
+ snippet: effectiveText,
203
208
  source: collection.startsWith("session:") || collection.startsWith("session_") ? "sessions" : "memory",
204
209
  citation: `${collection}:${item.id}`,
205
210
  };
@@ -1,7 +1,7 @@
1
1
  import { LibravDBClient } from "./libravdb-client.js";
2
2
  import type { LoggerLike, PluginConfig } from "./types.js";
3
3
  export type ClientGetter = () => Promise<LibravDBClient>;
4
- export declare const DEFAULT_RPC_TIMEOUT_MS = 30000;
4
+ export declare const DEFAULT_RPC_TIMEOUT_MS = 120000;
5
5
  export declare const STARTUP_HEALTH_TIMEOUT_MS = 2000;
6
6
  export declare const VALID_TLS_MODES: readonly ["auto", "tls", "insecure"];
7
7
  export type ValidTlsMode = typeof VALID_TLS_MODES[number];
@@ -1,13 +1,19 @@
1
1
  import { LibravDBClient, resolveClientEndpoint } from "./libravdb-client.js";
2
2
  import { formatError } from "./format-error.js";
3
+ import { resolveTenantKey } from "./identity.js";
3
4
  import { existsSync, statSync } from "node:fs";
4
5
  import path from "node:path";
5
- export const DEFAULT_RPC_TIMEOUT_MS = 30000;
6
+ export const DEFAULT_RPC_TIMEOUT_MS = 120_000;
6
7
  export const STARTUP_HEALTH_TIMEOUT_MS = 2000;
8
+ const ENV_RPC_TIMEOUT_MS = (() => {
9
+ const raw = Number(process.env.LIBRAVDB_RPC_TIMEOUT_MS);
10
+ return Number.isFinite(raw) && raw > 0 ? raw : 0;
11
+ })();
7
12
  export const VALID_TLS_MODES = ["auto", "tls", "insecure"];
8
13
  const isTlsModeValid = (m) => VALID_TLS_MODES.includes(m);
9
14
  export function resolveStartupHealthTimeoutMs(cfg) {
10
- return Math.max(STARTUP_HEALTH_TIMEOUT_MS, cfg.rpcTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS);
15
+ const timeout = cfg.rpcTimeoutMs ?? (ENV_RPC_TIMEOUT_MS || DEFAULT_RPC_TIMEOUT_MS);
16
+ return Math.max(STARTUP_HEALTH_TIMEOUT_MS, timeout);
11
17
  }
12
18
  export function daemonProvisioningHint() {
13
19
  return "If you installed the npm package, install and start libravdbd separately; the package does not provision the daemon binary, ONNX Runtime, or model assets.";
@@ -48,11 +54,12 @@ export function createPluginRuntime(cfg, logger = console) {
48
54
  validateTlsConfig(cfg, logger);
49
55
  client = new LibravDBClient({
50
56
  endpoint: cfg.grpcEndpoint || cfg.sidecarPath,
51
- timeoutMs: cfg.rpcTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS,
57
+ timeoutMs: cfg.rpcTimeoutMs ?? (ENV_RPC_TIMEOUT_MS || DEFAULT_RPC_TIMEOUT_MS),
52
58
  tlsCaPath: cfg.grpcEndpointTlsCa,
53
59
  tlsMode: cfg.grpcEndpointTlsMode,
54
60
  tlsClientCertPath: cfg.grpcEndpointTlsClientCert,
55
61
  tlsClientKeyPath: cfg.grpcEndpointTlsClientKey,
62
+ tenantKey: resolveTenantKey(cfg),
56
63
  });
57
64
  await client.bootstrapHandshake();
58
65
  return client;
package/dist/types.d.ts CHANGED
@@ -2,6 +2,11 @@ export interface PluginConfig {
2
2
  dbPath?: string;
3
3
  /** Legacy fallback alias for grpcEndpoint. */
4
4
  sidecarPath?: string;
5
+ /** Stable tenant identifier for multi-agent deployments. When set, the daemon
6
+ * routes this plugin instance to an isolated vector database. When unset,
7
+ * the plugin falls back to the auto-derived userId. Set different values per
8
+ * agent to isolate memory storage. */
9
+ tenantId?: string;
5
10
  /** Stable identity for cross-session durable memory. When set, all sessions
6
11
  * share memories under user:{userId}. When unset, the plugin auto-derives
7
12
  * identity from the OS and persists it to the identity file. */
package/docs/README.md CHANGED
@@ -6,14 +6,14 @@ deeper by goal.
6
6
 
7
7
  ## Start Here
8
8
 
9
- - [Install](./install.md) - shortest supported install and daemon lifecycle path.
9
+ - [Install](./install.md) - shortest supported install and vector service lifecycle path.
10
10
  - [Installation reference](./installation.md) - requirements, activation, verification, and troubleshooting.
11
- - [Uninstall](./uninstall.md) - safe disable, daemon shutdown, package removal, and optional data cleanup.
11
+ - [Uninstall](./uninstall.md) - safe disable, vector service shutdown, package removal, and optional data cleanup.
12
12
 
13
13
  ## Understand The System
14
14
 
15
15
  - [Problem](./problem.md) - why this plugin replaces the stock memory lifecycle.
16
- - [Architecture](./architecture.md) - plugin, sidecar, storage, retrieval, and compaction overview.
16
+ - [Architecture](./architecture.md) - plugin, vector service, storage, retrieval, and compaction overview.
17
17
  - [Dependency rationale](./dependencies.md) - why LibraVDB and slab-style storage fit this workload.
18
18
  - [Architecture decisions](./architecture-decisions/README.md) - accepted ADRs.
19
19
 
@@ -27,5 +27,5 @@ deeper by goal.
27
27
  ## Advanced And Source Docs
28
28
 
29
29
  - [Performance and tuning](./performance-and-tuning.md) - resource expectations and tuning knobs.
30
- - [Development](./development.md) - source setup, local daemon builds, generated IPC files, and validation commands.
30
+ - [Development](./development.md) - source setup, local vector service builds, generated IPC files, and validation commands.
31
31
  - [Contributing](./contributing.md) - contributor workflow and repository expectations.
@@ -1,6 +1,6 @@
1
1
  # TLS Configuration
2
2
 
3
- The plugin selects the right credentials automatically based on which address it connects to. Unix sockets and loopback addresses (localhost, 127.0.0.1, ::1) always use plaintext. All other addresses use TLS. In most deployments no TLS configuration is needed at all. The only time manual configuration is required is when the daemon serves TLS on a loopback address, when the daemon uses a self-signed or private-CA certificate, or when infrastructure such as a service mesh handles TLS outside the plugin.
3
+ The plugin selects the right credentials automatically based on which address it connects to. Unix sockets and loopback addresses (localhost, 127.0.0.1, ::1) always use plaintext. All other addresses use TLS. In most deployments no TLS configuration is needed at all. The only time manual configuration is required is when the vector service serves TLS on a loopback address, when the vector service uses a self-signed or private-CA certificate, or when infrastructure such as a service mesh handles TLS outside the plugin.
4
4
 
5
5
  ## Default behavior
6
6
 
@@ -18,21 +18,21 @@ Because these rules are automatic, most users do not set any TLS-related fields.
18
18
 
19
19
  | Field | Type | Default | When to use |
20
20
  |---|---|---|---|
21
- | `grpcEndpoint` | string | — | The daemon address. Set to a unix socket path, a loopback address, or a remote host. |
22
- | `grpcEndpointTlsCa` | string | — | Path to a CA certificate PEM file. Required only when the daemon certificate is self-signed or signed by a private CA not in the system certificate store. |
21
+ | `grpcEndpoint` | string | — | The vector service address. Set to a unix socket path, a loopback address, or a remote host. |
22
+ | `grpcEndpointTlsCa` | string | — | Path to a CA certificate PEM file. Required only when the vector service certificate is self-signed or signed by a private CA not in the system certificate store. |
23
23
  | `grpcEndpointTlsMode` | `"auto"` \| `"tls"` \| `"insecure"` | `"auto"` | Override the automatic selection. `"auto"` applies the default rules above. `"tls"` forces TLS regardless of address. `"insecure"` forces plaintext regardless of address. |
24
24
 
25
25
  `grpcEndpointTlsMode` values explained:
26
26
 
27
27
  - **`"auto"`** (default) — apply the automatic rules. Unix sockets and loopback use plaintext; all other addresses use TLS.
28
- - **`"tls"`** — always use TLS, even for loopback addresses. Use this when the daemon has TLS enabled on a loopback address.
28
+ - **`"tls"`** — always use TLS, even for loopback addresses. Use this when the vector service has TLS enabled on a loopback address.
29
29
  - **`"insecure"`** — always use plaintext, even for remote addresses. Use this only when a service mesh or TLS-terminating tunnel handles encryption externally.
30
30
 
31
31
  ## Deployment scenarios
32
32
 
33
- ### Local daemon (default)
33
+ ### Local vector service (default)
34
34
 
35
- The daemon runs on the same machine, listening on a unix socket or a loopback address.
35
+ The vector service runs on the same machine, listening on a unix socket or a loopback address.
36
36
 
37
37
  ```json
38
38
  {
@@ -50,9 +50,9 @@ or:
50
50
 
51
51
  The plugin automatically uses plaintext. No TLS fields are needed.
52
52
 
53
- ### Remote daemon with a trusted certificate
53
+ ### Remote vector service with a trusted certificate
54
54
 
55
- The daemon runs on a remote host and presents a certificate issued by a public CA such as Let's Encrypt or cert-manager.
55
+ The vector service runs on a remote host and presents a certificate issued by a public CA such as Let's Encrypt or cert-manager.
56
56
 
57
57
  ```json
58
58
  {
@@ -60,11 +60,11 @@ The daemon runs on a remote host and presents a certificate issued by a public C
60
60
  }
61
61
  ```
62
62
 
63
- TLS is automatic. The plugin uses the system certificate store to verify the daemon's certificate, so no additional configuration is needed.
63
+ TLS is automatic. The plugin uses the system certificate store to verify the vector service's certificate, so no additional configuration is needed.
64
64
 
65
- ### Remote daemon with a self-signed or private CA certificate
65
+ ### Remote vector service with a self-signed or private CA certificate
66
66
 
67
- The daemon runs on a remote host and uses a self-signed certificate or a certificate signed by a private/internal CA not in the system certificate store.
67
+ The vector service runs on a remote host and uses a self-signed certificate or a certificate signed by a private/internal CA not in the system certificate store.
68
68
 
69
69
  ```json
70
70
  {
@@ -73,11 +73,11 @@ The daemon runs on a remote host and uses a self-signed certificate or a certifi
73
73
  }
74
74
  ```
75
75
 
76
- The CA certificate must be the certificate of the CA that signed the daemon's server certificate — not the server certificate itself. The plugin uses this CA to verify the daemon's certificate during the TLS handshake. Without it, the plugin will reject the daemon's certificate as untrusted.
76
+ The CA certificate must be the certificate of the CA that signed the vector service's server certificate — not the server certificate itself. The plugin uses this CA to verify the vector service's certificate during the TLS handshake. Without it, the plugin will reject the vector service's certificate as untrusted.
77
77
 
78
78
  ### TLS on a loopback address
79
79
 
80
- The daemon has TLS enabled on a loopback address. This is uncommon. The automatic rules would select plaintext for a loopback address, so an explicit override is required.
80
+ The vector service has TLS enabled on a loopback address. This is uncommon. The automatic rules would select plaintext for a loopback address, so an explicit override is required.
81
81
 
82
82
  ```json
83
83
  {
@@ -86,11 +86,11 @@ The daemon has TLS enabled on a loopback address. This is uncommon. The automati
86
86
  }
87
87
  ```
88
88
 
89
- Set `grpcEndpointTlsMode` to `"tls"` to force the plugin to use TLS even on the loopback address. The plugin will use the system certificate store for verification. If the daemon uses a self-signed certificate, add `grpcEndpointTlsCa` as well.
89
+ Set `grpcEndpointTlsMode` to `"tls"` to force the plugin to use TLS even on the loopback address. The plugin will use the system certificate store for verification. If the vector service uses a self-signed certificate, add `grpcEndpointTlsCa` as well.
90
90
 
91
91
  ## Service mesh and tunnels
92
92
 
93
- When the daemon runs behind Istio, Envoy, or any other infrastructure that terminates TLS at the mesh or tunnel layer, the plugin should not attempt its own TLS. Set `grpcEndpointTlsMode` to `"insecure"` so the plugin uses plaintext and lets the mesh handle encryption:
93
+ When the vector service runs behind Istio, Envoy, or any other infrastructure that terminates TLS at the mesh or tunnel layer, the plugin should not attempt its own TLS. Set `grpcEndpointTlsMode` to `"insecure"` so the plugin uses plaintext and lets the mesh handle encryption:
94
94
 
95
95
  ```json
96
96
  {
@@ -105,8 +105,8 @@ This applies even for remote addresses. The mesh terminates TLS at the boundary,
105
105
 
106
106
  | Error | Likely cause | Fix |
107
107
  |---|---|---|
108
- | `UNAVAILABLE / connection closed / TLS handshake failed` when connecting to a loopback address | The daemon has TLS enabled on a loopback address but the plugin is using plaintext (the default for loopback). | Add `"grpcEndpointTlsMode": "tls"` to the plugin config. |
109
- | `x509: certificate signed by unknown authority` | The daemon uses a self-signed certificate or a certificate from a private CA not trusted by the system. | Set `grpcEndpointTlsCa` to the path of the CA certificate PEM file that signed the daemon's server certificate. |
108
+ | `UNAVAILABLE / connection closed / TLS handshake failed` when connecting to a loopback address | The vector service has TLS enabled on a loopback address but the plugin is using plaintext (the default for loopback). | Add `"grpcEndpointTlsMode": "tls"` to the plugin config. |
109
+ | `x509: certificate signed by unknown authority` | The vector service uses a self-signed certificate or a certificate from a private CA not trusted by the system. | Set `grpcEndpointTlsCa` to the path of the CA certificate PEM file that signed the vector service's server certificate. |
110
110
  | `failed to load TLS CA certificate from "...": ENOENT: no such file or directory` | The file path given in `grpcEndpointTlsCa` does not exist on the machine. | Verify the file path is correct and the CA certificate file exists. |
111
111
  | `LibraVDB: invalid grpcEndpointTlsMode "..."` | The value set in `grpcEndpointTlsMode` is not one of the accepted values. | Change the value to `"auto"`, `"tls"`, or `"insecure"`. |
112
112
  | `LIBRAVDB: grpcEndpointTlsCa is set but grpcEndpointTlsMode is "insecure"` (warning) | Both `grpcEndpointTlsCa` and `grpcEndpointTlsMode: "insecure"` are set. The CA file will not be used. | Remove `grpcEndpointTlsCa` if plaintext is intended, or change `grpcEndpointTlsMode` to `"auto"` or `"tls"` to use the CA file. |
@@ -6,7 +6,7 @@ The plugin requires local vector storage, ONNX inference, transport isolation, a
6
6
 
7
7
  ## Decision
8
8
 
9
- Implement the memory engine as a Go daemon with a narrow JSON-RPC transport boundary.
9
+ Implement the memory engine as a Go vector service with a narrow gRPC transport boundary.
10
10
 
11
11
  ## Alternatives Considered
12
12
 
@@ -10,9 +10,9 @@ LibraVDB Memory is split into two cooperating pieces:
10
10
 
11
11
  - a TypeScript OpenClaw plugin that owns the `memory` slot and registers
12
12
  context-engine capability at runtime
13
- - a Go sidecar daemon that owns storage, retrieval, and compaction
13
+ - a Go vector service that owns storage, retrieval, and compaction
14
14
 
15
- The plugin keeps the host integration light and stable. The daemon keeps the
15
+ The plugin keeps the host integration light and stable. The vector service keeps the
16
16
  data path local-first and handles the expensive memory operations outside the
17
17
  main chat process.
18
18
 
@@ -22,9 +22,9 @@ main chat process.
22
22
  flowchart LR
23
23
  Host["OpenClaw host"]
24
24
  Plugin["TypeScript plugin\nregistration + context engine"]
25
- Runtime["Plugin runtime\nlazy daemon connect + RPC"]
25
+ Runtime["Plugin runtime\nlazy vector service connect + RPC"]
26
26
  MPS["memoryPromptSection\ncapability header"]
27
- Sidecar["Go daemon"]
27
+ Sidecar["Go vector service"]
28
28
  Store["LibraVDB store"]
29
29
  Embed["Embedding engine"]
30
30
  Summarizer["Summarizer(s)"]
@@ -47,7 +47,7 @@ main retrieval path.
47
47
 
48
48
  ### `ingest`
49
49
 
50
- Session messages are written into the sidecar-backed store. User turns may also
50
+ Session messages are written into the vector service-backed store. User turns may also
51
51
  be promoted into durable user memory after gating.
52
52
 
53
53
  ### `assemble`
@@ -72,13 +72,13 @@ thresholds, compaction declines instead of forcing a rewrite.
72
72
  - retrieval happens in `assemble`
73
73
  - compaction is separate from prompt construction
74
74
  - lifecycle hints such as `before_reset` and `session_end` are advisory
75
- - the sidecar is the source of truth for stored memory state
75
+ - the vector service is the source of truth for stored memory state
76
76
 
77
77
  ## Failure Handling
78
78
 
79
79
  The plugin is designed to degrade gracefully:
80
80
 
81
- - if the daemon is unavailable, prompt assembly continues without recall
81
+ - if the vector service is unavailable, prompt assembly continues without recall
82
82
  - if compaction fails, the active session is not blocked
83
83
  - if summarization is unavailable, the system falls back to the safer path
84
84
 
@@ -93,5 +93,5 @@ This architecture keeps the host integration simple while still supporting:
93
93
  - explicit compaction
94
94
  - local-first storage and retrieval
95
95
 
96
- In short, the plugin owns the lifecycle contract, and the sidecar owns the
96
+ In short, the plugin owns the lifecycle contract, and the vector service owns the
97
97
  heavy lifting.
@@ -34,45 +34,45 @@ Use `grpcEndpointTlsMode` to override the default behavior:
34
34
 
35
35
  | Value | When to use |
36
36
  |---|---|
37
- | `"auto"` (default) | Standard operation — plugin heuristic matches daemon TLS setting automatically. |
38
- | `"tls"` | Daemon has TLS enabled on loopback or unix socket (rare; use when the daemon's `LIBRAVDB_GRPC_TLS_*` env vars are set on a local address). |
37
+ | `"auto"` (default) | Standard operation — plugin heuristic matches vector service TLS setting automatically. |
38
+ | `"tls"` | Daemon has TLS enabled on loopback or unix socket (rare; use when the vector service's `LIBRAVDB_GRPC_TLS_*` env vars are set on a local address). |
39
39
  | `"insecure"` | Service mesh or TLS-terminating tunnel handles encryption externally; both sides are plaintext. |
40
40
 
41
- **Default (local daemon):** No TLS configuration needed.
41
+ **Default (local vector service):** No TLS configuration needed.
42
42
  Unix socket and loopback endpoints are always plaintext regardless
43
43
  of any TLS settings.
44
44
 
45
- **K8 / remote daemon with CA-issued cert:**
45
+ **K8 / remote vector service with CA-issued cert:**
46
46
  No extra configuration needed. The plugin uses the system CA pool,
47
47
  which trusts certs issued by Let's Encrypt, cert-manager, and
48
48
  other public CAs automatically.
49
49
 
50
- **Remote daemon with self-signed or private CA cert:**
50
+ **Remote vector service with self-signed or private CA cert:**
51
51
  Set `grpcEndpointTlsCa` to the path of the CA certificate PEM file:
52
52
  ```json
53
53
  {
54
- "grpcEndpoint": "tcp:yourdaemon.internal:50051",
54
+ "grpcEndpoint": "tcp:yourvector service.internal:50051",
55
55
  "grpcEndpointTlsCa": "/etc/certs/ca.pem"
56
56
  }
57
57
  ```
58
- The daemon must be configured with matching TLS cert and key via
58
+ The vector service must be configured with matching TLS cert and key via
59
59
  `LIBRAVDB_GRPC_TLS_CERT` and `LIBRAVDB_GRPC_TLS_KEY`.
60
60
 
61
- **Remote daemon with mTLS (mutual TLS):**
62
- When the daemon requires client certificate authentication, set both
61
+ **Remote vector service with mTLS (mutual TLS):**
62
+ When the vector service requires client certificate authentication, set both
63
63
  `grpcEndpointTlsClientCert` and `grpcEndpointTlsClientKey` (they must both
64
64
  be present or both be omitted):
65
65
  ```json
66
66
  {
67
- "grpcEndpoint": "tcp:yourdaemon.internal:50051",
67
+ "grpcEndpoint": "tcp:yourvector service.internal:50051",
68
68
  "grpcEndpointTlsCa": "/etc/certs/ca.pem",
69
69
  "grpcEndpointTlsClientCert": "/etc/certs/client-cert.pem",
70
70
  "grpcEndpointTlsClientKey": "/etc/certs/client-key.pem"
71
71
  }
72
72
  ```
73
73
 
74
- **Local daemon with TLS enabled:**
75
- If the daemon has `LIBRAVDB_GRPC_TLS_CERT`/`LIBRAVDB_GRPC_TLS_KEY` set on a loopback
74
+ **Local vector service with TLS enabled:**
75
+ If the vector service has `LIBRAVDB_GRPC_TLS_CERT`/`LIBRAVDB_GRPC_TLS_KEY` set on a loopback
76
76
  address, explicitly set `grpcEndpointTlsMode: "tls"` to match:
77
77
  ```json
78
78
  {
@@ -87,9 +87,9 @@ address, explicitly set `grpcEndpointTlsMode: "tls"` to match:
87
87
  |---|---|---|---|
88
88
  | `embeddingProfile` | string | `nomic-embed-text-v1.5` | Primary embedding model |
89
89
  | `fallbackProfile` | string | `bge-small-en-v1.5` | Fallback when primary model fails dimension checks |
90
- | `embeddingBackend` | string | — | `bundled`, `onnx-local`, `custom-local`, or `remote` |
91
- | `onnxDevice` | string | `auto` | ONNX execution provider: `auto`, `cpu`, `coreml` (macOS), `cuda` (Linux/Windows), `directml` (Windows), `openvino` (Linux) |
92
- | `embeddingRuntimePath` | string | — | Path to ONNX runtime library visible to the daemon (maps to `LIBRAVDB_ONNX_RUNTIME`; required with `embeddingBackend: "onnx-local"`) |
90
+ | `embeddingBackend` | string | — | `gguf` (recommended default), `bundled`, `onnx-local`, `custom-local`, or `remote` |
91
+ | `onnxDevice` | string | `cpu` | ONNX execution provider: `auto`, `cpu`, `coreml` (macOS), `cuda` (Linux/Windows), `directml` (Windows), `openvino` (Linux) |
92
+ | `embeddingRuntimePath` | string | — | Path to ONNX runtime library visible to the vector service (maps to `LIBRAVDB_ONNX_RUNTIME`; required with `embeddingBackend: "onnx-local"`) |
93
93
  | `embeddingModelPath` | string | — | Path to the model directory containing `embedding.json`, `model.onnx`, and `tokenizer.json` (maps to `LIBRAVDB_EMBEDDING_MODEL`; required with `embeddingBackend: "onnx-local"`) |
94
94
  | `embeddingTokenizerPath` | string | — | Path to custom tokenizer file |
95
95
  | `embeddingDimensions` | number | — | Embedding dimension override |