@@ -22,7 +22,7 @@ afterEach(async () => {
2222 await Promise . all ( tempDirs . splice ( 0 ) . map ( ( dir ) => fs . rm ( dir , { recursive : true , force : true } ) ) ) ;
2323} ) ;
2424
25- function createHarness ( config : OpenClawConfig ) {
25+ function createHarness ( config : OpenClawConfig , workspaceDir ?: string ) {
2626 let beforeAgentReply :
2727 | ( (
2828 event : { cleanedBody : string } ,
@@ -36,7 +36,18 @@ function createHarness(config: OpenClawConfig) {
3636 } ;
3737
3838 const api = {
39- config,
39+ config : workspaceDir
40+ ? {
41+ ...config ,
42+ agents : {
43+ ...config . agents ,
44+ defaults : {
45+ ...config . agents ?. defaults ,
46+ workspace : workspaceDir ,
47+ } ,
48+ } ,
49+ }
50+ : config ,
4051 pluginConfig : { } ,
4152 logger,
4253 registerHook : vi . fn ( ) ,
@@ -65,26 +76,29 @@ describe("memory-core dreaming phases", () => {
6576 "utf-8" ,
6677 ) ;
6778
68- const { beforeAgentReply } = createHarness ( {
69- plugins : {
70- entries : {
71- "memory-core" : {
72- config : {
73- dreaming : {
74- enabled : true ,
75- phases : {
76- light : {
77- enabled : true ,
78- limit : 20 ,
79- lookbackDays : 2 ,
79+ const { beforeAgentReply } = createHarness (
80+ {
81+ plugins : {
82+ entries : {
83+ "memory-core" : {
84+ config : {
85+ dreaming : {
86+ enabled : true ,
87+ phases : {
88+ light : {
89+ enabled : true ,
90+ limit : 20 ,
91+ lookbackDays : 2 ,
92+ } ,
8093 } ,
8194 } ,
8295 } ,
8396 } ,
8497 } ,
8598 } ,
8699 } ,
87- } ) ;
100+ workspaceDir ,
101+ ) ;
88102
89103 const readSpy = vi . spyOn ( fs , "readFile" ) ;
90104 try {
@@ -103,7 +117,7 @@ describe("memory-core dreaming phases", () => {
103117 const dailyReadCount = readSpy . mock . calls . filter (
104118 ( [ target ] ) => String ( target ) === dailyPath ,
105119 ) . length ;
106- expect ( dailyReadCount ) . toBe ( 1 ) ;
120+ expect ( dailyReadCount ) . toBeLessThanOrEqual ( 1 ) ;
107121 await expect (
108122 fs . access ( path . join ( workspaceDir , "memory" , ".dreams" , "daily-ingestion.json" ) ) ,
109123 ) . resolves . toBeUndefined ( ) ;
@@ -129,26 +143,89 @@ describe("memory-core dreaming phases", () => {
129143 } ) ;
130144 expect ( before ) . toHaveLength ( 0 ) ;
131145
132- const { beforeAgentReply } = createHarness ( {
133- plugins : {
134- entries : {
135- "memory-core" : {
136- config : {
137- dreaming : {
138- enabled : true ,
139- phases : {
140- light : {
141- enabled : true ,
142- limit : 20 ,
143- lookbackDays : 2 ,
146+ const { beforeAgentReply } = createHarness (
147+ {
148+ plugins : {
149+ entries : {
150+ "memory-core" : {
151+ config : {
152+ dreaming : {
153+ enabled : true ,
154+ phases : {
155+ light : {
156+ enabled : true ,
157+ limit : 20 ,
158+ lookbackDays : 2 ,
159+ } ,
144160 } ,
145161 } ,
146162 } ,
147163 } ,
148164 } ,
149165 } ,
150166 } ,
167+ workspaceDir ,
168+ ) ;
169+
170+ await beforeAgentReply (
171+ { cleanedBody : "__openclaw_memory_core_light_sleep__" } ,
172+ { trigger : "heartbeat" , workspaceDir } ,
173+ ) ;
174+
175+ const after = await rankShortTermPromotionCandidates ( {
176+ workspaceDir,
177+ minScore : 0 ,
178+ minRecallCount : 0 ,
179+ minUniqueQueries : 0 ,
180+ nowMs : Date . parse ( "2026-04-05T10:05:00.000Z" ) ,
151181 } ) ;
182+ expect ( after ) . toHaveLength ( 1 ) ;
183+ expect ( after [ 0 ] ?. dailyCount ) . toBeGreaterThan ( 0 ) ;
184+ expect ( after [ 0 ] ?. startLine ) . toBe ( 3 ) ;
185+ expect ( after [ 0 ] ?. endLine ) . toBe ( 4 ) ;
186+ expect ( after [ 0 ] ?. snippet ) . toContain ( "Move backups to S3 Glacier." ) ;
187+ expect ( after [ 0 ] ?. snippet ) . toContain ( "Keep retention at 365 days." ) ;
188+ } ) ;
189+
190+ it ( "keeps section context when chunking durable daily notes" , async ( ) => {
191+ const workspaceDir = await createTempWorkspace ( ) ;
192+ await fs . mkdir ( path . join ( workspaceDir , "memory" ) , { recursive : true } ) ;
193+ await fs . writeFile (
194+ path . join ( workspaceDir , "memory" , "2026-04-05.md" ) ,
195+ [
196+ "# 2026-04-05" ,
197+ "" ,
198+ "## Emma Rees" ,
199+ "- She asked for more space after the last exchange." ,
200+ "- Better to keep messages short and low-pressure." ,
201+ "- Re-engagement should be time-bounded and optional." ,
202+ ] . join ( "\n" ) ,
203+ "utf-8" ,
204+ ) ;
205+
206+ const { beforeAgentReply } = createHarness (
207+ {
208+ plugins : {
209+ entries : {
210+ "memory-core" : {
211+ config : {
212+ dreaming : {
213+ enabled : true ,
214+ phases : {
215+ light : {
216+ enabled : true ,
217+ limit : 20 ,
218+ lookbackDays : 2 ,
219+ } ,
220+ } ,
221+ } ,
222+ } ,
223+ } ,
224+ } ,
225+ } ,
226+ } ,
227+ workspaceDir ,
228+ ) ;
152229
153230 await beforeAgentReply (
154231 { cleanedBody : "__openclaw_memory_core_light_sleep__" } ,
@@ -162,8 +239,86 @@ describe("memory-core dreaming phases", () => {
162239 minUniqueQueries : 0 ,
163240 nowMs : Date . parse ( "2026-04-05T10:05:00.000Z" ) ,
164241 } ) ;
165- expect ( after . length ) . toBeGreaterThan ( 0 ) ;
166- expect ( after . some ( ( candidate ) => ( candidate . dailyCount ?? 0 ) > 0 ) ) . toBe ( true ) ;
242+ expect ( after ) . toHaveLength ( 1 ) ;
243+ expect ( after [ 0 ] ?. startLine ) . toBe ( 4 ) ;
244+ expect ( after [ 0 ] ?. endLine ) . toBe ( 6 ) ;
245+ expect ( after [ 0 ] ?. snippet ) . toContain ( "Emma Rees:" ) ;
246+ expect ( after [ 0 ] ?. snippet ) . toContain ( "She asked for more space" ) ;
247+ expect ( after [ 0 ] ?. snippet ) . toContain ( "messages short and low-pressure" ) ;
248+ } ) ;
249+
250+ it ( "splits noisy daily notes into a few coherent chunks instead of one line per item" , async ( ) => {
251+ const workspaceDir = await createTempWorkspace ( ) ;
252+ await fs . mkdir ( path . join ( workspaceDir , "memory" ) , { recursive : true } ) ;
253+ await fs . writeFile (
254+ path . join ( workspaceDir , "memory" , "2026-04-05.md" ) ,
255+ [
256+ "# 2026-04-05" ,
257+ "" ,
258+ "## Operations" ,
259+ "- Restarted the gateway after auth drift." ,
260+ "- Tokens now line up again." ,
261+ "" ,
262+ "## Bex" ,
263+ "- She prefers direct plans over open-ended maybes." ,
264+ "- Better to offer one concrete time window." ,
265+ "" ,
266+ "11:30" ,
267+ "" ,
268+ "## Travel" ,
269+ "- Flight lands at 08:10." ,
270+ ] . join ( "\n" ) ,
271+ "utf-8" ,
272+ ) ;
273+
274+ const { beforeAgentReply } = createHarness (
275+ {
276+ plugins : {
277+ entries : {
278+ "memory-core" : {
279+ config : {
280+ dreaming : {
281+ enabled : true ,
282+ phases : {
283+ light : {
284+ enabled : true ,
285+ limit : 20 ,
286+ lookbackDays : 2 ,
287+ } ,
288+ } ,
289+ } ,
290+ } ,
291+ } ,
292+ } ,
293+ } ,
294+ } ,
295+ workspaceDir ,
296+ ) ;
297+
298+ await beforeAgentReply (
299+ { cleanedBody : "__openclaw_memory_core_light_sleep__" } ,
300+ { trigger : "heartbeat" , workspaceDir } ,
301+ ) ;
302+
303+ const after = await rankShortTermPromotionCandidates ( {
304+ workspaceDir,
305+ minScore : 0 ,
306+ minRecallCount : 0 ,
307+ minUniqueQueries : 0 ,
308+ nowMs : Date . parse ( "2026-04-05T10:05:00.000Z" ) ,
309+ } ) ;
310+ expect ( after ) . toHaveLength ( 3 ) ;
311+ expect ( after . map ( ( candidate ) => candidate . snippet ) ) . toEqual (
312+ expect . arrayContaining ( [
313+ expect . stringContaining (
314+ "Operations: Restarted the gateway after auth drift.; Tokens now line up again." ,
315+ ) ,
316+ expect . stringContaining (
317+ "Bex: She prefers direct plans over open-ended maybes.; Better to offer one concrete time window." ,
318+ ) ,
319+ expect . stringContaining ( "Travel: Flight lands at 08:10." ) ,
320+ ] ) ,
321+ ) ;
167322 } ) ;
168323
169324 it ( "records light/rem signals that reinforce deep promotion ranking" , async ( ) => {
@@ -210,32 +365,35 @@ describe("memory-core dreaming phases", () => {
210365 expect ( baseline ) . toHaveLength ( 1 ) ;
211366 const baselineScore = baseline [ 0 ] ! . score ;
212367
213- const { beforeAgentReply } = createHarness ( {
214- plugins : {
215- entries : {
216- "memory-core" : {
217- config : {
218- dreaming : {
219- enabled : true ,
220- phases : {
221- light : {
222- enabled : true ,
223- limit : 10 ,
224- lookbackDays : 7 ,
225- } ,
226- rem : {
227- enabled : true ,
228- limit : 10 ,
229- lookbackDays : 7 ,
230- minPatternStrength : 0 ,
368+ const { beforeAgentReply } = createHarness (
369+ {
370+ plugins : {
371+ entries : {
372+ "memory-core" : {
373+ config : {
374+ dreaming : {
375+ enabled : true ,
376+ phases : {
377+ light : {
378+ enabled : true ,
379+ limit : 10 ,
380+ lookbackDays : 7 ,
381+ } ,
382+ rem : {
383+ enabled : true ,
384+ limit : 10 ,
385+ lookbackDays : 7 ,
386+ minPatternStrength : 0 ,
387+ } ,
231388 } ,
232389 } ,
233390 } ,
234391 } ,
235392 } ,
236393 } ,
237394 } ,
238- } ) ;
395+ workspaceDir ,
396+ ) ;
239397
240398 await beforeAgentReply (
241399 { cleanedBody : "__openclaw_memory_core_light_sleep__" } ,
0 commit comments