Python: Fix A2AAgent context_providers to skip inactive history providers and suppress type errors#4764
Python: Fix A2AAgent context_providers to skip inactive history providers and suppress type errors#4764eavanvalkenburg wants to merge 8 commits intomicrosoft:mainfrom
Conversation
A2AAgent.run() overrode BaseAgent.run() without calling before_run/after_run on context_providers. Added _run_before_providers() to invoke before_run on each provider before the A2A call, and _run_after_providers() after the response completes, in both streaming and non-streaming paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…osoft#4754) Add type suppression comments for: - reportPrivateUsage on SessionContext._response access (cross-package) - reportUnknownMemberType/reportUnknownVariableType on ResponseStream chained calls, matching patterns used in core package Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…p logic - Remove REPRODUCTION_REPORT.md (debugging artifact, not for commit) - Add BaseHistoryProvider load_messages=False skip in _run_before_providers to match the pattern used in _prepare_session_and_messages and WorkflowAgent Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eavanvalkenburg
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 90%
✓ Correctness
The diff makes two changes: (1) removes a REPRODUCTION_REPORT.md debugging artifact that should not be committed, and (2) adds a BaseHistoryProvider skip check in A2AAgent._run_before_providers to match the identical pattern in BaseAgent._prepare_session_and_messages. Both changes are correct. The import of BaseHistoryProvider is valid (exported from agent_framework.init), and the skip logic correctly prevents calling before_run on history providers with load_messages=False, consistent with BaseAgent's behavior. The existing tests cover context provider invocation for both streaming and non-streaming paths and should continue to pass since they use BaseContextProvider (not BaseHistoryProvider with load_messages=False).
✓ Security Reliability
The diff makes two changes: (1) removes a debug investigation artifact (REPRODUCTION_REPORT.md) from the repo root, and (2) adds a BaseHistoryProvider skip check in A2AAgent._run_before_providers to match the identical pattern in BaseAgent._prepare_session_and_messages (line 1318-1321 of _agents.py). The code change is correct and consistent with the base class behavior. No security issues (no injection risks, secrets, or unsafe deserialization). There is a pre-existing reliability concern where _run_after_providers is not called if the A2A stream or response processing throws an exception (no try/finally guard), but this is the same pattern used throughout the base class and is not introduced by this diff.
✗ Test Coverage
The diff adds a
BaseHistoryProviderskip-when-load_messages=Falsecheck toA2AAgent._prepare_session_and_messages(), mirroring the same logic inBaseAgent._prepare_session_and_messages(). However, there are zero tests in the A2A test suite covering this new conditional branch. No test verifies that aBaseHistoryProviderwithload_messages=Falsehas itsbefore_runskipped, nor that one withload_messages=Truestill hasbefore_runcalled. The user also notes tests are failing on the PR, which should be investigated. Additionally, theREPRODUCTION_REPORT.mddeletion is fine—it was a scratch artifact.
✓ Design Approach
The fix correctly mirrors the existing
BaseAgent._prepare_session_and_messagespattern, which skipsbefore_runforBaseHistoryProviderinstances withload_messages=Falsewhile still allowingafter_run(for storing). All 50 A2A tests pass locally. The one design concern is a pre-existing abstraction leak — agents encode theload_messagessemantics as a skip condition rather than havingBaseHistoryProvider.before_runbe a no-op whenload_messages=False. This leak now exists in three places (BaseAgent, A2AAgent, and the workflow agent). However, since this PR is fixing a consistency bug (A2AAgent not calling providers at all), matching the existing pattern is the right approach. No new blocking issues are introduced by this change.
Flagged Issues
- No test covers the new BaseHistoryProvider with load_messages=False skip logic in A2AAgent._prepare_session_and_messages(). Add at least two tests: (1) a BaseHistoryProvider subclass with load_messages=False should NOT have before_run called, and (2) a BaseHistoryProvider subclass with load_messages=True (the default) should still have before_run called.
- The user reports tests are failing on the PR. The new code path needs to be verified with passing tests before merge. All 50 A2A tests pass locally, so if CI is failing it may be due to the unrelated AgentFrameworkWorkflow import error in ag-ui tests — worth confirming the specific failing test names.
Suggestions
- Consider adding edge-case tests: (1) a mix of BaseContextProvider and BaseHistoryProvider(load_messages=False) verifying the regular provider's before_run is still called while the history provider's is skipped, and (2) verifying after_run is still called for a BaseHistoryProvider with load_messages=False.
- Consider wrapping the non-streaming and streaming paths in try/finally to ensure _run_after_providers is always called even when the A2A stream or response processing throws. This is a pre-existing issue shared with the base class, not introduced by this PR, but worth addressing in a follow-up.
- The load_messages=False skip logic is now duplicated across three agent implementations (BaseAgent, A2AAgent, _workflows/_agent.py). Consider moving this skip into BaseHistoryProvider.before_run itself (making it a no-op when load_messages=False) so agents don't need to know about this flag. This would be a follow-up cleanup, not a blocker for this fix.
Automated review by eavanvalkenburg's agents
There was a problem hiding this comment.
Pull request overview
Aligns A2AAgent context provider execution with other agent implementations by skipping inactive history providers and addressing static type-checker complaints in the run() streaming/non-streaming paths.
Changes:
- Refactors
A2AAgent.run()to consistently invoke context providers (and adds_run_before_providers()helper). - Skips
BaseHistoryProviderinstances whenload_messages=Falseduringbefore_run. - Adds unit tests validating context provider invocation order and session handling.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| python/packages/a2a/agent_framework_a2a/_agent.py | Refactors run() to run provider hooks and skips inactive history providers; adds pyright suppression for private access and stream chaining. |
| python/packages/a2a/tests/test_a2a_agent.py | Adds coverage for context provider before_run/after_run invocation (streaming + non-streaming), ordering, and session propagation. |
You can also share your feedback on Copilot code review. Take the survey.
…tory provider tests - Fix line 350: replace duplicate # type: ignore with single # pyright: ignore - Add tests for BaseHistoryProvider with load_messages=False skip logic: - before_run is NOT called when load_messages=False - before_run IS called when load_messages=True (default) - after_run is always called regardless of load_messages - Streaming variant of load_messages=False - Mixed providers: regular provider still invoked alongside skipped history provider Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… test - Add fast-path in run() to skip provider hooks when no context_providers are configured, avoiding unnecessary SessionContext allocation - Add FailingHistoryProvider test: before_run raises AssertionError if called, proving load_messages=False skip logic works correctly - Previous commit already fixed pyright ignore syntax on line 350 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ing path (microsoft#4754) Initialize active_session and session_context before the conditional context_providers block so pyright can verify they are always bound. Add explicit None check for session_context in the after-providers guard. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eavanvalkenburg
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 85%
✓ Correctness
The fix correctly addresses an UnboundLocalError that occurs when
self.context_providersis truthy at the guard check on line 314 butactive_sessionandsession_contextwere never assigned (or more accurately, when the variables would be referenced without being assigned in theif self.context_providersblock). By initializing both variables toNonebefore the conditional block and adding asession_context is not Noneguard before accessing it, the code now safely handles all paths. The streaming path already had equivalent guards (lines 328-329). The fix is minimal, correct, and consistent with the streaming path's defensive pattern.
✓ Security Reliability
The fix correctly addresses an UnboundLocalError that occurs when
self.context_providersis truthy but_run_before_providerswas never called (or failed before assignment). By initializingactive_sessionandsession_contexttoNoneand adding asession_context is not Noneguard, the code avoids referencing potentially unbound locals. The streaming path (lines 322-332) already had equivalent None-safety via holder dicts and an explicitif session_context is None: returnguard, so this change brings the non-streaming path to parity. No security or reliability issues are introduced by this diff.
✗ Test Coverage
The fix initializes
active_sessionandsession_contexttoNoneand adds a defensivesession_context is not Noneguard, aligning the non-streaming path with the streaming path's existing pattern (line 329). This is a sound type-safety/defensive fix. However, there is no new test covering the exact edge case this change guards against, and there is a pre-existing gap: no test exercises the non-streamingcontinuation_tokenpath combined withcontext_providers, which is the code area being changed. Given the user noted failing tests, adding a targeted test for the non-streaming continuation+providers scenario would both validate the fix and prevent regressions.
✓ Design Approach
The diff correctly pre-initializes
active_sessionandsession_contexttoNonebefore the conditional block to prevent potentialUnboundLocalError(and satisfy pyright's 'possibly unbound' analysis), then adds asession_context is not Noneguard at the post-run call. The approach is sound:context_providersis a plainlistattribute (not a property), so it cannot return different values between the two checks in the same invocation, meaning theis not Noneguard is technically redundant whenself.context_providersis truthy—but it is correct and harmless. The underlying pattern (two separateif self.context_providers:guards that are coupled through local variables) is a bit fragile, but the fix is the minimal correct change. No design flaws are introduced. If tests are still failing on the PR, the root cause is likely in other files not shown in this diff (e.g., missing test fixtures, unimplemented streaming path parity, or other changes in the PR that are not reflected here), not in these two lines.
Flagged Issues
- No test covers the non-streaming
continuation_token+context_providerscombination. The existingtest_resume_via_continuation_tokenuses the defaulta2a_agentfixture (no context providers), so the modified guard at line 314 is never exercised. A test liketest_resume_via_continuation_token_with_context_providersis needed to validate the fix and prevent regressions.
Suggestions
- Add a test for the negative case: an agent with an empty
context_providerslist called withcontinuation_token, confirmingsession_contextremainsNoneand no crash occurs. This validates the newis not Noneguard specifically. - Consider collapsing both
if self.context_providers:blocks into a single try/finally or extracted helper to make the paired before/after contract structurally explicit and harder to break in future edits.
Automated review by eavanvalkenburg's agents
…4754) Add two tests exercising the non-streaming continuation_token path: - test_resume_via_continuation_token_with_context_providers: validates that before_run/after_run are invoked when resuming with providers - test_resume_via_continuation_token_no_context_providers: validates no crash when resuming without providers (exercises the guard at line 314) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eavanvalkenburg
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 90%
✓ Correctness
The two new tests are well-structured and follow established patterns already used throughout the test file (e.g., test_resume_via_continuation_token on line 775 and test_run_invokes_context_providers on line 880). The mock setup, assertion patterns, and API usage are all correct. A2AContinuationToken is a TypedDict and the bracket-access pattern matches the production code. The _run_non_streaming path correctly gates context-provider calls behind
if self.context_providers:, so the no-providers test should pass without error. If tests are currently failing on this PR, the root cause is likely in production code changes not shown in this diff rather than in these test definitions.
✓ Security Reliability
The diff adds two new test functions for the A2A agent's continuation_token resume flow: one verifying context providers are invoked during resume, and another verifying the agent works without context providers. Both tests are well-structured, use existing mock infrastructure (MockA2AClient.resubscribe_responses), and follow the same patterns as surrounding tests. No security or reliability issues are present — these are pure unit tests with no trust-boundary changes, no deserialization of untrusted data, no resource management concerns, and no secrets. The tests appear consistent with the existing implementation of A2AAgent.run() which calls context provider hooks regardless of whether a continuation_token is used.
✓ Test Coverage
The two new tests cover continuation-token resume paths with and without context providers. They are structurally sound and follow existing patterns in the file (TrackingContextProvider, MockA2AClient.resubscribe_responses). The assertions are meaningful, checking provider invocation flags and response content. However, the streaming resume path with context providers is not tested by these new tests (only the non-streaming path is), representing a gap. Also, the 'no providers' test does not verify that
mock_a2a_client.call_countchanged, which would confirm the resubscribe path was actually exercised rather than silently short-circuiting.
✓ Design Approach
The two new tests verify that context providers are correctly invoked (before_run / after_run) on the continuation-token / resubscribe code path, and that the no-providers path works without crashing. All 57 tests in the suite pass. The tests are well-targeted: they reuse the existing TrackingContextProvider and MockA2AClient infrastructure, and they test real observable behavior (context provider lifecycle on the resume path) rather than masking or papering over an underlying issue. No design concerns were found.
Suggestions
- Consider adding a test for the streaming resume path (
stream=True) with context providers to match the non-streaming coverage added here. - In
test_resume_via_continuation_token_no_context_providers, assertmock_a2a_client.call_count == 1to verify the resubscribe path was actually invoked, similar to how existing tests validate interaction with the mock client.
Automated review by eavanvalkenburg's agents
Motivation and Context
A2AAgent's
_run_before_providersdid not skipBaseHistoryProviderinstances whenload_messages=False, unlike the behavior inBaseAgent._prepare_session_and_messagesandWorkflowAgent. This inconsistency caused unexpected errors when context providers were configured onA2AAgent. Additionally, pyright reported type errors on cross-package private attribute access and chainedResponseStreamcalls.Fixes #4754
Description
The root cause was that
A2AAgent._run_before_providersunconditionally invokedbefore_runon all context providers, includingBaseHistoryProviderinstances withload_messages=Falsethat should be skipped—a pattern already established in the core agent implementations. The fix adds anisinstancecheck to skip those providers, matching the behavior inBaseAgentandWorkflowAgent. It also adds type suppression comments forSessionContext._responseprivate attribute access andResponseStreamchained method calls, aligning with patterns used elsewhere in the codebase.Contribution Checklist