Skip to content

Python: Fix A2AAgent to invoke context providers before and after run#4757

Merged
eavanvalkenburg merged 5 commits intomicrosoft:mainfrom
eavanvalkenburg:agent/fix-4754-1
Mar 19, 2026
Merged

Python: Fix A2AAgent to invoke context providers before and after run#4757
eavanvalkenburg merged 5 commits intomicrosoft:mainfrom
eavanvalkenburg:agent/fix-4754-1

Conversation

@eavanvalkenburg
Copy link
Member

Motivation and Context

A2AAgent's run() method did not call before_run/after_run on configured context providers, making it inconsistent with BaseAgent's contract and silently ignoring any context providers passed to it.

Fixes #4754

Description

The root cause was that A2AAgent.run() and its internal _map_a2a_stream() method never invoked the context provider lifecycle hooks that BaseAgent defines. The fix adds calls to each provider's before_run() at the start of _map_a2a_stream() and delegates to _run_after_providers() after the stream is fully consumed, matching the pattern used by other agent implementations. A SessionContext is constructed with input messages and populated with the response before calling after-run hooks, and an AgentSession is auto-created when providers are configured but no session is explicitly passed.

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? If yes, add "[BREAKING]" prefix to the title of the PR.

Note: PR autogenerated by eavanvalkenburg's agent

Copilot and others added 3 commits March 18, 2026 08:19
A2AAgent.run() bypassed the context provider lifecycle (before_run/after_run)
that BaseAgent defines as a contract for all agents. This caused A2AAgent to
violate the semantic definition of BaseAgent, resulting in inconsistency with
other agent implementations.

The fix follows the same pattern used by WorkflowAgent:
- Create SessionContext and run before_run on all context providers before
  processing the A2A stream
- Collect response updates and run after_run on all context providers after
  the stream is fully consumed
- Auto-create a session when context providers are configured but no session
  is explicitly passed

Fixes microsoft#4754

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@eavanvalkenburg eavanvalkenburg self-assigned this Mar 18, 2026
Copilot AI review requested due to automatic review settings March 18, 2026 08:23
Copy link
Member Author

@eavanvalkenburg eavanvalkenburg left a comment

Choose a reason for hiding this comment

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

Automated Code Review

Reviewers: 4 | Confidence: 95%

✓ Correctness

This diff deletes a REPRODUCTION_REPORT.md file from the repository root. The file was a temporary investigation artifact containing internal paths, timestamps, and debug notes from reproducing issue #4754. Removing it is correct — such files should not be committed to the repository. No code changes, no correctness concerns.

✓ Security Reliability

This diff deletes a REPRODUCTION_REPORT.md file that was accidentally committed to the repository. The file contained internal debugging notes, local filesystem paths, and investigation artifacts that should not be part of the codebase. The deletion is correct and desirable — the file leaked internal worktree paths and investigation workflow details. No security or reliability issues with this deletion; it is purely cleanup of a file that should never have been committed.

✓ Test Coverage

This diff only deletes a REPRODUCTION_REPORT.md file that was used as an internal investigation artifact. It contains no code changes, no test changes, and no behavioral modifications. There is nothing to review from a test coverage perspective.

✓ Design Approach

The diff deletes REPRODUCTION_REPORT.md, a temporary investigation artifact that should never have been committed to the repository. This is a cleanup-only change with no functional impact. There are no design concerns to flag.

Suggestions

  • Consider adding REPRODUCTION_REPORT.md to .gitignore to prevent accidental re-commits of investigation artifacts.

Automated review by eavanvalkenburg's agents

@markwallace-microsoft
Copy link
Member

markwallace-microsoft commented Mar 18, 2026

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/a2a/agent_framework_a2a
   _agent.py2051692%346, 351, 353, 434, 455, 476, 496, 510, 524, 536–537, 579–580, 609–611
TOTAL24025264189% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
5262 20 💤 0 ❌ 0 🔥 1m 26s ⏱️

Copy link
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

This PR fixes A2AAgent.run() to honor the BaseAgent context provider lifecycle by invoking before_run()/after_run() around A2A streaming/non-streaming execution, and adds tests to validate that behavior (including session auto-creation when needed).

Changes:

  • Invoke context providers’ before_run() at the start of A2A streaming and after_run() once the stream is consumed.
  • Construct and populate a SessionContext (including response attachment for after_run()).
  • Add unit tests covering provider invocation for both streaming and non-streaming runs.

Reviewed changes

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

File Description
python/packages/a2a/agent_framework_a2a/_agent.py Adds provider lifecycle hook invocation and SessionContext plumbing to A2AAgent run/stream mapping.
python/packages/a2a/tests/test_a2a_agent.py Adds tests ensuring context providers are invoked and receive input/response context.

You can also share your feedback on Copilot code review. Take the survey.

- Validate messages when no continuation_token: raise ValueError if
  normalized_messages is empty, preventing IndexError on messages[-1]
- Import BaseContextProvider/SessionContext from public agent_framework
  package instead of internal agent_framework._sessions module
- Add test for ValueError on run(None) without continuation_token

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Member Author

@eavanvalkenburg eavanvalkenburg left a comment

Choose a reason for hiding this comment

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

Automated Code Review

Reviewers: 4 | Confidence: 92%

✓ Correctness

The diff adds a guard to raise a ValueError when run() is called with no messages and no continuation_token, preventing an IndexError on normalized_messages[-1]. The test correctly verifies this behavior by passing None. The import refactoring in the test file moves BaseContextProvider and SessionContext from a private module to the public agent_framework namespace, which is correct since both are exported from init.py. All changes are logically sound.

✓ Security Reliability

The change adds an early validation check in A2AAgent.run() to raise a ValueError when no messages are provided and there is no continuation_token. This prevents a downstream IndexError on normalized_messages[-1] (empty list access) and makes the failure mode explicit. The test correctly validates this behavior. The import refactor in the test file switches from a private module path to the public API, which is a healthy cleanup. No security or reliability issues found.

✗ Test Coverage

The new guard clause correctly raises ValueError when no messages and no continuation_token are provided, and the test covers the None-input case with a meaningful assertion (matching the error message). However, the test only checks None input — the empty-list case (await agent.run([])) also triggers the guard but is not tested. Additionally, there is no test verifying that the guard does NOT fire when a continuation_token is present with no messages, which is the complementary valid path.

✓ Design Approach

The change correctly adds a precondition guard before accessing normalized_messages[-1], converting a confusing IndexError into a descriptive ValueError. The validation is placed after normalization and inside the correct branch where the message is actually required, so it accurately captures the contract: messages are optional only when a continuation_token is provided. The test import refactor (moving BaseContextProvider and SessionContext to the public agent_framework import) is a clean, unrelated improvement. No design issues were found — the fix targets the root cause and is not overly narrow.

Suggestions

  • Add a test case for await agent.run([]) (empty list) without continuation_token, since normalize_messages([]) also produces an empty list and hits the same guard. Consider parameterizing the existing test to cover both None and [].
  • Add a test verifying that run(None, continuation_token={...}) does NOT raise, confirming the guard is correctly scoped to the no-continuation-token branch.

Automated review by eavanvalkenburg's agents

…soft#4754)

- Parameterize test to cover both messages=None and messages=[] inputs
- Add test verifying run(None, continuation_token=...) does not raise

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Member Author

@eavanvalkenburg eavanvalkenburg left a comment

Choose a reason for hiding this comment

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

Automated Code Review

Reviewers: 4 | Confidence: 91%

✓ Correctness

The changes are correct. The parametrize decorator properly tests both None and empty-list cases for the ValueError path. The new continuation_token test correctly sets up the mock with a terminal-state task in resubscribe_responses matching the expected (Task, event) tuple format consumed by _map_a2a_stream. The pytest.raises match string is a valid substring of the actual error message. No logical issues found.

✓ Security Reliability

This diff modifies a test file only, adding parametrization to an existing test (covering both None and empty list inputs) and adding a new test for the continuation_token path. The changes are straightforward test improvements with no security or reliability concerns — no production code is modified, no secrets are introduced, no unsafe patterns are used, and the mock setup correctly matches the production code's resubscribe interface.

✓ Test Coverage

The diff improves test coverage by parametrizing the 'no messages' validation test to cover both None and empty-list inputs, and adds a new test verifying that a continuation_token bypasses the message requirement. Both changes are well-structured. However, the new continuation_token test (test_run_with_continuation_token_does_not_require_messages) only asserts response is not None, which is a weak assertion—especially since a very similar existing test (test_resume_via_continuation_token at line 775) already validates response content in detail. The new test adds value by verifying the no-raise path specifically, but its assertions could be stronger.

✓ Design Approach

The diff adds two things: (1) a parametrize decorator to cover the empty-list case alongside None in the existing validation test, and (2) a new test confirming that a continuation_token bypasses the message requirement. Both additions are correct and well-targeted. The parametrized test properly covers the [] branch of not normalized_messages, and the continuation-token test faithfully exercises the resubscribe path. The only minor design concern is that the new test name (test_run_with_continuation_token_does_not_require_messages) only covers messages=None, not messages=[]; the empty-list case with a continuation token is equally valid and untested, but this is a coverage gap rather than a fundamentally wrong approach.

Suggestions

  • The assertion assert response is not None in test_run_with_continuation_token_does_not_require_messages is weak. Consider additionally asserting on isinstance(response, AgentResponse) and verifying mock_a2a_client.call_count to confirm the resubscribe path was actually exercised, similar to how test_resume_via_continuation_token validates the response content.
  • The new continuation-token test only passes messages=None. Consider also parametrizing it with [] (empty list), since the production guard checks not normalized_messages, making both values equivalent — the same gap that motivated the parametrize fix in the first test.

Automated review by eavanvalkenburg's agents

@eavanvalkenburg eavanvalkenburg added this pull request to the merge queue Mar 19, 2026
Merged via the queue into microsoft:main with commit 4b21f38 Mar 19, 2026
44 of 52 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: A2AAgent inconsistent with BaseAgent: context_providers not triggered

5 participants