Skip to content

fix(tools): stop leaking raw tool results and model content through the status callback - #67

Merged
mudler merged 1 commit into
mainfrom
fix/status-channel-leak
Jul 16, 2026
Merged

fix(tools): stop leaking raw tool results and model content through the status callback#67
mudler merged 1 commit into
mainfrom
fix/status-channel-leak

Conversation

@localai-bot

Copy link
Copy Markdown
Collaborator

Problem

The status callback is documented as short human one-liners, but ExecuteTools pushed raw payloads through it, flooding consumer chat UIs:

  • every raw tool result (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 a looksLikeToolResultStatus() heuristic filter working around this exact leak.
  • the no-tool reply/reasoning in toolSelection — an exact duplicate of the reasoningCallback call 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.
  • the "Selected N tool(s)" counter line.

All four are removed. Raw results keep flowing on their dedicated channel (WithToolCallResultCallback), reasoning on WithReasoningCallback.

New: WithStepContentCallback

The 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: WithStepContentCallback fires 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 clean main too (parent/background sub-agent race on the shared mock queue) — unrelated, worth a separate fix.

Consumers

  • nib / dante-desktop: follow-up PRs wire OnStepContent through to the chat UI (interleaved assistant bubbles) — this fixes the raw-dump + ordering UX in Dante.
  • notetaker: after bumping, can delete the looksLikeToolResultStatus workaround (internal/notary/agent/chat.go).
  • Same-class leaks exist in extractors.go:77 / reviewer.go:59 (ExtractStructure/ContentReview flows) — left out of scope here.

🤖 Generated with Claude Code

…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
mudler merged commit 6c76f4b into main Jul 16, 2026
2 of 3 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants