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 type { GetReplyOptions } from "../types.js";
|
||||||
import { buildThreadingToolContext, resolveEnforceFinalTag } from "./agent-runner-utils.js";
|
import { buildThreadingToolContext, resolveEnforceFinalTag } from "./agent-runner-utils.js";
|
||||||
import {
|
import {
|
||||||
|
type MemoryFlushResult,
|
||||||
resolveMemoryFlushContextWindowTokens,
|
resolveMemoryFlushContextWindowTokens,
|
||||||
resolveMemoryFlushSettings,
|
resolveMemoryFlushSettings,
|
||||||
shouldRunMemoryFlush,
|
shouldRunMemoryFlush,
|
||||||
@ -37,9 +38,9 @@ export async function runMemoryFlushIfNeeded(params: {
|
|||||||
sessionKey?: string;
|
sessionKey?: string;
|
||||||
storePath?: string;
|
storePath?: string;
|
||||||
isHeartbeat: boolean;
|
isHeartbeat: boolean;
|
||||||
}): Promise<SessionEntry | undefined> {
|
}): Promise<MemoryFlushResult> {
|
||||||
const memoryFlushSettings = resolveMemoryFlushSettings(params.cfg);
|
const memoryFlushSettings = resolveMemoryFlushSettings(params.cfg);
|
||||||
if (!memoryFlushSettings) return params.sessionEntry;
|
if (!memoryFlushSettings) return { sessionEntry: params.sessionEntry, shouldResetSession: false };
|
||||||
|
|
||||||
const memoryFlushWritable = (() => {
|
const memoryFlushWritable = (() => {
|
||||||
if (!params.sessionKey) return true;
|
if (!params.sessionKey) return true;
|
||||||
@ -69,7 +70,7 @@ export async function runMemoryFlushIfNeeded(params: {
|
|||||||
softThresholdTokens: memoryFlushSettings.softThresholdTokens,
|
softThresholdTokens: memoryFlushSettings.softThresholdTokens,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!shouldFlushMemory) return params.sessionEntry;
|
if (!shouldFlushMemory) return { sessionEntry: params.sessionEntry, shouldResetSession: false };
|
||||||
|
|
||||||
let activeSessionEntry = params.sessionEntry;
|
let activeSessionEntry = params.sessionEntry;
|
||||||
const activeSessionStore = params.sessionStore;
|
const activeSessionStore = params.sessionStore;
|
||||||
@ -182,7 +183,12 @@ export async function runMemoryFlushIfNeeded(params: {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logVerbose(`memory flush run failed: ${String(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();
|
await typingSignals.signalRunStart();
|
||||||
|
|
||||||
activeSessionEntry = await runMemoryFlushIfNeeded({
|
const memoryFlushResult = await runMemoryFlushIfNeeded({
|
||||||
cfg,
|
cfg,
|
||||||
followupRun,
|
followupRun,
|
||||||
sessionCtx,
|
sessionCtx,
|
||||||
@ -212,6 +212,48 @@ export async function runReplyAgent(params: {
|
|||||||
storePath,
|
storePath,
|
||||||
isHeartbeat,
|
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({
|
const runFollowupTurn = createFollowupRunner({
|
||||||
opts,
|
opts,
|
||||||
@ -495,7 +537,9 @@ export async function runReplyAgent(params: {
|
|||||||
finalPayloads = [{ text: `🧹 Auto-compaction complete${suffix}.` }, ...finalPayloads];
|
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];
|
finalPayloads = [{ text: `🧭 New session: ${followupRun.run.sessionId}` }, ...finalPayloads];
|
||||||
}
|
}
|
||||||
if (responseUsageLine) {
|
if (responseUsageLine) {
|
||||||
|
|||||||
@ -5,6 +5,12 @@ import type { ClawdbotConfig } from "../../config/config.js";
|
|||||||
import type { SessionEntry } from "../../config/sessions.js";
|
import type { SessionEntry } from "../../config/sessions.js";
|
||||||
import { SILENT_REPLY_TOKEN } from "../tokens.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_SOFT_TOKENS = 4000;
|
||||||
|
|
||||||
export const DEFAULT_MEMORY_FLUSH_PROMPT = [
|
export const DEFAULT_MEMORY_FLUSH_PROMPT = [
|
||||||
@ -25,6 +31,8 @@ export type MemoryFlushSettings = {
|
|||||||
prompt: string;
|
prompt: string;
|
||||||
systemPrompt: string;
|
systemPrompt: string;
|
||||||
reserveTokensFloor: number;
|
reserveTokensFloor: number;
|
||||||
|
/** Reset the session after memory flush instead of continuing to compaction. */
|
||||||
|
resetSession: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizeNonNegativeInt = (value: unknown): number | null => {
|
const normalizeNonNegativeInt = (value: unknown): number | null => {
|
||||||
@ -45,12 +53,15 @@ export function resolveMemoryFlushSettings(cfg?: ClawdbotConfig): MemoryFlushSet
|
|||||||
normalizeNonNegativeInt(cfg?.agents?.defaults?.compaction?.reserveTokensFloor) ??
|
normalizeNonNegativeInt(cfg?.agents?.defaults?.compaction?.reserveTokensFloor) ??
|
||||||
DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR;
|
DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR;
|
||||||
|
|
||||||
|
const resetSession = defaults?.resetSession ?? false;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
enabled,
|
enabled,
|
||||||
softThresholdTokens,
|
softThresholdTokens,
|
||||||
prompt: ensureNoReplyHint(prompt),
|
prompt: ensureNoReplyHint(prompt),
|
||||||
systemPrompt: ensureNoReplyHint(systemPrompt),
|
systemPrompt: ensureNoReplyHint(systemPrompt),
|
||||||
reserveTokensFloor,
|
reserveTokensFloor,
|
||||||
|
resetSession,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -257,4 +257,6 @@ export type AgentCompactionMemoryFlushConfig = {
|
|||||||
prompt?: string;
|
prompt?: string;
|
||||||
/** System prompt appended for the memory flush turn. */
|
/** System prompt appended for the memory flush turn. */
|
||||||
systemPrompt?: string;
|
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(),
|
softThresholdTokens: z.number().int().nonnegative().optional(),
|
||||||
prompt: z.string().optional(),
|
prompt: z.string().optional(),
|
||||||
systemPrompt: z.string().optional(),
|
systemPrompt: z.string().optional(),
|
||||||
|
/** Reset the session after memory flush instead of continuing to compaction. */
|
||||||
|
resetSession: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.optional(),
|
.optional(),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user