@joshuaswarren/openclaw-engram 8.3.75 → 8.3.76

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/dist/index.js CHANGED
@@ -8805,6 +8805,18 @@ function clampGraphRecallExpandedEntries(entries, maxEntries = 64) {
8805
8805
  };
8806
8806
  }).filter((item) => item.path.length > 0 && item.namespace.length > 0).slice(0, limit);
8807
8807
  }
8808
+ var DEFAULT_TIER_MIGRATION_STATUS = {
8809
+ updatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
8810
+ lastCycle: null,
8811
+ totals: {
8812
+ cycles: 0,
8813
+ scanned: 0,
8814
+ migrated: 0,
8815
+ promoted: 0,
8816
+ demoted: 0,
8817
+ errors: 0
8818
+ }
8819
+ };
8808
8820
  var LastRecallStore = class {
8809
8821
  statePath;
8810
8822
  impressionsPath;
@@ -8870,6 +8882,70 @@ var LastRecallStore = class {
8870
8882
  }
8871
8883
  }
8872
8884
  };
8885
+ var TierMigrationStatusStore = class {
8886
+ statePath;
8887
+ state = structuredClone(DEFAULT_TIER_MIGRATION_STATUS);
8888
+ constructor(memoryDir) {
8889
+ this.statePath = path10.join(memoryDir, "state", "tier-migration-status.json");
8890
+ }
8891
+ async load() {
8892
+ try {
8893
+ const raw = await readFile8(this.statePath, "utf-8");
8894
+ const parsed = JSON.parse(raw);
8895
+ if (!parsed || typeof parsed !== "object") {
8896
+ this.state = structuredClone(DEFAULT_TIER_MIGRATION_STATUS);
8897
+ return;
8898
+ }
8899
+ const totals = parsed.totals && typeof parsed.totals === "object" ? parsed.totals : DEFAULT_TIER_MIGRATION_STATUS.totals;
8900
+ this.state = {
8901
+ updatedAt: typeof parsed.updatedAt === "string" && parsed.updatedAt.length > 0 ? parsed.updatedAt : DEFAULT_TIER_MIGRATION_STATUS.updatedAt,
8902
+ lastCycle: parsed.lastCycle && typeof parsed.lastCycle === "object" ? parsed.lastCycle : null,
8903
+ totals: {
8904
+ cycles: typeof totals.cycles === "number" && Number.isFinite(totals.cycles) ? totals.cycles : 0,
8905
+ scanned: typeof totals.scanned === "number" && Number.isFinite(totals.scanned) ? totals.scanned : 0,
8906
+ migrated: typeof totals.migrated === "number" && Number.isFinite(totals.migrated) ? totals.migrated : 0,
8907
+ promoted: typeof totals.promoted === "number" && Number.isFinite(totals.promoted) ? totals.promoted : 0,
8908
+ demoted: typeof totals.demoted === "number" && Number.isFinite(totals.demoted) ? totals.demoted : 0,
8909
+ errors: typeof totals.errors === "number" && Number.isFinite(totals.errors) ? totals.errors : 0
8910
+ }
8911
+ };
8912
+ } catch {
8913
+ this.state = structuredClone(DEFAULT_TIER_MIGRATION_STATUS);
8914
+ }
8915
+ }
8916
+ get() {
8917
+ return {
8918
+ updatedAt: this.state.updatedAt,
8919
+ lastCycle: this.state.lastCycle ? { ...this.state.lastCycle } : null,
8920
+ totals: { ...this.state.totals }
8921
+ };
8922
+ }
8923
+ async recordCycle(summary) {
8924
+ const now = (/* @__PURE__ */ new Date()).toISOString();
8925
+ const migratedDelta = summary.dryRun ? 0 : Math.max(0, summary.migrated);
8926
+ const promotedDelta = summary.dryRun ? 0 : Math.max(0, summary.promoted);
8927
+ const demotedDelta = summary.dryRun ? 0 : Math.max(0, summary.demoted);
8928
+ const next = {
8929
+ updatedAt: now,
8930
+ lastCycle: { ...summary },
8931
+ totals: {
8932
+ cycles: this.state.totals.cycles + 1,
8933
+ scanned: this.state.totals.scanned + Math.max(0, summary.scanned),
8934
+ migrated: this.state.totals.migrated + migratedDelta,
8935
+ promoted: this.state.totals.promoted + promotedDelta,
8936
+ demoted: this.state.totals.demoted + demotedDelta,
8937
+ errors: this.state.totals.errors + Math.max(0, summary.errorCount ?? 0)
8938
+ }
8939
+ };
8940
+ this.state = next;
8941
+ try {
8942
+ await mkdir8(path10.dirname(this.statePath), { recursive: true });
8943
+ await writeFile8(this.statePath, JSON.stringify(next, null, 2), "utf-8");
8944
+ } catch (err) {
8945
+ log.debug(`tier migration status write failed: ${err}`);
8946
+ }
8947
+ }
8948
+ };
8873
8949
 
8874
8950
  // src/session-observer-state.ts
8875
8951
  import path11 from "path";
@@ -13218,6 +13294,7 @@ var Orchestrator = class _Orchestrator {
13218
13294
  relevance;
13219
13295
  negatives;
13220
13296
  lastRecall;
13297
+ tierMigrationStatus;
13221
13298
  embeddingFallback;
13222
13299
  conversationIndexDir;
13223
13300
  extraction;
@@ -13311,6 +13388,7 @@ var Orchestrator = class _Orchestrator {
13311
13388
  this.relevance = new RelevanceStore(config.memoryDir);
13312
13389
  this.negatives = new NegativeExampleStore(config.memoryDir);
13313
13390
  this.lastRecall = new LastRecallStore(config.memoryDir);
13391
+ this.tierMigrationStatus = new TierMigrationStatusStore(config.memoryDir);
13314
13392
  this.sessionObserver = new SessionObserverState({
13315
13393
  memoryDir: config.memoryDir,
13316
13394
  debounceMs: config.sessionObserverDebounceMs ?? 12e4,
@@ -13455,6 +13533,7 @@ var Orchestrator = class _Orchestrator {
13455
13533
  await this.relevance.load();
13456
13534
  await this.negatives.load();
13457
13535
  await this.lastRecall.load();
13536
+ await this.tierMigrationStatus.load();
13458
13537
  await this.sessionObserver.load();
13459
13538
  if (this.config.factDeduplicationEnabled) {
13460
13539
  const stateDir2 = path26.join(this.config.memoryDir, "state");
@@ -15047,13 +15126,69 @@ _Context: ${topQuestion.context}_`);
15047
15126
  this.requestQmdMaintenance();
15048
15127
  await this.runTierMigrationCycle(storage, "extraction");
15049
15128
  }
15050
- async runTierMigrationCycle(storage, trigger) {
15051
- if (!this.config.qmdTierMigrationEnabled) return;
15052
- if (trigger === "maintenance" && !this.config.qmdTierAutoBackfillEnabled) return;
15053
- if (this.tierMigrationInFlight) return;
15054
- const budget = this.compounding?.tierMigrationCycleBudget(trigger) ?? defaultTierMigrationCycleBudget(this.config, trigger);
15129
+ async runTierMigrationCycle(storage, trigger, options) {
15130
+ const dryRun = options?.dryRun === true;
15131
+ const persistSkipped = options?.force === true || trigger === "manual";
15132
+ if (!this.config.qmdTierMigrationEnabled && options?.force !== true) {
15133
+ const skipped = {
15134
+ trigger,
15135
+ scanned: 0,
15136
+ migrated: 0,
15137
+ promoted: 0,
15138
+ demoted: 0,
15139
+ limit: 0,
15140
+ dryRun,
15141
+ skipped: "tier_migration_disabled"
15142
+ };
15143
+ if (persistSkipped) await this.tierMigrationStatus.recordCycle(skipped);
15144
+ return skipped;
15145
+ }
15146
+ if (trigger === "maintenance" && !this.config.qmdTierAutoBackfillEnabled && options?.force !== true) {
15147
+ const skipped = {
15148
+ trigger,
15149
+ scanned: 0,
15150
+ migrated: 0,
15151
+ promoted: 0,
15152
+ demoted: 0,
15153
+ limit: 0,
15154
+ dryRun,
15155
+ skipped: "maintenance_backfill_disabled"
15156
+ };
15157
+ if (persistSkipped) await this.tierMigrationStatus.recordCycle(skipped);
15158
+ return skipped;
15159
+ }
15160
+ if (this.tierMigrationInFlight) {
15161
+ const skipped = {
15162
+ trigger,
15163
+ scanned: 0,
15164
+ migrated: 0,
15165
+ promoted: 0,
15166
+ demoted: 0,
15167
+ limit: 0,
15168
+ dryRun,
15169
+ skipped: "migration_in_flight"
15170
+ };
15171
+ if (persistSkipped) await this.tierMigrationStatus.recordCycle(skipped);
15172
+ return skipped;
15173
+ }
15174
+ const budgetTrigger = trigger === "manual" ? "maintenance" : trigger;
15175
+ const budget = this.compounding?.tierMigrationCycleBudget(budgetTrigger) ?? defaultTierMigrationCycleBudget(this.config, budgetTrigger);
15176
+ const limit = options?.limitOverride !== void 0 ? Math.max(0, Math.floor(options.limitOverride)) : budget.limit;
15055
15177
  const nowMs = Date.now();
15056
- if (nowMs - this.lastTierMigrationRunAtMs < budget.minIntervalMs) return;
15178
+ if (options?.force !== true && nowMs - this.lastTierMigrationRunAtMs < budget.minIntervalMs) {
15179
+ const skipped = {
15180
+ trigger,
15181
+ scanned: 0,
15182
+ migrated: 0,
15183
+ promoted: 0,
15184
+ demoted: 0,
15185
+ limit,
15186
+ dryRun,
15187
+ skipped: "min_interval"
15188
+ };
15189
+ if (persistSkipped) await this.tierMigrationStatus.recordCycle(skipped);
15190
+ return skipped;
15191
+ }
15057
15192
  const policy = {
15058
15193
  enabled: this.config.qmdTierMigrationEnabled,
15059
15194
  demotionMinAgeDays: this.config.qmdTierDemotionMinAgeDays,
@@ -15083,29 +15218,70 @@ _Context: ${topQuestion.context}_`);
15083
15218
  autoEmbed: this.config.qmdAutoEmbedEnabled
15084
15219
  });
15085
15220
  let migrated = 0;
15221
+ let promoted = 0;
15222
+ let demoted = 0;
15086
15223
  for (const candidate of candidates) {
15087
- if (migrated >= budget.limit) break;
15224
+ if (migrated >= limit) break;
15088
15225
  const decision = decideTierTransition(candidate.memory, candidate.tier, policy, now);
15089
15226
  if (!decision.changed) continue;
15090
- const res = await migration.migrateMemory({
15091
- memory: candidate.memory,
15092
- fromTier: candidate.tier,
15093
- toTier: decision.nextTier,
15094
- reason: `${trigger}:${decision.reason}`
15095
- });
15096
- if (res.changed) migrated += 1;
15227
+ if (!dryRun) {
15228
+ const res = await migration.migrateMemory({
15229
+ memory: candidate.memory,
15230
+ fromTier: candidate.tier,
15231
+ toTier: decision.nextTier,
15232
+ reason: `${trigger}:${decision.reason}`
15233
+ });
15234
+ if (!res.changed) continue;
15235
+ }
15236
+ migrated += 1;
15237
+ if (decision.nextTier === "cold") demoted += 1;
15238
+ if (decision.nextTier === "hot") promoted += 1;
15097
15239
  }
15098
- this.lastTierMigrationRunAtMs = Date.now();
15240
+ if (!dryRun) this.lastTierMigrationRunAtMs = Date.now();
15099
15241
  log.debug(
15100
- `tier migration cycle completed: trigger=${trigger} scanned=${candidates.length} migrated=${migrated} limit=${budget.limit}`
15242
+ `tier migration cycle completed: trigger=${trigger} scanned=${candidates.length} migrated=${migrated} limit=${limit}${dryRun ? " dryRun=true" : ""}`
15101
15243
  );
15244
+ const summary = {
15245
+ trigger,
15246
+ scanned: candidates.length,
15247
+ migrated,
15248
+ promoted,
15249
+ demoted,
15250
+ limit,
15251
+ dryRun
15252
+ };
15253
+ const shouldPersistCycle = trigger === "manual" || migrated > 0;
15254
+ if (shouldPersistCycle) await this.tierMigrationStatus.recordCycle(summary);
15255
+ return summary;
15102
15256
  } catch (err) {
15103
15257
  this.lastTierMigrationRunAtMs = Date.now();
15104
15258
  log.warn(`tier migration cycle failed (${trigger}, fail-open): ${err}`);
15259
+ const failed = {
15260
+ trigger,
15261
+ scanned: 0,
15262
+ migrated: 0,
15263
+ promoted: 0,
15264
+ demoted: 0,
15265
+ limit,
15266
+ dryRun,
15267
+ errorCount: 1
15268
+ };
15269
+ await this.tierMigrationStatus.recordCycle(failed);
15270
+ return failed;
15105
15271
  } finally {
15106
15272
  this.tierMigrationInFlight = false;
15107
15273
  }
15108
15274
  }
15275
+ async getTierMigrationStatus() {
15276
+ return this.tierMigrationStatus.get();
15277
+ }
15278
+ async runTierMigrationNow(options) {
15279
+ return this.runTierMigrationCycle(this.storage, "manual", {
15280
+ dryRun: options?.dryRun === true,
15281
+ limitOverride: options?.limit,
15282
+ force: false
15283
+ });
15284
+ }
15109
15285
  maybeScheduleConsolidation(nonZeroExtraction) {
15110
15286
  if (this.config.consolidationRequireNonZeroExtraction && !nonZeroExtraction) return;
15111
15287
  if (this.nonZeroExtractionsSinceConsolidation < this.config.consolidateEveryN) return;
@@ -21523,6 +21699,15 @@ async function runGraphHealthCliCommand(options) {
21523
21699
  includeRepairGuidance: options.includeRepairGuidance
21524
21700
  });
21525
21701
  }
21702
+ async function runTierStatusCliCommand(orchestrator) {
21703
+ return orchestrator.getTierMigrationStatus();
21704
+ }
21705
+ async function runTierMigrateCliCommand(orchestrator, options = {}) {
21706
+ return orchestrator.runTierMigrationNow({
21707
+ dryRun: options.dryRun === true,
21708
+ limit: options.limit
21709
+ });
21710
+ }
21526
21711
  function incrementCounter(target, key) {
21527
21712
  const normalized = key && key.length > 0 ? key : "unknown";
21528
21713
  target[normalized] = (target[normalized] ?? 0) + 1;
@@ -22226,6 +22411,22 @@ function registerCli(api, orchestrator) {
22226
22411
  console.log(JSON.stringify(report, null, 2));
22227
22412
  console.log("OK");
22228
22413
  });
22414
+ cmd.command("tier-status").description("Show tier migration telemetry and last-cycle summary").action(async () => {
22415
+ const status = await runTierStatusCliCommand(orchestrator);
22416
+ console.log(JSON.stringify(status, null, 2));
22417
+ console.log("OK");
22418
+ });
22419
+ cmd.command("tier-migrate").description("Run one tier migration pass (dry-run by default)").option("--dry-run", "Evaluate and report moves without writing").option("--write", "Apply migration writes (default: dry-run)").option("--limit <n>", "Override migration move limit for this run").action(async (...args) => {
22420
+ const options = args[0] ?? {};
22421
+ const limitRaw = parseInt(String(options.limit ?? ""), 10);
22422
+ const explicitDryRun = options.dryRun === true;
22423
+ const summary = await runTierMigrateCliCommand(orchestrator, {
22424
+ dryRun: explicitDryRun || options.write !== true,
22425
+ limit: Number.isFinite(limitRaw) ? Math.max(0, limitRaw) : void 0
22426
+ });
22427
+ console.log(JSON.stringify(summary, null, 2));
22428
+ console.log("OK");
22429
+ });
22229
22430
  cmd.command("action-audit").description("Show namespace-aware memory action policy outcomes").option("--namespace <name>", "Filter to a single namespace").option("--limit <n>", "Max events to read per namespace", "200").action(async (...args) => {
22230
22431
  const options = args[0] ?? {};
22231
22432
  const limitRaw = parseInt(String(options.limit ?? "200"), 10);