@joshuaswarren/openclaw-engram 9.0.38 → 9.0.39

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
@@ -40,6 +40,7 @@ AI agents forget everything between conversations. Engram fixes that.
40
40
  - **Red-team benchmark packs** — Engram's eval harness can now validate and count typed `memory-red-team` benchmark packs so poisoning-defense regression suites stay explicit and reviewable instead of hiding inside generic benchmark metadata.
41
41
  - **Cue-anchor index foundation** — Engram can now, when `harmonicRetrievalEnabled` and `abstractionAnchorsEnabled` are enabled, persist typed cue anchors for entities, files, tools, outcomes, constraints, and dates, inspect them with `openclaw engram cue-anchor-status`, and keep harmonic retrieval grounded in explicit abstraction-to-cue links before blending logic lands.
42
42
  - **Harmonic retrieval diagnostics** — Engram can now, when `harmonicRetrievalEnabled` is enabled, blend abstraction-node evidence with cue-anchor matches into a dedicated `Harmonic Retrieval` recall section and inspect those blended results with `openclaw engram harmonic-search`.
43
+ - **Verified episodic recall** — Engram can now, when `verifiedRecallEnabled` is enabled, inject a dedicated `Verified Episodes` recall section that reuses memory boxes but only surfaces boxes whose cited source memories still verify as non-archived episodes.
43
44
  - **Zero-config start** — Install, add an API key, restart. Engram works out of the box with sensible defaults and progressively unlocks advanced features as you enable them.
44
45
 
45
46
  ## Quick Start
@@ -167,6 +168,8 @@ openclaw engram objective-state-status # Objective-state snapshot counts a
167
168
  openclaw engram causal-trajectory-status # Causal-trajectory record counts and latest stored chain
168
169
  openclaw engram trust-zone-status # Trust-zone record counts and latest stored record
169
170
  openclaw engram trust-zone-promote # Dry-run or apply a trust-zone promotion with provenance/corroboration enforcement
171
+ openclaw engram harmonic-search <query> # Preview blended harmonic retrieval matches
172
+ openclaw engram verified-recall-search <query> # Preview verified episodic recall matches
170
173
  openclaw engram conversation-index-health # Conversation index status
171
174
  openclaw engram graph-health # Entity graph status
172
175
  openclaw engram tier-status # Hot/cold tier metrics
@@ -203,6 +206,10 @@ Key settings:
203
206
  | `trustZoneRecallEnabled` | `false` | Inject prompt-relevant working and trusted trust-zone records into recall context |
204
207
  | `memoryPoisoningDefenseEnabled` | `false` | Enable deterministic provenance trust scoring and corroboration requirements for risky trusted promotions |
205
208
  | `memoryRedTeamBenchEnabled` | `false` | Enable typed memory red-team benchmark pack support and status accounting for poisoning-defense suites |
209
+ | `harmonicRetrievalEnabled` | `false` | Enable harmonic retrieval blending over abstraction nodes and cue anchors, including the dedicated recall section and `harmonic-search` diagnostics |
210
+ | `abstractionAnchorsEnabled` | `false` | Enable typed cue-anchor indexing for abstraction nodes and expose the anchor store through status tooling |
211
+ | `verifiedRecallEnabled` | `false` | Inject prompt-relevant memory boxes only when their cited source memories verify as non-archived episodes |
212
+ | `semanticRulePromotionEnabled` | `false` | Reserve semantic-rule promotion surfaces until verified episodic recall has been benchmarked in follow-on slices |
206
213
 
207
214
  Full reference: [Config Reference](docs/config-reference.md)
208
215
 
package/dist/index.js CHANGED
@@ -304,6 +304,8 @@ function parseConfig(raw) {
304
304
  memoryRedTeamBenchEnabled: cfg.memoryRedTeamBenchEnabled === true,
305
305
  harmonicRetrievalEnabled: cfg.harmonicRetrievalEnabled === true,
306
306
  abstractionAnchorsEnabled: cfg.abstractionAnchorsEnabled === true,
307
+ verifiedRecallEnabled: cfg.verifiedRecallEnabled === true,
308
+ semanticRulePromotionEnabled: cfg.semanticRulePromotionEnabled === true,
307
309
  abstractionNodeStoreDir: typeof cfg.abstractionNodeStoreDir === "string" && cfg.abstractionNodeStoreDir.trim().length > 0 ? cfg.abstractionNodeStoreDir.trim() : path.join(memoryDir, "state", "abstraction-nodes"),
308
310
  // Local LLM Provider (v2.1)
309
311
  localLlmEnabled: cfg.localLlmEnabled === true || cfg.localLlmEnabled === "true",
@@ -587,6 +589,12 @@ function buildDefaultRecallPipeline(cfg) {
587
589
  maxResults: 3,
588
590
  maxChars: 2200
589
591
  },
592
+ {
593
+ id: "verified-episodes",
594
+ enabled: cfg.verifiedRecallEnabled === true,
595
+ maxResults: 3,
596
+ maxChars: 1800
597
+ },
590
598
  {
591
599
  id: "memories",
592
600
  enabled: true,
@@ -15336,6 +15344,92 @@ async function searchHarmonicRetrieval(options) {
15336
15344
  ).slice(0, options.maxResults);
15337
15345
  }
15338
15346
 
15347
+ // src/verified-recall.ts
15348
+ function createReadOnlyBoxBuilder(memoryDir) {
15349
+ return new BoxBuilder(memoryDir, {
15350
+ memoryBoxesEnabled: true,
15351
+ traceWeaverEnabled: false,
15352
+ boxTopicShiftThreshold: 0.35,
15353
+ boxTimeGapMs: 30 * 60 * 1e3,
15354
+ boxMaxMemories: 50,
15355
+ traceWeaverLookbackDays: 7,
15356
+ traceWeaverOverlapThreshold: 0.4
15357
+ });
15358
+ }
15359
+ function scoreVerifiedEpisodeCandidate(box, verifiedMemories, queryTokens) {
15360
+ const matchedFields = /* @__PURE__ */ new Set();
15361
+ let score = 0;
15362
+ const topicMatches = countRecallTokenOverlap(queryTokens, box.topics.join(" "));
15363
+ if (topicMatches > 0) {
15364
+ score += topicMatches * 3;
15365
+ matchedFields.add("topics");
15366
+ }
15367
+ const goalMatches = countRecallTokenOverlap(queryTokens, box.goal);
15368
+ if (goalMatches > 0) {
15369
+ score += goalMatches * 4;
15370
+ matchedFields.add("goal");
15371
+ }
15372
+ const toolMatches = countRecallTokenOverlap(queryTokens, box.toolsUsed?.join(" "));
15373
+ if (toolMatches > 0) {
15374
+ score += toolMatches * 2;
15375
+ matchedFields.add("toolsUsed");
15376
+ }
15377
+ let episodeContentMatches = 0;
15378
+ for (const memory of verifiedMemories) {
15379
+ episodeContentMatches += countRecallTokenOverlap(queryTokens, memory.content);
15380
+ }
15381
+ if (episodeContentMatches > 0) {
15382
+ score += episodeContentMatches * 4;
15383
+ matchedFields.add("episodeContent");
15384
+ }
15385
+ return { score, matchedFields };
15386
+ }
15387
+ function resolveVerifiedEpisodeMemoriesFromMap(memoryById, memoryIds) {
15388
+ const verified = [];
15389
+ for (const memoryId of memoryIds) {
15390
+ try {
15391
+ const memory = memoryById.get(memoryId);
15392
+ if (!memory) continue;
15393
+ if (memory.frontmatter.status === "archived") continue;
15394
+ if (memory.frontmatter.memoryKind !== "episode") continue;
15395
+ verified.push(memory);
15396
+ } catch {
15397
+ }
15398
+ }
15399
+ return verified;
15400
+ }
15401
+ async function searchVerifiedEpisodes(options) {
15402
+ const queryTokens = new Set(normalizeRecallTokens(options.query, ["what", "which"]));
15403
+ if (queryTokens.size === 0 || options.maxResults <= 0) return [];
15404
+ const storage = new StorageManager(options.memoryDir);
15405
+ const verifiedMemoryById = new Map(
15406
+ (await storage.readAllMemories()).filter((memory) => memory.frontmatter.status !== "archived").filter((memory) => memory.frontmatter.memoryKind === "episode").map((memory) => [memory.frontmatter.id, memory])
15407
+ );
15408
+ const boxes = await createReadOnlyBoxBuilder(options.memoryDir).readRecentBoxes(Math.max(1, Math.floor(options.boxRecallDays ?? 3))).catch(() => []);
15409
+ const candidates = [];
15410
+ for (const box of boxes) {
15411
+ const verifiedMemories = resolveVerifiedEpisodeMemoriesFromMap(verifiedMemoryById, box.memoryIds);
15412
+ if (verifiedMemories.length === 0) continue;
15413
+ const { score, matchedFields } = scoreVerifiedEpisodeCandidate(box, verifiedMemories, queryTokens);
15414
+ if (score <= 0) continue;
15415
+ candidates.push({
15416
+ box,
15417
+ score,
15418
+ matchedFields,
15419
+ verifiedMemories
15420
+ });
15421
+ }
15422
+ return candidates.map((candidate) => ({
15423
+ box: candidate.box,
15424
+ score: candidate.score,
15425
+ verifiedEpisodeCount: candidate.verifiedMemories.length,
15426
+ verifiedMemoryIds: candidate.verifiedMemories.map((memory) => memory.frontmatter.id),
15427
+ matchedFields: [...candidate.matchedFields].sort()
15428
+ })).sort(
15429
+ (left, right) => right.score - left.score || right.verifiedEpisodeCount - left.verifiedEpisodeCount || right.box.sealedAt.localeCompare(left.box.sealedAt)
15430
+ ).slice(0, options.maxResults);
15431
+ }
15432
+
15339
15433
  // src/replay/types.ts
15340
15434
  var VALID_SOURCES = /* @__PURE__ */ new Set(["openclaw", "claude", "chatgpt"]);
15341
15435
  var VALID_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
@@ -19120,6 +19214,26 @@ ${r.snippet.trim()}
19120
19214
  timings.harmonicRetrieval = `${Date.now() - t0}ms`;
19121
19215
  return results.length > 0 ? this.formatHarmonicRetrievalResults(results) : null;
19122
19216
  })();
19217
+ const verifiedRecallPromise = (async () => {
19218
+ const t0 = Date.now();
19219
+ if (!this.config.verifiedRecallEnabled || !this.isRecallSectionEnabled("verified-episodes", this.config.verifiedRecallEnabled === true)) {
19220
+ timings.verifiedRecall = "skip";
19221
+ return null;
19222
+ }
19223
+ const maxResults = this.getRecallSectionNumber("verified-episodes", "maxResults") ?? 3;
19224
+ if (maxResults <= 0) {
19225
+ timings.verifiedRecall = "skip(limit=0)";
19226
+ return null;
19227
+ }
19228
+ const results = await searchVerifiedEpisodes({
19229
+ memoryDir: this.config.memoryDir,
19230
+ query: retrievalQuery,
19231
+ maxResults,
19232
+ boxRecallDays: this.config.boxRecallDays
19233
+ });
19234
+ timings.verifiedRecall = `${Date.now() - t0}ms`;
19235
+ return results.length > 0 ? this.formatVerifiedEpisodeResults(results) : null;
19236
+ })();
19123
19237
  const qmdPromise = (async () => {
19124
19238
  if (recallResultLimit <= 0) {
19125
19239
  timings.qmd = "skip(limit=0)";
@@ -19327,6 +19441,7 @@ ${formatted}`;
19327
19441
  causalTrajectorySection,
19328
19442
  trustZoneSection,
19329
19443
  harmonicRetrievalSection,
19444
+ verifiedRecallSection,
19330
19445
  qmdResult,
19331
19446
  transcriptSection,
19332
19447
  compactionSection,
@@ -19343,6 +19458,7 @@ ${formatted}`;
19343
19458
  causalTrajectoryPromise,
19344
19459
  trustZonePromise,
19345
19460
  harmonicRetrievalPromise,
19461
+ verifiedRecallPromise,
19346
19462
  qmdPromise,
19347
19463
  transcriptPromise,
19348
19464
  compactionPromise,
@@ -19409,6 +19525,9 @@ ${tmtNode.summary}`);
19409
19525
  if (harmonicRetrievalSection) {
19410
19526
  this.appendRecallSection(sectionBuckets, "harmonic-retrieval", harmonicRetrievalSection);
19411
19527
  }
19528
+ if (verifiedRecallSection) {
19529
+ this.appendRecallSection(sectionBuckets, "verified-episodes", verifiedRecallSection);
19530
+ }
19412
19531
  if (qmdResult) {
19413
19532
  const t0 = Date.now();
19414
19533
  const { memoryResultsLists, globalResults } = qmdResult;
@@ -21609,6 +21728,29 @@ ${details.join("\n")}`;
21609
21728
  });
21610
21729
  return `## Harmonic Retrieval
21611
21730
 
21731
+ ${lines.join("\n\n")}`;
21732
+ }
21733
+ formatVerifiedEpisodeResults(results) {
21734
+ const lines = results.map(({ box, verifiedEpisodeCount, matchedFields }, index) => {
21735
+ const header = [
21736
+ `[${index + 1}] ${box.sealedAt.replace("T", " ").slice(0, 16)}`,
21737
+ box.traceId ? `trace:${box.traceId.slice(0, 12)}` : "trace:none"
21738
+ ].join(" | ");
21739
+ const details = [
21740
+ box.goal ?? `topics: ${box.topics.join(", ")}`,
21741
+ `verified episodes: ${verifiedEpisodeCount}`
21742
+ ];
21743
+ if (box.toolsUsed && box.toolsUsed.length > 0) {
21744
+ details.push(`tools: ${box.toolsUsed.join(", ")}`);
21745
+ }
21746
+ if (matchedFields.length > 0) {
21747
+ details.push(`matched: ${matchedFields.join(", ")}`);
21748
+ }
21749
+ return `${header}
21750
+ ${details.join("\n")}`;
21751
+ });
21752
+ return `## Verified Episodes
21753
+
21612
21754
  ${lines.join("\n\n")}`;
21613
21755
  }
21614
21756
  summarizeIdentityText(raw, maxLines, maxChars) {
@@ -27342,6 +27484,15 @@ async function runHarmonicSearchCliCommand(options) {
27342
27484
  anchorsEnabled: options.abstractionAnchorsEnabled
27343
27485
  });
27344
27486
  }
27487
+ async function runVerifiedRecallSearchCliCommand(options) {
27488
+ if (!options.verifiedRecallEnabled) return [];
27489
+ return searchVerifiedEpisodes({
27490
+ memoryDir: options.memoryDir,
27491
+ query: options.query,
27492
+ maxResults: Math.max(1, Math.floor(options.maxResults ?? 3)),
27493
+ boxRecallDays: options.boxRecallDays
27494
+ });
27495
+ }
27345
27496
  async function runTrustZonePromoteCliCommand(options) {
27346
27497
  const result = await promoteTrustZoneRecord({
27347
27498
  memoryDir: options.memoryDir,
@@ -28587,6 +28738,20 @@ function registerCli(api, orchestrator) {
28587
28738
  console.log(JSON.stringify(result, null, 2));
28588
28739
  console.log("OK");
28589
28740
  });
28741
+ cmd.command("verified-recall-search").description("Preview verified episodic recall over recent memory boxes").argument("<query>", "Prompt-like query to evaluate against verified episodic recall").option("--max-results <count>", "Maximum number of verified episodic results to return", "3").action(async (...args) => {
28742
+ const query = typeof args[0] === "string" ? args[0] : "";
28743
+ const options = args[1] ?? {};
28744
+ const maxResults = typeof options.maxResults === "string" ? Number.parseInt(options.maxResults, 10) : 3;
28745
+ const results = await runVerifiedRecallSearchCliCommand({
28746
+ memoryDir: orchestrator.config.memoryDir,
28747
+ verifiedRecallEnabled: orchestrator.config.verifiedRecallEnabled,
28748
+ query,
28749
+ maxResults: Number.isFinite(maxResults) ? maxResults : 3,
28750
+ boxRecallDays: orchestrator.config.boxRecallDays
28751
+ });
28752
+ console.log(JSON.stringify(results, null, 2));
28753
+ console.log("OK");
28754
+ });
28590
28755
  cmd.command("conversation-index-health").description("Show conversation index backend health and index stats").action(async () => {
28591
28756
  const health = await runConversationIndexHealthCliCommand(orchestrator);
28592
28757
  console.log(JSON.stringify(health, null, 2));