Skip to content

Python: fix(ag-ui): dedupe client-replayed transcripts on resume - #8149

Open
LI (ktz03) wants to merge 6 commits into
microsoft:mainfrom
ktz03:fix/ag-ui-resume-transcript-dedupe
Open

Python: fix(ag-ui): dedupe client-replayed transcripts on resume#8149
LI (ktz03) wants to merge 6 commits into
microsoft:mainfrom
ktz03:fix/ag-ui-resume-transcript-dedupe

Conversation

@ktz03

@ktz03 LI (ktz03) commented Sep 8, 2026

Copy link
Copy Markdown

Motivation & Context

AG-UI resume can receive a client-replayed transcript that already overlaps the thread snapshot. Naively concatenating snapshot history with the resume payload duplicated message ids and broke HITL / confirm_changes resume semantics.

Fixes the non-use_service_session resume path so overlapping transcripts are reconciled instead of appended twice. Scenario: AG-UI client resumes after interrupt with a replayed conversation history.

Fixes #8140

Description & Review Guide

  • What are the major changes?

    • Seed history via _reconstruct_messages_from_thread_snapshot / ThreadSnapshotSession.reconcile_resume_messages instead of naive concatenation on non-service AG-UI resume.
    • Apply the same reconstructor for workflow resume / checkpoint builder seeding when a stored snapshot exists.
    • Empty generic resumes stay on the save-time prepend path (do not reconcile []); only non-empty client-replayed resumes are reconciled and marked seeded.
    • Add a focused regression test for replayed-transcript vs interrupt-only resume shapes.
  • What is the impact of these changes?

    • Prevents duplicate message ids on AG-UI resume when the client replays the transcript.
    • Keeps interrupt-only / empty confirm_changes resume behavior unchanged.
  • What do you want reviewers to focus on?

    • Empty vs non-empty resume branching in reconcile_resume_messages / generic resume path.
    • That workflow checkpoint seeding uses the same reconstructor when a snapshot exists.

Related Issue

Fixes #8140

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

)

Route non-service-session (and workflow) resume seeding through _reconstruct_messages_from_thread_snapshot so a reference AG-UI client that replays its transcript is not double-persisted into the thread snapshot.
Copilot AI balanced review requested due to automatic review settings September 8, 2026 13:10
@ktz03
LI (ktz03) deployed to github-app-auth September 8, 2026 13:10 — with GitHub Actions Active
@ktz03
LI (ktz03) deployed to github-app-auth September 8, 2026 13:10 — with GitHub Actions Active
@agent-framework-automation agent-framework-automation Bot added the python Usage: [Issues, PRs], Target: Python label Sep 8, 2026
@ktz03
LI (ktz03) deployed to github-app-auth September 8, 2026 13:11 — with GitHub Actions Active

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.

🟡 Changes recommended

Empty-message resumes can lose stored history in both agent and workflow paths.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Updates AG-UI resume handling to deduplicate client-replayed transcripts using snapshot reconstruction.

Changes:

  • Applies reconstruction to agent and workflow resume history.
  • Adds transcript deduplication regression tests.
File summaries
File Review
python/packages/ag-ui/tests/ag_ui/test_resume_transcript_dedupe.py Adds tests, but misses empty-message integration coverage and the required copyright header.
python/packages/ag-ui/agent_framework_ag_ui/_workflow.py Reconstruction drops stored history for empty checkpoint-resume input.
python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py Reconstruction drops stored history for empty approval-resume input.
Review details

Suppressed comments (1)

python/packages/ag-ui/tests/ag_ui/test_resume_transcript_dedupe.py:1

  • This new Python file is missing the required first-line copyright header mandated by python/CODING_STANDARD.md:453 and used throughout this test directory.
"""Regression for AG-UI resume with client-replayed transcript (#8140)."""
  • Files reviewed: 3/3 changed files
  • Comments generated: 3
  • Review effort level: Balanced

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

Comment thread python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py Outdated
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_workflow.py Outdated
Comment thread python/packages/ag-ui/tests/ag_ui/test_resume_transcript_dedupe.py Outdated
@ktz03

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Use the transcript reconstructor only when the client sends a non-empty messages list; empty approval/checkpoint resumes still go through resume_seeded_messages so stored history is preserved.

Also add the required copyright header and document the empty-input reconstructor contract in tests.
@ktz03
LI (ktz03) deployed to github-app-auth September 8, 2026 15:13 — with GitHub Actions Active
@ktz03

Copy link
Copy Markdown
Author

Addressed Copilot review:

  1. Empty messages on resume — agent (_agent_run) and workflow (_workflow) now use _reconstruct_messages_from_thread_snapshot only when the client sent a non-empty transcript; empty approval/checkpoint resumes still go through resume_seeded_messages so stored history is kept.
  2. Tests — added the required copyright header, documented the empty-input reconstructor contract, and clarified the partial-turn case.

Existing test_endpoint / test_workflow_agent empty-resume assertions should stay green with this branching.

@ktz03

Copy link
Copy Markdown
Author

Addressed on head 3bb98c18:

  • Empty messages on approval/checkpoint resume again goes through resume_seeded_messages (agent + workflow), so stored history is preserved.
  • Non-empty / client-replayed transcripts still use _reconstruct_messages_from_thread_snapshot for dedupe.
  • Added a unit test documenting the reconstructor’s empty-input contract ([] unchanged → call sites must seed).

Existing test_endpoint / test_workflow_agent empty-resume assertions should stay green with this branching. Please re-run Copilot review on the latest head when convenient.

Comment thread python/packages/ag-ui/agent_framework_ag_ui/_workflow.py Outdated
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py
Add reconcile_resume_messages for empty vs replayed resume shapes, use it from agent and workflow runners, and mark generic reconstructed resumes as seeded so save-time prepend does not duplicate history.
@ktz03

Copy link
Copy Markdown
Author

Thanks Evan Mattson (@moonbox3) — addressed both points on this head:

  1. ThreadSnapshotSession.reconcile_resume_messages now owns the empty-vs-nonempty resume decision (empty → resume_seeded_messages, non-empty → overlap reconstructor). Agent approval resume and workflow resume/checkpoint seeding both call this single method.
  2. Generic non-service resumes (resume_payload without approval-interrupt ids) also go through reconcile and set seeded_resume_from_snapshot = True, so save-time resume_seeded_messages no longer double-prepends after a client-replayed transcript.

Added a unit test covering the session-owned empty vs replayed shapes.

@ktz03
LI (ktz03) deployed to github-app-auth September 9, 2026 03:38 — with GitHub Actions Active
@moonbox3

Copy link
Copy Markdown
Contributor

Please resolve individual comments with a note if addressed or not. Then resolve when complete.

@ktz03

Copy link
Copy Markdown
Author

Evan Mattson (@moonbox3) Done — replied on each review thread with addressed/partial notes and resolved all five threads. Please take another look when convenient.

@moonbox3 Evan Mattson (moonbox3) 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.

Please also fix the failing CI/CD checks.

Comment thread python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py Outdated
Generic/predictive resumes with messages:[] must not prepend stored history into the turn input — confirm_changes synthesizes only the resume tool message and short-circuits without another model call. Seeding that history caused approval mismatch warnings and an extra stream invocation (test_agent_endpoint_confirm_changes_clears_persisted_interrupt).

Only non-empty client-replayed generic resumes go through reconcile_resume_messages + seeded_resume_from_snapshot. Approval and workflow/checkpoint paths still seed empty via reconcile.
@ktz03
LI (ktz03) deployed to github-app-auth September 9, 2026 09:28 — with GitHub Actions Active
@ktz03

Copy link
Copy Markdown
Author

Evan Mattson (@moonbox3) Fixed on head \�682021\ — empty generic resumes no longer seed via
econcile_resume_messages([])\ (that was causing the confirm_changes \call_count\ regression / CI failure). Only non-empty client-replayed generic resumes are reconciled and marked seeded; empty confirm_changes stays on the save-time prepend path as you described. Please re-check when convenient.

@eavanvalkenburg

Copy link
Copy Markdown
Member

LI (@ktz03) make sure to use the defined PR template

@ktz03

Copy link
Copy Markdown
Author

Eduard van Valkenburg (@eavanvalkenburg) Updated the description to follow the repo PR template — please take another look when convenient.

@ktz03
LI (ktz03) deployed to github-app-auth September 9, 2026 13:15 — with GitHub Actions Active
…ssages

Address review: empty incoming still prepends stored history; non-empty uses the overlap reconstructor so client-replayed transcripts are not duplicated.
@ktz03
LI (ktz03) deployed to github-app-auth September 9, 2026 14:45 — with GitHub Actions Active
@ktz03

Copy link
Copy Markdown
Author

Eduard van Valkenburg (@eavanvalkenburg) Folded reconcile_resume_messages into resume_seeded_messages on 40a0733 as suggested — one method owns both empty-prepend and non-empty reconstruct. Please take another look when convenient.

Per review: keep resume_seeded_messages as blind prepend (save-time / empty seeds). Use reconstruct only for non-empty client-replayed transcripts. Remove dedicated small test file and fold coverage into test_snapshot_session.
@ktz03
LI (ktz03) deployed to github-app-auth September 9, 2026 23:09 — with GitHub Actions Active
@ktz03

Copy link
Copy Markdown
Author

Eduard van Valkenburg (@eavanvalkenburg) Addressed both review points on latest head:

  1. No separate reconcile_* method — resume_seeded_messages stays blind prepend; non-empty client replays use _reconstruct_messages_from_thread_snapshot at the call site.
  2. Removed the small dedicated test file; coverage folded into test_snapshot_session.

This should also clear the confirm_changes / prepend CI regressions from the earlier combine attempt. Please re-check when convenient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: AG-UI resume duplicates the interrupted turn when the client replays its transcript

4 participants