feat(moa): show each reference model's output as a labelled block before the aggregator (#53793)

When a MoA preset is selected, each reference model's answer now renders in the
CLI as a thinking-style block labelled with its source model, BEFORE the
aggregator responds — so the mixture-of-agents process is visible instead of a
silent pause. The aggregator's response (and its tool actions) follow as normal.

Mechanism (shared seam, all surfaces):
- MoAChatCompletions/MoAClient take an optional reference_callback and emit
  'moa.reference' (index/count/label/text) per reference, then 'moa.aggregating'
  (aggregator label) once. agent_init wires this to the agent's
  tool_progress_callback, which every surface already consumes — so the events
  reach CLI/TUI/desktop/gateway with no new plumbing.
- CLI _on_tool_progress renders 'moa.reference' as a labelled '┊ ◇ Reference
  i/n — <model>' header + a thinking-style preview (reusing _emit_reasoning_
  preview), and 'moa.aggregating' as a spinner transition. Display-only; never
  touches message history (cache-safe).

Turn-scoped reference cache: the agent loop calls the facade once per tool-loop
iteration, but the advisory message view is identical across iterations within a
turn, so references are now run AND displayed once per user turn (keyed by the
advisory view's signature) instead of re-running/re-spamming on every iteration.
This also cuts reference API cost from O(iterations) back to O(turns).

Verified live via interactive PTY on the opus-gpt preset (gpt-5.5 + opus refs):
reference blocks render once per turn, labelled by model, before the aggregator;
fresh blocks on each new turn; aggregator tool actions still execute.

Follow-up: TUI/desktop rich rendering + gateway batched-summary already receive
the events via tool_progress_callback; their surface-specific renderers are a
separate change.
This commit is contained in:
Teknium 2026-06-27 12:45:23 -07:00 committed by GitHub
parent dbbf102b8e
commit 3b44a3c8bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 319 additions and 10 deletions

View file

@ -251,3 +251,45 @@ class TestToolProgressScrollback:
# First entry consumed, second remains
assert len(cli._pending_tool_info.get("terminal", [])) == 1
assert cli._pending_tool_info["terminal"][0] == {"command": "pwd"}
class TestMoAReferenceBlocks:
"""moa.reference renders a labelled thinking-style block; moa.aggregating
updates the spinner. Both are display-only and must commit regardless of
tool_progress_mode (MoA is non-streaming)."""
def test_reference_event_prints_labelled_block(self):
cli = _make_cli(tool_progress="all")
with patch.object(_cli_mod, "_cprint") as mock_print:
cli._on_tool_progress(
"moa.reference",
"openrouter:openai/gpt-5.5",
"Paris is the capital.",
None,
moa_index=1,
moa_count=2,
)
printed = " ".join(str(c.args[0]) for c in mock_print.call_args_list)
# Header names the source model + index/count; body carries the text.
assert "openrouter:openai/gpt-5.5" in printed
assert "Reference 1/2" in printed
assert "Paris is the capital." in printed
def test_reference_event_prints_even_when_progress_off(self):
"""Reference blocks are the MoA process view, not tool progress — they
must show even with tool_progress: off."""
cli = _make_cli(tool_progress="off")
with patch.object(_cli_mod, "_cprint") as mock_print:
cli._on_tool_progress(
"moa.reference", "openrouter:anthropic/claude-opus-4.8", "Four.", None,
moa_index=2, moa_count=2,
)
assert mock_print.called
def test_aggregating_event_updates_spinner_only(self):
cli = _make_cli(tool_progress="all")
with patch.object(_cli_mod, "_cprint") as mock_print:
cli._on_tool_progress("moa.aggregating", "openrouter:anthropic/claude-opus-4.8", None, None)
assert "aggregating" in cli._spinner_text
# aggregating is a spinner-only transition; no committed scrollback line.
mock_print.assert_not_called()