@joshuaswarren/openclaw-engram 9.0.13 → 9.0.15

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
@@ -5,6 +5,22 @@
5
5
  [![npm version](https://img.shields.io/npm/v/@joshuaswarren/openclaw-engram)](https://www.npmjs.com/package/@joshuaswarren/openclaw-engram)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
7
 
8
+ ## Product Thesis
9
+
10
+ Engram is being built around three requirements:
11
+
12
+ - **Memory that improves action outcomes**
13
+ - **Memory that survives long horizons and failures**
14
+ - **Memory that can defend itself**
15
+
16
+ That product thesis drives the roadmap order:
17
+
18
+ 1. Evaluation harness and shadow-mode measurement
19
+ 2. Objective-state and causal trajectory memory
20
+ 3. Trust-zoned memory promotion and poisoning defense
21
+ 4. Harmonic retrieval over abstractions plus anchors
22
+ 5. Creation-memory, commitments, and recoverability
23
+
8
24
  ## Why Engram?
9
25
 
10
26
  AI agents forget everything between conversations. Engram fixes that.
@@ -13,6 +29,8 @@ AI agents forget everything between conversations. Engram fixes that.
13
29
  - **Smart recall** — Before each conversation, Engram injects the most relevant memories into the agent's context. Your agents remember what they need, when they need it.
14
30
  - **Local-first** — All memory data stays on your filesystem as plain markdown files. No cloud dependency, no vendor lock-in, fully portable.
15
31
  - **Pluggable search** — Choose from six search backends: QMD (hybrid BM25+vector+reranking), LanceDB, Meilisearch, Orama, remote HTTP, or bring your own.
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
+ - **Benchmark-first roadmap** — Engram now has an evaluation-harness foundation so memory improvements can be measured on real agent trajectories instead of subjective recall demos.
16
34
  - **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.
17
35
 
18
36
  ## Quick Start
@@ -121,6 +139,7 @@ Engram's capabilities are organized into feature families that you can enable pr
121
139
  | **Compounding** | Weekly synthesis that surfaces patterns and recurring mistakes |
122
140
  | **Hot/Cold Tiering** | Automatic migration of aging memories to cold storage |
123
141
  | **Behavior Loop Tuning** | Runtime self-tuning of extraction and recall parameters |
142
+ | **Evaluation Harness Foundation** | Tracks benchmark packs and run summaries so future PRs can be gated on memory quality instead of anecdotes |
124
143
 
125
144
  Start with defaults, then enable features as needed. See [Enable All Features](docs/enable-all-v8.md) for a full-feature config profile.
126
145
 
@@ -130,6 +149,7 @@ Start with defaults, then enable features as needed. See [Enable All Features](d
130
149
  openclaw engram stats # Memory counts, search status, health
131
150
  openclaw engram search "your query" # Search memories from CLI
132
151
  openclaw engram compat --strict # Compatibility check
152
+ openclaw engram benchmark-status # Benchmark/eval harness packs, runs, latest summary
133
153
  openclaw engram conversation-index-health # Conversation index status
134
154
  openclaw engram graph-health # Entity graph status
135
155
  openclaw engram tier-status # Hot/cold tier metrics
@@ -149,6 +169,8 @@ Key settings:
149
169
  | `searchBackend` | `"qmd"` | Search engine: `qmd`, `orama`, `lancedb`, `meilisearch`, `remote`, `noop` |
150
170
  | `qmdEnabled` | `true` | Enable QMD hybrid search |
151
171
  | `memoryDir` | `~/.openclaw/workspace/memory/local` | Memory storage root |
172
+ | `evalHarnessEnabled` | `false` | Enable the evaluation harness foundation for benchmark packs and run summaries |
173
+ | `evalShadowModeEnabled` | `false` | Reserve shadow-mode measurement paths for future benchmark instrumentation |
152
174
 
153
175
  Full reference: [Config Reference](docs/config-reference.md)
154
176
 
@@ -158,6 +180,7 @@ Full reference: [Config Reference](docs/config-reference.md)
158
180
  - [Search Backends](docs/search-backends.md) — Choosing and configuring search engines
159
181
  - [Writing a Search Backend](docs/writing-a-search-backend.md) — Build your own adapter
160
182
  - [Config Reference](docs/config-reference.md) — Every setting with defaults
183
+ - [Evaluation Harness](docs/evaluation-harness.md) — Benchmark pack and run-summary format
161
184
  - [Architecture Overview](docs/architecture/overview.md) — System design and storage layout
162
185
  - [Retrieval Pipeline](docs/architecture/retrieval-pipeline.md) — How recall works
163
186
  - [Memory Lifecycle](docs/architecture/memory-lifecycle.md) — Write, consolidation, expiry
@@ -166,6 +189,7 @@ Full reference: [Config Reference](docs/config-reference.md)
166
189
  - [Namespaces](docs/namespaces.md) — Multi-agent memory isolation
167
190
  - [Shared Context](docs/shared-context.md) — Cross-agent intelligence
168
191
  - [Identity Continuity](docs/identity-continuity.md) — Consistent agent personality
192
+ - [Agentic Memory Roadmap](docs/plans/2026-03-06-engram-agentic-memory-roadmap.md) — Benchmark-first roadmap and PR slices
169
193
 
170
194
  ## Developer Install
171
195
 
package/dist/index.js CHANGED
@@ -281,6 +281,9 @@ function parseConfig(raw) {
281
281
  conversationRecallTopK: typeof cfg.conversationRecallTopK === "number" ? cfg.conversationRecallTopK : 3,
282
282
  conversationRecallMaxChars: typeof cfg.conversationRecallMaxChars === "number" ? cfg.conversationRecallMaxChars : 2500,
283
283
  conversationRecallTimeoutMs: typeof cfg.conversationRecallTimeoutMs === "number" ? cfg.conversationRecallTimeoutMs : 800,
284
+ evalHarnessEnabled: cfg.evalHarnessEnabled === true,
285
+ evalShadowModeEnabled: cfg.evalShadowModeEnabled === true,
286
+ evalStoreDir: typeof cfg.evalStoreDir === "string" && cfg.evalStoreDir.trim().length > 0 ? cfg.evalStoreDir.trim() : path.join(memoryDir, "state", "evals"),
284
287
  // Local LLM Provider (v2.1)
285
288
  localLlmEnabled: cfg.localLlmEnabled === true || cfg.localLlmEnabled === "true",
286
289
  // default: false
@@ -985,6 +988,7 @@ var LocalLlmClient = class _LocalLlmClient {
985
988
  consecutive400s = 0;
986
989
  cooldownUntilMs = 0;
987
990
  modelRegistry;
991
+ _disableThinking = false;
988
992
  static HEALTH_CHECK_INTERVAL_MS = 6e4;
989
993
  // 1 minute
990
994
  static LMS_CACHE_INTERVAL_MS = 3e4;
@@ -993,6 +997,14 @@ var LocalLlmClient = class _LocalLlmClient {
993
997
  this.config = config;
994
998
  this.modelRegistry = modelRegistry;
995
999
  }
1000
+ /**
1001
+ * Disable thinking/reasoning mode for models that support it (e.g. Qwen 3.5).
1002
+ * When enabled, adds chat_template_kwargs to suppress chain-of-thought,
1003
+ * reducing latency for fast-tier operations.
1004
+ */
1005
+ set disableThinking(value) {
1006
+ this._disableThinking = value;
1007
+ }
996
1008
  resolveHomeDir() {
997
1009
  return this.config.localLlmHomeDir || process.env.HOME || os.homedir();
998
1010
  }
@@ -1432,6 +1444,9 @@ var LocalLlmClient = class _LocalLlmClient {
1432
1444
  if (options.responseFormat?.type === "json_schema") {
1433
1445
  requestBody.response_format = options.responseFormat;
1434
1446
  }
1447
+ if (this._disableThinking) {
1448
+ requestBody.chat_template_kwargs = { enable_thinking: false };
1449
+ }
1435
1450
  const baseUrl = this.config.localLlmUrl.replace("localhost", "127.0.0.1").replace(/\/+$/, "");
1436
1451
  const chatUrl = `${baseUrl}/chat/completions`;
1437
1452
  const requestBodyJson = JSON.stringify(requestBody);
@@ -16495,10 +16510,14 @@ var Orchestrator = class _Orchestrator {
16495
16510
  this.policyRuntime = new PolicyRuntimeManager(config.memoryDir, config);
16496
16511
  this.summarizer = new HourlySummarizer(config, config.gatewayConfig, this.modelRegistry, this.transcript);
16497
16512
  this.localLlm = new LocalLlmClient(config, this.modelRegistry);
16498
- this.fastLlm = config.localLlmFastEnabled ? new LocalLlmClient(
16499
- { ...config, localLlmModel: config.localLlmFastModel || config.localLlmModel, localLlmUrl: config.localLlmFastUrl, localLlmTimeoutMs: config.localLlmFastTimeoutMs },
16500
- this.modelRegistry
16501
- ) : this.localLlm;
16513
+ this.fastLlm = config.localLlmFastEnabled ? (() => {
16514
+ const client = new LocalLlmClient(
16515
+ { ...config, localLlmModel: config.localLlmFastModel || config.localLlmModel, localLlmUrl: config.localLlmFastUrl, localLlmTimeoutMs: config.localLlmFastTimeoutMs },
16516
+ this.modelRegistry
16517
+ );
16518
+ client.disableThinking = true;
16519
+ return client;
16520
+ })() : this.localLlm;
16502
16521
  this.extraction = new ExtractionEngine(config, this.localLlm, config.gatewayConfig, this.modelRegistry);
16503
16522
  this.threading = new ThreadingManager(
16504
16523
  path30.join(config.memoryDir, "threads"),
@@ -22892,8 +22911,8 @@ promotionCandidates: ${res.promotionCandidateCount}`
22892
22911
  }
22893
22912
 
22894
22913
  // src/cli.ts
22895
- import path50 from "path";
22896
- import { access as access3, readFile as readFile36, readdir as readdir22, unlink as unlink7 } from "fs/promises";
22914
+ import path51 from "path";
22915
+ import { access as access3, readFile as readFile37, readdir as readdir23, unlink as unlink7 } from "fs/promises";
22897
22916
  import { createHash as createHash10 } from "crypto";
22898
22917
 
22899
22918
  // src/transfer/export-json.ts
@@ -23773,8 +23792,8 @@ function gatherCandidates(input, warnings) {
23773
23792
  const record = rec;
23774
23793
  const content = typeof record.content === "string" ? record.content : null;
23775
23794
  if (!content) continue;
23776
- const path52 = typeof record.path === "string" ? record.path : "";
23777
- if (!path52.startsWith("transcripts/") && !path52.includes("/transcripts/")) continue;
23795
+ const path53 = typeof record.path === "string" ? record.path : "";
23796
+ if (!path53.startsWith("transcripts/") && !path53.includes("/transcripts/")) continue;
23778
23797
  rows.push(...parseJsonl(content, warnings));
23779
23798
  }
23780
23799
  return rows;
@@ -25382,6 +25401,189 @@ async function runCompatChecks(options) {
25382
25401
  };
25383
25402
  }
25384
25403
 
25404
+ // src/evals.ts
25405
+ import path50 from "path";
25406
+ import { readdir as readdir22, readFile as readFile36 } from "fs/promises";
25407
+ function isRecord(value) {
25408
+ return typeof value === "object" && value !== null && !Array.isArray(value);
25409
+ }
25410
+ function assertString(value, field) {
25411
+ if (typeof value !== "string" || value.trim().length === 0) {
25412
+ throw new Error(`${field} must be a non-empty string`);
25413
+ }
25414
+ return value.trim();
25415
+ }
25416
+ function optionalStringArray(value, field) {
25417
+ if (value === void 0) return void 0;
25418
+ if (!Array.isArray(value)) {
25419
+ throw new Error(`${field} must be an array of strings`);
25420
+ }
25421
+ const out = value.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item.length > 0);
25422
+ if (out.length !== value.length) {
25423
+ throw new Error(`${field} must be an array of non-empty strings`);
25424
+ }
25425
+ return out;
25426
+ }
25427
+ function resolveEvalStoreDir(memoryDir, overrideDir) {
25428
+ if (typeof overrideDir === "string" && overrideDir.trim().length > 0) {
25429
+ return overrideDir.trim();
25430
+ }
25431
+ return path50.join(memoryDir, "state", "evals");
25432
+ }
25433
+ function validateEvalBenchmarkManifest(raw) {
25434
+ if (!isRecord(raw)) throw new Error("benchmark manifest must be an object");
25435
+ if (raw.schemaVersion !== 1) throw new Error("schemaVersion must be 1");
25436
+ if (!Array.isArray(raw.cases)) throw new Error("cases must be an array");
25437
+ const cases = raw.cases.map((item, index) => {
25438
+ if (!isRecord(item)) throw new Error(`cases[${index}] must be an object`);
25439
+ return {
25440
+ id: assertString(item.id, `cases[${index}].id`),
25441
+ prompt: assertString(item.prompt, `cases[${index}].prompt`),
25442
+ expectedSignals: optionalStringArray(item.expectedSignals, `cases[${index}].expectedSignals`),
25443
+ notes: typeof item.notes === "string" && item.notes.trim().length > 0 ? item.notes.trim() : void 0
25444
+ };
25445
+ });
25446
+ return {
25447
+ schemaVersion: 1,
25448
+ benchmarkId: assertString(raw.benchmarkId, "benchmarkId"),
25449
+ title: assertString(raw.title, "title"),
25450
+ description: typeof raw.description === "string" && raw.description.trim().length > 0 ? raw.description.trim() : void 0,
25451
+ tags: optionalStringArray(raw.tags, "tags"),
25452
+ sourceLinks: optionalStringArray(raw.sourceLinks, "sourceLinks"),
25453
+ cases
25454
+ };
25455
+ }
25456
+ function validateEvalRunSummary(raw) {
25457
+ if (!isRecord(raw)) throw new Error("eval run summary must be an object");
25458
+ if (raw.schemaVersion !== 1) throw new Error("schemaVersion must be 1");
25459
+ const status = assertString(raw.status, "status");
25460
+ if (!["running", "completed", "failed", "partial"].includes(status)) {
25461
+ throw new Error("status must be one of running|completed|failed|partial");
25462
+ }
25463
+ const totalCases = Number(raw.totalCases);
25464
+ const passedCases = Number(raw.passedCases);
25465
+ const failedCases = Number(raw.failedCases);
25466
+ if (!Number.isFinite(totalCases) || totalCases < 0) throw new Error("totalCases must be a non-negative number");
25467
+ if (!Number.isFinite(passedCases) || passedCases < 0) throw new Error("passedCases must be a non-negative number");
25468
+ if (!Number.isFinite(failedCases) || failedCases < 0) throw new Error("failedCases must be a non-negative number");
25469
+ const metrics = isRecord(raw.metrics) ? {
25470
+ recallPrecisionAtK: typeof raw.metrics.recallPrecisionAtK === "number" ? raw.metrics.recallPrecisionAtK : void 0,
25471
+ actionOutcomeScore: typeof raw.metrics.actionOutcomeScore === "number" ? raw.metrics.actionOutcomeScore : void 0,
25472
+ objectiveStateCoverage: typeof raw.metrics.objectiveStateCoverage === "number" ? raw.metrics.objectiveStateCoverage : void 0,
25473
+ causalPathRecall: typeof raw.metrics.causalPathRecall === "number" ? raw.metrics.causalPathRecall : void 0,
25474
+ trustViolationRate: typeof raw.metrics.trustViolationRate === "number" ? raw.metrics.trustViolationRate : void 0,
25475
+ creationRecoveryScore: typeof raw.metrics.creationRecoveryScore === "number" ? raw.metrics.creationRecoveryScore : void 0
25476
+ } : void 0;
25477
+ return {
25478
+ schemaVersion: 1,
25479
+ runId: assertString(raw.runId, "runId"),
25480
+ benchmarkId: assertString(raw.benchmarkId, "benchmarkId"),
25481
+ status,
25482
+ startedAt: assertString(raw.startedAt, "startedAt"),
25483
+ completedAt: typeof raw.completedAt === "string" && raw.completedAt.trim().length > 0 ? raw.completedAt.trim() : void 0,
25484
+ totalCases,
25485
+ passedCases,
25486
+ failedCases,
25487
+ metrics,
25488
+ notes: typeof raw.notes === "string" && raw.notes.trim().length > 0 ? raw.notes.trim() : void 0,
25489
+ gitRef: typeof raw.gitRef === "string" && raw.gitRef.trim().length > 0 ? raw.gitRef.trim() : void 0
25490
+ };
25491
+ }
25492
+ async function listJsonFiles(dir) {
25493
+ try {
25494
+ const entries = await readdir22(dir, { withFileTypes: true });
25495
+ const out = [];
25496
+ for (const entry of entries) {
25497
+ const fullPath = path50.join(dir, entry.name);
25498
+ if (entry.isDirectory()) {
25499
+ out.push(...await listJsonFiles(fullPath));
25500
+ } else if (entry.isFile() && entry.name.endsWith(".json")) {
25501
+ out.push(fullPath);
25502
+ }
25503
+ }
25504
+ return out.sort();
25505
+ } catch {
25506
+ return [];
25507
+ }
25508
+ }
25509
+ async function readJsonFile2(filePath) {
25510
+ return JSON.parse(await readFile36(filePath, "utf-8"));
25511
+ }
25512
+ async function getEvalHarnessStatus(options) {
25513
+ const rootDir = resolveEvalStoreDir(options.memoryDir, options.evalStoreDir);
25514
+ const benchmarkDir = path50.join(rootDir, "benchmarks");
25515
+ const runsDir = path50.join(rootDir, "runs");
25516
+ const benchmarkFiles = await listJsonFiles(benchmarkDir);
25517
+ const runFiles = await listJsonFiles(runsDir);
25518
+ const invalidBenchmarks = [];
25519
+ const invalidRuns = [];
25520
+ const manifests = [];
25521
+ for (const filePath of benchmarkFiles) {
25522
+ try {
25523
+ manifests.push(validateEvalBenchmarkManifest(await readJsonFile2(filePath)));
25524
+ } catch (error) {
25525
+ invalidBenchmarks.push({
25526
+ path: filePath,
25527
+ error: error instanceof Error ? error.message : String(error)
25528
+ });
25529
+ }
25530
+ }
25531
+ const runs = [];
25532
+ for (const filePath of runFiles) {
25533
+ try {
25534
+ runs.push(validateEvalRunSummary(await readJsonFile2(filePath)));
25535
+ } catch (error) {
25536
+ invalidRuns.push({
25537
+ path: filePath,
25538
+ error: error instanceof Error ? error.message : String(error)
25539
+ });
25540
+ }
25541
+ }
25542
+ runs.sort((a, b) => {
25543
+ const aTime = Date.parse(a.completedAt ?? a.startedAt);
25544
+ const bTime = Date.parse(b.completedAt ?? b.startedAt);
25545
+ return (Number.isNaN(bTime) ? 0 : bTime) - (Number.isNaN(aTime) ? 0 : aTime);
25546
+ });
25547
+ const latestRun = runs[0];
25548
+ const tags = /* @__PURE__ */ new Set();
25549
+ const sourceLinks = /* @__PURE__ */ new Set();
25550
+ let totalCases = 0;
25551
+ for (const manifest of manifests) {
25552
+ totalCases += manifest.cases.length;
25553
+ for (const tag of manifest.tags ?? []) tags.add(tag);
25554
+ for (const link of manifest.sourceLinks ?? []) sourceLinks.add(link);
25555
+ }
25556
+ return {
25557
+ enabled: options.enabled,
25558
+ shadowModeEnabled: options.shadowModeEnabled,
25559
+ rootDir,
25560
+ benchmarkDir,
25561
+ runsDir,
25562
+ benchmarks: {
25563
+ total: benchmarkFiles.length,
25564
+ valid: manifests.length,
25565
+ invalid: invalidBenchmarks.length,
25566
+ totalCases,
25567
+ tags: [...tags].sort(),
25568
+ sourceLinks: [...sourceLinks].sort()
25569
+ },
25570
+ runs: {
25571
+ total: runFiles.length,
25572
+ invalid: invalidRuns.length,
25573
+ completed: runs.filter((run) => run.status === "completed").length,
25574
+ failed: runs.filter((run) => run.status === "failed").length,
25575
+ partial: runs.filter((run) => run.status === "partial").length,
25576
+ running: runs.filter((run) => run.status === "running").length,
25577
+ latestRunId: latestRun?.runId,
25578
+ latestBenchmarkId: latestRun?.benchmarkId,
25579
+ latestCompletedAt: latestRun?.completedAt
25580
+ },
25581
+ latestRun,
25582
+ invalidBenchmarks,
25583
+ invalidRuns
25584
+ };
25585
+ }
25586
+
25385
25587
  // src/cli.ts
25386
25588
  function rankCandidateForKeep(a, b) {
25387
25589
  const aConfidence = typeof a.frontmatter.confidence === "number" ? a.frontmatter.confidence : 0;
@@ -25538,6 +25740,14 @@ async function runGraphHealthCliCommand(options) {
25538
25740
  includeRepairGuidance: options.includeRepairGuidance
25539
25741
  });
25540
25742
  }
25743
+ async function runBenchmarkStatusCliCommand(options) {
25744
+ return getEvalHarnessStatus({
25745
+ memoryDir: options.memoryDir,
25746
+ evalStoreDir: options.evalStoreDir,
25747
+ enabled: options.evalHarnessEnabled,
25748
+ shadowModeEnabled: options.evalShadowModeEnabled
25749
+ });
25750
+ }
25541
25751
  async function runSessionCheckCliCommand(options) {
25542
25752
  return analyzeSessionIntegrity({ memoryDir: options.memoryDir });
25543
25753
  }
@@ -25765,7 +25975,7 @@ function policyVersionForValues(values, config) {
25765
25975
  return createHash10("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 12);
25766
25976
  }
25767
25977
  async function readRuntimePolicySnapshot2(config, fileName) {
25768
- const filePath = path50.join(config.memoryDir, "state", fileName);
25978
+ const filePath = path51.join(config.memoryDir, "state", fileName);
25769
25979
  const snapshot = await readRuntimePolicySnapshot(filePath, {
25770
25980
  maxStaleDecayThreshold: config.lifecycleArchiveDecayThreshold
25771
25981
  });
@@ -26265,7 +26475,7 @@ async function withTimeout(promise, timeoutMs, timeoutMessage) {
26265
26475
  }
26266
26476
  async function runReplayCliCommand(orchestrator, options) {
26267
26477
  const extractionIdleTimeoutMs = Number.isFinite(options.extractionIdleTimeoutMs) ? Math.max(1e3, Math.floor(options.extractionIdleTimeoutMs)) : 15 * 6e4;
26268
- const inputRaw = await readFile36(options.inputPath, "utf-8");
26478
+ const inputRaw = await readFile37(options.inputPath, "utf-8");
26269
26479
  const registry = buildReplayNormalizerRegistry([
26270
26480
  openclawReplayNormalizer,
26271
26481
  claudeReplayNormalizer,
@@ -26330,7 +26540,7 @@ async function runReplayCliCommand(orchestrator, options) {
26330
26540
  async function getPluginVersion() {
26331
26541
  try {
26332
26542
  const pkgPath = new URL("../package.json", import.meta.url);
26333
- const raw = await readFile36(pkgPath, "utf-8");
26543
+ const raw = await readFile37(pkgPath, "utf-8");
26334
26544
  const parsed = JSON.parse(raw);
26335
26545
  return parsed.version ?? "unknown";
26336
26546
  } catch {
@@ -26349,32 +26559,32 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
26349
26559
  const ns = (namespace ?? "").trim();
26350
26560
  if (!ns) return orchestrator.config.memoryDir;
26351
26561
  if (!orchestrator.config.namespacesEnabled) return orchestrator.config.memoryDir;
26352
- const candidate = path50.join(orchestrator.config.memoryDir, "namespaces", ns);
26562
+ const candidate = path51.join(orchestrator.config.memoryDir, "namespaces", ns);
26353
26563
  if (ns === orchestrator.config.defaultNamespace) {
26354
26564
  return await exists2(candidate) ? candidate : orchestrator.config.memoryDir;
26355
26565
  }
26356
26566
  return candidate;
26357
26567
  }
26358
26568
  async function readAllMemoryFiles(memoryDir) {
26359
- const roots = [path50.join(memoryDir, "facts"), path50.join(memoryDir, "corrections")];
26569
+ const roots = [path51.join(memoryDir, "facts"), path51.join(memoryDir, "corrections")];
26360
26570
  const out = [];
26361
26571
  const walk = async (dir) => {
26362
26572
  let entries;
26363
26573
  try {
26364
- entries = await readdir22(dir, { withFileTypes: true });
26574
+ entries = await readdir23(dir, { withFileTypes: true });
26365
26575
  } catch {
26366
26576
  return;
26367
26577
  }
26368
26578
  for (const entry of entries) {
26369
26579
  const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
26370
- const fullPath = path50.join(dir, entryName);
26580
+ const fullPath = path51.join(dir, entryName);
26371
26581
  if (entry.isDirectory()) {
26372
26582
  await walk(fullPath);
26373
26583
  continue;
26374
26584
  }
26375
26585
  if (!entry.isFile() || !entryName.endsWith(".md")) continue;
26376
26586
  try {
26377
- const raw = await readFile36(fullPath, "utf-8");
26587
+ const raw = await readFile37(fullPath, "utf-8");
26378
26588
  const parsed = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
26379
26589
  if (!parsed) continue;
26380
26590
  const fmRaw = parsed[1];
@@ -26635,6 +26845,16 @@ function registerCli(api, orchestrator) {
26635
26845
  }
26636
26846
  console.log("OK");
26637
26847
  });
26848
+ cmd.command("benchmark-status").description("Show benchmark/evaluation harness status, benchmark packs, and latest run summary").action(async () => {
26849
+ const status = await runBenchmarkStatusCliCommand({
26850
+ memoryDir: orchestrator.config.memoryDir,
26851
+ evalStoreDir: orchestrator.config.evalStoreDir,
26852
+ evalHarnessEnabled: orchestrator.config.evalHarnessEnabled,
26853
+ evalShadowModeEnabled: orchestrator.config.evalShadowModeEnabled
26854
+ });
26855
+ console.log(JSON.stringify(status, null, 2));
26856
+ console.log("OK");
26857
+ });
26638
26858
  cmd.command("conversation-index-health").description("Show conversation index backend health and index stats").action(async () => {
26639
26859
  const health = await runConversationIndexHealthCliCommand(orchestrator);
26640
26860
  console.log(JSON.stringify(health, null, 2));
@@ -27284,7 +27504,7 @@ function registerCli(api, orchestrator) {
27284
27504
  }
27285
27505
  });
27286
27506
  cmd.command("identity").description("Show agent identity reflections").action(async () => {
27287
- const workspaceDir = path50.join(process.env.HOME ?? "~", ".openclaw", "workspace");
27507
+ const workspaceDir = path51.join(process.env.HOME ?? "~", ".openclaw", "workspace");
27288
27508
  const identity = await orchestrator.storage.readIdentity(workspaceDir);
27289
27509
  if (!identity) {
27290
27510
  console.log("No identity file found.");
@@ -27507,8 +27727,8 @@ function registerCli(api, orchestrator) {
27507
27727
  const options = args[0] ?? {};
27508
27728
  const threadId = options.thread;
27509
27729
  const top = parseInt(options.top ?? "10", 10);
27510
- const memoryDir = path50.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
27511
- const threading = new ThreadingManager(path50.join(memoryDir, "threads"));
27730
+ const memoryDir = path51.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
27731
+ const threading = new ThreadingManager(path51.join(memoryDir, "threads"));
27512
27732
  if (threadId) {
27513
27733
  const thread = await threading.loadThread(threadId);
27514
27734
  if (!thread) {
@@ -27681,9 +27901,9 @@ function parseDuration(duration) {
27681
27901
  }
27682
27902
 
27683
27903
  // src/index.ts
27684
- import { readFile as readFile37, writeFile as writeFile29 } from "fs/promises";
27904
+ import { readFile as readFile38, writeFile as writeFile29 } from "fs/promises";
27685
27905
  import { readFileSync as readFileSync4 } from "fs";
27686
- import path51 from "path";
27906
+ import path52 from "path";
27687
27907
  import os6 from "os";
27688
27908
  var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
27689
27909
  var ENGRAM_HOOK_APIS = "__openclawEngramHookApis";
@@ -27691,7 +27911,7 @@ function loadPluginConfigFromFile() {
27691
27911
  try {
27692
27912
  const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
27693
27913
  const homeDir = process.env.HOME ?? os6.homedir();
27694
- const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path51.join(homeDir, ".openclaw", "openclaw.json");
27914
+ const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path52.join(homeDir, ".openclaw", "openclaw.json");
27695
27915
  const content = readFileSync4(configPath, "utf-8");
27696
27916
  const config = JSON.parse(content);
27697
27917
  const pluginEntry = config?.plugins?.entries?.["openclaw-engram"];
@@ -27928,7 +28148,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
27928
28148
  `session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
27929
28149
  );
27930
28150
  const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
27931
- const signalPath = path51.join(
28151
+ const signalPath = path52.join(
27932
28152
  workspaceDir,
27933
28153
  `.compaction-reset-signal-${safeSessionKey}`
27934
28154
  );
@@ -27959,11 +28179,11 @@ Use this context naturally when relevant. Never quote or expose this memory cont
27959
28179
  );
27960
28180
  async function ensureHourlySummaryCron(api2) {
27961
28181
  const jobId = "engram-hourly-summary";
27962
- const cronFilePath = path51.join(os6.homedir(), ".openclaw", "cron", "jobs.json");
28182
+ const cronFilePath = path52.join(os6.homedir(), ".openclaw", "cron", "jobs.json");
27963
28183
  try {
27964
28184
  let jobsData = { version: 1, jobs: [] };
27965
28185
  try {
27966
- const content = await readFile37(cronFilePath, "utf-8");
28186
+ const content = await readFile38(cronFilePath, "utf-8");
27967
28187
  jobsData = JSON.parse(content);
27968
28188
  } catch {
27969
28189
  }