@xdarkicex/openclaw-memory-libravdb 1.10.1 → 1.10.4
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 +34 -5
- package/dist/identity.d.ts +1 -1
- package/dist/identity.js +7 -1
- package/dist/index.js +39 -13
- package/dist/libravdb-client.d.ts +4 -0
- package/dist/libravdb-client.js +13 -7
- package/dist/rules.d.ts +1 -0
- package/dist/rules.js +5 -2
- package/dist/types.d.ts +5 -0
- package/openclaw.plugin.json +12 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -160,6 +160,7 @@ If your service runs elsewhere, set `sidecarPath`:
|
|
|
160
160
|
- **Memory-mapped embedding cache.** Frequently embedded text is cached in a file-backed mmap region that survives daemon restarts. Cold starts are faster, repeat queries are instant.
|
|
161
161
|
- **Pluggable summarization backend.** The memory kernel's extractive summarization can replace LLM-based compaction — zero tokens burned on summarization.
|
|
162
162
|
- **Local-first inference.** GGUF, ONNX, or remote embedding backends. Hardware-native acceleration on Apple Silicon and NVIDIA. No cloud required.
|
|
163
|
+
- **PII scrubbing & hard constraint rules.** Set rules with keywords — the rules engine scans every reply before dispatch. If a rule keyword is found, the reply is blocked and replaced. No LLM overhead, no latency, can't be bypassed. Names, employers, locations, API keys — anything you don't want leaking gets enforced at the reply layer, not the prompt layer.
|
|
163
164
|
- **Operational CLI.** `libravdbd status`, `health`, `search`, `tenant evict`, `migrate` — live observability and management without interrupting active sessions.
|
|
164
165
|
|
|
165
166
|
### Identity Tracking & User Cards
|
|
@@ -180,10 +181,10 @@ The daemon tracks **who you are** — not just what you said. Every speaker the
|
|
|
180
181
|
- Cards participate in the causal graph (`memory_kind: "identity"`) — identity patterns detected by the cognitive scheduler at macro scale
|
|
181
182
|
- `PredictiveContext` seeds the card node — BFS surfaces causally connected memories alongside identity
|
|
182
183
|
|
|
183
|
-
### Hard Constraint Rules
|
|
184
|
+
### PII Scrubbing & Hard Constraint Rules
|
|
184
185
|
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
Agent replies are scanned for forbidden keywords before dispatch. Rules are stored
|
|
187
|
+
locally and enforced through two layers:
|
|
187
188
|
|
|
188
189
|
**Agent tools for rules:**
|
|
189
190
|
- `set_rule(rule, keywords, priority)` — create a rule with comma-separated keywords for reply scanning. Max 20 rules.
|
|
@@ -192,8 +193,8 @@ persisted across sessions, and enforced through two complementary mechanisms:
|
|
|
192
193
|
- `delete_rule(rule_id)` — remove a rule.
|
|
193
194
|
|
|
194
195
|
**Dual enforcement layers:**
|
|
195
|
-
- **
|
|
196
|
-
- **
|
|
196
|
+
- **Reply scan (hard enforcement)** — the `before_agent_reply` hook runs `scanReply` on every agent response. If any rule keyword is found anywhere in the reply text, the response is blocked and replaced with "I cannot answer that." This happens at the dispatch layer — the model can't override it, hallucinate around it, or leak PII. Substring match, case-insensitive, zero LLM overhead.
|
|
197
|
+
- **System prompt (behavioral guidance)** — rules are injected at `prependSystemContext` level (AGENTS.md equivalent) so the model follows behavioral constraints ("always use TypeScript", "never delete files without asking").
|
|
197
198
|
|
|
198
199
|
**Example:**
|
|
199
200
|
```
|
|
@@ -204,6 +205,8 @@ Keywords: "acme corp, acmecorp, acme.com"
|
|
|
204
205
|
If the agent's reply contains any of those keywords, it's blocked and replaced
|
|
205
206
|
with "I cannot answer that." — regardless of what the model chose to say.
|
|
206
207
|
|
|
208
|
+
**Config:** `maxRules` sets the cap (default 20, set to 0 to disable rules entirely).
|
|
209
|
+
|
|
207
210
|
**Storage:** persisted to `~/.openclaw/cache/libravdb/rules.json`. Survives
|
|
208
211
|
gateway restarts and plugin updates.
|
|
209
212
|
|
|
@@ -306,6 +309,8 @@ All keys are optional. For the full reference, see [Configuration](./docs/config
|
|
|
306
309
|
| `onnxDevice` | string | `cpu` | ONNX execution provider; `cpu` is the default; `auto` lets libravdbd auto-detect |
|
|
307
310
|
| `userId` | string | auto-derived | Stable identity for cross-session durable memory |
|
|
308
311
|
| `tenantId` | string | auto-derived | Multi-tenant identifier. Resolved as `cfg.tenantId` > `LIBRAVDB_AGENT_ID` env > `userId`. Isolates the agent to a dedicated `.libravdb` file. |
|
|
312
|
+
| `tenantIdByAgent` | object | — | Per-agent tenant map. `{"agent1": "t1"}`. Unlisted agents fall through to `tenantId` → `userId`. |
|
|
313
|
+
| `maxRules` | number | 20 | Max hard constraint rules. Set to 0 to disable rules entirely. |
|
|
309
314
|
| `crossSessionRecall` | boolean | `true` | When `false`, only session-scoped memories are retrieved |
|
|
310
315
|
| `compactSessionTokenBudget` | number | `2000` | Auto-compaction token threshold; `0` disables |
|
|
311
316
|
|
|
@@ -330,6 +335,30 @@ If you want to run multiple distinct agents (e.g., a "research-agent" and a "cod
|
|
|
330
335
|
}
|
|
331
336
|
```
|
|
332
337
|
|
|
338
|
+
**Per-agent routing on a single host:** If you run multiple agents through one
|
|
339
|
+
OpenClaw instance, use `tenantIdByAgent` to map each agent to its own tenant:
|
|
340
|
+
|
|
341
|
+
```json
|
|
342
|
+
{
|
|
343
|
+
"plugins": {
|
|
344
|
+
"entries": {
|
|
345
|
+
"libravdb-memory": {
|
|
346
|
+
"enabled": true,
|
|
347
|
+
"config": {
|
|
348
|
+
"tenantIdByAgent": {
|
|
349
|
+
"jarvis": "jarvis",
|
|
350
|
+
"gideon": "gideon",
|
|
351
|
+
"steel": "steel"
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Unlisted agents fall through to `tenantId` → `userId`. Zero daemon changes required.
|
|
361
|
+
|
|
333
362
|
The memory kernel will seamlessly route the agent's requests to a dedicated, isolated vector database file. It manages all tenant instances efficiently within a single process and automatically shares a centralized, memory-mapped embedding cache to keep hardware usage incredibly low.
|
|
334
363
|
|
|
335
364
|
### Directory Structure
|
package/dist/identity.d.ts
CHANGED
|
@@ -21,4 +21,4 @@ export declare function resolveIdentity(params: {
|
|
|
21
21
|
* 2. LIBRAVDB_AGENT_ID env var (container/CI override)
|
|
22
22
|
* 3. Fall back to resolved userId (existing identity system)
|
|
23
23
|
*/
|
|
24
|
-
export declare function resolveTenantKey(cfg: PluginConfig): string;
|
|
24
|
+
export declare function resolveTenantKey(cfg: PluginConfig, agentId?: string): string;
|
package/dist/identity.js
CHANGED
|
@@ -126,7 +126,13 @@ export function resolveIdentity(params) {
|
|
|
126
126
|
* 2. LIBRAVDB_AGENT_ID env var (container/CI override)
|
|
127
127
|
* 3. Fall back to resolved userId (existing identity system)
|
|
128
128
|
*/
|
|
129
|
-
export function resolveTenantKey(cfg) {
|
|
129
|
+
export function resolveTenantKey(cfg, agentId) {
|
|
130
|
+
// Per-agent override (highest priority).
|
|
131
|
+
if (agentId && cfg.tenantIdByAgent) {
|
|
132
|
+
const byAgent = cfg.tenantIdByAgent[agentId]?.trim();
|
|
133
|
+
if (byAgent)
|
|
134
|
+
return byAgent;
|
|
135
|
+
}
|
|
130
136
|
const explicit = cfg.tenantId?.trim();
|
|
131
137
|
if (explicit)
|
|
132
138
|
return explicit;
|
package/dist/index.js
CHANGED
|
@@ -16900,7 +16900,11 @@ function resolveIdentity(params) {
|
|
|
16900
16900
|
}
|
|
16901
16901
|
return { userId: autoId, source: "auto" };
|
|
16902
16902
|
}
|
|
16903
|
-
function resolveTenantKey(cfg) {
|
|
16903
|
+
function resolveTenantKey(cfg, agentId) {
|
|
16904
|
+
if (agentId && cfg.tenantIdByAgent) {
|
|
16905
|
+
const byAgent = cfg.tenantIdByAgent[agentId]?.trim();
|
|
16906
|
+
if (byAgent) return byAgent;
|
|
16907
|
+
}
|
|
16904
16908
|
const explicit = cfg.tenantId?.trim();
|
|
16905
16909
|
if (explicit) return explicit;
|
|
16906
16910
|
const envId = process.env.LIBRAVDB_AGENT_ID?.trim();
|
|
@@ -18432,7 +18436,10 @@ import { join as join3 } from "node:path";
|
|
|
18432
18436
|
// src/rules.ts
|
|
18433
18437
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
18434
18438
|
import { dirname as dirname2 } from "node:path";
|
|
18435
|
-
var
|
|
18439
|
+
var maxRules = 20;
|
|
18440
|
+
function setMaxRules(n) {
|
|
18441
|
+
maxRules = Math.max(0, n);
|
|
18442
|
+
}
|
|
18436
18443
|
function jsonResult(details) {
|
|
18437
18444
|
return { content: [{ type: "text", text: JSON.stringify(details, null, 2) }], details };
|
|
18438
18445
|
}
|
|
@@ -18471,7 +18478,7 @@ function getRule(id) {
|
|
|
18471
18478
|
}
|
|
18472
18479
|
function setRule(ruleText, keywords, priority) {
|
|
18473
18480
|
let replaced = false;
|
|
18474
|
-
if (rules.length >=
|
|
18481
|
+
if (maxRules > 0 && rules.length >= maxRules) {
|
|
18475
18482
|
let minIdx = 0;
|
|
18476
18483
|
for (let i = 1; i < rules.length; i++) {
|
|
18477
18484
|
if (rules[i].priority < rules[minIdx].priority) minIdx = i;
|
|
@@ -35005,6 +35012,7 @@ var LibravDBClient = class {
|
|
|
35005
35012
|
legacyProbeTimeoutMs;
|
|
35006
35013
|
nonceHex;
|
|
35007
35014
|
closed = false;
|
|
35015
|
+
tenantKey;
|
|
35008
35016
|
constructor(options = {}) {
|
|
35009
35017
|
this.secret = options.secret ?? loadSecretFromEnv();
|
|
35010
35018
|
const rawEndpoint = resolveClientEndpoint(options.endpoint);
|
|
@@ -35038,14 +35046,14 @@ var LibravDBClient = class {
|
|
|
35038
35046
|
bootstrap: () => self.bootstrapHandshake(),
|
|
35039
35047
|
rpcMutex
|
|
35040
35048
|
});
|
|
35049
|
+
this.tenantKey = options.tenantKey;
|
|
35041
35050
|
const interceptors = [];
|
|
35042
|
-
|
|
35043
|
-
|
|
35044
|
-
|
|
35045
|
-
|
|
35046
|
-
|
|
35047
|
-
|
|
35048
|
-
}
|
|
35051
|
+
interceptors.push((next) => async (req) => {
|
|
35052
|
+
if (self.tenantKey) {
|
|
35053
|
+
req.header.set("libravdb-tenant-key", self.tenantKey);
|
|
35054
|
+
}
|
|
35055
|
+
return next(req);
|
|
35056
|
+
});
|
|
35049
35057
|
interceptors.push(authInterceptor);
|
|
35050
35058
|
const transport = createGrpcTransport({
|
|
35051
35059
|
baseUrl: targetUrl,
|
|
@@ -35090,6 +35098,11 @@ var LibravDBClient = class {
|
|
|
35090
35098
|
throw new Error("LibravDB client is closed");
|
|
35091
35099
|
}
|
|
35092
35100
|
}
|
|
35101
|
+
/** Update the tenant key for subsequent gRPC calls. Thread-safe —
|
|
35102
|
+
* the interceptor reads this.tenantKey on every request. */
|
|
35103
|
+
setTenantKey(key) {
|
|
35104
|
+
this.tenantKey = key;
|
|
35105
|
+
}
|
|
35093
35106
|
// ── Session lifecycle ────────────────────────────────────────────
|
|
35094
35107
|
async health(req = {}) {
|
|
35095
35108
|
this.guardOpen();
|
|
@@ -35506,7 +35519,10 @@ function register(api) {
|
|
|
35506
35519
|
const runtimeOrNull = isLightweight ? null : createPluginRuntime(cfg, logger);
|
|
35507
35520
|
if (runtimeOrNull && !isLightweight) {
|
|
35508
35521
|
const cacheDir = api.cacheDir ?? process.env.OPENCLAW_CACHE_DIR ?? (process.env.HOME || process.env.USERPROFILE || "") + "/.openclaw/cache";
|
|
35509
|
-
if (cacheDir)
|
|
35522
|
+
if (cacheDir) {
|
|
35523
|
+
initRuleStore(cacheDir + "/libravdb", logger);
|
|
35524
|
+
if (cfg.maxRules !== void 0) setMaxRules(cfg.maxRules);
|
|
35525
|
+
}
|
|
35510
35526
|
}
|
|
35511
35527
|
registerMemoryCli(api, runtimeOrNull, cfg, logger);
|
|
35512
35528
|
const ownsMemorySlot = memSlot === MEMORY_ID;
|
|
@@ -35650,9 +35666,19 @@ function register(api) {
|
|
|
35650
35666
|
}
|
|
35651
35667
|
});
|
|
35652
35668
|
api.on("before_prompt_build", async (_event, ctx) => {
|
|
35653
|
-
const
|
|
35654
|
-
const
|
|
35669
|
+
const c = ctx;
|
|
35670
|
+
const sessionId = c?.sessionId;
|
|
35671
|
+
const trigger = c?.trigger;
|
|
35655
35672
|
if (sessionId) setSessionTrigger(sessionId, trigger);
|
|
35673
|
+
if (runtimeOrNull) {
|
|
35674
|
+
const agentId = c?.agentId;
|
|
35675
|
+
const tenantKey = resolveTenantKey(cfg, agentId);
|
|
35676
|
+
try {
|
|
35677
|
+
const client = await runtimeOrNull.getClient();
|
|
35678
|
+
client.setTenantKey(tenantKey);
|
|
35679
|
+
} catch {
|
|
35680
|
+
}
|
|
35681
|
+
}
|
|
35656
35682
|
});
|
|
35657
35683
|
const MULTI_SPEAKER_PROVIDERS = /* @__PURE__ */ new Set([
|
|
35658
35684
|
"discord",
|
|
@@ -35,9 +35,13 @@ export declare class LibravDBClient {
|
|
|
35
35
|
private readonly legacyProbeTimeoutMs;
|
|
36
36
|
private nonceHex;
|
|
37
37
|
private closed;
|
|
38
|
+
private tenantKey;
|
|
38
39
|
constructor(options?: LibravDBClientOptions);
|
|
39
40
|
bootstrapHandshake(): Promise<void>;
|
|
40
41
|
private guardOpen;
|
|
42
|
+
/** Update the tenant key for subsequent gRPC calls. Thread-safe —
|
|
43
|
+
* the interceptor reads this.tenantKey on every request. */
|
|
44
|
+
setTenantKey(key: string): void;
|
|
41
45
|
health(req?: PartialMessage<HealthRequest>): Promise<HealthResponse>;
|
|
42
46
|
status(req?: PartialMessage<MemoryStatusRequest>): Promise<MemoryStatusResponse>;
|
|
43
47
|
flush(req?: PartialMessage<FlushRequest>): Promise<FlushResponse>;
|
package/dist/libravdb-client.js
CHANGED
|
@@ -149,6 +149,7 @@ export class LibravDBClient {
|
|
|
149
149
|
legacyProbeTimeoutMs;
|
|
150
150
|
nonceHex;
|
|
151
151
|
closed = false;
|
|
152
|
+
tenantKey;
|
|
152
153
|
constructor(options = {}) {
|
|
153
154
|
this.secret = options.secret ?? loadSecretFromEnv();
|
|
154
155
|
const rawEndpoint = resolveClientEndpoint(options.endpoint);
|
|
@@ -180,14 +181,14 @@ export class LibravDBClient {
|
|
|
180
181
|
bootstrap: () => self.bootstrapHandshake(),
|
|
181
182
|
rpcMutex,
|
|
182
183
|
});
|
|
184
|
+
this.tenantKey = options.tenantKey;
|
|
183
185
|
const interceptors = [];
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
186
|
+
interceptors.push((next) => async (req) => {
|
|
187
|
+
if (self.tenantKey) {
|
|
188
|
+
req.header.set("libravdb-tenant-key", self.tenantKey);
|
|
189
|
+
}
|
|
190
|
+
return next(req);
|
|
191
|
+
});
|
|
191
192
|
interceptors.push(authInterceptor);
|
|
192
193
|
const transport = createGrpcTransport({
|
|
193
194
|
baseUrl: targetUrl,
|
|
@@ -231,6 +232,11 @@ export class LibravDBClient {
|
|
|
231
232
|
throw new Error("LibravDB client is closed");
|
|
232
233
|
}
|
|
233
234
|
}
|
|
235
|
+
/** Update the tenant key for subsequent gRPC calls. Thread-safe —
|
|
236
|
+
* the interceptor reads this.tenantKey on every request. */
|
|
237
|
+
setTenantKey(key) {
|
|
238
|
+
this.tenantKey = key;
|
|
239
|
+
}
|
|
234
240
|
// ── Session lifecycle ────────────────────────────────────────────
|
|
235
241
|
async health(req = {}) {
|
|
236
242
|
this.guardOpen();
|
package/dist/rules.d.ts
CHANGED
package/dist/rules.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
-
|
|
3
|
+
let maxRules = 20;
|
|
4
|
+
export function setMaxRules(n) {
|
|
5
|
+
maxRules = Math.max(0, n);
|
|
6
|
+
}
|
|
4
7
|
function jsonResult(details) {
|
|
5
8
|
return { content: [{ type: "text", text: JSON.stringify(details, null, 2) }], details };
|
|
6
9
|
}
|
|
@@ -41,7 +44,7 @@ export function getRule(id) {
|
|
|
41
44
|
}
|
|
42
45
|
export function setRule(ruleText, keywords, priority) {
|
|
43
46
|
let replaced = false;
|
|
44
|
-
if (rules.length >=
|
|
47
|
+
if (maxRules > 0 && rules.length >= maxRules) {
|
|
45
48
|
let minIdx = 0;
|
|
46
49
|
for (let i = 1; i < rules.length; i++) {
|
|
47
50
|
if (rules[i].priority < rules[minIdx].priority)
|
package/dist/types.d.ts
CHANGED
|
@@ -7,6 +7,11 @@ export interface PluginConfig {
|
|
|
7
7
|
* the plugin falls back to the auto-derived userId. Set different values per
|
|
8
8
|
* agent to isolate memory storage. */
|
|
9
9
|
tenantId?: string;
|
|
10
|
+
/** Per-agent tenant overrides. Maps agentId → tenantId. Agents not in this
|
|
11
|
+
* map fall through to tenantId → userId. Opt-in. */
|
|
12
|
+
tenantIdByAgent?: Record<string, string>;
|
|
13
|
+
/** Maximum number of hard constraint rules. Default 20. */
|
|
14
|
+
maxRules?: number;
|
|
10
15
|
/** Stable identity for cross-session durable memory. When set, all sessions
|
|
11
16
|
* share memories under user:{userId}. When unset, the plugin auto-derives
|
|
12
17
|
* identity from the OS and persists it to the identity file. */
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "libravdb-memory",
|
|
3
3
|
"name": "LibraVDB Memory",
|
|
4
4
|
"description": "Cognitive memory engine — causal graph reasoning, predictive context, identity tracking, and hybrid vector recall with back-door adjustment scoring",
|
|
5
|
-
"version": "1.10.
|
|
5
|
+
"version": "1.10.4",
|
|
6
6
|
"kind": [
|
|
7
7
|
"memory",
|
|
8
8
|
"context-engine"
|
|
@@ -81,6 +81,17 @@
|
|
|
81
81
|
"type": "string",
|
|
82
82
|
"description": "Stable tenant identifier for multi-agent deployments. When set, the daemon routes this plugin instance to an isolated vector database. When unset, the plugin falls back to the auto-derived userId. Set different values per agent to isolate memory storage."
|
|
83
83
|
},
|
|
84
|
+
"tenantIdByAgent": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"additionalProperties": { "type": "string" },
|
|
87
|
+
"description": "Per-agent tenant overrides. Maps agentId to tenantId. Agents not listed fall through to tenantId -> userId. Example: {\"jarvis\": \"jarvis\", \"gideon\": \"gideon\"}."
|
|
88
|
+
},
|
|
89
|
+
"maxRules": {
|
|
90
|
+
"type": "number",
|
|
91
|
+
"default": 20,
|
|
92
|
+
"minimum": 0,
|
|
93
|
+
"description": "Maximum hard constraint rules. 0 disables rules. Default 20."
|
|
94
|
+
},
|
|
84
95
|
"userId": {
|
|
85
96
|
"type": "string",
|
|
86
97
|
"description": "Stable identity for cross-session durable memory. When set, sessions share memories under user:{userId}."
|