Skip to content

[Bug]: transform_llm_output plugin output silently dropped in CLI streaming mode #57269

Description

@kweiner

Bug Description

Content appended by a transform_llm_output plugin hook is silently dropped when Hermes CLI streams a response token-by-token. The appended content is present in the final response text and stored in conversation history, but the user never sees it on screen.

Steps to Reproduce

  1. Install a plugin that implements transform_llm_output and appends a suffix to the response (e.g. a disclaimer, citation, or additional content).
  2. Run hermes in interactive CLI mode (streaming enabled by default).
  3. Send any message that triggers the plugin to append content.
  4. Observe that only the streamed response is shown — the appended content is missing from the terminal output.

To confirm the content is actually there: check conversation history or run in a non-streaming surface (Dashboard, Desktop, TUI). The appended content appears correctly on all non-streaming surfaces.

Expected Behavior

After streaming completes and transform_llm_output fires, any content the plugin appended to the response should be printed to the terminal.

Actual Behavior

The CLI enters the already_streamed branch after streaming and skips all output with pass. Since transform_llm_output fires after the stream is closed, the plugin's appended content is never displayed.

Relevant code in cli.py:

elif already_streamed:
    # Response was already streamed token-by-token with box framing;
    # _flush_stream() already closed the box. Skip Rich Panel.
    pass  # ← appended content is silently dropped here

Root Cause Analysis

The sequence is:

  1. CLI streams response token-by-token and sets already_streamed = True.
  2. After the tool loop, finalize_turn() fires transform_llm_output hooks.
  3. A plugin appends content, returning the full new response string.
  4. finalize_turn() returns the transformed response in the result dict.
  5. Back in the CLI, the already_streamed branch executes pass — the transformed response with appended content is discarded from the display path.

The fix requires two small changes:

agent/turn_finalizer.py — capture the pre-transform response so the CLI knows what was already streamed:

_pre_transform_response = None

# ... inside the transform loop ...
for _hook_result in _transform_results:
    if isinstance(_hook_result, str) and _hook_result:
        _pre_transform_response = final_response   # save before overwrite
        final_response = _hook_result
        _response_transformed = True
        break

# ... in the result dict ...
"response_transformed": _response_transformed,
"pre_transform_response": _pre_transform_response,   # new field

cli.py — in the already_streamed branch, print only the appended suffix:

elif already_streamed:
    if result and result.get("response_transformed"):
        _pre = result.get("pre_transform_response") or ""
        _appended = response[len(_pre):]
        if _appended.strip():
            _cprint(_appended)

A PR with this fix is ready: https://github.com/kweiner/hermes-agent/tree/fix/transform-llm-output-streaming

Debug Report

N/A — this is a code-level bug with a clear root cause, not a configuration or environment issue.

Operating System

Ubuntu 24.04.4 LTS

Python Version

3.11.15

Hermes Version

0.18.0

Are you willing to submit a PR for this?

  • I'd like to fix this myself and submit a PR

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low — cosmetic, nice to havearea/streamingStreaming responses: gateway delivery, provider wirecomp/cliCLI entry point, hermes_cli/, setup wizardtype/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions