Skip to content

Python: fire loop turn-scoped after_run only on clean streaming exit - #9818

Open
RongJie G (CorgiBoyG) wants to merge 1 commit into
microsoft:mainfrom
CorgiBoyG:fix/loop-turn-scoped-after-run-clean-exit
Open

RongJie G (CorgiBoyG) wants to merge 1 commit into
microsoft:mainfrom
CorgiBoyG:fix/loop-turn-scoped-after-run-clean-exit

Conversation

@CorgiBoyG

@CorgiBoyG RongJie G (CorgiBoyG) commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Motivation & Context

After a successful streaming loop iteration, an exception or early close during a later iteration still invokes turn-scoped after_run providers with the previous iteration's response. The streaming loop invokes them unconditionally from finally, while the non-streaming loop skips them when execution fails.

This makes provider finalization depend on the streaming mode. Providers can receive a stale response and run side effects after an incomplete loop. For example, CompactionProvider with an after_strategy can run its stored-history compaction through this unexpected callback.

Description & Review Guide

  • What are the major changes? Gate turn-scoped finalization on completed_cleanly, set after the loop exits normally. Convert the three normal-stop returns to breaks so they reach that point.
  • What is the impact of these changes? Normal stopping, iteration limits, pending approval and handled MiddlewareTermination retain their existing behavior. Propagating exceptions, cancellation and early close skip turn-scoped finalization. The inner stream's context manager and iteration-token cleanup remain unchanged.
  • What do you want reviewers to focus on? Whether every intentional stop reaches finalization and every failed or abandoned iteration preserves cleanup without invoking the deferred providers.

Related Issue

Fixes #9817

Eduard van Valkenburg (@eavanvalkenburg) marked the issue ready-for-implementation. No competing open PR was found.

AI Assistance

  • No material AI assistance was used.
  • This is an AI-assisted contribution. I reviewed, understood, and verified all submitted content and accept responsibility for it.

AI assistance details: Source analysis, implementation, regression tests and PR description. Verification includes deterministic reproduction and regression testing.

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 links to an agreed issue with no competing open PR, or the Related Issue section documents a trivial-change or repository-automation exception.
  • 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.

CI on e93cf0d passes; its Python 3.11 Ubuntu run reports 20,456 passed, 52 skipped and 2 xfailed. Local verification of the patch combined with current main reports 1,201 related tests passed. Lint, formatting and strict type checks pass. The clean-build checkbox is left unchecked because test and dependency setup output includes existing warnings.

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.

🟢 Approval recommended

No active review issues were identified for the change restricting streaming turn-scoped finalization to clean loop exits.

No open issues 🎉

What changed in this PR

Addresses #9817 by aligning streaming loop turn-scoped after_run behavior with non-streaming runs, preventing finalization with a stale response after an exception or early stream closure.

Changes:

  • Gates turn-scoped providers on clean loop completion while preserving unconditional iteration-token cleanup.
  • Converts normal-stop returns to breaks so successful exits still trigger finalization.
  • Adds regression tests for streaming exceptions, early closure, and the non-streaming exception control.
File Description
python/​packages/​core/​tests/​core/​test_harness_loop.py Adds regression coverage for failed and early-closed loop runs.
python/​packages/​core/​agent_framework/​_harness/​_loop.py Restricts streaming turn-scoped finalization to clean exits.

Review effort: Balanced

The streaming AgentLoopMiddleware path fired turn-scoped after_run providers
(after_run_once_per_turn, e.g. CompactionProvider) from a finally block, so an
iteration that raised - or an early stream close - still ran them with the
stale final response from a prior iteration. That diverged from the
non-streaming path and the normal non-loop run contract, both of which skip
after_run when the run raises.

Gate the streaming finally call on a completed_cleanly flag set only after a
clean loop exit (should_continue stop, max_iterations, approval escape hatch,
or MiddlewareTermination), converting the normal-stop returns to break. Normal
completion behavior is unchanged; exception and early-close paths no longer
fire turn-scoped after_run.

Adds deterministic regressions for the streaming exception, streaming
early-close, and the non-streaming exception control.

Fixes microsoft#9817

Signed-off-by: CorgiBoyG <111257566+CorgiBoyG@users.noreply.github.com>
@CorgiBoyG
RongJie G (CorgiBoyG) force-pushed the fix/loop-turn-scoped-after-run-clean-exit branch from 40fb9ac to e93cf0d Compare November 1, 2026 15:12
@CorgiBoyG
RongJie G (CorgiBoyG) marked this pull request as ready for review November 2, 2026 16:06
@CorgiBoyG

Copy link
Copy Markdown
Contributor Author

Eduard van Valkenburg (@eavanvalkenburg) Thanks for marking #9817 ready for implementation. This PR is now ready for review.

CI for e93cf0d passes. I also verified the unchanged patch with current main, including #9681: 1,201 related tests pass, and both streaming regressions still fail with the unfixed method. Inner-stream and iteration-token cleanup remain intact.

This branch was previously deployed

1 inactive deployment
github-app-auth — e93cf0da Deployed Nov 2, 2026 by CorgiBoyG via add_label #26854
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]: AgentLoopMiddleware fires turn-scoped after_run on a failed/early-closed streaming iteration using a stale prior response

2 participants