@joshuaswarren/openclaw-engram 9.0.47 → 9.0.48
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 +5 -0
- package/dist/index.js +210 -28
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +20 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,6 +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
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.
|
|
51
52
|
|
|
52
53
|
## Quick Start
|
|
@@ -183,6 +184,8 @@ openclaw engram commitment-lifecycle-run # Expire overdue commitments and clean
|
|
|
183
184
|
openclaw engram work-product-status # Work-product ledger counts and latest recorded output
|
|
184
185
|
openclaw engram work-product-record # Record a typed work-product ledger entry
|
|
185
186
|
openclaw engram work-product-recall-search <query> # Preview reusable work products from the creation-memory ledger
|
|
187
|
+
openclaw engram utility-status # Utility-learning telemetry counts and latest observed outcome event
|
|
188
|
+
openclaw engram utility-record # Record a typed utility-learning telemetry event
|
|
186
189
|
openclaw engram conversation-index-health # Conversation index status
|
|
187
190
|
openclaw engram graph-health # Entity graph status
|
|
188
191
|
openclaw engram tier-status # Hot/cold tier metrics
|
|
@@ -225,6 +228,8 @@ Key settings:
|
|
|
225
228
|
| `semanticRulePromotionEnabled` | `false` | Enable deterministic promotion of explicit `IF ... THEN ...` rules from verified episodic memories via `openclaw engram semantic-rule-promote` |
|
|
226
229
|
| `semanticRuleVerificationEnabled` | `false` | Verify promoted semantic rules against their cited source episodes at recall time and inject a dedicated `Verified Rules` section via `openclaw engram semantic-rule-verify` |
|
|
227
230
|
| `creationMemoryEnabled` | `false` | Enable the creation-memory foundation, including the work-product ledger and operator-facing write/status commands |
|
|
231
|
+
| `memoryUtilityLearningEnabled` | `false` | Enable typed utility-learning telemetry storage for future promotion/ranking learners |
|
|
232
|
+
| `promotionByOutcomeEnabled` | `false` | Reserve outcome-aware promotion controls that later slices will drive from learned utility signals |
|
|
228
233
|
| `commitmentLedgerEnabled` | `false` | Enable the explicit commitment ledger for promises, follow-ups, deadlines, and unfinished obligations |
|
|
229
234
|
| `commitmentLifecycleEnabled` | `false` | Enable commitment lifecycle transitions, stale tracking, and resolved-entry cleanup for the commitment ledger |
|
|
230
235
|
| `commitmentStaleDays` | `14` | Days before an open commitment without a due date is considered stale in lifecycle status |
|
package/dist/index.js
CHANGED
|
@@ -308,6 +308,8 @@ function parseConfig(raw) {
|
|
|
308
308
|
semanticRulePromotionEnabled: cfg.semanticRulePromotionEnabled === true,
|
|
309
309
|
semanticRuleVerificationEnabled: cfg.semanticRuleVerificationEnabled === true,
|
|
310
310
|
creationMemoryEnabled: cfg.creationMemoryEnabled === true,
|
|
311
|
+
memoryUtilityLearningEnabled: cfg.memoryUtilityLearningEnabled === true,
|
|
312
|
+
promotionByOutcomeEnabled: cfg.promotionByOutcomeEnabled === true,
|
|
311
313
|
commitmentLedgerEnabled: cfg.commitmentLedgerEnabled === true,
|
|
312
314
|
commitmentLifecycleEnabled: cfg.commitmentLifecycleEnabled === true,
|
|
313
315
|
commitmentStaleDays: typeof cfg.commitmentStaleDays === "number" ? cfg.commitmentStaleDays : 14,
|
|
@@ -25309,7 +25311,7 @@ promotionCandidates: ${res.promotionCandidateCount}`
|
|
|
25309
25311
|
}
|
|
25310
25312
|
|
|
25311
25313
|
// src/cli.ts
|
|
25312
|
-
import
|
|
25314
|
+
import path61 from "path";
|
|
25313
25315
|
import { access as access3, readFile as readFile37, readdir as readdir24, unlink as unlink8 } from "fs/promises";
|
|
25314
25316
|
import { createHash as createHash10 } from "crypto";
|
|
25315
25317
|
|
|
@@ -26190,8 +26192,8 @@ function gatherCandidates(input, warnings) {
|
|
|
26190
26192
|
const record = rec;
|
|
26191
26193
|
const content = typeof record.content === "string" ? record.content : null;
|
|
26192
26194
|
if (!content) continue;
|
|
26193
|
-
const
|
|
26194
|
-
if (!
|
|
26195
|
+
const path63 = typeof record.path === "string" ? record.path : "";
|
|
26196
|
+
if (!path63.startsWith("transcripts/") && !path63.includes("/transcripts/")) continue;
|
|
26195
26197
|
rows.push(...parseJsonl(content, warnings));
|
|
26196
26198
|
}
|
|
26197
26199
|
return rows;
|
|
@@ -27825,15 +27827,147 @@ async function runCompatChecks(options) {
|
|
|
27825
27827
|
};
|
|
27826
27828
|
}
|
|
27827
27829
|
|
|
27828
|
-
// src/
|
|
27830
|
+
// src/utility-telemetry.ts
|
|
27829
27831
|
import path59 from "path";
|
|
27830
27832
|
import { mkdir as mkdir40, writeFile as writeFile37 } from "fs/promises";
|
|
27833
|
+
function resolveUtilityTelemetryDir(memoryDir, overrideDir) {
|
|
27834
|
+
if (typeof overrideDir === "string" && overrideDir.trim().length > 0) {
|
|
27835
|
+
return overrideDir.trim();
|
|
27836
|
+
}
|
|
27837
|
+
return path59.join(memoryDir, "state", "utility-telemetry");
|
|
27838
|
+
}
|
|
27839
|
+
function assertUtilityScore(value) {
|
|
27840
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
27841
|
+
throw new Error("utilityScore must be a finite number");
|
|
27842
|
+
}
|
|
27843
|
+
if (value < -1 || value > 1) {
|
|
27844
|
+
throw new Error("utilityScore must be between -1 and 1");
|
|
27845
|
+
}
|
|
27846
|
+
return value;
|
|
27847
|
+
}
|
|
27848
|
+
function validateUtilityTelemetryEvent(raw) {
|
|
27849
|
+
if (!isRecord2(raw)) throw new Error("utility telemetry event must be an object");
|
|
27850
|
+
if (raw.schemaVersion !== 1) throw new Error("schemaVersion must be 1");
|
|
27851
|
+
const source = assertString2(raw.source, "source");
|
|
27852
|
+
if (!["cli", "system", "benchmark", "tool_result"].includes(source)) {
|
|
27853
|
+
throw new Error("source must be one of cli|system|benchmark|tool_result");
|
|
27854
|
+
}
|
|
27855
|
+
const target = assertString2(raw.target, "target");
|
|
27856
|
+
if (!["promotion", "ranking"].includes(target)) {
|
|
27857
|
+
throw new Error("target must be one of promotion|ranking");
|
|
27858
|
+
}
|
|
27859
|
+
const decision = assertString2(raw.decision, "decision");
|
|
27860
|
+
if (!["promote", "demote", "hold", "boost", "suppress"].includes(decision)) {
|
|
27861
|
+
throw new Error("decision must be one of promote|demote|hold|boost|suppress");
|
|
27862
|
+
}
|
|
27863
|
+
const outcome = assertString2(raw.outcome, "outcome");
|
|
27864
|
+
if (!["helpful", "neutral", "harmful"].includes(outcome)) {
|
|
27865
|
+
throw new Error("outcome must be one of helpful|neutral|harmful");
|
|
27866
|
+
}
|
|
27867
|
+
return {
|
|
27868
|
+
schemaVersion: 1,
|
|
27869
|
+
eventId: assertSafePathSegment(assertString2(raw.eventId, "eventId"), "eventId"),
|
|
27870
|
+
recordedAt: assertIsoRecordedAt(assertString2(raw.recordedAt, "recordedAt")),
|
|
27871
|
+
sessionKey: assertString2(raw.sessionKey, "sessionKey"),
|
|
27872
|
+
source,
|
|
27873
|
+
target,
|
|
27874
|
+
decision,
|
|
27875
|
+
outcome,
|
|
27876
|
+
utilityScore: assertUtilityScore(raw.utilityScore),
|
|
27877
|
+
summary: assertString2(raw.summary, "summary"),
|
|
27878
|
+
memoryIds: optionalStringArray2(raw.memoryIds, "memoryIds"),
|
|
27879
|
+
entityRefs: optionalStringArray2(raw.entityRefs, "entityRefs"),
|
|
27880
|
+
tags: optionalStringArray2(raw.tags, "tags"),
|
|
27881
|
+
metadata: validateStringRecord(raw.metadata, "metadata")
|
|
27882
|
+
};
|
|
27883
|
+
}
|
|
27884
|
+
async function recordUtilityTelemetryEvent(options) {
|
|
27885
|
+
const rootDir = resolveUtilityTelemetryDir(options.memoryDir, options.utilityTelemetryDir);
|
|
27886
|
+
const validated = validateUtilityTelemetryEvent(options.event);
|
|
27887
|
+
const day = recordStoreDay(validated.recordedAt);
|
|
27888
|
+
const eventsDir = path59.join(rootDir, "events", day);
|
|
27889
|
+
const filePath = path59.join(eventsDir, `${validated.eventId}.json`);
|
|
27890
|
+
await mkdir40(eventsDir, { recursive: true });
|
|
27891
|
+
await writeFile37(filePath, JSON.stringify(validated, null, 2), "utf8");
|
|
27892
|
+
return filePath;
|
|
27893
|
+
}
|
|
27894
|
+
async function readUtilityTelemetryEvents(options) {
|
|
27895
|
+
const rootDir = resolveUtilityTelemetryDir(options.memoryDir, options.utilityTelemetryDir);
|
|
27896
|
+
const files = await listJsonFiles(path59.join(rootDir, "events"));
|
|
27897
|
+
const events = [];
|
|
27898
|
+
const invalidEvents = [];
|
|
27899
|
+
for (const filePath of files) {
|
|
27900
|
+
try {
|
|
27901
|
+
events.push(validateUtilityTelemetryEvent(await readJsonFile(filePath)));
|
|
27902
|
+
} catch (error) {
|
|
27903
|
+
invalidEvents.push({
|
|
27904
|
+
path: filePath,
|
|
27905
|
+
error: error instanceof Error ? error.message : String(error)
|
|
27906
|
+
});
|
|
27907
|
+
}
|
|
27908
|
+
}
|
|
27909
|
+
return { files, events, invalidEvents };
|
|
27910
|
+
}
|
|
27911
|
+
async function getUtilityTelemetryStatus(options) {
|
|
27912
|
+
const rootDir = resolveUtilityTelemetryDir(options.memoryDir, options.utilityTelemetryDir);
|
|
27913
|
+
const eventsDir = path59.join(rootDir, "events");
|
|
27914
|
+
if (!options.enabled) {
|
|
27915
|
+
return {
|
|
27916
|
+
enabled: false,
|
|
27917
|
+
promotionByOutcomeEnabled: options.promotionByOutcomeEnabled === true,
|
|
27918
|
+
rootDir,
|
|
27919
|
+
eventsDir,
|
|
27920
|
+
events: {
|
|
27921
|
+
total: 0,
|
|
27922
|
+
valid: 0,
|
|
27923
|
+
invalid: 0,
|
|
27924
|
+
byTarget: {},
|
|
27925
|
+
byDecision: {},
|
|
27926
|
+
byOutcome: {}
|
|
27927
|
+
},
|
|
27928
|
+
invalidEvents: []
|
|
27929
|
+
};
|
|
27930
|
+
}
|
|
27931
|
+
const { files, events, invalidEvents } = await readUtilityTelemetryEvents(options);
|
|
27932
|
+
events.sort((a, b) => b.recordedAt.localeCompare(a.recordedAt));
|
|
27933
|
+
const byTarget = {};
|
|
27934
|
+
const byDecision = {};
|
|
27935
|
+
const byOutcome = {};
|
|
27936
|
+
for (const event of events) {
|
|
27937
|
+
byTarget[event.target] = (byTarget[event.target] ?? 0) + 1;
|
|
27938
|
+
byDecision[event.decision] = (byDecision[event.decision] ?? 0) + 1;
|
|
27939
|
+
byOutcome[event.outcome] = (byOutcome[event.outcome] ?? 0) + 1;
|
|
27940
|
+
}
|
|
27941
|
+
return {
|
|
27942
|
+
enabled: true,
|
|
27943
|
+
promotionByOutcomeEnabled: options.promotionByOutcomeEnabled === true,
|
|
27944
|
+
rootDir,
|
|
27945
|
+
eventsDir,
|
|
27946
|
+
events: {
|
|
27947
|
+
total: files.length,
|
|
27948
|
+
valid: events.length,
|
|
27949
|
+
invalid: invalidEvents.length,
|
|
27950
|
+
byTarget,
|
|
27951
|
+
byDecision,
|
|
27952
|
+
byOutcome,
|
|
27953
|
+
latestEventId: events[0]?.eventId,
|
|
27954
|
+
latestRecordedAt: events[0]?.recordedAt,
|
|
27955
|
+
latestSessionKey: events[0]?.sessionKey
|
|
27956
|
+
},
|
|
27957
|
+
latestEvent: events[0],
|
|
27958
|
+
invalidEvents
|
|
27959
|
+
};
|
|
27960
|
+
}
|
|
27961
|
+
|
|
27962
|
+
// src/resume-bundles.ts
|
|
27963
|
+
import path60 from "path";
|
|
27964
|
+
import { mkdir as mkdir41, writeFile as writeFile38 } from "fs/promises";
|
|
27831
27965
|
var DEFAULT_RESUME_BUNDLE_REF_LIMIT = 5;
|
|
27832
27966
|
function resolveResumeBundleDir(memoryDir, overrideDir) {
|
|
27833
27967
|
if (typeof overrideDir === "string" && overrideDir.trim().length > 0) {
|
|
27834
27968
|
return overrideDir.trim();
|
|
27835
27969
|
}
|
|
27836
|
-
return
|
|
27970
|
+
return path60.join(memoryDir, "state", "resume-bundles");
|
|
27837
27971
|
}
|
|
27838
27972
|
function validateResumeBundle(raw) {
|
|
27839
27973
|
if (!isRecord2(raw)) throw new Error("resume bundle must be an object");
|
|
@@ -27877,7 +28011,7 @@ async function readValidatedItems(options) {
|
|
|
27877
28011
|
async function readObjectiveStateSnapshotsForSession(options) {
|
|
27878
28012
|
const rootDir = resolveObjectiveStateStoreDir(options.memoryDir, options.objectiveStateStoreDir);
|
|
27879
28013
|
const items = await readValidatedItems({
|
|
27880
|
-
rootDir:
|
|
28014
|
+
rootDir: path60.join(rootDir, "snapshots"),
|
|
27881
28015
|
validate: validateObjectiveStateSnapshot
|
|
27882
28016
|
});
|
|
27883
28017
|
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);
|
|
@@ -27885,7 +28019,7 @@ async function readObjectiveStateSnapshotsForSession(options) {
|
|
|
27885
28019
|
async function readWorkProductEntriesForSession(options) {
|
|
27886
28020
|
const rootDir = resolveWorkProductLedgerDir(options.memoryDir, options.workProductLedgerDir);
|
|
27887
28021
|
const items = await readValidatedItems({
|
|
27888
|
-
rootDir:
|
|
28022
|
+
rootDir: path60.join(rootDir, "entries"),
|
|
27889
28023
|
validate: validateWorkProductLedgerEntry
|
|
27890
28024
|
});
|
|
27891
28025
|
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);
|
|
@@ -27893,7 +28027,7 @@ async function readWorkProductEntriesForSession(options) {
|
|
|
27893
28027
|
async function readCommitmentEntriesForSession(options) {
|
|
27894
28028
|
const rootDir = resolveCommitmentLedgerDir(options.memoryDir, options.commitmentLedgerDir);
|
|
27895
28029
|
const items = await readValidatedItems({
|
|
27896
|
-
rootDir:
|
|
28030
|
+
rootDir: path60.join(rootDir, "entries"),
|
|
27897
28031
|
validate: validateCommitmentLedgerEntry
|
|
27898
28032
|
});
|
|
27899
28033
|
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);
|
|
@@ -27989,15 +28123,15 @@ async function recordResumeBundle(options) {
|
|
|
27989
28123
|
const rootDir = resolveResumeBundleDir(options.memoryDir, options.resumeBundleDir);
|
|
27990
28124
|
const validated = validateResumeBundle(options.bundle);
|
|
27991
28125
|
const day = recordStoreDay(validated.recordedAt);
|
|
27992
|
-
const bundlesDir =
|
|
27993
|
-
const filePath =
|
|
27994
|
-
await
|
|
27995
|
-
await
|
|
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");
|
|
27996
28130
|
return filePath;
|
|
27997
28131
|
}
|
|
27998
28132
|
async function readResumeBundles(options) {
|
|
27999
28133
|
const rootDir = resolveResumeBundleDir(options.memoryDir, options.resumeBundleDir);
|
|
28000
|
-
const files = await listJsonFiles(
|
|
28134
|
+
const files = await listJsonFiles(path60.join(rootDir, "bundles"));
|
|
28001
28135
|
const bundles = [];
|
|
28002
28136
|
const invalidBundles = [];
|
|
28003
28137
|
for (const filePath of files) {
|
|
@@ -28014,7 +28148,7 @@ async function readResumeBundles(options) {
|
|
|
28014
28148
|
}
|
|
28015
28149
|
async function getResumeBundleStatus(options) {
|
|
28016
28150
|
const rootDir = resolveResumeBundleDir(options.memoryDir, options.resumeBundleDir);
|
|
28017
|
-
const bundlesDir =
|
|
28151
|
+
const bundlesDir = path60.join(rootDir, "bundles");
|
|
28018
28152
|
if (!options.enabled) {
|
|
28019
28153
|
return {
|
|
28020
28154
|
enabled: false,
|
|
@@ -28444,6 +28578,20 @@ async function runWorkProductStatusCliCommand(options) {
|
|
|
28444
28578
|
enabled: options.creationMemoryEnabled
|
|
28445
28579
|
});
|
|
28446
28580
|
}
|
|
28581
|
+
async function runUtilityTelemetryStatusCliCommand(options) {
|
|
28582
|
+
return getUtilityTelemetryStatus({
|
|
28583
|
+
memoryDir: options.memoryDir,
|
|
28584
|
+
enabled: options.memoryUtilityLearningEnabled,
|
|
28585
|
+
promotionByOutcomeEnabled: options.promotionByOutcomeEnabled
|
|
28586
|
+
});
|
|
28587
|
+
}
|
|
28588
|
+
async function runUtilityTelemetryRecordCliCommand(options) {
|
|
28589
|
+
if (!options.memoryUtilityLearningEnabled) return null;
|
|
28590
|
+
return recordUtilityTelemetryEvent({
|
|
28591
|
+
memoryDir: options.memoryDir,
|
|
28592
|
+
event: options.event
|
|
28593
|
+
});
|
|
28594
|
+
}
|
|
28447
28595
|
async function runWorkProductRecordCliCommand(options) {
|
|
28448
28596
|
if (!options.creationMemoryEnabled) return null;
|
|
28449
28597
|
return recordWorkProductLedgerEntry({
|
|
@@ -28791,7 +28939,7 @@ function policyVersionForValues(values, config) {
|
|
|
28791
28939
|
return createHash10("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 12);
|
|
28792
28940
|
}
|
|
28793
28941
|
async function readRuntimePolicySnapshot2(config, fileName) {
|
|
28794
|
-
const filePath =
|
|
28942
|
+
const filePath = path61.join(config.memoryDir, "state", fileName);
|
|
28795
28943
|
const snapshot = await readRuntimePolicySnapshot(filePath, {
|
|
28796
28944
|
maxStaleDecayThreshold: config.lifecycleArchiveDecayThreshold
|
|
28797
28945
|
});
|
|
@@ -29375,14 +29523,14 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
|
|
|
29375
29523
|
const ns = (namespace ?? "").trim();
|
|
29376
29524
|
if (!ns) return orchestrator.config.memoryDir;
|
|
29377
29525
|
if (!orchestrator.config.namespacesEnabled) return orchestrator.config.memoryDir;
|
|
29378
|
-
const candidate =
|
|
29526
|
+
const candidate = path61.join(orchestrator.config.memoryDir, "namespaces", ns);
|
|
29379
29527
|
if (ns === orchestrator.config.defaultNamespace) {
|
|
29380
29528
|
return await exists2(candidate) ? candidate : orchestrator.config.memoryDir;
|
|
29381
29529
|
}
|
|
29382
29530
|
return candidate;
|
|
29383
29531
|
}
|
|
29384
29532
|
async function readAllMemoryFiles(memoryDir) {
|
|
29385
|
-
const roots = [
|
|
29533
|
+
const roots = [path61.join(memoryDir, "facts"), path61.join(memoryDir, "corrections")];
|
|
29386
29534
|
const out = [];
|
|
29387
29535
|
const walk = async (dir) => {
|
|
29388
29536
|
let entries;
|
|
@@ -29393,7 +29541,7 @@ async function readAllMemoryFiles(memoryDir) {
|
|
|
29393
29541
|
}
|
|
29394
29542
|
for (const entry of entries) {
|
|
29395
29543
|
const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
|
|
29396
|
-
const fullPath =
|
|
29544
|
+
const fullPath = path61.join(dir, entryName);
|
|
29397
29545
|
if (entry.isDirectory()) {
|
|
29398
29546
|
await walk(fullPath);
|
|
29399
29547
|
continue;
|
|
@@ -29772,6 +29920,40 @@ function registerCli(api, orchestrator) {
|
|
|
29772
29920
|
console.log(JSON.stringify(results, null, 2));
|
|
29773
29921
|
console.log("OK");
|
|
29774
29922
|
});
|
|
29923
|
+
cmd.command("utility-status").description("Show utility-learning telemetry status, event counts, and the latest utility event").action(async () => {
|
|
29924
|
+
const status = await runUtilityTelemetryStatusCliCommand({
|
|
29925
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
29926
|
+
memoryUtilityLearningEnabled: orchestrator.config.memoryUtilityLearningEnabled,
|
|
29927
|
+
promotionByOutcomeEnabled: orchestrator.config.promotionByOutcomeEnabled
|
|
29928
|
+
});
|
|
29929
|
+
console.log(JSON.stringify(status, null, 2));
|
|
29930
|
+
console.log("OK");
|
|
29931
|
+
});
|
|
29932
|
+
cmd.command("utility-record").description("Record a utility-learning telemetry event when utility learning is enabled").requiredOption("--event-id <eventId>", "Utility telemetry event id").requiredOption("--recorded-at <recordedAt>", "ISO timestamp for the event").requiredOption("--session-key <sessionKey>", "Session key associated with the event").requiredOption("--source <source>", "Event source (cli|system|benchmark|tool_result)").requiredOption("--target <target>", "Telemetry target (promotion|ranking)").requiredOption("--decision <decision>", "Decision taken (promote|demote|hold|boost|suppress)").requiredOption("--outcome <outcome>", "Observed outcome (helpful|neutral|harmful)").requiredOption("--utility-score <utilityScore>", "Bounded utility score between -1 and 1").requiredOption("--summary <summary>", "Human-readable summary of the measured utility event").option("--memory-id <memoryId...>", "Memory ids linked to the utility event").option("--entity-ref <entityRef...>", "Entity refs linked to the utility event").option("--tag <tag...>", "Tags to attach to the utility event").action(async (...args) => {
|
|
29933
|
+
const options = args[0] ?? {};
|
|
29934
|
+
const utilityScore = typeof options.utilityScore === "string" ? Number.parseFloat(options.utilityScore) : Number.NaN;
|
|
29935
|
+
const filePath = await runUtilityTelemetryRecordCliCommand({
|
|
29936
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
29937
|
+
memoryUtilityLearningEnabled: orchestrator.config.memoryUtilityLearningEnabled,
|
|
29938
|
+
event: {
|
|
29939
|
+
schemaVersion: 1,
|
|
29940
|
+
eventId: String(options.eventId ?? ""),
|
|
29941
|
+
recordedAt: String(options.recordedAt ?? ""),
|
|
29942
|
+
sessionKey: String(options.sessionKey ?? ""),
|
|
29943
|
+
source: String(options.source ?? ""),
|
|
29944
|
+
target: String(options.target ?? ""),
|
|
29945
|
+
decision: String(options.decision ?? ""),
|
|
29946
|
+
outcome: String(options.outcome ?? ""),
|
|
29947
|
+
utilityScore,
|
|
29948
|
+
summary: String(options.summary ?? ""),
|
|
29949
|
+
memoryIds: Array.isArray(options.memoryId) ? options.memoryId.map(String) : void 0,
|
|
29950
|
+
entityRefs: Array.isArray(options.entityRef) ? options.entityRef.map(String) : void 0,
|
|
29951
|
+
tags: Array.isArray(options.tag) ? options.tag.map(String) : void 0
|
|
29952
|
+
}
|
|
29953
|
+
});
|
|
29954
|
+
console.log(JSON.stringify({ wrote: filePath !== null, filePath }, null, 2));
|
|
29955
|
+
console.log("OK");
|
|
29956
|
+
});
|
|
29775
29957
|
cmd.command("resume-bundle-status").description("Show resume bundle status, bundle counts, and the latest recorded handoff bundle").action(async () => {
|
|
29776
29958
|
const status = await runResumeBundleStatusCliCommand({
|
|
29777
29959
|
memoryDir: orchestrator.config.memoryDir,
|
|
@@ -30678,7 +30860,7 @@ function registerCli(api, orchestrator) {
|
|
|
30678
30860
|
}
|
|
30679
30861
|
});
|
|
30680
30862
|
cmd.command("identity").description("Show agent identity reflections").action(async () => {
|
|
30681
|
-
const workspaceDir =
|
|
30863
|
+
const workspaceDir = path61.join(process.env.HOME ?? "~", ".openclaw", "workspace");
|
|
30682
30864
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
30683
30865
|
if (!identity) {
|
|
30684
30866
|
console.log("No identity file found.");
|
|
@@ -30901,8 +31083,8 @@ function registerCli(api, orchestrator) {
|
|
|
30901
31083
|
const options = args[0] ?? {};
|
|
30902
31084
|
const threadId = options.thread;
|
|
30903
31085
|
const top = parseInt(options.top ?? "10", 10);
|
|
30904
|
-
const memoryDir =
|
|
30905
|
-
const threading = new ThreadingManager(
|
|
31086
|
+
const memoryDir = path61.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
|
|
31087
|
+
const threading = new ThreadingManager(path61.join(memoryDir, "threads"));
|
|
30906
31088
|
if (threadId) {
|
|
30907
31089
|
const thread = await threading.loadThread(threadId);
|
|
30908
31090
|
if (!thread) {
|
|
@@ -31378,9 +31560,9 @@ async function recordObjectiveStateSnapshotsFromAgentMessages(options) {
|
|
|
31378
31560
|
}
|
|
31379
31561
|
|
|
31380
31562
|
// src/index.ts
|
|
31381
|
-
import { readFile as readFile38, writeFile as
|
|
31563
|
+
import { readFile as readFile38, writeFile as writeFile39 } from "fs/promises";
|
|
31382
31564
|
import { readFileSync as readFileSync4 } from "fs";
|
|
31383
|
-
import
|
|
31565
|
+
import path62 from "path";
|
|
31384
31566
|
import os6 from "os";
|
|
31385
31567
|
var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
|
|
31386
31568
|
var ENGRAM_HOOK_APIS = "__openclawEngramHookApis";
|
|
@@ -31388,7 +31570,7 @@ function loadPluginConfigFromFile() {
|
|
|
31388
31570
|
try {
|
|
31389
31571
|
const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
|
|
31390
31572
|
const homeDir = process.env.HOME ?? os6.homedir();
|
|
31391
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
31573
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path62.join(homeDir, ".openclaw", "openclaw.json");
|
|
31392
31574
|
const content = readFileSync4(configPath, "utf-8");
|
|
31393
31575
|
const config = JSON.parse(content);
|
|
31394
31576
|
const pluginEntry = config?.plugins?.entries?.["openclaw-engram"];
|
|
@@ -31638,11 +31820,11 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
31638
31820
|
`session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
|
|
31639
31821
|
);
|
|
31640
31822
|
const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
|
|
31641
|
-
const signalPath =
|
|
31823
|
+
const signalPath = path62.join(
|
|
31642
31824
|
workspaceDir,
|
|
31643
31825
|
`.compaction-reset-signal-${safeSessionKey}`
|
|
31644
31826
|
);
|
|
31645
|
-
await
|
|
31827
|
+
await writeFile39(
|
|
31646
31828
|
signalPath,
|
|
31647
31829
|
JSON.stringify({
|
|
31648
31830
|
sessionKey,
|
|
@@ -31669,7 +31851,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
31669
31851
|
);
|
|
31670
31852
|
async function ensureHourlySummaryCron(api2) {
|
|
31671
31853
|
const jobId = "engram-hourly-summary";
|
|
31672
|
-
const cronFilePath =
|
|
31854
|
+
const cronFilePath = path62.join(os6.homedir(), ".openclaw", "cron", "jobs.json");
|
|
31673
31855
|
try {
|
|
31674
31856
|
let jobsData = { version: 1, jobs: [] };
|
|
31675
31857
|
try {
|
|
@@ -31710,7 +31892,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
31710
31892
|
state: {}
|
|
31711
31893
|
};
|
|
31712
31894
|
jobsData.jobs.push(newJob);
|
|
31713
|
-
await
|
|
31895
|
+
await writeFile39(cronFilePath, JSON.stringify(jobsData, null, 2), "utf-8");
|
|
31714
31896
|
log.info("auto-registered hourly summary cron job");
|
|
31715
31897
|
} catch (err) {
|
|
31716
31898
|
log.error("failed to auto-register hourly summary cron job:", err);
|