Skip to content

feat(agent): implement display module for message rendering and printing#8

Merged
sequenzia merged 2 commits intomainfrom
printing-agent-messages
Feb 4, 2026
Merged

feat(agent): implement display module for message rendering and printing#8
sequenzia merged 2 commits intomainfrom
printing-agent-messages

Conversation

@sequenzia
Copy link
Owner

Add comprehensive display rendering system with three renderers (Rich, PlainText, HTML) and three presets (compact, detailed, verbose) for visualizing agent messages. Includes:

  • MessageRenderer ABC with Rich, PlainText, and HTML implementations
  • DisplayPreset system with get_preset() factory and override support
  • Standalone print_stats(), print_timeline(), print_tools() functions
  • MessageQuery.print_stats/timeline/tools() convenience methods
  • Rich rich_console protocol for direct Rich console rendering
  • 45 comprehensive tests covering all renderers, presets, and edge cases
  • 36 snapshot golden files for deterministic output validation
  • Full module exports in agent/init.py and top-level init.py

The display module enables sophisticated output of message analytics (token counts, role breakdowns) and tool interaction timelines (turns, tool calls with args/results). Renderers adapt output formatting to the target medium (Rich Console with colors/tables, plain ASCII, semantic HTML). All display code follows project patterns: @DataClass for output types, ABC for extension points, and lazy imports to avoid circular dependencies.

Add comprehensive display rendering system with three renderers (Rich, PlainText, HTML) and three presets (compact, detailed, verbose) for visualizing agent messages. Includes:

- MessageRenderer ABC with Rich, PlainText, and HTML implementations
- DisplayPreset system with get_preset() factory and override support
- Standalone print_stats(), print_timeline(), print_tools() functions
- MessageQuery.print_stats/timeline/tools() convenience methods
- Rich __rich_console__ protocol for direct Rich console rendering
- 45 comprehensive tests covering all renderers, presets, and edge cases
- 36 snapshot golden files for deterministic output validation
- Full module exports in agent/__init__.py and top-level __init__.py

The display module enables sophisticated output of message analytics (token counts, role breakdowns) and tool interaction timelines (turns, tool calls with args/results). Renderers adapt output formatting to the target medium (Rich Console with colors/tables, plain ASCII, semantic HTML). All display code follows project patterns: @DataClass for output types, ABC for extension points, and lazy imports to avoid circular dependencies.
Copilot AI review requested due to automatic review settings February 4, 2026 00:14
@greptile-apps
Copy link

greptile-apps bot commented Feb 4, 2026

Greptile Overview

Greptile Summary

This PR implements a comprehensive display rendering system for agent message analytics, adding three renderer implementations (Rich, PlainText, HTML) and three display presets (compact, detailed, verbose) to visualize conversation data.

Key Changes:

  • Implements ABC-based renderer architecture with MessageRenderer base class
  • Adds three concrete renderers: RichRenderer for terminal output with tables/panels, PlainTextRenderer for ASCII output, HtmlRenderer for Jupyter notebooks
  • Introduces DisplayPreset dataclass with factory function for controlling output detail
  • Provides standalone print_stats(), print_timeline(), print_tools() functions
  • Adds MessageQuery.print_stats/timeline/tools() convenience methods
  • Implements Rich __rich_console__ protocol on MessageStats, ToolCallInfo, and Turn for direct Rich console rendering
  • Includes 45 comprehensive tests with 36 snapshot golden files for visual regression testing
  • Adds rich>=13.0 dependency to pyproject.toml
  • Updates all module exports in __init__.py files

Architecture:
The implementation follows the Strategy pattern with pluggable renderers selected by format string ("rich", "plain", "html"). Display presets control verbosity across all renderers. The system integrates seamlessly with existing MessageQuery analytics while supporting both method-based and protocol-based rendering paths.

Testing:
Comprehensive test suite includes unit tests for each renderer, preset tests, export tests, snapshot/golden file tests for visual regression detection, and Rich console protocol tests. The snapshot tests use fixed console width (100) for deterministic output across environments.

Confidence Score: 5/5

  • This PR is safe to merge with high confidence - well-architected implementation with comprehensive test coverage
  • Score reflects excellent code quality: follows project ABC pattern and Strategy design pattern, comprehensive documentation with Google-style docstrings, 45 tests with snapshot validation for visual regression detection, proper use of TYPE_CHECKING for lazy imports to avoid circular dependencies, clean separation of concerns with renderer ABC and three concrete implementations, and thorough integration with existing MessageQuery system
  • No files require special attention

Important Files Changed

Filename Overview
src/mamba_agents/agent/display/renderer.py Defines ABC for message renderers with three abstract methods
src/mamba_agents/agent/display/presets.py Implements DisplayPreset dataclass and factory function with three named presets
src/mamba_agents/agent/display/functions.py Provides standalone print_stats, print_timeline, print_tools functions with renderer resolution
src/mamba_agents/agent/display/rich_renderer.py Implements Rich Console renderer with tables, panels, and styled text output
src/mamba_agents/agent/display/plain_renderer.py Implements plain ASCII text renderer with aligned column formatting
src/mamba_agents/agent/display/html_renderer.py Implements HTML renderer with semantic markup for Jupyter notebooks
src/mamba_agents/agent/messages.py Adds rich_console protocol and print_* convenience methods to message classes
pyproject.toml Added rich>=13.0 as new dependency for display rendering

Sequence Diagram

sequenceDiagram
    participant User
    participant MessageQuery
    participant Functions as print_stats/timeline/tools
    participant PresetFactory as get_preset()
    participant RendererFactory as _resolve_renderer()
    participant Renderer as RichRenderer/PlainTextRenderer/HtmlRenderer
    participant Console as Rich Console

    User->>MessageQuery: agent.messages.print_stats(preset="detailed", format="rich")
    MessageQuery->>Functions: Delegates to print_stats(stats, preset, format)
    Functions->>PresetFactory: get_preset("detailed", **options)
    PresetFactory-->>Functions: Returns DisplayPreset instance
    Functions->>RendererFactory: _resolve_renderer("rich")
    RendererFactory-->>Functions: Returns RichRenderer instance
    Functions->>Renderer: render_stats(stats, preset, console)
    Renderer->>Console: Creates Console(record=True)
    Renderer->>Renderer: Build Rich Table/Panel objects
    Renderer->>Console: console.print(renderable)
    Console-->>Renderer: Captures output
    Renderer->>Console: export_text()
    Console-->>Renderer: Returns formatted string
    Renderer-->>Functions: Returns rendered output
    Functions-->>MessageQuery: Returns output string
    MessageQuery-->>User: Displays formatted output

    Note over User,Console: Alternative: Direct Rich protocol usage
    User->>Console: console.print(stats)
    Console->>MessageStats: __rich_console__(console, options)
    MessageStats->>Renderer: RichRenderer().render_stats_renderables(self, DETAILED)
    Renderer-->>MessageStats: Yields Rich objects
    MessageStats-->>Console: Yields renderables
    Console-->>User: Renders directly to terminal
Loading

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements a new agent.display subsystem to render message analytics (stats, timeline, tool usage) across Rich, plain text, and HTML formats, with preset-based configuration and integration into MessageQuery plus Rich’s __rich_console__ protocol.

Changes:

  • Added MessageRenderer ABC, DisplayPreset + named presets, and a get_preset() factory with override support.
  • Implemented RichRenderer, PlainTextRenderer, and HtmlRenderer, plus standalone print_stats/print_timeline/print_tools helpers.
  • Integrated display functionality via MessageQuery.print_*, Rich protocol methods on data models, broad exports, and snapshot/unit tests.

Reviewed changes

Copilot reviewed 65 out of 67 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
uv.lock Adds rich to the locked dependency set.
pyproject.toml Adds rich>=13.0 as a project dependency.
src/mamba_agents/agent/display/init.py Public display-module API exports.
src/mamba_agents/agent/display/renderer.py Defines MessageRenderer ABC contract.
src/mamba_agents/agent/display/presets.py Adds DisplayPreset, named presets, and get_preset() factory.
src/mamba_agents/agent/display/rich_renderer.py Implements Rich-based rendering and renderable helpers for __rich_console__.
src/mamba_agents/agent/display/plain_renderer.py Implements ASCII/plain-text rendering with optional output stream.
src/mamba_agents/agent/display/html_renderer.py Implements semantic HTML rendering for stats/timeline/tools.
src/mamba_agents/agent/display/functions.py Adds standalone print_* functions and renderer selection by format.
src/mamba_agents/agent/messages.py Adds __rich_console__ protocol + MessageQuery.print_* convenience methods.
src/mamba_agents/agent/init.py Re-exports display types/functions from mamba_agents.agent.
src/mamba_agents/init.py Re-exports display types/functions from top-level mamba_agents.
tests/unit/test_display_presets.py Tests preset defaults/overrides and MessageRenderer ABC behavior.
tests/unit/test_display_functions.py Tests standalone print_* functions, renderer selection, and error paths.
tests/unit/test_rich_renderer.py Unit tests for RichRenderer behavior and presets.
tests/unit/test_plain_renderer.py Unit tests for PlainTextRenderer behavior and presets.
tests/unit/test_rich_console_protocol.py Tests Rich protocol behavior on MessageStats/ToolCallInfo/Turn.
tests/unit/test_message_query_print_stats.py Tests MessageQuery.print_stats() delegation/forwarding and edge cases.
tests/unit/test_message_query_print_timeline.py Tests MessageQuery.print_timeline() delegation/forwarding and edge cases.
tests/unit/test_message_query_print_tools.py Tests MessageQuery.print_tools() delegation/forwarding and edge cases.
tests/unit/test_display_exports.py Verifies exports/identity across display/agent/top-level and circular import safety.
tests/unit/test_display_snapshots.py Snapshot harness validating deterministic output across renderer/preset combinations.
tests/unit/snapshots/display/rich_stats_compact.txt Golden snapshot: Rich stats (compact).
tests/unit/snapshots/display/rich_stats_detailed.txt Golden snapshot: Rich stats (detailed).
tests/unit/snapshots/display/rich_stats_verbose.txt Golden snapshot: Rich stats (verbose).
tests/unit/snapshots/display/rich_stats_empty.txt Golden snapshot: Rich stats (empty).
tests/unit/snapshots/display/rich_timeline_compact.txt Golden snapshot: Rich timeline (compact).
tests/unit/snapshots/display/rich_timeline_detailed.txt Golden snapshot: Rich timeline (detailed).
tests/unit/snapshots/display/rich_timeline_verbose.txt Golden snapshot: Rich timeline (verbose).
tests/unit/snapshots/display/rich_timeline_empty.txt Golden snapshot: Rich timeline (empty).
tests/unit/snapshots/display/rich_tools_compact.txt Golden snapshot: Rich tools (compact).
tests/unit/snapshots/display/rich_tools_detailed.txt Golden snapshot: Rich tools (detailed).
tests/unit/snapshots/display/rich_tools_verbose.txt Golden snapshot: Rich tools (verbose).
tests/unit/snapshots/display/rich_tools_empty.txt Golden snapshot: Rich tools (empty).
tests/unit/snapshots/display/plain_stats_compact.txt Golden snapshot: Plain stats (compact).
tests/unit/snapshots/display/plain_stats_detailed.txt Golden snapshot: Plain stats (detailed).
tests/unit/snapshots/display/plain_stats_verbose.txt Golden snapshot: Plain stats (verbose).
tests/unit/snapshots/display/plain_stats_empty.txt Golden snapshot: Plain stats (empty).
tests/unit/snapshots/display/plain_timeline_compact.txt Golden snapshot: Plain timeline (compact).
tests/unit/snapshots/display/plain_timeline_detailed.txt Golden snapshot: Plain timeline (detailed).
tests/unit/snapshots/display/plain_timeline_verbose.txt Golden snapshot: Plain timeline (verbose).
tests/unit/snapshots/display/plain_timeline_empty.txt Golden snapshot: Plain timeline (empty).
tests/unit/snapshots/display/plain_tools_compact.txt Golden snapshot: Plain tools (compact).
tests/unit/snapshots/display/plain_tools_detailed.txt Golden snapshot: Plain tools (detailed).
tests/unit/snapshots/display/plain_tools_verbose.txt Golden snapshot: Plain tools (verbose).
tests/unit/snapshots/display/plain_tools_empty.txt Golden snapshot: Plain tools (empty).
tests/unit/snapshots/display/html_stats_compact.html Golden snapshot: HTML stats (compact).
tests/unit/snapshots/display/html_stats_detailed.html Golden snapshot: HTML stats (detailed).
tests/unit/snapshots/display/html_stats_verbose.html Golden snapshot: HTML stats (verbose).
tests/unit/snapshots/display/html_stats_empty.html Golden snapshot: HTML stats (empty).
tests/unit/snapshots/display/html_timeline_compact.html Golden snapshot: HTML timeline (compact).
tests/unit/snapshots/display/html_timeline_detailed.html Golden snapshot: HTML timeline (detailed).
tests/unit/snapshots/display/html_timeline_verbose.html Golden snapshot: HTML timeline (verbose).
tests/unit/snapshots/display/html_timeline_empty.html Golden snapshot: HTML timeline (empty).
tests/unit/snapshots/display/html_tools_compact.html Golden snapshot: HTML tools (compact).
tests/unit/snapshots/display/html_tools_detailed.html Golden snapshot: HTML tools (detailed).
tests/unit/snapshots/display/html_tools_verbose.html Golden snapshot: HTML tools (verbose).
tests/unit/snapshots/display/html_tools_empty.html Golden snapshot: HTML tools (empty).
internal/specs/printing-agent-messages-SPEC.md Adds detailed spec/PRD for the display feature.
CLAUDE.md Updates repository guidance/docs to mention the new display subsystem.
.claude/sessions/exec-session-20260203/task_log.md Session artifact documenting execution tasks.
.claude/sessions/exec-session-20260203/session_summary.md Session artifact summarizing completed tasks.
.claude/sessions/exec-session-20260203/execution_plan.md Session artifact describing the execution plan.
.claude/sessions/exec-session-20260203/execution_context.md Session artifact describing decisions/patterns and file map.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +574 to +576
def test_render_100_messages_under_50ms(self, renderer: PlainTextRenderer) -> None:
"""Test that rendering 100 messages' worth of turns completes in < 50ms."""
# Create 100 turns simulating a conversation.
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test name/docstring says "under 50ms", but the assertion allows up to 5000ms. Either tighten the threshold to match the stated requirement or rename the test/docstring to reflect the actual tolerance to avoid misleading performance expectations.

Copilot uses AI. Check for mistakes.
Comment on lines +346 to +348
if console is not None:
return Console(record=True, width=console.width)
return Console(record=True)
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a Console is passed in, this creates and returns a brand-new Console(record=True) instead of using the caller-provided console. That means the console= parameter does not actually control where output is printed (e.g., custom file=, color settings, capture hooks), and it contradicts the render_* docstrings that say output is printed to the provided console. Consider either (1) using the provided console when possible (e.g., if it was created with record=True), or (2) printing to the provided console and separately capturing to a recording console, or (3) updating the API/docstrings to clarify that console is only used for width.

Copilot uses AI. Check for mistakes.
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
"""Render as a Rich table when passed to ``rich.print()`` or ``Console.print()``.

Delegates to ``RichRenderer.render_stats()`` with the ``detailed`` preset,
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring says this delegates to RichRenderer.render_stats(), but the implementation yields from render_stats_renderables(). Update the docstring to match the actual delegation target to avoid confusing users reading the protocol docs.

Suggested change
Delegates to ``RichRenderer.render_stats()`` with the ``detailed`` preset,
Delegates to ``RichRenderer.render_stats_renderables()`` with the ``DETAILED`` preset,

Copilot uses AI. Check for mistakes.
Comment on lines +296 to +301
parts.append(f"<br>args: <code>{html.escape(args_str)}</code>")

result = interaction.get("result", "")
if result:
result_str = self._truncate_str(str(result), preset.max_tool_arg_length)
parts.append(f"<br>result: <code>{html.escape(result_str)}</code>")
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<code> does not preserve newlines/indentation in HTML, so JSON/tool output with embedded \n and spaces will render as collapsed whitespace in browsers/notebooks. To keep args/results readable, wrap these blocks in <pre><code>...</code></pre> (or apply an equivalent whitespace-preserving approach) here and in _format_tool_details() where the same pattern is used.

Suggested change
parts.append(f"<br>args: <code>{html.escape(args_str)}</code>")
result = interaction.get("result", "")
if result:
result_str = self._truncate_str(str(result), preset.max_tool_arg_length)
parts.append(f"<br>result: <code>{html.escape(result_str)}</code>")
parts.append(f"<br>args: <pre><code>{html.escape(args_str)}</code></pre>")
result = interaction.get("result", "")
if result:
result_str = self._truncate_str(str(result), preset.max_tool_arg_length)
parts.append(f"<br>result: <pre><code>{html.escape(result_str)}</code></pre>")

Copilot uses AI. Check for mistakes.
- `MessageRenderer` is an ABC with `render_stats()`, `render_timeline()`, `render_tools()` methods
- Three renderers: `RichRenderer` (Rich Console), `PlainTextRenderer` (ASCII), `HtmlRenderer` (semantic HTML)
- Three presets: `COMPACT`, `DETAILED`, `VERBOSE` — access via `get_preset("name", **overrides)`
- `DisplayPreset` is `@dataclass(frozen=True)` with fields: show_role_breakdown, show_token_info, show_tool_args, show_tool_results, max_content_length
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section describes DisplayPreset fields (show_role_breakdown, show_token_info, show_tool_args, show_tool_results, ...) that don't exist in the implemented DisplayPreset (which uses show_tokens, expand, show_tool_details, max_tool_arg_length, limit, etc.). Please update the documentation to reflect the actual dataclass fields so contributors don't implement against stale guidance.

Suggested change
- `DisplayPreset` is `@dataclass(frozen=True)` with fields: show_role_breakdown, show_token_info, show_tool_args, show_tool_results, max_content_length
- `DisplayPreset` is `@dataclass(frozen=True)` with fields: show_tokens, expand, show_tool_details, max_tool_arg_length, limit

Copilot uses AI. Check for mistakes.

def test_import_display_module_directly(self) -> None:
"""Importing display module directly does not raise ImportError."""
import mamba_agents.agent.display
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module 'mamba_agents.agent.display' is imported with both 'import' and 'import from'.

Copilot uses AI. Check for mistakes.

def test_import_agent_module_with_display(self) -> None:
"""Importing agent module (with display re-exports) does not raise."""
import mamba_agents.agent
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module 'mamba_agents.agent' is imported with both 'import' and 'import from'.

Copilot uses AI. Check for mistakes.

def test_import_top_level_with_display(self) -> None:
"""Importing top-level package (with display exports) does not raise."""
import mamba_agents
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module 'mamba_agents' is imported with both 'import' and 'import from'.

Copilot uses AI. Check for mistakes.

def test_all_three_in_display_all(self) -> None:
"""Test that all three functions are in the display module __all__."""
import mamba_agents.agent.display as display_module
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module 'mamba_agents.agent.display' is imported with both 'import' and 'import from'.

Copilot uses AI. Check for mistakes.

def test_rich_types_in_type_checking_only(self) -> None:
"""Test that Rich types are imported under TYPE_CHECKING only."""
import mamba_agents.agent.messages as mod
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module 'mamba_agents.agent.messages' is imported with both 'import' and 'import from'.

Copilot uses AI. Check for mistakes.
@claude
Copy link

claude bot commented Feb 4, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

- Format plain_renderer.py, test_display_snapshots.py, test_plain_renderer.py to comply with ruff line-length rules
- Remove unused GitHub Actions workflows (claude-code-review.yml, claude.yml)
- All tests pass (1429)
@sequenzia sequenzia merged commit 36fcf89 into main Feb 4, 2026
4 checks passed
@sequenzia sequenzia deleted the printing-agent-messages branch February 4, 2026 01:15
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