feat(memoryFlush): add resetSession option to reset session after memory flush
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.'
This commit is contained in:
parent
2a4ccb624a
commit
5b325ae413
@ -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<SessionEntry | undefined> {
|
||||
}): Promise<MemoryFlushResult> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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(),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user