Skip to content

fix: preserve AG-UI reasoning message lifecycle - #942

Merged
Quim Muntal (qmuntal) merged 1 commit into
microsoft:mainfrom
by291:fix/agui-reasoning-lifecycle
Aug 29, 2026
Merged

fix: preserve AG-UI reasoning message lifecycle#942
Quim Muntal (qmuntal) merged 1 commit into
microsoft:mainfrom
by291:fix/agui-reasoning-lifecycle

Conversation

@by291

Copy link
Copy Markdown
Contributor

Summary

  • keep one stable AG-UI message ID across streaming deltas that do not carry ResponseUpdate.MessageID
  • give reasoning and assistant text distinct message IDs when one update contains both content types
  • close the reasoning block before starting text and emit the complete REASONING_START / REASONING_END lifecycle

Why

Responses providers can produce a single update containing both TextReasoningContent and TextContent under one upstream message ID. The previous converter pre-opened both AG-UI messages with that same ID, so clients keyed by message ID could merge visible reasoning into the assistant answer. Updates without a message ID also received a fresh ID per delta, fragmenting a single stream into many messages.

The converter now tracks reasoning and text as separate logical streams and processes content in emission order, matching the Python Agent Framework AG-UI adapter behavior.

Testing

  • go test ./provider/aguiprovider
  • go vet ./provider/aguiprovider
  • go test ./...

Added handler-level regressions for mixed reasoning/text updates and no-ID reasoning/text deltas.

Copilot AI lite review requested due to automatic review settings August 29, 2026 06:54
@by291
by291 (by291) requested a review from a team as a code owner August 29, 2026 06:54
@github-actions github-actions Bot added area:provider Changes files in the provider area area:provider/agui Changes files in the provider / agui area size:xlarge More than 300 changed lines or 10 files pending-auto-risk Automatic risk classification is in progress labels Aug 29, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes the AG-UI hosting event converter so that reasoning and assistant text are emitted as distinct, correctly-ordered AG-UI message lifecycles, while also preserving a stable message ID across streaming deltas that omit ResponseUpdate.MessageID.

Changes:

  • Track reasoning vs text as separate logical streams, allocate stable message IDs, and emit a complete REASONING_STARTREASONING_END lifecycle (closing reasoning before emitting text-like content).
  • Refactor “text-like” detection into a single helper used consistently for lifecycle decisions.
  • Add handler-level regression tests covering mixed reasoning+text in a single update and stable IDs across no-messageID deltas.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
provider/aguiprovider/hosting_events.go Reworks update-to-event conversion to manage reasoning/text lifecycles separately with stable ID allocation and explicit reasoning start/end closure.
provider/aguiprovider/hosting_test.go Adds SSE decoding helpers and new regression tests asserting ordered lifecycles and stable/distinct message IDs.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@github-actions github-actions Bot added risk:medium Contained production impact requiring normal review depth parity-approved Go API consistency review found no parity issues and removed pending-auto-risk Automatic risk classification is in progress labels Aug 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Parity Review — AG-UI Reasoning Message Lifecycle Fix

Scope: user-visible behavior (streaming AG-UI event sequence)
Changed Go contract: No exported Go API surface changed. All modifications are in unexported helpers within provider/aguiprovider/hosting_events.go — specifically the updatesToAGUIEvents converter, the new closeReasoning/closeText/allocateMessageID closures, and the refactored isTextLikeContent/hasTextLikeContent functions.
Upstream evidence reviewed:

  • python/packages/ag-ui/agent_framework_ag_ui/_run_common.py_emit_text_reasoning, _close_reasoning_block, _emit_content functions
  • python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py — end-of-run reasoning block close and segment ordering logic

Result: aligned

Parity Assessment

The Go PR aligns well with the Python upstream behavior in _run_common.py:

  1. Reasoning/text distinction: Python's _emit_content calls _close_reasoning_block when non-reasoning content arrives, then opens a text stream separately. The Go change mirrors this pattern: closeReasoning() is called before emitting any text-like content.

  2. Mixed content in one update: The PR correctly handles a single ResponseUpdate containing both TextReasoningContent and TextContent by allocating separate message IDs for each (mixedReasoningAndText flag forces a generated ID for the reasoning message). Python handles this implicitly through separate content.id or generate_event_id() calls per content item.

  3. Stable message ID across deltas: When ResponseUpdate.MessageID is absent, Go now reuses the previously-allocated reasoning or text message ID across deltas rather than generating a new one per delta. This matches the Python streaming model where flow.reasoning_message_id is preserved across content events.

  4. Full REASONING_START / REASONING_END lifecycle: The PR emits NewReasoningStartEvent on open and NewReasoningEndEvent on close, completing the full protocol lifecycle. Python emits the same ReasoningStartEvent / ReasoningEndEvent envelope in _emit_text_reasoning.

  5. Empty content guard: Skipping TextReasoningContent when both Text and ProtectedData are empty matches the Python guard if not text and content.protected_data is None: return [].

No exported API surface changed, so the public-api-change label is not added.

Generated by Go API Consistency Review Agent · sonnet46 · 40.1 AIC · ⌖ 5.16 AIC · ⊞ 6.4K ·

@qmuntal
Quim Muntal (qmuntal) added this pull request to the merge queue Aug 29, 2026
Merged via the queue into microsoft:main with commit 6c58ac4 Aug 29, 2026
36 checks passed
@by291
by291 (by291) deleted the fix/agui-reasoning-lifecycle branch August 29, 2026 07:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider/agui Changes files in the provider / agui area area:provider Changes files in the provider area parity-approved Go API consistency review found no parity issues risk:medium Contained production impact requiring normal review depth size:xlarge More than 300 changed lines or 10 files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants