mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-19 18:15:41 +00:00
refactor(compaction): remove minMessages guard, use token-only threshold
Message count-based compaction trigger (default 200) was a legacy safety net from when token estimation was unreliable. With calibrated overhead estimation using actual LLM PromptTokens, the token threshold alone is sufficient — matching industry standard (Claude Code, Anthropic API, LangChain all use token-only). Also fixes EstimateTokens vs EstimateHistoryTokens inconsistency in end-of-run overhead calculation (loop_history.go), and updates stale CLAUDE.md docs from 75% to 85%.
This commit is contained in:
1 parent
db03bf92e0
commit
0c6cd02285
12 files changed
+7
-36
No files matched your search
@@ -57,7 +57,7 @@ ui/web/ React SPA (pnpm, Vite, Tailwind, Radix UI)
|
||||
- **Agent types:** `open` (per-user context, 7 files) vs `predefined` (shared context + USER.md per-user)
|
||||
- **Context files:** `agent_context_files` (agent-level) + `user_context_files` (per-user), routed via `ContextFileInterceptor`
|
||||
- **Providers:** Anthropic (native HTTP+SSE), OpenAI-compat (HTTP+SSE), DashScope (Alibaba Qwen), Claude CLI (stdio+MCP bridge), ACP (Anthropic Console Proxy), Codex (OpenAI). All use `RetryDo()` for retries. Loads from `llm_providers` table with encrypted API keys
|
||||
- **Agent loop:** `RunRequest` → think→act→observe → `RunResult`. Events: `run.started`, `run.completed`, `chunk`, `tool.call`, `tool.result`. Auto-summarization at >75% context
|
||||
- **Agent loop:** `RunRequest` → think→act→observe → `RunResult`. Events: `run.started`, `run.completed`, `chunk`, `tool.call`, `tool.result`. Auto-summarization at >85% context (token-based only)
|
||||
- **Context propagation:** `store.WithAgentType(ctx)`, `store.WithUserID(ctx)`, `store.WithAgentID(ctx)`, `store.WithLocale(ctx)`
|
||||
- **WebSocket protocol (v3):** Frame types `req`/`res`/`event`. First request must be `connect`
|
||||
- **Config:** JSON5 at `GOCLAW_CONFIG` env. Secrets in `.env.local` or env vars, never in config.json
|
||||
|
||||
@@ -553,18 +553,15 @@ func (l *Loop) maybeSummarize(ctx context.Context, sessionKey string) {
|
||||
}
|
||||
tokenEstimate := EstimateTokensWithCalibration(history, adjustedLastPT, lastMC)
|
||||
|
||||
// Resolve compaction thresholds from config with sensible defaults.
|
||||
// Resolve compaction threshold from config: token-only (no message count guard).
|
||||
// Industry standard — Claude Code, Anthropic API, LangChain all use token-based thresholds.
|
||||
historyShare := config.DefaultHistoryShare
|
||||
if l.compactionCfg != nil && l.compactionCfg.MaxHistoryShare > 0 {
|
||||
historyShare = l.compactionCfg.MaxHistoryShare
|
||||
}
|
||||
minMessages := 200
|
||||
if l.compactionCfg != nil && l.compactionCfg.MinMessages > 0 {
|
||||
minMessages = l.compactionCfg.MinMessages
|
||||
}
|
||||
|
||||
threshold := int(float64(l.contextWindow) * historyShare)
|
||||
if len(history) <= minMessages && tokenEstimate <= threshold {
|
||||
if tokenEstimate <= threshold {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -681,7 +678,7 @@ func (l *Loop) estimateOverhead(history []providers.Message, lastPromptTokens, l
|
||||
if count > len(history) {
|
||||
count = len(history)
|
||||
}
|
||||
historyEstAtCalibration := EstimateTokens(history[:count])
|
||||
historyEstAtCalibration := EstimateHistoryTokens(history[:count])
|
||||
overhead := lastPromptTokens - historyEstAtCalibration
|
||||
if overhead < 0 {
|
||||
overhead = 0
|
||||
|
||||
@@ -133,8 +133,7 @@ type AgentDefaults struct {
|
||||
// Matching TS agents.defaults.compaction.
|
||||
type CompactionConfig struct {
|
||||
ReserveTokensFloor int `json:"reserveTokensFloor,omitempty"` // min reserve tokens (default 20000)
|
||||
MaxHistoryShare float64 `json:"maxHistoryShare,omitempty"` // max share of context for history (default 0.75)
|
||||
MinMessages int `json:"minMessages,omitempty"` // min messages before compaction triggers (default 200)
|
||||
MaxHistoryShare float64 `json:"maxHistoryShare,omitempty"` // max share of context for history (default 0.85)
|
||||
KeepLastMessages int `json:"keepLastMessages,omitempty"` // messages to keep after compaction (default 4)
|
||||
MemoryFlush *MemoryFlushConfig `json:"memoryFlush,omitempty"` // pre-compaction flush
|
||||
}
|
||||
|
||||
@@ -344,8 +344,6 @@
|
||||
"description": "Context window compaction and memory flush settings",
|
||||
"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",
|
||||
|
||||
@@ -344,8 +344,6 @@
|
||||
"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",
|
||||
"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",
|
||||
|
||||
@@ -344,8 +344,6 @@
|
||||
"description": "上下文窗口压缩和压缩前记忆设置",
|
||||
"maxHistoryShare": "最大历史份额 (0-1)",
|
||||
"maxHistoryShareTip": "上下文窗口用于对话历史的最大比例(如0.75 = 75%)。超过时触发压缩。",
|
||||
"minMessages": "最小消息数",
|
||||
"minMessagesTip": "压缩触发前所需的最少消息数,与令牌数无关。",
|
||||
"keepLastMessages": "保留最后消息数",
|
||||
"keepLastMessagesTip": "压缩后保留的最近消息数。旧消息被摘要替换。",
|
||||
"memoryFlush": "压缩前记忆",
|
||||
|
||||
@@ -17,7 +17,6 @@ export interface MemoryConfig {
|
||||
export interface CompactionConfig {
|
||||
reserveTokensFloor?: number
|
||||
maxHistoryShare?: number
|
||||
minMessages?: number
|
||||
keepLastMessages?: number
|
||||
memoryFlush?: {
|
||||
enabled?: boolean
|
||||
|
||||
@@ -591,8 +591,6 @@
|
||||
"description": "Context window compaction and memory flush settings",
|
||||
"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",
|
||||
|
||||
@@ -591,8 +591,6 @@
|
||||
"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",
|
||||
"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",
|
||||
|
||||
@@ -591,8 +591,6 @@
|
||||
"description": "上下文窗口压缩和压缩前记忆设置",
|
||||
"maxHistoryShare": "最大历史份额 (0-1)",
|
||||
"maxHistoryShareTip": "上下文窗口用于对话历史的最大比例(如0.75 = 75%)。超过时触发压缩。",
|
||||
"minMessages": "最小消息数",
|
||||
"minMessagesTip": "压缩触发前所需的最少消息数,与令牌数无关。",
|
||||
"keepLastMessages": "保留最后消息数",
|
||||
"keepLastMessagesTip": "压缩后保留的最近消息数。旧消息被摘要替换。",
|
||||
"memoryFlush": "压缩前记忆",
|
||||
|
||||
@@ -25,22 +25,11 @@ export function CompactionSection({ value, onChange }: CompactionSectionProps) {
|
||||
<Input
|
||||
type="number"
|
||||
step="0.05"
|
||||
placeholder="0.75"
|
||||
placeholder="0.85"
|
||||
value={value.maxHistoryShare ?? ""}
|
||||
onChange={(e) => onChange({ ...value, maxHistoryShare: numOrUndef(e.target.value) })}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<InfoLabel tip={t(`${s}.minMessagesTip`)}>{t(`${s}.minMessages`)}</InfoLabel>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="50"
|
||||
value={value.minMessages ?? ""}
|
||||
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={t(`${s}.keepLastMessagesTip`)}>{t(`${s}.keepLastMessages`)}</InfoLabel>
|
||||
<Input
|
||||
|
||||
@@ -22,7 +22,6 @@ export interface SubagentsConfig {
|
||||
export interface CompactionConfig {
|
||||
reserveTokensFloor?: number;
|
||||
maxHistoryShare?: number;
|
||||
minMessages?: number;
|
||||
keepLastMessages?: number;
|
||||
memoryFlush?: {
|
||||
enabled?: boolean;
|
||||
|
||||
Reference in new issue
Block a user