@joshuaswarren/openclaw-engram 9.0.40 → 9.0.41

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
@@ -42,6 +42,7 @@ AI agents forget everything between conversations. Engram fixes that.
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
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.
44
44
  - **Semantic rule promotion** — Engram can now, when `semanticRulePromotionEnabled` is enabled, promote explicit `IF ... THEN ...` rules from verified episodic memories into durable `rule` memories with lineage, source-memory provenance, duplicate suppression, and the operator-facing `openclaw engram semantic-rule-promote` CLI.
45
+ - **Verified rule recall** — Engram can now, when `semanticRuleVerificationEnabled` is enabled, inject a dedicated `Verified Rules` recall section that re-checks promoted rule memories against their cited source episodes at recall time and downgrades stale provenance before the rule can surface.
45
46
  - **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.
46
47
 
47
48
  ## Quick Start
@@ -211,6 +212,7 @@ Key settings:
211
212
  | `abstractionAnchorsEnabled` | `false` | Enable typed cue-anchor indexing for abstraction nodes and expose the anchor store through status tooling |
212
213
  | `verifiedRecallEnabled` | `false` | Inject prompt-relevant memory boxes only when their cited source memories verify as non-archived episodes |
213
214
  | `semanticRulePromotionEnabled` | `false` | Enable deterministic promotion of explicit `IF ... THEN ...` rules from verified episodic memories via `openclaw engram semantic-rule-promote` |
215
+ | `semanticRuleVerificationEnabled` | `false` | Verify promoted semantic rules against their cited source episodes at recall time and inject a dedicated `Verified Rules` section via `openclaw engram semantic-rule-verify` |
214
216
 
215
217
  Full reference: [Config Reference](docs/config-reference.md)
216
218
 
package/dist/index.js CHANGED
@@ -306,6 +306,7 @@ function parseConfig(raw) {
306
306
  abstractionAnchorsEnabled: cfg.abstractionAnchorsEnabled === true,
307
307
  verifiedRecallEnabled: cfg.verifiedRecallEnabled === true,
308
308
  semanticRulePromotionEnabled: cfg.semanticRulePromotionEnabled === true,
309
+ semanticRuleVerificationEnabled: cfg.semanticRuleVerificationEnabled === true,
309
310
  abstractionNodeStoreDir: typeof cfg.abstractionNodeStoreDir === "string" && cfg.abstractionNodeStoreDir.trim().length > 0 ? cfg.abstractionNodeStoreDir.trim() : path.join(memoryDir, "state", "abstraction-nodes"),
310
311
  // Local LLM Provider (v2.1)
311
312
  localLlmEnabled: cfg.localLlmEnabled === true || cfg.localLlmEnabled === "true",
@@ -595,6 +596,12 @@ function buildDefaultRecallPipeline(cfg) {
595
596
  maxResults: 3,
596
597
  maxChars: 1800
597
598
  },
599
+ {
600
+ id: "verified-rules",
601
+ enabled: cfg.semanticRuleVerificationEnabled === true,
602
+ maxResults: 3,
603
+ maxChars: 1800
604
+ },
598
605
  {
599
606
  id: "memories",
600
607
  enabled: true,
@@ -15430,6 +15437,100 @@ async function searchVerifiedEpisodes(options) {
15430
15437
  ).slice(0, options.maxResults);
15431
15438
  }
15432
15439
 
15440
+ // src/semantic-rule-verifier.ts
15441
+ var DEFAULT_MIN_EFFECTIVE_CONFIDENCE = 0.45;
15442
+ function verificationConfidenceMultiplier(status) {
15443
+ switch (status) {
15444
+ case "verified":
15445
+ return 1;
15446
+ case "source-memory-not-episode":
15447
+ return 0.45;
15448
+ case "source-memory-archived":
15449
+ return 0.4;
15450
+ case "source-memory-missing":
15451
+ return 0.35;
15452
+ default:
15453
+ return 0.35;
15454
+ }
15455
+ }
15456
+ function resolveVerificationStatus(sourceMemory) {
15457
+ if (!sourceMemory) return "source-memory-missing";
15458
+ if (sourceMemory.frontmatter.status === "archived") return "source-memory-archived";
15459
+ if (sourceMemory.frontmatter.memoryKind !== "episode") return "source-memory-not-episode";
15460
+ return "verified";
15461
+ }
15462
+ function resolveEffectiveConfidence(rule, sourceMemory) {
15463
+ const status = resolveVerificationStatus(sourceMemory);
15464
+ const ruleConfidence = Number.isFinite(rule.frontmatter.confidence) ? rule.frontmatter.confidence : 0.8;
15465
+ const sourceConfidence = Number.isFinite(sourceMemory?.frontmatter.confidence) ? sourceMemory.frontmatter.confidence : ruleConfidence;
15466
+ const anchoredConfidence = Math.min(ruleConfidence, sourceConfidence);
15467
+ const effectiveConfidence = Math.max(
15468
+ 0,
15469
+ Math.min(1, anchoredConfidence * verificationConfidenceMultiplier(status))
15470
+ );
15471
+ return { status, effectiveConfidence };
15472
+ }
15473
+ function scoreVerifiedSemanticRuleCandidate(rule, sourceMemory, queryTokens, effectiveConfidence) {
15474
+ const matchedFields = /* @__PURE__ */ new Set();
15475
+ let score = 0;
15476
+ const ruleContentMatches = countRecallTokenOverlap(queryTokens, rule.content);
15477
+ if (ruleContentMatches > 0) {
15478
+ score += ruleContentMatches * 5;
15479
+ matchedFields.add("ruleContent");
15480
+ }
15481
+ const tagMatches = countRecallTokenOverlap(queryTokens, rule.frontmatter.tags?.join(" "));
15482
+ if (tagMatches > 0) {
15483
+ score += tagMatches * 2;
15484
+ matchedFields.add("tags");
15485
+ }
15486
+ const sourceContentMatches = countRecallTokenOverlap(queryTokens, sourceMemory?.content);
15487
+ if (sourceContentMatches > 0) {
15488
+ score += sourceContentMatches * 2;
15489
+ matchedFields.add("sourceContent");
15490
+ }
15491
+ if (score > 0) {
15492
+ score += effectiveConfidence;
15493
+ }
15494
+ return { score, matchedFields };
15495
+ }
15496
+ async function searchVerifiedSemanticRules(options) {
15497
+ const queryTokens = new Set(normalizeRecallTokens(options.query, ["what", "which"]));
15498
+ if (queryTokens.size === 0 || options.maxResults <= 0) return [];
15499
+ const storage = new StorageManager(options.memoryDir);
15500
+ const allMemories = await storage.readAllMemories();
15501
+ const memoryById = new Map(allMemories.map((memory) => [memory.frontmatter.id, memory]));
15502
+ const minEffectiveConfidence = options.minEffectiveConfidence ?? DEFAULT_MIN_EFFECTIVE_CONFIDENCE;
15503
+ const candidates = [];
15504
+ for (const memory of allMemories) {
15505
+ if (memory.frontmatter.category !== "rule") continue;
15506
+ if (memory.frontmatter.status === "archived") continue;
15507
+ if (memory.frontmatter.source !== "semantic-rule-promotion") continue;
15508
+ const sourceMemoryId = memory.frontmatter.sourceMemoryId;
15509
+ if (!sourceMemoryId) continue;
15510
+ const sourceMemory = memoryById.get(sourceMemoryId);
15511
+ const { status, effectiveConfidence } = resolveEffectiveConfidence(memory, sourceMemory);
15512
+ if (effectiveConfidence < minEffectiveConfidence) continue;
15513
+ const { score, matchedFields } = scoreVerifiedSemanticRuleCandidate(
15514
+ memory,
15515
+ sourceMemory,
15516
+ queryTokens,
15517
+ effectiveConfidence
15518
+ );
15519
+ if (score <= 0) continue;
15520
+ candidates.push({
15521
+ rule: memory,
15522
+ score,
15523
+ sourceMemoryId,
15524
+ verificationStatus: status,
15525
+ effectiveConfidence,
15526
+ matchedFields: [...matchedFields].sort()
15527
+ });
15528
+ }
15529
+ return candidates.sort(
15530
+ (left, right) => right.score - left.score || right.effectiveConfidence - left.effectiveConfidence || right.rule.frontmatter.updated.localeCompare(left.rule.frontmatter.updated)
15531
+ ).slice(0, options.maxResults);
15532
+ }
15533
+
15433
15534
  // src/replay/types.ts
15434
15535
  var VALID_SOURCES = /* @__PURE__ */ new Set(["openclaw", "claude", "chatgpt"]);
15435
15536
  var VALID_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
@@ -19234,6 +19335,25 @@ ${r.snippet.trim()}
19234
19335
  timings.verifiedRecall = `${Date.now() - t0}ms`;
19235
19336
  return results.length > 0 ? this.formatVerifiedEpisodeResults(results) : null;
19236
19337
  })();
19338
+ const verifiedRulesPromise = (async () => {
19339
+ const t0 = Date.now();
19340
+ if (!this.config.semanticRuleVerificationEnabled || !this.isRecallSectionEnabled("verified-rules", this.config.semanticRuleVerificationEnabled === true)) {
19341
+ timings.verifiedRules = "skip";
19342
+ return null;
19343
+ }
19344
+ const maxResults = this.getRecallSectionNumber("verified-rules", "maxResults") ?? 3;
19345
+ if (maxResults <= 0) {
19346
+ timings.verifiedRules = "skip(limit=0)";
19347
+ return null;
19348
+ }
19349
+ const results = await searchVerifiedSemanticRules({
19350
+ memoryDir: this.config.memoryDir,
19351
+ query: retrievalQuery,
19352
+ maxResults
19353
+ });
19354
+ timings.verifiedRules = `${Date.now() - t0}ms`;
19355
+ return results.length > 0 ? this.formatVerifiedSemanticRuleResults(results) : null;
19356
+ })();
19237
19357
  const qmdPromise = (async () => {
19238
19358
  if (recallResultLimit <= 0) {
19239
19359
  timings.qmd = "skip(limit=0)";
@@ -19442,6 +19562,7 @@ ${formatted}`;
19442
19562
  trustZoneSection,
19443
19563
  harmonicRetrievalSection,
19444
19564
  verifiedRecallSection,
19565
+ verifiedRulesSection,
19445
19566
  qmdResult,
19446
19567
  transcriptSection,
19447
19568
  compactionSection,
@@ -19459,6 +19580,7 @@ ${formatted}`;
19459
19580
  trustZonePromise,
19460
19581
  harmonicRetrievalPromise,
19461
19582
  verifiedRecallPromise,
19583
+ verifiedRulesPromise,
19462
19584
  qmdPromise,
19463
19585
  transcriptPromise,
19464
19586
  compactionPromise,
@@ -19528,6 +19650,9 @@ ${tmtNode.summary}`);
19528
19650
  if (verifiedRecallSection) {
19529
19651
  this.appendRecallSection(sectionBuckets, "verified-episodes", verifiedRecallSection);
19530
19652
  }
19653
+ if (verifiedRulesSection) {
19654
+ this.appendRecallSection(sectionBuckets, "verified-rules", verifiedRulesSection);
19655
+ }
19531
19656
  if (qmdResult) {
19532
19657
  const t0 = Date.now();
19533
19658
  const { memoryResultsLists, globalResults } = qmdResult;
@@ -21751,6 +21876,27 @@ ${details.join("\n")}`;
21751
21876
  });
21752
21877
  return `## Verified Episodes
21753
21878
 
21879
+ ${lines.join("\n\n")}`;
21880
+ }
21881
+ formatVerifiedSemanticRuleResults(results) {
21882
+ const lines = results.map(({ rule, sourceMemoryId, verificationStatus, effectiveConfidence, matchedFields }, index) => {
21883
+ const header = [
21884
+ `[${index + 1}] ${rule.frontmatter.updated.replace("T", " ").slice(0, 16)}`,
21885
+ verificationStatus,
21886
+ `confidence:${effectiveConfidence.toFixed(2)}`
21887
+ ].join(" | ");
21888
+ const details = [
21889
+ rule.content,
21890
+ `source memory: ${sourceMemoryId}`
21891
+ ];
21892
+ if (matchedFields.length > 0) {
21893
+ details.push(`matched: ${matchedFields.join(", ")}`);
21894
+ }
21895
+ return `${header}
21896
+ ${details.join("\n")}`;
21897
+ });
21898
+ return `## Verified Rules
21899
+
21754
21900
  ${lines.join("\n\n")}`;
21755
21901
  }
21756
21902
  summarizeIdentityText(raw, maxLines, maxChars) {
@@ -27621,6 +27767,14 @@ async function runSemanticRulePromoteCliCommand(options) {
27621
27767
  dryRun: options.dryRun
27622
27768
  });
27623
27769
  }
27770
+ async function runSemanticRuleVerifyCliCommand(options) {
27771
+ if (!options.semanticRuleVerificationEnabled) return [];
27772
+ return searchVerifiedSemanticRules({
27773
+ memoryDir: options.memoryDir,
27774
+ query: options.query,
27775
+ maxResults: Math.max(1, Math.floor(options.maxResults ?? 3))
27776
+ });
27777
+ }
27624
27778
  async function runTrustZonePromoteCliCommand(options) {
27625
27779
  const result = await promoteTrustZoneRecord({
27626
27780
  memoryDir: options.memoryDir,
@@ -28891,6 +29045,19 @@ function registerCli(api, orchestrator) {
28891
29045
  console.log(JSON.stringify(result, null, 2));
28892
29046
  console.log("OK");
28893
29047
  });
29048
+ cmd.command("semantic-rule-verify").description("Preview verified semantic-rule recall with provenance-aware confidence downgrades").argument("<query>", "Prompt-like query to evaluate against verified semantic-rule recall").option("--max-results <count>", "Maximum number of verified semantic rules to return", "3").action(async (...args) => {
29049
+ const query = typeof args[0] === "string" ? args[0] : "";
29050
+ const options = args[1] ?? {};
29051
+ const maxResults = typeof options.maxResults === "string" ? Number.parseInt(options.maxResults, 10) : 3;
29052
+ const results = await runSemanticRuleVerifyCliCommand({
29053
+ memoryDir: orchestrator.config.memoryDir,
29054
+ semanticRuleVerificationEnabled: orchestrator.config.semanticRuleVerificationEnabled,
29055
+ query,
29056
+ maxResults: Number.isFinite(maxResults) ? maxResults : 3
29057
+ });
29058
+ console.log(JSON.stringify(results, null, 2));
29059
+ console.log("OK");
29060
+ });
28894
29061
  cmd.command("conversation-index-health").description("Show conversation index backend health and index stats").action(async () => {
28895
29062
  const health = await runConversationIndexHealthCliCommand(orchestrator);
28896
29063
  console.log(JSON.stringify(health, null, 2));