@joshuaswarren/openclaw-engram 9.0.46 → 9.0.47
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 +2 -2
- package/dist/index.js +173 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ AI agents forget everything between conversations. Engram fixes that.
|
|
|
46
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`.
|
|
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
|
-
- **Resume-bundle
|
|
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
50
|
- **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.
|
|
51
51
|
|
|
52
52
|
## Quick Start
|
|
@@ -229,7 +229,7 @@ Key settings:
|
|
|
229
229
|
| `commitmentLifecycleEnabled` | `false` | Enable commitment lifecycle transitions, stale tracking, and resolved-entry cleanup for the commitment ledger |
|
|
230
230
|
| `commitmentStaleDays` | `14` | Days before an open commitment without a due date is considered stale in lifecycle status |
|
|
231
231
|
| `commitmentLedgerDir` | `{memoryDir}/state/commitment-ledger` | Root directory for typed commitment ledger entries |
|
|
232
|
-
| `resumeBundlesEnabled` | `false` | Enable typed resume-bundle storage plus the operator-facing `resume-bundle-status` and `resume-bundle-
|
|
232
|
+
| `resumeBundlesEnabled` | `false` | Enable typed resume-bundle storage plus the operator-facing `resume-bundle-status`, `resume-bundle-record`, and `resume-bundle-build` commands |
|
|
233
233
|
| `resumeBundleDir` | `{memoryDir}/state/resume-bundles` | Root directory for typed resume bundles |
|
|
234
234
|
| `workProductRecallEnabled` | `false` | Inject prompt-relevant work-product ledger entries into recall and expose `openclaw engram work-product-recall-search` |
|
|
235
235
|
| `workProductLedgerDir` | `{memoryDir}/state/work-product-ledger` | Root directory for typed work-product ledger entries |
|
package/dist/index.js
CHANGED
|
@@ -27828,6 +27828,7 @@ async function runCompatChecks(options) {
|
|
|
27828
27828
|
// src/resume-bundles.ts
|
|
27829
27829
|
import path59 from "path";
|
|
27830
27830
|
import { mkdir as mkdir40, writeFile as writeFile37 } from "fs/promises";
|
|
27831
|
+
var DEFAULT_RESUME_BUNDLE_REF_LIMIT = 5;
|
|
27831
27832
|
function resolveResumeBundleDir(memoryDir, overrideDir) {
|
|
27832
27833
|
if (typeof overrideDir === "string" && overrideDir.trim().length > 0) {
|
|
27833
27834
|
return overrideDir.trim();
|
|
@@ -27862,6 +27863,128 @@ function validateResumeBundle(raw) {
|
|
|
27862
27863
|
metadata: validateStringRecord(raw.metadata, "metadata")
|
|
27863
27864
|
};
|
|
27864
27865
|
}
|
|
27866
|
+
async function readValidatedItems(options) {
|
|
27867
|
+
const files = await listJsonFiles(options.rootDir);
|
|
27868
|
+
const items = [];
|
|
27869
|
+
for (const filePath of files) {
|
|
27870
|
+
try {
|
|
27871
|
+
items.push(options.validate(await readJsonFile(filePath)));
|
|
27872
|
+
} catch {
|
|
27873
|
+
}
|
|
27874
|
+
}
|
|
27875
|
+
return items;
|
|
27876
|
+
}
|
|
27877
|
+
async function readObjectiveStateSnapshotsForSession(options) {
|
|
27878
|
+
const rootDir = resolveObjectiveStateStoreDir(options.memoryDir, options.objectiveStateStoreDir);
|
|
27879
|
+
const items = await readValidatedItems({
|
|
27880
|
+
rootDir: path59.join(rootDir, "snapshots"),
|
|
27881
|
+
validate: validateObjectiveStateSnapshot
|
|
27882
|
+
});
|
|
27883
|
+
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);
|
|
27884
|
+
}
|
|
27885
|
+
async function readWorkProductEntriesForSession(options) {
|
|
27886
|
+
const rootDir = resolveWorkProductLedgerDir(options.memoryDir, options.workProductLedgerDir);
|
|
27887
|
+
const items = await readValidatedItems({
|
|
27888
|
+
rootDir: path59.join(rootDir, "entries"),
|
|
27889
|
+
validate: validateWorkProductLedgerEntry
|
|
27890
|
+
});
|
|
27891
|
+
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);
|
|
27892
|
+
}
|
|
27893
|
+
async function readCommitmentEntriesForSession(options) {
|
|
27894
|
+
const rootDir = resolveCommitmentLedgerDir(options.memoryDir, options.commitmentLedgerDir);
|
|
27895
|
+
const items = await readValidatedItems({
|
|
27896
|
+
rootDir: path59.join(rootDir, "entries"),
|
|
27897
|
+
validate: validateCommitmentLedgerEntry
|
|
27898
|
+
});
|
|
27899
|
+
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);
|
|
27900
|
+
}
|
|
27901
|
+
function buildRecoveryFact(recoverySummary) {
|
|
27902
|
+
return recoverySummary.healthy ? `Transcript recovery healthy with ${recoverySummary.issueCount} issue(s), ${recoverySummary.incompleteTurns} incomplete turn(s), and ${recoverySummary.brokenChains} broken chain(s).` : `Transcript recovery flagged ${recoverySummary.issueCount} issue(s), ${recoverySummary.incompleteTurns} incomplete turn(s), and ${recoverySummary.brokenChains} broken chain(s); checkpoint healthy: ${recoverySummary.checkpointHealthy ? "yes" : "no"}.`;
|
|
27903
|
+
}
|
|
27904
|
+
function buildBundleSummary(options) {
|
|
27905
|
+
const parts = [
|
|
27906
|
+
`${options.openCommitmentCount} open commitment(s)`,
|
|
27907
|
+
`${options.workProductCount} recent work product(s)`,
|
|
27908
|
+
`${options.objectiveSnapshotCount} recent objective-state snapshot(s)`
|
|
27909
|
+
];
|
|
27910
|
+
if (options.recoveryHealthy === false) {
|
|
27911
|
+
parts.push("transcript recovery issues remain");
|
|
27912
|
+
}
|
|
27913
|
+
return `Resume ${options.sessionKey}: ${parts.join(", ")}.`;
|
|
27914
|
+
}
|
|
27915
|
+
async function buildResumeBundleFromState(options) {
|
|
27916
|
+
const recordedAt = assertIsoRecordedAt(options.recordedAt, "recordedAt");
|
|
27917
|
+
const maxRefsPerStore = Math.max(1, Math.floor(options.maxRefsPerStore ?? DEFAULT_RESUME_BUNDLE_REF_LIMIT));
|
|
27918
|
+
const objectiveSnapshots = options.objectiveStateMemoryEnabled ? await readObjectiveStateSnapshotsForSession({
|
|
27919
|
+
memoryDir: options.memoryDir,
|
|
27920
|
+
objectiveStateStoreDir: options.objectiveStateStoreDir,
|
|
27921
|
+
sessionKey: options.sessionKey,
|
|
27922
|
+
maxResults: maxRefsPerStore
|
|
27923
|
+
}) : [];
|
|
27924
|
+
const workProducts = options.creationMemoryEnabled ? await readWorkProductEntriesForSession({
|
|
27925
|
+
memoryDir: options.memoryDir,
|
|
27926
|
+
workProductLedgerDir: options.workProductLedgerDir,
|
|
27927
|
+
sessionKey: options.sessionKey,
|
|
27928
|
+
maxResults: maxRefsPerStore
|
|
27929
|
+
}) : [];
|
|
27930
|
+
const commitments = options.creationMemoryEnabled && options.commitmentLedgerEnabled ? await readCommitmentEntriesForSession({
|
|
27931
|
+
memoryDir: options.memoryDir,
|
|
27932
|
+
commitmentLedgerDir: options.commitmentLedgerDir,
|
|
27933
|
+
sessionKey: options.sessionKey,
|
|
27934
|
+
maxResults: maxRefsPerStore,
|
|
27935
|
+
state: "open"
|
|
27936
|
+
}) : [];
|
|
27937
|
+
const openCommitments = commitments;
|
|
27938
|
+
const recordedAtMs = Date.parse(recordedAt);
|
|
27939
|
+
const overdueCommitments = openCommitments.filter((entry) => {
|
|
27940
|
+
if (!entry.dueAt) return false;
|
|
27941
|
+
const dueAtMs = Date.parse(entry.dueAt);
|
|
27942
|
+
return Number.isFinite(dueAtMs) && dueAtMs < recordedAtMs;
|
|
27943
|
+
});
|
|
27944
|
+
let recoverySummary;
|
|
27945
|
+
if (options.transcriptEnabled) {
|
|
27946
|
+
const transcript = new TranscriptManager(parseConfig({
|
|
27947
|
+
memoryDir: options.memoryDir,
|
|
27948
|
+
transcriptEnabled: true
|
|
27949
|
+
}));
|
|
27950
|
+
await transcript.initialize();
|
|
27951
|
+
recoverySummary = await transcript.getRecoverySummary(options.sessionKey);
|
|
27952
|
+
}
|
|
27953
|
+
const keyFacts = [
|
|
27954
|
+
recoverySummary ? buildRecoveryFact(recoverySummary) : void 0,
|
|
27955
|
+
workProducts[0]?.summary,
|
|
27956
|
+
objectiveSnapshots[0]?.summary
|
|
27957
|
+
].filter((value) => typeof value === "string" && value.length > 0);
|
|
27958
|
+
const nextActions = openCommitments.map((entry) => entry.summary).filter((summary, index, values) => values.indexOf(summary) === index).slice(0, maxRefsPerStore);
|
|
27959
|
+
const riskFlags = [
|
|
27960
|
+
recoverySummary && !recoverySummary.healthy ? buildRecoveryFact(recoverySummary) : void 0,
|
|
27961
|
+
...objectiveSnapshots.filter((snapshot) => snapshot.outcome === "failure" || snapshot.outcome === "partial").map((snapshot) => snapshot.summary),
|
|
27962
|
+
...overdueCommitments.map((entry) => `Overdue commitment: ${entry.summary}`)
|
|
27963
|
+
].filter(
|
|
27964
|
+
(value, index, values) => typeof value === "string" && value.length > 0 && values.indexOf(value) === index
|
|
27965
|
+
);
|
|
27966
|
+
return validateResumeBundle({
|
|
27967
|
+
schemaVersion: 1,
|
|
27968
|
+
bundleId: options.bundleId,
|
|
27969
|
+
recordedAt,
|
|
27970
|
+
sessionKey: options.sessionKey,
|
|
27971
|
+
source: options.source ?? "system",
|
|
27972
|
+
scope: options.scope,
|
|
27973
|
+
summary: buildBundleSummary({
|
|
27974
|
+
sessionKey: options.sessionKey,
|
|
27975
|
+
openCommitmentCount: openCommitments.length,
|
|
27976
|
+
workProductCount: workProducts.length,
|
|
27977
|
+
objectiveSnapshotCount: objectiveSnapshots.length,
|
|
27978
|
+
recoveryHealthy: recoverySummary?.healthy
|
|
27979
|
+
}),
|
|
27980
|
+
objectiveStateSnapshotRefs: objectiveSnapshots.map((snapshot) => snapshot.snapshotId),
|
|
27981
|
+
workProductEntryRefs: workProducts.map((entry) => entry.entryId),
|
|
27982
|
+
commitmentEntryRefs: openCommitments.map((entry) => entry.entryId),
|
|
27983
|
+
keyFacts: keyFacts.length > 0 ? keyFacts : void 0,
|
|
27984
|
+
nextActions: nextActions.length > 0 ? nextActions : void 0,
|
|
27985
|
+
riskFlags: riskFlags.length > 0 ? riskFlags : void 0
|
|
27986
|
+
});
|
|
27987
|
+
}
|
|
27865
27988
|
async function recordResumeBundle(options) {
|
|
27866
27989
|
const rootDir = resolveResumeBundleDir(options.memoryDir, options.resumeBundleDir);
|
|
27867
27990
|
const validated = validateResumeBundle(options.bundle);
|
|
@@ -28354,6 +28477,31 @@ async function runResumeBundleRecordCliCommand(options) {
|
|
|
28354
28477
|
bundle: options.bundle
|
|
28355
28478
|
});
|
|
28356
28479
|
}
|
|
28480
|
+
async function runResumeBundleBuildCliCommand(options) {
|
|
28481
|
+
if (!options.creationMemoryEnabled || !options.resumeBundlesEnabled) {
|
|
28482
|
+
return null;
|
|
28483
|
+
}
|
|
28484
|
+
const bundle = await buildResumeBundleFromState({
|
|
28485
|
+
memoryDir: options.memoryDir,
|
|
28486
|
+
sessionKey: options.sessionKey,
|
|
28487
|
+
bundleId: options.bundleId,
|
|
28488
|
+
recordedAt: options.recordedAt,
|
|
28489
|
+
scope: options.scope,
|
|
28490
|
+
transcriptEnabled: options.transcriptEnabled,
|
|
28491
|
+
objectiveStateMemoryEnabled: options.objectiveStateMemoryEnabled,
|
|
28492
|
+
objectiveStateStoreDir: options.objectiveStateStoreDir,
|
|
28493
|
+
creationMemoryEnabled: options.creationMemoryEnabled,
|
|
28494
|
+
workProductLedgerDir: options.workProductLedgerDir,
|
|
28495
|
+
commitmentLedgerEnabled: options.commitmentLedgerEnabled,
|
|
28496
|
+
commitmentLedgerDir: options.commitmentLedgerDir
|
|
28497
|
+
});
|
|
28498
|
+
const filePath = await recordResumeBundle({
|
|
28499
|
+
memoryDir: options.memoryDir,
|
|
28500
|
+
resumeBundleDir: options.resumeBundleDir,
|
|
28501
|
+
bundle
|
|
28502
|
+
});
|
|
28503
|
+
return { bundle, filePath };
|
|
28504
|
+
}
|
|
28357
28505
|
async function runCommitmentStatusCliCommand(options) {
|
|
28358
28506
|
return getCommitmentLedgerStatus({
|
|
28359
28507
|
memoryDir: options.memoryDir,
|
|
@@ -29669,6 +29817,31 @@ function registerCli(api, orchestrator) {
|
|
|
29669
29817
|
console.log(JSON.stringify({ wrote: filePath !== null, filePath }, null, 2));
|
|
29670
29818
|
console.log("OK");
|
|
29671
29819
|
});
|
|
29820
|
+
cmd.command("resume-bundle-build").description("Build and persist a resume bundle from transcript recovery, objective state, work products, and open commitments").requiredOption("--bundle-id <bundleId>", "Resume bundle id").requiredOption("--recorded-at <recordedAt>", "ISO timestamp for the bundle").requiredOption("--session-key <sessionKey>", "Session key that owns the bundle").requiredOption("--scope <scope>", "Primary scope or recovery domain for the bundle").action(async (...args) => {
|
|
29821
|
+
const options = args[0] ?? {};
|
|
29822
|
+
const built = await runResumeBundleBuildCliCommand({
|
|
29823
|
+
memoryDir: orchestrator.config.memoryDir,
|
|
29824
|
+
resumeBundleDir: orchestrator.config.resumeBundleDir,
|
|
29825
|
+
objectiveStateStoreDir: orchestrator.config.objectiveStateStoreDir,
|
|
29826
|
+
workProductLedgerDir: orchestrator.config.workProductLedgerDir,
|
|
29827
|
+
commitmentLedgerDir: orchestrator.config.commitmentLedgerDir,
|
|
29828
|
+
creationMemoryEnabled: orchestrator.config.creationMemoryEnabled,
|
|
29829
|
+
resumeBundlesEnabled: orchestrator.config.resumeBundlesEnabled,
|
|
29830
|
+
transcriptEnabled: orchestrator.config.transcriptEnabled,
|
|
29831
|
+
objectiveStateMemoryEnabled: orchestrator.config.objectiveStateMemoryEnabled,
|
|
29832
|
+
commitmentLedgerEnabled: orchestrator.config.commitmentLedgerEnabled,
|
|
29833
|
+
bundleId: String(options.bundleId ?? ""),
|
|
29834
|
+
recordedAt: String(options.recordedAt ?? ""),
|
|
29835
|
+
sessionKey: String(options.sessionKey ?? ""),
|
|
29836
|
+
scope: String(options.scope ?? "")
|
|
29837
|
+
});
|
|
29838
|
+
console.log(JSON.stringify({
|
|
29839
|
+
wrote: built !== null,
|
|
29840
|
+
filePath: built?.filePath ?? null,
|
|
29841
|
+
bundle: built?.bundle ?? null
|
|
29842
|
+
}, null, 2));
|
|
29843
|
+
console.log("OK");
|
|
29844
|
+
});
|
|
29672
29845
|
cmd.command("commitment-status").description("Show commitment ledger status, entry counts, and the latest recorded commitment").action(async () => {
|
|
29673
29846
|
const status = await runCommitmentStatusCliCommand({
|
|
29674
29847
|
memoryDir: orchestrator.config.memoryDir,
|