@@ -35,6 +35,9 @@ describe("BasicMemoryContextEngine", () => {
3535 search : jest . Mock
3636 recentActivity : jest . Mock
3737 indexConversation : jest . Mock
38+ writeNote : jest . Mock
39+ editNote : jest . Mock
40+ deleteNote : jest . Mock
3841 }
3942
4043 beforeEach ( ( ) => {
@@ -56,6 +59,26 @@ describe("BasicMemoryContextEngine", () => {
5659 } ,
5760 ] ) ,
5861 indexConversation : jest . fn ( ) . mockResolvedValue ( undefined ) ,
62+ writeNote : jest . fn ( ) . mockResolvedValue ( {
63+ title : "subagent-handoff-agent-test-subagent-child-1" ,
64+ permalink : "agent/subagents/subagent-handoff-agent-test-subagent-child-1" ,
65+ file_path :
66+ "memory/agent/subagents/subagent-handoff-agent-test-subagent-child-1.md" ,
67+ content : "" ,
68+ } ) ,
69+ editNote : jest . fn ( ) . mockResolvedValue ( {
70+ title : "subagent-handoff-agent-test-subagent-child-1" ,
71+ permalink : "agent/subagents/subagent-handoff-agent-test-subagent-child-1" ,
72+ file_path :
73+ "memory/agent/subagents/subagent-handoff-agent-test-subagent-child-1.md" ,
74+ operation : "append" ,
75+ } ) ,
76+ deleteNote : jest . fn ( ) . mockResolvedValue ( {
77+ title : "subagent-handoff-agent-test-subagent-child-1" ,
78+ permalink : "agent/subagents/subagent-handoff-agent-test-subagent-child-1" ,
79+ file_path :
80+ "memory/agent/subagents/subagent-handoff-agent-test-subagent-child-1.md" ,
81+ } ) ,
5982 }
6083 } )
6184
@@ -207,6 +230,102 @@ describe("BasicMemoryContextEngine", () => {
207230 expect ( second . systemPromptAddition ) . toBe ( first . systemPromptAddition )
208231 } )
209232
233+ it ( "creates a parent-to-child BM handoff note on subagent spawn" , async ( ) => {
234+ const engine = new BasicMemoryContextEngine (
235+ mockClient as unknown as BmClient ,
236+ makeConfig ( ) ,
237+ )
238+
239+ await engine . bootstrap ( {
240+ sessionId : "parent-session" ,
241+ sessionFile : "/tmp/parent-session.jsonl" ,
242+ } )
243+
244+ const preparation = await engine . prepareSubagentSpawn ( {
245+ parentSessionKey : "parent-session" ,
246+ childSessionKey : "agent:test:subagent:child-1" ,
247+ } )
248+
249+ expect ( preparation ) . toBeDefined ( )
250+ expect ( mockClient . writeNote ) . toHaveBeenCalledWith (
251+ "subagent-handoff-agent-test-subagent-child-1" ,
252+ expect . stringContaining ( "## Parent Basic Memory Context" ) ,
253+ "agent/subagents" ,
254+ )
255+ } )
256+
257+ it ( "rolls back the handoff note when subagent spawn fails after preparation" , async ( ) => {
258+ const engine = new BasicMemoryContextEngine (
259+ mockClient as unknown as BmClient ,
260+ makeConfig ( ) ,
261+ )
262+
263+ const preparation = await engine . prepareSubagentSpawn ( {
264+ parentSessionKey : "parent-session" ,
265+ childSessionKey : "agent:test:subagent:child-rollback" ,
266+ } )
267+
268+ expect ( preparation ) . toBeDefined ( )
269+ await preparation ?. rollback ( )
270+
271+ expect ( mockClient . deleteNote ) . toHaveBeenCalledWith (
272+ "agent/subagents/subagent-handoff-agent-test-subagent-child-1" ,
273+ )
274+ } )
275+
276+ it ( "appends completion details to the handoff note when a child session completes" , async ( ) => {
277+ const engine = new BasicMemoryContextEngine (
278+ mockClient as unknown as BmClient ,
279+ makeConfig ( ) ,
280+ )
281+
282+ await engine . prepareSubagentSpawn ( {
283+ parentSessionKey : "parent-session" ,
284+ childSessionKey : "agent:test:subagent:child-complete" ,
285+ } )
286+
287+ await engine . onSubagentEnded ( {
288+ childSessionKey : "agent:test:subagent:child-complete" ,
289+ reason : "completed" ,
290+ } )
291+
292+ expect ( mockClient . editNote ) . toHaveBeenCalledWith (
293+ "agent/subagents/subagent-handoff-agent-test-subagent-child-1" ,
294+ "append" ,
295+ expect . stringContaining ( "Reason: completed" ) ,
296+ )
297+ expect ( mockClient . editNote ) . toHaveBeenCalledWith (
298+ "agent/subagents/subagent-handoff-agent-test-subagent-child-1" ,
299+ "append" ,
300+ expect . stringContaining ( "Durable conversation capture continues through the normal afterTurn path." ) ,
301+ )
302+ } )
303+
304+ it ( "handles deleted, released, and swept child endings without errors" , async ( ) => {
305+ const reasons = [ "deleted" , "released" , "swept" ] as const
306+
307+ for ( const reason of reasons ) {
308+ const engine = new BasicMemoryContextEngine (
309+ mockClient as unknown as BmClient ,
310+ makeConfig ( ) ,
311+ )
312+
313+ await engine . prepareSubagentSpawn ( {
314+ parentSessionKey : "parent-session" ,
315+ childSessionKey : `agent:test:subagent:${ reason } ` ,
316+ } )
317+
318+ await expect (
319+ engine . onSubagentEnded ( {
320+ childSessionKey : `agent:test:subagent:${ reason } ` ,
321+ reason,
322+ } ) ,
323+ ) . resolves . toBeUndefined ( )
324+ }
325+
326+ expect ( mockClient . editNote ) . toHaveBeenCalledTimes ( 3 )
327+ } )
328+
210329 it ( "captures only the current turn after prePromptMessageCount" , async ( ) => {
211330 const engine = new BasicMemoryContextEngine (
212331 mockClient as unknown as BmClient ,
0 commit comments