diff --git a/extensions/memory-core/src/dreaming-narrative.ts b/extensions/memory-core/src/dreaming-narrative.ts index 2c0381b27fb..58b8ab74b6a 100644 --- a/extensions/memory-core/src/dreaming-narrative.ts +++ b/extensions/memory-core/src/dreaming-narrative.ts @@ -911,7 +911,7 @@ export async function generateAndAppendDreamNarrative(params: { `memory-core: narrative generation failed for ${params.data.phase} phase: ${formatErrorMessage(err)}`, ); } finally { - if (runId && waitStatus === "timeout") { + if (params.subagent && runId && waitStatus === "timeout") { try { const settle = await params.subagent.waitForRun({ runId, @@ -929,12 +929,15 @@ export async function generateAndAppendDreamNarrative(params: { } } - try { - await params.subagent.deleteSession({ sessionKey }); - } catch (cleanupErr) { - params.logger.warn( - `memory-core: narrative session cleanup failed for ${params.data.phase} phase: ${formatErrorMessage(cleanupErr)}`, - ); + // Guard against subagent becoming unavailable mid-flight (throws TypeError without this). + if (params.subagent) { + try { + await params.subagent.deleteSession({ sessionKey }); + } catch (cleanupErr) { + params.logger.warn( + `memory-core: narrative session cleanup failed for ${params.data.phase} phase: ${formatErrorMessage(cleanupErr)}`, + ); + } } await scrubDreamingNarrativeArtifacts(params.logger).catch((scrubErr: unknown) => { diff --git a/extensions/memory-core/src/dreaming-phases.ts b/extensions/memory-core/src/dreaming-phases.ts index 993129c7a70..99abdb14047 100644 --- a/extensions/memory-core/src/dreaming-phases.ts +++ b/extensions/memory-core/src/dreaming-phases.ts @@ -1641,6 +1641,9 @@ export async function runDreamingSweepPhases(params: { subagent?: Parameters[0]["subagent"]; nowMs?: number; }): Promise { + // Normalize nowMs once so all phase timestamps and narrative session keys are consistent. + const sweepNowMs = Number.isFinite(params.nowMs) ? params.nowMs : Date.now(); + const light = resolveMemoryLightDreamingConfig({ pluginConfig: params.pluginConfig, cfg: params.cfg, @@ -1652,8 +1655,18 @@ export async function runDreamingSweepPhases(params: { config: light, logger: params.logger, subagent: params.subagent, - nowMs: params.nowMs, + nowMs: sweepNowMs, }); + // Defensive cleanup: ensure the light-phase narrative session is deleted even if + // generateAndAppendDreamNarrative's primary cleanup was skipped due to an error. + if (params.subagent) { + const lightSessionKey = `dreaming-narrative-light-${sweepNowMs}`; + await params.subagent + .deleteSession({ sessionKey: lightSessionKey }) + .catch(() => { + // Swallow errors — this is best-effort cleanup. + }); + } } const rem = resolveMemoryRemDreamingConfig({ @@ -1667,8 +1680,17 @@ export async function runDreamingSweepPhases(params: { config: rem, logger: params.logger, subagent: params.subagent, - nowMs: params.nowMs, + nowMs: sweepNowMs, }); + // Defensive cleanup: ensure the REM-phase narrative session is deleted. + if (params.subagent) { + const remSessionKey = `dreaming-narrative-rem-${sweepNowMs}`; + await params.subagent + .deleteSession({ sessionKey: remSessionKey }) + .catch(() => { + // Swallow errors — this is best-effort cleanup. + }); + } } }