Skip to content

.NET: Validate MessageMerger ordering invariants#5825

Draft
Copilot wants to merge 11 commits into
mainfrom
copilot/create-test-for-handoff-issue
Draft

.NET: Validate MessageMerger ordering invariants#5825
Copilot wants to merge 11 commits into
mainfrom
copilot/create-test-for-handoff-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 13, 2026

Motivation and Context

MessageMerger, the internal component that folds streaming AgentResponseUpdate items into a final AgentResponse, had an implicit contract with no tests validating its ordering and grouping behavior. This created two issues:

  1. Message ordering bug: When updates lacked CreatedAt timestamps, CompareByDateTimeOffset treated null timestamps as "greater than" any value, pushing untimestamped messages unpredictably to the end rather than preserving their arrival order. In multi-agent scenarios (handoff, group chat), this caused message reordering that broke conversation coherence.

  2. Missing invariant documentation: The merger's guarantees were never written down, and the code contained dead state (createdTimes HashSet) suggesting abandoned functionality. Future refactors risked silently breaking the contract.

Description

This PR fixes the message ordering issue, documents the merger invariants in ADR 0026, and adds comprehensive tests to pin the expected behavior.

Bug fix in MessageMerger.CompareByDateTimeOffset:

ADR 0026 establishes three invariants:

  1. Single ResponseId per turn — Hosting executors must assign a ResponseId if the agent doesn't provide one; updates with ResponseId == null are "dangling" and appended at the end
  2. Output order preservation — When updates lack CreatedAt, their relative order in the merged output matches arrival order
  3. Per-ResponseId grouping — Messages from each ResponseId appear as a contiguous block (no interleaving), enabling per-agent grouping in multi-agent scenarios

Cleanup:

  • Removed unused createdTimes HashSet that was populated but never consumed

Test coverage added in MessageMergerTests:

  • Insertion-order preservation with no timestamps
  • Insertion-order preservation with mixed timestamps
  • Determinism across repeated runs with mixed timestamps
  • Per-ResponseId grouping for interleaved multi-agent streams
  • Per-ResponseId grouping with distinct response IDs
  • Function call/result ordering preservation
  • FinishReason propagation

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? No — this fix preserves intended behavior while correcting a subtle ordering bug

Copilot AI requested review from Copilot and lokitoth and removed request for Copilot May 13, 2026 19:49
@moonbox3 moonbox3 added documentation Improvements or additions to documentation .NET workflows Related to Workflows in agent-framework labels May 13, 2026
@github-actions github-actions Bot changed the title Validate MessageMerger ordering invariants .NET: Validate MessageMerger ordering invariants May 13, 2026
Copilot AI requested review from Copilot and removed request for Copilot May 13, 2026 20:47
Copilot AI requested review from Copilot and removed request for Copilot May 13, 2026 20:51
Copilot AI review requested due to automatic review settings May 13, 2026 20:52
Copy link
Copy Markdown
Contributor

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

Documents and pins the implicit ordering/grouping contract of the internal MessageMerger used by Workflows, fixes a subtle bug where untimestamped updates were unpredictably pushed to the end (broke multi-agent transcripts), and removes dead state.

Changes:

  • Rewrites CompareByDateTimeOffset to fall back to insertion index when timestamps are missing or equal, preserving arrival order; threads insertion indices through ComputeMerged.
  • Removes the unused createdTimes HashSet.
  • Adds ADR 0026 documenting three merger invariants (single ResponseId per turn, output order preservation, per-ResponseId grouping) plus known edge cases, and adds extensive MessageMergerTests coverage.

Reviewed changes

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

File Description
dotnet/src/Microsoft.Agents.AI.Workflows/MessageMerger.cs Index-aware comparer fixing untimestamped ordering; removes dead createdTimes state.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageMergerTests.cs New regression tests for insertion order, mixed-timestamp determinism, per-ResponseId grouping, and function call/result ordering.
docs/decisions/0026-message-merger-invariants.md New ADR capturing merger invariants, decision rationale, and known edge cases.
Comments suppressed due to low confidence (1)

dotnet/src/Microsoft.Agents.AI.Workflows/MessageMerger.cs:108

  • The new comparer is intentionally non-transitive (as called out in the ADR's "Known edge cases" #1). List<T>.Sort uses introsort, which gives undefined results for non-transitive comparers and may even throw InvalidOperationException on some inputs in the runtime's checked sort paths. Even though the chosen tests pass today, this is a latent bug waiting to happen as input shapes vary. A safer approach would be to use a strictly transitive key — for example, sort by (CreatedAt ?? sentinel, insertionIndex) where the sentinel makes untimestamped messages either always earliest or always latest, then break ties with insertion index — or perform a stable sort over only the timestamped subset and merge back by insertion index. Worth considering even if deferred to a follow-up.
    private int CompareByDateTimeOffset(AgentResponse left, int leftIndex, AgentResponse right, int rightIndex)
    {
        if (!left.CreatedAt.HasValue || !right.CreatedAt.HasValue || left.CreatedAt == right.CreatedAt)
        {
            return leftIndex.CompareTo(rightIndex);
        }

        return left.CreatedAt.Value.CompareTo(right.CreatedAt.Value);
    }

Comment thread dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/MessageMergerTests.cs Outdated
@lokitoth lokitoth moved this to In Progress in Agent Framework May 14, 2026
@lokitoth lokitoth marked this pull request as ready for review May 14, 2026 02:03
@lokitoth lokitoth marked this pull request as draft May 14, 2026 04:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation .NET workflows Related to Workflows in agent-framework

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

.NET: Workflow Host-as-Agent can improperly reorder messages in history and when running non-streaming

4 participants