mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 20:17:20 +00:00
fix(compaction): memory flush never triggering due to threshold mismatch
Flush threshold (176K) was higher than compaction threshold (150K), so flush was always skipped. Removed independent threshold calculation — flush now always runs before compaction, gated only by enabled check and per-cycle dedup guard. Also localized hardcoded compaction tooltips (en/vi/zh), fixed inaccurate Memory Flush description, and removed dead reserveTokensFloor/softThreshold UI inputs.
This commit is contained in:
1 parent
9181eebcea
commit
2fed2a57d3
5 files changed
+41
-89
No files matched your search
@@ -21,16 +21,13 @@ const (
|
||||
DefaultMemoryFlushSystemPrompt = "Pre-compaction memory flush turn. " +
|
||||
"The session is near auto-compaction; capture durable memories to disk. " +
|
||||
"You may reply, but usually NO_REPLY is correct."
|
||||
|
||||
DefaultSoftThresholdTokens = 4000
|
||||
)
|
||||
|
||||
// MemoryFlushSettings holds resolved flush config with defaults applied.
|
||||
type MemoryFlushSettings struct {
|
||||
Enabled bool
|
||||
SoftThresholdTokens int
|
||||
Prompt string
|
||||
SystemPrompt string
|
||||
Enabled bool
|
||||
Prompt string
|
||||
SystemPrompt string
|
||||
}
|
||||
|
||||
// ResolveMemoryFlushSettings resolves flush settings from config, applying defaults.
|
||||
@@ -39,10 +36,9 @@ func ResolveMemoryFlushSettings(compaction *config.CompactionConfig) *MemoryFlus
|
||||
if compaction == nil || compaction.MemoryFlush == nil {
|
||||
// Default: enabled
|
||||
return &MemoryFlushSettings{
|
||||
Enabled: true,
|
||||
SoftThresholdTokens: DefaultSoftThresholdTokens,
|
||||
Prompt: DefaultMemoryFlushPrompt,
|
||||
SystemPrompt: DefaultMemoryFlushSystemPrompt,
|
||||
Enabled: true,
|
||||
Prompt: DefaultMemoryFlushPrompt,
|
||||
SystemPrompt: DefaultMemoryFlushSystemPrompt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,15 +48,11 @@ func ResolveMemoryFlushSettings(compaction *config.CompactionConfig) *MemoryFlus
|
||||
}
|
||||
|
||||
settings := &MemoryFlushSettings{
|
||||
Enabled: true,
|
||||
SoftThresholdTokens: DefaultSoftThresholdTokens,
|
||||
Prompt: DefaultMemoryFlushPrompt,
|
||||
SystemPrompt: DefaultMemoryFlushSystemPrompt,
|
||||
Enabled: true,
|
||||
Prompt: DefaultMemoryFlushPrompt,
|
||||
SystemPrompt: DefaultMemoryFlushSystemPrompt,
|
||||
}
|
||||
|
||||
if mf.SoftThresholdTokens > 0 {
|
||||
settings.SoftThresholdTokens = mf.SoftThresholdTokens
|
||||
}
|
||||
if mf.Prompt != "" {
|
||||
settings.Prompt = mf.Prompt
|
||||
}
|
||||
@@ -72,7 +64,8 @@ func ResolveMemoryFlushSettings(compaction *config.CompactionConfig) *MemoryFlus
|
||||
}
|
||||
|
||||
// shouldRunMemoryFlush checks whether a memory flush should run before compaction.
|
||||
// Matching TS memory-flush.ts:shouldRunMemoryFlush.
|
||||
// Flush always runs when compaction triggers (called inside maybeSummarize),
|
||||
// gated only by enabled/memory checks and a dedup guard per compaction cycle.
|
||||
func (l *Loop) shouldRunMemoryFlush(sessionKey string, totalTokens int, settings *MemoryFlushSettings) bool {
|
||||
if settings == nil || !settings.Enabled || !l.hasMemory {
|
||||
return false
|
||||
@@ -82,23 +75,7 @@ func (l *Loop) shouldRunMemoryFlush(sessionKey string, totalTokens int, settings
|
||||
return false
|
||||
}
|
||||
|
||||
// Threshold = contextWindow - reserveTokensFloor - softThresholdTokens
|
||||
// When totalTokens >= threshold → flush
|
||||
reserveFloor := 20000 // default
|
||||
if l.compactionCfg != nil && l.compactionCfg.ReserveTokensFloor > 0 {
|
||||
reserveFloor = l.compactionCfg.ReserveTokensFloor
|
||||
}
|
||||
|
||||
threshold := l.contextWindow - reserveFloor - settings.SoftThresholdTokens
|
||||
if threshold <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if totalTokens < threshold {
|
||||
return false
|
||||
}
|
||||
|
||||
// Deduplication: skip if already flushed in this compaction cycle
|
||||
// Deduplication: skip if already flushed in this compaction cycle.
|
||||
compactionCount := l.sessions.GetCompactionCount(sessionKey)
|
||||
lastFlushAt := l.sessions.GetMemoryFlushCompactionCount(sessionKey)
|
||||
if lastFlushAt >= 0 && lastFlushAt == compactionCount {
|
||||
|
||||
@@ -271,12 +271,14 @@
|
||||
"compaction": {
|
||||
"title": "Compaction",
|
||||
"description": "Context window compaction and memory flush settings",
|
||||
"reserveTokensFloor": "Reserve Tokens Floor",
|
||||
"maxHistoryShare": "Max History Share (0-1)",
|
||||
"maxHistoryShareTip": "Maximum fraction of context window for conversation history (e.g. 0.75 = 75%). Compaction triggers when exceeded.",
|
||||
"minMessages": "Min Messages",
|
||||
"minMessagesTip": "Minimum messages before compaction can trigger, regardless of token count.",
|
||||
"keepLastMessages": "Keep Last Messages",
|
||||
"keepLastMessagesTip": "Recent messages kept after compaction. Older messages are replaced by a summary.",
|
||||
"memoryFlush": "Memory Flush",
|
||||
"softThreshold": "Soft Threshold (tokens)"
|
||||
"memoryFlushTip": "Before compaction, the agent gets a turn to save important context to memory files. Also triggers Knowledge Graph extraction."
|
||||
},
|
||||
"contextPruning": {
|
||||
"title": "Context Pruning",
|
||||
|
||||
@@ -270,13 +270,15 @@
|
||||
},
|
||||
"compaction": {
|
||||
"title": "Nén ngữ cảnh",
|
||||
"description": "Cài đặt nén cửa sổ ngữ cảnh và xả bộ nhớ",
|
||||
"reserveTokensFloor": "Sàn token dự trữ",
|
||||
"description": "Cài đặt nén cửa sổ ngữ cảnh và ghi nhớ trước nén",
|
||||
"maxHistoryShare": "Phần lịch sử tối đa (0-1)",
|
||||
"maxHistoryShareTip": "Tỷ lệ tối đa của cửa sổ ngữ cảnh dành cho lịch sử hội thoại (vd: 0.75 = 75%). Nén kích hoạt khi vượt quá.",
|
||||
"minMessages": "Tin nhắn tối thiểu",
|
||||
"minMessagesTip": "Số tin nhắn tối thiểu trước khi nén có thể kích hoạt, bất kể số token.",
|
||||
"keepLastMessages": "Giữ tin nhắn cuối",
|
||||
"memoryFlush": "Xả bộ nhớ",
|
||||
"softThreshold": "Ngưỡng mềm (token)"
|
||||
"keepLastMessagesTip": "Số tin nhắn gần nhất giữ lại sau khi nén. Các tin nhắn cũ hơn được thay thế bằng bản tóm tắt.",
|
||||
"memoryFlush": "Ghi nhớ trước nén",
|
||||
"memoryFlushTip": "Trước khi nén, agent được một lượt để lưu ngữ cảnh quan trọng vào file bộ nhớ. Cũng kích hoạt trích xuất Knowledge Graph."
|
||||
},
|
||||
"contextPruning": {
|
||||
"title": "Cắt bớt ngữ cảnh",
|
||||
|
||||
@@ -270,13 +270,15 @@
|
||||
},
|
||||
"compaction": {
|
||||
"title": "压缩",
|
||||
"description": "上下文窗口压缩和内存刷新设置",
|
||||
"reserveTokensFloor": "保留令牌下限",
|
||||
"description": "上下文窗口压缩和压缩前记忆设置",
|
||||
"maxHistoryShare": "最大历史份额 (0-1)",
|
||||
"maxHistoryShareTip": "上下文窗口用于对话历史的最大比例(如0.75 = 75%)。超过时触发压缩。",
|
||||
"minMessages": "最小消息数",
|
||||
"minMessagesTip": "压缩触发前所需的最少消息数,与令牌数无关。",
|
||||
"keepLastMessages": "保留最后消息数",
|
||||
"memoryFlush": "内存刷新",
|
||||
"softThreshold": "软阈值(令牌)"
|
||||
"keepLastMessagesTip": "压缩后保留的最近消息数。旧消息被摘要替换。",
|
||||
"memoryFlush": "压缩前记忆",
|
||||
"memoryFlushTip": "压缩前,Agent可以将重要上下文保存到记忆文件。同时触发知识图谱提取。"
|
||||
},
|
||||
"contextPruning": {
|
||||
"title": "上下文裁剪",
|
||||
|
||||
@@ -23,16 +23,7 @@ export function CompactionSection({ enabled, value, onToggle, onChange }: Compac
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<InfoLabel tip="Minimum tokens reserved for the LLM response. Higher values give more room for output but reduce available context for history.">{t(`${s}.reserveTokensFloor`)}</InfoLabel>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="20000"
|
||||
value={value.reserveTokensFloor ?? ""}
|
||||
onChange={(e) => onChange({ ...value, reserveTokensFloor: numOrUndef(e.target.value) })}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<InfoLabel tip="Maximum fraction of the context window used for conversation history before compaction triggers (e.g. 0.75 = 75%).">{t(`${s}.maxHistoryShare`)}</InfoLabel>
|
||||
<InfoLabel tip={t(`${s}.maxHistoryShareTip`)}>{t(`${s}.maxHistoryShare`)}</InfoLabel>
|
||||
<Input
|
||||
type="number"
|
||||
step="0.05"
|
||||
@@ -41,10 +32,8 @@ export function CompactionSection({ enabled, value, onToggle, onChange }: Compac
|
||||
onChange={(e) => onChange({ ...value, maxHistoryShare: numOrUndef(e.target.value) })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<InfoLabel tip="Minimum number of messages in history before compaction can trigger, regardless of token usage.">{t(`${s}.minMessages`)}</InfoLabel>
|
||||
<InfoLabel tip={t(`${s}.minMessagesTip`)}>{t(`${s}.minMessages`)}</InfoLabel>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="50"
|
||||
@@ -52,8 +41,10 @@ export function CompactionSection({ enabled, value, onToggle, onChange }: Compac
|
||||
onChange={(e) => onChange({ ...value, minMessages: numOrUndef(e.target.value) })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<InfoLabel tip="Number of recent messages to preserve after compaction. The rest is replaced by a summary.">{t(`${s}.keepLastMessages`)}</InfoLabel>
|
||||
<InfoLabel tip={t(`${s}.keepLastMessagesTip`)}>{t(`${s}.keepLastMessages`)}</InfoLabel>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="4"
|
||||
@@ -62,36 +53,14 @@ export function CompactionSection({ enabled, value, onToggle, onChange }: Compac
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={value.memoryFlush?.enabled ?? true}
|
||||
onCheckedChange={(v) =>
|
||||
onChange({ ...value, memoryFlush: { ...value.memoryFlush, enabled: v } })
|
||||
}
|
||||
/>
|
||||
<InfoLabel tip="When enabled, compacted history is also saved to long-term memory for future retrieval.">{t(`${s}.memoryFlush`)}</InfoLabel>
|
||||
</div>
|
||||
{(value.memoryFlush?.enabled ?? true) && (
|
||||
<div className="space-y-2 pl-6">
|
||||
<InfoLabel tip="Token count threshold that triggers memory flush. When summary exceeds this, older memories are flushed to storage.">{t(`${s}.softThreshold`)}</InfoLabel>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="4000"
|
||||
value={value.memoryFlush?.softThresholdTokens ?? ""}
|
||||
onChange={(e) =>
|
||||
onChange({
|
||||
...value,
|
||||
memoryFlush: {
|
||||
...value.memoryFlush,
|
||||
enabled: true,
|
||||
softThresholdTokens: numOrUndef(e.target.value),
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={value.memoryFlush?.enabled ?? true}
|
||||
onCheckedChange={(v) =>
|
||||
onChange({ ...value, memoryFlush: { ...value.memoryFlush, enabled: v } })
|
||||
}
|
||||
/>
|
||||
<InfoLabel tip={t(`${s}.memoryFlushTip`)}>{t(`${s}.memoryFlush`)}</InfoLabel>
|
||||
</div>
|
||||
</ConfigSection>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user