From c32ea01f60bd03a3af206879b2bd7c2e412ff187 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Sun, 26 Apr 2026 19:25:57 +0200 Subject: [PATCH] agent: interrupt is now pause, not cancel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With mid-turn SaveSession in place, interrupt keeps in-memory session state matching the on-disk state rather than restoring the pre-turn snapshot. The model resumes naturally from the saved history on the next turn. Remove sessionClean — no longer needed now that all interrupted sessions are kept rather than conditionally discarded. Co-Authored-By: Claude Sonnet 4.6 --- pkg/agent/core.go | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/pkg/agent/core.go b/pkg/agent/core.go index b421f42..ce36572 100644 --- a/pkg/agent/core.go +++ b/pkg/agent/core.go @@ -817,20 +817,18 @@ func (s *agent) executeTurn(ctx context.Context, input string, handler EventHand s.setState("idle") if err != nil { - interrupted := errors.Is(err, context.Canceled) || errors.Is(err, ErrInterrupted) - if interrupted && s.session != nil && sessionClean(s.session.messages) { - // Interrupt with clean state: keep whatever was built during the turn. + if errors.Is(err, context.Canceled) || errors.Is(err, ErrInterrupted) { + // Interrupted: mid-turn state was already saved to disk by SaveSession. + // Keep in-memory session as-is so it matches the on-disk state. return "" } - // Real error or dirty session (dangling tool calls): restore pre-turn state. + // Real error: restore pre-turn snapshot. if snapSession == nil { s.session = nil } else { s.session.messages = snapMessages } - if !interrupted { - handler(Event{Role: "error", Content: err.Error()}) - } + handler(Event{Role: "error", Content: err.Error()}) return "" } @@ -866,17 +864,6 @@ func (s *agent) executeTurn(ctx context.Context, input string, handler EventHand return "" } -// sessionClean reports whether the message history is in a consistent state -// that is safe to resume from. An interrupted turn may leave dangling tool -// calls (assistant message with ToolCalls but no following tool results) or -// end on a tool result with no subsequent assistant message — both are unsafe. -func sessionClean(msgs []backend.Message) bool { - if len(msgs) == 0 { - return true - } - last := msgs[len(msgs)-1] - return last.Role == "assistant" && len(last.ToolCalls) == 0 -} func toolInfosToBackend(infos []tools.ToolInfo) []backend.Tool { out := make([]backend.Tool, len(infos))