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:
viettranx committed 2026-03-29 10:14:52 +07:00
1 parent db03bf92e0
commit 0c6cd02285
12 files changed
+7 -36

No files matched your search

+1 -1
View File
@@ -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
+4 -7
View File
@@ -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
+1 -2
View File
@@ -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": "压缩前记忆",
-1
View File
@@ -17,7 +17,6 @@ export interface MemoryConfig {
export interface CompactionConfig {
reserveTokensFloor?: number
maxHistoryShare?: number
minMessages?: number
keepLastMessages?: number
memoryFlush?: {
enabled?: boolean
-2
View File
@@ -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",
-2
View File
@@ -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",
-2
View File
@@ -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
-1
View File
@@ -22,7 +22,6 @@ export interface SubagentsConfig {
export interface CompactionConfig {
reserveTokensFloor?: number;
maxHistoryShare?: number;
minMessages?: number;
keepLastMessages?: number;
memoryFlush?: {
enabled?: boolean;