From 5b325ae413f8d4ddc94b2587aa9a635c66cd8c0f Mon Sep 17 00:00:00 2001 From: Kai Valo Date: Mon, 26 Jan 2026 15:09:48 +0000 Subject: [PATCH] feat(memoryFlush): add resetSession option to reset session after memory flush MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new 'resetSession' config option to memoryFlush that, when enabled, resets the session after the memory flush turn completes instead of continuing to auto-compaction. This allows agents to: 1. Write both daily notes (historical log) AND a handover file (minimum viable memory) 2. Get a fresh session that loads only the curated handover context 3. Continue work with lean, focused context instead of compacted history Config example: ```yaml agents: defaults: compaction: memoryFlush: enabled: true softThresholdTokens: 90000 resetSession: true prompt: 'Write handover to memory/handover.md...' ``` When resetSession is true: - Memory flush turn runs as normal - Agent writes its memory/handover files - Session is reset (new session ID, fresh transcript) - Next turn starts fresh, loading handover.md via AGENTS.md - Verbose mode shows '๐Ÿ“ Memory flush complete. Session reset.' --- src/auto-reply/reply/agent-runner-memory.ts | 14 ++++-- src/auto-reply/reply/agent-runner.ts | 48 ++++++++++++++++++++- src/auto-reply/reply/memory-flush.ts | 11 +++++ src/config/types.agent-defaults.ts | 2 + src/config/zod-schema.agent-defaults.ts | 2 + 5 files changed, 71 insertions(+), 6 deletions(-) diff --git a/src/auto-reply/reply/agent-runner-memory.ts b/src/auto-reply/reply/agent-runner-memory.ts index a7d590750..728586480 100644 --- a/src/auto-reply/reply/agent-runner-memory.ts +++ b/src/auto-reply/reply/agent-runner-memory.ts @@ -17,6 +17,7 @@ import type { VerboseLevel } from "../thinking.js"; import type { GetReplyOptions } from "../types.js"; import { buildThreadingToolContext, resolveEnforceFinalTag } from "./agent-runner-utils.js"; import { + type MemoryFlushResult, resolveMemoryFlushContextWindowTokens, resolveMemoryFlushSettings, shouldRunMemoryFlush, @@ -37,9 +38,9 @@ export async function runMemoryFlushIfNeeded(params: { sessionKey?: string; storePath?: string; isHeartbeat: boolean; -}): Promise { +}): Promise { const memoryFlushSettings = resolveMemoryFlushSettings(params.cfg); - if (!memoryFlushSettings) return params.sessionEntry; + if (!memoryFlushSettings) return { sessionEntry: params.sessionEntry, shouldResetSession: false }; const memoryFlushWritable = (() => { if (!params.sessionKey) return true; @@ -69,7 +70,7 @@ export async function runMemoryFlushIfNeeded(params: { softThresholdTokens: memoryFlushSettings.softThresholdTokens, }); - if (!shouldFlushMemory) return params.sessionEntry; + if (!shouldFlushMemory) return { sessionEntry: params.sessionEntry, shouldResetSession: false }; let activeSessionEntry = params.sessionEntry; const activeSessionStore = params.sessionStore; @@ -182,7 +183,12 @@ export async function runMemoryFlushIfNeeded(params: { } } catch (err) { logVerbose(`memory flush run failed: ${String(err)}`); + // On failure, don't reset session + return { sessionEntry: activeSessionEntry, shouldResetSession: false }; } - return activeSessionEntry; + return { + sessionEntry: activeSessionEntry, + shouldResetSession: memoryFlushSettings.resetSession, + }; } diff --git a/src/auto-reply/reply/agent-runner.ts b/src/auto-reply/reply/agent-runner.ts index 227e6f17e..08550f85f 100644 --- a/src/auto-reply/reply/agent-runner.ts +++ b/src/auto-reply/reply/agent-runner.ts @@ -198,7 +198,7 @@ export async function runReplyAgent(params: { await typingSignals.signalRunStart(); - activeSessionEntry = await runMemoryFlushIfNeeded({ + const memoryFlushResult = await runMemoryFlushIfNeeded({ cfg, followupRun, sessionCtx, @@ -212,6 +212,48 @@ export async function runReplyAgent(params: { storePath, isHeartbeat, }); + activeSessionEntry = memoryFlushResult.sessionEntry; + + // Handle session reset after memory flush if configured + let memoryFlushResetCompleted = false; + if (memoryFlushResult.shouldResetSession && sessionKey && activeSessionStore && storePath) { + const prevEntry = activeSessionStore[sessionKey] ?? activeSessionEntry; + if (prevEntry) { + const nextSessionId = crypto.randomUUID(); + const agentId = resolveAgentIdFromSessionKey(sessionKey); + const nextSessionFile = resolveSessionTranscriptPath( + nextSessionId, + agentId, + sessionCtx.MessageThreadId, + ); + const nextEntry: SessionEntry = { + ...prevEntry, + sessionId: nextSessionId, + updatedAt: Date.now(), + systemSent: false, + abortedLastRun: false, + sessionFile: nextSessionFile, + }; + activeSessionStore[sessionKey] = nextEntry; + try { + await updateSessionStore(storePath, (store) => { + store[sessionKey] = nextEntry; + }); + followupRun.run.sessionId = nextSessionId; + followupRun.run.sessionFile = nextSessionFile; + activeSessionEntry = nextEntry; + activeIsNewSession = true; + memoryFlushResetCompleted = true; + defaultRuntime.error( + `Memory flush complete. Session reset: ${sessionKey} -> ${nextSessionId}`, + ); + } catch (err) { + defaultRuntime.error( + `Failed to persist session reset after memory flush (${sessionKey}): ${String(err)}`, + ); + } + } + } const runFollowupTurn = createFollowupRunner({ opts, @@ -495,7 +537,9 @@ export async function runReplyAgent(params: { finalPayloads = [{ text: `๐Ÿงน Auto-compaction complete${suffix}.` }, ...finalPayloads]; } } - if (verboseEnabled && activeIsNewSession) { + if (memoryFlushResetCompleted && verboseEnabled) { + finalPayloads = [{ text: `๐Ÿ“ Memory flush complete. Session reset.` }, ...finalPayloads]; + } else if (verboseEnabled && activeIsNewSession) { finalPayloads = [{ text: `๐Ÿงญ New session: ${followupRun.run.sessionId}` }, ...finalPayloads]; } if (responseUsageLine) { diff --git a/src/auto-reply/reply/memory-flush.ts b/src/auto-reply/reply/memory-flush.ts index 867f7da88..e42b7eb06 100644 --- a/src/auto-reply/reply/memory-flush.ts +++ b/src/auto-reply/reply/memory-flush.ts @@ -5,6 +5,12 @@ import type { ClawdbotConfig } from "../../config/config.js"; import type { SessionEntry } from "../../config/sessions.js"; import { SILENT_REPLY_TOKEN } from "../tokens.js"; +export type MemoryFlushResult = { + sessionEntry: SessionEntry | undefined; + /** True if the session should be reset after memory flush. */ + shouldResetSession: boolean; +}; + export const DEFAULT_MEMORY_FLUSH_SOFT_TOKENS = 4000; export const DEFAULT_MEMORY_FLUSH_PROMPT = [ @@ -25,6 +31,8 @@ export type MemoryFlushSettings = { prompt: string; systemPrompt: string; reserveTokensFloor: number; + /** Reset the session after memory flush instead of continuing to compaction. */ + resetSession: boolean; }; const normalizeNonNegativeInt = (value: unknown): number | null => { @@ -45,12 +53,15 @@ export function resolveMemoryFlushSettings(cfg?: ClawdbotConfig): MemoryFlushSet normalizeNonNegativeInt(cfg?.agents?.defaults?.compaction?.reserveTokensFloor) ?? DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR; + const resetSession = defaults?.resetSession ?? false; + return { enabled, softThresholdTokens, prompt: ensureNoReplyHint(prompt), systemPrompt: ensureNoReplyHint(systemPrompt), reserveTokensFloor, + resetSession, }; } diff --git a/src/config/types.agent-defaults.ts b/src/config/types.agent-defaults.ts index 2a42d3623..a05e023ac 100644 --- a/src/config/types.agent-defaults.ts +++ b/src/config/types.agent-defaults.ts @@ -257,4 +257,6 @@ export type AgentCompactionMemoryFlushConfig = { prompt?: string; /** System prompt appended for the memory flush turn. */ systemPrompt?: string; + /** Reset the session after memory flush instead of continuing to compaction (default: false). */ + resetSession?: boolean; }; diff --git a/src/config/zod-schema.agent-defaults.ts b/src/config/zod-schema.agent-defaults.ts index c4b8a8f2c..fb4d408fa 100644 --- a/src/config/zod-schema.agent-defaults.ts +++ b/src/config/zod-schema.agent-defaults.ts @@ -96,6 +96,8 @@ export const AgentDefaultsSchema = z softThresholdTokens: z.number().int().nonnegative().optional(), prompt: z.string().optional(), systemPrompt: z.string().optional(), + /** Reset the session after memory flush instead of continuing to compaction. */ + resetSession: z.boolean().optional(), }) .strict() .optional(),