fix(agent): loop improvements, history compaction, config tweaks

This commit is contained in:
viettranx committed 2026-03-20 21:46:22 +07:00
1 parent 001fe2721c
commit 0cffbf5bfa
8 files changed
+46 -20

No files matched your search

+37 -15
View File
@@ -1333,11 +1333,46 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
finalContent += req.ContentSuffix
}
pendingMsgs = append(pendingMsgs, providers.Message{
// Collect forwarded media + dedup + populate sizes BEFORE saving to session,
// so we can attach output MediaRefs to the assistant message for history reload.
for _, mf := range req.ForwardMedia {
ct := mf.MimeType
if ct == "" {
ct = mimeFromExt(filepath.Ext(mf.Path))
}
mediaResults = append(mediaResults, MediaResult{Path: mf.Path, ContentType: ct})
}
mediaResults = deduplicateMedia(mediaResults)
for i := range mediaResults {
if mediaResults[i].Size == 0 {
if info, err := os.Stat(mediaResults[i].Path); err == nil {
mediaResults[i].Size = info.Size()
}
}
}
// Build final assistant message with output media refs for history persistence.
assistantMsg := providers.Message{
Role: "assistant",
Content: finalContent,
Thinking: finalThinking,
})
}
for _, mr := range mediaResults {
kind := "document"
if strings.HasPrefix(mr.ContentType, "image/") {
kind = "image"
} else if strings.HasPrefix(mr.ContentType, "audio/") {
kind = "audio"
} else if strings.HasPrefix(mr.ContentType, "video/") {
kind = "video"
}
assistantMsg.MediaRefs = append(assistantMsg.MediaRefs, providers.MediaRef{
ID: filepath.Base(mr.Path),
MimeType: mr.ContentType,
Kind: kind,
})
}
pendingMsgs = append(pendingMsgs, assistantMsg)
// Bootstrap nudge: if model didn't call write_file on turn 2+, inject reminder
// into session history so the next turn sees it. Appended to pendingMsgs so it's
@@ -1414,19 +1449,6 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
// 5. Maybe summarize
l.maybeSummarize(ctx, req.SessionKey)
// Include forwarded media from delegation results (not cleaned up like req.Media)
for _, mf := range req.ForwardMedia {
ct := mf.MimeType
if ct == "" {
ct = mimeFromExt(filepath.Ext(mf.Path))
}
mediaResults = append(mediaResults, MediaResult{Path: mf.Path, ContentType: ct})
}
// Deduplicate media by path — prevents the same image being sent twice
// (e.g. once via ForwardMedia and again when the LLM reads the file).
mediaResults = deduplicateMedia(mediaResults)
return &RunResult{
Content: finalContent,
RunID: req.RunID,
+1 -1
View File
@@ -482,7 +482,7 @@ func (l *Loop) maybeSummarize(ctx context.Context, sessionKey string) {
if l.compactionCfg != nil && l.compactionCfg.MaxHistoryShare > 0 {
historyShare = l.compactionCfg.MaxHistoryShare
}
minMessages := 50
minMessages := 200
if l.compactionCfg != nil && l.compactionCfg.MinMessages > 0 {
minMessages = l.compactionCfg.MinMessages
}
+3
View File
@@ -178,6 +178,9 @@ func (l *Loop) Run(ctx context.Context, req RunRequest) (*RunResult, error) {
"cache_read_tokens": result.Usage.CacheReadTokens,
}
}
if len(result.Media) > 0 {
completedPayload["media"] = result.Media
}
emitRun(AgentEvent{
Type: protocol.AgentEventRunCompleted,
AgentID: l.id,
+1
View File
@@ -413,5 +413,6 @@ type RunResult struct {
type MediaResult struct {
Path string `json:"path"` // local file path
ContentType string `json:"content_type,omitempty"` // MIME type
Size int64 `json:"size,omitempty"` // file size in bytes
AsVoice bool `json:"as_voice,omitempty"` // send as voice message (Telegram OGG)
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"github.com/nextlevelbuilder/goclaw/internal/providers"
)
const titleGenerateTimeout = 10 * time.Second
const titleGenerateTimeout = 30 * time.Second
const titleSystemPrompt = `Generate a short title (max 15 words) for this conversation based on the user's message. Reply with only the title, no quotes or punctuation wrapping.`
+1 -1
View File
@@ -23,7 +23,7 @@ import (
const maxHistoryKeys = 1000
// DefaultGroupHistoryLimit is the default pending message limit per group.
const DefaultGroupHistoryLimit = 50
const DefaultGroupHistoryLimit = 100
const (
flushInterval = 3 * time.Second // periodic flush interval
+1 -1
View File
@@ -15,7 +15,7 @@ import (
// CompactionConfig configures LLM-based history compaction.
type CompactionConfig struct {
Threshold int // trigger compaction when entries exceed this (default 50)
Threshold int // trigger compaction when entries exceed this (default 100)
KeepRecent int // keep this many recent raw messages (default 15)
MaxTokens int // max output tokens for summarization (default 4096)
Provider providers.Provider // LLM provider for summarization
+1 -1
View File
@@ -132,7 +132,7 @@ type AgentDefaults struct {
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 50)
MinMessages int `json:"minMessages,omitempty"` // min messages before compaction triggers (default 200)
KeepLastMessages int `json:"keepLastMessages,omitempty"` // messages to keep after compaction (default 4)
MemoryFlush *MemoryFlushConfig `json:"memoryFlush,omitempty"` // pre-compaction flush
}