fix(cli): flush per-round preambles separately instead of concatenating#261
Merged
Conversation
ea-rus
approved these changes
Jul 22, 2026
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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
In a multi-round turn (
text → tool → text → tool → text), the CLI printed the text preambles of every round and the final answer concatenated back-to-back, with no separators. The user saw something like:This is purely a rendering bug in
StreamDisplay(anton/chat_ui.py) — in the message history these are separate, correct assistant messages.Root cause
StreamDisplayassumed a single-round shape: "a bit of inner-speech → tools → one final answer". It kept two accumulators —_initial_text(text before the first tool) and_buffer(everything after) — and printed_bufferexactly once, as a single Markdown block, infinish().In a real multi-round turn every preamble except the first fell into
_buffer, which was never reset between rounds and had no separator inserted between messages. So all intermediate preambles piled up together with the true final answer and were rendered glued together and misclassified as part of the answer.Fix
Switch to a single-accumulator model with a flush at each tool boundary:
_initial_text+_bufferwith one_pendingaccumulator.on_tool_use_start, if_pendingholds text, it is the current round's preamble — print it immediately as dimmed inner-speech (anton.muted) and clear_pending. This is the natural flush point and gives each preamble its own line, in the correct chronological position.finish()now prints only whatever remains in_pending— the text after the last tool, i.e. the real final answer — with theanton>prompt as Markdown. The "no tools at all" case collapses into the same code path._in_tool_phase,_initial_printed, and_last_was_tool.Resulting output order for a multi-round turn:
Concatenation is now impossible because round boundaries coincide with flush points.
Tests
tests/test_chat_ui.py:_pending.test_finish_prints_activity_summaryto verify the preamble is flushed dimmed at tool start (not infinish()).anton>answer block); and the no-tools regression (single Markdown answer).pytest tests/test_chat_ui.py— 22 passed.ruff check— clean.Fixes ENG-901.