agent: mid-turn compaction and runCompact consolidation
Extract runCompact() to handle the full compact cycle (pre-hook, compact, spawn-context re-injection, post-hook) consistently across all three call sites. Wire AutoCompact into agentConfig so loop.go triggers it after each tool round, not just before turns. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
77dd5c6168
commit
51922b78fc
|
|
@ -206,40 +206,10 @@ func (s *agent) handleCommand(ctx context.Context, input string, handler EventHa
|
|||
handler(infoEvent("nothing to compact"))
|
||||
return
|
||||
}
|
||||
payload := map[string]string{"session_id": s.sessionID, "trigger": "manual", "cwd": s.CWD()}
|
||||
pre := s.hooks.Run(ctx, HookPreCompact, payload, s.log)
|
||||
// TODO: route hook info to debug/err file instead of chat
|
||||
// if pre.Ran {
|
||||
// handler(infoEvent(hooksRan(1)))
|
||||
// }
|
||||
if pre.Warning != "" {
|
||||
handler(infoEvent(pre.Warning))
|
||||
}
|
||||
if pre.Blocked {
|
||||
handler(infoEvent("compact cancelled by hook"))
|
||||
return
|
||||
}
|
||||
if pre.Context != "" {
|
||||
s.session.appendUserMessage(pre.Context)
|
||||
}
|
||||
snapshot := s.session.PreCompactionSnapshot()
|
||||
s.setState("compacting")
|
||||
n, _, err := s.session.compact(ctx, s.cfg.Backend)
|
||||
n, err := s.runCompact(ctx, "manual", handler)
|
||||
s.setState("idle")
|
||||
if sc := s.spawnContext(ctx, handler); sc != "" {
|
||||
s.session.appendUserMessage(sc)
|
||||
}
|
||||
post := s.hooks.Run(ctx, HookPostCompact, payload, s.log)
|
||||
// TODO: route hook info to debug/err file instead of chat
|
||||
// if post.Ran {
|
||||
// handler(infoEvent(hooksRan(1)))
|
||||
// }
|
||||
if post.Warning != "" {
|
||||
handler(infoEvent(post.Warning))
|
||||
}
|
||||
if post.Context != "" {
|
||||
s.session.appendUserMessage(post.Context)
|
||||
}
|
||||
if err != nil {
|
||||
handler(infoEvent("compact error: " + err.Error()))
|
||||
return
|
||||
|
|
|
|||
|
|
@ -505,6 +505,41 @@ func (s *agent) spawnContext(ctx context.Context, handler EventHandler) string {
|
|||
return strings.Join(parts, "\n\n---\n\n")
|
||||
}
|
||||
|
||||
// runCompact executes a full compaction cycle: pre-hook, compact, spawn-context
|
||||
// re-injection, post-hook. Returns (n compacted, error). Returns (0, nil) if
|
||||
// the pre-hook blocked or there was nothing to compact. Caller manages setState.
|
||||
func (s *agent) runCompact(ctx context.Context, trigger string, handler EventHandler) (int, error) {
|
||||
payload := map[string]string{"session_id": s.sessionID, "trigger": trigger, "cwd": s.CWD()}
|
||||
pre := s.hooks.Run(ctx, HookPreCompact, payload, s.log)
|
||||
if pre.Warning != "" {
|
||||
handler(infoEvent(pre.Warning))
|
||||
}
|
||||
if pre.Blocked {
|
||||
handler(infoEvent("compact cancelled by hook"))
|
||||
return 0, nil
|
||||
}
|
||||
if pre.Context != "" {
|
||||
s.session.appendUserMessage(pre.Context)
|
||||
}
|
||||
n, _, err := s.session.compact(ctx, s.cfg.Backend)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n > 0 {
|
||||
if sc := s.spawnContext(ctx, handler); sc != "" {
|
||||
s.session.appendUserMessage(sc)
|
||||
}
|
||||
}
|
||||
post := s.hooks.Run(ctx, HookPostCompact, payload, s.log)
|
||||
if post.Warning != "" {
|
||||
handler(infoEvent(post.Warning))
|
||||
}
|
||||
if post.Context != "" {
|
||||
s.session.appendUserMessage(post.Context)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (s *agent) saveSession() {
|
||||
if s.session == nil || s.sessionID == "" || s.sessionsDir == "" {
|
||||
return
|
||||
|
|
@ -741,44 +776,31 @@ func (s *agent) executeTurn(ctx context.Context, input string, handler EventHand
|
|||
}
|
||||
return ""
|
||||
}
|
||||
s.cfg.AutoCompact = func(ctx context.Context) {
|
||||
if ctx.Err() != nil || s.session == nil {
|
||||
return
|
||||
}
|
||||
limit := s.autoCompactLimit(ctx)
|
||||
if limit <= 0 || s.session.estimateTokens() < limit {
|
||||
return
|
||||
}
|
||||
emit(s.cfg, Event{Role: "info", Content: "auto-compacting context...\n"})
|
||||
s.setState("compacting")
|
||||
if _, err := s.runCompact(ctx, "auto", handler); err != nil {
|
||||
panic(fmt.Sprintf("mid-turn auto-compact: %v", err))
|
||||
}
|
||||
s.setState("thinking")
|
||||
}
|
||||
|
||||
// Auto-compact before the turn if approaching the context limit.
|
||||
if s.session != nil {
|
||||
if limit := s.autoCompactLimit(ctx); limit > 0 && s.session.estimateTokens() >= limit {
|
||||
payload := map[string]string{"session_id": s.sessionID, "trigger": "auto", "cwd": s.CWD()}
|
||||
pre := s.hooks.Run(ctx, HookPreCompact, payload, s.log)
|
||||
// TODO: route hook info to debug/err file instead of chat
|
||||
// if pre.Ran {
|
||||
// handler(infoEvent(hooksRan(1)))
|
||||
// }
|
||||
if pre.Warning != "" {
|
||||
handler(infoEvent(pre.Warning))
|
||||
}
|
||||
if !pre.Blocked {
|
||||
if pre.Context != "" {
|
||||
s.session.appendUserMessage(pre.Context)
|
||||
}
|
||||
emit(s.cfg, Event{Role: "info", Content: "auto-compacting context...\n"})
|
||||
s.setState("compacting")
|
||||
if _, _, err := s.session.compact(ctx, s.cfg.Backend); err != nil {
|
||||
panic(fmt.Sprintf("auto-compact: %v", err))
|
||||
}
|
||||
s.setState("thinking")
|
||||
if sc := s.spawnContext(ctx, handler); sc != "" {
|
||||
s.session.appendUserMessage(sc)
|
||||
}
|
||||
post := s.hooks.Run(ctx, HookPostCompact, payload, s.log)
|
||||
// TODO: route hook info to debug/err file instead of chat
|
||||
// if post.Ran {
|
||||
// handler(infoEvent(hooksRan(1)))
|
||||
// }
|
||||
if post.Warning != "" {
|
||||
handler(infoEvent(post.Warning))
|
||||
}
|
||||
if post.Context != "" {
|
||||
s.session.appendUserMessage(post.Context)
|
||||
}
|
||||
emit(s.cfg, Event{Role: "info", Content: "auto-compacting context...\n"})
|
||||
s.setState("compacting")
|
||||
if _, err := s.runCompact(ctx, "auto", handler); err != nil {
|
||||
panic(fmt.Sprintf("auto-compact: %v", err))
|
||||
}
|
||||
s.setState("thinking")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ type agentConfig struct {
|
|||
Output EventHandler
|
||||
preamble string // compiled system+agent prompt sent as the system role
|
||||
GenerationParams backend.GenerationParams
|
||||
PopInject func() string // returns and clears pending inject, or ""
|
||||
PopInject func() string // returns and clears pending inject, or ""
|
||||
AutoCompact func(ctx context.Context) // called after each tool round; may compact in-place
|
||||
}
|
||||
|
||||
func run(ctx context.Context, cfg agentConfig, state state) error {
|
||||
|
|
@ -221,6 +222,10 @@ func run(ctx context.Context, cfg agentConfig, state state) error {
|
|||
break
|
||||
}
|
||||
|
||||
if cfg.AutoCompact != nil {
|
||||
cfg.AutoCompact(ctx)
|
||||
}
|
||||
|
||||
step++
|
||||
}
|
||||
|
||||
|
|
|
|||
Reference in New Issue