From 466cf9ca8d7c58128ef65d920daaa56a28f5e043 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Tue, 23 Jun 2026 09:12:28 -0700 Subject: [PATCH] fix(engine): dispose completed spawned child sessions --- .changeset/dispose-spawned-child-sessions.md | 5 +++++ .../engine/src/__tests__/executor-pause.test.ts | 7 +++++-- packages/engine/src/executor.ts | 13 ++++++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 .changeset/dispose-spawned-child-sessions.md diff --git a/.changeset/dispose-spawned-child-sessions.md b/.changeset/dispose-spawned-child-sessions.md new file mode 100644 index 0000000000..ed32d3ee22 --- /dev/null +++ b/.changeset/dispose-spawned-child-sessions.md @@ -0,0 +1,5 @@ +--- +"@runfusion/fusion": patch +--- + +Dispose completed spawned child agent sessions so execution memory is released promptly after `fn_spawn_agent` children finish. diff --git a/packages/engine/src/__tests__/executor-pause.test.ts b/packages/engine/src/__tests__/executor-pause.test.ts index 491dffefae..793c39c83a 100644 --- a/packages/engine/src/__tests__/executor-pause.test.ts +++ b/packages/engine/src/__tests__/executor-pause.test.ts @@ -709,7 +709,8 @@ describe("Agent Spawning - runSpawnedChild", () => { // Should transition: running → active expect(agentStore.updateAgentState).toHaveBeenCalledWith("agent-test", "running"); expect(agentStore.updateAgentState).toHaveBeenCalledWith("agent-test", "active"); - // Should clean up + // Should clean up and release session resources + expect(mockSession.dispose).toHaveBeenCalledOnce(); expect(internals.childSessions.has("agent-test")).toBe(false); expect(internals.totalSpawnedCount).toBe(0); }); @@ -732,7 +733,8 @@ describe("Agent Spawning - runSpawnedChild", () => { expect(agentStore.updateAgentState).toHaveBeenCalledWith("agent-test", "running"); expect(agentStore.updateAgentState).toHaveBeenCalledWith("agent-test", "error"); - // Should still clean up + // Should still clean up and release session resources + expect(mockSession.dispose).toHaveBeenCalledOnce(); expect(internals.childSessions.has("agent-test")).toBe(false); expect(internals.totalSpawnedCount).toBe(0); }); @@ -751,6 +753,7 @@ describe("Agent Spawning - runSpawnedChild", () => { // Should not throw even when state updates fail await internals.runSpawnedChild("agent-test", mockSession, "Do the research"); + expect(mockSession.dispose).toHaveBeenCalledOnce(); expect(internals.childSessions.has("agent-test")).toBe(false); expect(internals.totalSpawnedCount).toBe(0); }); diff --git a/packages/engine/src/executor.ts b/packages/engine/src/executor.ts index 9a50ecd15f..dc81084ff3 100644 --- a/packages/engine/src/executor.ts +++ b/packages/engine/src/executor.ts @@ -15607,7 +15607,18 @@ You have access to the file system to review changes.${verdictBlock}`; const errorMessage = err instanceof Error ? err.message : String(err); executorLog.warn(`Child agent ${agentId} failed: ${errorMessage}`); } finally { - this.childSessions.delete(agentId); + /* + FNXC:AgentSpawning 2026-06-23-12:25: + Server memory must return to baseline after spawned child execution. A normally completed child session owns provider/runtime state until disposed; deleting it from childSessions first makes later parent cleanup unable to reach it. + */ + if (this.childSessions.get(agentId) === childSession) { + try { + childSession.dispose(); + } catch (disposeErr) { + executorLog.warn(`Child agent ${agentId} session dispose failed: ${disposeErr instanceof Error ? disposeErr.message : String(disposeErr)}`); + } + this.childSessions.delete(agentId); + } this.totalSpawnedCount = Math.max(0, this.totalSpawnedCount - 1); } }