fix(agent): forceCompression must not assume history[0] is system prompt

Session history (GetHistory) contains only user/assistant/tool messages.
The system prompt is built dynamically by BuildMessages and is never
stored in session. The previous code incorrectly treated history[0] as
a system prompt, skipping the first user message and appending a
compression note to it.

Fix: operate on the full history slice, and record the compression
note in the session summary (which BuildMessages already injects into
the system prompt) rather than modifying any history message.
This commit is contained in:
xiaoen 2026-03-13 15:13:04 +08:00
parent 9c82b0baa2
commit 9c65d78b07

View file

@ -1556,56 +1556,47 @@ func (al *AgentLoop) maybeSummarize(agent *AgentInstance, sessionKey, channel, c
} }
// forceCompression aggressively reduces context when the limit is hit. // forceCompression aggressively reduces context when the limit is hit.
// It drops the oldest ~50% of messages (keeping system prompt and last user message), // It drops the oldest ~50% of messages, aligning the split to a safe
// aligning the split to a safe boundary so tool-call sequences stay intact. // boundary so tool-call sequences stay intact.
//
// Session history contains only user/assistant/tool messages — the system
// prompt is built dynamically by BuildMessages and is NOT stored here.
// The compression note is recorded in the session summary so that
// BuildMessages can include it in the next system prompt.
func (al *AgentLoop) forceCompression(agent *AgentInstance, sessionKey string) { func (al *AgentLoop) forceCompression(agent *AgentInstance, sessionKey string) {
history := agent.Sessions.GetHistory(sessionKey) history := agent.Sessions.GetHistory(sessionKey)
if len(history) <= 4 { if len(history) <= 2 {
return
}
// Keep system prompt (usually [0]) and the very last message (user's trigger)
// We want to drop the oldest half of the *conversation*
// Assuming [0] is system, [1:] is conversation
conversation := history[1 : len(history)-1]
if len(conversation) == 0 {
return return
} }
// Find a safe mid-point that does not split a tool-call sequence. // Find a safe mid-point that does not split a tool-call sequence.
mid := findSafeBoundary(conversation, len(conversation)/2) mid := findSafeBoundary(history, len(history)/2)
if mid <= 0 {
// New history structure: return
// 1. System Prompt (with compression note appended) }
// 2. Second half of conversation
// 3. Last message
droppedCount := mid droppedCount := mid
keptConversation := conversation[mid:] keptHistory := history[mid:]
newHistory := make([]providers.Message, 0, 1+len(keptConversation)+1) // Record compression in the session summary so BuildMessages includes it
// in the system prompt. We do not modify history messages themselves.
// Append compression note to the original system prompt instead of adding a new system message existingSummary := agent.Sessions.GetSummary(sessionKey)
// This avoids having two consecutive system messages which some APIs (like Zhipu) reject
compressionNote := fmt.Sprintf( compressionNote := fmt.Sprintf(
"\n\n[System Note: Emergency compression dropped %d oldest messages due to context limit]", "[Emergency compression dropped %d oldest messages due to context limit]",
droppedCount, droppedCount,
) )
enhancedSystemPrompt := history[0] if existingSummary != "" {
enhancedSystemPrompt.Content = enhancedSystemPrompt.Content + compressionNote compressionNote = existingSummary + "\n\n" + compressionNote
newHistory = append(newHistory, enhancedSystemPrompt) }
agent.Sessions.SetSummary(sessionKey, compressionNote)
newHistory = append(newHistory, keptConversation...) agent.Sessions.SetHistory(sessionKey, keptHistory)
newHistory = append(newHistory, history[len(history)-1]) // Last message
// Update session
agent.Sessions.SetHistory(sessionKey, newHistory)
agent.Sessions.Save(sessionKey) agent.Sessions.Save(sessionKey)
logger.WarnCF("agent", "Forced compression executed", map[string]any{ logger.WarnCF("agent", "Forced compression executed", map[string]any{
"session_key": sessionKey, "session_key": sessionKey,
"dropped_msgs": droppedCount, "dropped_msgs": droppedCount,
"new_count": len(newHistory), "new_count": len(keptHistory),
}) })
} }