Python: fix: surface AG-UI workflow intermediate events as reasoning - #8003
Conversation
The AG-UI workflow runner only handled "output" and "data" workflow events, so "intermediate" events (the modern designation produced by executors listed under intermediate_output_from) fell through to the generic CustomEvent fallback instead of being converted to AG-UI reasoning events. Add "intermediate" to the handled set and route intermediate text content to text_reasoning so consumers render it as a collapsible "thinking" block rather than a final assistant message. The deprecated "data" alias is treated the same as "intermediate" (per its definition as the intermediate compatibility alias in _events.py). "output" keeps the terminal-message behavior, and non-text content (tool calls and results) still emits as its native AG-UI events. Fixes microsoft#8000
There was a problem hiding this comment.
🟡 Changes recommended
Reasoning conversion currently drops encrypted protected_data metadata.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Routes intermediate workflow text through AG-UI’s reasoning pipeline.
Changes:
- Handles
intermediateand deprecateddataevents as reasoning. - Adds regression tests for intermediate and terminal output behavior.
File summaries
| File | Description |
|---|---|
_workflow_run.py |
Converts intermediate text into reasoning content. |
test_workflow_run.py |
Tests intermediate and deprecated alias routing. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
| # message, so the last_assistant_text dedup does not apply. | ||
| for content in contents: | ||
| reasoning_content = _as_reasoning_content(content) | ||
| for out_event in _emit_content( |
There was a problem hiding this comment.
It may make sense for the workflow snapshot path to retain the reasoning emitted here. _emit_content records it in flow.reasoning_messages, but run_workflow_stream never emits a MessagesSnapshotEvent, and _WorkflowSnapshotBuilder.observe ignores the reasoning events. With a snapshot store enabled, intermediate output renders live but disappears when the thread is hydrated; teaching _WorkflowSnapshotBuilder to fold these events, or emitting the same terminal snapshot as the agent runner, would keep live and replayed output consistent.
There was a problem hiding this comment.
Agreed this is a real gap — the workflow runner records reasoning into flow.reasoning_messages but never emits a MessagesSnapshotEvent, and _WorkflowSnapshotBuilder.observe ignores the reasoning events, so intermediate output renders live but doesn't survive hydration (the agent runner emits the terminal snapshot; the workflow runner doesn't). It's larger than the fixes here and touches the snapshot/hydration path, so I'd lean toward a focused follow-up PR rather than expanding this one — unless you'd prefer it land here. Happy either way; let me know and I'll pick it up.
Follow-up to the intermediate-reasoning change on this branch, addressing maintainer review: - Preserve protected_data when re-tagging intermediate text as reasoning so the ReasoningEncryptedValueEvent and the snapshot encryptedValue survive. - Close any open reasoning block (and assistant text message) before every terminal event (RUN_FINISHED / RUN_ERROR) and before a request_info tool call, via an idempotent _drain_open_blocks() helper. Previously the post-loop cleanup emitted the reasoning end events after the terminal event, so a client that stopped at RUN_FINISHED never saw them; a request_info tool call could likewise sit inside an unclosed reasoning block. - Keep role-less AgentResponseUpdate text (and tool content) instead of dropping it to a CUSTOM workflow_output. Explicit non-assistant roles and approval requests stay excluded.
Motivation & Context
The AG-UI workflow runner only processed
outputanddataworkflow events, sointermediateevents — the modern designation produced by executors listed underintermediate_output_from— fell through to the genericCustomEventfallback (_workflow_run.py, theif event_type in {"output", "data"}branch). As a result, intermediate workflow output was emitted asCustomEvent(name="intermediate")instead of AG-UI reasoning events, so consumers (CopilotKit, assistant-ui, …) could not render it as a collapsible "thinking" block.AGENT_FORWARDED_EVENT_TYPESin_workflows/_events.pyalready listsoutput,intermediate, anddata(the deprecated alias forintermediate), so the AG-UI layer was silently dropping a forwarded event type into the diagnostics fallback.Description & Review Guide
What are the major changes?
"intermediate"to the handled event set inrun_workflow_streamso intermediate events are no longer routed to the genericCustomEventfallback._as_reasoning_content()helper that re-tags it astext_reasoning, so it emits via the existing_emit_text_reasoningpath (REASONING_*events). Non-text content (tool calls/results, etc.) passes through unchanged and still emits as its native AG-UI events."data"alias is handled identically to"intermediate", matching its definition in_events.pyas the intermediate compatibility alias."output"keeps the existing terminal-message behavior, including thelast_assistant_textde-duplication (reasoning is a separate channel, so that de-dup does not apply to intermediate output).WorkflowAgent._convert_workflow_event_to_agent_response_updates()(Option A), which is being addressed under Python: [Bug]: WorkflowAgent drops response metadata when forwarding AgentResponseUpdate #7952.What is the impact of these changes?
type="data"alias previously rendered as a final assistant message; it now renders as reasoning, aligning the deprecated path with itsintermediatereplacement. If you would prefer to keepdata→ final message for strict backward-compat, that is a one-line change (drop"data"fromis_intermediate) plus removing the alias test — happy to adjust either way.What do you want reviewers to focus on?
data-alias rendering decision above.Related Issue
Fixes #8000
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.