@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 +24 -0
- package/dist/index.js +245 -25
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +29 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,22 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@joshuaswarren/openclaw-engram)
|
|
6
6
|
[](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 ?
|
|
16499
|
-
|
|
16500
|
-
|
|
16501
|
-
|
|
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
|
|
22896
|
-
import { access as access3, readFile as
|
|
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
|
|
23777
|
-
if (!
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 = [
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
27511
|
-
const threading = new ThreadingManager(
|
|
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
|
|
27904
|
+
import { readFile as readFile38, writeFile as writeFile29 } from "fs/promises";
|
|
27685
27905
|
import { readFileSync as readFileSync4 } from "fs";
|
|
27686
|
-
import
|
|
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 :
|
|
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 =
|
|
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 =
|
|
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
|
|
28186
|
+
const content = await readFile38(cronFilePath, "utf-8");
|
|
27967
28187
|
jobsData = JSON.parse(content);
|
|
27968
28188
|
} catch {
|
|
27969
28189
|
}
|