From 95847bca97838cf25b0c43c2bbf022c40e58785b Mon Sep 17 00:00:00 2001 From: YS Liu Date: Wed, 25 Feb 2026 22:55:01 +0800 Subject: [PATCH] feat(session): read-time truncation only, preserve full history on disk - summarizeSession: remove TruncateHistory(sessionKey, 4); keep SetSummary + Save - forceCompression: return []providers.Message, do not SetHistory/Save; use for retry only - runLLMIteration: use forceCompression return value for BuildMessages, no disk write - Process: cap history to maxHistoryMessagesForContext (200) before BuildMessages - session.Save: optional cap at maxStoredMessages (500) when writing to limit file size Session file no longer truncated to last 4 messages; truncation happens only when building context for the LLM (read-time), aligning with OpenClaw behavior. Co-authored-by: Cursor --- pkg/agent/loop.go | 28 ++++++++++++++++------------ pkg/session/manager.go | 12 ++++++++++-- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index dbc4a9b87..18d3d6dbe 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -29,6 +29,10 @@ import ( "github.com/sipeed/picoclaw/pkg/utils" ) +// maxHistoryMessagesForContext is the maximum number of history messages to send to the LLM. +// Read-time cap only; session storage keeps full history. +const maxHistoryMessagesForContext = 200 + type AgentLoop struct { bus *bus.MessageBus cfg *config.Config @@ -413,6 +417,10 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, agent *AgentInstance, opt if !opts.NoHistory { history = agent.Sessions.GetHistory(opts.SessionKey) summary = agent.Sessions.GetSummary(opts.SessionKey) + // Read-time cap: only the last N messages go to the LLM; session storage is unchanged. + if len(history) > maxHistoryMessagesForContext { + history = history[len(history)-maxHistoryMessagesForContext:] + } } messages := agent.ContextBuilder.BuildMessages( history, @@ -573,11 +581,10 @@ func (al *AgentLoop) runLLMIteration( }) } - al.forceCompression(agent, opts.SessionKey) - newHistory := agent.Sessions.GetHistory(opts.SessionKey) + compressed := al.forceCompression(agent, opts.SessionKey) newSummary := agent.Sessions.GetSummary(opts.SessionKey) messages = agent.ContextBuilder.BuildMessages( - newHistory, newSummary, "", + compressed, newSummary, "", nil, opts.Channel, opts.ChatID, ) continue @@ -775,10 +782,11 @@ func (al *AgentLoop) maybeSummarize(agent *AgentInstance, sessionKey, channel, c // forceCompression aggressively reduces context when the limit is hit. // It drops the oldest 50% of messages (keeping system prompt and last user message). -func (al *AgentLoop) forceCompression(agent *AgentInstance, sessionKey string) { +// Returns the compressed history for use in the current request only; does not persist to session. +func (al *AgentLoop) forceCompression(agent *AgentInstance, sessionKey string) []providers.Message { history := agent.Sessions.GetHistory(sessionKey) if len(history) <= 4 { - return + return history } // Keep system prompt (usually [0]) and the very last message (user's trigger) @@ -786,7 +794,7 @@ func (al *AgentLoop) forceCompression(agent *AgentInstance, sessionKey string) { // Assuming [0] is system, [1:] is conversation conversation := history[1 : len(history)-1] if len(conversation) == 0 { - return + return history } // Helper to find the mid-point of the conversation @@ -815,15 +823,12 @@ func (al *AgentLoop) forceCompression(agent *AgentInstance, sessionKey string) { newHistory = append(newHistory, keptConversation...) newHistory = append(newHistory, history[len(history)-1]) // Last message - // Update session - agent.Sessions.SetHistory(sessionKey, newHistory) - agent.Sessions.Save(sessionKey) - - logger.WarnCF("agent", "Forced compression executed", map[string]any{ + logger.WarnCF("agent", "Forced compression executed (in-memory only, not persisted)", map[string]any{ "session_key": sessionKey, "dropped_msgs": droppedCount, "new_count": len(newHistory), }) + return newHistory } // GetStartupInfo returns information about loaded tools and skills for logging. @@ -981,7 +986,6 @@ func (al *AgentLoop) summarizeSession(agent *AgentInstance, sessionKey string) { if finalSummary != "" { agent.Sessions.SetSummary(sessionKey, finalSummary) - agent.Sessions.TruncateHistory(sessionKey, 4) agent.Sessions.Save(sessionKey) } } diff --git a/pkg/session/manager.go b/pkg/session/manager.go index 08f0b0ad2..a6051c20d 100644 --- a/pkg/session/manager.go +++ b/pkg/session/manager.go @@ -11,6 +11,10 @@ import ( "github.com/sipeed/picoclaw/pkg/providers" ) +// maxStoredMessages is the maximum number of messages to persist per session (safety cap to limit file size). +// Older messages are dropped only at write time; in-memory session may hold more until next Save. +const maxStoredMessages = 500 + type Session struct { Key string `json:"key"` Messages []providers.Message `json:"messages"` @@ -184,8 +188,12 @@ func (sm *SessionManager) Save(key string) error { Updated: stored.Updated, } if len(stored.Messages) > 0 { - snapshot.Messages = make([]providers.Message, len(stored.Messages)) - copy(snapshot.Messages, stored.Messages) + toStore := stored.Messages + if len(toStore) > maxStoredMessages { + toStore = toStore[len(toStore)-maxStoredMessages:] + } + snapshot.Messages = make([]providers.Message, len(toStore)) + copy(snapshot.Messages, toStore) } else { snapshot.Messages = []providers.Message{} }