Skip to content

Commit d1c7d9a

Browse files
committed
feat(memory-sdk): add memory event journal bridge
1 parent fbbe2a1 commit d1c7d9a

14 files changed

Lines changed: 413 additions & 5 deletions

File tree

docs/plugins/sdk-migration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,13 @@ Current bundled provider examples:
287287
| `plugin-sdk/memory-core-host-multimodal` | Memory host multimodal helpers | Memory host multimodal helpers |
288288
| `plugin-sdk/memory-core-host-query` | Memory host query helpers | Memory host query helpers |
289289
| `plugin-sdk/memory-core-host-secret` | Memory host secret helpers | Memory host secret helpers |
290+
| `plugin-sdk/memory-core-host-events` | Memory host event journal helpers | Memory host event journal helpers |
290291
| `plugin-sdk/memory-core-host-status` | Memory host status helpers | Memory host status helpers |
291292
| `plugin-sdk/memory-core-host-runtime-cli` | Memory host CLI runtime | Memory host CLI runtime helpers |
292293
| `plugin-sdk/memory-core-host-runtime-core` | Memory host core runtime | Memory host core runtime helpers |
293294
| `plugin-sdk/memory-core-host-runtime-files` | Memory host file/runtime helpers | Memory host file/runtime helpers |
294295
| `plugin-sdk/memory-host-core` | Memory host core runtime alias | Vendor-neutral alias for memory host core runtime helpers |
296+
| `plugin-sdk/memory-host-events` | Memory host event journal alias | Vendor-neutral alias for memory host event journal helpers |
295297
| `plugin-sdk/memory-host-files` | Memory host file/runtime alias | Vendor-neutral alias for memory host file/runtime helpers |
296298
| `plugin-sdk/memory-host-markdown` | Managed markdown helpers | Shared managed-markdown helpers for memory-adjacent plugins |
297299
| `plugin-sdk/memory-host-status` | Memory host status alias | Vendor-neutral alias for memory host status helpers |

docs/plugins/sdk-overview.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,13 @@ explicitly promotes one as public.
253253
| `plugin-sdk/memory-core-host-multimodal` | Memory host multimodal helpers |
254254
| `plugin-sdk/memory-core-host-query` | Memory host query helpers |
255255
| `plugin-sdk/memory-core-host-secret` | Memory host secret helpers |
256+
| `plugin-sdk/memory-core-host-events` | Memory host event journal helpers |
256257
| `plugin-sdk/memory-core-host-status` | Memory host status helpers |
257258
| `plugin-sdk/memory-core-host-runtime-cli` | Memory host CLI runtime helpers |
258259
| `plugin-sdk/memory-core-host-runtime-core` | Memory host core runtime helpers |
259260
| `plugin-sdk/memory-core-host-runtime-files` | Memory host file/runtime helpers |
260261
| `plugin-sdk/memory-host-core` | Vendor-neutral alias for memory host core runtime helpers |
262+
| `plugin-sdk/memory-host-events` | Vendor-neutral alias for memory host event journal helpers |
261263
| `plugin-sdk/memory-host-files` | Vendor-neutral alias for memory host file/runtime helpers |
262264
| `plugin-sdk/memory-host-markdown` | Shared managed-markdown helpers for memory-adjacent plugins |
263265
| `plugin-sdk/memory-host-status` | Vendor-neutral alias for memory host status helpers |

extensions/memory-core/src/dreaming-markdown.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
type MemoryDreamingPhaseName,
66
type MemoryDreamingStorageConfig,
77
} from "openclaw/plugin-sdk/memory-core-host-status";
8+
import { appendMemoryHostEvent } from "openclaw/plugin-sdk/memory-host-events";
89
import {
910
replaceManagedMarkdownBlock,
1011
withTrailingNewline,
@@ -104,6 +105,16 @@ export async function writeDailyDreamingPhaseBlock(params: {
104105
await fs.writeFile(reportPath, report, "utf-8");
105106
}
106107

108+
await appendMemoryHostEvent(params.workspaceDir, {
109+
type: "memory.dream.completed",
110+
timestamp: new Date(nowMs).toISOString(),
111+
phase: params.phase,
112+
...(inlinePath ? { inlinePath } : {}),
113+
...(reportPath ? { reportPath } : {}),
114+
lineCount: params.bodyLines.length,
115+
storageMode: params.storage.mode,
116+
});
117+
107118
return {
108119
...(inlinePath ? { inlinePath } : {}),
109120
...(reportPath ? { reportPath } : {}),
@@ -125,5 +136,13 @@ export async function writeDeepDreamingReport(params: {
125136
await fs.mkdir(path.dirname(reportPath), { recursive: true });
126137
const body = params.bodyLines.length > 0 ? params.bodyLines.join("\n") : "- No durable changes.";
127138
await fs.writeFile(reportPath, `# Deep Sleep\n\n${body}\n`, "utf-8");
139+
await appendMemoryHostEvent(params.workspaceDir, {
140+
type: "memory.dream.completed",
141+
timestamp: new Date(nowMs).toISOString(),
142+
phase: "deep",
143+
reportPath,
144+
lineCount: params.bodyLines.length,
145+
storageMode: params.storage.mode,
146+
});
128147
return reportPath;
129148
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import fs from "node:fs/promises";
2+
import os from "node:os";
3+
import path from "node:path";
4+
import { readMemoryHostEvents } from "openclaw/plugin-sdk/memory-host-events";
5+
import { afterEach, describe, expect, it } from "vitest";
6+
import { writeDailyDreamingPhaseBlock } from "./dreaming-markdown.js";
7+
import {
8+
applyShortTermPromotions,
9+
rankShortTermPromotionCandidates,
10+
recordShortTermRecalls,
11+
} from "./short-term-promotion.js";
12+
13+
const tempDirs: string[] = [];
14+
15+
afterEach(async () => {
16+
await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
17+
});
18+
19+
describe("memory host event journal integration", () => {
20+
it("records recall and promotion events from short-term promotion flows", async () => {
21+
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "memory-core-events-"));
22+
tempDirs.push(workspaceDir);
23+
await fs.mkdir(path.join(workspaceDir, "memory"), { recursive: true });
24+
await fs.writeFile(
25+
path.join(workspaceDir, "memory", "2026-04-05.md"),
26+
"# Daily\n\nalpha\nbeta\ngamma\n",
27+
"utf8",
28+
);
29+
30+
await recordShortTermRecalls({
31+
workspaceDir,
32+
query: "alpha memory",
33+
results: [
34+
{
35+
path: "memory/2026-04-05.md",
36+
startLine: 3,
37+
endLine: 4,
38+
score: 0.92,
39+
snippet: "alpha beta",
40+
source: "memory",
41+
},
42+
],
43+
nowMs: Date.UTC(2026, 3, 5, 12, 0, 0),
44+
});
45+
46+
const candidates = await rankShortTermPromotionCandidates({
47+
workspaceDir,
48+
minScore: 0,
49+
minRecallCount: 0,
50+
minUniqueQueries: 0,
51+
nowMs: Date.UTC(2026, 3, 5, 12, 5, 0),
52+
});
53+
const applied = await applyShortTermPromotions({
54+
workspaceDir,
55+
candidates,
56+
minScore: 0,
57+
minRecallCount: 0,
58+
minUniqueQueries: 0,
59+
nowMs: Date.UTC(2026, 3, 5, 12, 10, 0),
60+
});
61+
62+
expect(applied.applied).toBe(1);
63+
64+
const events = await readMemoryHostEvents({ workspaceDir });
65+
66+
expect(events.map((event) => event.type)).toEqual([
67+
"memory.recall.recorded",
68+
"memory.promotion.applied",
69+
]);
70+
expect(events[0]).toMatchObject({
71+
type: "memory.recall.recorded",
72+
resultCount: 1,
73+
query: "alpha memory",
74+
});
75+
expect(events[1]).toMatchObject({
76+
type: "memory.promotion.applied",
77+
applied: 1,
78+
});
79+
});
80+
81+
it("records dreaming completion events when phase artifacts are written", async () => {
82+
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "memory-core-dream-events-"));
83+
tempDirs.push(workspaceDir);
84+
85+
const written = await writeDailyDreamingPhaseBlock({
86+
workspaceDir,
87+
phase: "light",
88+
bodyLines: ["- staged note", "- second note"],
89+
nowMs: Date.UTC(2026, 3, 5, 13, 0, 0),
90+
storage: { mode: "both", separateReports: true },
91+
});
92+
93+
const events = await readMemoryHostEvents({ workspaceDir });
94+
95+
expect(written.inlinePath).toBeTruthy();
96+
expect(written.reportPath).toBeTruthy();
97+
expect(events).toHaveLength(1);
98+
expect(events[0]).toMatchObject({
99+
type: "memory.dream.completed",
100+
phase: "light",
101+
lineCount: 2,
102+
storageMode: "both",
103+
});
104+
});
105+
});

extensions/memory-core/src/short-term-promotion.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
33
import path from "node:path";
44
import type { MemorySearchResult } from "openclaw/plugin-sdk/memory-core-host-runtime-files";
55
import { formatMemoryDreamingDay } from "openclaw/plugin-sdk/memory-core-host-status";
6+
import { appendMemoryHostEvent } from "openclaw/plugin-sdk/memory-host-events";
67
import {
78
deriveConceptTags,
89
MAX_CONCEPT_TAGS,
@@ -631,6 +632,18 @@ export async function recordShortTermRecalls(params: {
631632

632633
store.updatedAt = nowIso;
633634
await writeStore(workspaceDir, store);
635+
await appendMemoryHostEvent(workspaceDir, {
636+
type: "memory.recall.recorded",
637+
timestamp: nowIso,
638+
query,
639+
resultCount: relevant.length,
640+
results: relevant.map((result) => ({
641+
path: normalizeMemoryPath(result.path),
642+
startLine: Math.max(1, Math.floor(result.startLine)),
643+
endLine: Math.max(1, Math.floor(result.endLine)),
644+
score: clampScore(result.score),
645+
})),
646+
});
634647
});
635648
}
636649

@@ -1042,6 +1055,20 @@ export async function applyShortTermPromotions(
10421055
}
10431056
store.updatedAt = nowIso;
10441057
await writeStore(workspaceDir, store);
1058+
await appendMemoryHostEvent(workspaceDir, {
1059+
type: "memory.promotion.applied",
1060+
timestamp: nowIso,
1061+
memoryPath,
1062+
applied: rehydratedSelected.length,
1063+
candidates: rehydratedSelected.map((candidate) => ({
1064+
key: candidate.key,
1065+
path: candidate.path,
1066+
startLine: candidate.startLine,
1067+
endLine: candidate.endLine,
1068+
score: candidate.score,
1069+
recallCount: candidate.recallCount,
1070+
})),
1071+
});
10451072

10461073
return {
10471074
memoryPath,

extensions/memory-wiki/src/bridge.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fs from "node:fs/promises";
22
import os from "node:os";
33
import path from "node:path";
4+
import { appendMemoryHostEvent } from "openclaw/plugin-sdk/memory-host-events";
45
import { afterEach, describe, expect, it } from "vitest";
56
import type { OpenClawConfig } from "../api.js";
67
import { syncMemoryWikiBridgeSources } from "./bridge.js";
@@ -106,4 +107,58 @@ describe("syncMemoryWikiBridgeSources", () => {
106107
pagePaths: [],
107108
});
108109
});
110+
111+
it("imports the public memory event journal when followMemoryEvents is enabled", async () => {
112+
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "memory-wiki-bridge-events-ws-"));
113+
const vaultDir = await fs.mkdtemp(path.join(os.tmpdir(), "memory-wiki-bridge-events-vault-"));
114+
tempDirs.push(workspaceDir, vaultDir);
115+
116+
await appendMemoryHostEvent(workspaceDir, {
117+
type: "memory.recall.recorded",
118+
timestamp: "2026-04-05T12:00:00.000Z",
119+
query: "bridge events",
120+
resultCount: 1,
121+
results: [
122+
{
123+
path: "memory/2026-04-05.md",
124+
startLine: 1,
125+
endLine: 2,
126+
score: 0.8,
127+
},
128+
],
129+
});
130+
131+
const config = resolveMemoryWikiConfig(
132+
{
133+
vaultMode: "bridge",
134+
vault: { path: vaultDir },
135+
bridge: {
136+
enabled: true,
137+
followMemoryEvents: true,
138+
},
139+
},
140+
{ homedir: "/Users/tester" },
141+
);
142+
const appConfig: OpenClawConfig = {
143+
plugins: {
144+
entries: {
145+
"memory-core": {
146+
enabled: true,
147+
config: {},
148+
},
149+
},
150+
},
151+
agents: {
152+
list: [{ id: "main", default: true, workspace: workspaceDir }],
153+
},
154+
};
155+
156+
const result = await syncMemoryWikiBridgeSources({ config, appConfig });
157+
158+
expect(result.artifactCount).toBe(1);
159+
expect(result.importedCount).toBe(1);
160+
const page = await fs.readFile(path.join(vaultDir, result.pagePaths[0] ?? ""), "utf8");
161+
expect(page).toContain("sourceType: memory-bridge-events");
162+
expect(page).toContain('"type":"memory.recall.recorded"');
163+
});
109164
});

0 commit comments

Comments
 (0)