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 <cursoragent@cursor.com>
This commit is contained in:
parent
ec6da7a530
commit
95847bca97
2 changed files with 26 additions and 14 deletions
|
|
@ -29,6 +29,10 @@ import (
|
||||||
"github.com/sipeed/picoclaw/pkg/utils"
|
"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 {
|
type AgentLoop struct {
|
||||||
bus *bus.MessageBus
|
bus *bus.MessageBus
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
|
|
@ -413,6 +417,10 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, agent *AgentInstance, opt
|
||||||
if !opts.NoHistory {
|
if !opts.NoHistory {
|
||||||
history = agent.Sessions.GetHistory(opts.SessionKey)
|
history = agent.Sessions.GetHistory(opts.SessionKey)
|
||||||
summary = agent.Sessions.GetSummary(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(
|
messages := agent.ContextBuilder.BuildMessages(
|
||||||
history,
|
history,
|
||||||
|
|
@ -573,11 +581,10 @@ func (al *AgentLoop) runLLMIteration(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
al.forceCompression(agent, opts.SessionKey)
|
compressed := al.forceCompression(agent, opts.SessionKey)
|
||||||
newHistory := agent.Sessions.GetHistory(opts.SessionKey)
|
|
||||||
newSummary := agent.Sessions.GetSummary(opts.SessionKey)
|
newSummary := agent.Sessions.GetSummary(opts.SessionKey)
|
||||||
messages = agent.ContextBuilder.BuildMessages(
|
messages = agent.ContextBuilder.BuildMessages(
|
||||||
newHistory, newSummary, "",
|
compressed, newSummary, "",
|
||||||
nil, opts.Channel, opts.ChatID,
|
nil, opts.Channel, opts.ChatID,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
@ -775,10 +782,11 @@ 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 (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)
|
history := agent.Sessions.GetHistory(sessionKey)
|
||||||
if len(history) <= 4 {
|
if len(history) <= 4 {
|
||||||
return
|
return history
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep system prompt (usually [0]) and the very last message (user's trigger)
|
// 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
|
// Assuming [0] is system, [1:] is conversation
|
||||||
conversation := history[1 : len(history)-1]
|
conversation := history[1 : len(history)-1]
|
||||||
if len(conversation) == 0 {
|
if len(conversation) == 0 {
|
||||||
return
|
return history
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to find the mid-point of the conversation
|
// 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, keptConversation...)
|
||||||
newHistory = append(newHistory, history[len(history)-1]) // Last message
|
newHistory = append(newHistory, history[len(history)-1]) // Last message
|
||||||
|
|
||||||
// Update session
|
logger.WarnCF("agent", "Forced compression executed (in-memory only, not persisted)", map[string]any{
|
||||||
agent.Sessions.SetHistory(sessionKey, newHistory)
|
|
||||||
agent.Sessions.Save(sessionKey)
|
|
||||||
|
|
||||||
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(newHistory),
|
||||||
})
|
})
|
||||||
|
return newHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStartupInfo returns information about loaded tools and skills for logging.
|
// GetStartupInfo returns information about loaded tools and skills for logging.
|
||||||
|
|
@ -981,7 +986,6 @@ func (al *AgentLoop) summarizeSession(agent *AgentInstance, sessionKey string) {
|
||||||
|
|
||||||
if finalSummary != "" {
|
if finalSummary != "" {
|
||||||
agent.Sessions.SetSummary(sessionKey, finalSummary)
|
agent.Sessions.SetSummary(sessionKey, finalSummary)
|
||||||
agent.Sessions.TruncateHistory(sessionKey, 4)
|
|
||||||
agent.Sessions.Save(sessionKey)
|
agent.Sessions.Save(sessionKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ import (
|
||||||
"github.com/sipeed/picoclaw/pkg/providers"
|
"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 {
|
type Session struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Messages []providers.Message `json:"messages"`
|
Messages []providers.Message `json:"messages"`
|
||||||
|
|
@ -184,8 +188,12 @@ func (sm *SessionManager) Save(key string) error {
|
||||||
Updated: stored.Updated,
|
Updated: stored.Updated,
|
||||||
}
|
}
|
||||||
if len(stored.Messages) > 0 {
|
if len(stored.Messages) > 0 {
|
||||||
snapshot.Messages = make([]providers.Message, len(stored.Messages))
|
toStore := stored.Messages
|
||||||
copy(snapshot.Messages, stored.Messages)
|
if len(toStore) > maxStoredMessages {
|
||||||
|
toStore = toStore[len(toStore)-maxStoredMessages:]
|
||||||
|
}
|
||||||
|
snapshot.Messages = make([]providers.Message, len(toStore))
|
||||||
|
copy(snapshot.Messages, toStore)
|
||||||
} else {
|
} else {
|
||||||
snapshot.Messages = []providers.Message{}
|
snapshot.Messages = []providers.Message{}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue