From fb21228ca18bf413c1be1aaf12c3f80268f6d92c Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Wed, 29 Jul 2026 09:33:46 +0200 Subject: [PATCH] agent: keep 'calling:' state during tool execution, not 'thinking' Previously, the Output callback set state to 'thinking' on every tool event (including streaming chunks), so the state flickered immediately away from 'calling:'. Now: - Tool events no longer trigger setState('thinking') - The loop emits a 'state' event with 'thinking' at the top of each iteration (before calling the backend), which is the correct time to transition. - New 'state' event role in Output callback for explicit state transitions. This keeps the detach button visible in the GUI during tool execution. --- pkg/agent/core.go | 3 ++- pkg/agent/loop.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/agent/core.go b/pkg/agent/core.go index 16d6397..465adaa 100644 --- a/pkg/agent/core.go +++ b/pkg/agent/core.go @@ -1320,8 +1320,9 @@ func (s *agent) executeTurn(ctx context.Context, input string) string { s.setState("calling: " + ev.Name) s.auditLog.Debug("call: %s %s", ev.Name, auditTruncate(string(ev.Content))) case "tool": - s.setState("thinking") s.auditLog.Debug("result: %s %s", ev.Name, auditTruncate(ev.Content)) + case "state": + s.setState(ev.Content) case "limitretry": s.setState("limitretry") case "error": diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 01ac608..2a9ec39 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -91,6 +91,7 @@ func run(ctx context.Context, cfg agentConfig, state state) error { } for { + emit(cfg, Event{Role: "state", Content: "thinking"}) // Proactive context gate: strip cold material before calling the backend. if budget := contextBudget(ctx, cfg.Backend); budget > 0 && state.estimateTokens() > budget { state.stripCold(ctx, cfg.Backend)