fix(tools): stop leaking raw tool results and model content through the status callback - #67
Merged
Merged
Conversation
…he status callback
The status callback is meant for short human one-liners, but ExecuteTools
pushed three other things through it, flooding consumer chat UIs (e.g.
dante-desktop rendered every tool result twice — once as an unbounded,
unstyled "status" wall of text, once as its styled tool block; notetaker
carries a looksLikeToolResultStatus() workaround for the same leak):
- every raw tool result (statusCallback(execResult.result)) — already
delivered on the dedicated WithToolCallResultCallback channel
- the no-tool reply/reasoning in toolSelection — already delivered via
the reasoning callback one line below
- selectedToolFragment.LastMessage().Content — empty in the normal path
(toolSelection strips content), so pure noise
- the "Selected N tool(s)" counter line
The assistant content that accompanies a tool selection ("I'll search
for X now…") was previously discarded outright; it now has a dedicated
channel: WithStepContentCallback fires at the step boundary, before the
selected tools execute, so consumers can render the commentary in
chronological order relative to tool results. It never fires for empty
content, for the guided (forceReasoning) path (which carries no message),
or for the turn's final reply.
Regression-tested in status_channel_test.go: the status channel stays
clean, the step content arrives on its own channel before the tool
result, and silent steps don't fire.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mudler
added a commit
that referenced
this pull request
Jul 16, 2026
…s and sequential steps Follow-up to #67: one step-content fire per decision step — before that step's tool results, never per tool call — across a step with two parallel tool calls and a subsequent sequential step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The status callback is documented as short human one-liners, but
ExecuteToolspushed raw payloads through it, flooding consumer chat UIs:statusCallback(execResult.result)) — dante-desktop rendered each tool result twice: once as an unbounded, unstyled "status" wall of text directly in the chat, once as its styled collapsed tool block. notetaker carries alooksLikeToolResultStatus()heuristic filter working around this exact leak.toolSelection— an exact duplicate of thereasoningCallbackcall on the next line, and can be a full-length model reply.selectedToolFragment.LastMessage().Content— empty in the normal path (toolSelection strips content), pure noise."Selected N tool(s)"counter line.All four are removed. Raw results keep flowing on their dedicated channel (
WithToolCallResultCallback), reasoning onWithReasoningCallback.New:
WithStepContentCallbackThe assistant content accompanying a tool selection ("I'll search for X now…",
decisionResult.message) was previously discarded outright — never in history, never in a callback (only visible as live stream deltas). It now has a dedicated channel:WithStepContentCallbackfires at the step boundary, before the selected tools execute, so consumers can render the commentary chronologically (text → tool result → … → final answer).It never fires for empty content, for the guided (
forceReasoning) path (which carries no message), or for the turn's final reply.Tests
status_channel_test.go(2 new specs): the status channel receives neither raw results nor step content; step content arrives on its own channel ordered before the tool result; silent steps don't fire.Unit suite green: 73/73 (
--label-filter='!e2e'). Note:agent_test.go"spawn agent in background and return ID" is flaky on cleanmaintoo (parent/background sub-agent race on the shared mock queue) — unrelated, worth a separate fix.Consumers
OnStepContentthrough to the chat UI (interleaved assistant bubbles) — this fixes the raw-dump + ordering UX in Dante.looksLikeToolResultStatusworkaround (internal/notary/agent/chat.go).extractors.go:77/reviewer.go:59(ExtractStructure/ContentReview flows) — left out of scope here.🤖 Generated with Claude Code