From 19877f0a064e1f4a37de87977556d334b32f3074 Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Sat, 23 May 2026 13:16:41 +0200 Subject: [PATCH] agent/loop: send full compacted history instead of recent-only tail After compaction, the loop was discarding cold+warm zones and only sending the last 8 messages to the backend. This caused the agent to lose context after manual or auto compaction. Remove the recentHistory path entirely: state.history() already returns the compacted (small) history post-compact. The TaskState render block remains as additional steering injected after the system prompt. Removed: StepContext struct, recentHistory from state interface and Session. --- pkg/agent/loop.go | 28 +++++-------------------- pkg/agent/loop_maxsteps_helpers_test.go | 7 ------- pkg/agent/session.go | 7 ------- pkg/agent/state.go | 3 --- 4 files changed, 5 insertions(+), 40 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 2e183da..7552c12 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -81,36 +81,18 @@ func run(ctx context.Context, cfg agentConfig, state state) error { // sync.Map is required because execOne may run in concurrent goroutines. var resultCache sync.Map - // stepCtx carries per-turn context assembled once at the top of each iteration. - type StepContext struct { - Goal string - TaskState *TaskState - Recent []backend.Message // last N messages (hot window) - } - for { // Proactive context gate: strip cold material before calling the backend. if budget := contextBudget(ctx, cfg.Backend); budget > 0 && state.estimateTokens() > budget { state.stripCold() } - sc := StepContext{ - Goal: cfg.preamble, - TaskState: state.taskState(), - Recent: state.recentHistory(hotTailSize), + history := state.history() + if cfg.preamble != "" { + history = append([]backend.Message{{Role: "system", Content: cfg.preamble}}, history...) } - - var history []backend.Message - if sc.TaskState != nil { - history = sc.Recent - } else { - history = state.history() - } - if sc.Goal != "" { - history = append([]backend.Message{{Role: "system", Content: sc.Goal}}, history...) - } - if sc.TaskState != nil { - if msg := sc.TaskState.render(); msg != "" && len(history) > 0 { + if ts := state.taskState(); ts != nil { + if msg := ts.render(); msg != "" && len(history) > 0 { // Insert after system prompt, before conversation. history = append([]backend.Message{history[0], {Role: "user", Content: msg}}, history[1:]...) } diff --git a/pkg/agent/loop_maxsteps_helpers_test.go b/pkg/agent/loop_maxsteps_helpers_test.go index 88d996b..1c74dc5 100644 --- a/pkg/agent/loop_maxsteps_helpers_test.go +++ b/pkg/agent/loop_maxsteps_helpers_test.go @@ -48,13 +48,6 @@ func (s *simpleState) history() []backend.Message { return s.msgs } -func (s *simpleState) recentHistory(n int) []backend.Message { - if n >= len(s.msgs) { - return s.msgs - } - return s.msgs[len(s.msgs)-n:] -} - func (s *simpleState) taskState() *TaskState { return nil } func (s *simpleState) updateTaskState(TaskState) {} diff --git a/pkg/agent/session.go b/pkg/agent/session.go index f79f5a0..7f7e054 100644 --- a/pkg/agent/session.go +++ b/pkg/agent/session.go @@ -158,13 +158,6 @@ func (s *Session) history() []backend.Message { return s.messages } -func (s *Session) recentHistory(n int) []backend.Message { - if n >= len(s.messages) { - return s.messages - } - return s.messages[len(s.messages)-n:] -} - func (s *Session) taskState() *TaskState { return s.TaskState } diff --git a/pkg/agent/state.go b/pkg/agent/state.go index f3be429..bc3645f 100644 --- a/pkg/agent/state.go +++ b/pkg/agent/state.go @@ -27,9 +27,6 @@ type state interface { // The first entry is always the initial user message containing the goal. history() []backend.Message - // recentHistory returns the last n messages from the history. - recentHistory(n int) []backend.Message - // taskState returns the current structured task state, or nil if not set. taskState() *TaskState