@joshuaswarren/openclaw-engram 9.0.40 → 9.0.42
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 +7 -0
- package/dist/index.js +339 -17
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +30 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,6 +42,8 @@ AI agents forget everything between conversations. Engram fixes that.
|
|
|
42
42
|
- **Harmonic retrieval diagnostics** — Engram can now, when `harmonicRetrievalEnabled` is enabled, blend abstraction-node evidence with cue-anchor matches into a dedicated `Harmonic Retrieval` recall section and inspect those blended results with `openclaw engram harmonic-search`.
|
|
43
43
|
- **Verified episodic recall** — Engram can now, when `verifiedRecallEnabled` is enabled, inject a dedicated `Verified Episodes` recall section that reuses memory boxes but only surfaces boxes whose cited source memories still verify as non-archived episodes.
|
|
44
44
|
- **Semantic rule promotion** — Engram can now, when `semanticRulePromotionEnabled` is enabled, promote explicit `IF ... THEN ...` rules from verified episodic memories into durable `rule` memories with lineage, source-memory provenance, duplicate suppression, and the operator-facing `openclaw engram semantic-rule-promote` CLI.
|
|
45
|
+
- **Verified rule recall** — Engram can now, when `semanticRuleVerificationEnabled` is enabled, inject a dedicated `Verified Rules` recall section that re-checks promoted rule memories against their cited source episodes at recall time and downgrades stale provenance before the rule can surface.
|
|
46
|
+
- **Creation-memory ledger** — Engram can now, when `creationMemoryEnabled` is enabled, persist a typed work-product ledger for explicit outputs agents create or update, inspect it with `openclaw engram work-product-status`, and write deterministic entries through `openclaw engram work-product-record`.
|
|
45
47
|
- **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.
|
|
46
48
|
|
|
47
49
|
## Quick Start
|
|
@@ -171,6 +173,8 @@ openclaw engram trust-zone-status # Trust-zone record counts and lates
|
|
|
171
173
|
openclaw engram trust-zone-promote # Dry-run or apply a trust-zone promotion with provenance/corroboration enforcement
|
|
172
174
|
openclaw engram harmonic-search <query> # Preview blended harmonic retrieval matches
|
|
173
175
|
openclaw engram verified-recall-search <query> # Preview verified episodic recall matches
|
|
176
|
+
openclaw engram work-product-status # Work-product ledger counts and latest recorded output
|
|
177
|
+
openclaw engram work-product-record # Record a typed work-product ledger entry
|
|
174
178
|
openclaw engram conversation-index-health # Conversation index status
|
|
175
179
|
openclaw engram graph-health # Entity graph status
|
|
176
180
|
openclaw engram tier-status # Hot/cold tier metrics
|
|
@@ -211,6 +215,9 @@ Key settings:
|
|
|
211
215
|
| `abstractionAnchorsEnabled` | `false` | Enable typed cue-anchor indexing for abstraction nodes and expose the anchor store through status tooling |
|
|
212
216
|
| `verifiedRecallEnabled` | `false` | Inject prompt-relevant memory boxes only when their cited source memories verify as non-archived episodes |
|
|
213
217
|
| `semanticRulePromotionEnabled` | `false` | Enable deterministic promotion of explicit `IF ... THEN ...` rules from verified episodic memories via `openclaw engram semantic-rule-promote` |
|
|
218
|
+
| `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` |
|
|
219
|
+
| `creationMemoryEnabled` | `false` | Enable the creation-memory foundation, including the work-product ledger and operator-facing write/status commands |
|
|
220
|
+
| `workProductLedgerDir` | `{memoryDir}/state/work-product-ledger` | Root directory for typed work-product ledger entries |
|
|
214
221
|
|
|
215
222
|
Full reference: [Config Reference](docs/config-reference.md)
|
|
216
223
|
|
package/dist/index.js
CHANGED
|
@@ -306,6 +306,9 @@ function parseConfig(raw) {
|
|
|
306
306
|
abstractionAnchorsEnabled: cfg.abstractionAnchorsEnabled === true,
|
|
307
307
|
verifiedRecallEnabled: cfg.verifiedRecallEnabled === true,
|
|
308
308
|
semanticRulePromotionEnabled: cfg.semanticRulePromotionEnabled === true,
|
|
309
|
+
semanticRuleVerificationEnabled: cfg.semanticRuleVerificationEnabled === true,
|
|
310
|
+
creationMemoryEnabled: cfg.creationMemoryEnabled === true,
|
|
311
|
+
workProductLedgerDir: typeof cfg.workProductLedgerDir === "string" && cfg.workProductLedgerDir.trim().length > 0 ? cfg.workProductLedgerDir.trim() : path.join(memoryDir, "state", "work-product-ledger"),
|
|
309
312
|
abstractionNodeStoreDir: typeof cfg.abstractionNodeStoreDir === "string" && cfg.abstractionNodeStoreDir.trim().length > 0 ? cfg.abstractionNodeStoreDir.trim() : path.join(memoryDir, "state", "abstraction-nodes"),
|
|
310
313
|
// Local LLM Provider (v2.1)
|
|
311
314
|
localLlmEnabled: cfg.localLlmEnabled === true || cfg.localLlmEnabled === "true",
|
|
@@ -595,6 +598,12 @@ function buildDefaultRecallPipeline(cfg) {
|
|
|
595
598
|
maxResults: 3,
|
|
596
599
|
maxChars: 1800
|
|
597
600
|
},
|
|
601
|
+
{
|
|
602
|
+
id: "verified-rules",
|
|
603
|
+
enabled: cfg.semanticRuleVerificationEnabled === true,
|
|
604
|
+
maxResults: 3,
|
|
605
|
+
maxChars: 1800
|
|
606
|
+
},
|
|
598
607
|
{
|
|
599
608
|
id: "memories",
|
|
600
609
|
enabled: true,
|
|
@@ -15430,6 +15439,100 @@ async function searchVerifiedEpisodes(options) {
|
|
|
15430
15439
|
).slice(0, options.maxResults);
|
|
15431
15440
|
}
|
|
15432
15441
|
|
|
15442
|
+
// src/semantic-rule-verifier.ts
|
|
15443
|
+
var DEFAULT_MIN_EFFECTIVE_CONFIDENCE = 0.45;
|
|
15444
|
+
function verificationConfidenceMultiplier(status) {
|
|
15445
|
+
switch (status) {
|
|
15446
|
+
case "verified":
|
|
15447
|
+
return 1;
|
|
15448
|
+
case "source-memory-not-episode":
|
|
15449
|
+
return 0.45;
|
|
15450
|
+
case "source-memory-archived":
|
|
15451
|
+
return 0.4;
|
|
15452
|
+
case "source-memory-missing":
|
|
15453
|
+
return 0.35;
|
|
15454
|
+
default:
|
|
15455
|
+
return 0.35;
|
|
15456
|
+
}
|
|
15457
|
+
}
|
|
15458
|
+
function resolveVerificationStatus(sourceMemory) {
|
|
15459
|
+
if (!sourceMemory) return "source-memory-missing";
|
|
15460
|
+
if (sourceMemory.frontmatter.status === "archived") return "source-memory-archived";
|
|
15461
|
+
if (sourceMemory.frontmatter.memoryKind !== "episode") return "source-memory-not-episode";
|
|
15462
|
+
return "verified";
|
|
15463
|
+
}
|
|
15464
|
+
function resolveEffectiveConfidence(rule, sourceMemory) {
|
|
15465
|
+
const status = resolveVerificationStatus(sourceMemory);
|
|
15466
|
+
const ruleConfidence = Number.isFinite(rule.frontmatter.confidence) ? rule.frontmatter.confidence : 0.8;
|
|
15467
|
+
const sourceConfidence = Number.isFinite(sourceMemory?.frontmatter.confidence) ? sourceMemory.frontmatter.confidence : ruleConfidence;
|
|
15468
|
+
const anchoredConfidence = Math.min(ruleConfidence, sourceConfidence);
|
|
15469
|
+
const effectiveConfidence = Math.max(
|
|
15470
|
+
0,
|
|
15471
|
+
Math.min(1, anchoredConfidence * verificationConfidenceMultiplier(status))
|
|
15472
|
+
);
|
|
15473
|
+
return { status, effectiveConfidence };
|
|
15474
|
+
}
|
|
15475
|
+
function scoreVerifiedSemanticRuleCandidate(rule, sourceMemory, queryTokens, effectiveConfidence) {
|
|
15476
|
+
const matchedFields = /* @__PURE__ */ new Set();
|
|
15477
|
+
let score = 0;
|
|
15478
|
+
const ruleContentMatches = countRecallTokenOverlap(queryTokens, rule.content);
|
|
15479
|
+
if (ruleContentMatches > 0) {
|
|
15480
|
+
score += ruleContentMatches * 5;
|
|
15481
|
+
matchedFields.add("ruleContent");
|
|
15482
|
+
}
|
|
15483
|
+
const tagMatches = countRecallTokenOverlap(queryTokens, rule.frontmatter.tags?.join(" "));
|
|
15484
|
+
if (tagMatches > 0) {
|
|
15485
|
+
score += tagMatches * 2;
|
|
15486
|
+
matchedFields.add("tags");
|
|
15487
|
+
}
|
|
15488
|
+
const sourceContentMatches = countRecallTokenOverlap(queryTokens, sourceMemory?.content);
|
|
15489
|
+
if (sourceContentMatches > 0) {
|
|
15490
|
+
score += sourceContentMatches * 2;
|
|
15491
|
+
matchedFields.add("sourceContent");
|
|
15492
|
+
}
|
|
15493
|
+
if (score > 0) {
|
|
15494
|
+
score += effectiveConfidence;
|
|
15495
|
+
}
|
|
15496
|
+
return { score, matchedFields };
|
|
15497
|
+
}
|
|
15498
|
+
async function searchVerifiedSemanticRules(options) {
|
|
15499
|
+
const queryTokens = new Set(normalizeRecallTokens(options.query, ["what", "which"]));
|
|
15500
|
+
if (queryTokens.size === 0 || options.maxResults <= 0) return [];
|
|
15501
|
+
const storage = new StorageManager(options.memoryDir);
|
|
15502
|
+
const allMemories = await storage.readAllMemories();
|
|
15503
|
+
const memoryById = new Map(allMemories.map((memory) => [memory.frontmatter.id, memory]));
|
|
15504
|
+
const minEffectiveConfidence = options.minEffectiveConfidence ?? DEFAULT_MIN_EFFECTIVE_CONFIDENCE;
|
|
15505
|
+
const candidates = [];
|
|
15506
|
+
for (const memory of allMemories) {
|
|
15507
|
+
if (memory.frontmatter.category !== "rule") continue;
|
|
15508
|
+
if (memory.frontmatter.status === "archived") continue;
|
|
15509
|
+
if (memory.frontmatter.source !== "semantic-rule-promotion") continue;
|
|
15510
|
+
const sourceMemoryId = memory.frontmatter.sourceMemoryId;
|
|
15511
|
+
if (!sourceMemoryId) continue;
|
|
15512
|
+
const sourceMemory = memoryById.get(sourceMemoryId);
|
|
15513
|
+
const { status, effectiveConfidence } = resolveEffectiveConfidence(memory, sourceMemory);
|
|
15514
|
+
if (effectiveConfidence < minEffectiveConfidence) continue;
|
|
15515
|
+
const { score, matchedFields } = scoreVerifiedSemanticRuleCandidate(
|
|
15516
|
+
memory,
|
|
15517
|
+
sourceMemory,
|
|
15518
|
+
queryTokens,
|
|
15519
|
+
effectiveConfidence
|
|
15520
|
+
);
|
|
15521
|
+
if (score <= 0) continue;
|
|
15522
|
+
candidates.push({
|
|
15523
|
+
rule: memory,
|
|
15524
|
+
score,
|
|
15525
|
+
sourceMemoryId,
|
|
15526
|
+
verificationStatus: status,
|
|
15527
|
+
effectiveConfidence,
|
|
15528
|
+
matchedFields: [...matchedFields].sort()
|
|
15529
|
+
});
|
|
15530
|
+
}
|
|
15531
|
+
return candidates.sort(
|
|
15532
|
+
(left, right) => right.score - left.score || right.effectiveConfidence - left.effectiveConfidence || right.rule.frontmatter.updated.localeCompare(left.rule.frontmatter.updated)
|
|
15533
|
+
).slice(0, options.maxResults);
|
|
15534
|
+
}
|
|
15535
|
+
|
|
15433
15536
|
// src/replay/types.ts
|
|
15434
15537
|
var VALID_SOURCES = /* @__PURE__ */ new Set(["openclaw", "claude", "chatgpt"]);
|
|
15435
15538
|
var VALID_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
@@ -19234,6 +19337,25 @@ ${r.snippet.trim()}
|
|
|
19234
19337
|
timings.verifiedRecall = `${Date.now() - t0}ms`;
|
|
19235
19338
|
return results.length > 0 ? this.formatVerifiedEpisodeResults(results) : null;
|
|
19236
19339
|
})();
|
|
19340
|
+
const verifiedRulesPromise = (async () => {
|
|
19341
|
+
const t0 = Date.now();
|
|
19342
|
+
if (!this.config.semanticRuleVerificationEnabled || !this.isRecallSectionEnabled("verified-rules", this.config.semanticRuleVerificationEnabled === true)) {
|
|
19343
|
+
timings.verifiedRules = "skip";
|
|
19344
|
+
return null;
|
|
19345
|
+
}
|
|
19346
|
+
const maxResults = this.getRecallSectionNumber("verified-rules", "maxResults") ?? 3;
|
|
19347
|
+
if (maxResults <= 0) {
|
|
19348
|
+
timings.verifiedRules = "skip(limit=0)";
|
|
19349
|
+
return null;
|
|
19350
|
+
}
|
|
19351
|
+
const results = await searchVerifiedSemanticRules({
|
|
19352
|
+
memoryDir: this.config.memoryDir,
|
|
19353
|
+
query: retrievalQuery,
|
|
19354
|
+
maxResults
|
|
19355
|
+
});
|
|
19356
|
+
timings.verifiedRules = `${Date.now() - t0}ms`;
|
|
19357
|
+
return results.length > 0 ? this.formatVerifiedSemanticRuleResults(results) : null;
|
|
19358
|
+
})();
|
|
19237
19359
|
const qmdPromise = (async () => {
|
|
19238
19360
|
if (recallResultLimit <= 0) {
|
|
19239
19361
|
timings.qmd = "skip(limit=0)";
|
|
@@ -19442,6 +19564,7 @@ ${formatted}`;
|
|
|
19442
19564
|
trustZoneSection,
|
|
19443
19565
|
harmonicRetrievalSection,
|
|
19444
19566
|
verifiedRecallSection,
|
|
19567
|
+
verifiedRulesSection,
|
|
19445
19568
|
qmdResult,
|
|
19446
19569
|
transcriptSection,
|
|
19447
19570
|
compactionSection,
|
|
@@ -19459,6 +19582,7 @@ ${formatted}`;
|
|
|
19459
19582
|
trustZonePromise,
|
|
19460
19583
|
harmonicRetrievalPromise,
|
|
19461
19584
|
verifiedRecallPromise,
|
|
19585
|
+
verifiedRulesPromise,
|
|
19462
19586
|
qmdPromise,
|
|
19463
19587
|
transcriptPromise,
|
|
19464
19588
|
compactionPromise,
|
|
@@ -19528,6 +19652,9 @@ ${tmtNode.summary}`);
|
|
|
19528
19652
|
if (verifiedRecallSection) {
|
|
19529
19653
|
this.appendRecallSection(sectionBuckets, "verified-episodes", verifiedRecallSection);
|
|
19530
19654
|
}
|
|
19655
|
+
if (verifiedRulesSection) {
|
|
19656
|
+
this.appendRecallSection(sectionBuckets, "verified-rules", verifiedRulesSection);
|
|
19657
|
+
}
|
|
19531
19658
|
if (qmdResult) {
|
|
19532
19659
|
const t0 = Date.now();
|
|
19533
19660
|
const { memoryResultsLists, globalResults } = qmdResult;
|
|
@@ -21751,6 +21878,27 @@ ${details.join("\n")}`;
|
|
|
21751
21878
|
});
|
|
21752
21879
|
return `## Verified Episodes
|
|
21753
21880
|
|
|
21881
|
+
${lines.join("\n\n")}`;
|
|
21882
|
+
}
|
|
21883
|
+
formatVerifiedSemanticRuleResults(results) {
|
|
21884
|
+
const lines = results.map(({ rule, sourceMemoryId, verificationStatus, effectiveConfidence, matchedFields }, index) => {
|
|
21885
|
+
const header = [
|
|
21886
|
+
`[${index + 1}] ${rule.frontmatter.updated.replace("T", " ").slice(0, 16)}`,
|
|
21887
|
+
verificationStatus,
|
|
21888
|
+
`confidence:${effectiveConfidence.toFixed(2)}`
|
|
21889
|
+
].join(" | ");
|
|
21890
|
+
const details = [
|
|
21891
|
+
rule.content,
|
|
21892
|
+
`source memory: ${sourceMemoryId}`
|
|
21893
|
+
];
|
|
21894
|
+
if (matchedFields.length > 0) {
|
|
21895
|
+
details.push(`matched: ${matchedFields.join(", ")}`);
|
|
21896
|
+
}
|
|
21897
|
+
return `${header}
|
|
21898
|
+
${details.join("\n")}`;
|
|
21899
|
+
});
|
|
21900
|
+
return `## Verified Rules
|
|
21901
|
+
|
|
21754
21902
|
${lines.join("\n\n")}`;
|
|
21755
21903
|
}
|
|
21756
21904
|
summarizeIdentityText(raw, maxLines, maxChars) {
|
|
@@ -24732,7 +24880,7 @@ promotionCandidates: ${res.promotionCandidateCount}`
|
|
|
24732
24880
|
}
|
|
24733
24881
|
|
|
24734
24882
|
// src/cli.ts
|
|
24735
|
-
import
|
|
24883
|
+
import path58 from "path";
|
|
24736
24884
|
import { access as access3, readFile as readFile37, readdir as readdir24, unlink as unlink7 } from "fs/promises";
|
|
24737
24885
|
import { createHash as createHash10 } from "crypto";
|
|
24738
24886
|
|
|
@@ -25613,8 +25761,8 @@ function gatherCandidates(input, warnings) {
|
|
|
25613
25761
|
const record = rec;
|
|
25614
25762
|
const content = typeof record.content === "string" ? record.content : null;
|
|
25615
25763
|
if (!content) continue;
|
|
25616
|
-
const
|
|
25617
|
-
if (!
|
|
25764
|
+
const path60 = typeof record.path === "string" ? record.path : "";
|
|
25765
|
+
if (!path60.startsWith("transcripts/") && !path60.includes("/transcripts/")) continue;
|
|
25618
25766
|
rows.push(...parseJsonl(content, warnings));
|
|
25619
25767
|
}
|
|
25620
25768
|
return rows;
|
|
@@ -27248,6 +27396,104 @@ async function runCompatChecks(options) {
|
|
|
27248
27396
|
};
|
|
27249
27397
|
}
|
|
27250
27398
|
|
|
27399
|
+
// src/work-product-ledger.ts
|
|
27400
|
+
import path57 from "path";
|
|
27401
|
+
import { mkdir as mkdir38, writeFile as writeFile35 } from "fs/promises";
|
|
27402
|
+
function resolveWorkProductLedgerDir(memoryDir, overrideDir) {
|
|
27403
|
+
if (typeof overrideDir === "string" && overrideDir.trim().length > 0) {
|
|
27404
|
+
return overrideDir.trim();
|
|
27405
|
+
}
|
|
27406
|
+
return path57.join(memoryDir, "state", "work-product-ledger");
|
|
27407
|
+
}
|
|
27408
|
+
function validateWorkProductLedgerEntry(raw) {
|
|
27409
|
+
if (!isRecord2(raw)) throw new Error("work-product ledger entry must be an object");
|
|
27410
|
+
if (raw.schemaVersion !== 1) throw new Error("schemaVersion must be 1");
|
|
27411
|
+
const source = assertString2(raw.source, "source");
|
|
27412
|
+
if (!["tool_result", "cli", "system", "manual"].includes(source)) {
|
|
27413
|
+
throw new Error("source must be one of tool_result|cli|system|manual");
|
|
27414
|
+
}
|
|
27415
|
+
const kind = assertString2(raw.kind, "kind");
|
|
27416
|
+
if (!["artifact", "file", "record", "report", "workspace"].includes(kind)) {
|
|
27417
|
+
throw new Error("kind must be one of artifact|file|record|report|workspace");
|
|
27418
|
+
}
|
|
27419
|
+
const action = assertString2(raw.action, "action");
|
|
27420
|
+
if (!["created", "updated", "deleted", "referenced", "published"].includes(action)) {
|
|
27421
|
+
throw new Error("action must be one of created|updated|deleted|referenced|published");
|
|
27422
|
+
}
|
|
27423
|
+
return {
|
|
27424
|
+
schemaVersion: 1,
|
|
27425
|
+
entryId: assertSafePathSegment(assertString2(raw.entryId, "entryId"), "entryId"),
|
|
27426
|
+
recordedAt: assertIsoRecordedAt(assertString2(raw.recordedAt, "recordedAt")),
|
|
27427
|
+
sessionKey: assertString2(raw.sessionKey, "sessionKey"),
|
|
27428
|
+
source,
|
|
27429
|
+
kind,
|
|
27430
|
+
action,
|
|
27431
|
+
scope: assertString2(raw.scope, "scope"),
|
|
27432
|
+
summary: assertString2(raw.summary, "summary"),
|
|
27433
|
+
artifactPath: optionalString(raw.artifactPath),
|
|
27434
|
+
objectiveStateSnapshotRefs: optionalStringArray2(raw.objectiveStateSnapshotRefs, "objectiveStateSnapshotRefs"),
|
|
27435
|
+
entityRefs: optionalStringArray2(raw.entityRefs, "entityRefs"),
|
|
27436
|
+
tags: optionalStringArray2(raw.tags, "tags"),
|
|
27437
|
+
metadata: validateStringRecord(raw.metadata, "metadata")
|
|
27438
|
+
};
|
|
27439
|
+
}
|
|
27440
|
+
async function recordWorkProductLedgerEntry(options) {
|
|
27441
|
+
const rootDir = resolveWorkProductLedgerDir(options.memoryDir, options.workProductLedgerDir);
|
|
27442
|
+
const validated = validateWorkProductLedgerEntry(options.entry);
|
|
27443
|
+
const day = recordStoreDay(validated.recordedAt);
|
|
27444
|
+
const entriesDir = path57.join(rootDir, "entries", day);
|
|
27445
|
+
const filePath = path57.join(entriesDir, `${validated.entryId}.json`);
|
|
27446
|
+
await mkdir38(entriesDir, { recursive: true });
|
|
27447
|
+
await writeFile35(filePath, JSON.stringify(validated, null, 2), "utf8");
|
|
27448
|
+
return filePath;
|
|
27449
|
+
}
|
|
27450
|
+
async function readWorkProductLedgerEntries(options) {
|
|
27451
|
+
const rootDir = resolveWorkProductLedgerDir(options.memoryDir, options.workProductLedgerDir);
|
|
27452
|
+
const files = await listJsonFiles(path57.join(rootDir, "entries"));
|
|
27453
|
+
const entries = [];
|
|
27454
|
+
const invalidEntries = [];
|
|
27455
|
+
for (const filePath of files) {
|
|
27456
|
+
try {
|
|
27457
|
+
entries.push(validateWorkProductLedgerEntry(await readJsonFile(filePath)));
|
|
27458
|
+
} catch (error) {
|
|
27459
|
+
invalidEntries.push({
|
|
27460
|
+
path: filePath,
|
|
27461
|
+
error: error instanceof Error ? error.message : String(error)
|
|
27462
|
+
});
|
|
27463
|
+
}
|
|
27464
|
+
}
|
|
27465
|
+
return { files, entries, invalidEntries };
|
|
27466
|
+
}
|
|
27467
|
+
async function getWorkProductLedgerStatus(options) {
|
|
27468
|
+
const rootDir = resolveWorkProductLedgerDir(options.memoryDir, options.workProductLedgerDir);
|
|
27469
|
+
const entriesDir = path57.join(rootDir, "entries");
|
|
27470
|
+
const { files, entries, invalidEntries } = await readWorkProductLedgerEntries(options);
|
|
27471
|
+
entries.sort((a, b) => b.recordedAt.localeCompare(a.recordedAt));
|
|
27472
|
+
const byKind = {};
|
|
27473
|
+
const byAction = {};
|
|
27474
|
+
for (const entry of entries) {
|
|
27475
|
+
byKind[entry.kind] = (byKind[entry.kind] ?? 0) + 1;
|
|
27476
|
+
byAction[entry.action] = (byAction[entry.action] ?? 0) + 1;
|
|
27477
|
+
}
|
|
27478
|
+
return {
|
|
27479
|
+
enabled: options.enabled,
|
|
27480
|
+
rootDir,
|
|
27481
|
+
entriesDir,
|
|
27482
|
+
entries: {
|
|
27483
|
+
total: files.length,
|
|
27484
|
+
valid: entries.length,
|
|
27485
|
+
invalid: invalidEntries.length,
|
|
27486
|
+
byKind,
|
|
27487
|
+
byAction,
|
|
27488
|
+
latestEntryId: entries[0]?.entryId,
|
|
27489
|
+
latestRecordedAt: entries[0]?.recordedAt,
|
|
27490
|
+
latestSessionKey: entries[0]?.sessionKey
|
|
27491
|
+
},
|
|
27492
|
+
latestEntry: entries[0],
|
|
27493
|
+
invalidEntries
|
|
27494
|
+
};
|
|
27495
|
+
}
|
|
27496
|
+
|
|
27251
27497
|
// src/semantic-rule-promotion.ts
|
|
27252
27498
|
function normalizeRuleWhitespace(value) {
|
|
27253
27499
|
return value.replace(/\s+/g, " ").trim();
|
|
@@ -27621,6 +27867,29 @@ async function runSemanticRulePromoteCliCommand(options) {
|
|
|
27621
27867
|
dryRun: options.dryRun
|
|
27622
27868
|
});
|
|
27623
27869
|
}
|
|
27870
|
+
async function runSemanticRuleVerifyCliCommand(options) {
|
|
27871
|
+
if (!options.semanticRuleVerificationEnabled) return [];
|
|
27872
|
+
return searchVerifiedSemanticRules({
|
|
27873
|
+
memoryDir: options.memoryDir,
|
|
27874
|
+
query: options.query,
|
|
27875
|
+
maxResults: Math.max(1, Math.floor(options.maxResults ?? 3))
|
|
27876
|
+
});
|
|
27877
|
+
}
|
|
27878
|
+
async function runWorkProductStatusCliCommand(options) {
|
|
27879
|
+
return getWorkProductLedgerStatus({
|
|
27880
|
+
memoryDir: options.memoryDir,
|
|
27881
|
+
workProductLedgerDir: options.workProductLedgerDir,
|
|
27882
|
+
enabled: options.creationMemoryEnabled
|
|
27883
|
+
});
|
|
27884
|
+
}
|
|
27885
|
+
async function runWorkProductRecordCliCommand(options) {
|
|
27886
|
+
if (!options.creationMemoryEnabled) return null;
|
|
27887
|
+
return recordWorkProductLedgerEntry({
|
|
27888
|
+
memoryDir: options.memoryDir,
|
|
27889
|
+
workProductLedgerDir: options.workProductLedgerDir,
|
|
27890
|
+
entry: options.entry
|
|
27891
|
+
});
|
|
27892
|
+
}
|
|
27624
27893
|
async function runTrustZonePromoteCliCommand(options) {
|
|
27625
27894
|
const result = await promoteTrustZoneRecord({
|
|
27626
27895
|
memoryDir: options.memoryDir,
|
|
@@ -27867,7 +28136,7 @@ function policyVersionForValues(values, config) {
|
|
|
27867
28136
|
return createHash10("sha256").update(JSON.stringify(normalized)).digest("hex").slice(0, 12);
|
|
27868
28137
|
}
|
|
27869
28138
|
async function readRuntimePolicySnapshot2(config, fileName) {
|
|
27870
|
-
const filePath =
|
|
28139
|
+
const filePath = path58.join(config.memoryDir, "state", fileName);
|
|
27871
28140
|
const snapshot = await readRuntimePolicySnapshot(filePath, {
|
|
27872
28141
|
maxStaleDecayThreshold: config.lifecycleArchiveDecayThreshold
|
|
27873
28142
|
});
|
|
@@ -28451,14 +28720,14 @@ async function resolveMemoryDirForNamespace(orchestrator, namespace) {
|
|
|
28451
28720
|
const ns = (namespace ?? "").trim();
|
|
28452
28721
|
if (!ns) return orchestrator.config.memoryDir;
|
|
28453
28722
|
if (!orchestrator.config.namespacesEnabled) return orchestrator.config.memoryDir;
|
|
28454
|
-
const candidate =
|
|
28723
|
+
const candidate = path58.join(orchestrator.config.memoryDir, "namespaces", ns);
|
|
28455
28724
|
if (ns === orchestrator.config.defaultNamespace) {
|
|
28456
28725
|
return await exists2(candidate) ? candidate : orchestrator.config.memoryDir;
|
|
28457
28726
|
}
|
|
28458
28727
|
return candidate;
|
|
28459
28728
|
}
|
|
28460
28729
|
async function readAllMemoryFiles(memoryDir) {
|
|
28461
|
-
const roots = [
|
|
28730
|
+
const roots = [path58.join(memoryDir, "facts"), path58.join(memoryDir, "corrections")];
|
|
28462
28731
|
const out = [];
|
|
28463
28732
|
const walk = async (dir) => {
|
|
28464
28733
|
let entries;
|
|
@@ -28469,7 +28738,7 @@ async function readAllMemoryFiles(memoryDir) {
|
|
|
28469
28738
|
}
|
|
28470
28739
|
for (const entry of entries) {
|
|
28471
28740
|
const entryName = typeof entry.name === "string" ? entry.name : entry.name.toString("utf-8");
|
|
28472
|
-
const fullPath =
|
|
28741
|
+
const fullPath = path58.join(dir, entryName);
|
|
28473
28742
|
if (entry.isDirectory()) {
|
|
28474
28743
|
await walk(fullPath);
|
|
28475
28744
|
continue;
|
|
@@ -28848,6 +29117,46 @@ function registerCli(api, orchestrator) {
|
|
|
28848
29117
|
console.log(JSON.stringify(results, null, 2));
|
|
28849
29118
|
console.log("OK");
|
|
28850
29119
|
});
|
|
29120
|
+
cmd.command("work-product-status").description("Show work-product ledger status, entry counts, and the latest recorded work product").action(async () => {
|
|
29121
|
+
const status = await runWorkProductStatusCliCommand({
|
|
29122
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
29123
|
+
workProductLedgerDir: orchestrator.config.workProductLedgerDir,
|
|
29124
|
+
creationMemoryEnabled: orchestrator.config.creationMemoryEnabled
|
|
29125
|
+
});
|
|
29126
|
+
console.log(JSON.stringify(status, null, 2));
|
|
29127
|
+
console.log("OK");
|
|
29128
|
+
});
|
|
29129
|
+
cmd.command("work-product-record").description("Record a work-product ledger entry when creation-memory is enabled").requiredOption("--entry-id <entryId>", "Ledger entry id").requiredOption("--recorded-at <recordedAt>", "ISO timestamp for the entry").requiredOption("--session-key <sessionKey>", "Session key that created the work product").requiredOption("--source <source>", "Entry source (tool_result|cli|system|manual)").requiredOption("--kind <kind>", "Entry kind (artifact|file|record|report|workspace)").requiredOption(
|
|
29130
|
+
"--entry-action <entryAction>",
|
|
29131
|
+
"Entry action (created|updated|deleted|referenced|published)"
|
|
29132
|
+
).requiredOption("--scope <scope>", "Primary scope or identifier for the created work product").requiredOption("--summary <summary>", "Human-readable summary of the work product").option("--artifact-path <artifactPath>", "Optional path to the created artifact").option("--tag <tag...>", "Tags to attach to the work-product entry").option("--entity-ref <entityRef...>", "Entity refs to attach to the work-product entry").option(
|
|
29133
|
+
"--objective-state-snapshot-ref <objectiveStateSnapshotRef...>",
|
|
29134
|
+
"Objective-state snapshot refs to link to this work product"
|
|
29135
|
+
).action(async (...args) => {
|
|
29136
|
+
const options = args[0] ?? {};
|
|
29137
|
+
const filePath = await runWorkProductRecordCliCommand({
|
|
29138
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
29139
|
+
workProductLedgerDir: orchestrator.config.workProductLedgerDir,
|
|
29140
|
+
creationMemoryEnabled: orchestrator.config.creationMemoryEnabled,
|
|
29141
|
+
entry: {
|
|
29142
|
+
schemaVersion: 1,
|
|
29143
|
+
entryId: String(options.entryId ?? ""),
|
|
29144
|
+
recordedAt: String(options.recordedAt ?? ""),
|
|
29145
|
+
sessionKey: String(options.sessionKey ?? ""),
|
|
29146
|
+
source: String(options.source ?? ""),
|
|
29147
|
+
kind: String(options.kind ?? ""),
|
|
29148
|
+
action: String(options.entryAction ?? ""),
|
|
29149
|
+
scope: String(options.scope ?? ""),
|
|
29150
|
+
summary: String(options.summary ?? ""),
|
|
29151
|
+
artifactPath: typeof options.artifactPath === "string" ? options.artifactPath : void 0,
|
|
29152
|
+
tags: Array.isArray(options.tag) ? options.tag.map(String) : void 0,
|
|
29153
|
+
entityRefs: Array.isArray(options.entityRef) ? options.entityRef.map(String) : void 0,
|
|
29154
|
+
objectiveStateSnapshotRefs: Array.isArray(options.objectiveStateSnapshotRef) ? options.objectiveStateSnapshotRef.map(String) : void 0
|
|
29155
|
+
}
|
|
29156
|
+
});
|
|
29157
|
+
console.log(JSON.stringify({ wrote: filePath !== null, filePath }, null, 2));
|
|
29158
|
+
console.log("OK");
|
|
29159
|
+
});
|
|
28851
29160
|
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) => {
|
|
28852
29161
|
const options = args[0] ?? {};
|
|
28853
29162
|
const result = await runTrustZonePromoteCliCommand({
|
|
@@ -28891,6 +29200,19 @@ function registerCli(api, orchestrator) {
|
|
|
28891
29200
|
console.log(JSON.stringify(result, null, 2));
|
|
28892
29201
|
console.log("OK");
|
|
28893
29202
|
});
|
|
29203
|
+
cmd.command("semantic-rule-verify").description("Preview verified semantic-rule recall with provenance-aware confidence downgrades").argument("<query>", "Prompt-like query to evaluate against verified semantic-rule recall").option("--max-results <count>", "Maximum number of verified semantic rules to return", "3").action(async (...args) => {
|
|
29204
|
+
const query = typeof args[0] === "string" ? args[0] : "";
|
|
29205
|
+
const options = args[1] ?? {};
|
|
29206
|
+
const maxResults = typeof options.maxResults === "string" ? Number.parseInt(options.maxResults, 10) : 3;
|
|
29207
|
+
const results = await runSemanticRuleVerifyCliCommand({
|
|
29208
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
29209
|
+
semanticRuleVerificationEnabled: orchestrator.config.semanticRuleVerificationEnabled,
|
|
29210
|
+
query,
|
|
29211
|
+
maxResults: Number.isFinite(maxResults) ? maxResults : 3
|
|
29212
|
+
});
|
|
29213
|
+
console.log(JSON.stringify(results, null, 2));
|
|
29214
|
+
console.log("OK");
|
|
29215
|
+
});
|
|
28894
29216
|
cmd.command("conversation-index-health").description("Show conversation index backend health and index stats").action(async () => {
|
|
28895
29217
|
const health = await runConversationIndexHealthCliCommand(orchestrator);
|
|
28896
29218
|
console.log(JSON.stringify(health, null, 2));
|
|
@@ -29540,7 +29862,7 @@ function registerCli(api, orchestrator) {
|
|
|
29540
29862
|
}
|
|
29541
29863
|
});
|
|
29542
29864
|
cmd.command("identity").description("Show agent identity reflections").action(async () => {
|
|
29543
|
-
const workspaceDir =
|
|
29865
|
+
const workspaceDir = path58.join(process.env.HOME ?? "~", ".openclaw", "workspace");
|
|
29544
29866
|
const identity = await orchestrator.storage.readIdentity(workspaceDir);
|
|
29545
29867
|
if (!identity) {
|
|
29546
29868
|
console.log("No identity file found.");
|
|
@@ -29763,8 +30085,8 @@ function registerCli(api, orchestrator) {
|
|
|
29763
30085
|
const options = args[0] ?? {};
|
|
29764
30086
|
const threadId = options.thread;
|
|
29765
30087
|
const top = parseInt(options.top ?? "10", 10);
|
|
29766
|
-
const memoryDir =
|
|
29767
|
-
const threading = new ThreadingManager(
|
|
30088
|
+
const memoryDir = path58.join(process.env.HOME ?? "~", ".openclaw", "workspace", "memory", "local");
|
|
30089
|
+
const threading = new ThreadingManager(path58.join(memoryDir, "threads"));
|
|
29768
30090
|
if (threadId) {
|
|
29769
30091
|
const thread = await threading.loadThread(threadId);
|
|
29770
30092
|
if (!thread) {
|
|
@@ -30240,9 +30562,9 @@ async function recordObjectiveStateSnapshotsFromAgentMessages(options) {
|
|
|
30240
30562
|
}
|
|
30241
30563
|
|
|
30242
30564
|
// src/index.ts
|
|
30243
|
-
import { readFile as readFile38, writeFile as
|
|
30565
|
+
import { readFile as readFile38, writeFile as writeFile36 } from "fs/promises";
|
|
30244
30566
|
import { readFileSync as readFileSync4 } from "fs";
|
|
30245
|
-
import
|
|
30567
|
+
import path59 from "path";
|
|
30246
30568
|
import os6 from "os";
|
|
30247
30569
|
var ENGRAM_REGISTERED_GUARD = "__openclawEngramRegistered";
|
|
30248
30570
|
var ENGRAM_HOOK_APIS = "__openclawEngramHookApis";
|
|
@@ -30250,7 +30572,7 @@ function loadPluginConfigFromFile() {
|
|
|
30250
30572
|
try {
|
|
30251
30573
|
const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
|
|
30252
30574
|
const homeDir = process.env.HOME ?? os6.homedir();
|
|
30253
|
-
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath :
|
|
30575
|
+
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path59.join(homeDir, ".openclaw", "openclaw.json");
|
|
30254
30576
|
const content = readFileSync4(configPath, "utf-8");
|
|
30255
30577
|
const config = JSON.parse(content);
|
|
30256
30578
|
const pluginEntry = config?.plugins?.entries?.["openclaw-engram"];
|
|
@@ -30500,11 +30822,11 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
30500
30822
|
`session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
|
|
30501
30823
|
);
|
|
30502
30824
|
const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
|
|
30503
|
-
const signalPath =
|
|
30825
|
+
const signalPath = path59.join(
|
|
30504
30826
|
workspaceDir,
|
|
30505
30827
|
`.compaction-reset-signal-${safeSessionKey}`
|
|
30506
30828
|
);
|
|
30507
|
-
await
|
|
30829
|
+
await writeFile36(
|
|
30508
30830
|
signalPath,
|
|
30509
30831
|
JSON.stringify({
|
|
30510
30832
|
sessionKey,
|
|
@@ -30531,7 +30853,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
30531
30853
|
);
|
|
30532
30854
|
async function ensureHourlySummaryCron(api2) {
|
|
30533
30855
|
const jobId = "engram-hourly-summary";
|
|
30534
|
-
const cronFilePath =
|
|
30856
|
+
const cronFilePath = path59.join(os6.homedir(), ".openclaw", "cron", "jobs.json");
|
|
30535
30857
|
try {
|
|
30536
30858
|
let jobsData = { version: 1, jobs: [] };
|
|
30537
30859
|
try {
|
|
@@ -30572,7 +30894,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
30572
30894
|
state: {}
|
|
30573
30895
|
};
|
|
30574
30896
|
jobsData.jobs.push(newJob);
|
|
30575
|
-
await
|
|
30897
|
+
await writeFile36(cronFilePath, JSON.stringify(jobsData, null, 2), "utf-8");
|
|
30576
30898
|
log.info("auto-registered hourly summary cron job");
|
|
30577
30899
|
} catch (err) {
|
|
30578
30900
|
log.error("failed to auto-register hourly summary cron job:", err);
|