@xdarkicex/openclaw-memory-libravdb 1.10.13 → 1.10.14-beta.1

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
@@ -1,21 +1,69 @@
1
- # ♎ LibraVDB - Memory and Context Management
1
+ # ♎ LibraVDB Cognitive Memory Engine for OpenClaw
2
2
 
3
3
  <div align="center">
4
- <img src="./docs/assets/libravdb-logo.svg" alt="LibraVDB" width="640">
4
+ <img src="./docs/assets/libravdb-logo.svg" alt="LibraVDB" width="480">
5
+
6
+ <p><strong>Local-first memory kernel with causal graph reasoning, identity tracking, and hard PII enforcement.</strong><br/>
7
+ <em>768-dim vector search • 6-tier cognitive classification • Zero-cloud guarantee</em></p>
8
+
9
+ <p>
10
+ <a href="https://www.npmjs.com/package/@xdarkicex/openclaw-memory-libravdb"><img src="https://img.shields.io/npm/v/%40xdarkicex%2Fopenclaw-memory-libravdb?label=release&color=5B21B6" alt="npm"></a>
11
+ <a href="https://github.com/xDarkicex/openclaw-memory-libravdb"><img src="https://img.shields.io/github/stars/xDarkicex/openclaw-memory-libravdb?style=social" alt="stars"></a>
12
+ <a href="https://discord.gg/DWn4BpRQAS"><img src="https://img.shields.io/badge/Discord-LibraVDB-5865F2?logo=discord&logoColor=white" alt="Discord"></a>
13
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License"></a>
14
+ <br>
15
+ <a href="https://github.com/xDarkicex/libravdbd"><img src="https://img.shields.io/badge/daemon-v1.9.3-00ADD8?logo=go&logoColor=white" alt="Daemon"></a>
16
+ <img src="https://img.shields.io/badge/TypeScript-5.x-3178C6?logo=typescript&logoColor=white" alt="TS">
17
+ <img src="https://img.shields.io/badge/OpenClaw-2026.3%2B-111827" alt="OC">
18
+ <img src="https://img.shields.io/badge/K8s-ready-326CE5?logo=kubernetes&logoColor=white" alt="k8s">
19
+ </p>
5
20
  </div>
6
21
 
7
- <div align="center">
8
- <a href="https://github.com/xDarkicex/libravdbd"><img src="https://img.shields.io/badge/Go-1.25%2B-00ADD8?logo=go&logoColor=white" alt="Go 1.25+"></a>
9
- <a href="./package.json"><img src="https://img.shields.io/badge/TypeScript-5.x-3178C6?logo=typescript&logoColor=white" alt="TypeScript 5.x"></a>
10
- <a href="./openclaw.plugin.json"><img src="https://img.shields.io/badge/OpenClaw-memory%20plugin-111827" alt="OpenClaw memory plugin"></a>
11
- <a href="https://www.npmjs.com/package/@xdarkicex/openclaw-memory-libravdb"><img src="https://img.shields.io/npm/v/%40xdarkicex%2Fopenclaw-memory-libravdb?label=release&color=5B21B6" alt="Release"></a>
12
- <a href="https://discord.gg/DWn4BpRQAS"><img src="https://img.shields.io/badge/Discord-LibraVDB-5865F2?logo=discord&logoColor=white" alt="LibraVDB Discord"></a>
13
- </div>
22
+ ```mermaid
23
+ graph TB
24
+ OC[OpenClaw Agent] -->|gRPC| PLUGIN[Plugin: context-engine + tools]
25
+ PLUGIN -->|gRPC + mTLS| DAEMON[libravdbd Memory Kernel]
26
+ DAEMON --> EMBED[GGUF / ONNX / Remote]
27
+ DAEMON --> COG[Causal Graph: BOSS + TE/LK + SAT]
28
+ EMBED --> DB[(Vector DB)]
29
+ COG --> GRAPH[(Causal DAG)]
30
+ PLUGIN --> AGENT[LLM Agent]
31
+ ```
32
+
33
+ ## 🚀 Quick Start
14
34
 
15
- `@xdarkicex/openclaw-memory-libravdb` is a local-first OpenClaw memory plugin
16
- backed by the `libravdbd` memory kernel. It replaces the lightweight default memory
17
- path with scoped session, user, and global memory; continuity-aware prompt
18
- assembly; durable recall; and kernel-owned compaction.
35
+ ```bash
36
+ # 1. Install daemon (pick your OS)
37
+ brew tap xDarkicex/homebrew-openclaw-libravdb-memory && brew install libravdbd && brew services start libravdbd # macOS
38
+ # apt install libravdbd # Linux
39
+ # See Arch Linux instructions below # Arch (AUR postponed)
40
+
41
+ # 2. Install plugin
42
+ openclaw plugins install @xdarkicex/openclaw-memory-libravdb
43
+
44
+ # 3. Verify
45
+ openclaw memory status
46
+ ```
47
+
48
+ > ⚡ **Done.** Your agent has persistent vector memory, identity tracking, and causal graph reasoning.
49
+
50
+ <details>
51
+ <summary>🐳 Docker (30 seconds) · ☸️ Kubernetes</summary>
52
+
53
+ ```bash
54
+ # Docker
55
+ cd deploy && docker compose up -d
56
+
57
+ # K8s with Helm
58
+ helm install libravdbd ./deploy/helm/libravdbd
59
+
60
+ # K8s with mTLS
61
+ helm install libravdbd ./deploy/helm/libravdbd \
62
+ --set tls.enabled=true --set tls.cert=$(base64 -i server.crt) --set tls.key=$(base64 -i server.key)
63
+ ```
64
+
65
+ Terraform modules for EKS/GKE/AKS: [`deploy/terraform/`](deploy/terraform/)
66
+ </details>
19
67
 
20
68
  [Install](./docs/install.md) · [Full installation reference](./docs/installation.md) · [Architecture](./docs/architecture.md) · [Security](./docs/security.md) · [Performance and tuning](./docs/performance-and-tuning.md) · [Contributing](./docs/contributing.md)
21
69
 
@@ -57,10 +105,18 @@ sudo apt install libravdbd
57
105
  systemctl --user enable --now libravdbd
58
106
  ```
59
107
 
60
- **Linux (AUR)**
108
+ **Linux (Arch — custom pacman repo)**
109
+
110
+ AUR distribution is temporarily postponed due to the June 2026 AUR supply chain incident (new account registrations frozen). Use the official pacman repository instead:
61
111
 
62
112
  ```bash
63
- yay -S libravdbd-bin
113
+ # Add the repo to /etc/pacman.conf:
114
+ # [libravdbd]
115
+ # SigLevel = Optional
116
+ # Server = https://raw.githubusercontent.com/xDarkicex/aur-libravdbd-bin/main/$arch
117
+
118
+ echo -e '\n[libravdbd]\nSigLevel = Optional\nServer = https://raw.githubusercontent.com/xDarkicex/aur-libravdbd-bin/main/$arch' | sudo tee -a /etc/pacman.conf
119
+ sudo pacman -Sy libravdbd-bin
64
120
  systemctl --user enable --now libravdbd
65
121
  ```
66
122
 
@@ -142,6 +198,41 @@ If your service runs elsewhere, set `sidecarPath`:
142
198
  }
143
199
  ```
144
200
 
201
+ ## Docker & Kubernetes Deployment
202
+
203
+ The daemon is K8-ready. Deployment files live in [`deploy/`](deploy/):
204
+
205
+ | File | Use |
206
+ |------|-----|
207
+ | [`Dockerfile`](deploy/Dockerfile) | Production image (Alpine) — pulls the binary from GitHub releases |
208
+ | [`docker-compose.yml`](deploy/docker-compose.yml) | Local dev / single-server: `docker compose up -d` |
209
+ | [`helm/libravdbd/`](deploy/helm/libravdbd) | Full Helm chart — ConfigMap, PVC, Service, Deployment, mTLS support |
210
+
211
+ ```bash
212
+ # Quick start with Docker
213
+ cd deploy && docker compose up -d
214
+
215
+ # K8s with Helm
216
+ helm install libravdbd ./deploy/helm/libravdbd
217
+
218
+ # K8s with mTLS
219
+ helm install libravdbd ./deploy/helm/libravdbd \
220
+ --set tls.enabled=true \
221
+ --set tls.cert=$(base64 -i server.crt) \
222
+ --set tls.key=$(base64 -i server.key)
223
+ ```
224
+
225
+ The image pulls pre-built binaries from [GitHub Releases](https://github.com/zephyr-systems/libravdbd/releases) — no build step needed. Supports `linux/amd64` and `linux/arm64`.
226
+
227
+ **Terraform** modules for cloud deployment live in [`deploy/terraform/`](deploy/terraform/):
228
+
229
+ | Provider | Directory | Use |
230
+ |----------|-----------|-----|
231
+ | AWS EKS | [`eks/`](deploy/terraform/eks) | `terraform apply -var cluster_name=my-cluster` |
232
+ | GCP GKE | [`gke/`](deploy/terraform/gke) | `terraform apply -var project=my-project -var cluster_name=my-cluster` |
233
+ | Azure AKS | [`aks/`](deploy/terraform/aks) | `terraform apply -var resource_group=my-rg -var cluster_name=my-cluster` |
234
+ | Standalone VM | [`vm/`](deploy/terraform/vm) | EC2 + Docker, no k8s required |
235
+
145
236
  ## Highlights
146
237
 
147
238
  ### Why LibraVDB over other memory plugins
@@ -181,6 +272,51 @@ The daemon tracks **who you are** — not just what you said. Every speaker the
181
272
  - Cards participate in the causal graph (`memory_kind: "identity"`) — identity patterns detected by the cognitive scheduler at macro scale
182
273
  - `PredictiveContext` seeds the card node — BFS surfaces causally connected memories alongside identity
183
274
 
275
+ ### Bot Persona
276
+
277
+ Define who the bot IS — personality, tone, boundaries, and behavior. The persona
278
+ is stored as a user card (`__bot_persona__`) and injected at the top of every
279
+ session as `<bot_persona>`.
280
+
281
+ **Agent tools:**
282
+ - `set_persona(persona)` — define or update the bot's personality. Prose format.
283
+ Empty string deletes the persona.
284
+ - `get_persona()` — read the current persona.
285
+
286
+ **Example personas:**
287
+
288
+ ```prose
289
+ # Professional
290
+ You are a senior software architect. You speak precisely, use correct
291
+ terminology, and default to TypeScript patterns. You never apologize for
292
+ being thorough. When asked a question, you give the answer plus the
293
+ reasoning behind it.
294
+ ```
295
+
296
+ ```prose
297
+ # Creative
298
+ You are a playful creative assistant. You speak casually, use emoji freely,
299
+ and default to brainstorming mode. You always offer 3-5 wild ideas before
300
+ narrowing down. You never say "I can't" — you say "here's a different approach."
301
+ ```
302
+
303
+ ```prose
304
+ # Minimalist
305
+ You are a terse, no-nonsense assistant. You answer in as few words as
306
+ possible. No greetings, no sign-offs, no fluff. Just the answer.
307
+ If you don't know, you say "I don't know" — nothing else.
308
+ ```
309
+
310
+ ```prose
311
+ # Character
312
+ You are a 1920s detective. You call everyone "pal" or "doll." You use
313
+ noir slang and describe things like a hardboiled novel. You're cynical
314
+ but secretly have a heart of gold.
315
+ ```
316
+
317
+ The persona is injected **before** the user card and rules at session start,
318
+ so it acts as the foundational identity layer for the agent.
319
+
184
320
  ### PII Scrubbing & Hard Constraint Rules
185
321
 
186
322
  Agent replies are scanned for forbidden keywords before dispatch. Rules are stored
@@ -289,6 +425,13 @@ libravdbd search --tenant <key> --session <id> -k 10 "query"
289
425
  # Tenant management
290
426
  libravdbd tenant evict <key> # force-close a tenant DB
291
427
  libravdbd migrate # run pending DB migrations
428
+
429
+ # Database export (libravdbd v1.10.0+)
430
+ libravdbd export # export all collections as JSON to stdout
431
+ libravdbd export --tenant <key> # export a specific tenant's data
432
+ libravdbd export --collection <name># export a single collection
433
+ libravdbd export --format jsonl # newline-delimited JSON (one record per line)
434
+ libravdbd export --output out.json # write to file instead of stdout
292
435
  ```
293
436
 
294
437
  Use [Install](./docs/install.md) for service lifecycle commands and
@@ -611,3 +754,11 @@ service checkout. For the full source workflow, read [Development](./docs/develo
611
754
  - default macOS/Linux endpoint: `unix:$HOME/.libravdbd/run/libravdb.sock`
612
755
  - default Windows endpoint: `tcp:127.0.0.1:37421`
613
756
 
757
+
758
+ ---
759
+
760
+ <div align="center">
761
+ <sub>Built with ♎ by <a href="https://github.com/xDarkicex">xDarkicex</a> — local-first, privacy-first, forever free.</sub>
762
+
763
+ [![Star History Chart](https://api.star-history.com/svg?repos=xDarkicex/openclaw-memory-libravdb&type=Date)](https://star-history.com/#xDarkicex/openclaw-memory-libravdb&Date)
764
+ </div>
@@ -12,7 +12,7 @@ type OpenClawCompatibleMessage = {
12
12
  id?: string;
13
13
  [key: string]: unknown;
14
14
  };
15
- type OpenClawCompatiblePromptAuthority = "preassembly_may_overflow";
15
+ type OpenClawCompatiblePromptAuthority = "assembled" | "preassembly_may_overflow";
16
16
  type OpenClawCompatibleAssembleResult = {
17
17
  messages: OpenClawCompatibleMessage[];
18
18
  estimatedTokens: number;
@@ -31,7 +31,6 @@ type OpenClawCompatibleCompactResult = {
31
31
  reason?: string;
32
32
  result?: {
33
33
  summary?: string;
34
- summaryText?: string;
35
34
  firstKeptEntryId?: string;
36
35
  tokensBefore: number;
37
36
  tokensAfter?: number;
@@ -83,7 +82,7 @@ export declare function normalizeAssembleResult(result: {
83
82
  estimatedTokens?: number;
84
83
  systemPromptAddition?: string;
85
84
  debug?: AssembleContextInternalResponse["debug"];
86
- }, sourceMessages?: OpenClawCompatibleMessage[]): OpenClawCompatibleAssembleResult;
85
+ }, sourceMessages?: OpenClawCompatibleMessage[], promptAuthority?: OpenClawCompatiblePromptAuthority): OpenClawCompatibleAssembleResult;
87
86
  export declare function setSessionTrigger(sessionId: string, trigger: string | undefined): void;
88
87
  export declare function clearSessionTrigger(sessionId: string): void;
89
88
  export declare function consumeSubagentBudget(sessionKey: string, tokens: number): number;
@@ -107,7 +107,7 @@ function normalizeCompactResult(response, options = {}) {
107
107
  const threshold = options.threshold;
108
108
  const overBudget = threshold != null && tokensBefore >= threshold;
109
109
  const engineRefused = !didCompact && overBudget;
110
- const tokensAfter = didCompact && typeof response?.tokensAfter === "number" && response.tokensAfter > 0
110
+ const tokensAfter = didCompact && typeof response?.tokensAfter === "number" && response.tokensAfter >= 0
111
111
  ? response.tokensAfter
112
112
  : undefined;
113
113
  return {
@@ -117,8 +117,7 @@ function normalizeCompactResult(response, options = {}) {
117
117
  result: {
118
118
  tokensBefore,
119
119
  ...(tokensAfter != null ? { tokensAfter } : {}),
120
- ...(details.summaryMethod ? { summary: details.summaryMethod } : {}),
121
- ...(details.summaryText ? { summaryText: details.summaryText } : {}),
120
+ ...(details.summaryText ? { summary: details.summaryText } : {}),
122
121
  details: { ...details, ...(threshold != null ? { threshold } : {}) },
123
122
  },
124
123
  };
@@ -215,6 +214,27 @@ function findLastUserMessageIndex(messages) {
215
214
  }
216
215
  return -1;
217
216
  }
217
+ /**
218
+ * Selects one exact contiguous suffix from the OpenClaw transcript.
219
+ *
220
+ * The cut is moved backward to a user-message boundary so an assistant tool
221
+ * call and its tool results can never be separated by tail windowing. Message
222
+ * objects are returned untouched; daemon-normalized messages are never mapped
223
+ * back onto the provider transcript.
224
+ */
225
+ function selectTurnAlignedSourceSuffix(messages, maxMessages) {
226
+ if (messages.length === 0)
227
+ return [];
228
+ let start = Math.max(0, messages.length - Math.max(1, Math.floor(maxMessages)));
229
+ while (start > 0 && messages[start]?.role !== "user") {
230
+ start -= 1;
231
+ }
232
+ if (messages[start]?.role !== "user") {
233
+ const firstUserIndex = messages.findIndex((message) => message.role === "user");
234
+ start = firstUserIndex >= 0 ? firstUserIndex : 0;
235
+ }
236
+ return messages.slice(start);
237
+ }
218
238
  /**
219
239
  * Normalizes kernel content (string or block array) to a flat string.
220
240
  */
@@ -673,6 +693,43 @@ function trimMessagesToBudget(messages, tokenBudget) {
673
693
  }
674
694
  return [{ ...last, content: truncated }];
675
695
  }
696
+ /**
697
+ * Trims a compacted source projection only at user-turn boundaries.
698
+ *
699
+ * The newest user turn is mandatory, even if it alone exceeds the budget:
700
+ * dropping part of an in-flight tool-call/result sequence is more dangerous
701
+ * than surfacing a truthful overflow. Older turns are kept only as a
702
+ * contiguous suffix and only when their complete bundle fits.
703
+ */
704
+ function trimTurnAlignedSourceSuffixToBudget(messages, tokenBudget) {
705
+ if (messages.length === 0)
706
+ return [];
707
+ const userStarts = [];
708
+ for (let index = 0; index < messages.length; index += 1) {
709
+ if (messages[index]?.role === "user")
710
+ userStarts.push(index);
711
+ }
712
+ if (userStarts.length === 0) {
713
+ return messages;
714
+ }
715
+ const keptBundles = [];
716
+ let used = 0;
717
+ let bundleEnd = messages.length;
718
+ for (let index = userStarts.length - 1; index >= 0; index -= 1) {
719
+ const bundleStart = userStarts[index];
720
+ const bundle = messages.slice(bundleStart, bundleEnd);
721
+ const bundleCost = approximateMessagesTokens(bundle);
722
+ if (keptBundles.length > 0 && used + bundleCost > tokenBudget) {
723
+ break;
724
+ }
725
+ keptBundles.unshift(bundle);
726
+ used += bundleCost;
727
+ bundleEnd = bundleStart;
728
+ if (used >= tokenBudget)
729
+ break;
730
+ }
731
+ return keptBundles.flat();
732
+ }
676
733
  /**
677
734
  * Bounds after-turn messages for ingest, trimming if over max tokens.
678
735
  */
@@ -720,6 +777,39 @@ function enforceTokenBudgetInvariant(result, tokenBudget) {
720
777
  estimatedTokens: Math.min(effectiveBudget, systemPromptTokens + trimmedEstimate),
721
778
  };
722
779
  }
780
+ /**
781
+ * Enforces the model budget for a daemon-compacted projection without ever
782
+ * splitting the newest user/tool turn. The compacted system context and older
783
+ * complete turns yield budget before the live turn does.
784
+ */
785
+ function enforceCompactedProjectionBudgetInvariant(result, tokenBudget) {
786
+ if (typeof tokenBudget !== "number" || !Number.isFinite(tokenBudget) || tokenBudget <= 0) {
787
+ return result;
788
+ }
789
+ const effectiveBudget = resolveEffectiveAssembleBudget(Math.max(1, Math.floor(tokenBudget)));
790
+ const approximateTotal = approximateTokenCount(result.systemPromptAddition) +
791
+ approximateMessagesTokens(result.messages);
792
+ if (result.estimatedTokens <= effectiveBudget && approximateTotal <= effectiveBudget) {
793
+ return result;
794
+ }
795
+ const lastUserIndex = findLastUserMessageIndex(result.messages);
796
+ const mandatoryMessages = lastUserIndex >= 0
797
+ ? result.messages.slice(lastUserIndex)
798
+ : result.messages;
799
+ const mandatoryTokens = approximateMessagesTokens(mandatoryMessages);
800
+ const systemPromptBudget = Math.max(0, effectiveBudget - mandatoryTokens);
801
+ const systemPromptAddition = truncateSystemPromptAdditionToTokenBudget(result.systemPromptAddition, systemPromptBudget);
802
+ const systemPromptTokens = approximateTokenCount(systemPromptAddition);
803
+ const messageBudget = Math.max(mandatoryTokens, effectiveBudget - systemPromptTokens);
804
+ const messages = trimTurnAlignedSourceSuffixToBudget(result.messages, messageBudget);
805
+ const estimatedTokens = systemPromptTokens + approximateMessagesTokens(messages);
806
+ return {
807
+ ...result,
808
+ messages,
809
+ systemPromptAddition,
810
+ estimatedTokens,
811
+ };
812
+ }
723
813
  function buildBudgetFallbackContext(messages, tokenBudget) {
724
814
  const effectiveBudget = resolveEffectiveAssembleBudget(tokenBudget);
725
815
  const fallbackMessages = trimMessagesToBudget(messages.map((message) => ({ ...message })), effectiveBudget);
@@ -733,7 +823,11 @@ function buildBudgetFallbackContext(messages, tokenBudget) {
733
823
  const DAEMON_AUTHORED_CONTEXT_RE = /<authored_context\b[^>]*>([\s\S]*?)<\/authored_context>/gi;
734
824
  const DAEMON_AUTHORED_CONTEXT_GUIDANCE_RE = /^\s*Treat the authored entries below as active project rules and identity context\.?\s*$/i;
735
825
  const COMPACTED_SESSION_CONTEXT_RE = /<compacted_session_context\b([^>]*)>([\s\S]*?)<\/compacted_session_context>/gi;
826
+ const HAS_COMPACTED_SESSION_CONTEXT_RE = /<compacted_session_context\b/i;
736
827
  const COMPACTED_SESSION_RENDER_LEDGER_RE = /(?:^|\n)(?:Artifacts:|Constraints:|Open Next Steps:|Extracted context anchors:)(?:\n|$)/;
828
+ function hasCompactedSessionContext(text) {
829
+ return HAS_COMPACTED_SESSION_CONTEXT_RE.test(text);
830
+ }
737
831
  function sanitizeDaemonSystemPromptAddition(text) {
738
832
  return demoteDaemonAuthoredContextBlocks(sanitizeToolCallPatterns(canonicalizeCompactedSessionContextBlocks(text)));
739
833
  }
@@ -1170,11 +1264,11 @@ function ensureReplaySafeUserTurn(assembled, sourceMessages, logger, tokenBudget
1170
1264
  /**
1171
1265
  * Normalizes a compact result into the OpenClaw-compatible assemble result format.
1172
1266
  */
1173
- export function normalizeAssembleResult(result, sourceMessages) {
1174
- // The daemon's visibleMsgs is a filtered echo of args.Messages (toolResult
1175
- // stripped). Use sourceMessages directly they carry the full transcript
1176
- // with tool protocol intact. The daemon contributes memory context via
1177
- // systemPromptAddition, not message manipulation.
1267
+ export function normalizeAssembleResult(result, sourceMessages, promptAuthority = PROMPT_AUTHORITY_PREASSEMBLY_MAY_OVERFLOW) {
1268
+ // The daemon's visibleMsgs is a normalized projection that cannot preserve
1269
+ // provider tool protocol. Use only an exact source transcript projection.
1270
+ // The caller decides whether that is the full pre-compaction transcript or
1271
+ // the user-turn-aligned suffix owned by daemon compaction.
1178
1272
  const systemPromptAddition = typeof result.systemPromptAddition === "string"
1179
1273
  ? sanitizeDaemonSystemPromptAddition(result.systemPromptAddition)
1180
1274
  : "";
@@ -1182,7 +1276,7 @@ export function normalizeAssembleResult(result, sourceMessages) {
1182
1276
  messages: sourceMessages ?? [],
1183
1277
  estimatedTokens: typeof result.estimatedTokens === "number" ? result.estimatedTokens : 0,
1184
1278
  systemPromptAddition,
1185
- promptAuthority: PROMPT_AUTHORITY_PREASSEMBLY_MAY_OVERFLOW,
1279
+ promptAuthority,
1186
1280
  ...(result.debug != null ? { debug: result.debug } : {}),
1187
1281
  };
1188
1282
  }
@@ -1277,6 +1371,7 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
1277
1371
  setOptimizationMemoCacheSize(cfg.optimizationMemoCacheSize);
1278
1372
  }
1279
1373
  const predictiveContextCache = new Map();
1374
+ const compactedProjectionSessions = new Set();
1280
1375
  const PREDICTIVE_CACHE_MAX_SIZE = 100;
1281
1376
  // BeforeTurnKernel state
1282
1377
  const turnCache = new TurnMemoryCache(100);
@@ -1285,6 +1380,21 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
1285
1380
  let lastUserMessageHash = null;
1286
1381
  let cachedIdentity = null;
1287
1382
  let cachedSessionKey;
1383
+ function activateCompactedProjection(sessionId, source) {
1384
+ const wasActive = compactedProjectionSessions.has(sessionId);
1385
+ compactedProjectionSessions.add(sessionId);
1386
+ predictiveContextCache.delete(sessionId);
1387
+ postToolRecallCache.delete(sessionId);
1388
+ turnCache.invalidateSession(sessionId);
1389
+ if (!wasActive) {
1390
+ logger.info?.(`LibraVDB activated daemon-owned compacted projection sessionId=${sessionId} source=${source}`);
1391
+ }
1392
+ }
1393
+ function enforceAssembleBudget(result, tokenBudget, compactionProjectionActive) {
1394
+ return compactionProjectionActive
1395
+ ? enforceCompactedProjectionBudgetInvariant(result, tokenBudget)
1396
+ : enforceTokenBudgetInvariant(result, tokenBudget);
1397
+ }
1288
1398
  function resolveUserId(args) {
1289
1399
  // Framework-provided userId takes priority (channels, future SDK compat).
1290
1400
  const fwUserId = args?.userIdOverride?.trim();
@@ -1750,11 +1860,15 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
1750
1860
  try {
1751
1861
  const client = await runtime.getClient();
1752
1862
  const threshold = getDynamicCompactThreshold(args.tokenBudget);
1753
- return normalizeCompactResult(await client.compactSession(request), {
1863
+ const result = normalizeCompactResult(await client.compactSession(request), {
1754
1864
  tokensBefore: args.currentTokenCount,
1755
1865
  logger,
1756
1866
  ...(threshold != null ? { threshold } : {}),
1757
1867
  });
1868
+ if (result.ok && result.compacted) {
1869
+ activateCompactedProjection(args.sessionId, "compact");
1870
+ }
1871
+ return result;
1758
1872
  }
1759
1873
  catch (error) {
1760
1874
  return {
@@ -1814,6 +1928,7 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
1814
1928
  const sessionId = requireSessionId(args.sessionId, "bootstrap");
1815
1929
  predictiveContextCache.delete(sessionId);
1816
1930
  postToolRecallCache.delete(sessionId);
1931
+ turnCache.invalidateSession(sessionId);
1817
1932
  asyncIngestionQueues.delete(sessionId);
1818
1933
  const userId = resolveUserId({
1819
1934
  userIdOverride: args.userId,
@@ -1856,6 +1971,7 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
1856
1971
  },
1857
1972
  async assemble(args) {
1858
1973
  const sessionId = requireSessionId(args.sessionId, "assemble");
1974
+ let compactionProjectionActive = compactedProjectionSessions.has(sessionId);
1859
1975
  const userId = resolveUserId({
1860
1976
  userIdOverride: args.userId,
1861
1977
  sessionKey: args.sessionKey,
@@ -1865,10 +1981,22 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
1865
1981
  // Processing the full history on every turn is O(N²) and the
1866
1982
  // primary source of growing turn latency.
1867
1983
  const normalizeWindow = 50;
1868
- const recentMessages = args.messages.length > normalizeWindow
1869
- ? args.messages.slice(-normalizeWindow)
1870
- : args.messages;
1984
+ const recentMessages = selectTurnAlignedSourceSuffix(args.messages, normalizeWindow);
1871
1985
  const messages = normalizeKernelMessages(recentMessages);
1986
+ const projectedSourceMessages = () => compactionProjectionActive
1987
+ ? recentMessages
1988
+ : args.messages;
1989
+ const buildAssembleFallback = () => {
1990
+ if (!compactionProjectionActive) {
1991
+ return buildBudgetFallbackContext(args.messages, args.tokenBudget);
1992
+ }
1993
+ return enforceCompactedProjectionBudgetInvariant({
1994
+ messages: projectedSourceMessages(),
1995
+ estimatedTokens: approximateMessagesTokens(projectedSourceMessages()),
1996
+ systemPromptAddition: "",
1997
+ promptAuthority: "assembled",
1998
+ }, args.tokenBudget);
1999
+ };
1872
2000
  const strippedPrompt = args.prompt
1873
2001
  ? normalizeKernelContent(args.prompt, { retainOpenClawContext: false })
1874
2002
  : "";
@@ -1919,10 +2047,15 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
1919
2047
  compacted: compactionResult.compacted,
1920
2048
  reason: compactionResult.reason,
1921
2049
  });
1922
- if (!compactionResult.ok) {
2050
+ compactionProjectionActive = compactedProjectionSessions.has(sessionId);
2051
+ if (!compactionResult.ok && !compactionProjectionActive) {
1923
2052
  logger.info?.(`LibraVDB predictive compaction blocked assemble path at ${currentContextTokens} tokens ` +
1924
2053
  `(threshold=${dynamicCompactThreshold}): ${compactionResult.reason ?? "compaction failed"}`);
1925
- return ensureReplaySafeUserTurn(buildBudgetFallbackContext(args.messages, args.tokenBudget), args.messages, logger, args.tokenBudget);
2054
+ return ensureReplaySafeUserTurn(buildAssembleFallback(), args.messages, logger, args.tokenBudget);
2055
+ }
2056
+ if (!compactionResult.ok) {
2057
+ logger.info?.(`LibraVDB predictive compaction made no additional progress at ${currentContextTokens} tokens; ` +
2058
+ "continuing with the existing daemon-owned compacted projection.");
1926
2059
  }
1927
2060
  }
1928
2061
  // BeforeTurnKernel: semantic memory retrieval against the current user query.
@@ -1976,8 +2109,13 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
1976
2109
  }
1977
2110
  }
1978
2111
  if (cachedSystemPrompt !== undefined) {
1979
- const mockResp = { messages: args.messages, systemPromptAddition: cachedSystemPrompt };
1980
- enforced = enforceTokenBudgetInvariant(normalizeAssembleResult(mockResp, args.messages), args.tokenBudget);
2112
+ if (hasCompactedSessionContext(cachedSystemPrompt)) {
2113
+ activateCompactedProjection(sessionId, "assemble");
2114
+ compactionProjectionActive = true;
2115
+ }
2116
+ const sourceProjection = projectedSourceMessages();
2117
+ const mockResp = { messages: sourceProjection, systemPromptAddition: cachedSystemPrompt };
2118
+ enforced = enforceAssembleBudget(normalizeAssembleResult(mockResp, sourceProjection, compactionProjectionActive ? "assembled" : PROMPT_AUTHORITY_PREASSEMBLY_MAY_OVERFLOW), args.tokenBudget, compactionProjectionActive);
1981
2119
  }
1982
2120
  else {
1983
2121
  // Drain pending async ingestion for this session so the daemon's
@@ -2038,7 +2176,13 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
2038
2176
  }),
2039
2177
  new Promise((_, reject) => setTimeout(() => reject(new Error(`AssembleContextInternal timed out after ${assembleTimeout}ms`)), assembleTimeout)),
2040
2178
  ]);
2041
- const assembled = normalizeAssembleResult(resp, args.messages);
2179
+ if (typeof resp.systemPromptAddition === "string" &&
2180
+ hasCompactedSessionContext(resp.systemPromptAddition)) {
2181
+ activateCompactedProjection(sessionId, "assemble");
2182
+ compactionProjectionActive = true;
2183
+ }
2184
+ const sourceProjection = projectedSourceMessages();
2185
+ const assembled = normalizeAssembleResult(resp, sourceProjection, compactionProjectionActive ? "assembled" : PROMPT_AUTHORITY_PREASSEMBLY_MAY_OVERFLOW);
2042
2186
  const continuityContext = await injectContinuityContext({
2043
2187
  client,
2044
2188
  userId,
@@ -2070,13 +2214,13 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
2070
2214
  withContext = { ...withContext, systemPromptAddition: appendSystemPromptAddition(withContext.systemPromptAddition, rulesContext) };
2071
2215
  }
2072
2216
  }
2073
- enforced = enforceTokenBudgetInvariant(await augmentWithExactRecall(withContext, {
2217
+ enforced = enforceAssembleBudget(await augmentWithExactRecall(withContext, {
2074
2218
  queryText: retrievalQuery,
2075
2219
  userId,
2076
2220
  sessionId,
2077
2221
  tokenBudget: args.tokenBudget,
2078
2222
  reservedTokens: reservedCurrentTurnTokens,
2079
- }), args.tokenBudget);
2223
+ }), args.tokenBudget, compactionProjectionActive);
2080
2224
  const predictions = predictiveContextCache.get(sessionId) || [];
2081
2225
  predictiveContextCache.delete(sessionId);
2082
2226
  if (predictions.length > 0) {
@@ -2128,14 +2272,15 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
2128
2272
  systemPromptAddition: enforced.systemPromptAddition,
2129
2273
  });
2130
2274
  }
2131
- enforced = enforceTokenBudgetInvariant(enforced, args.tokenBudget);
2132
- // normalizeAssembleResult uses sourceMessages directly full transcript
2133
- // with tool protocol intact. No message re-classification needed.
2275
+ enforced = enforceAssembleBudget(enforced, args.tokenBudget, compactionProjectionActive);
2276
+ // The returned transcript is always an untouched source projection.
2277
+ // Once daemon compaction owns history, OpenClaw must precheck this
2278
+ // assembled suffix rather than the unwindowed session transcript.
2134
2279
  return ensureReplaySafeUserTurn(enforced, args.messages, logger, args.tokenBudget);
2135
2280
  }
2136
2281
  catch (error) {
2137
2282
  logger.warn?.(`LibraVDB assemble failed, using budget-clamped fallback context: ${error instanceof Error ? error.message : String(error)}`);
2138
- return ensureReplaySafeUserTurn(buildBudgetFallbackContext(args.messages, args.tokenBudget), args.messages, logger, args.tokenBudget);
2283
+ return ensureReplaySafeUserTurn(buildAssembleFallback(), args.messages, logger, args.tokenBudget);
2139
2284
  }
2140
2285
  },
2141
2286
  async compact(args) {
@@ -2343,6 +2488,7 @@ export function buildContextEngineFactory(runtime, cfg, logger = console) {
2343
2488
  }
2344
2489
  }
2345
2490
  predictiveContextCache.clear();
2491
+ compactedProjectionSessions.clear();
2346
2492
  postToolRecallCache.clear();
2347
2493
  asyncIngestionQueues.clear();
2348
2494
  triggerCache.clear();
package/dist/index.js CHANGED
@@ -18615,10 +18615,10 @@ function buildRulesContext() {
18615
18615
  }
18616
18616
 
18617
18617
  // src/manifest.ts
18618
- import * as fs2 from "fs";
18619
- import * as path2 from "path";
18620
- import * as crypto from "crypto";
18621
- import * as os2 from "os";
18618
+ import * as fs2 from "node:fs";
18619
+ import * as path2 from "node:path";
18620
+ import * as crypto from "node:crypto";
18621
+ import * as os2 from "node:os";
18622
18622
  var TurnManifestStore = class {
18623
18623
  manifestDir;
18624
18624
  constructor() {
@@ -18923,7 +18923,7 @@ function normalizeCompactResult(response, options = {}) {
18923
18923
  const threshold = options.threshold;
18924
18924
  const overBudget = threshold != null && tokensBefore >= threshold;
18925
18925
  const engineRefused = !didCompact && overBudget;
18926
- const tokensAfter = didCompact && typeof response?.tokensAfter === "number" && response.tokensAfter > 0 ? response.tokensAfter : void 0;
18926
+ const tokensAfter = didCompact && typeof response?.tokensAfter === "number" && response.tokensAfter >= 0 ? response.tokensAfter : void 0;
18927
18927
  return {
18928
18928
  ok: !engineRefused,
18929
18929
  compacted: didCompact,
@@ -18931,8 +18931,7 @@ function normalizeCompactResult(response, options = {}) {
18931
18931
  result: {
18932
18932
  tokensBefore,
18933
18933
  ...tokensAfter != null ? { tokensAfter } : {},
18934
- ...details.summaryMethod ? { summary: details.summaryMethod } : {},
18935
- ...details.summaryText ? { summaryText: details.summaryText } : {},
18934
+ ...details.summaryText ? { summary: details.summaryText } : {},
18936
18935
  details: { ...details, ...threshold != null ? { threshold } : {} }
18937
18936
  }
18938
18937
  };
@@ -19011,6 +19010,18 @@ function findLastUserMessageIndex(messages) {
19011
19010
  }
19012
19011
  return -1;
19013
19012
  }
19013
+ function selectTurnAlignedSourceSuffix(messages, maxMessages) {
19014
+ if (messages.length === 0) return [];
19015
+ let start = Math.max(0, messages.length - Math.max(1, Math.floor(maxMessages)));
19016
+ while (start > 0 && messages[start]?.role !== "user") {
19017
+ start -= 1;
19018
+ }
19019
+ if (messages[start]?.role !== "user") {
19020
+ const firstUserIndex = messages.findIndex((message) => message.role === "user");
19021
+ start = firstUserIndex >= 0 ? firstUserIndex : 0;
19022
+ }
19023
+ return messages.slice(start);
19024
+ }
19014
19025
  function normalizeKernelContent(content, options = {}) {
19015
19026
  const text = typeof content === "string" ? content : Array.isArray(content) ? content.map(stringifyKernelBlock).filter((part) => part.length > 0).join("\n") : "";
19016
19027
  return stripOpenClawUntrustedMetadataEnvelope(text, {
@@ -19374,6 +19385,32 @@ function trimMessagesToBudget(messages, tokenBudget) {
19374
19385
  }
19375
19386
  return [{ ...last, content: truncated }];
19376
19387
  }
19388
+ function trimTurnAlignedSourceSuffixToBudget(messages, tokenBudget) {
19389
+ if (messages.length === 0) return [];
19390
+ const userStarts = [];
19391
+ for (let index = 0; index < messages.length; index += 1) {
19392
+ if (messages[index]?.role === "user") userStarts.push(index);
19393
+ }
19394
+ if (userStarts.length === 0) {
19395
+ return messages;
19396
+ }
19397
+ const keptBundles = [];
19398
+ let used = 0;
19399
+ let bundleEnd = messages.length;
19400
+ for (let index = userStarts.length - 1; index >= 0; index -= 1) {
19401
+ const bundleStart = userStarts[index];
19402
+ const bundle = messages.slice(bundleStart, bundleEnd);
19403
+ const bundleCost = approximateMessagesTokens(bundle);
19404
+ if (keptBundles.length > 0 && used + bundleCost > tokenBudget) {
19405
+ break;
19406
+ }
19407
+ keptBundles.unshift(bundle);
19408
+ used += bundleCost;
19409
+ bundleEnd = bundleStart;
19410
+ if (used >= tokenBudget) break;
19411
+ }
19412
+ return keptBundles.flat();
19413
+ }
19377
19414
  function boundAfterTurnMessagesForIngest(messages, logger, sessionId) {
19378
19415
  const estimatedTokens = approximateMessagesTokens(messages);
19379
19416
  if (estimatedTokens <= AFTER_TURN_INGEST_MAX_TOKENS) {
@@ -19414,6 +19451,34 @@ function enforceTokenBudgetInvariant(result, tokenBudget) {
19414
19451
  estimatedTokens: Math.min(effectiveBudget, systemPromptTokens + trimmedEstimate)
19415
19452
  };
19416
19453
  }
19454
+ function enforceCompactedProjectionBudgetInvariant(result, tokenBudget) {
19455
+ if (typeof tokenBudget !== "number" || !Number.isFinite(tokenBudget) || tokenBudget <= 0) {
19456
+ return result;
19457
+ }
19458
+ const effectiveBudget = resolveEffectiveAssembleBudget(Math.max(1, Math.floor(tokenBudget)));
19459
+ const approximateTotal = approximateTokenCount(result.systemPromptAddition) + approximateMessagesTokens(result.messages);
19460
+ if (result.estimatedTokens <= effectiveBudget && approximateTotal <= effectiveBudget) {
19461
+ return result;
19462
+ }
19463
+ const lastUserIndex = findLastUserMessageIndex(result.messages);
19464
+ const mandatoryMessages = lastUserIndex >= 0 ? result.messages.slice(lastUserIndex) : result.messages;
19465
+ const mandatoryTokens = approximateMessagesTokens(mandatoryMessages);
19466
+ const systemPromptBudget = Math.max(0, effectiveBudget - mandatoryTokens);
19467
+ const systemPromptAddition = truncateSystemPromptAdditionToTokenBudget(
19468
+ result.systemPromptAddition,
19469
+ systemPromptBudget
19470
+ );
19471
+ const systemPromptTokens = approximateTokenCount(systemPromptAddition);
19472
+ const messageBudget = Math.max(mandatoryTokens, effectiveBudget - systemPromptTokens);
19473
+ const messages = trimTurnAlignedSourceSuffixToBudget(result.messages, messageBudget);
19474
+ const estimatedTokens = systemPromptTokens + approximateMessagesTokens(messages);
19475
+ return {
19476
+ ...result,
19477
+ messages,
19478
+ systemPromptAddition,
19479
+ estimatedTokens
19480
+ };
19481
+ }
19417
19482
  function buildBudgetFallbackContext(messages, tokenBudget) {
19418
19483
  const effectiveBudget = resolveEffectiveAssembleBudget(tokenBudget);
19419
19484
  const fallbackMessages = trimMessagesToBudget(
@@ -19430,7 +19495,11 @@ function buildBudgetFallbackContext(messages, tokenBudget) {
19430
19495
  var DAEMON_AUTHORED_CONTEXT_RE = /<authored_context\b[^>]*>([\s\S]*?)<\/authored_context>/gi;
19431
19496
  var DAEMON_AUTHORED_CONTEXT_GUIDANCE_RE = /^\s*Treat the authored entries below as active project rules and identity context\.?\s*$/i;
19432
19497
  var COMPACTED_SESSION_CONTEXT_RE = /<compacted_session_context\b([^>]*)>([\s\S]*?)<\/compacted_session_context>/gi;
19498
+ var HAS_COMPACTED_SESSION_CONTEXT_RE = /<compacted_session_context\b/i;
19433
19499
  var COMPACTED_SESSION_RENDER_LEDGER_RE = /(?:^|\n)(?:Artifacts:|Constraints:|Open Next Steps:|Extracted context anchors:)(?:\n|$)/;
19500
+ function hasCompactedSessionContext(text) {
19501
+ return HAS_COMPACTED_SESSION_CONTEXT_RE.test(text);
19502
+ }
19434
19503
  function sanitizeDaemonSystemPromptAddition(text) {
19435
19504
  return demoteDaemonAuthoredContextBlocks(
19436
19505
  sanitizeToolCallPatterns(canonicalizeCompactedSessionContextBlocks(text))
@@ -19781,13 +19850,13 @@ function ensureReplaySafeUserTurn(assembled, sourceMessages, logger, tokenBudget
19781
19850
  estimatedTokens: baseEstimatedTokens + approximateMessageTokens(fallbackUser)
19782
19851
  };
19783
19852
  }
19784
- function normalizeAssembleResult(result, sourceMessages) {
19853
+ function normalizeAssembleResult(result, sourceMessages, promptAuthority = PROMPT_AUTHORITY_PREASSEMBLY_MAY_OVERFLOW) {
19785
19854
  const systemPromptAddition = typeof result.systemPromptAddition === "string" ? sanitizeDaemonSystemPromptAddition(result.systemPromptAddition) : "";
19786
19855
  return {
19787
19856
  messages: sourceMessages ?? [],
19788
19857
  estimatedTokens: typeof result.estimatedTokens === "number" ? result.estimatedTokens : 0,
19789
19858
  systemPromptAddition,
19790
- promptAuthority: PROMPT_AUTHORITY_PREASSEMBLY_MAY_OVERFLOW,
19859
+ promptAuthority,
19791
19860
  ...result.debug != null ? { debug: result.debug } : {}
19792
19861
  };
19793
19862
  }
@@ -19858,6 +19927,7 @@ function buildContextEngineFactory(runtime, cfg, logger = console) {
19858
19927
  setOptimizationMemoCacheSize(cfg.optimizationMemoCacheSize);
19859
19928
  }
19860
19929
  const predictiveContextCache = /* @__PURE__ */ new Map();
19930
+ const compactedProjectionSessions = /* @__PURE__ */ new Set();
19861
19931
  const PREDICTIVE_CACHE_MAX_SIZE = 100;
19862
19932
  const turnCache = new TurnMemoryCache(100);
19863
19933
  const circuitBreakers = /* @__PURE__ */ new Map();
@@ -19865,6 +19935,21 @@ function buildContextEngineFactory(runtime, cfg, logger = console) {
19865
19935
  let lastUserMessageHash = null;
19866
19936
  let cachedIdentity = null;
19867
19937
  let cachedSessionKey;
19938
+ function activateCompactedProjection(sessionId, source) {
19939
+ const wasActive = compactedProjectionSessions.has(sessionId);
19940
+ compactedProjectionSessions.add(sessionId);
19941
+ predictiveContextCache.delete(sessionId);
19942
+ postToolRecallCache.delete(sessionId);
19943
+ turnCache.invalidateSession(sessionId);
19944
+ if (!wasActive) {
19945
+ logger.info?.(
19946
+ `LibraVDB activated daemon-owned compacted projection sessionId=${sessionId} source=${source}`
19947
+ );
19948
+ }
19949
+ }
19950
+ function enforceAssembleBudget(result, tokenBudget, compactionProjectionActive) {
19951
+ return compactionProjectionActive ? enforceCompactedProjectionBudgetInvariant(result, tokenBudget) : enforceTokenBudgetInvariant(result, tokenBudget);
19952
+ }
19868
19953
  function resolveUserId(args) {
19869
19954
  const fwUserId = args?.userIdOverride?.trim();
19870
19955
  if (fwUserId) return fwUserId;
@@ -20302,11 +20387,15 @@ ${cached}
20302
20387
  try {
20303
20388
  const client = await runtime.getClient();
20304
20389
  const threshold = getDynamicCompactThreshold(args.tokenBudget);
20305
- return normalizeCompactResult(await client.compactSession(request3), {
20390
+ const result = normalizeCompactResult(await client.compactSession(request3), {
20306
20391
  tokensBefore: args.currentTokenCount,
20307
20392
  logger,
20308
20393
  ...threshold != null ? { threshold } : {}
20309
20394
  });
20395
+ if (result.ok && result.compacted) {
20396
+ activateCompactedProjection(args.sessionId, "compact");
20397
+ }
20398
+ return result;
20310
20399
  } catch (error2) {
20311
20400
  return {
20312
20401
  ok: false,
@@ -20363,6 +20452,7 @@ ${cached}
20363
20452
  const sessionId = requireSessionId(args.sessionId, "bootstrap");
20364
20453
  predictiveContextCache.delete(sessionId);
20365
20454
  postToolRecallCache.delete(sessionId);
20455
+ turnCache.invalidateSession(sessionId);
20366
20456
  asyncIngestionQueues.delete(sessionId);
20367
20457
  const userId = resolveUserId({
20368
20458
  userIdOverride: args.userId,
@@ -20406,13 +20496,26 @@ ${cached}
20406
20496
  },
20407
20497
  async assemble(args) {
20408
20498
  const sessionId = requireSessionId(args.sessionId, "assemble");
20499
+ let compactionProjectionActive = compactedProjectionSessions.has(sessionId);
20409
20500
  const userId = resolveUserId({
20410
20501
  userIdOverride: args.userId,
20411
20502
  sessionKey: args.sessionKey
20412
20503
  });
20413
20504
  const normalizeWindow = 50;
20414
- const recentMessages = args.messages.length > normalizeWindow ? args.messages.slice(-normalizeWindow) : args.messages;
20505
+ const recentMessages = selectTurnAlignedSourceSuffix(args.messages, normalizeWindow);
20415
20506
  const messages = normalizeKernelMessages(recentMessages);
20507
+ const projectedSourceMessages = () => compactionProjectionActive ? recentMessages : args.messages;
20508
+ const buildAssembleFallback = () => {
20509
+ if (!compactionProjectionActive) {
20510
+ return buildBudgetFallbackContext(args.messages, args.tokenBudget);
20511
+ }
20512
+ return enforceCompactedProjectionBudgetInvariant({
20513
+ messages: projectedSourceMessages(),
20514
+ estimatedTokens: approximateMessagesTokens(projectedSourceMessages()),
20515
+ systemPromptAddition: "",
20516
+ promptAuthority: "assembled"
20517
+ }, args.tokenBudget);
20518
+ };
20416
20519
  const strippedPrompt = args.prompt ? normalizeKernelContent(args.prompt, { retainOpenClawContext: false }) : "";
20417
20520
  const lastUserIndex = findLastUserMessageIndex(messages);
20418
20521
  const isPostToolContinuation = lastUserIndex >= 0 && lastUserIndex < messages.length - 1 && hasLiveToolProtocolAfterLastUser(messages, lastUserIndex);
@@ -20458,17 +20561,23 @@ ${cached}
20458
20561
  compacted: compactionResult.compacted,
20459
20562
  reason: compactionResult.reason
20460
20563
  });
20461
- if (!compactionResult.ok) {
20564
+ compactionProjectionActive = compactedProjectionSessions.has(sessionId);
20565
+ if (!compactionResult.ok && !compactionProjectionActive) {
20462
20566
  logger.info?.(
20463
20567
  `LibraVDB predictive compaction blocked assemble path at ${currentContextTokens} tokens (threshold=${dynamicCompactThreshold}): ${compactionResult.reason ?? "compaction failed"}`
20464
20568
  );
20465
20569
  return ensureReplaySafeUserTurn(
20466
- buildBudgetFallbackContext(args.messages, args.tokenBudget),
20570
+ buildAssembleFallback(),
20467
20571
  args.messages,
20468
20572
  logger,
20469
20573
  args.tokenBudget
20470
20574
  );
20471
20575
  }
20576
+ if (!compactionResult.ok) {
20577
+ logger.info?.(
20578
+ `LibraVDB predictive compaction made no additional progress at ${currentContextTokens} tokens; continuing with the existing daemon-owned compacted projection.`
20579
+ );
20580
+ }
20472
20581
  }
20473
20582
  const btLog = cfg.beforeTurnDebug ? (msg) => logger.info?.(msg) : (_msg) => {
20474
20583
  };
@@ -20515,10 +20624,20 @@ ${cached}
20515
20624
  }
20516
20625
  }
20517
20626
  if (cachedSystemPrompt !== void 0) {
20518
- const mockResp = { messages: args.messages, systemPromptAddition: cachedSystemPrompt };
20519
- enforced = enforceTokenBudgetInvariant(
20520
- normalizeAssembleResult(mockResp, args.messages),
20521
- args.tokenBudget
20627
+ if (hasCompactedSessionContext(cachedSystemPrompt)) {
20628
+ activateCompactedProjection(sessionId, "assemble");
20629
+ compactionProjectionActive = true;
20630
+ }
20631
+ const sourceProjection = projectedSourceMessages();
20632
+ const mockResp = { messages: sourceProjection, systemPromptAddition: cachedSystemPrompt };
20633
+ enforced = enforceAssembleBudget(
20634
+ normalizeAssembleResult(
20635
+ mockResp,
20636
+ sourceProjection,
20637
+ compactionProjectionActive ? "assembled" : PROMPT_AUTHORITY_PREASSEMBLY_MAY_OVERFLOW
20638
+ ),
20639
+ args.tokenBudget,
20640
+ compactionProjectionActive
20522
20641
  );
20523
20642
  } else {
20524
20643
  const pending = asyncIngestionQueues.get(sessionId);
@@ -20580,7 +20699,16 @@ ${cached}
20580
20699
  (_, reject) => setTimeout(() => reject(new Error(`AssembleContextInternal timed out after ${assembleTimeout}ms`)), assembleTimeout)
20581
20700
  )
20582
20701
  ]);
20583
- const assembled = normalizeAssembleResult(resp, args.messages);
20702
+ if (typeof resp.systemPromptAddition === "string" && hasCompactedSessionContext(resp.systemPromptAddition)) {
20703
+ activateCompactedProjection(sessionId, "assemble");
20704
+ compactionProjectionActive = true;
20705
+ }
20706
+ const sourceProjection = projectedSourceMessages();
20707
+ const assembled = normalizeAssembleResult(
20708
+ resp,
20709
+ sourceProjection,
20710
+ compactionProjectionActive ? "assembled" : PROMPT_AUTHORITY_PREASSEMBLY_MAY_OVERFLOW
20711
+ );
20584
20712
  const continuityContext = await injectContinuityContext({
20585
20713
  client,
20586
20714
  userId,
@@ -20609,7 +20737,7 @@ ${cached}
20609
20737
  withContext = { ...withContext, systemPromptAddition: appendSystemPromptAddition(withContext.systemPromptAddition, rulesContext) };
20610
20738
  }
20611
20739
  }
20612
- enforced = enforceTokenBudgetInvariant(
20740
+ enforced = enforceAssembleBudget(
20613
20741
  await augmentWithExactRecall(withContext, {
20614
20742
  queryText: retrievalQuery,
20615
20743
  userId,
@@ -20617,7 +20745,8 @@ ${cached}
20617
20745
  tokenBudget: args.tokenBudget,
20618
20746
  reservedTokens: reservedCurrentTurnTokens
20619
20747
  }),
20620
- args.tokenBudget
20748
+ args.tokenBudget,
20749
+ compactionProjectionActive
20621
20750
  );
20622
20751
  const predictions = predictiveContextCache.get(sessionId) || [];
20623
20752
  predictiveContextCache.delete(sessionId);
@@ -20674,9 +20803,10 @@ ${cached}
20674
20803
  systemPromptAddition: enforced.systemPromptAddition
20675
20804
  });
20676
20805
  }
20677
- enforced = enforceTokenBudgetInvariant(
20806
+ enforced = enforceAssembleBudget(
20678
20807
  enforced,
20679
- args.tokenBudget
20808
+ args.tokenBudget,
20809
+ compactionProjectionActive
20680
20810
  );
20681
20811
  return ensureReplaySafeUserTurn(enforced, args.messages, logger, args.tokenBudget);
20682
20812
  } catch (error2) {
@@ -20684,7 +20814,7 @@ ${cached}
20684
20814
  `LibraVDB assemble failed, using budget-clamped fallback context: ${error2 instanceof Error ? error2.message : String(error2)}`
20685
20815
  );
20686
20816
  return ensureReplaySafeUserTurn(
20687
- buildBudgetFallbackContext(args.messages, args.tokenBudget),
20817
+ buildAssembleFallback(),
20688
20818
  args.messages,
20689
20819
  logger,
20690
20820
  args.tokenBudget
@@ -20872,6 +21002,7 @@ ${cached}
20872
21002
  }
20873
21003
  }
20874
21004
  predictiveContextCache.clear();
21005
+ compactedProjectionSessions.clear();
20875
21006
  postToolRecallCache.clear();
20876
21007
  asyncIngestionQueues.clear();
20877
21008
  triggerCache.clear();
@@ -31194,6 +31325,12 @@ function buildToolGuidance(availableTools) {
31194
31325
  return [];
31195
31326
  }
31196
31327
  const lines = [];
31328
+ const hasSearch = availableTools.has("memory_search");
31329
+ const hasGet = availableTools.has("memory_get");
31330
+ const hasDescribe = availableTools.has("memory_describe");
31331
+ const hasExpand = availableTools.has("memory_expand");
31332
+ const hasGrep = availableTools.has("memory_grep");
31333
+ const hasUserCard = availableTools.has("get_user_card");
31197
31334
  const hasGetCard = availableTools.has("get_user_card");
31198
31335
  const hasListCards = availableTools.has("list_user_cards");
31199
31336
  if (hasGetCard || hasListCards) {
@@ -31204,7 +31341,7 @@ function buildToolGuidance(availableTools) {
31204
31341
  hasListCards ? "- `list_user_cards()` \u2014 MANDATORY roster check. Call if unsure whether a card exists." : "",
31205
31342
  "Cards are the canonical record. You MUST call these tools. Do NOT answer from",
31206
31343
  "memory, context, or training data without checking the card first.",
31207
- "Only use memory_search if the card is empty or missing.",
31344
+ hasSearch ? "Only use memory_search if the card is empty or missing." : "Use the card result as the available identity record.",
31208
31345
  "",
31209
31346
  "**Autonomous card maintenance:**",
31210
31347
  hasGetCard ? "- When ANY speaker is mentioned with new or changed information (status, relationships, jobs, life events, feelings), call `update_user_card` BEFORE responding. Update the card first, then reply. Do NOT wait to be asked. Build the world picture proactively. Every person the user mentions matters." : "",
@@ -31212,20 +31349,19 @@ function buildToolGuidance(availableTools) {
31212
31349
  ""
31213
31350
  );
31214
31351
  }
31215
- lines.push(
31216
- "Call `memory_search` once per user question for prior turns, remembered",
31217
- "facts, earliest interactions, and channel history. Do not answer memory",
31218
- "questions from prior transcript claims \u2014 perform a search every time.",
31219
- "After receiving results, use them directly; do not re-call in the same turn.",
31220
- ...availableTools.has("memory_get") ? [
31221
- "After a `memory_search` hit, call `memory_get` when exact wording or more context is needed.",
31222
- "IMPORTANT: If a search snippet is cluttered with metadata, do NOT claim nothing was found. Call `memory_get` on the hit's path to read the full record first. The data is there \u2014 expand before giving up."
31223
- ] : [],
31224
- ""
31225
- );
31226
- const hasDescribe = availableTools.has("memory_describe");
31227
- const hasExpand = availableTools.has("memory_expand");
31228
- const hasGrep = availableTools.has("memory_grep");
31352
+ if (hasSearch) {
31353
+ lines.push(
31354
+ "Call `memory_search` once per user question for prior turns, remembered",
31355
+ "facts, earliest interactions, and channel history. Do not answer memory",
31356
+ "questions from prior transcript claims \u2014 perform a search every time.",
31357
+ "After receiving results, use them directly; do not re-call in the same turn.",
31358
+ ...hasGet ? [
31359
+ "After a `memory_search` hit, call `memory_get` when exact wording or more context is needed.",
31360
+ "IMPORTANT: If a search snippet is cluttered with metadata, do NOT claim nothing was found. Call `memory_get` on the hit's path to read the full record first. The data is there \u2014 expand before giving up."
31361
+ ] : [],
31362
+ ""
31363
+ );
31364
+ }
31229
31365
  if (hasDescribe || hasExpand || hasGrep) {
31230
31366
  lines.push(
31231
31367
  "**Compacted summaries \u2014 recall hierarchy (cheap \u2192 expensive):**",
@@ -31272,13 +31408,16 @@ function buildToolGuidance(availableTools) {
31272
31408
  ""
31273
31409
  );
31274
31410
  if (hasExpand) {
31411
+ const traversalSteps = [
31412
+ hasSearch ? "1. `memory_search` for the people/events in question to get record IDs" : "1. Start from a record ID already present in context or another tool result",
31413
+ "2. `memory_expand` with the most relevant `record_id` to walk typed causal and 6W1H hop edges",
31414
+ hasGet ? "3. Follow interesting edges; use `memory_get` for exact detail on connected records" : "3. Use the typed edge snippets to decide whether the connected records answer the question",
31415
+ ...hasUserCard ? ["4. Use `get_user_card` to cross-reference identity context"] : []
31416
+ ];
31275
31417
  lines.push(
31276
31418
  "### Causal Graph Traversal",
31277
31419
  "When the user asks about causes, patterns, or relationships:",
31278
- "1. `memory_search` for the people/events in question to get record IDs",
31279
- "2. `memory_expand` with the most relevant `record_id` to walk causal edges",
31280
- "3. Follow interesting edges \u2014 use `memory_get` for full detail on connected records",
31281
- "4. Use `get_user_card` to cross-reference identity context",
31420
+ ...traversalSteps,
31282
31421
  ""
31283
31422
  );
31284
31423
  }
@@ -31466,7 +31605,7 @@ function createMemoryExpandTool(getClient, getSessionKey, logger = console, getS
31466
31605
  return {
31467
31606
  name: "memory_expand",
31468
31607
  label: "Memory Expand",
31469
- description: "Expand compacted summaries OR walk causal graph edges from ANY record. Summary mode (summaryIds): walk the summary tree up to maxDepth levels. Graph mode (record_id): walk causal edges (why_ids/how_ids/hop_targets) from a record ID. Use exact IDs from memory_search or memory_get results \u2014 any ingested turn, memory, or summary has graph edges. After get_user_card for context, search for related people/events then expand the most relevant hit. For large expansions, spawns a sub-agent. Use memory_describe first to check if expansion is warranted.",
31608
+ description: "Expand compacted summaries OR walk causal graph edges from ANY record. Summary mode (summaryIds): walk the summary tree up to maxDepth levels. Graph mode (record_id): walk causal edges (why_ids/how_ids/hop_targets) from a record ID. Use exact IDs from memory_search or memory_get results \u2014 any ingested turn, memory, or summary has graph edges. For large expansions, spawns a sub-agent. Use memory_describe first to check if expansion is warranted.",
31470
31609
  parameters: MEMORY_EXPAND_SCHEMA,
31471
31610
  execute: async (_toolCallId, rawParams) => {
31472
31611
  const params = asParams(rawParams);
@@ -31481,10 +31620,16 @@ function createMemoryExpandTool(getClient, getSessionKey, logger = console, getS
31481
31620
  const client = await getClient();
31482
31621
  const resp = await client.expandSummary({ recordId, maxDepth });
31483
31622
  let text = resp.text ?? "";
31484
- const connected = resp.connected;
31485
- if (connected && connected.length > 0) {
31623
+ const connected = (resp.connected ?? []).map((c) => ({
31624
+ recordId: c.recordId,
31625
+ text: c.text ?? "",
31626
+ depth: c.depth,
31627
+ edgeType: c.edgeType || "unknown",
31628
+ edgeWeight: c.edgeWeight ?? 0
31629
+ }));
31630
+ if (connected.length > 0) {
31486
31631
  text = connected.map(
31487
- (c) => `[depth=${c.depth}] ${c.recordId}: ${c.text || ""}`
31632
+ (c) => `[depth=${c.depth} edge=${c.edgeType} weight=${c.edgeWeight}] ${c.recordId}: ${c.text}`
31488
31633
  ).join("\n\n");
31489
31634
  }
31490
31635
  if (!text && resp.whyIds?.length) {
@@ -31494,7 +31639,7 @@ hop_targets: ${resp.hopTargets?.join(", ") ?? "none"}`;
31494
31639
  }
31495
31640
  return {
31496
31641
  content: [{ type: "text", text: text || "(no graph edges found)" }],
31497
- details: { summaryId: recordId, depth: maxDepth, text: text || "", truncated: false, exceededBudget: false, parentCount: connected?.length ?? 0 }
31642
+ details: { summaryId: recordId, depth: maxDepth, text: text || "", truncated: false, exceededBudget: false, parentCount: connected.length, connected }
31498
31643
  };
31499
31644
  } catch (error2) {
31500
31645
  logger.warn?.(`memory_expand graph mode failed: ${formatError(error2)}`);
package/dist/manifest.js CHANGED
@@ -1,7 +1,7 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import * as crypto from "crypto";
4
- import * as os from "os";
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as crypto from "node:crypto";
4
+ import * as os from "node:os";
5
5
  export class TurnManifestStore {
6
6
  manifestDir;
7
7
  constructor() {
@@ -25,22 +25,27 @@ function buildToolGuidance(availableTools) {
25
25
  return [];
26
26
  }
27
27
  const lines = [];
28
+ const hasSearch = availableTools.has("memory_search");
29
+ const hasGet = availableTools.has("memory_get");
30
+ const hasDescribe = availableTools.has("memory_describe");
31
+ const hasExpand = availableTools.has("memory_expand");
32
+ const hasGrep = availableTools.has("memory_grep");
33
+ const hasUserCard = availableTools.has("get_user_card");
28
34
  // ── User card tools (identity-first override) ──
29
35
  const hasGetCard = availableTools.has("get_user_card");
30
36
  const hasListCards = availableTools.has("list_user_cards");
31
37
  if (hasGetCard || hasListCards) {
32
- lines.push("**Identity/Entity questions — MANDATORY card lookup:**", "BEFORE answering any question about a person, pet, place, or named thing:", hasGetCard ? "- `get_user_card(user_id)` — MANDATORY lookup for a specific entity." : "", hasListCards ? "- `list_user_cards()` — MANDATORY roster check. Call if unsure whether a card exists." : "", "Cards are the canonical record. You MUST call these tools. Do NOT answer from", "memory, context, or training data without checking the card first.", "Only use memory_search if the card is empty or missing.", "", "**Autonomous card maintenance:**", hasGetCard ? "- When ANY speaker is mentioned with new or changed information (status, relationships, jobs, life events, feelings), call `update_user_card` BEFORE responding. Update the card first, then reply. Do NOT wait to be asked. Build the world picture proactively. Every person the user mentions matters." : "", hasGetCard ? "- If a card for the speaker doesn't exist yet, CREATE one with `update_user_card`. Better to have a stub card than no card at all." : "", "");
38
+ lines.push("**Identity/Entity questions — MANDATORY card lookup:**", "BEFORE answering any question about a person, pet, place, or named thing:", hasGetCard ? "- `get_user_card(user_id)` — MANDATORY lookup for a specific entity." : "", hasListCards ? "- `list_user_cards()` — MANDATORY roster check. Call if unsure whether a card exists." : "", "Cards are the canonical record. You MUST call these tools. Do NOT answer from", "memory, context, or training data without checking the card first.", hasSearch ? "Only use memory_search if the card is empty or missing." : "Use the card result as the available identity record.", "", "**Autonomous card maintenance:**", hasGetCard ? "- When ANY speaker is mentioned with new or changed information (status, relationships, jobs, life events, feelings), call `update_user_card` BEFORE responding. Update the card first, then reply. Do NOT wait to be asked. Build the world picture proactively. Every person the user mentions matters." : "", hasGetCard ? "- If a card for the speaker doesn't exist yet, CREATE one with `update_user_card`. Better to have a stub card than no card at all." : "", "");
39
+ }
40
+ if (hasSearch) {
41
+ lines.push("Call `memory_search` once per user question for prior turns, remembered", "facts, earliest interactions, and channel history. Do not answer memory", "questions from prior transcript claims — perform a search every time.", "After receiving results, use them directly; do not re-call in the same turn.", ...(hasGet
42
+ ? [
43
+ "After a `memory_search` hit, call `memory_get` when exact wording or more context is needed.",
44
+ "IMPORTANT: If a search snippet is cluttered with metadata, do NOT claim nothing was found. Call `memory_get` on the hit's path to read the full record first. The data is there — expand before giving up."
45
+ ]
46
+ : []), "");
33
47
  }
34
- lines.push("Call `memory_search` once per user question for prior turns, remembered", "facts, earliest interactions, and channel history. Do not answer memory", "questions from prior transcript claims — perform a search every time.", "After receiving results, use them directly; do not re-call in the same turn.", ...(availableTools.has("memory_get")
35
- ? [
36
- "After a `memory_search` hit, call `memory_get` when exact wording or more context is needed.",
37
- "IMPORTANT: If a search snippet is cluttered with metadata, do NOT claim nothing was found. Call `memory_get` on the hit's path to read the full record first. The data is there — expand before giving up."
38
- ]
39
- : []), "");
40
48
  // ── Summaries / recall (when available) ──
41
- const hasDescribe = availableTools.has("memory_describe");
42
- const hasExpand = availableTools.has("memory_expand");
43
- const hasGrep = availableTools.has("memory_grep");
44
49
  if (hasDescribe || hasExpand || hasGrep) {
45
50
  lines.push("**Compacted summaries — recall hierarchy (cheap → expensive):**", "", "Summaries in search results show `[Summary sum_xxx]: [eviction cue]`.", "The cue lists what the summary covers — anchors (files, tools, versions),", "decisions, constraints, and signal counts. Many questions can be answered", "from the cue alone without expanding.", "");
46
51
  if (hasDescribe) {
@@ -58,7 +63,17 @@ function buildToolGuidance(availableTools) {
58
63
  lines.push("### Hard Constraint Rules", "Rules are injected at session start as `<hard_constraints>`. They are non-negotiable.", "Use `set_rule` to create one (max 20), `list_rules` to see current rules,", "`delete_rule` to remove one. Rules override all other instructions.", "Never reason around a rule, find loopholes, or deprioritize it.", "");
59
64
  // ── Causal graph traversal (when expand supports record_id) ──
60
65
  if (hasExpand) {
61
- lines.push("### Causal Graph Traversal", "When the user asks about causes, patterns, or relationships:", "1. `memory_search` for the people/events in question to get record IDs", "2. `memory_expand` with the most relevant `record_id` to walk causal edges", "3. Follow interesting edges — use `memory_get` for full detail on connected records", "4. Use `get_user_card` to cross-reference identity context", "");
66
+ const traversalSteps = [
67
+ hasSearch
68
+ ? "1. `memory_search` for the people/events in question to get record IDs"
69
+ : "1. Start from a record ID already present in context or another tool result",
70
+ "2. `memory_expand` with the most relevant `record_id` to walk typed causal and 6W1H hop edges",
71
+ hasGet
72
+ ? "3. Follow interesting edges; use `memory_get` for exact detail on connected records"
73
+ : "3. Use the typed edge snippets to decide whether the connected records answer the question",
74
+ ...(hasUserCard ? ["4. Use `get_user_card` to cross-reference identity context"] : []),
75
+ ];
76
+ lines.push("### Causal Graph Traversal", "When the user asks about causes, patterns, or relationships:", ...traversalSteps, "");
62
77
  }
63
78
  lines.push("LibraVDB memory is vector-backed and retrieved through tools, not files.", "");
64
79
  return lines;
@@ -26,6 +26,13 @@ type MemoryExpandDetails = {
26
26
  truncated: boolean;
27
27
  exceededBudget: boolean;
28
28
  parentCount: number;
29
+ connected?: Array<{
30
+ recordId: string;
31
+ text: string;
32
+ depth: number;
33
+ edgeType: string;
34
+ edgeWeight: number;
35
+ }>;
29
36
  error?: string;
30
37
  };
31
38
  type MemoryGrepDetails = {
@@ -199,8 +199,6 @@ export function createMemoryExpandTool(getClient, getSessionKey, logger = consol
199
199
  "Graph mode (record_id): walk causal edges (why_ids/how_ids/hop_targets) " +
200
200
  "from a record ID. Use exact IDs from memory_search or memory_get results — " +
201
201
  "any ingested turn, memory, or summary has graph edges. " +
202
- "After get_user_card for context, search for related people/events then " +
203
- "expand the most relevant hit. " +
204
202
  "For large expansions, spawns a sub-agent. " +
205
203
  "Use memory_describe first to check if expansion is warranted.",
206
204
  parameters: MEMORY_EXPAND_SCHEMA,
@@ -218,16 +216,22 @@ export function createMemoryExpandTool(getClient, getSessionKey, logger = consol
218
216
  const client = await getClient();
219
217
  const resp = await client.expandSummary({ recordId, maxDepth });
220
218
  let text = resp.text ?? "";
221
- const connected = resp.connected;
222
- if (connected && connected.length > 0) {
223
- text = connected.map((c) => `[depth=${c.depth}] ${c.recordId}: ${c.text || ""}`).join("\n\n");
219
+ const connected = (resp.connected ?? []).map((c) => ({
220
+ recordId: c.recordId,
221
+ text: c.text ?? "",
222
+ depth: c.depth,
223
+ edgeType: c.edgeType || "unknown",
224
+ edgeWeight: c.edgeWeight ?? 0,
225
+ }));
226
+ if (connected.length > 0) {
227
+ text = connected.map((c) => `[depth=${c.depth} edge=${c.edgeType} weight=${c.edgeWeight}] ${c.recordId}: ${c.text}`).join("\n\n");
224
228
  }
225
229
  if (!text && resp.whyIds?.length) {
226
230
  text = `why_ids: ${resp.whyIds.join(", ")}\nhow_ids: ${resp.howIds?.join(", ") ?? "none"}\nhop_targets: ${resp.hopTargets?.join(", ") ?? "none"}`;
227
231
  }
228
232
  return {
229
233
  content: [{ type: "text", text: text || "(no graph edges found)" }],
230
- details: { summaryId: recordId, depth: maxDepth, text: text || "", truncated: false, exceededBudget: false, parentCount: connected?.length ?? 0 },
234
+ details: { summaryId: recordId, depth: maxDepth, text: text || "", truncated: false, exceededBudget: false, parentCount: connected.length, connected },
231
235
  };
232
236
  }
233
237
  catch (error) {
@@ -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.13",
5
+ "version": "1.10.14-beta.1",
6
6
  "kind": [
7
7
  "memory",
8
8
  "context-engine"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xdarkicex/openclaw-memory-libravdb",
3
- "version": "1.10.13",
3
+ "version": "1.10.14-beta.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",