@joshuaswarren/openclaw-engram 9.0.36 → 9.0.37
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 +1 -1
- package/dist/index.js +116 -17
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ AI agents forget everything between conversations. Engram fixes that.
|
|
|
38
38
|
- **Trust-zone recall** — Engram can now, when `trustZoneRecallEnabled` is enabled, inject prompt-relevant `working` and `trusted` trust-zone records into recall context as a separate `Trust Zones` section while keeping `quarantine` material out of recall by default.
|
|
39
39
|
- **Poisoning-defense corroboration** — Engram can now, when `memoryPoisoningDefenseEnabled` is enabled, score trust-zone provenance deterministically and require independent non-quarantine corroboration before risky `working -> trusted` promotions succeed.
|
|
40
40
|
- **Red-team benchmark packs** — Engram's eval harness can now validate and count typed `memory-red-team` benchmark packs so poisoning-defense regression suites stay explicit and reviewable instead of hiding inside generic benchmark metadata.
|
|
41
|
-
- **
|
|
41
|
+
- **Cue-anchor index foundation** — Engram can now, when `harmonicRetrievalEnabled` and `abstractionAnchorsEnabled` are enabled, persist typed cue anchors for entities, files, tools, outcomes, constraints, and dates, inspect them with `openclaw engram cue-anchor-status`, and keep harmonic retrieval grounded in explicit abstraction-to-cue links before blending logic lands.
|
|
42
42
|
- **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.
|
|
43
43
|
|
|
44
44
|
## Quick Start
|
package/dist/index.js
CHANGED
|
@@ -24223,7 +24223,7 @@ promotionCandidates: ${res.promotionCandidateCount}`
|
|
|
24223
24223
|
}
|
|
24224
24224
|
|
|
24225
24225
|
// src/cli.ts
|
|
24226
|
-
import
|
|
24226
|
+
import path56 from "path";
|
|
24227
24227
|
import { access as access3, readFile as readFile37, readdir as readdir24, unlink as unlink7 } from "fs/promises";
|
|
24228
24228
|
import { createHash as createHash10 } from "crypto";
|
|
24229
24229
|
|
|
@@ -25104,8 +25104,8 @@ function gatherCandidates(input, warnings) {
|
|
|
25104
25104
|
const record = rec;
|
|
25105
25105
|
const content = typeof record.content === "string" ? record.content : null;
|
|
25106
25106
|
if (!content) continue;
|
|
25107
|
-
const
|
|
25108
|
-
if (!
|
|
25107
|
+
const path58 = typeof record.path === "string" ? record.path : "";
|
|
25108
|
+
if (!path58.startsWith("transcripts/") && !path58.includes("/transcripts/")) continue;
|
|
25109
25109
|
rows.push(...parseJsonl(content, warnings));
|
|
25110
25110
|
}
|
|
25111
25111
|
return rows;
|
|
@@ -26823,6 +26823,87 @@ async function getAbstractionNodeStoreStatus(options) {
|
|
|
26823
26823
|
};
|
|
26824
26824
|
}
|
|
26825
26825
|
|
|
26826
|
+
// src/cue-anchors.ts
|
|
26827
|
+
import path55 from "path";
|
|
26828
|
+
import { mkdir as mkdir37, writeFile as writeFile34 } from "fs/promises";
|
|
26829
|
+
function validateAnchorType(raw) {
|
|
26830
|
+
const value = assertString2(raw, "anchorType");
|
|
26831
|
+
if (!["entity", "file", "tool", "outcome", "constraint", "date"].includes(value)) {
|
|
26832
|
+
throw new Error("anchorType must be one of entity|file|tool|outcome|constraint|date");
|
|
26833
|
+
}
|
|
26834
|
+
return value;
|
|
26835
|
+
}
|
|
26836
|
+
function validateNodeRefs(raw) {
|
|
26837
|
+
const nodeRefs = optionalStringArray2(raw, "nodeRefs");
|
|
26838
|
+
if (!nodeRefs || nodeRefs.length === 0) {
|
|
26839
|
+
throw new Error("nodeRefs must contain at least one node reference");
|
|
26840
|
+
}
|
|
26841
|
+
return nodeRefs.map((nodeRef, index) => assertSafePathSegment(nodeRef, `nodeRefs[${index}]`));
|
|
26842
|
+
}
|
|
26843
|
+
function resolveCueAnchorStoreDir(abstractionNodeStoreDir, overrideDir) {
|
|
26844
|
+
if (typeof overrideDir === "string" && overrideDir.trim().length > 0) {
|
|
26845
|
+
return overrideDir.trim();
|
|
26846
|
+
}
|
|
26847
|
+
return path55.join(abstractionNodeStoreDir, "anchors");
|
|
26848
|
+
}
|
|
26849
|
+
function validateCueAnchor(raw) {
|
|
26850
|
+
if (!isRecord2(raw)) throw new Error("cue anchor must be an object");
|
|
26851
|
+
if (raw.schemaVersion !== 1) throw new Error("schemaVersion must be 1");
|
|
26852
|
+
return {
|
|
26853
|
+
schemaVersion: 1,
|
|
26854
|
+
anchorId: assertSafePathSegment(assertString2(raw.anchorId, "anchorId"), "anchorId"),
|
|
26855
|
+
anchorType: validateAnchorType(raw.anchorType),
|
|
26856
|
+
anchorValue: assertString2(raw.anchorValue, "anchorValue"),
|
|
26857
|
+
normalizedCue: assertString2(raw.normalizedCue, "normalizedCue"),
|
|
26858
|
+
recordedAt: assertIsoRecordedAt(assertString2(raw.recordedAt, "recordedAt")),
|
|
26859
|
+
sessionKey: assertString2(raw.sessionKey, "sessionKey"),
|
|
26860
|
+
nodeRefs: validateNodeRefs(raw.nodeRefs),
|
|
26861
|
+
tags: optionalStringArray2(raw.tags, "tags"),
|
|
26862
|
+
metadata: validateStringRecord(raw.metadata, "metadata")
|
|
26863
|
+
};
|
|
26864
|
+
}
|
|
26865
|
+
async function getCueAnchorStoreStatus(options) {
|
|
26866
|
+
const abstractionNodeStoreDir = options.abstractionNodeStoreDir?.trim().length ? options.abstractionNodeStoreDir.trim() : path55.join(options.memoryDir, "state", "abstraction-nodes");
|
|
26867
|
+
const rootDir = resolveCueAnchorStoreDir(abstractionNodeStoreDir, options.cueAnchorStoreDir);
|
|
26868
|
+
const files = await listJsonFiles(rootDir);
|
|
26869
|
+
const anchors = [];
|
|
26870
|
+
const invalidAnchors = [];
|
|
26871
|
+
for (const filePath of files) {
|
|
26872
|
+
try {
|
|
26873
|
+
anchors.push(validateCueAnchor(await readJsonFile(filePath)));
|
|
26874
|
+
} catch (error) {
|
|
26875
|
+
invalidAnchors.push({
|
|
26876
|
+
path: filePath,
|
|
26877
|
+
error: error instanceof Error ? error.message : String(error)
|
|
26878
|
+
});
|
|
26879
|
+
}
|
|
26880
|
+
}
|
|
26881
|
+
anchors.sort((a, b) => b.recordedAt.localeCompare(a.recordedAt));
|
|
26882
|
+
const byType = {};
|
|
26883
|
+
let totalNodeRefs = 0;
|
|
26884
|
+
for (const anchor of anchors) {
|
|
26885
|
+
byType[anchor.anchorType] = (byType[anchor.anchorType] ?? 0) + 1;
|
|
26886
|
+
totalNodeRefs += anchor.nodeRefs.length;
|
|
26887
|
+
}
|
|
26888
|
+
return {
|
|
26889
|
+
enabled: options.enabled,
|
|
26890
|
+
anchorsEnabled: options.anchorsEnabled,
|
|
26891
|
+
rootDir,
|
|
26892
|
+
anchors: {
|
|
26893
|
+
total: files.length,
|
|
26894
|
+
valid: anchors.length,
|
|
26895
|
+
invalid: invalidAnchors.length,
|
|
26896
|
+
byType,
|
|
26897
|
+
totalNodeRefs,
|
|
26898
|
+
latestAnchorId: anchors[0]?.anchorId,
|
|
26899
|
+
latestRecordedAt: anchors[0]?.recordedAt,
|
|
26900
|
+
latestSessionKey: anchors[0]?.sessionKey
|
|
26901
|
+
},
|
|
26902
|
+
latestAnchor: anchors[0],
|
|
26903
|
+
invalidAnchors
|
|
26904
|
+
};
|
|
26905
|
+
}
|
|
26906
|
+
|
|
26826
26907
|
// src/cli.ts
|
|
26827
26908
|
function rankCandidateForKeep(a, b) {
|
|
26828
26909
|
const aConfidence = typeof a.frontmatter.confidence === "number" ? a.frontmatter.confidence : 0;
|
|
@@ -27040,6 +27121,14 @@ async function runAbstractionNodeStatusCliCommand(options) {
|
|
|
27040
27121
|
anchorsEnabled: options.abstractionAnchorsEnabled
|
|
27041
27122
|
});
|
|
27042
27123
|
}
|
|
27124
|
+
async function runCueAnchorStatusCliCommand(options) {
|
|
27125
|
+
return getCueAnchorStoreStatus({
|
|
27126
|
+
memoryDir: options.memoryDir,
|
|
27127
|
+
abstractionNodeStoreDir: options.abstractionNodeStoreDir,
|
|
27128
|
+
enabled: options.harmonicRetrievalEnabled,
|
|
27129
|
+
anchorsEnabled: options.abstractionAnchorsEnabled
|
|
27130
|
+
});
|
|
27131
|
+
}
|
|
27043
27132
|
async function runTrustZonePromoteCliCommand(options) {
|
|
27044
27133
|
const result = await promoteTrustZoneRecord({
|
|
27045
27134
|
memoryDir: options.memoryDir,
|
|
@@ -27286,7 +27375,7 @@ function policyVersionForValues(values, config) {
|
|
|
27286
27375
|
return createHash10("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 12);
|
|
27287
27376
|
}
|
|
27288
27377
|
async function readRuntimePolicySnapshot2(config, fileName) {
|
|
27289
|
-
const filePath =
|
|
27378
|
+
const filePath = path56.join(config.memoryDir, "state", fileName);
|
|
27290
27379
|
const snapshot = await readRuntimePolicySnapshot(filePath, {
|
|
27291
27380
|
maxStaleDecayThreshold: config.lifecycleArchiveDecayThreshold
|
|
27292
27381
|
});
|
|
@@ -27870,14 +27959,14 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
|
|
|
27870
27959
|
const ns = (namespace ?? "").trim();
|
|
27871
27960
|
if (!ns) return orchestrator.config.memoryDir;
|
|
27872
27961
|
if (!orchestrator.config.namespacesEnabled) return orchestrator.config.memoryDir;
|
|
27873
|
-
const candidate =
|
|
27962
|
+
const candidate = path56.join(orchestrator.config.memoryDir, "namespaces", ns);
|
|
27874
27963
|
if (ns === orchestrator.config.defaultNamespace) {
|
|
27875
27964
|
return await exists2(candidate) ? candidate : orchestrator.config.memoryDir;
|
|
27876
27965
|
}
|
|
27877
27966
|
return candidate;
|
|
27878
27967
|
}
|
|
27879
27968
|
async function readAllMemoryFiles(memoryDir) {
|
|
27880
|
-
const roots = [
|
|
27969
|
+
const roots = [path56.join(memoryDir, "facts"), path56.join(memoryDir, "corrections")];
|
|
27881
27970
|
const out = [];
|
|
27882
27971
|
const walk = async (dir) => {
|
|
27883
27972
|
let entries;
|
|
@@ -27888,7 +27977,7 @@ async function readAllMemoryFiles(memoryDir) {
|
|
|
27888
27977
|
}
|
|
27889
27978
|
for (const entry of entries) {
|
|
27890
27979
|
const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
|
|
27891
|
-
const fullPath =
|
|
27980
|
+
const fullPath = path56.join(dir, entryName);
|
|
27892
27981
|
if (entry.isDirectory()) {
|
|
27893
27982
|
await walk(fullPath);
|
|
27894
27983
|
continue;
|
|
@@ -28241,6 +28330,16 @@ function registerCli(api, orchestrator) {
|
|
|
28241
28330
|
console.log(JSON.stringify(status, null, 2));
|
|
28242
28331
|
console.log("OK");
|
|
28243
28332
|
});
|
|
28333
|
+
cmd.command("cue-anchor-status").description("Show cue-anchor index status, anchor counts, and the latest stored cue anchor").action(async () => {
|
|
28334
|
+
const status = await runCueAnchorStatusCliCommand({
|
|
28335
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
28336
|
+
abstractionNodeStoreDir: orchestrator.config.abstractionNodeStoreDir,
|
|
28337
|
+
harmonicRetrievalEnabled: orchestrator.config.harmonicRetrievalEnabled,
|
|
28338
|
+
abstractionAnchorsEnabled: orchestrator.config.abstractionAnchorsEnabled
|
|
28339
|
+
});
|
|
28340
|
+
console.log(JSON.stringify(status, null, 2));
|
|
28341
|
+
console.log("OK");
|
|
28342
|
+
});
|
|
28244
28343
|
cmd.command("trust-zone-promote").description("Dry-run or apply a trust-zone promotion with provenance enforcement").requiredOption("--record-id <recordId>", "Source trust-zone record id").requiredOption("--target-zone <targetZone>", "Promotion target zone (working|trusted)").requiredOption("--reason <reason>", "Human-readable promotion reason").option("--recorded-at <isoTimestamp>", "Promotion timestamp (defaults to now)").option("--summary <summary>", "Optional replacement summary for the promoted record").option("--dry-run", "Show the promotion plan without writing the promoted record").action(async (...args) => {
|
|
28245
28344
|
const options = args[0] ?? {};
|
|
28246
28345
|
const result = await runTrustZonePromoteCliCommand({
|
|
@@ -28908,7 +29007,7 @@ function registerCli(api, orchestrator) {
|
|
|
28908
29007
|
}
|
|
28909
29008
|
});
|
|
28910
29009
|
cmd.command("identity").description("Show agent identity reflections").action(async () => {
|
|
28911
|
-
const workspaceDir =
|
|
29010
|
+
const workspaceDir = path56.join(process.env.HOME ?? "~", ".openclaw", "workspace");
|
|
28912
29011
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
28913
29012
|
if (!identity) {
|
|
28914
29013
|
console.log("No identity file found.");
|
|
@@ -29131,8 +29230,8 @@ function registerCli(api, orchestrator) {
|
|
|
29131
29230
|
const options = args[0] ?? {};
|
|
29132
29231
|
const threadId = options.thread;
|
|
29133
29232
|
const top = parseInt(options.top ?? "10", 10);
|
|
29134
|
-
const memoryDir =
|
|
29135
|
-
const threading = new ThreadingManager(
|
|
29233
|
+
const memoryDir = path56.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
|
|
29234
|
+
const threading = new ThreadingManager(path56.join(memoryDir, "threads"));
|
|
29136
29235
|
if (threadId) {
|
|
29137
29236
|
const thread = await threading.loadThread(threadId);
|
|
29138
29237
|
if (!thread) {
|
|
@@ -29608,9 +29707,9 @@ async function recordObjectiveStateSnapshotsFromAgentMessages(options) {
|
|
|
29608
29707
|
}
|
|
29609
29708
|
|
|
29610
29709
|
// src/index.ts
|
|
29611
|
-
import { readFile as readFile38, writeFile as
|
|
29710
|
+
import { readFile as readFile38, writeFile as writeFile35 } from "fs/promises";
|
|
29612
29711
|
import { readFileSync as readFileSync4 } from "fs";
|
|
29613
|
-
import
|
|
29712
|
+
import path57 from "path";
|
|
29614
29713
|
import os6 from "os";
|
|
29615
29714
|
var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
|
|
29616
29715
|
var ENGRAM_HOOK_APIS = "__openclawEngramHookApis";
|
|
@@ -29618,7 +29717,7 @@ function loadPluginConfigFromFile() {
|
|
|
29618
29717
|
try {
|
|
29619
29718
|
const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
|
|
29620
29719
|
const homeDir = process.env.HOME ?? os6.homedir();
|
|
29621
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
29720
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path57.join(homeDir, ".openclaw", "openclaw.json");
|
|
29622
29721
|
const content = readFileSync4(configPath, "utf-8");
|
|
29623
29722
|
const config = JSON.parse(content);
|
|
29624
29723
|
const pluginEntry = config?.plugins?.entries?.["openclaw-engram"];
|
|
@@ -29868,11 +29967,11 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
29868
29967
|
`session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
|
|
29869
29968
|
);
|
|
29870
29969
|
const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
|
|
29871
|
-
const signalPath =
|
|
29970
|
+
const signalPath = path57.join(
|
|
29872
29971
|
workspaceDir,
|
|
29873
29972
|
`.compaction-reset-signal-${safeSessionKey}`
|
|
29874
29973
|
);
|
|
29875
|
-
await
|
|
29974
|
+
await writeFile35(
|
|
29876
29975
|
signalPath,
|
|
29877
29976
|
JSON.stringify({
|
|
29878
29977
|
sessionKey,
|
|
@@ -29899,7 +29998,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
29899
29998
|
);
|
|
29900
29999
|
async function ensureHourlySummaryCron(api2) {
|
|
29901
30000
|
const jobId = "engram-hourly-summary";
|
|
29902
|
-
const cronFilePath =
|
|
30001
|
+
const cronFilePath = path57.join(os6.homedir(), ".openclaw", "cron", "jobs.json");
|
|
29903
30002
|
try {
|
|
29904
30003
|
let jobsData = { version: 1, jobs: [] };
|
|
29905
30004
|
try {
|
|
@@ -29940,7 +30039,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
29940
30039
|
state: {}
|
|
29941
30040
|
};
|
|
29942
30041
|
jobsData.jobs.push(newJob);
|
|
29943
|
-
await
|
|
30042
|
+
await writeFile35(cronFilePath, JSON.stringify(jobsData, null, 2), "utf-8");
|
|
29944
30043
|
log.info("auto-registered hourly summary cron job");
|
|
29945
30044
|
} catch (err) {
|
|
29946
30045
|
log.error("failed to auto-register hourly summary cron job:", err);
|