mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 21:04:33 +00:00
fix(memoryFlush): guard transcript-size forced flush against repeated runs (#32358)
The `forceFlushTranscriptBytes` path (introduced in d729ab21) bypasses the
`memoryFlushCompactionCount` guard that prevents repeated flushes within the
same compaction cycle. Once the session transcript exceeds 2 MB, memory flush
fires on every single message — even when token count is well under the
compaction threshold.
Extract `hasAlreadyFlushedForCurrentCompaction()` from the inline guard in
`shouldRunMemoryFlush` and apply it to both the token-based and the
transcript-size trigger paths.
Fixes #32317
Signed-off-by: HCL <chenglunhu@gmail.com>
This commit is contained in:
@@ -31,6 +31,7 @@ import {
|
|||||||
resolveModelFallbackOptions,
|
resolveModelFallbackOptions,
|
||||||
} from "./agent-runner-utils.js";
|
} from "./agent-runner-utils.js";
|
||||||
import {
|
import {
|
||||||
|
hasAlreadyFlushedForCurrentCompaction,
|
||||||
resolveMemoryFlushContextWindowTokens,
|
resolveMemoryFlushContextWindowTokens,
|
||||||
resolveMemoryFlushPromptForRun,
|
resolveMemoryFlushPromptForRun,
|
||||||
resolveMemoryFlushSettings,
|
resolveMemoryFlushSettings,
|
||||||
@@ -437,7 +438,9 @@ export async function runMemoryFlushIfNeeded(params: {
|
|||||||
reserveTokensFloor: memoryFlushSettings.reserveTokensFloor,
|
reserveTokensFloor: memoryFlushSettings.reserveTokensFloor,
|
||||||
softThresholdTokens: memoryFlushSettings.softThresholdTokens,
|
softThresholdTokens: memoryFlushSettings.softThresholdTokens,
|
||||||
})) ||
|
})) ||
|
||||||
shouldForceFlushByTranscriptSize;
|
(shouldForceFlushByTranscriptSize &&
|
||||||
|
entry != null &&
|
||||||
|
!hasAlreadyFlushedForCurrentCompaction(entry));
|
||||||
|
|
||||||
if (!shouldFlushMemory) {
|
if (!shouldFlushMemory) {
|
||||||
return entry ?? params.sessionEntry;
|
return entry ?? params.sessionEntry;
|
||||||
|
|||||||
@@ -161,11 +161,22 @@ export function shouldRunMemoryFlush(params: {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const compactionCount = params.entry.compactionCount ?? 0;
|
if (hasAlreadyFlushedForCurrentCompaction(params.entry)) {
|
||||||
const lastFlushAt = params.entry.memoryFlushCompactionCount;
|
|
||||||
if (typeof lastFlushAt === "number" && lastFlushAt === compactionCount) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true when a memory flush has already been performed for the current
|
||||||
|
* compaction cycle. This prevents repeated flush runs within the same cycle —
|
||||||
|
* important for both the token-based and transcript-size–based trigger paths.
|
||||||
|
*/
|
||||||
|
export function hasAlreadyFlushedForCurrentCompaction(
|
||||||
|
entry: Pick<SessionEntry, "compactionCount" | "memoryFlushCompactionCount">,
|
||||||
|
): boolean {
|
||||||
|
const compactionCount = entry.compactionCount ?? 0;
|
||||||
|
const lastFlushAt = entry.memoryFlushCompactionCount;
|
||||||
|
return typeof lastFlushAt === "number" && lastFlushAt === compactionCount;
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
DEFAULT_MEMORY_FLUSH_FORCE_TRANSCRIPT_BYTES,
|
DEFAULT_MEMORY_FLUSH_FORCE_TRANSCRIPT_BYTES,
|
||||||
DEFAULT_MEMORY_FLUSH_SOFT_TOKENS,
|
DEFAULT_MEMORY_FLUSH_SOFT_TOKENS,
|
||||||
|
hasAlreadyFlushedForCurrentCompaction,
|
||||||
resolveMemoryFlushContextWindowTokens,
|
resolveMemoryFlushContextWindowTokens,
|
||||||
resolveMemoryFlushSettings,
|
resolveMemoryFlushSettings,
|
||||||
shouldRunMemoryFlush,
|
shouldRunMemoryFlush,
|
||||||
@@ -350,6 +351,42 @@ describe("shouldRunMemoryFlush", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("hasAlreadyFlushedForCurrentCompaction", () => {
|
||||||
|
it("returns true when memoryFlushCompactionCount matches compactionCount", () => {
|
||||||
|
expect(
|
||||||
|
hasAlreadyFlushedForCurrentCompaction({
|
||||||
|
compactionCount: 3,
|
||||||
|
memoryFlushCompactionCount: 3,
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns false when memoryFlushCompactionCount differs", () => {
|
||||||
|
expect(
|
||||||
|
hasAlreadyFlushedForCurrentCompaction({
|
||||||
|
compactionCount: 3,
|
||||||
|
memoryFlushCompactionCount: 2,
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns false when memoryFlushCompactionCount is undefined", () => {
|
||||||
|
expect(
|
||||||
|
hasAlreadyFlushedForCurrentCompaction({
|
||||||
|
compactionCount: 1,
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("treats missing compactionCount as 0", () => {
|
||||||
|
expect(
|
||||||
|
hasAlreadyFlushedForCurrentCompaction({
|
||||||
|
memoryFlushCompactionCount: 0,
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("resolveMemoryFlushContextWindowTokens", () => {
|
describe("resolveMemoryFlushContextWindowTokens", () => {
|
||||||
it("falls back to agent config or default tokens", () => {
|
it("falls back to agent config or default tokens", () => {
|
||||||
expect(resolveMemoryFlushContextWindowTokens({ agentCfgContextTokens: 42_000 })).toBe(42_000);
|
expect(resolveMemoryFlushContextWindowTokens({ agentCfgContextTokens: 42_000 })).toBe(42_000);
|
||||||
|
|||||||
Reference in New Issue
Block a user