@xdarkicex/openclaw-memory-libravdb 1.10.1-beta.6 → 1.10.3

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 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,6 +181,33 @@ 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
 
184
+ ### PII Scrubbing & Hard Constraint Rules
185
+
186
+ Agent replies are scanned for forbidden keywords before dispatch. Rules are stored
187
+ locally and enforced through two layers:
188
+
189
+ **Agent tools for rules:**
190
+ - `set_rule(rule, keywords, priority)` — create a rule with comma-separated keywords for reply scanning. Max 20 rules.
191
+ - `get_rule(rule_id)` — read a specific rule.
192
+ - `list_rules()` — list all rules sorted by priority.
193
+ - `delete_rule(rule_id)` — remove a rule.
194
+
195
+ **Dual enforcement layers:**
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").
198
+
199
+ **Example:**
200
+ ```
201
+ Rule: "Never reveal my employer"
202
+ Keywords: "acme corp, acmecorp, acme.com"
203
+ ```
204
+
205
+ If the agent's reply contains any of those keywords, it's blocked and replaced
206
+ with "I cannot answer that." — regardless of what the model chose to say.
207
+
208
+ **Storage:** persisted to `~/.openclaw/cache/libravdb/rules.json`. Survives
209
+ gateway restarts and plugin updates.
210
+
183
211
  ### Technical Architecture
184
212
 
185
213
  - **Unified Cognitive Scoring** — mathematically blends cosine similarity with frequency, recency, authored salience, and cognitive authority composite weights (`ω(c)`).
@@ -303,6 +331,30 @@ If you want to run multiple distinct agents (e.g., a "research-agent" and a "cod
303
331
  }
304
332
  ```
305
333
 
334
+ **Per-agent routing on a single host:** If you run multiple agents through one
335
+ OpenClaw instance, use `tenantIdByAgent` to map each agent to its own tenant:
336
+
337
+ ```json
338
+ {
339
+ "plugins": {
340
+ "entries": {
341
+ "libravdb-memory": {
342
+ "enabled": true,
343
+ "config": {
344
+ "tenantIdByAgent": {
345
+ "jarvis": "jarvis",
346
+ "gideon": "gideon",
347
+ "steel": "steel"
348
+ }
349
+ }
350
+ }
351
+ }
352
+ }
353
+ }
354
+ ```
355
+
356
+ Unlisted agents fall through to `tenantId` → `userId`. Zero daemon changes required.
357
+
306
358
  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.
307
359
 
308
360
  ### Directory Structure
@@ -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();
@@ -35005,6 +35009,7 @@ var LibravDBClient = class {
35005
35009
  legacyProbeTimeoutMs;
35006
35010
  nonceHex;
35007
35011
  closed = false;
35012
+ tenantKey;
35008
35013
  constructor(options = {}) {
35009
35014
  this.secret = options.secret ?? loadSecretFromEnv();
35010
35015
  const rawEndpoint = resolveClientEndpoint(options.endpoint);
@@ -35038,14 +35043,14 @@ var LibravDBClient = class {
35038
35043
  bootstrap: () => self.bootstrapHandshake(),
35039
35044
  rpcMutex
35040
35045
  });
35046
+ this.tenantKey = options.tenantKey;
35041
35047
  const interceptors = [];
35042
- if (options.tenantKey) {
35043
- const tenantKey = options.tenantKey;
35044
- interceptors.push((next) => async (req) => {
35045
- req.header.set("libravdb-tenant-key", tenantKey);
35046
- return next(req);
35047
- });
35048
- }
35048
+ interceptors.push((next) => async (req) => {
35049
+ if (self.tenantKey) {
35050
+ req.header.set("libravdb-tenant-key", self.tenantKey);
35051
+ }
35052
+ return next(req);
35053
+ });
35049
35054
  interceptors.push(authInterceptor);
35050
35055
  const transport = createGrpcTransport({
35051
35056
  baseUrl: targetUrl,
@@ -35090,6 +35095,11 @@ var LibravDBClient = class {
35090
35095
  throw new Error("LibravDB client is closed");
35091
35096
  }
35092
35097
  }
35098
+ /** Update the tenant key for subsequent gRPC calls. Thread-safe —
35099
+ * the interceptor reads this.tenantKey on every request. */
35100
+ setTenantKey(key) {
35101
+ this.tenantKey = key;
35102
+ }
35093
35103
  // ── Session lifecycle ────────────────────────────────────────────
35094
35104
  async health(req = {}) {
35095
35105
  this.guardOpen();
@@ -35650,9 +35660,19 @@ function register(api) {
35650
35660
  }
35651
35661
  });
35652
35662
  api.on("before_prompt_build", async (_event, ctx) => {
35653
- const sessionId = ctx?.sessionId;
35654
- const trigger = ctx?.trigger;
35663
+ const c = ctx;
35664
+ const sessionId = c?.sessionId;
35665
+ const trigger = c?.trigger;
35655
35666
  if (sessionId) setSessionTrigger(sessionId, trigger);
35667
+ if (runtimeOrNull) {
35668
+ const agentId = c?.agentId;
35669
+ const tenantKey = resolveTenantKey(cfg, agentId);
35670
+ try {
35671
+ const client = await runtimeOrNull.getClient();
35672
+ client.setTenantKey(tenantKey);
35673
+ } catch {
35674
+ }
35675
+ }
35656
35676
  });
35657
35677
  const MULTI_SPEAKER_PROVIDERS = /* @__PURE__ */ new Set([
35658
35678
  "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>;
@@ -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
- 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
- }
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/types.d.ts CHANGED
@@ -7,6 +7,9 @@ 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>;
10
13
  /** Stable identity for cross-session durable memory. When set, all sessions
11
14
  * share memories under user:{userId}. When unset, the plugin auto-derives
12
15
  * identity from the OS and persists it to the identity file. */
@@ -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.1-beta.6",
5
+ "version": "1.10.3",
6
6
  "kind": [
7
7
  "memory",
8
8
  "context-engine"
@@ -81,6 +81,11 @@
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
+ },
84
89
  "userId": {
85
90
  "type": "string",
86
91
  "description": "Stable identity for cross-session durable memory. When set, sessions share memories under user:{userId}."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xdarkicex/openclaw-memory-libravdb",
3
- "version": "1.10.1-beta.6",
3
+ "version": "1.10.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",