@joshuaswarren/openclaw-engram 9.0.51 → 9.0.53

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
@@ -32,6 +32,8 @@ AI agents forget everything between conversations. Engram fixes that.
32
32
  - **Memory OS features** — Graph recall, temporal memory tree, lifecycle policy, compounding, shared context, memory boxes, and identity continuity can be enabled progressively as your install grows.
33
33
  - **Benchmark-first roadmap** — Engram now has an evaluation harness with live shadow recall recording and a CI benchmark delta gate, so memory improvements can be measured and regression-checked instead of argued from anecdotes.
34
34
  - **Baseline snapshot discipline** — Engram can now, when `benchmarkBaselineSnapshotsEnabled` is enabled, capture typed baseline snapshots of the latest completed benchmark runs so later PR delta reporting can compare candidates against a stable stored reference instead of an ad hoc branch state.
35
+ - **Named baseline delta reporting** — Engram can now, when `benchmarkDeltaReporterEnabled` is enabled, compare the current eval store against a stored baseline snapshot, emit a machine-readable delta report plus markdown summary, and fail fast when a candidate regresses a benchmark that previously passed.
36
+ - **Required CI baseline gate** — Engram's `eval-benchmark-gate` workflow now reads a named stored baseline snapshot from the base branch fixture store and blocks merges when the candidate branch regresses relative to that required baseline.
35
37
  - **Objective-state recall** — Engram can now store normalized file, process, and tool outcomes and, when `objectiveStateRecallEnabled` is enabled, inject the most relevant objective-state snapshots back into recall context as a separate `Objective State` section.
36
38
  - **Causal trajectory graph foundation** — Engram can now persist typed `goal -> action -> observation -> outcome -> follow-up` chains when `causalTrajectoryMemoryEnabled` is enabled and, with `actionGraphRecallEnabled`, emit deterministic action-conditioned edges into the causal graph for later trajectory-aware retrieval.
37
39
  - **Causal trajectory recall** — Engram can now, when `causalTrajectoryRecallEnabled` is enabled, inject prompt-relevant causal chains back into recall context as a separate `Causal Trajectories` section with lightweight match explainability.
@@ -172,6 +174,7 @@ openclaw engram benchmark-status # Benchmark/eval harness packs, run
172
174
  openclaw engram benchmark-validate <path> # Validate a benchmark manifest or pack directory
173
175
  openclaw engram benchmark-import <path> # Import a validated benchmark pack into the eval store
174
176
  openclaw engram benchmark-baseline-snapshot # Capture a typed baseline snapshot of the latest completed benchmark runs
177
+ openclaw engram benchmark-baseline-report # Compare the current eval store against a stored baseline snapshot
175
178
  openclaw engram benchmark-ci-gate # Compare base vs candidate eval stores and fail on regressions
176
179
  openclaw engram objective-state-status # Objective-state snapshot counts and latest stored snapshot
177
180
  openclaw engram causal-trajectory-status # Causal-trajectory record counts and latest stored chain
@@ -212,6 +215,13 @@ Key settings:
212
215
  | `evalHarnessEnabled` | `false` | Enable the evaluation harness for benchmark packs, run summaries, and shadow recall bookkeeping |
213
216
  | `evalShadowModeEnabled` | `false` | Record live recall decisions to the eval store without changing injected output |
214
217
  | `benchmarkBaselineSnapshotsEnabled` | `false` | Enable versioned baseline snapshot artifacts for the latest completed benchmark runs |
218
+ | `benchmarkDeltaReporterEnabled` | `false` | Enable named-baseline delta reports against the current eval store |
219
+
220
+ The repo's required benchmark check uses the committed fixture snapshot at
221
+ `tests/fixtures/eval-ci/store/baselines/required-main.json` as the stable
222
+ release baseline for PR gating. During the rollout PR that first introduces
223
+ that file, the gate bootstraps from the candidate branch snapshot once; after
224
+ that, PRs resolve the required baseline from the base branch checkout.
215
225
  | `evalStoreDir` | `{memoryDir}/state/evals` | Root directory for benchmark packs, run summaries, and shadow recall records |
216
226
  | `objectiveStateMemoryEnabled` | `false` | Enable the objective-state memory foundation for normalized world/tool state snapshots |
217
227
  | `objectiveStateSnapshotWritesEnabled` | `false` | Permit objective-state snapshot writers to persist typed state records |
package/dist/index.js CHANGED
@@ -288,6 +288,7 @@ function parseConfig(raw) {
288
288
  evalHarnessEnabled: cfg.evalHarnessEnabled === true,
289
289
  evalShadowModeEnabled: cfg.evalShadowModeEnabled === true,
290
290
  benchmarkBaselineSnapshotsEnabled: cfg.benchmarkBaselineSnapshotsEnabled === true,
291
+ benchmarkDeltaReporterEnabled: cfg.benchmarkDeltaReporterEnabled === true,
291
292
  evalStoreDir: typeof cfg.evalStoreDir === "string" && cfg.evalStoreDir.trim().length > 0 ? cfg.evalStoreDir.trim() : path.join(memoryDir, "state", "evals"),
292
293
  objectiveStateMemoryEnabled: cfg.objectiveStateMemoryEnabled === true,
293
294
  objectiveStateSnapshotWritesEnabled: cfg.objectiveStateSnapshotWritesEnabled === true,
@@ -11908,6 +11909,48 @@ function compareMetricDeltas(baseMetrics, candidateMetrics) {
11908
11909
  }
11909
11910
  return { deltas, regressions, improvements };
11910
11911
  }
11912
+ function formatEvalBaselineDeltaMarkdown(report) {
11913
+ const lines = [
11914
+ "# Eval Baseline Delta Report",
11915
+ "",
11916
+ `- Passed: ${report.passed ? "yes" : "no"}`,
11917
+ `- Baseline snapshot: ${report.baselineSnapshotId}`,
11918
+ `- Baseline created: ${report.baselineCreatedAt}`,
11919
+ `- Baseline source root: ${report.baselineSourceRootDir}`,
11920
+ `- Candidate root: ${report.candidateRootDir}`,
11921
+ `- Benchmarks compared: ${report.comparedBenchmarks}`
11922
+ ];
11923
+ if (report.missingCandidateBenchmarks.length > 0) {
11924
+ lines.push(`- Missing candidate benchmarks: ${report.missingCandidateBenchmarks.join(", ")}`);
11925
+ }
11926
+ lines.push(
11927
+ `- Invalid candidate artifacts: benchmarks=${report.invalidArtifacts.candidate.benchmarks}, runs=${report.invalidArtifacts.candidate.runs}, shadows=${report.invalidArtifacts.candidate.shadows}, baselines=${report.invalidArtifacts.candidate.baselines}`,
11928
+ "",
11929
+ "## Regressions"
11930
+ );
11931
+ if (report.regressions.length === 0) {
11932
+ lines.push("- none");
11933
+ } else {
11934
+ for (const regression of report.regressions) lines.push(`- ${regression}`);
11935
+ }
11936
+ lines.push("", "## Improvements");
11937
+ if (report.improvements.length === 0) {
11938
+ lines.push("- none");
11939
+ } else {
11940
+ for (const improvement of report.improvements) lines.push(`- ${improvement}`);
11941
+ }
11942
+ lines.push("", "## Benchmark Deltas");
11943
+ if (report.deltas.length === 0) {
11944
+ lines.push("- none");
11945
+ } else {
11946
+ for (const delta of report.deltas) {
11947
+ lines.push(
11948
+ `- ${delta.benchmarkId}: passRate ${delta.basePassRate} -> ${delta.candidatePassRate} (delta ${delta.passRateDelta})`
11949
+ );
11950
+ }
11951
+ }
11952
+ return lines.join("\n");
11953
+ }
11911
11954
  async function collectEvalStoreSnapshot(options) {
11912
11955
  const rootDir = options.rootDir;
11913
11956
  const benchmarkDir = path15.join(rootDir, "benchmarks");
@@ -12179,6 +12222,112 @@ async function createEvalBaselineSnapshot(options) {
12179
12222
  await writeFile11(targetPath, JSON.stringify(snapshot, null, 2), "utf-8");
12180
12223
  return { targetPath, snapshot };
12181
12224
  }
12225
+ async function runEvalBaselineDeltaReport(options) {
12226
+ if (options.benchmarkDeltaReporterEnabled !== true) {
12227
+ throw new Error("benchmark delta reporter is disabled");
12228
+ }
12229
+ const snapshotId = assertSafePathSegment(assertString(options.snapshotId, "snapshotId"), "snapshotId");
12230
+ const candidateRootDir = resolveEvalStoreDir(options.memoryDir, options.evalStoreDir);
12231
+ const candidateSnapshot = await collectEvalStoreSnapshot({
12232
+ rootDir: candidateRootDir,
12233
+ enabled: true,
12234
+ shadowModeEnabled: true,
12235
+ baselineSnapshotsEnabled: true,
12236
+ memoryRedTeamBenchEnabled: true
12237
+ });
12238
+ const baselineSnapshot = candidateSnapshot.baselines.find((snapshot) => snapshot.snapshotId === snapshotId);
12239
+ if (!baselineSnapshot) {
12240
+ throw new Error(`benchmark baseline snapshot not found: ${snapshotId}`);
12241
+ }
12242
+ return buildEvalBaselineDeltaReport({
12243
+ baselineSnapshot,
12244
+ candidateSnapshot
12245
+ });
12246
+ }
12247
+ function buildEvalBaselineDeltaReport(options) {
12248
+ const { baselineSnapshot, candidateSnapshot } = options;
12249
+ const regressions = [];
12250
+ const improvements = [];
12251
+ if (candidateSnapshot.status.invalidBenchmarks.length > 0) {
12252
+ regressions.push(`candidate store has ${candidateSnapshot.status.invalidBenchmarks.length} invalid benchmark manifest(s)`);
12253
+ }
12254
+ if (candidateSnapshot.status.invalidRuns.length > 0) {
12255
+ regressions.push(`candidate store has ${candidateSnapshot.status.invalidRuns.length} invalid run summary file(s)`);
12256
+ }
12257
+ if (candidateSnapshot.status.invalidShadows.length > 0) {
12258
+ regressions.push(`candidate store has ${candidateSnapshot.status.invalidShadows.length} invalid shadow record(s)`);
12259
+ }
12260
+ if (candidateSnapshot.status.invalidBaselines.length > 0) {
12261
+ regressions.push(`candidate store has ${candidateSnapshot.status.invalidBaselines.length} invalid baseline snapshot file(s)`);
12262
+ }
12263
+ const candidateRuns = latestCompletedRunsByBenchmark(candidateSnapshot.runs);
12264
+ const baselineBenchmarks = new Map(
12265
+ baselineSnapshot.benchmarks.map((benchmark) => [benchmark.benchmarkId, benchmark])
12266
+ );
12267
+ const missingCandidateBenchmarks = [...baselineBenchmarks.keys()].filter((benchmarkId) => !candidateRuns.has(benchmarkId)).sort();
12268
+ for (const benchmarkId of missingCandidateBenchmarks) {
12269
+ regressions.push(`candidate is missing latest completed benchmark run for ${benchmarkId}`);
12270
+ }
12271
+ const deltas = [];
12272
+ for (const benchmarkId of [...baselineBenchmarks.keys()].sort()) {
12273
+ const baseBenchmark = baselineBenchmarks.get(benchmarkId);
12274
+ const candidateRun = candidateRuns.get(benchmarkId);
12275
+ if (!baseBenchmark || !candidateRun) continue;
12276
+ const passRateDelta = computePassRate(candidateRun) - baseBenchmark.passRate;
12277
+ const delta = {
12278
+ benchmarkId,
12279
+ baseRunId: baseBenchmark.runId,
12280
+ candidateRunId: candidateRun.runId,
12281
+ basePassRate: baseBenchmark.passRate,
12282
+ candidatePassRate: computePassRate(candidateRun),
12283
+ passRateDelta,
12284
+ metricDeltas: {},
12285
+ regressions: [],
12286
+ improvements: []
12287
+ };
12288
+ if (passRateDelta < 0) {
12289
+ delta.regressions.push(`passRate ${baseBenchmark.passRate} -> ${delta.candidatePassRate}`);
12290
+ regressions.push(`${benchmarkId} pass rate regressed (${baseBenchmark.passRate} -> ${delta.candidatePassRate})`);
12291
+ } else if (passRateDelta > 0) {
12292
+ delta.improvements.push(`passRate ${baseBenchmark.passRate} -> ${delta.candidatePassRate}`);
12293
+ improvements.push(`${benchmarkId} pass rate improved (${baseBenchmark.passRate} -> ${delta.candidatePassRate})`);
12294
+ }
12295
+ const metricDelta = compareMetricDeltas(baseBenchmark.metrics, candidateRun.metrics);
12296
+ delta.metricDeltas = metricDelta.deltas;
12297
+ for (const regression of metricDelta.regressions) {
12298
+ delta.regressions.push(regression);
12299
+ regressions.push(`${benchmarkId} ${regression}`);
12300
+ }
12301
+ for (const improvement of metricDelta.improvements) {
12302
+ delta.improvements.push(improvement);
12303
+ improvements.push(`${benchmarkId} ${improvement}`);
12304
+ }
12305
+ deltas.push(delta);
12306
+ }
12307
+ const report = {
12308
+ passed: regressions.length === 0,
12309
+ baselineSnapshotId: baselineSnapshot.snapshotId,
12310
+ baselineCreatedAt: baselineSnapshot.createdAt,
12311
+ baselineSourceRootDir: baselineSnapshot.sourceRootDir,
12312
+ candidateRootDir: candidateSnapshot.status.rootDir,
12313
+ comparedBenchmarks: deltas.length,
12314
+ missingCandidateBenchmarks,
12315
+ invalidArtifacts: {
12316
+ candidate: {
12317
+ benchmarks: candidateSnapshot.status.invalidBenchmarks.length,
12318
+ runs: candidateSnapshot.status.invalidRuns.length,
12319
+ shadows: candidateSnapshot.status.invalidShadows.length,
12320
+ baselines: candidateSnapshot.status.invalidBaselines.length
12321
+ }
12322
+ },
12323
+ regressions,
12324
+ improvements,
12325
+ deltas,
12326
+ markdownReport: ""
12327
+ };
12328
+ report.markdownReport = formatEvalBaselineDeltaMarkdown(report);
12329
+ return report;
12330
+ }
12182
12331
  function resolveRequiredEvalStoreRoot(options, label) {
12183
12332
  if (typeof options.evalStoreDir === "string" && options.evalStoreDir.trim().length > 0) {
12184
12333
  return options.evalStoreDir.trim();
@@ -28964,6 +29113,14 @@ async function runBenchmarkCiGateCliCommand(options) {
28964
29113
  candidateEvalStoreDir: options.candidateEvalStoreDir
28965
29114
  });
28966
29115
  }
29116
+ async function runBenchmarkBaselineReportCliCommand(options) {
29117
+ return runEvalBaselineDeltaReport({
29118
+ memoryDir: options.memoryDir,
29119
+ evalStoreDir: options.evalStoreDir,
29120
+ benchmarkDeltaReporterEnabled: options.benchmarkDeltaReporterEnabled,
29121
+ snapshotId: options.snapshotId
29122
+ });
29123
+ }
28967
29124
  async function runObjectiveStateStatusCliCommand(options) {
28968
29125
  return getObjectiveStateStoreStatus({
28969
29126
  memoryDir: options.memoryDir,
@@ -30354,6 +30511,22 @@ function registerCli(api, orchestrator) {
30354
30511
  }
30355
30512
  console.log("OK");
30356
30513
  });
30514
+ cmd.command("benchmark-baseline-report").description("Compare the current eval store against a named stored benchmark baseline snapshot").requiredOption("--snapshot-id <id>", "Stable baseline snapshot identifier").action(async (...args) => {
30515
+ const options = args[0] ?? {};
30516
+ const summary = await runBenchmarkBaselineReportCliCommand({
30517
+ memoryDir: orchestrator.config.memoryDir,
30518
+ evalStoreDir: orchestrator.config.evalStoreDir,
30519
+ benchmarkDeltaReporterEnabled: orchestrator.config.benchmarkDeltaReporterEnabled,
30520
+ snapshotId: typeof options.snapshotId === "string" ? options.snapshotId : ""
30521
+ });
30522
+ const { markdownReport, ...jsonSummary } = summary;
30523
+ console.log(JSON.stringify(jsonSummary, null, 2));
30524
+ console.log(markdownReport);
30525
+ if (!summary.passed) {
30526
+ throw new Error("benchmark baseline report detected regressions");
30527
+ }
30528
+ console.log("OK");
30529
+ });
30357
30530
  cmd.command("objective-state-status").description("Show objective-state store status, snapshot counts, and latest stored snapshot").action(async () => {
30358
30531
  const status = await runObjectiveStateStatusCliCommand({
30359
30532
  memoryDir: orchestrator.config.memoryDir,