Keep tool pairs and fold reminders into the last result
ci / vet-and-test (pull_request) Successful in 45s
ci / docker-checkout-hostgateway (pull_request) Successful in 1s

Why: Old tool results were filling the window, compaction could split a tool group or commit a stale summary, and reminders arrived as extra user messages.
Evidence: Compaction retries a conflict and opens the circuit after three failures; clearing keeps the last five tool results; a stale file write is rejected; RPC reports started, queued, or handled.
Impact: Requests over the context window replace older tool results with a placeholder. Rewriting an existing file now requires a read in the same run. Subagent text over 32000 characters is capped.
Refs: feature/context-engineering
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-03 16:19:36 +00:00
co-authored by Cursor
parent 9335fd40e6
commit 6fd1d441a9
20 changed files with 810 additions and 42 deletions
+27 -7
View File
@@ -67,15 +67,35 @@ func (s *Store) ReadMeta() map[string]string {
return meta
}
const maxCompactAttempts = 3
// Compact appends a compaction entry. The file keeps every earlier record.
// Load hides the summarized prefix behind the summary plus the last
// keepLastN non-system messages. Returns false when there is nothing to fold.
// A session change during summary is retried from the new transcript.
// Three consecutive summary failures or conflicts trip the breaker and
// leave the file unchanged.
func (s *Store) Compact(keepLastN int, summarize func(messages []llm.Message) (string, error)) (bool, error) {
var last error
for attempt := 0; attempt < maxCompactAttempts; attempt++ {
done, err, retry := s.compactOnce(keepLastN, summarize)
if !retry {
return done, err
}
last = err
}
if last == nil {
last = fmt.Errorf("compaction conflict: session changed during summary")
}
return false, fmt.Errorf("compaction circuit open: %w", last)
}
func (s *Store) compactOnce(keepLastN int, summarize func(messages []llm.Message) (string, error)) (bool, error, bool) {
s.mu.Lock()
entries, err := s.readEntriesLocked()
if err != nil {
s.mu.Unlock()
return false, err
return false, err, false
}
leaf := s.leafLocked(entries)
generation := len(entries)
@@ -90,7 +110,7 @@ func (s *Store) Compact(keepLastN int, summarize func(messages []llm.Message) (s
}
}
if len(messages) <= keepLastN+1 {
return false, nil
return false, nil, false
}
rest := messages
restEntries := msgEntries
@@ -100,13 +120,13 @@ func (s *Store) Compact(keepLastN int, summarize func(messages []llm.Message) (s
}
tailStart := len(rest) - keepLastN
if tailStart <= 0 {
return false, nil
return false, nil, false
}
tailStart = alignToolTail(rest, tailStart)
head := rest[:tailStart]
summary, err := summarize(head)
if err != nil {
return false, err
return false, err, true
}
var tailIDs []string
for _, e := range restEntries[tailStart:] {
@@ -116,13 +136,13 @@ func (s *Store) Compact(keepLastN int, summarize func(messages []llm.Message) (s
defer s.mu.Unlock()
now, err := s.readEntriesLocked()
if err != nil {
return false, err
return false, err, false
}
if len(now) != generation {
return false, fmt.Errorf("compaction conflict: session changed during summary")
return false, fmt.Errorf("compaction conflict: session changed during summary"), true
}
_, err = s.appendEntryLocked(Entry{Type: TypeCompaction, Summary: summary, TailIDs: tailIDs})
return err == nil, err
return err == nil, err, false
}
// alignToolTail 让保留尾巴从一条非 tool 消息开始,避免把 tool_calls 和结果拆开。