@joshuaswarren/openclaw-engram 9.0.48 → 9.0.49

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
@@ -47,7 +47,7 @@ AI agents forget everything between conversations. Engram fixes that.
47
47
  - **Artifact recovery recall** — Engram can now, when both `creationMemoryEnabled` and `workProductRecallEnabled` are enabled, surface prompt-relevant work-product ledger entries back into recall as a dedicated `Work Products` section and inspect reuse candidates with `openclaw engram work-product-recall-search`.
48
48
  - **Commitment lifecycle foundation** — Engram can now, when `creationMemoryEnabled`, `commitmentLedgerEnabled`, and `commitmentLifecycleEnabled` are enabled, transition existing commitments to `fulfilled` / `cancelled` / `expired`, inspect overdue and stale obligations in `openclaw engram commitment-status`, and run deterministic lifecycle cleanup with `openclaw engram commitment-lifecycle-run`.
49
49
  - **Resume-bundle builder** — Engram can now, when `creationMemoryEnabled` and `resumeBundlesEnabled` are enabled, persist typed crash-recovery resume bundles, inspect them with `openclaw engram resume-bundle-status`, write explicit handoff shells through `openclaw engram resume-bundle-record`, and build bounded resume bundles from transcript recovery, recent objective-state snapshots, work products, and open commitments through `openclaw engram resume-bundle-build`.
50
- - **Utility-learning telemetry foundation** — Engram can now, when `memoryUtilityLearningEnabled` is enabled, persist typed downstream utility events for promotion and ranking decisions, inspect them with `openclaw engram utility-status`, and write deterministic benchmark/operator telemetry through `openclaw engram utility-record` before learned weighting lands.
50
+ - **Utility-learning offline learner** — Engram can now, when `memoryUtilityLearningEnabled` is enabled, persist typed downstream utility events for promotion and ranking decisions, inspect the raw event ledger with `openclaw engram utility-status`, record deterministic benchmark/operator telemetry through `openclaw engram utility-record`, and learn bounded offline promotion/ranking weights through `openclaw engram utility-learn` and `openclaw engram utility-learning-status` before runtime weighting lands.
51
51
  - **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.
52
52
 
53
53
  ## Quick Start
@@ -186,6 +186,8 @@ openclaw engram work-product-record # Record a typed work-product ledger
186
186
  openclaw engram work-product-recall-search <query> # Preview reusable work products from the creation-memory ledger
187
187
  openclaw engram utility-status # Utility-learning telemetry counts and latest observed outcome event
188
188
  openclaw engram utility-record # Record a typed utility-learning telemetry event
189
+ openclaw engram utility-learning-status # Latest offline utility-learning snapshot and learned weight counts
190
+ openclaw engram utility-learn # Learn bounded offline promotion/ranking weights from recorded utility events
189
191
  openclaw engram conversation-index-health # Conversation index status
190
192
  openclaw engram graph-health # Entity graph status
191
193
  openclaw engram tier-status # Hot/cold tier metrics
package/dist/index.js CHANGED
@@ -25311,8 +25311,8 @@ promotionCandidates: ${res.promotionCandidateCount}`
25311
25311
  }
25312
25312
 
25313
25313
  // src/cli.ts
25314
- import path61 from "path";
25315
- import { access as access3, readFile as readFile37, readdir as readdir24, unlink as unlink8 } from "fs/promises";
25314
+ import path62 from "path";
25315
+ import { access as access3, readFile as readFile38, readdir as readdir24, unlink as unlink8 } from "fs/promises";
25316
25316
  import { createHash as createHash10 } from "crypto";
25317
25317
 
25318
25318
  // src/transfer/export-json.ts
@@ -26192,8 +26192,8 @@ function gatherCandidates(input, warnings) {
26192
26192
  const record = rec;
26193
26193
  const content = typeof record.content === "string" ? record.content : null;
26194
26194
  if (!content) continue;
26195
- const path63 = typeof record.path === "string" ? record.path : "";
26196
- if (!path63.startsWith("transcripts/") && !path63.includes("/transcripts/")) continue;
26195
+ const path64 = typeof record.path === "string" ? record.path : "";
26196
+ if (!path64.startsWith("transcripts/") && !path64.includes("/transcripts/")) continue;
26197
26197
  rows.push(...parseJsonl(content, warnings));
26198
26198
  }
26199
26199
  return rows;
@@ -27959,15 +27959,268 @@ async function getUtilityTelemetryStatus(options) {
27959
27959
  };
27960
27960
  }
27961
27961
 
27962
- // src/resume-bundles.ts
27962
+ // src/utility-learner.ts
27963
27963
  import path60 from "path";
27964
- import { mkdir as mkdir41, writeFile as writeFile38 } from "fs/promises";
27964
+ import { mkdir as mkdir41, readFile as readFile37, rename as rename4, writeFile as writeFile38 } from "fs/promises";
27965
+ var UTILITY_LEARNING_SNAPSHOT_VERSION = 1;
27966
+ var UTILITY_LEARNING_STATE_FILE = "learning-state.json";
27967
+ function clampWeight(value, maxWeightMagnitude) {
27968
+ const limit = Number.isFinite(maxWeightMagnitude) && maxWeightMagnitude > 0 ? maxWeightMagnitude : 0;
27969
+ return Math.max(-limit, Math.min(limit, value));
27970
+ }
27971
+ function coerceLearningWindowDays(value) {
27972
+ if (!Number.isFinite(value)) return 14;
27973
+ return Math.max(1, Math.floor(value));
27974
+ }
27975
+ function coerceMinEventCount(value) {
27976
+ if (!Number.isFinite(value)) return 3;
27977
+ return Math.max(1, Math.floor(value));
27978
+ }
27979
+ function coerceMaxWeightMagnitude(value) {
27980
+ if (!Number.isFinite(value)) return 0.35;
27981
+ return Math.max(0, Math.min(1, value));
27982
+ }
27983
+ function roundWeight(value) {
27984
+ return Math.round(value * 1e3) / 1e3;
27985
+ }
27986
+ function outcomeCountsFor(events) {
27987
+ const counts = {};
27988
+ for (const event of events) {
27989
+ counts[event.outcome] = (counts[event.outcome] ?? 0) + 1;
27990
+ }
27991
+ return counts;
27992
+ }
27993
+ function selectRecentEvents(events, now, windowDays) {
27994
+ if (!Number.isFinite(windowDays) || windowDays <= 0) return [...events];
27995
+ const minTimestamp = now.getTime() - windowDays * 864e5;
27996
+ return events.filter((event) => {
27997
+ const ts = Date.parse(event.recordedAt);
27998
+ return Number.isFinite(ts) && ts >= minTimestamp;
27999
+ });
28000
+ }
28001
+ function confidenceFromEvents(eventCount, averageUtilityScore) {
28002
+ if (eventCount <= 0) return 0;
28003
+ return roundWeight(clamp01(Math.abs(averageUtilityScore) * Math.min(1, eventCount / 10)));
28004
+ }
28005
+ function validateUtilityLearningSnapshot(raw) {
28006
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
28007
+ throw new Error("utility learning snapshot must be an object");
28008
+ }
28009
+ const record = raw;
28010
+ if (record.version !== UTILITY_LEARNING_SNAPSHOT_VERSION) {
28011
+ throw new Error("utility learning snapshot version must be 1");
28012
+ }
28013
+ if (typeof record.updatedAt !== "string" || record.updatedAt.length === 0) {
28014
+ throw new Error("utility learning snapshot updatedAt must be a string");
28015
+ }
28016
+ if (typeof record.windowDays !== "number" || !Number.isFinite(record.windowDays) || record.windowDays < 0) {
28017
+ throw new Error("utility learning snapshot windowDays must be a non-negative number");
28018
+ }
28019
+ if (typeof record.minEventCount !== "number" || !Number.isFinite(record.minEventCount) || record.minEventCount < 1) {
28020
+ throw new Error("utility learning snapshot minEventCount must be >= 1");
28021
+ }
28022
+ if (typeof record.maxWeightMagnitude !== "number" || !Number.isFinite(record.maxWeightMagnitude) || record.maxWeightMagnitude < 0) {
28023
+ throw new Error("utility learning snapshot maxWeightMagnitude must be >= 0");
28024
+ }
28025
+ if (!Array.isArray(record.weights)) {
28026
+ throw new Error("utility learning snapshot weights must be an array");
28027
+ }
28028
+ const weights = record.weights.map((entry) => {
28029
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
28030
+ throw new Error("utility learning weight must be an object");
28031
+ }
28032
+ const weight = entry;
28033
+ const target = weight.target;
28034
+ const decision = weight.decision;
28035
+ if (target !== "promotion" && target !== "ranking") {
28036
+ throw new Error("utility learning weight target must be promotion|ranking");
28037
+ }
28038
+ if (!["promote", "demote", "hold", "boost", "suppress"].includes(String(decision))) {
28039
+ throw new Error("utility learning weight decision is invalid");
28040
+ }
28041
+ if (typeof weight.eventCount !== "number" || !Number.isFinite(weight.eventCount) || weight.eventCount < 0) {
28042
+ throw new Error("utility learning weight eventCount must be >= 0");
28043
+ }
28044
+ if (typeof weight.learnedWeight !== "number" || !Number.isFinite(weight.learnedWeight)) {
28045
+ throw new Error("utility learning weight learnedWeight must be finite");
28046
+ }
28047
+ if (typeof weight.averageUtilityScore !== "number" || !Number.isFinite(weight.averageUtilityScore)) {
28048
+ throw new Error("utility learning weight averageUtilityScore must be finite");
28049
+ }
28050
+ if (typeof weight.confidence !== "number" || !Number.isFinite(weight.confidence)) {
28051
+ throw new Error("utility learning weight confidence must be finite");
28052
+ }
28053
+ if (typeof weight.updatedAt !== "string" || weight.updatedAt.length === 0) {
28054
+ throw new Error("utility learning weight updatedAt must be a string");
28055
+ }
28056
+ const outcomeCounts = weight.outcomeCounts ?? {};
28057
+ return {
28058
+ target,
28059
+ decision,
28060
+ eventCount: weight.eventCount,
28061
+ learnedWeight: roundWeight(weight.learnedWeight),
28062
+ averageUtilityScore: roundWeight(weight.averageUtilityScore),
28063
+ confidence: roundWeight(clamp01(weight.confidence)),
28064
+ outcomeCounts,
28065
+ updatedAt: weight.updatedAt
28066
+ };
28067
+ });
28068
+ return {
28069
+ version: 1,
28070
+ updatedAt: record.updatedAt,
28071
+ windowDays: record.windowDays,
28072
+ minEventCount: record.minEventCount,
28073
+ maxWeightMagnitude: record.maxWeightMagnitude,
28074
+ weights
28075
+ };
28076
+ }
28077
+ function resolveUtilityLearningStatePath(memoryDir, utilityTelemetryDir) {
28078
+ return path60.join(resolveUtilityTelemetryDir(memoryDir, utilityTelemetryDir), UTILITY_LEARNING_STATE_FILE);
28079
+ }
28080
+ async function readUtilityLearningSnapshot(memoryDir, utilityTelemetryDir) {
28081
+ const statePath = resolveUtilityLearningStatePath(memoryDir, utilityTelemetryDir);
28082
+ try {
28083
+ const raw = JSON.parse(await readFile37(statePath, "utf8"));
28084
+ return validateUtilityLearningSnapshot(raw);
28085
+ } catch {
28086
+ return null;
28087
+ }
28088
+ }
28089
+ async function writeUtilityLearningSnapshot(statePath, snapshot) {
28090
+ const tempPath = `${statePath}.tmp`;
28091
+ await mkdir41(path60.dirname(statePath), { recursive: true });
28092
+ await writeFile38(tempPath, `${JSON.stringify(snapshot, null, 2)}
28093
+ `, "utf8");
28094
+ await rename4(tempPath, statePath);
28095
+ }
28096
+ async function learnUtilityPromotionWeights(options) {
28097
+ const statePath = resolveUtilityLearningStatePath(options.memoryDir, options.utilityTelemetryDir);
28098
+ if (!options.enabled) {
28099
+ return {
28100
+ applied: false,
28101
+ reason: "disabled",
28102
+ statePath,
28103
+ snapshot: null
28104
+ };
28105
+ }
28106
+ const now = options.now ?? /* @__PURE__ */ new Date();
28107
+ const updatedAt = now.toISOString();
28108
+ const windowDays = coerceLearningWindowDays(options.learningWindowDays);
28109
+ const minEventCount = coerceMinEventCount(options.minEventCount);
28110
+ const maxWeightMagnitude = coerceMaxWeightMagnitude(options.maxWeightMagnitude);
28111
+ const recentEvents = selectRecentEvents(
28112
+ (await readUtilityTelemetryEvents({
28113
+ memoryDir: options.memoryDir,
28114
+ utilityTelemetryDir: options.utilityTelemetryDir
28115
+ })).events,
28116
+ now,
28117
+ windowDays
28118
+ );
28119
+ const grouped = /* @__PURE__ */ new Map();
28120
+ for (const event of recentEvents) {
28121
+ const key = `${event.target}:${event.decision}`;
28122
+ const existing = grouped.get(key);
28123
+ if (existing) {
28124
+ existing.push(event);
28125
+ } else {
28126
+ grouped.set(key, [event]);
28127
+ }
28128
+ }
28129
+ const weights = [];
28130
+ for (const events of grouped.values()) {
28131
+ if (events.length < minEventCount) continue;
28132
+ const target = events[0].target;
28133
+ const decision = events[0].decision;
28134
+ const averageUtilityScore = events.reduce((sum, event) => sum + event.utilityScore, 0) / events.length;
28135
+ const confidence = confidenceFromEvents(events.length, averageUtilityScore);
28136
+ const learnedWeight = roundWeight(
28137
+ clampWeight(averageUtilityScore * confidence, maxWeightMagnitude)
28138
+ );
28139
+ weights.push({
28140
+ target,
28141
+ decision,
28142
+ eventCount: events.length,
28143
+ learnedWeight,
28144
+ averageUtilityScore: roundWeight(averageUtilityScore),
28145
+ confidence,
28146
+ outcomeCounts: outcomeCountsFor(events),
28147
+ updatedAt
28148
+ });
28149
+ }
28150
+ weights.sort((left, right) => {
28151
+ const targetCompare = left.target.localeCompare(right.target);
28152
+ if (targetCompare !== 0) return targetCompare;
28153
+ return left.decision.localeCompare(right.decision);
28154
+ });
28155
+ const snapshot = {
28156
+ version: 1,
28157
+ updatedAt,
28158
+ windowDays,
28159
+ minEventCount,
28160
+ maxWeightMagnitude,
28161
+ weights
28162
+ };
28163
+ if (weights.length === 0) {
28164
+ return {
28165
+ applied: false,
28166
+ reason: "insufficient_events",
28167
+ statePath,
28168
+ snapshot
28169
+ };
28170
+ }
28171
+ await writeUtilityLearningSnapshot(statePath, snapshot);
28172
+ return {
28173
+ applied: true,
28174
+ reason: "learned",
28175
+ statePath,
28176
+ snapshot
28177
+ };
28178
+ }
28179
+ async function getUtilityLearningStatus(options) {
28180
+ const rootDir = resolveUtilityTelemetryDir(options.memoryDir, options.utilityTelemetryDir);
28181
+ const statePath = resolveUtilityLearningStatePath(options.memoryDir, options.utilityTelemetryDir);
28182
+ if (!options.enabled) {
28183
+ return {
28184
+ enabled: false,
28185
+ promotionByOutcomeEnabled: options.promotionByOutcomeEnabled === true,
28186
+ rootDir,
28187
+ statePath,
28188
+ snapshot: null,
28189
+ weights: {
28190
+ total: 0,
28191
+ positive: 0,
28192
+ negative: 0,
28193
+ zero: 0
28194
+ }
28195
+ };
28196
+ }
28197
+ const snapshot = await readUtilityLearningSnapshot(options.memoryDir, options.utilityTelemetryDir);
28198
+ const weights = snapshot?.weights ?? [];
28199
+ return {
28200
+ enabled: true,
28201
+ promotionByOutcomeEnabled: options.promotionByOutcomeEnabled === true,
28202
+ rootDir,
28203
+ statePath,
28204
+ snapshot,
28205
+ weights: {
28206
+ total: weights.length,
28207
+ positive: weights.filter((entry) => entry.learnedWeight > 0).length,
28208
+ negative: weights.filter((entry) => entry.learnedWeight < 0).length,
28209
+ zero: weights.filter((entry) => entry.learnedWeight === 0).length,
28210
+ latestUpdatedAt: snapshot?.updatedAt
28211
+ }
28212
+ };
28213
+ }
28214
+
28215
+ // src/resume-bundles.ts
28216
+ import path61 from "path";
28217
+ import { mkdir as mkdir42, writeFile as writeFile39 } from "fs/promises";
27965
28218
  var DEFAULT_RESUME_BUNDLE_REF_LIMIT = 5;
27966
28219
  function resolveResumeBundleDir(memoryDir, overrideDir) {
27967
28220
  if (typeof overrideDir === "string" && overrideDir.trim().length > 0) {
27968
28221
  return overrideDir.trim();
27969
28222
  }
27970
- return path60.join(memoryDir, "state", "resume-bundles");
28223
+ return path61.join(memoryDir, "state", "resume-bundles");
27971
28224
  }
27972
28225
  function validateResumeBundle(raw) {
27973
28226
  if (!isRecord2(raw)) throw new Error("resume bundle must be an object");
@@ -28011,7 +28264,7 @@ async function readValidatedItems(options) {
28011
28264
  async function readObjectiveStateSnapshotsForSession(options) {
28012
28265
  const rootDir = resolveObjectiveStateStoreDir(options.memoryDir, options.objectiveStateStoreDir);
28013
28266
  const items = await readValidatedItems({
28014
- rootDir: path60.join(rootDir, "snapshots"),
28267
+ rootDir: path61.join(rootDir, "snapshots"),
28015
28268
  validate: validateObjectiveStateSnapshot
28016
28269
  });
28017
28270
  return items.filter((item) => item.sessionKey === options.sessionKey).sort((left, right) => right.recordedAt.localeCompare(left.recordedAt)).slice(0, options.maxResults ?? DEFAULT_RESUME_BUNDLE_REF_LIMIT);
@@ -28019,7 +28272,7 @@ async function readObjectiveStateSnapshotsForSession(options) {
28019
28272
  async function readWorkProductEntriesForSession(options) {
28020
28273
  const rootDir = resolveWorkProductLedgerDir(options.memoryDir, options.workProductLedgerDir);
28021
28274
  const items = await readValidatedItems({
28022
- rootDir: path60.join(rootDir, "entries"),
28275
+ rootDir: path61.join(rootDir, "entries"),
28023
28276
  validate: validateWorkProductLedgerEntry
28024
28277
  });
28025
28278
  return items.filter((item) => item.sessionKey === options.sessionKey).sort((left, right) => right.recordedAt.localeCompare(left.recordedAt)).slice(0, options.maxResults ?? DEFAULT_RESUME_BUNDLE_REF_LIMIT);
@@ -28027,7 +28280,7 @@ async function readWorkProductEntriesForSession(options) {
28027
28280
  async function readCommitmentEntriesForSession(options) {
28028
28281
  const rootDir = resolveCommitmentLedgerDir(options.memoryDir, options.commitmentLedgerDir);
28029
28282
  const items = await readValidatedItems({
28030
- rootDir: path60.join(rootDir, "entries"),
28283
+ rootDir: path61.join(rootDir, "entries"),
28031
28284
  validate: validateCommitmentLedgerEntry
28032
28285
  });
28033
28286
  return items.filter((item) => item.sessionKey === options.sessionKey).filter((item) => options.state ? item.state === options.state : true).sort((left, right) => right.recordedAt.localeCompare(left.recordedAt)).slice(0, options.maxResults ?? DEFAULT_RESUME_BUNDLE_REF_LIMIT);
@@ -28123,15 +28376,15 @@ async function recordResumeBundle(options) {
28123
28376
  const rootDir = resolveResumeBundleDir(options.memoryDir, options.resumeBundleDir);
28124
28377
  const validated = validateResumeBundle(options.bundle);
28125
28378
  const day = recordStoreDay(validated.recordedAt);
28126
- const bundlesDir = path60.join(rootDir, "bundles", day);
28127
- const filePath = path60.join(bundlesDir, `${validated.bundleId}.json`);
28128
- await mkdir41(bundlesDir, { recursive: true });
28129
- await writeFile38(filePath, JSON.stringify(validated, null, 2), "utf8");
28379
+ const bundlesDir = path61.join(rootDir, "bundles", day);
28380
+ const filePath = path61.join(bundlesDir, `${validated.bundleId}.json`);
28381
+ await mkdir42(bundlesDir, { recursive: true });
28382
+ await writeFile39(filePath, JSON.stringify(validated, null, 2), "utf8");
28130
28383
  return filePath;
28131
28384
  }
28132
28385
  async function readResumeBundles(options) {
28133
28386
  const rootDir = resolveResumeBundleDir(options.memoryDir, options.resumeBundleDir);
28134
- const files = await listJsonFiles(path60.join(rootDir, "bundles"));
28387
+ const files = await listJsonFiles(path61.join(rootDir, "bundles"));
28135
28388
  const bundles = [];
28136
28389
  const invalidBundles = [];
28137
28390
  for (const filePath of files) {
@@ -28148,7 +28401,7 @@ async function readResumeBundles(options) {
28148
28401
  }
28149
28402
  async function getResumeBundleStatus(options) {
28150
28403
  const rootDir = resolveResumeBundleDir(options.memoryDir, options.resumeBundleDir);
28151
- const bundlesDir = path60.join(rootDir, "bundles");
28404
+ const bundlesDir = path61.join(rootDir, "bundles");
28152
28405
  if (!options.enabled) {
28153
28406
  return {
28154
28407
  enabled: false,
@@ -28592,6 +28845,22 @@ async function runUtilityTelemetryRecordCliCommand(options) {
28592
28845
  event: options.event
28593
28846
  });
28594
28847
  }
28848
+ async function runUtilityLearningStatusCliCommand(options) {
28849
+ return getUtilityLearningStatus({
28850
+ memoryDir: options.memoryDir,
28851
+ enabled: options.memoryUtilityLearningEnabled,
28852
+ promotionByOutcomeEnabled: options.promotionByOutcomeEnabled
28853
+ });
28854
+ }
28855
+ async function runUtilityLearningCliCommand(options) {
28856
+ return learnUtilityPromotionWeights({
28857
+ memoryDir: options.memoryDir,
28858
+ enabled: options.memoryUtilityLearningEnabled,
28859
+ learningWindowDays: options.learningWindowDays ?? 14,
28860
+ minEventCount: options.minEventCount ?? 3,
28861
+ maxWeightMagnitude: options.maxWeightMagnitude ?? 0.35
28862
+ });
28863
+ }
28595
28864
  async function runWorkProductRecordCliCommand(options) {
28596
28865
  if (!options.creationMemoryEnabled) return null;
28597
28866
  return recordWorkProductLedgerEntry({
@@ -28939,7 +29208,7 @@ function policyVersionForValues(values, config) {
28939
29208
  return createHash10("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 12);
28940
29209
  }
28941
29210
  async function readRuntimePolicySnapshot2(config, fileName) {
28942
- const filePath = path61.join(config.memoryDir, "state", fileName);
29211
+ const filePath = path62.join(config.memoryDir, "state", fileName);
28943
29212
  const snapshot = await readRuntimePolicySnapshot(filePath, {
28944
29213
  maxStaleDecayThreshold: config.lifecycleArchiveDecayThreshold
28945
29214
  });
@@ -29439,7 +29708,7 @@ async function withTimeout(promise, timeoutMs, timeoutMessage) {
29439
29708
  }
29440
29709
  async function runReplayCliCommand(orchestrator, options) {
29441
29710
  const extractionIdleTimeoutMs = Number.isFinite(options.extractionIdleTimeoutMs) ? Math.max(1e3, Math.floor(options.extractionIdleTimeoutMs)) : 15 * 6e4;
29442
- const inputRaw = await readFile37(options.inputPath, "utf-8");
29711
+ const inputRaw = await readFile38(options.inputPath, "utf-8");
29443
29712
  const registry = buildReplayNormalizerRegistry([
29444
29713
  openclawReplayNormalizer,
29445
29714
  claudeReplayNormalizer,
@@ -29504,7 +29773,7 @@ async function runReplayCliCommand(orchestrator, options) {
29504
29773
  async function getPluginVersion() {
29505
29774
  try {
29506
29775
  const pkgPath = new URL("../package.json", import.meta.url);
29507
- const raw = await readFile37(pkgPath, "utf-8");
29776
+ const raw = await readFile38(pkgPath, "utf-8");
29508
29777
  const parsed = JSON.parse(raw);
29509
29778
  return parsed.version ?? "unknown";
29510
29779
  } catch {
@@ -29523,14 +29792,14 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
29523
29792
  const ns = (namespace ?? "").trim();
29524
29793
  if (!ns) return orchestrator.config.memoryDir;
29525
29794
  if (!orchestrator.config.namespacesEnabled) return orchestrator.config.memoryDir;
29526
- const candidate = path61.join(orchestrator.config.memoryDir, "namespaces", ns);
29795
+ const candidate = path62.join(orchestrator.config.memoryDir, "namespaces", ns);
29527
29796
  if (ns === orchestrator.config.defaultNamespace) {
29528
29797
  return await exists2(candidate) ? candidate : orchestrator.config.memoryDir;
29529
29798
  }
29530
29799
  return candidate;
29531
29800
  }
29532
29801
  async function readAllMemoryFiles(memoryDir) {
29533
- const roots = [path61.join(memoryDir, "facts"), path61.join(memoryDir, "corrections")];
29802
+ const roots = [path62.join(memoryDir, "facts"), path62.join(memoryDir, "corrections")];
29534
29803
  const out = [];
29535
29804
  const walk = async (dir) => {
29536
29805
  let entries;
@@ -29541,14 +29810,14 @@ async function readAllMemoryFiles(memoryDir) {
29541
29810
  }
29542
29811
  for (const entry of entries) {
29543
29812
  const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
29544
- const fullPath = path61.join(dir, entryName);
29813
+ const fullPath = path62.join(dir, entryName);
29545
29814
  if (entry.isDirectory()) {
29546
29815
  await walk(fullPath);
29547
29816
  continue;
29548
29817
  }
29549
29818
  if (!entry.isFile() || !entryName.endsWith(".md")) continue;
29550
29819
  try {
29551
- const raw = await readFile37(fullPath, "utf-8");
29820
+ const raw = await readFile38(fullPath, "utf-8");
29552
29821
  const parsed = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
29553
29822
  if (!parsed) continue;
29554
29823
  const fmRaw = parsed[1];
@@ -29954,6 +30223,30 @@ function registerCli(api, orchestrator) {
29954
30223
  console.log(JSON.stringify({ wrote: filePath !== null, filePath }, null, 2));
29955
30224
  console.log("OK");
29956
30225
  });
30226
+ cmd.command("utility-learning-status").description("Show offline utility-learning snapshot status and learned weight counts").action(async () => {
30227
+ const status = await runUtilityLearningStatusCliCommand({
30228
+ memoryDir: orchestrator.config.memoryDir,
30229
+ memoryUtilityLearningEnabled: orchestrator.config.memoryUtilityLearningEnabled,
30230
+ promotionByOutcomeEnabled: orchestrator.config.promotionByOutcomeEnabled
30231
+ });
30232
+ console.log(JSON.stringify(status, null, 2));
30233
+ console.log("OK");
30234
+ });
30235
+ cmd.command("utility-learn").description("Learn bounded offline promotion/ranking weights from recorded utility telemetry").option("--window-days <days>", "Telemetry lookback window in days", "14").option("--min-event-count <count>", "Minimum event count required per target/decision group", "3").option("--max-weight-magnitude <value>", "Maximum absolute learned weight magnitude", "0.35").action(async (...args) => {
30236
+ const options = args[0] ?? {};
30237
+ const learningWindowDays = typeof options.windowDays === "string" ? Number.parseInt(options.windowDays, 10) : 14;
30238
+ const minEventCount = typeof options.minEventCount === "string" ? Number.parseInt(options.minEventCount, 10) : 3;
30239
+ const maxWeightMagnitude = typeof options.maxWeightMagnitude === "string" ? Number.parseFloat(options.maxWeightMagnitude) : 0.35;
30240
+ const result = await runUtilityLearningCliCommand({
30241
+ memoryDir: orchestrator.config.memoryDir,
30242
+ memoryUtilityLearningEnabled: orchestrator.config.memoryUtilityLearningEnabled,
30243
+ learningWindowDays,
30244
+ minEventCount,
30245
+ maxWeightMagnitude
30246
+ });
30247
+ console.log(JSON.stringify(result, null, 2));
30248
+ console.log("OK");
30249
+ });
29957
30250
  cmd.command("resume-bundle-status").description("Show resume bundle status, bundle counts, and the latest recorded handoff bundle").action(async () => {
29958
30251
  const status = await runResumeBundleStatusCliCommand({
29959
30252
  memoryDir: orchestrator.config.memoryDir,
@@ -30860,7 +31153,7 @@ function registerCli(api, orchestrator) {
30860
31153
  }
30861
31154
  });
30862
31155
  cmd.command("identity").description("Show agent identity reflections").action(async () => {
30863
- const workspaceDir = path61.join(process.env.HOME ?? "~", ".openclaw", "workspace");
31156
+ const workspaceDir = path62.join(process.env.HOME ?? "~", ".openclaw", "workspace");
30864
31157
  const identity = await orchestrator.storage.readIdentity(workspaceDir);
30865
31158
  if (!identity) {
30866
31159
  console.log("No identity file found.");
@@ -31083,8 +31376,8 @@ function registerCli(api, orchestrator) {
31083
31376
  const options = args[0] ?? {};
31084
31377
  const threadId = options.thread;
31085
31378
  const top = parseInt(options.top ?? "10", 10);
31086
- const memoryDir = path61.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
31087
- const threading = new ThreadingManager(path61.join(memoryDir, "threads"));
31379
+ const memoryDir = path62.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
31380
+ const threading = new ThreadingManager(path62.join(memoryDir, "threads"));
31088
31381
  if (threadId) {
31089
31382
  const thread = await threading.loadThread(threadId);
31090
31383
  if (!thread) {
@@ -31560,9 +31853,9 @@ async function recordObjectiveStateSnapshotsFromAgentMessages(options) {
31560
31853
  }
31561
31854
 
31562
31855
  // src/index.ts
31563
- import { readFile as readFile38, writeFile as writeFile39 } from "fs/promises";
31856
+ import { readFile as readFile39, writeFile as writeFile40 } from "fs/promises";
31564
31857
  import { readFileSync as readFileSync4 } from "fs";
31565
- import path62 from "path";
31858
+ import path63 from "path";
31566
31859
  import os6 from "os";
31567
31860
  var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
31568
31861
  var ENGRAM_HOOK_APIS = "__openclawEngramHookApis";
@@ -31570,7 +31863,7 @@ function loadPluginConfigFromFile() {
31570
31863
  try {
31571
31864
  const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
31572
31865
  const homeDir = process.env.HOME ?? os6.homedir();
31573
- const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path62.join(homeDir, ".openclaw", "openclaw.json");
31866
+ const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path63.join(homeDir, ".openclaw", "openclaw.json");
31574
31867
  const content = readFileSync4(configPath, "utf-8");
31575
31868
  const config = JSON.parse(content);
31576
31869
  const pluginEntry = config?.plugins?.entries?.["openclaw-engram"];
@@ -31820,11 +32113,11 @@ Use this context naturally when relevant. Never quote or expose this memory cont
31820
32113
  `session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
31821
32114
  );
31822
32115
  const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
31823
- const signalPath = path62.join(
32116
+ const signalPath = path63.join(
31824
32117
  workspaceDir,
31825
32118
  `.compaction-reset-signal-${safeSessionKey}`
31826
32119
  );
31827
- await writeFile39(
32120
+ await writeFile40(
31828
32121
  signalPath,
31829
32122
  JSON.stringify({
31830
32123
  sessionKey,
@@ -31851,11 +32144,11 @@ Use this context naturally when relevant. Never quote or expose this memory cont
31851
32144
  );
31852
32145
  async function ensureHourlySummaryCron(api2) {
31853
32146
  const jobId = "engram-hourly-summary";
31854
- const cronFilePath = path62.join(os6.homedir(), ".openclaw", "cron", "jobs.json");
32147
+ const cronFilePath = path63.join(os6.homedir(), ".openclaw", "cron", "jobs.json");
31855
32148
  try {
31856
32149
  let jobsData = { version: 1, jobs: [] };
31857
32150
  try {
31858
- const content = await readFile38(cronFilePath, "utf-8");
32151
+ const content = await readFile39(cronFilePath, "utf-8");
31859
32152
  jobsData = JSON.parse(content);
31860
32153
  } catch {
31861
32154
  }
@@ -31892,7 +32185,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
31892
32185
  state: {}
31893
32186
  };
31894
32187
  jobsData.jobs.push(newJob);
31895
- await writeFile39(cronFilePath, JSON.stringify(jobsData, null, 2), "utf-8");
32188
+ await writeFile40(cronFilePath, JSON.stringify(jobsData, null, 2), "utf-8");
31896
32189
  log.info("auto-registered hourly summary cron job");
31897
32190
  } catch (err) {
31898
32191
  log.error("failed to auto-register hourly summary cron job:", err);