@joshuaswarren/openclaw-engram 9.0.48 → 9.0.49
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 +3 -1
- package/dist/index.js +327 -34
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,7 +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
|
|
50
|
+
- **Utility-learning offline learner** — Engram can now, when `memoryUtilityLearningEnabled` is enabled, persist typed downstream utility events for promotion and ranking decisions, inspect the raw event ledger with `openclaw engram utility-status`, record deterministic benchmark/operator telemetry through `openclaw engram utility-record`, and learn bounded offline promotion/ranking weights through `openclaw engram utility-learn` and `openclaw engram utility-learning-status` before runtime weighting lands.
|
|
51
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.
|
|
52
52
|
|
|
53
53
|
## Quick Start
|
|
@@ -186,6 +186,8 @@ openclaw engram work-product-record # Record a typed work-product ledger
|
|
|
186
186
|
openclaw engram work-product-recall-search <query> # Preview reusable work products from the creation-memory ledger
|
|
187
187
|
openclaw engram utility-status # Utility-learning telemetry counts and latest observed outcome event
|
|
188
188
|
openclaw engram utility-record # Record a typed utility-learning telemetry event
|
|
189
|
+
openclaw engram utility-learning-status # Latest offline utility-learning snapshot and learned weight counts
|
|
190
|
+
openclaw engram utility-learn # Learn bounded offline promotion/ranking weights from recorded utility events
|
|
189
191
|
openclaw engram conversation-index-health # Conversation index status
|
|
190
192
|
openclaw engram graph-health # Entity graph status
|
|
191
193
|
openclaw engram tier-status # Hot/cold tier metrics
|
package/dist/index.js
CHANGED
|
@@ -25311,8 +25311,8 @@ promotionCandidates: ${res.promotionCandidateCount}`
|
|
|
25311
25311
|
}
|
|
25312
25312
|
|
|
25313
25313
|
// src/cli.ts
|
|
25314
|
-
import
|
|
25315
|
-
import { access as access3, readFile as
|
|
25314
|
+
import path62 from "path";
|
|
25315
|
+
import { access as access3, readFile as readFile38, readdir as readdir24, unlink as unlink8 } from "fs/promises";
|
|
25316
25316
|
import { createHash as createHash10 } from "crypto";
|
|
25317
25317
|
|
|
25318
25318
|
// src/transfer/export-json.ts
|
|
@@ -26192,8 +26192,8 @@ function gatherCandidates(input, warnings) {
|
|
|
26192
26192
|
const record = rec;
|
|
26193
26193
|
const content = typeof record.content === "string" ? record.content : null;
|
|
26194
26194
|
if (!content) continue;
|
|
26195
|
-
const
|
|
26196
|
-
if (!
|
|
26195
|
+
const path64 = typeof record.path === "string" ? record.path : "";
|
|
26196
|
+
if (!path64.startsWith("transcripts/") && !path64.includes("/transcripts/")) continue;
|
|
26197
26197
|
rows.push(...parseJsonl(content, warnings));
|
|
26198
26198
|
}
|
|
26199
26199
|
return rows;
|
|
@@ -27959,15 +27959,268 @@ async function getUtilityTelemetryStatus(options) {
|
|
|
27959
27959
|
};
|
|
27960
27960
|
}
|
|
27961
27961
|
|
|
27962
|
-
// src/
|
|
27962
|
+
// src/utility-learner.ts
|
|
27963
27963
|
import path60 from "path";
|
|
27964
|
-
import { mkdir as mkdir41, writeFile as writeFile38 } from "fs/promises";
|
|
27964
|
+
import { mkdir as mkdir41, readFile as readFile37, rename as rename4, writeFile as writeFile38 } from "fs/promises";
|
|
27965
|
+
var UTILITY_LEARNING_SNAPSHOT_VERSION = 1;
|
|
27966
|
+
var UTILITY_LEARNING_STATE_FILE = "learning-state.json";
|
|
27967
|
+
function clampWeight(value, maxWeightMagnitude) {
|
|
27968
|
+
const limit = Number.isFinite(maxWeightMagnitude) && maxWeightMagnitude > 0 ? maxWeightMagnitude : 0;
|
|
27969
|
+
return Math.max(-limit, Math.min(limit, value));
|
|
27970
|
+
}
|
|
27971
|
+
function coerceLearningWindowDays(value) {
|
|
27972
|
+
if (!Number.isFinite(value)) return 14;
|
|
27973
|
+
return Math.max(1, Math.floor(value));
|
|
27974
|
+
}
|
|
27975
|
+
function coerceMinEventCount(value) {
|
|
27976
|
+
if (!Number.isFinite(value)) return 3;
|
|
27977
|
+
return Math.max(1, Math.floor(value));
|
|
27978
|
+
}
|
|
27979
|
+
function coerceMaxWeightMagnitude(value) {
|
|
27980
|
+
if (!Number.isFinite(value)) return 0.35;
|
|
27981
|
+
return Math.max(0, Math.min(1, value));
|
|
27982
|
+
}
|
|
27983
|
+
function roundWeight(value) {
|
|
27984
|
+
return Math.round(value * 1e3) / 1e3;
|
|
27985
|
+
}
|
|
27986
|
+
function outcomeCountsFor(events) {
|
|
27987
|
+
const counts = {};
|
|
27988
|
+
for (const event of events) {
|
|
27989
|
+
counts[event.outcome] = (counts[event.outcome] ?? 0) + 1;
|
|
27990
|
+
}
|
|
27991
|
+
return counts;
|
|
27992
|
+
}
|
|
27993
|
+
function selectRecentEvents(events, now, windowDays) {
|
|
27994
|
+
if (!Number.isFinite(windowDays) || windowDays <= 0) return [...events];
|
|
27995
|
+
const minTimestamp = now.getTime() - windowDays * 864e5;
|
|
27996
|
+
return events.filter((event) => {
|
|
27997
|
+
const ts = Date.parse(event.recordedAt);
|
|
27998
|
+
return Number.isFinite(ts) && ts >= minTimestamp;
|
|
27999
|
+
});
|
|
28000
|
+
}
|
|
28001
|
+
function confidenceFromEvents(eventCount, averageUtilityScore) {
|
|
28002
|
+
if (eventCount <= 0) return 0;
|
|
28003
|
+
return roundWeight(clamp01(Math.abs(averageUtilityScore) * Math.min(1, eventCount / 10)));
|
|
28004
|
+
}
|
|
28005
|
+
function validateUtilityLearningSnapshot(raw) {
|
|
28006
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
28007
|
+
throw new Error("utility learning snapshot must be an object");
|
|
28008
|
+
}
|
|
28009
|
+
const record = raw;
|
|
28010
|
+
if (record.version !== UTILITY_LEARNING_SNAPSHOT_VERSION) {
|
|
28011
|
+
throw new Error("utility learning snapshot version must be 1");
|
|
28012
|
+
}
|
|
28013
|
+
if (typeof record.updatedAt !== "string" || record.updatedAt.length === 0) {
|
|
28014
|
+
throw new Error("utility learning snapshot updatedAt must be a string");
|
|
28015
|
+
}
|
|
28016
|
+
if (typeof record.windowDays !== "number" || !Number.isFinite(record.windowDays) || record.windowDays < 0) {
|
|
28017
|
+
throw new Error("utility learning snapshot windowDays must be a non-negative number");
|
|
28018
|
+
}
|
|
28019
|
+
if (typeof record.minEventCount !== "number" || !Number.isFinite(record.minEventCount) || record.minEventCount < 1) {
|
|
28020
|
+
throw new Error("utility learning snapshot minEventCount must be >= 1");
|
|
28021
|
+
}
|
|
28022
|
+
if (typeof record.maxWeightMagnitude !== "number" || !Number.isFinite(record.maxWeightMagnitude) || record.maxWeightMagnitude < 0) {
|
|
28023
|
+
throw new Error("utility learning snapshot maxWeightMagnitude must be >= 0");
|
|
28024
|
+
}
|
|
28025
|
+
if (!Array.isArray(record.weights)) {
|
|
28026
|
+
throw new Error("utility learning snapshot weights must be an array");
|
|
28027
|
+
}
|
|
28028
|
+
const weights = record.weights.map((entry) => {
|
|
28029
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
28030
|
+
throw new Error("utility learning weight must be an object");
|
|
28031
|
+
}
|
|
28032
|
+
const weight = entry;
|
|
28033
|
+
const target = weight.target;
|
|
28034
|
+
const decision = weight.decision;
|
|
28035
|
+
if (target !== "promotion" && target !== "ranking") {
|
|
28036
|
+
throw new Error("utility learning weight target must be promotion|ranking");
|
|
28037
|
+
}
|
|
28038
|
+
if (!["promote", "demote", "hold", "boost", "suppress"].includes(String(decision))) {
|
|
28039
|
+
throw new Error("utility learning weight decision is invalid");
|
|
28040
|
+
}
|
|
28041
|
+
if (typeof weight.eventCount !== "number" || !Number.isFinite(weight.eventCount) || weight.eventCount < 0) {
|
|
28042
|
+
throw new Error("utility learning weight eventCount must be >= 0");
|
|
28043
|
+
}
|
|
28044
|
+
if (typeof weight.learnedWeight !== "number" || !Number.isFinite(weight.learnedWeight)) {
|
|
28045
|
+
throw new Error("utility learning weight learnedWeight must be finite");
|
|
28046
|
+
}
|
|
28047
|
+
if (typeof weight.averageUtilityScore !== "number" || !Number.isFinite(weight.averageUtilityScore)) {
|
|
28048
|
+
throw new Error("utility learning weight averageUtilityScore must be finite");
|
|
28049
|
+
}
|
|
28050
|
+
if (typeof weight.confidence !== "number" || !Number.isFinite(weight.confidence)) {
|
|
28051
|
+
throw new Error("utility learning weight confidence must be finite");
|
|
28052
|
+
}
|
|
28053
|
+
if (typeof weight.updatedAt !== "string" || weight.updatedAt.length === 0) {
|
|
28054
|
+
throw new Error("utility learning weight updatedAt must be a string");
|
|
28055
|
+
}
|
|
28056
|
+
const outcomeCounts = weight.outcomeCounts ?? {};
|
|
28057
|
+
return {
|
|
28058
|
+
target,
|
|
28059
|
+
decision,
|
|
28060
|
+
eventCount: weight.eventCount,
|
|
28061
|
+
learnedWeight: roundWeight(weight.learnedWeight),
|
|
28062
|
+
averageUtilityScore: roundWeight(weight.averageUtilityScore),
|
|
28063
|
+
confidence: roundWeight(clamp01(weight.confidence)),
|
|
28064
|
+
outcomeCounts,
|
|
28065
|
+
updatedAt: weight.updatedAt
|
|
28066
|
+
};
|
|
28067
|
+
});
|
|
28068
|
+
return {
|
|
28069
|
+
version: 1,
|
|
28070
|
+
updatedAt: record.updatedAt,
|
|
28071
|
+
windowDays: record.windowDays,
|
|
28072
|
+
minEventCount: record.minEventCount,
|
|
28073
|
+
maxWeightMagnitude: record.maxWeightMagnitude,
|
|
28074
|
+
weights
|
|
28075
|
+
};
|
|
28076
|
+
}
|
|
28077
|
+
function resolveUtilityLearningStatePath(memoryDir, utilityTelemetryDir) {
|
|
28078
|
+
return path60.join(resolveUtilityTelemetryDir(memoryDir, utilityTelemetryDir), UTILITY_LEARNING_STATE_FILE);
|
|
28079
|
+
}
|
|
28080
|
+
async function readUtilityLearningSnapshot(memoryDir, utilityTelemetryDir) {
|
|
28081
|
+
const statePath = resolveUtilityLearningStatePath(memoryDir, utilityTelemetryDir);
|
|
28082
|
+
try {
|
|
28083
|
+
const raw = JSON.parse(await readFile37(statePath, "utf8"));
|
|
28084
|
+
return validateUtilityLearningSnapshot(raw);
|
|
28085
|
+
} catch {
|
|
28086
|
+
return null;
|
|
28087
|
+
}
|
|
28088
|
+
}
|
|
28089
|
+
async function writeUtilityLearningSnapshot(statePath, snapshot) {
|
|
28090
|
+
const tempPath = `${statePath}.tmp`;
|
|
28091
|
+
await mkdir41(path60.dirname(statePath), { recursive: true });
|
|
28092
|
+
await writeFile38(tempPath, `${JSON.stringify(snapshot, null, 2)}
|
|
28093
|
+
`, "utf8");
|
|
28094
|
+
await rename4(tempPath, statePath);
|
|
28095
|
+
}
|
|
28096
|
+
async function learnUtilityPromotionWeights(options) {
|
|
28097
|
+
const statePath = resolveUtilityLearningStatePath(options.memoryDir, options.utilityTelemetryDir);
|
|
28098
|
+
if (!options.enabled) {
|
|
28099
|
+
return {
|
|
28100
|
+
applied: false,
|
|
28101
|
+
reason: "disabled",
|
|
28102
|
+
statePath,
|
|
28103
|
+
snapshot: null
|
|
28104
|
+
};
|
|
28105
|
+
}
|
|
28106
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
28107
|
+
const updatedAt = now.toISOString();
|
|
28108
|
+
const windowDays = coerceLearningWindowDays(options.learningWindowDays);
|
|
28109
|
+
const minEventCount = coerceMinEventCount(options.minEventCount);
|
|
28110
|
+
const maxWeightMagnitude = coerceMaxWeightMagnitude(options.maxWeightMagnitude);
|
|
28111
|
+
const recentEvents = selectRecentEvents(
|
|
28112
|
+
(await readUtilityTelemetryEvents({
|
|
28113
|
+
memoryDir: options.memoryDir,
|
|
28114
|
+
utilityTelemetryDir: options.utilityTelemetryDir
|
|
28115
|
+
})).events,
|
|
28116
|
+
now,
|
|
28117
|
+
windowDays
|
|
28118
|
+
);
|
|
28119
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
28120
|
+
for (const event of recentEvents) {
|
|
28121
|
+
const key = `${event.target}:${event.decision}`;
|
|
28122
|
+
const existing = grouped.get(key);
|
|
28123
|
+
if (existing) {
|
|
28124
|
+
existing.push(event);
|
|
28125
|
+
} else {
|
|
28126
|
+
grouped.set(key, [event]);
|
|
28127
|
+
}
|
|
28128
|
+
}
|
|
28129
|
+
const weights = [];
|
|
28130
|
+
for (const events of grouped.values()) {
|
|
28131
|
+
if (events.length < minEventCount) continue;
|
|
28132
|
+
const target = events[0].target;
|
|
28133
|
+
const decision = events[0].decision;
|
|
28134
|
+
const averageUtilityScore = events.reduce((sum, event) => sum + event.utilityScore, 0) / events.length;
|
|
28135
|
+
const confidence = confidenceFromEvents(events.length, averageUtilityScore);
|
|
28136
|
+
const learnedWeight = roundWeight(
|
|
28137
|
+
clampWeight(averageUtilityScore * confidence, maxWeightMagnitude)
|
|
28138
|
+
);
|
|
28139
|
+
weights.push({
|
|
28140
|
+
target,
|
|
28141
|
+
decision,
|
|
28142
|
+
eventCount: events.length,
|
|
28143
|
+
learnedWeight,
|
|
28144
|
+
averageUtilityScore: roundWeight(averageUtilityScore),
|
|
28145
|
+
confidence,
|
|
28146
|
+
outcomeCounts: outcomeCountsFor(events),
|
|
28147
|
+
updatedAt
|
|
28148
|
+
});
|
|
28149
|
+
}
|
|
28150
|
+
weights.sort((left, right) => {
|
|
28151
|
+
const targetCompare = left.target.localeCompare(right.target);
|
|
28152
|
+
if (targetCompare !== 0) return targetCompare;
|
|
28153
|
+
return left.decision.localeCompare(right.decision);
|
|
28154
|
+
});
|
|
28155
|
+
const snapshot = {
|
|
28156
|
+
version: 1,
|
|
28157
|
+
updatedAt,
|
|
28158
|
+
windowDays,
|
|
28159
|
+
minEventCount,
|
|
28160
|
+
maxWeightMagnitude,
|
|
28161
|
+
weights
|
|
28162
|
+
};
|
|
28163
|
+
if (weights.length === 0) {
|
|
28164
|
+
return {
|
|
28165
|
+
applied: false,
|
|
28166
|
+
reason: "insufficient_events",
|
|
28167
|
+
statePath,
|
|
28168
|
+
snapshot
|
|
28169
|
+
};
|
|
28170
|
+
}
|
|
28171
|
+
await writeUtilityLearningSnapshot(statePath, snapshot);
|
|
28172
|
+
return {
|
|
28173
|
+
applied: true,
|
|
28174
|
+
reason: "learned",
|
|
28175
|
+
statePath,
|
|
28176
|
+
snapshot
|
|
28177
|
+
};
|
|
28178
|
+
}
|
|
28179
|
+
async function getUtilityLearningStatus(options) {
|
|
28180
|
+
const rootDir = resolveUtilityTelemetryDir(options.memoryDir, options.utilityTelemetryDir);
|
|
28181
|
+
const statePath = resolveUtilityLearningStatePath(options.memoryDir, options.utilityTelemetryDir);
|
|
28182
|
+
if (!options.enabled) {
|
|
28183
|
+
return {
|
|
28184
|
+
enabled: false,
|
|
28185
|
+
promotionByOutcomeEnabled: options.promotionByOutcomeEnabled === true,
|
|
28186
|
+
rootDir,
|
|
28187
|
+
statePath,
|
|
28188
|
+
snapshot: null,
|
|
28189
|
+
weights: {
|
|
28190
|
+
total: 0,
|
|
28191
|
+
positive: 0,
|
|
28192
|
+
negative: 0,
|
|
28193
|
+
zero: 0
|
|
28194
|
+
}
|
|
28195
|
+
};
|
|
28196
|
+
}
|
|
28197
|
+
const snapshot = await readUtilityLearningSnapshot(options.memoryDir, options.utilityTelemetryDir);
|
|
28198
|
+
const weights = snapshot?.weights ?? [];
|
|
28199
|
+
return {
|
|
28200
|
+
enabled: true,
|
|
28201
|
+
promotionByOutcomeEnabled: options.promotionByOutcomeEnabled === true,
|
|
28202
|
+
rootDir,
|
|
28203
|
+
statePath,
|
|
28204
|
+
snapshot,
|
|
28205
|
+
weights: {
|
|
28206
|
+
total: weights.length,
|
|
28207
|
+
positive: weights.filter((entry) => entry.learnedWeight > 0).length,
|
|
28208
|
+
negative: weights.filter((entry) => entry.learnedWeight < 0).length,
|
|
28209
|
+
zero: weights.filter((entry) => entry.learnedWeight === 0).length,
|
|
28210
|
+
latestUpdatedAt: snapshot?.updatedAt
|
|
28211
|
+
}
|
|
28212
|
+
};
|
|
28213
|
+
}
|
|
28214
|
+
|
|
28215
|
+
// src/resume-bundles.ts
|
|
28216
|
+
import path61 from "path";
|
|
28217
|
+
import { mkdir as mkdir42, writeFile as writeFile39 } from "fs/promises";
|
|
27965
28218
|
var DEFAULT_RESUME_BUNDLE_REF_LIMIT = 5;
|
|
27966
28219
|
function resolveResumeBundleDir(memoryDir, overrideDir) {
|
|
27967
28220
|
if (typeof overrideDir === "string" && overrideDir.trim().length > 0) {
|
|
27968
28221
|
return overrideDir.trim();
|
|
27969
28222
|
}
|
|
27970
|
-
return
|
|
28223
|
+
return path61.join(memoryDir, "state", "resume-bundles");
|
|
27971
28224
|
}
|
|
27972
28225
|
function validateResumeBundle(raw) {
|
|
27973
28226
|
if (!isRecord2(raw)) throw new Error("resume bundle must be an object");
|
|
@@ -28011,7 +28264,7 @@ async function readValidatedItems(options) {
|
|
|
28011
28264
|
async function readObjectiveStateSnapshotsForSession(options) {
|
|
28012
28265
|
const rootDir = resolveObjectiveStateStoreDir(options.memoryDir, options.objectiveStateStoreDir);
|
|
28013
28266
|
const items = await readValidatedItems({
|
|
28014
|
-
rootDir:
|
|
28267
|
+
rootDir: path61.join(rootDir, "snapshots"),
|
|
28015
28268
|
validate: validateObjectiveStateSnapshot
|
|
28016
28269
|
});
|
|
28017
28270
|
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);
|
|
@@ -28019,7 +28272,7 @@ async function readObjectiveStateSnapshotsForSession(options) {
|
|
|
28019
28272
|
async function readWorkProductEntriesForSession(options) {
|
|
28020
28273
|
const rootDir = resolveWorkProductLedgerDir(options.memoryDir, options.workProductLedgerDir);
|
|
28021
28274
|
const items = await readValidatedItems({
|
|
28022
|
-
rootDir:
|
|
28275
|
+
rootDir: path61.join(rootDir, "entries"),
|
|
28023
28276
|
validate: validateWorkProductLedgerEntry
|
|
28024
28277
|
});
|
|
28025
28278
|
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);
|
|
@@ -28027,7 +28280,7 @@ async function readWorkProductEntriesForSession(options) {
|
|
|
28027
28280
|
async function readCommitmentEntriesForSession(options) {
|
|
28028
28281
|
const rootDir = resolveCommitmentLedgerDir(options.memoryDir, options.commitmentLedgerDir);
|
|
28029
28282
|
const items = await readValidatedItems({
|
|
28030
|
-
rootDir:
|
|
28283
|
+
rootDir: path61.join(rootDir, "entries"),
|
|
28031
28284
|
validate: validateCommitmentLedgerEntry
|
|
28032
28285
|
});
|
|
28033
28286
|
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);
|
|
@@ -28123,15 +28376,15 @@ async function recordResumeBundle(options) {
|
|
|
28123
28376
|
const rootDir = resolveResumeBundleDir(options.memoryDir, options.resumeBundleDir);
|
|
28124
28377
|
const validated = validateResumeBundle(options.bundle);
|
|
28125
28378
|
const day = recordStoreDay(validated.recordedAt);
|
|
28126
|
-
const bundlesDir =
|
|
28127
|
-
const filePath =
|
|
28128
|
-
await
|
|
28129
|
-
await
|
|
28379
|
+
const bundlesDir = path61.join(rootDir, "bundles", day);
|
|
28380
|
+
const filePath = path61.join(bundlesDir, `${validated.bundleId}.json`);
|
|
28381
|
+
await mkdir42(bundlesDir, { recursive: true });
|
|
28382
|
+
await writeFile39(filePath, JSON.stringify(validated, null, 2), "utf8");
|
|
28130
28383
|
return filePath;
|
|
28131
28384
|
}
|
|
28132
28385
|
async function readResumeBundles(options) {
|
|
28133
28386
|
const rootDir = resolveResumeBundleDir(options.memoryDir, options.resumeBundleDir);
|
|
28134
|
-
const files = await listJsonFiles(
|
|
28387
|
+
const files = await listJsonFiles(path61.join(rootDir, "bundles"));
|
|
28135
28388
|
const bundles = [];
|
|
28136
28389
|
const invalidBundles = [];
|
|
28137
28390
|
for (const filePath of files) {
|
|
@@ -28148,7 +28401,7 @@ async function readResumeBundles(options) {
|
|
|
28148
28401
|
}
|
|
28149
28402
|
async function getResumeBundleStatus(options) {
|
|
28150
28403
|
const rootDir = resolveResumeBundleDir(options.memoryDir, options.resumeBundleDir);
|
|
28151
|
-
const bundlesDir =
|
|
28404
|
+
const bundlesDir = path61.join(rootDir, "bundles");
|
|
28152
28405
|
if (!options.enabled) {
|
|
28153
28406
|
return {
|
|
28154
28407
|
enabled: false,
|
|
@@ -28592,6 +28845,22 @@ async function runUtilityTelemetryRecordCliCommand(options) {
|
|
|
28592
28845
|
event: options.event
|
|
28593
28846
|
});
|
|
28594
28847
|
}
|
|
28848
|
+
async function runUtilityLearningStatusCliCommand(options) {
|
|
28849
|
+
return getUtilityLearningStatus({
|
|
28850
|
+
memoryDir: options.memoryDir,
|
|
28851
|
+
enabled: options.memoryUtilityLearningEnabled,
|
|
28852
|
+
promotionByOutcomeEnabled: options.promotionByOutcomeEnabled
|
|
28853
|
+
});
|
|
28854
|
+
}
|
|
28855
|
+
async function runUtilityLearningCliCommand(options) {
|
|
28856
|
+
return learnUtilityPromotionWeights({
|
|
28857
|
+
memoryDir: options.memoryDir,
|
|
28858
|
+
enabled: options.memoryUtilityLearningEnabled,
|
|
28859
|
+
learningWindowDays: options.learningWindowDays ?? 14,
|
|
28860
|
+
minEventCount: options.minEventCount ?? 3,
|
|
28861
|
+
maxWeightMagnitude: options.maxWeightMagnitude ?? 0.35
|
|
28862
|
+
});
|
|
28863
|
+
}
|
|
28595
28864
|
async function runWorkProductRecordCliCommand(options) {
|
|
28596
28865
|
if (!options.creationMemoryEnabled) return null;
|
|
28597
28866
|
return recordWorkProductLedgerEntry({
|
|
@@ -28939,7 +29208,7 @@ function policyVersionForValues(values, config) {
|
|
|
28939
29208
|
return createHash10("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 12);
|
|
28940
29209
|
}
|
|
28941
29210
|
async function readRuntimePolicySnapshot2(config, fileName) {
|
|
28942
|
-
const filePath =
|
|
29211
|
+
const filePath = path62.join(config.memoryDir, "state", fileName);
|
|
28943
29212
|
const snapshot = await readRuntimePolicySnapshot(filePath, {
|
|
28944
29213
|
maxStaleDecayThreshold: config.lifecycleArchiveDecayThreshold
|
|
28945
29214
|
});
|
|
@@ -29439,7 +29708,7 @@ async function withTimeout(promise, timeoutMs, timeoutMessage) {
|
|
|
29439
29708
|
}
|
|
29440
29709
|
async function runReplayCliCommand(orchestrator, options) {
|
|
29441
29710
|
const extractionIdleTimeoutMs = Number.isFinite(options.extractionIdleTimeoutMs) ? Math.max(1e3, Math.floor(options.extractionIdleTimeoutMs)) : 15 * 6e4;
|
|
29442
|
-
const inputRaw = await
|
|
29711
|
+
const inputRaw = await readFile38(options.inputPath, "utf-8");
|
|
29443
29712
|
const registry = buildReplayNormalizerRegistry([
|
|
29444
29713
|
openclawReplayNormalizer,
|
|
29445
29714
|
claudeReplayNormalizer,
|
|
@@ -29504,7 +29773,7 @@ async function runReplayCliCommand(orchestrator, options) {
|
|
|
29504
29773
|
async function getPluginVersion() {
|
|
29505
29774
|
try {
|
|
29506
29775
|
const pkgPath = new URL("../package.json", import.meta.url);
|
|
29507
|
-
const raw = await
|
|
29776
|
+
const raw = await readFile38(pkgPath, "utf-8");
|
|
29508
29777
|
const parsed = JSON.parse(raw);
|
|
29509
29778
|
return parsed.version ?? "unknown";
|
|
29510
29779
|
} catch {
|
|
@@ -29523,14 +29792,14 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
|
|
|
29523
29792
|
const ns = (namespace ?? "").trim();
|
|
29524
29793
|
if (!ns) return orchestrator.config.memoryDir;
|
|
29525
29794
|
if (!orchestrator.config.namespacesEnabled) return orchestrator.config.memoryDir;
|
|
29526
|
-
const candidate =
|
|
29795
|
+
const candidate = path62.join(orchestrator.config.memoryDir, "namespaces", ns);
|
|
29527
29796
|
if (ns === orchestrator.config.defaultNamespace) {
|
|
29528
29797
|
return await exists2(candidate) ? candidate : orchestrator.config.memoryDir;
|
|
29529
29798
|
}
|
|
29530
29799
|
return candidate;
|
|
29531
29800
|
}
|
|
29532
29801
|
async function readAllMemoryFiles(memoryDir) {
|
|
29533
|
-
const roots = [
|
|
29802
|
+
const roots = [path62.join(memoryDir, "facts"), path62.join(memoryDir, "corrections")];
|
|
29534
29803
|
const out = [];
|
|
29535
29804
|
const walk = async (dir) => {
|
|
29536
29805
|
let entries;
|
|
@@ -29541,14 +29810,14 @@ async function readAllMemoryFiles(memoryDir) {
|
|
|
29541
29810
|
}
|
|
29542
29811
|
for (const entry of entries) {
|
|
29543
29812
|
const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
|
|
29544
|
-
const fullPath =
|
|
29813
|
+
const fullPath = path62.join(dir, entryName);
|
|
29545
29814
|
if (entry.isDirectory()) {
|
|
29546
29815
|
await walk(fullPath);
|
|
29547
29816
|
continue;
|
|
29548
29817
|
}
|
|
29549
29818
|
if (!entry.isFile() || !entryName.endsWith(".md")) continue;
|
|
29550
29819
|
try {
|
|
29551
|
-
const raw = await
|
|
29820
|
+
const raw = await readFile38(fullPath, "utf-8");
|
|
29552
29821
|
const parsed = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
29553
29822
|
if (!parsed) continue;
|
|
29554
29823
|
const fmRaw = parsed[1];
|
|
@@ -29954,6 +30223,30 @@ function registerCli(api, orchestrator) {
|
|
|
29954
30223
|
console.log(JSON.stringify({ wrote: filePath !== null, filePath }, null, 2));
|
|
29955
30224
|
console.log("OK");
|
|
29956
30225
|
});
|
|
30226
|
+
cmd.command("utility-learning-status").description("Show offline utility-learning snapshot status and learned weight counts").action(async () => {
|
|
30227
|
+
const status = await runUtilityLearningStatusCliCommand({
|
|
30228
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
30229
|
+
memoryUtilityLearningEnabled: orchestrator.config.memoryUtilityLearningEnabled,
|
|
30230
|
+
promotionByOutcomeEnabled: orchestrator.config.promotionByOutcomeEnabled
|
|
30231
|
+
});
|
|
30232
|
+
console.log(JSON.stringify(status, null, 2));
|
|
30233
|
+
console.log("OK");
|
|
30234
|
+
});
|
|
30235
|
+
cmd.command("utility-learn").description("Learn bounded offline promotion/ranking weights from recorded utility telemetry").option("--window-days <days>", "Telemetry lookback window in days", "14").option("--min-event-count <count>", "Minimum event count required per target/decision group", "3").option("--max-weight-magnitude <value>", "Maximum absolute learned weight magnitude", "0.35").action(async (...args) => {
|
|
30236
|
+
const options = args[0] ?? {};
|
|
30237
|
+
const learningWindowDays = typeof options.windowDays === "string" ? Number.parseInt(options.windowDays, 10) : 14;
|
|
30238
|
+
const minEventCount = typeof options.minEventCount === "string" ? Number.parseInt(options.minEventCount, 10) : 3;
|
|
30239
|
+
const maxWeightMagnitude = typeof options.maxWeightMagnitude === "string" ? Number.parseFloat(options.maxWeightMagnitude) : 0.35;
|
|
30240
|
+
const result = await runUtilityLearningCliCommand({
|
|
30241
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
30242
|
+
memoryUtilityLearningEnabled: orchestrator.config.memoryUtilityLearningEnabled,
|
|
30243
|
+
learningWindowDays,
|
|
30244
|
+
minEventCount,
|
|
30245
|
+
maxWeightMagnitude
|
|
30246
|
+
});
|
|
30247
|
+
console.log(JSON.stringify(result, null, 2));
|
|
30248
|
+
console.log("OK");
|
|
30249
|
+
});
|
|
29957
30250
|
cmd.command("resume-bundle-status").description("Show resume bundle status, bundle counts, and the latest recorded handoff bundle").action(async () => {
|
|
29958
30251
|
const status = await runResumeBundleStatusCliCommand({
|
|
29959
30252
|
memoryDir: orchestrator.config.memoryDir,
|
|
@@ -30860,7 +31153,7 @@ function registerCli(api, orchestrator) {
|
|
|
30860
31153
|
}
|
|
30861
31154
|
});
|
|
30862
31155
|
cmd.command("identity").description("Show agent identity reflections").action(async () => {
|
|
30863
|
-
const workspaceDir =
|
|
31156
|
+
const workspaceDir = path62.join(process.env.HOME ?? "~", ".openclaw", "workspace");
|
|
30864
31157
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
30865
31158
|
if (!identity) {
|
|
30866
31159
|
console.log("No identity file found.");
|
|
@@ -31083,8 +31376,8 @@ function registerCli(api, orchestrator) {
|
|
|
31083
31376
|
const options = args[0] ?? {};
|
|
31084
31377
|
const threadId = options.thread;
|
|
31085
31378
|
const top = parseInt(options.top ?? "10", 10);
|
|
31086
|
-
const memoryDir =
|
|
31087
|
-
const threading = new ThreadingManager(
|
|
31379
|
+
const memoryDir = path62.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
|
|
31380
|
+
const threading = new ThreadingManager(path62.join(memoryDir, "threads"));
|
|
31088
31381
|
if (threadId) {
|
|
31089
31382
|
const thread = await threading.loadThread(threadId);
|
|
31090
31383
|
if (!thread) {
|
|
@@ -31560,9 +31853,9 @@ async function recordObjectiveStateSnapshotsFromAgentMessages(options) {
|
|
|
31560
31853
|
}
|
|
31561
31854
|
|
|
31562
31855
|
// src/index.ts
|
|
31563
|
-
import { readFile as
|
|
31856
|
+
import { readFile as readFile39, writeFile as writeFile40 } from "fs/promises";
|
|
31564
31857
|
import { readFileSync as readFileSync4 } from "fs";
|
|
31565
|
-
import
|
|
31858
|
+
import path63 from "path";
|
|
31566
31859
|
import os6 from "os";
|
|
31567
31860
|
var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
|
|
31568
31861
|
var ENGRAM_HOOK_APIS = "__openclawEngramHookApis";
|
|
@@ -31570,7 +31863,7 @@ function loadPluginConfigFromFile() {
|
|
|
31570
31863
|
try {
|
|
31571
31864
|
const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
|
|
31572
31865
|
const homeDir = process.env.HOME ?? os6.homedir();
|
|
31573
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
31866
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path63.join(homeDir, ".openclaw", "openclaw.json");
|
|
31574
31867
|
const content = readFileSync4(configPath, "utf-8");
|
|
31575
31868
|
const config = JSON.parse(content);
|
|
31576
31869
|
const pluginEntry = config?.plugins?.entries?.["openclaw-engram"];
|
|
@@ -31820,11 +32113,11 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
31820
32113
|
`session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
|
|
31821
32114
|
);
|
|
31822
32115
|
const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
|
|
31823
|
-
const signalPath =
|
|
32116
|
+
const signalPath = path63.join(
|
|
31824
32117
|
workspaceDir,
|
|
31825
32118
|
`.compaction-reset-signal-${safeSessionKey}`
|
|
31826
32119
|
);
|
|
31827
|
-
await
|
|
32120
|
+
await writeFile40(
|
|
31828
32121
|
signalPath,
|
|
31829
32122
|
JSON.stringify({
|
|
31830
32123
|
sessionKey,
|
|
@@ -31851,11 +32144,11 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
31851
32144
|
);
|
|
31852
32145
|
async function ensureHourlySummaryCron(api2) {
|
|
31853
32146
|
const jobId = "engram-hourly-summary";
|
|
31854
|
-
const cronFilePath =
|
|
32147
|
+
const cronFilePath = path63.join(os6.homedir(), ".openclaw", "cron", "jobs.json");
|
|
31855
32148
|
try {
|
|
31856
32149
|
let jobsData = { version: 1, jobs: [] };
|
|
31857
32150
|
try {
|
|
31858
|
-
const content = await
|
|
32151
|
+
const content = await readFile39(cronFilePath, "utf-8");
|
|
31859
32152
|
jobsData = JSON.parse(content);
|
|
31860
32153
|
} catch {
|
|
31861
32154
|
}
|
|
@@ -31892,7 +32185,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
31892
32185
|
state: {}
|
|
31893
32186
|
};
|
|
31894
32187
|
jobsData.jobs.push(newJob);
|
|
31895
|
-
await
|
|
32188
|
+
await writeFile40(cronFilePath, JSON.stringify(jobsData, null, 2), "utf-8");
|
|
31896
32189
|
log.info("auto-registered hourly summary cron job");
|
|
31897
32190
|
} catch (err) {
|
|
31898
32191
|
log.error("failed to auto-register hourly summary cron job:", err);
|