@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.
- package/README.md +219 -38
- package/dist/context-engine.js +72 -8
- package/dist/identity.d.ts +10 -1
- package/dist/identity.js +20 -0
- package/dist/index.js +814 -136
- package/dist/libravdb-client.d.ts +3 -0
- package/dist/libravdb-client.js +12 -1
- package/dist/manifest.d.ts +46 -0
- package/dist/manifest.js +127 -0
- package/dist/markdown-ingest.js +14 -1
- package/dist/memory-runtime.js +12 -7
- package/dist/plugin-runtime.d.ts +1 -1
- package/dist/plugin-runtime.js +10 -3
- package/dist/types.d.ts +5 -0
- package/docs/README.md +4 -4
- package/docs/TLS_configuration.md +17 -17
- package/docs/architecture-decisions/adr-004-sidecar-over-native-ts.md +1 -1
- package/docs/architecture.md +8 -8
- package/docs/configuration.md +15 -15
- package/docs/contributing.md +5 -5
- package/docs/dependencies.md +1 -1
- package/docs/development.md +9 -9
- package/docs/embedding-profiles.md +13 -11
- package/docs/features.md +6 -6
- package/docs/install.md +19 -19
- package/docs/installation.md +33 -25
- package/docs/mTLS_configuration.md +2 -2
- package/docs/models.md +3 -3
- package/docs/performance-and-tuning.md +5 -5
- package/docs/problem.md +1 -1
- package/docs/security.md +4 -4
- package/docs/uninstall.md +5 -5
- package/openclaw.plugin.json +7 -2
- package/package.json +2 -2
|
@@ -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;
|
package/dist/libravdb-client.js
CHANGED
|
@@ -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
|
|
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;
|
package/dist/manifest.js
ADDED
|
@@ -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();
|
package/dist/markdown-ingest.js
CHANGED
|
@@ -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);
|
package/dist/memory-runtime.js
CHANGED
|
@@ -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
|
-
|
|
79
|
-
|
|
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,
|
|
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,
|
|
205
|
+
endLine: Math.max(1, effectiveText.split("\n").length),
|
|
201
206
|
score: item.score,
|
|
202
|
-
snippet:
|
|
207
|
+
snippet: effectiveText,
|
|
203
208
|
source: collection.startsWith("session:") || collection.startsWith("session_") ? "sessions" : "memory",
|
|
204
209
|
citation: `${collection}:${item.id}`,
|
|
205
210
|
};
|
package/dist/plugin-runtime.d.ts
CHANGED
|
@@ -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 =
|
|
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];
|
package/dist/plugin-runtime.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
22
|
-
| `grpcEndpointTlsCa` | string | — | Path to a CA certificate PEM file. Required only when the
|
|
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
|
|
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
|
|
33
|
+
### Local vector service (default)
|
|
34
34
|
|
|
35
|
-
The
|
|
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
|
|
53
|
+
### Remote vector service with a trusted certificate
|
|
54
54
|
|
|
55
|
-
The
|
|
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
|
|
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
|
|
65
|
+
### Remote vector service with a self-signed or private CA certificate
|
|
66
66
|
|
|
67
|
-
The
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
109
|
-
| `x509: certificate signed by unknown authority` | The
|
|
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
|
|
9
|
+
Implement the memory engine as a Go vector service with a narrow gRPC transport boundary.
|
|
10
10
|
|
|
11
11
|
## Alternatives Considered
|
|
12
12
|
|
package/docs/architecture.md
CHANGED
|
@@ -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
|
|
13
|
+
- a Go vector service that owns storage, retrieval, and compaction
|
|
14
14
|
|
|
15
|
-
The plugin keeps the host integration light and stable. 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
|
|
25
|
+
Runtime["Plugin runtime\nlazy vector service connect + RPC"]
|
|
26
26
|
MPS["memoryPromptSection\ncapability header"]
|
|
27
|
-
Sidecar["Go
|
|
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
|
|
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
|
|
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
|
|
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
|
|
96
|
+
In short, the plugin owns the lifecycle contract, and the vector service owns the
|
|
97
97
|
heavy lifting.
|
package/docs/configuration.md
CHANGED
|
@@ -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
|
|
38
|
-
| `"tls"` | Daemon has TLS enabled on loopback or unix socket (rare; use when the
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
54
|
+
"grpcEndpoint": "tcp:yourvector service.internal:50051",
|
|
55
55
|
"grpcEndpointTlsCa": "/etc/certs/ca.pem"
|
|
56
56
|
}
|
|
57
57
|
```
|
|
58
|
-
The
|
|
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
|
|
62
|
-
When the
|
|
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:
|
|
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
|
|
75
|
-
If the
|
|
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 | `
|
|
92
|
-
| `embeddingRuntimePath` | string | — | Path to ONNX runtime library visible to the
|
|
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 |
|