@joshuaswarren/openclaw-engram 9.0.28 → 9.0.29

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 CHANGED
@@ -34,6 +34,7 @@ AI agents forget everything between conversations. Engram fixes that.
34
34
  - **Objective-state recall** — Engram can now store normalized file, process, and tool outcomes and, when `objectiveStateRecallEnabled` is enabled, inject the most relevant objective-state snapshots back into recall context as a separate `Objective State` section.
35
35
  - **Causal trajectory graph foundation** — Engram can now persist typed `goal -> action -> observation -> outcome -> follow-up` chains when `causalTrajectoryMemoryEnabled` is enabled and, with `actionGraphRecallEnabled`, emit deterministic action-conditioned edges into the causal graph for later trajectory-aware retrieval.
36
36
  - **Causal trajectory recall** — Engram can now, when `causalTrajectoryRecallEnabled` is enabled, inject prompt-relevant causal chains back into recall context as a separate `Causal Trajectories` section with lightweight match explainability.
37
+ - **Trust-zone store foundation** — Engram can now, when `trustZonesEnabled` is enabled, persist typed quarantine, working, and trusted records with provenance metadata into a dedicated trust-zone store for later promotion and defense slices.
37
38
  - **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.
38
39
 
39
40
  ## Quick Start
@@ -189,6 +190,9 @@ Key settings:
189
190
  | `causalTrajectoryStoreDir` | `{memoryDir}/state/causal-trajectories` | Root directory for causal-trajectory records |
190
191
  | `causalTrajectoryRecallEnabled` | `false` | Inject prompt-relevant causal trajectories into recall context |
191
192
  | `actionGraphRecallEnabled` | `false` | Write action-conditioned causal-stage edges from typed trajectory records into the causal graph |
193
+ | `trustZonesEnabled` | `false` | Enable the trust-zone memory foundation for quarantine, working, and trusted records |
194
+ | `quarantinePromotionEnabled` | `false` | Reserve future promotion flows from quarantine into higher-trust zones |
195
+ | `trustZoneStoreDir` | `{memoryDir}/state/trust-zones` | Root directory for trust-zone records |
192
196
 
193
197
  Full reference: [Config Reference](docs/config-reference.md)
194
198
 
package/dist/index.js CHANGED
@@ -296,6 +296,9 @@ function parseConfig(raw) {
296
296
  causalTrajectoryStoreDir: typeof cfg.causalTrajectoryStoreDir === "string" && cfg.causalTrajectoryStoreDir.trim().length > 0 ? cfg.causalTrajectoryStoreDir.trim() : path.join(memoryDir, "state", "causal-trajectories"),
297
297
  causalTrajectoryRecallEnabled: cfg.causalTrajectoryRecallEnabled === true,
298
298
  actionGraphRecallEnabled: cfg.actionGraphRecallEnabled === true,
299
+ trustZonesEnabled: cfg.trustZonesEnabled === true,
300
+ quarantinePromotionEnabled: cfg.quarantinePromotionEnabled === true,
301
+ trustZoneStoreDir: typeof cfg.trustZoneStoreDir === "string" && cfg.trustZoneStoreDir.trim().length > 0 ? cfg.trustZoneStoreDir.trim() : path.join(memoryDir, "state", "trust-zones"),
299
302
  // Local LLM Provider (v2.1)
300
303
  localLlmEnabled: cfg.localLlmEnabled === true || cfg.localLlmEnabled === "true",
301
304
  // default: false
@@ -14213,9 +14216,9 @@ function assertSafePathSegment(value, field) {
14213
14216
  }
14214
14217
  return value;
14215
14218
  }
14216
- function assertIsoRecordedAt(value) {
14219
+ function assertIsoRecordedAt(value, field = "recordedAt") {
14217
14220
  if (!/^\d{4}-\d{2}-\d{2}T/.test(value)) {
14218
- throw new Error("recordedAt must be an ISO timestamp");
14221
+ throw new Error(`${field} must be an ISO timestamp`);
14219
14222
  }
14220
14223
  return value;
14221
14224
  }
@@ -23718,7 +23721,7 @@ promotionCandidates: ${res.promotionCandidateCount}`
23718
23721
  }
23719
23722
 
23720
23723
  // src/cli.ts
23721
- import path53 from "path";
23724
+ import path54 from "path";
23722
23725
  import { access as access3, readFile as readFile37, readdir as readdir24, unlink as unlink7 } from "fs/promises";
23723
23726
  import { createHash as createHash10 } from "crypto";
23724
23727
 
@@ -24599,8 +24602,8 @@ function gatherCandidates(input, warnings) {
24599
24602
  const record = rec;
24600
24603
  const content = typeof record.content === "string" ? record.content : null;
24601
24604
  if (!content) continue;
24602
- const path55 = typeof record.path === "string" ? record.path : "";
24603
- if (!path55.startsWith("transcripts/") && !path55.includes("/transcripts/")) continue;
24605
+ const path56 = typeof record.path === "string" ? record.path : "";
24606
+ if (!path56.startsWith("transcripts/") && !path56.includes("/transcripts/")) continue;
24604
24607
  rows.push(...parseJsonl(content, warnings));
24605
24608
  }
24606
24609
  return rows;
@@ -26234,6 +26237,111 @@ async function runCompatChecks(options) {
26234
26237
  };
26235
26238
  }
26236
26239
 
26240
+ // src/trust-zones.ts
26241
+ import path53 from "path";
26242
+ import { mkdir as mkdir35, writeFile as writeFile32 } from "fs/promises";
26243
+ function validateMetadata3(raw) {
26244
+ return validateStringRecord(raw, "metadata");
26245
+ }
26246
+ function validateZone(raw, field) {
26247
+ const value = assertString2(raw, field);
26248
+ if (!["quarantine", "working", "trusted"].includes(value)) {
26249
+ throw new Error(`${field} must be one of quarantine|working|trusted`);
26250
+ }
26251
+ return value;
26252
+ }
26253
+ function validateKind(raw) {
26254
+ const value = assertString2(raw, "kind");
26255
+ if (!["memory", "artifact", "state", "trajectory", "external"].includes(value)) {
26256
+ throw new Error("kind must be one of memory|artifact|state|trajectory|external");
26257
+ }
26258
+ return value;
26259
+ }
26260
+ function validateProvenance(raw) {
26261
+ if (!isRecord2(raw)) throw new Error("provenance must be an object");
26262
+ const sourceClass = assertString2(raw.sourceClass, "provenance.sourceClass");
26263
+ if (!["tool_output", "web_content", "subagent_trace", "system_memory", "user_input", "manual"].includes(sourceClass)) {
26264
+ throw new Error("provenance.sourceClass must be one of tool_output|web_content|subagent_trace|system_memory|user_input|manual");
26265
+ }
26266
+ return {
26267
+ sourceClass,
26268
+ observedAt: assertIsoRecordedAt(assertString2(raw.observedAt, "provenance.observedAt"), "provenance.observedAt"),
26269
+ sessionKey: optionalString(raw.sessionKey),
26270
+ sourceId: optionalString(raw.sourceId),
26271
+ evidenceHash: optionalString(raw.evidenceHash)
26272
+ };
26273
+ }
26274
+ function resolveTrustZoneStoreDir(memoryDir, overrideDir) {
26275
+ if (typeof overrideDir === "string" && overrideDir.trim().length > 0) {
26276
+ return overrideDir.trim();
26277
+ }
26278
+ return path53.join(memoryDir, "state", "trust-zones");
26279
+ }
26280
+ function validateTrustZoneRecord(raw) {
26281
+ if (!isRecord2(raw)) throw new Error("trust-zone record must be an object");
26282
+ if (raw.schemaVersion !== 1) throw new Error("schemaVersion must be 1");
26283
+ return {
26284
+ schemaVersion: 1,
26285
+ recordId: assertSafePathSegment(assertString2(raw.recordId, "recordId"), "recordId"),
26286
+ zone: validateZone(raw.zone, "zone"),
26287
+ recordedAt: assertIsoRecordedAt(assertString2(raw.recordedAt, "recordedAt")),
26288
+ kind: validateKind(raw.kind),
26289
+ summary: assertString2(raw.summary, "summary"),
26290
+ provenance: validateProvenance(raw.provenance),
26291
+ promotedFromZone: raw.promotedFromZone === void 0 ? void 0 : validateZone(raw.promotedFromZone, "promotedFromZone"),
26292
+ entityRefs: optionalStringArray2(raw.entityRefs, "entityRefs"),
26293
+ tags: optionalStringArray2(raw.tags, "tags"),
26294
+ metadata: validateMetadata3(raw.metadata)
26295
+ };
26296
+ }
26297
+ async function readTrustZoneRecords(options) {
26298
+ const rootDir = resolveTrustZoneStoreDir(options.memoryDir, options.trustZoneStoreDir);
26299
+ const files = await listJsonFiles(path53.join(rootDir, "zones"));
26300
+ const records = [];
26301
+ const invalidRecords = [];
26302
+ for (const filePath of files) {
26303
+ try {
26304
+ records.push(validateTrustZoneRecord(await readJsonFile(filePath)));
26305
+ } catch (error) {
26306
+ invalidRecords.push({
26307
+ path: filePath,
26308
+ error: error instanceof Error ? error.message : String(error)
26309
+ });
26310
+ }
26311
+ }
26312
+ return { files, records, invalidRecords };
26313
+ }
26314
+ async function getTrustZoneStoreStatus(options) {
26315
+ const rootDir = resolveTrustZoneStoreDir(options.memoryDir, options.trustZoneStoreDir);
26316
+ const zonesDir = path53.join(rootDir, "zones");
26317
+ const { files, records, invalidRecords } = await readTrustZoneRecords(options);
26318
+ records.sort((a, b) => b.recordedAt.localeCompare(a.recordedAt));
26319
+ const byZone = {};
26320
+ const byKind = {};
26321
+ for (const record of records) {
26322
+ byZone[record.zone] = (byZone[record.zone] ?? 0) + 1;
26323
+ byKind[record.kind] = (byKind[record.kind] ?? 0) + 1;
26324
+ }
26325
+ return {
26326
+ enabled: options.enabled,
26327
+ promotionEnabled: options.promotionEnabled,
26328
+ rootDir,
26329
+ zonesDir,
26330
+ records: {
26331
+ total: files.length,
26332
+ valid: records.length,
26333
+ invalid: invalidRecords.length,
26334
+ byZone,
26335
+ byKind,
26336
+ latestRecordId: records[0]?.recordId,
26337
+ latestRecordedAt: records[0]?.recordedAt,
26338
+ latestZone: records[0]?.zone
26339
+ },
26340
+ latestRecord: records[0],
26341
+ invalidRecords
26342
+ };
26343
+ }
26344
+
26237
26345
  // src/cli.ts
26238
26346
  function rankCandidateForKeep(a, b) {
26239
26347
  const aConfidence = typeof a.frontmatter.confidence === "number" ? a.frontmatter.confidence : 0;
@@ -26430,6 +26538,14 @@ async function runCausalTrajectoryStatusCliCommand(options) {
26430
26538
  enabled: options.causalTrajectoryMemoryEnabled
26431
26539
  });
26432
26540
  }
26541
+ async function runTrustZoneStatusCliCommand(options) {
26542
+ return getTrustZoneStoreStatus({
26543
+ memoryDir: options.memoryDir,
26544
+ trustZoneStoreDir: options.trustZoneStoreDir,
26545
+ enabled: options.trustZonesEnabled,
26546
+ promotionEnabled: options.quarantinePromotionEnabled
26547
+ });
26548
+ }
26433
26549
  async function runSessionCheckCliCommand(options) {
26434
26550
  return analyzeSessionIntegrity({ memoryDir: options.memoryDir });
26435
26551
  }
@@ -26657,7 +26773,7 @@ function policyVersionForValues(values, config) {
26657
26773
  return createHash10("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 12);
26658
26774
  }
26659
26775
  async function readRuntimePolicySnapshot2(config, fileName) {
26660
- const filePath = path53.join(config.memoryDir, "state", fileName);
26776
+ const filePath = path54.join(config.memoryDir, "state", fileName);
26661
26777
  const snapshot = await readRuntimePolicySnapshot(filePath, {
26662
26778
  maxStaleDecayThreshold: config.lifecycleArchiveDecayThreshold
26663
26779
  });
@@ -27241,14 +27357,14 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
27241
27357
  const ns = (namespace ?? "").trim();
27242
27358
  if (!ns) return orchestrator.config.memoryDir;
27243
27359
  if (!orchestrator.config.namespacesEnabled) return orchestrator.config.memoryDir;
27244
- const candidate = path53.join(orchestrator.config.memoryDir, "namespaces", ns);
27360
+ const candidate = path54.join(orchestrator.config.memoryDir, "namespaces", ns);
27245
27361
  if (ns === orchestrator.config.defaultNamespace) {
27246
27362
  return await exists2(candidate) ? candidate : orchestrator.config.memoryDir;
27247
27363
  }
27248
27364
  return candidate;
27249
27365
  }
27250
27366
  async function readAllMemoryFiles(memoryDir) {
27251
- const roots = [path53.join(memoryDir, "facts"), path53.join(memoryDir, "corrections")];
27367
+ const roots = [path54.join(memoryDir, "facts"), path54.join(memoryDir, "corrections")];
27252
27368
  const out = [];
27253
27369
  const walk = async (dir) => {
27254
27370
  let entries;
@@ -27259,7 +27375,7 @@ async function readAllMemoryFiles(memoryDir) {
27259
27375
  }
27260
27376
  for (const entry of entries) {
27261
27377
  const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
27262
- const fullPath = path53.join(dir, entryName);
27378
+ const fullPath = path54.join(dir, entryName);
27263
27379
  if (entry.isDirectory()) {
27264
27380
  await walk(fullPath);
27265
27381
  continue;
@@ -27588,6 +27704,16 @@ function registerCli(api, orchestrator) {
27588
27704
  console.log(JSON.stringify(status, null, 2));
27589
27705
  console.log("OK");
27590
27706
  });
27707
+ cmd.command("trust-zone-status").description("Show trust-zone store status, zoned record counts, and latest stored record").action(async () => {
27708
+ const status = await runTrustZoneStatusCliCommand({
27709
+ memoryDir: orchestrator.config.memoryDir,
27710
+ trustZoneStoreDir: orchestrator.config.trustZoneStoreDir,
27711
+ trustZonesEnabled: orchestrator.config.trustZonesEnabled,
27712
+ quarantinePromotionEnabled: orchestrator.config.quarantinePromotionEnabled
27713
+ });
27714
+ console.log(JSON.stringify(status, null, 2));
27715
+ console.log("OK");
27716
+ });
27591
27717
  cmd.command("conversation-index-health").description("Show conversation index backend health and index stats").action(async () => {
27592
27718
  const health = await runConversationIndexHealthCliCommand(orchestrator);
27593
27719
  console.log(JSON.stringify(health, null, 2));
@@ -28237,7 +28363,7 @@ function registerCli(api, orchestrator) {
28237
28363
  }
28238
28364
  });
28239
28365
  cmd.command("identity").description("Show agent identity reflections").action(async () => {
28240
- const workspaceDir = path53.join(process.env.HOME ?? "~", ".openclaw", "workspace");
28366
+ const workspaceDir = path54.join(process.env.HOME ?? "~", ".openclaw", "workspace");
28241
28367
  const identity = await orchestrator.storage.readIdentity(workspaceDir);
28242
28368
  if (!identity) {
28243
28369
  console.log("No identity file found.");
@@ -28460,8 +28586,8 @@ function registerCli(api, orchestrator) {
28460
28586
  const options = args[0] ?? {};
28461
28587
  const threadId = options.thread;
28462
28588
  const top = parseInt(options.top ?? "10", 10);
28463
- const memoryDir = path53.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
28464
- const threading = new ThreadingManager(path53.join(memoryDir, "threads"));
28589
+ const memoryDir = path54.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
28590
+ const threading = new ThreadingManager(path54.join(memoryDir, "threads"));
28465
28591
  if (threadId) {
28466
28592
  const thread = await threading.loadThread(threadId);
28467
28593
  if (!thread) {
@@ -28937,9 +29063,9 @@ async function recordObjectiveStateSnapshotsFromAgentMessages(options) {
28937
29063
  }
28938
29064
 
28939
29065
  // src/index.ts
28940
- import { readFile as readFile38, writeFile as writeFile32 } from "fs/promises";
29066
+ import { readFile as readFile38, writeFile as writeFile33 } from "fs/promises";
28941
29067
  import { readFileSync as readFileSync4 } from "fs";
28942
- import path54 from "path";
29068
+ import path55 from "path";
28943
29069
  import os6 from "os";
28944
29070
  var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
28945
29071
  var ENGRAM_HOOK_APIS = "__openclawEngramHookApis";
@@ -28947,7 +29073,7 @@ function loadPluginConfigFromFile() {
28947
29073
  try {
28948
29074
  const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
28949
29075
  const homeDir = process.env.HOME ?? os6.homedir();
28950
- const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path54.join(homeDir, ".openclaw", "openclaw.json");
29076
+ const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path55.join(homeDir, ".openclaw", "openclaw.json");
28951
29077
  const content = readFileSync4(configPath, "utf-8");
28952
29078
  const config = JSON.parse(content);
28953
29079
  const pluginEntry = config?.plugins?.entries?.["openclaw-engram"];
@@ -29197,11 +29323,11 @@ Use this context naturally when relevant. Never quote or expose this memory cont
29197
29323
  `session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
29198
29324
  );
29199
29325
  const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
29200
- const signalPath = path54.join(
29326
+ const signalPath = path55.join(
29201
29327
  workspaceDir,
29202
29328
  `.compaction-reset-signal-${safeSessionKey}`
29203
29329
  );
29204
- await writeFile32(
29330
+ await writeFile33(
29205
29331
  signalPath,
29206
29332
  JSON.stringify({
29207
29333
  sessionKey,
@@ -29228,7 +29354,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
29228
29354
  );
29229
29355
  async function ensureHourlySummaryCron(api2) {
29230
29356
  const jobId = "engram-hourly-summary";
29231
- const cronFilePath = path54.join(os6.homedir(), ".openclaw", "cron", "jobs.json");
29357
+ const cronFilePath = path55.join(os6.homedir(), ".openclaw", "cron", "jobs.json");
29232
29358
  try {
29233
29359
  let jobsData = { version: 1, jobs: [] };
29234
29360
  try {
@@ -29269,7 +29395,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
29269
29395
  state: {}
29270
29396
  };
29271
29397
  jobsData.jobs.push(newJob);
29272
- await writeFile32(cronFilePath, JSON.stringify(jobsData, null, 2), "utf-8");
29398
+ await writeFile33(cronFilePath, JSON.stringify(jobsData, null, 2), "utf-8");
29273
29399
  log.info("auto-registered hourly summary cron job");
29274
29400
  } catch (err) {
29275
29401
  log.error("failed to auto-register hourly summary cron job:", err);