@joshuaswarren/openclaw-engram 9.0.35 → 9.0.36
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 -0
- package/dist/index.js +122 -17
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +29 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,6 +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
|
+
- **Abstraction-node foundation** — Engram can now, when `harmonicRetrievalEnabled` is enabled, persist typed abstraction nodes for later harmonic retrieval slices, with `abstractionAnchorsEnabled` reserved for the future cue-anchor layer.
|
|
41
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.
|
|
42
43
|
|
|
43
44
|
## Quick Start
|
package/dist/index.js
CHANGED
|
@@ -302,6 +302,9 @@ function parseConfig(raw) {
|
|
|
302
302
|
trustZoneRecallEnabled: cfg.trustZoneRecallEnabled === true,
|
|
303
303
|
memoryPoisoningDefenseEnabled: cfg.memoryPoisoningDefenseEnabled === true,
|
|
304
304
|
memoryRedTeamBenchEnabled: cfg.memoryRedTeamBenchEnabled === true,
|
|
305
|
+
harmonicRetrievalEnabled: cfg.harmonicRetrievalEnabled === true,
|
|
306
|
+
abstractionAnchorsEnabled: cfg.abstractionAnchorsEnabled === true,
|
|
307
|
+
abstractionNodeStoreDir: typeof cfg.abstractionNodeStoreDir === "string" && cfg.abstractionNodeStoreDir.trim().length > 0 ? cfg.abstractionNodeStoreDir.trim() : path.join(memoryDir, "state", "abstraction-nodes"),
|
|
305
308
|
// Local LLM Provider (v2.1)
|
|
306
309
|
localLlmEnabled: cfg.localLlmEnabled === true || cfg.localLlmEnabled === "true",
|
|
307
310
|
// default: false
|
|
@@ -24220,7 +24223,7 @@ promotionCandidates: ${res.promotionCandidateCount}`
|
|
|
24220
24223
|
}
|
|
24221
24224
|
|
|
24222
24225
|
// src/cli.ts
|
|
24223
|
-
import
|
|
24226
|
+
import path55 from "path";
|
|
24224
24227
|
import { access as access3, readFile as readFile37, readdir as readdir24, unlink as unlink7 } from "fs/promises";
|
|
24225
24228
|
import { createHash as createHash10 } from "crypto";
|
|
24226
24229
|
|
|
@@ -25101,8 +25104,8 @@ function gatherCandidates(input, warnings) {
|
|
|
25101
25104
|
const record = rec;
|
|
25102
25105
|
const content = typeof record.content === "string" ? record.content : null;
|
|
25103
25106
|
if (!content) continue;
|
|
25104
|
-
const
|
|
25105
|
-
if (!
|
|
25107
|
+
const path57 = typeof record.path === "string" ? record.path : "";
|
|
25108
|
+
if (!path57.startsWith("transcripts/") && !path57.includes("/transcripts/")) continue;
|
|
25106
25109
|
rows.push(...parseJsonl(content, warnings));
|
|
25107
25110
|
}
|
|
25108
25111
|
return rows;
|
|
@@ -26736,6 +26739,90 @@ async function runCompatChecks(options) {
|
|
|
26736
26739
|
};
|
|
26737
26740
|
}
|
|
26738
26741
|
|
|
26742
|
+
// src/abstraction-nodes.ts
|
|
26743
|
+
import path54 from "path";
|
|
26744
|
+
import { mkdir as mkdir36, writeFile as writeFile33 } from "fs/promises";
|
|
26745
|
+
function validateKind2(raw) {
|
|
26746
|
+
const value = assertString2(raw, "kind");
|
|
26747
|
+
if (!["episode", "topic", "project", "workflow", "constraint"].includes(value)) {
|
|
26748
|
+
throw new Error("kind must be one of episode|topic|project|workflow|constraint");
|
|
26749
|
+
}
|
|
26750
|
+
return value;
|
|
26751
|
+
}
|
|
26752
|
+
function validateLevel(raw) {
|
|
26753
|
+
const value = assertString2(raw, "abstractionLevel");
|
|
26754
|
+
if (!["micro", "meso", "macro"].includes(value)) {
|
|
26755
|
+
throw new Error("abstractionLevel must be one of micro|meso|macro");
|
|
26756
|
+
}
|
|
26757
|
+
return value;
|
|
26758
|
+
}
|
|
26759
|
+
function resolveAbstractionNodeStoreDir(memoryDir, overrideDir) {
|
|
26760
|
+
if (typeof overrideDir === "string" && overrideDir.trim().length > 0) {
|
|
26761
|
+
return overrideDir.trim();
|
|
26762
|
+
}
|
|
26763
|
+
return path54.join(memoryDir, "state", "abstraction-nodes");
|
|
26764
|
+
}
|
|
26765
|
+
function validateAbstractionNode(raw) {
|
|
26766
|
+
if (!isRecord2(raw)) throw new Error("abstraction node must be an object");
|
|
26767
|
+
if (raw.schemaVersion !== 1) throw new Error("schemaVersion must be 1");
|
|
26768
|
+
return {
|
|
26769
|
+
schemaVersion: 1,
|
|
26770
|
+
nodeId: assertSafePathSegment(assertString2(raw.nodeId, "nodeId"), "nodeId"),
|
|
26771
|
+
recordedAt: assertIsoRecordedAt(assertString2(raw.recordedAt, "recordedAt")),
|
|
26772
|
+
sessionKey: assertString2(raw.sessionKey, "sessionKey"),
|
|
26773
|
+
kind: validateKind2(raw.kind),
|
|
26774
|
+
abstractionLevel: validateLevel(raw.abstractionLevel),
|
|
26775
|
+
title: assertString2(raw.title, "title"),
|
|
26776
|
+
summary: assertString2(raw.summary, "summary"),
|
|
26777
|
+
sourceMemoryIds: optionalStringArray2(raw.sourceMemoryIds, "sourceMemoryIds"),
|
|
26778
|
+
entityRefs: optionalStringArray2(raw.entityRefs, "entityRefs"),
|
|
26779
|
+
tags: optionalStringArray2(raw.tags, "tags"),
|
|
26780
|
+
metadata: validateStringRecord(raw.metadata, "metadata")
|
|
26781
|
+
};
|
|
26782
|
+
}
|
|
26783
|
+
async function getAbstractionNodeStoreStatus(options) {
|
|
26784
|
+
const rootDir = resolveAbstractionNodeStoreDir(options.memoryDir, options.abstractionNodeStoreDir);
|
|
26785
|
+
const nodesDir = path54.join(rootDir, "nodes");
|
|
26786
|
+
const files = await listJsonFiles(nodesDir);
|
|
26787
|
+
const nodes = [];
|
|
26788
|
+
const invalidNodes = [];
|
|
26789
|
+
for (const filePath of files) {
|
|
26790
|
+
try {
|
|
26791
|
+
nodes.push(validateAbstractionNode(await readJsonFile(filePath)));
|
|
26792
|
+
} catch (error) {
|
|
26793
|
+
invalidNodes.push({
|
|
26794
|
+
path: filePath,
|
|
26795
|
+
error: error instanceof Error ? error.message : String(error)
|
|
26796
|
+
});
|
|
26797
|
+
}
|
|
26798
|
+
}
|
|
26799
|
+
nodes.sort((a, b) => b.recordedAt.localeCompare(a.recordedAt));
|
|
26800
|
+
const byKind = {};
|
|
26801
|
+
const byLevel = {};
|
|
26802
|
+
for (const node of nodes) {
|
|
26803
|
+
byKind[node.kind] = (byKind[node.kind] ?? 0) + 1;
|
|
26804
|
+
byLevel[node.abstractionLevel] = (byLevel[node.abstractionLevel] ?? 0) + 1;
|
|
26805
|
+
}
|
|
26806
|
+
return {
|
|
26807
|
+
enabled: options.enabled,
|
|
26808
|
+
anchorsEnabled: options.anchorsEnabled,
|
|
26809
|
+
rootDir,
|
|
26810
|
+
nodesDir,
|
|
26811
|
+
nodes: {
|
|
26812
|
+
total: files.length,
|
|
26813
|
+
valid: nodes.length,
|
|
26814
|
+
invalid: invalidNodes.length,
|
|
26815
|
+
byKind,
|
|
26816
|
+
byLevel,
|
|
26817
|
+
latestNodeId: nodes[0]?.nodeId,
|
|
26818
|
+
latestRecordedAt: nodes[0]?.recordedAt,
|
|
26819
|
+
latestSessionKey: nodes[0]?.sessionKey
|
|
26820
|
+
},
|
|
26821
|
+
latestNode: nodes[0],
|
|
26822
|
+
invalidNodes
|
|
26823
|
+
};
|
|
26824
|
+
}
|
|
26825
|
+
|
|
26739
26826
|
// src/cli.ts
|
|
26740
26827
|
function rankCandidateForKeep(a, b) {
|
|
26741
26828
|
const aConfidence = typeof a.frontmatter.confidence === "number" ? a.frontmatter.confidence : 0;
|
|
@@ -26945,6 +27032,14 @@ async function runTrustZoneStatusCliCommand(options) {
|
|
|
26945
27032
|
poisoningDefenseEnabled: options.memoryPoisoningDefenseEnabled
|
|
26946
27033
|
});
|
|
26947
27034
|
}
|
|
27035
|
+
async function runAbstractionNodeStatusCliCommand(options) {
|
|
27036
|
+
return getAbstractionNodeStoreStatus({
|
|
27037
|
+
memoryDir: options.memoryDir,
|
|
27038
|
+
abstractionNodeStoreDir: options.abstractionNodeStoreDir,
|
|
27039
|
+
enabled: options.harmonicRetrievalEnabled,
|
|
27040
|
+
anchorsEnabled: options.abstractionAnchorsEnabled
|
|
27041
|
+
});
|
|
27042
|
+
}
|
|
26948
27043
|
async function runTrustZonePromoteCliCommand(options) {
|
|
26949
27044
|
const result = await promoteTrustZoneRecord({
|
|
26950
27045
|
memoryDir: options.memoryDir,
|
|
@@ -27191,7 +27286,7 @@ function policyVersionForValues(values, config) {
|
|
|
27191
27286
|
return createHash10("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 12);
|
|
27192
27287
|
}
|
|
27193
27288
|
async function readRuntimePolicySnapshot2(config, fileName) {
|
|
27194
|
-
const filePath =
|
|
27289
|
+
const filePath = path55.join(config.memoryDir, "state", fileName);
|
|
27195
27290
|
const snapshot = await readRuntimePolicySnapshot(filePath, {
|
|
27196
27291
|
maxStaleDecayThreshold: config.lifecycleArchiveDecayThreshold
|
|
27197
27292
|
});
|
|
@@ -27775,14 +27870,14 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
|
|
|
27775
27870
|
const ns = (namespace ?? "").trim();
|
|
27776
27871
|
if (!ns) return orchestrator.config.memoryDir;
|
|
27777
27872
|
if (!orchestrator.config.namespacesEnabled) return orchestrator.config.memoryDir;
|
|
27778
|
-
const candidate =
|
|
27873
|
+
const candidate = path55.join(orchestrator.config.memoryDir, "namespaces", ns);
|
|
27779
27874
|
if (ns === orchestrator.config.defaultNamespace) {
|
|
27780
27875
|
return await exists2(candidate) ? candidate : orchestrator.config.memoryDir;
|
|
27781
27876
|
}
|
|
27782
27877
|
return candidate;
|
|
27783
27878
|
}
|
|
27784
27879
|
async function readAllMemoryFiles(memoryDir) {
|
|
27785
|
-
const roots = [
|
|
27880
|
+
const roots = [path55.join(memoryDir, "facts"), path55.join(memoryDir, "corrections")];
|
|
27786
27881
|
const out = [];
|
|
27787
27882
|
const walk = async (dir) => {
|
|
27788
27883
|
let entries;
|
|
@@ -27793,7 +27888,7 @@ async function readAllMemoryFiles(memoryDir) {
|
|
|
27793
27888
|
}
|
|
27794
27889
|
for (const entry of entries) {
|
|
27795
27890
|
const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
|
|
27796
|
-
const fullPath =
|
|
27891
|
+
const fullPath = path55.join(dir, entryName);
|
|
27797
27892
|
if (entry.isDirectory()) {
|
|
27798
27893
|
await walk(fullPath);
|
|
27799
27894
|
continue;
|
|
@@ -28136,6 +28231,16 @@ function registerCli(api, orchestrator) {
|
|
|
28136
28231
|
console.log(JSON.stringify(status, null, 2));
|
|
28137
28232
|
console.log("OK");
|
|
28138
28233
|
});
|
|
28234
|
+
cmd.command("abstraction-node-status").description("Show abstraction-node store status, abstraction counts, and latest stored node").action(async () => {
|
|
28235
|
+
const status = await runAbstractionNodeStatusCliCommand({
|
|
28236
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
28237
|
+
abstractionNodeStoreDir: orchestrator.config.abstractionNodeStoreDir,
|
|
28238
|
+
harmonicRetrievalEnabled: orchestrator.config.harmonicRetrievalEnabled,
|
|
28239
|
+
abstractionAnchorsEnabled: orchestrator.config.abstractionAnchorsEnabled
|
|
28240
|
+
});
|
|
28241
|
+
console.log(JSON.stringify(status, null, 2));
|
|
28242
|
+
console.log("OK");
|
|
28243
|
+
});
|
|
28139
28244
|
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) => {
|
|
28140
28245
|
const options = args[0] ?? {};
|
|
28141
28246
|
const result = await runTrustZonePromoteCliCommand({
|
|
@@ -28803,7 +28908,7 @@ function registerCli(api, orchestrator) {
|
|
|
28803
28908
|
}
|
|
28804
28909
|
});
|
|
28805
28910
|
cmd.command("identity").description("Show agent identity reflections").action(async () => {
|
|
28806
|
-
const workspaceDir =
|
|
28911
|
+
const workspaceDir = path55.join(process.env.HOME ?? "~", ".openclaw", "workspace");
|
|
28807
28912
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
28808
28913
|
if (!identity) {
|
|
28809
28914
|
console.log("No identity file found.");
|
|
@@ -29026,8 +29131,8 @@ function registerCli(api, orchestrator) {
|
|
|
29026
29131
|
const options = args[0] ?? {};
|
|
29027
29132
|
const threadId = options.thread;
|
|
29028
29133
|
const top = parseInt(options.top ?? "10", 10);
|
|
29029
|
-
const memoryDir =
|
|
29030
|
-
const threading = new ThreadingManager(
|
|
29134
|
+
const memoryDir = path55.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
|
|
29135
|
+
const threading = new ThreadingManager(path55.join(memoryDir, "threads"));
|
|
29031
29136
|
if (threadId) {
|
|
29032
29137
|
const thread = await threading.loadThread(threadId);
|
|
29033
29138
|
if (!thread) {
|
|
@@ -29503,9 +29608,9 @@ async function recordObjectiveStateSnapshotsFromAgentMessages(options) {
|
|
|
29503
29608
|
}
|
|
29504
29609
|
|
|
29505
29610
|
// src/index.ts
|
|
29506
|
-
import { readFile as readFile38, writeFile as
|
|
29611
|
+
import { readFile as readFile38, writeFile as writeFile34 } from "fs/promises";
|
|
29507
29612
|
import { readFileSync as readFileSync4 } from "fs";
|
|
29508
|
-
import
|
|
29613
|
+
import path56 from "path";
|
|
29509
29614
|
import os6 from "os";
|
|
29510
29615
|
var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
|
|
29511
29616
|
var ENGRAM_HOOK_APIS = "__openclawEngramHookApis";
|
|
@@ -29513,7 +29618,7 @@ function loadPluginConfigFromFile() {
|
|
|
29513
29618
|
try {
|
|
29514
29619
|
const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
|
|
29515
29620
|
const homeDir = process.env.HOME ?? os6.homedir();
|
|
29516
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
29621
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path56.join(homeDir, ".openclaw", "openclaw.json");
|
|
29517
29622
|
const content = readFileSync4(configPath, "utf-8");
|
|
29518
29623
|
const config = JSON.parse(content);
|
|
29519
29624
|
const pluginEntry = config?.plugins?.entries?.["openclaw-engram"];
|
|
@@ -29763,11 +29868,11 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
29763
29868
|
`session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
|
|
29764
29869
|
);
|
|
29765
29870
|
const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
|
|
29766
|
-
const signalPath =
|
|
29871
|
+
const signalPath = path56.join(
|
|
29767
29872
|
workspaceDir,
|
|
29768
29873
|
`.compaction-reset-signal-${safeSessionKey}`
|
|
29769
29874
|
);
|
|
29770
|
-
await
|
|
29875
|
+
await writeFile34(
|
|
29771
29876
|
signalPath,
|
|
29772
29877
|
JSON.stringify({
|
|
29773
29878
|
sessionKey,
|
|
@@ -29794,7 +29899,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
29794
29899
|
);
|
|
29795
29900
|
async function ensureHourlySummaryCron(api2) {
|
|
29796
29901
|
const jobId = "engram-hourly-summary";
|
|
29797
|
-
const cronFilePath =
|
|
29902
|
+
const cronFilePath = path56.join(os6.homedir(), ".openclaw", "cron", "jobs.json");
|
|
29798
29903
|
try {
|
|
29799
29904
|
let jobsData = { version: 1, jobs: [] };
|
|
29800
29905
|
try {
|
|
@@ -29835,7 +29940,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
29835
29940
|
state: {}
|
|
29836
29941
|
};
|
|
29837
29942
|
jobsData.jobs.push(newJob);
|
|
29838
|
-
await
|
|
29943
|
+
await writeFile34(cronFilePath, JSON.stringify(jobsData, null, 2), "utf-8");
|
|
29839
29944
|
log.info("auto-registered hourly summary cron job");
|
|
29840
29945
|
} catch (err) {
|
|
29841
29946
|
log.error("failed to auto-register hourly summary cron job:", err);
|