Skip to content

Instantly share code, notes, and snippets.

@zeroasterisk
Last active June 22, 2026 14:10
Show Gist options
  • Select an option

  • Save zeroasterisk/bf27ea72ef0e6744fed201d2b10930c8 to your computer and use it in GitHub Desktop.

Select an option

Save zeroasterisk/bf27ea72ef0e6744fed201d2b10930c8 to your computer and use it in GitHub Desktop.
ADK A2A v1.0 Migration Plan — upgrading google/adk-python from a2a-sdk 0.3.x to 1.0

ADK A2A v1.0 Migration Plan

Executive Summary

Google's Agent Development Kit (ADK, google/adk-python) currently pins a2a-sdk>=0.3.4,<0.4.0 and uses Pydantic-based types for all A2A protocol objects. The a2a-sdk v1.0 release migrates every protocol type from Pydantic models to Protobuf-generated classes, which breaks construction patterns (Part(root=TextPart(...)) becomes Part(text=...)), enum naming (TaskState.working becomes TASK_STATE_WORKING), field access (.root.text becomes .text), and serialization calls (.model_dump() is gone on protobuf objects). The AgentCard.url field is removed in favor of supported_interfaces.

ADK's A2A integration is marked experimental and spans approximately 20 source files under src/google/adk/a2a/ plus src/google/adk/agents/remote_a2a_agent.py, with roughly 12,000 lines of test coverage across 15+ test files. The migration affects every file in this surface area. The v1.0 SDK ships a a2a.compat.v0_3 compatibility layer, but it does not cover ADK's internal patterns (direct .root access, isinstance checks on discriminated-union members, model_dump/model_validate calls on A2A types). A phased approach -- bump dependency with compat shims first, migrate tests to define the target contract, then update implementation -- minimizes risk.

Current State

Attribute Value
ADK version 2.2.0 (google-adk)
a2a-sdk dependency >=0.3.4,<0.4.0 (main); >=0.3.0,<0.4.0 (dev)
Status Experimental (decorator-gated, suppressible via env var)
Source files (a2a/) ~20 Python modules
Test files 15+ files, ~12,000 lines

Key Classes and Their Roles

Class / Module Role File
A2aAgentExecutor Server-side: runs ADK agent against A2A request executor/a2a_agent_executor.py
_A2aAgentExecutor New (v2) executor implementation executor/a2a_agent_executor_impl.py
A2aAgentExecutorConfig Configuration for executor (converters, interceptors) executor/config.py
ExecutorContext Runtime context (app_name, user_id, session_id, runner) executor/executor_context.py
TaskResultAggregator Aggregates streaming task status into final state executor/task_result_aggregator.py
RemoteA2aAgent Client-side: calls remote A2A agent from ADK agents/remote_a2a_agent.py
A2aRemoteAgentConfig Configuration for remote agent (converters, interceptors) agent/config.py
AgentCardBuilder Builds AgentCard from ADK agent metadata utils/agent_card_builder.py
to_a2a() One-liner to wrap ADK agent as A2A Starlette app utils/agent_to_a2a.py
convert_a2a_part_to_genai_part A2A Part -> GenAI Part converters/part_converter.py
convert_genai_part_to_a2a_part GenAI Part -> A2A Part converters/part_converter.py
convert_event_to_a2a_events ADK Event -> list of A2A events (legacy) converters/event_converter.py
convert_event_to_a2a_events ADK Event -> list of A2A events (new impl) converters/from_adk_event.py
convert_a2a_task_to_event A2A Task -> ADK Event converters/to_adk_event.py
convert_a2a_message_to_event A2A Message -> ADK Event converters/to_adk_event.py
convert_a2a_status_update_to_event A2A TaskStatusUpdateEvent -> ADK Event converters/to_adk_event.py
convert_a2a_artifact_update_to_event A2A TaskArtifactUpdateEvent -> ADK Event converters/to_adk_event.py
LongRunningFunctions Tracks long-running function calls across events converters/long_running_functions.py
Log utilities Structured logging for A2A requests/responses logs/log_utils.py

Test Coverage Summary

Test File Lines Focus
test_part_converter.py 1,057 Part round-trip conversion
test_event_converter.py 992 Legacy event conversion
test_event_round_trip.py 208 Event round-trip consistency
test_from_adk.py 108 ADK Event -> A2A events (new impl)
test_to_adk.py 420 A2A -> ADK Event (new impl)
test_request_converter.py 411 A2A request -> AgentRunRequest
test_utils.py 204 Metadata key utilities
test_a2a_agent_executor.py 1,074 Legacy executor
test_a2a_agent_executor_impl.py 811 New executor
test_task_result_aggregator.py 315 Task result aggregation
test_client_server.py 638 Integration: HTTP round-trip
test_log_utils.py 381 Logging utilities
test_agent_card_builder.py 1,174 AgentCard construction
test_agent_to_a2a.py 1,053 to_a2a() function
test_remote_a2a_agent.py 2,900 RemoteA2aAgent
Total ~12,000

Target State

  • a2a-sdk>=1.0,<2 in pyproject.toml
  • All A2A protocol types use Protobuf-generated classes
  • v1.0 wire protocol (JSON-RPC with protobuf serialization)
  • Backward compatibility maintained for ADK's public API signatures (constructor parameters, return types)
  • to_a2a() produces valid v1.0 agent cards and handles v1.0 messages
  • All existing A2A tests pass

Breaking Changes Inventory

Type System (CRITICAL)

The discriminated union Part changes from Pydantic to a flat protobuf oneof:

v0.3 Pattern v1.0 Pattern Occurrences (src) Occurrences (test)
Part(root=TextPart(text=...)) Part(text=TextPart(text=...)) 3 25
Part(root=FilePart(...)) Part(file=FilePart(...)) 2 ~5
Part(root=DataPart(...)) Part(data=DataPart(...)) 6 ~10
part.root (access inner type) Direct field access (part.text, part.file, part.data) 28 (src), 54 (test) --
isinstance(part.root, TextPart) part.HasField("text") or part.text is not None 5 (src) ~15
isinstance(part.root, DataPart) part.HasField("data") or part.data is not None 3 (src) ~5
isinstance(part.root, FilePart) part.HasField("file") or part.file is not None 1 (src) ~5
isinstance(part.file, FileWithUri) part.file.HasField("uri") or check field presence 1 (src) ~3
isinstance(part.file, FileWithBytes) part.file.HasField("bytes_") or check field presence 1 (src) ~3
part.model_dump_json(...) on A2A types Protobuf serialization (MessageToJson or SDK helper) ~8 (src) ~10
DataPart.model_validate_json(...) Protobuf deserialization (Parse or SDK helper) 1 (src) ~2
part.model_dump(...) on A2A Message/Task Protobuf serialization ~10 (src) ~5

Enums (HIGH)

All enums change from snake_case Pydantic string enums to SCREAMING_SNAKE_CASE protobuf enums:

v0.3 Value v1.0 Value Occurrences (src)
TaskState.submitted TaskState.TASK_STATE_SUBMITTED 3
TaskState.working TaskState.TASK_STATE_WORKING 8
TaskState.completed TaskState.TASK_STATE_COMPLETED 2
TaskState.failed TaskState.TASK_STATE_FAILED 5
TaskState.input_required TaskState.TASK_STATE_INPUT_REQUIRED 7
TaskState.auth_required TaskState.TASK_STATE_AUTH_REQUIRED 5
Role.agent Role.ROLE_AGENT 10
Role.user Role.ROLE_USER 1
Total enum sites ~41 src, ~106 test

Server Architecture (MEDIUM)

v0.3 v1.0 Impact
A2AStarletteApplication Route factory functions (or updated class API) agent_to_a2a.py line 181
A2AStarletteApplication.add_routes_to_app() New route registration pattern agent_to_a2a.py line 187
DefaultRequestHandler constructor May have new/changed parameters agent_to_a2a.py line 157
InMemoryTaskStore May be renamed or restructured agent_to_a2a.py line 148
InMemoryPushNotificationConfigStore May be renamed or restructured agent_to_a2a.py line 155

Client API (MEDIUM)

v0.3 v1.0 Impact
A2AClient.send_message() return type May change from AsyncGenerator[ClientEvent] remote_a2a_agent.py line 669
ClientEvent (tuple of Task, update) Likely restructured remote_a2a_agent.py lines 446-510
A2ACardResolver May have API changes remote_a2a_agent.py line 253
A2AClientFactory / ClientConfig May have parameter changes remote_a2a_agent.py line 237
ClientCallContext May change from Pydantic to protobuf agent/config.py, agent/utils.py

Agent Cards (MEDIUM)

v0.3 v1.0 Impact
AgentCard.url Removed; use supported_interfaces agent_card_builder.py line 82, remote_a2a_agent.py lines 296-308
AgentCard(**json_data) construction Protobuf ParseDict or SDK factory agent_to_a2a.py line 69, remote_a2a_agent.py line 275
AgentCapabilities() default construction Protobuf construction agent_card_builder.py line 65
AgentSkill construction Protobuf construction agent_card_builder.py (many sites)
SecurityScheme type hint May change to protobuf type agent_card_builder.py line 58

Migration Phases

Phase 0: Preparation (no code changes)

  1. Read the a2a-sdk v1.0 migration guide and changelog at a2aproject/a2a-python
  2. Inventory all v0.3 type usage in ADK -- see the interfaces inventory and breaking changes checklist in this gist
  3. Check a2a.compat.v0_3 -- determine which imports can be temporarily aliased through the compat layer
  4. Set up test infrastructure -- ensure all ~12K lines of A2A tests run cleanly on current main as the baseline

Phase 1: Update dependency + compat layer

Goal: Existing tests pass with new SDK version by routing imports through a2a.compat.v0_3.

  1. Bump a2a-sdk to >=1.0,<2 in pyproject.toml (both main and dev extras)
  2. Add a thin _compat.py shim in src/google/adk/a2a/ that re-exports v0.3-compatible types:
    try:
        from a2a.compat.v0_3 import Part, TextPart, DataPart, ...
    except ImportError:
        from a2a.types import Part, TextPart, DataPart, ...
  3. Update all imports in src/google/adk/a2a/ to use the shim
  4. Run full test suite -- fix any import errors or immediate breakage
  5. Success criteria: All existing tests pass with a2a-sdk>=1.0

Phase 2: Migrate tests first

Goal: Tests define the v1.0 contract that implementation must satisfy.

Work through test files one at a time, updating:

  • Part(root=TextPart(...)) -> Part(text=TextPart(...))
  • .root.text -> .text.text (or appropriate field access)
  • TaskState.working -> TaskState.TASK_STATE_WORKING
  • Role.agent -> Role.ROLE_AGENT
  • .model_dump() -> protobuf serialization
  • isinstance checks -> field presence checks

Order (by dependency, easiest first):

  1. test_utils.py (no A2A type changes needed)
  2. test_part_converter.py (foundational -- defines part contract)
  3. test_log_utils.py
  4. test_event_converter.py
  5. test_event_round_trip.py
  6. test_from_adk.py
  7. test_to_adk.py
  8. test_request_converter.py
  9. test_task_result_aggregator.py
  10. test_a2a_agent_executor.py
  11. test_a2a_agent_executor_impl.py
  12. test_agent_card_builder.py
  13. test_agent_to_a2a.py
  14. test_client_server.py (integration -- last)
  15. test_remote_a2a_agent.py

Run tests continuously: after updating each file, run the full suite to catch regressions.

Phase 3: Migrate implementation

Work through source files in dependency order:

3a. Part converters (HIGHEST RISK)

File: src/google/adk/a2a/converters/part_converter.py

This is the most critical file -- every other converter depends on it.

Changes:

  • convert_a2a_part_to_genai_part(): Remove .root access; use part.text, part.file, part.data with field-presence checks instead of isinstance
  • convert_genai_part_to_a2a_part(): Replace Part(root=...) with Part(text=...), Part(file=...), Part(data=...)
  • Replace model_dump_json() / model_validate_json() on A2A types with protobuf serialization
  • Replace model_dump() calls on DataPart with protobuf dict conversion

3b. Event converters

Files:

  • converters/event_converter.py (legacy)
  • converters/from_adk_event.py (new impl)
  • converters/to_adk_event.py

Changes:

  • All TaskState.xxx -> TaskState.TASK_STATE_XXX
  • All Role.xxx -> Role.ROLE_XXX
  • All .root.metadata -> .metadata (if protobuf flattens the union)
  • All Part(root=TextPart(...)) -> Part(text=TextPart(...))
  • All isinstance(a2a_part.root, DataPart) -> field-presence check

3c. Long-running functions

File: converters/long_running_functions.py

Changes:

  • isinstance(a2a_part.root, DataPart) -> field-presence check (3 sites)
  • .root.metadata access -> direct metadata access
  • TaskState.input_required / TaskState.auth_required -> SCREAMING_SNAKE_CASE
  • Role.agent -> Role.ROLE_AGENT
  • Part(root=TextPart(...)) -> Part(text=TextPart(...))

3d. Request converter

File: converters/request_converter.py

Changes:

  • No direct A2A type construction (uses part_converter)
  • RequestContext import may need updating

3e. AgentCardBuilder

File: utils/agent_card_builder.py

Changes:

  • AgentCard(url=..., ...) -> Use supported_interfaces instead of url
  • AgentCapabilities() -> Protobuf construction
  • AgentSkill(...) -> Protobuf construction
  • AgentProvider, SecurityScheme type hints -> Protobuf types

3f. Executor (legacy + new)

Files:

  • executor/a2a_agent_executor.py
  • executor/a2a_agent_executor_impl.py
  • executor/task_result_aggregator.py

Changes:

  • All TaskState.xxx enum values (30+ sites across files)
  • All Role.xxx enum values
  • TextPart(text=...) construction for error messages
  • Message(...) construction
  • TaskStatusUpdateEvent(...) construction
  • TaskArtifactUpdateEvent(...) construction
  • Task(...) construction (in _A2aAgentExecutor)

3g. RemoteA2aAgent

File: agents/remote_a2a_agent.py

Changes:

  • AgentCard.url -> supported_interfaces
  • TaskState.submitted, TaskState.working -> SCREAMING_SNAKE_CASE
  • Role.user -> Role.ROLE_USER
  • .model_dump() on A2A Message/Task -> protobuf serialization
  • A2AMessage(...) construction
  • Client API changes (if any in v1.0)

3h. Log utilities

File: logs/log_utils.py

Changes:

  • All .root.text, .root.data, .root.metadata -> direct field access
  • isinstance checks -> field-presence checks or type().__name__ fallbacks
  • .model_dump_json() on A2A Part -> protobuf serialization

3i. to_a2a()

File: utils/agent_to_a2a.py

Changes:

  • A2AStarletteApplication -> new server setup pattern
  • AgentCard(**json_data) -> protobuf construction
  • DefaultRequestHandler -> updated constructor
  • InMemoryTaskStore, InMemoryPushNotificationConfigStore -> updated names/APIs

Phase 4: Integration validation

  1. Run ADK's full A2A test suite -- all ~12K lines must pass
  2. Run the integration test (test_client_server.py) -- validates real HTTP round-trip with v1.0 wire protocol
  3. End-to-end manual test: to_a2a(agent) -> uvicorn -> RemoteA2aAgent -> verify response
  4. ITK compatibility (bonus): Run the A2A Interoperability Test Kit against an ADK-served agent to validate v1.0 wire-level compliance

Phase 5: Cleanup

  1. Remove compat layer imports (if used in Phase 1)
  2. Remove _compat.py shim
  3. Update inline documentation and docstrings
  4. Update sample code in contributing/samples/a2a_*
  5. Consider removing "experimental" warnings if the API is now stable

File-by-File Migration Guide

File (relative to repo root) Changes Needed Risk Dependencies
src/google/adk/a2a/converters/utils.py None (no A2A types) NONE --
src/google/adk/a2a/converters/part_converter.py Part construction (9 sites), isinstance (5), .root access (1 fn), model_dump/validate (6) CRITICAL None
src/google/adk/a2a/converters/event_converter.py TaskState enums (8), Role enums (4), .root access (12), Part construction (1) HIGH part_converter
src/google/adk/a2a/converters/from_adk_event.py TaskState (2), Role (1), Part construction (1), model_dump (2), .root checks implicit HIGH part_converter
src/google/adk/a2a/converters/to_adk_event.py TaskState (1), .root access (13 via part_converter calls), model_dump (4 on EventActions -- safe, these are ADK types) MEDIUM part_converter
src/google/adk/a2a/converters/long_running_functions.py TaskState (5), Role (2), isinstance (3), .root access (6), Part construction (2) HIGH part_converter
src/google/adk/a2a/converters/request_converter.py Minimal (uses part_converter) LOW part_converter
src/google/adk/a2a/executor/config.py Type aliases reference A2A types (no construction) LOW converters
src/google/adk/a2a/executor/executor_context.py None (no A2A types) NONE --
src/google/adk/a2a/executor/task_result_aggregator.py TaskState enums (10) MEDIUM --
src/google/adk/a2a/executor/a2a_agent_executor.py TaskState (6), Role (1), TextPart construction (1), Message construction (2) HIGH converters, config
src/google/adk/a2a/executor/a2a_agent_executor_impl.py TaskState (5), Role (1), TextPart (1), Message (2), Task (1) HIGH converters, config
src/google/adk/a2a/executor/utils.py None (delegates to interceptors) NONE config
src/google/adk/a2a/executor/interceptors/include_artifacts_in_a2a_event.py Artifact, TaskArtifactUpdateEvent construction LOW part_converter
src/google/adk/a2a/agent/config.py Type aliases (no construction) LOW converters
src/google/adk/a2a/agent/utils.py None (delegates to interceptors) NONE config
src/google/adk/a2a/agent/interceptors/new_integration_extension.py ClientCallContext, HTTP_EXTENSION_HEADER LOW --
src/google/adk/a2a/utils/agent_card_builder.py AgentCard.url removal, AgentCapabilities/AgentSkill/AgentProvider construction HIGH --
src/google/adk/a2a/utils/agent_to_a2a.py A2AStarletteApplication removal, AgentCard construction HIGH agent_card_builder
src/google/adk/a2a/logs/log_utils.py .root access (5), isinstance checks (5), model_dump_json (2) MEDIUM --
src/google/adk/agents/remote_a2a_agent.py AgentCard.url (3), TaskState (4), Role (1), model_dump (5), Message construction (1), client API HIGH converters, config
src/google/adk/a2a/experimental.py None NONE --

Testing Strategy

Primary Safety Net: Existing Tests

The ~12,000 lines of existing tests are the main guardrail. After migrating tests (Phase 2), they define the v1.0 contract. Every implementation change in Phase 3 must keep these tests green.

Supplementary: Integration Test

tests/unittests/a2a/integration/test_client_server.py (638 lines) tests a real HTTP round-trip:

  • Spins up a Starlette server with to_a2a()
  • Sends messages via A2A client
  • Validates responses end-to-end

This is the most valuable single test for catching wire-protocol issues.

Supplementary: ITK

The A2A Interoperability Test Kit (a2aproject/a2a-python ITK) tests SDK-to-SDK interop at the wire level. It does NOT test ADK's wrapper layer, but running it against an ADK-served agent validates that ADK produces valid v1.0 responses.

New Tests (if needed)

  • Property-based tests for Part round-trip conversion (v1.0 types -> GenAI -> v1.0 types)
  • Snapshot tests for serialized AgentCard output (to catch regressions in card structure)

Risk Assessment

Risk Likelihood Impact Mitigation
Protobuf types lack Pydantic helpers (model_dump, model_validate) CERTAIN HIGH Write thin wrappers or use protobuf's MessageToDict/ParseDict
Part.root removal breaks 80+ access sites CERTAIN HIGH Systematic find-replace with field-presence checks
A2AStarletteApplication removal breaks to_a2a() LIKELY MEDIUM Follow v1.0 SDK server setup docs; may need significant rewrite of agent_to_a2a.py
AgentCard.url removal breaks card construction + validation CERTAIN MEDIUM Switch to supported_interfaces
Enum value changes break 40+ comparisons CERTAIN MEDIUM Systematic find-replace; create enum mapping if compat needed
Client API changes break RemoteA2aAgent LIKELY MEDIUM Review v1.0 client API; may need restructuring of response handling
Compat layer doesn't cover all patterns LIKELY LOW Identified upfront; compat layer is Phase 1 bridge only
Test mocking of A2A types breaks with protobuf LIKELY MEDIUM Protobuf objects may not support Mock specs; update test patterns
Protobuf objects not hashable/comparable the same way POSSIBLE LOW Update assertions to compare field-by-field

Success Criteria

  1. All existing A2A tests pass with a2a-sdk>=1.0 types (no compat imports)
  2. Integration test (test_client_server.py) passes with v1.0 wire protocol
  3. No breaking changes to ADK's public API signatures:
    • to_a2a() function signature unchanged
    • RemoteA2aAgent constructor unchanged
    • A2aAgentExecutor constructor unchanged
    • A2aAgentExecutorConfig fields unchanged
    • AgentCardBuilder constructor unchanged
  4. to_a2a() produces valid v1.0 agent cards
  5. Sample apps in contributing/samples/a2a_* updated and working
  6. ITK compatibility validated (stretch goal)

Appendix: Key Repositories

ADK A2A Interfaces Inventory

Complete inventory of all public A2A interfaces in ADK (google/adk-python v2.2.0). Each interface is marked with its v1.0 migration status:

  • [BREAKS] -- requires changes for a2a-sdk v1.0
  • [SAFE] -- no changes needed (uses only ADK-internal types)

All paths are relative to src/google/adk/.


1. Part Converters (a2a/converters/part_converter.py)

Type Aliases

A2APartToGenAIPartConverter = Callable[
    [a2a_types.Part], Union[Optional[genai_types.Part], List[genai_types.Part]]
]

[BREAKS] -- a2a_types.Part changes from Pydantic to Protobuf.

GenAIPartToA2APartConverter = Callable[
    [genai_types.Part], Union[Optional[a2a_types.Part], List[a2a_types.Part]]
]

[BREAKS] -- a2a_types.Part return type changes.

Public Functions

def convert_a2a_part_to_genai_part(a2a_part: a2a_types.Part) -> Optional[genai_types.Part]

[BREAKS] -- Accesses a2a_part.root (line 62), uses isinstance(part, TextPart/FilePart/DataPart) (lines 63, 69, 95), accesses part.file with isinstance(FileWithUri/FileWithBytes) (lines 70, 79), uses model_validate (lines 125, 135, 144, 153), uses model_dump_json (line 160).

def convert_genai_part_to_a2a_part(part: genai_types.Part) -> Optional[a2a_types.Part]

[BREAKS] -- Constructs Part(root=TextPart(...)) (line 186), Part(root=FilePart(...)) (line 189, 229), Part(root=DataPart.model_validate_json(...)) (line 206), Part(root=DataPart(...)) (lines 247, 257, 271, 285). Uses model_dump on A2A DataPart (lines 249, 259, 273, 287).

Constants

A2A_DATA_PART_METADATA_TYPE_KEY = 'type'                          # [SAFE]
A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY = 'is_long_running'    # [SAFE]
A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL = 'function_call'       # [SAFE]
A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE = 'function_response'  # [SAFE]
A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT = 'code_execution_result'  # [SAFE]
A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE = 'executable_code'   # [SAFE]
A2A_DATA_PART_TEXT_MIME_TYPE = 'text/plain'                        # [SAFE]
A2A_DATA_PART_START_TAG = b'<a2a_datapart_json>'                  # [SAFE]
A2A_DATA_PART_END_TAG = b'</a2a_datapart_json>'                   # [SAFE]

2. Event Converter - Legacy (a2a/converters/event_converter.py)

Type Aliases

AdkEventToA2AEventsConverter = Callable[
    [Event, InvocationContext, Optional[str], Optional[str], GenAIPartToA2APartConverter],
    List[A2AEvent],
]

[BREAKS] -- A2AEvent type from a2a.server changes.

Public Functions

def convert_a2a_task_to_event(
    a2a_task: Task,
    author: Optional[str] = None,
    invocation_context: Optional[InvocationContext] = None,
    part_converter: A2APartToGenAIPartConverter = convert_a2a_part_to_genai_part,
) -> Event

[BREAKS] -- Uses a2a_task.artifacts[-1].parts, a2a_task.status.message, a2a_task.history, Role.agent, Message(message_id=..., role=Role.agent, parts=...).

def convert_a2a_message_to_event(
    a2a_message: Message,
    author: Optional[str] = None,
    invocation_context: Optional[InvocationContext] = None,
    part_converter: A2APartToGenAIPartConverter = convert_a2a_part_to_genai_part,
) -> Event

[BREAKS] -- Accesses .root.metadata on parts (lines 325-326). Uses Message(message_id=..., role=..., parts=[]).

def convert_event_to_a2a_message(
    event: Event,
    invocation_context: InvocationContext | None = None,
    role: Role = Role.agent,
    part_converter: GenAIPartToA2APartConverter = convert_genai_part_to_a2a_part,
) -> Optional[Message]

[BREAKS] -- Default role=Role.agent -> Role.ROLE_AGENT. Constructs Message(message_id=..., role=role, parts=output_parts).

def convert_event_to_a2a_events(
    event: Event,
    invocation_context: InvocationContext,
    task_id: Optional[str] = None,
    context_id: Optional[str] = None,
    part_converter: GenAIPartToA2APartConverter = convert_genai_part_to_a2a_part,
) -> List[A2AEvent]

[BREAKS] -- Uses TaskState.failed, TaskState.working, TaskState.auth_required, TaskState.input_required, Role.agent. Constructs TaskStatusUpdateEvent(...), TaskStatus(...), Message(...), TextPart(...). Accesses .root.metadata and .root.data on parts (lines 496-519).


3. Event Converter - New Impl (a2a/converters/from_adk_event.py)

Type Aliases

A2AUpdateEvent = Union[TaskStatusUpdateEvent, TaskArtifactUpdateEvent]

AdkEventToA2AEventsConverter = Callable[
    [Event, Optional[Dict[str, str]], Optional[str], Optional[str], GenAIPartToA2APartConverter],
    List[A2AUpdateEvent],
]

[BREAKS] -- A2A event types change.

Public Functions

def create_error_status_event(
    event: Event,
    task_id: Optional[str] = None,
    context_id: Optional[str] = None,
) -> TaskStatusUpdateEvent

[BREAKS] -- Uses TaskState.failed, Role.agent, Part(root=TextPart(...)), Message(...), TaskStatusUpdateEvent(...), TaskStatus(...).

def convert_event_to_a2a_events(
    event: Event,
    agents_artifacts: Dict[str, str],
    task_id: Optional[str] = None,
    context_id: Optional[str] = None,
    part_converter: GenAIPartToA2APartConverter = convert_genai_part_to_a2a_part,
) -> List[A2AUpdateEvent]

[BREAKS] -- Uses TaskArtifactUpdateEvent(...), Artifact(...), TaskStatusUpdateEvent(...), TaskStatus(state=TaskState.working, ...), Message(...), Role.agent.


4. To-ADK Event Converter (a2a/converters/to_adk_event.py)

Type Aliases

A2AMessageToEventConverter = Callable[
    [Message, Optional[str], Optional[InvocationContext], A2APartToGenAIPartConverter],
    Optional[Event],
]

A2ATaskToEventConverter = Callable[
    [Task, Optional[str], Optional[InvocationContext], A2APartToGenAIPartConverter],
    Optional[Event],
]

A2AStatusUpdateToEventConverter = Callable[
    [TaskStatusUpdateEvent, Optional[str], Optional[InvocationContext], A2APartToGenAIPartConverter],
    Optional[Event],
]

A2AArtifactUpdateToEventConverter = Callable[
    [TaskArtifactUpdateEvent, Optional[str], Optional[InvocationContext], A2APartToGenAIPartConverter],
    Optional[Event],
]

[BREAKS] -- All reference A2A types that change from Pydantic to Protobuf.

Public Functions

def convert_a2a_task_to_event(a2a_task: Task, ...) -> Optional[Event]

[BREAKS] -- Uses a2a_task.status.state == TaskState.input_required (line 322), accesses a2a_task.artifacts, a2a_task.status.message.parts, a2a_task.status.message.metadata.

def convert_a2a_message_to_event(a2a_message: Message, ...) -> Optional[Event]

[BREAKS] -- Accesses a2a_message.parts, a2a_message.metadata.

def convert_a2a_status_update_to_event(a2a_status_update: TaskStatusUpdateEvent, ...) -> Optional[Event]

[BREAKS] -- Accesses a2a_status_update.status.message.parts, a2a_status_update.status.message.metadata.

def convert_a2a_artifact_update_to_event(a2a_artifact_update: TaskArtifactUpdateEvent, ...) -> Optional[Event]

[BREAKS] -- Accesses a2a_artifact_update.artifact.parts, a2a_artifact_update.artifact.metadata, a2a_artifact_update.last_chunk.


5. Long-Running Functions (a2a/converters/long_running_functions.py)

Classes

class LongRunningFunctions:
    def __init__(self, part_converter: A2APartToGenAIPartConverter | None = None) -> None
    def has_long_running_function_calls(self) -> bool
    def process_event(self, event: Event) -> Event
    def create_long_running_function_call_event(self, task_id: str, context_id: str) -> TaskStatusUpdateEvent

[BREAKS] -- __init__: sets self._task_state = TaskState.input_required (line 54). create_long_running_function_call_event: uses TaskState, Role.agent, Message(...), TaskStatusUpdateEvent(...), TaskStatus(...). Internal _mark_long_running_function_call: uses isinstance(a2a_part.root, DataPart), .root.metadata.

Public Functions

def handle_user_input(context: RequestContext) -> TaskStatusUpdateEvent | None

[BREAKS] -- Uses TaskState.input_required, TaskState.auth_required, isinstance(a2a_part.root, DataPart), .root.metadata, Part(root=TextPart(...)), Message(...), TaskStatusUpdateEvent(...), TaskStatus(...), Role.agent.


6. Request Converter (a2a/converters/request_converter.py)

Classes

class AgentRunRequest(BaseModel):
    user_id: Optional[str] = None
    session_id: Optional[str] = None
    invocation_id: Optional[str] = None
    new_message: Optional[genai_types.Content] = None
    state_delta: Optional[dict[str, Any]] = None
    run_config: Optional[RunConfig] = None

[SAFE] -- Uses only ADK/GenAI types.

Type Aliases

A2ARequestToAgentRunRequestConverter = Callable[
    [RequestContext, A2APartToGenAIPartConverter],
    AgentRunRequest,
]

[BREAKS] -- RequestContext may change in v1.0 SDK.

Public Functions

def convert_a2a_request_to_agent_run_request(
    request: RequestContext,
    part_converter: A2APartToGenAIPartConverter = convert_a2a_part_to_genai_part,
) -> AgentRunRequest

[BREAKS] -- Accesses request.message.parts, request.call_context.user.user_name, request.context_id, request.metadata.


7. Converter Utilities (a2a/converters/utils.py)

Constants

ADK_METADATA_KEY_PREFIX = "adk_"     # [SAFE]
ADK_CONTEXT_ID_PREFIX = "ADK"        # [SAFE]
ADK_CONTEXT_ID_SEPARATOR = "/"       # [SAFE]

Public Functions

def _get_adk_metadata_key(key: str) -> str           # [SAFE]
def _to_a2a_context_id(app_name, user_id, session_id) -> str  # [SAFE]
def _from_a2a_context_id(context_id) -> tuple         # [SAFE]

8. Executor Config (a2a/executor/config.py)

Classes

@dataclasses.dataclass
class ExecuteInterceptor:
    before_agent: Optional[Callable[[RequestContext], Awaitable[RequestContext]]] = None
    after_event: Optional[Callable[[ExecutorContext, A2AEvent, Event], Awaitable[Union[A2AEvent, list[A2AEvent], None]]]] = None
    after_agent: Optional[Callable[[ExecutorContext, TaskStatusUpdateEvent], Awaitable[TaskStatusUpdateEvent]]] = None

[BREAKS] -- RequestContext, A2AEvent, TaskStatusUpdateEvent are A2A types.

class A2aAgentExecutorConfig(BaseModel):
    a2a_part_converter: A2APartToGenAIPartConverter = convert_a2a_part_to_genai_part
    gen_ai_part_converter: GenAIPartToA2APartConverter = convert_genai_part_to_a2a_part
    request_converter: A2ARequestToAgentRunRequestConverter = convert_a2a_request_to_agent_run_request
    event_converter: AdkEventToA2AEventsConverter = legacy_convert_event_to_a2a_events
    adk_event_converter: AdkEventToA2AEventsConverterImpl = convert_event_to_a2a_events_impl
    execute_interceptors: Optional[list[ExecuteInterceptor]] = None

[BREAKS] -- Type aliases reference A2A types.


9. Executor Context (a2a/executor/executor_context.py)

class ExecutorContext:
    def __init__(self, app_name: str, user_id: str, session_id: str, runner: Runner)

    @property app_name -> str
    @property user_id -> str
    @property session_id -> str
    @property runner -> Runner

[SAFE] -- No A2A types used.


10. A2aAgentExecutor (a2a/executor/a2a_agent_executor.py)

class A2aAgentExecutor(AgentExecutor):
    def __init__(
        self,
        *,
        runner: Runner | Callable[..., Runner | Awaitable[Runner]],
        config: Optional[A2aAgentExecutorConfig] = None,
        use_legacy: bool = False,
        force_new_version: bool = False,
    )

    async def cancel(self, context: RequestContext, event_queue: EventQueue)
    async def execute(self, context: RequestContext, event_queue: EventQueue)

[BREAKS] -- Inherits from a2a.server.agent_execution.AgentExecutor. Uses RequestContext, EventQueue, TaskStatusUpdateEvent, TaskStatus, TaskState, Message, Role, TextPart, Artifact, TaskArtifactUpdateEvent.


11. A2aAgentExecutor Impl (a2a/executor/a2a_agent_executor_impl.py)

class _A2aAgentExecutor(AgentExecutor):
    def __init__(
        self,
        *,
        runner: Runner | Callable[..., Runner | Awaitable[Runner]],
        config: Optional[A2aAgentExecutorConfig] = None,
    )

    async def cancel(self, context: RequestContext, event_queue: EventQueue)
    async def execute(self, context: RequestContext, event_queue: EventQueue)

[BREAKS] -- Same as above, plus uses Task(id=..., status=..., context_id=..., history=..., metadata=...).


12. TaskResultAggregator (a2a/executor/task_result_aggregator.py)

class TaskResultAggregator:
    def __init__(self)
    def process_event(self, event: Event)

    @property task_state -> TaskState
    @property task_status_message -> Message | None

[BREAKS] -- Uses TaskState.working/failed/auth_required/input_required enum values (10 sites). Accesses event.status.state, event.status.message. Mutates event.status.state = TaskState.working.


13. Executor Interceptor Utils (a2a/executor/utils.py)

async def execute_before_agent_interceptors(context, interceptors) -> RequestContext
async def execute_after_event_interceptors(a2a_event, executor_context, adk_event, interceptors) -> list[A2AEvent]
async def execute_after_agent_interceptors(executor_context, final_event, interceptors) -> TaskStatusUpdateEvent

[BREAKS] -- Parameter and return types reference RequestContext, A2AEvent, TaskStatusUpdateEvent.


14. AgentCardBuilder (a2a/utils/agent_card_builder.py)

class AgentCardBuilder:
    def __init__(
        self,
        *,
        agent: BaseAgent,
        rpc_url: Optional[str] = None,
        capabilities: Optional[AgentCapabilities] = None,
        doc_url: Optional[str] = None,
        provider: Optional[AgentProvider] = None,
        agent_version: Optional[str] = None,
        security_schemes: Optional[Dict[str, SecurityScheme]] = None,
    )

    async def build(self) -> AgentCard

[BREAKS] -- Constructor uses AgentCapabilities() default (line 65). build() constructs AgentCard(name=..., description=..., url=..., ...) (line 78) -- url field removed in v1.0. Constructs AgentSkill(...) (many sites). Uses AgentProvider, SecurityScheme types.


15. to_a2a() (a2a/utils/agent_to_a2a.py)

def to_a2a(
    agent: BaseAgent,
    *,
    host: str = "localhost",
    port: int = 8000,
    protocol: str = "http",
    agent_card: Optional[Union[AgentCard, str]] = None,
    push_config_store: Optional[PushNotificationConfigStore] = None,
    runner: Optional[Runner] = None,
    lifespan: Optional[Callable[[Starlette], AsyncIterator[None]]] = None,
) -> Starlette

[BREAKS] -- Uses A2AStarletteApplication (line 24, 181), DefaultRequestHandler (line 26, 157), InMemoryTaskStore (line 27, 148), InMemoryPushNotificationConfigStore (line 28, 155), AgentCard(**json_data) construction (line 69). The A2AStarletteApplication class may be removed or restructured in v1.0.


16. Remote Agent Config (a2a/agent/config.py)

class ParametersConfig(BaseModel):
    request_metadata: Optional[dict[str, Any]] = None
    client_call_context: Optional[ClientCallContext] = None

[BREAKS] -- ClientCallContext is from a2a.client.middleware.

class RequestInterceptor(BaseModel):
    before_request: Optional[Callable[...]]
    after_request: Optional[Callable[...]]

[BREAKS] -- References A2AMessage, A2AEvent from a2a types.

class A2aRemoteAgentConfig(BaseModel):
    a2a_message_converter: A2AMessageToEventConverter
    a2a_task_converter: A2ATaskToEventConverter
    a2a_status_update_converter: A2AStatusUpdateToEventConverter
    a2a_artifact_update_converter: A2AArtifactUpdateToEventConverter
    a2a_part_converter: A2APartToGenAIPartConverter
    request_interceptors: Optional[list[RequestInterceptor]] = None

[BREAKS] -- All converter type aliases reference A2A types.


17. Remote Agent Utils (a2a/agent/utils.py)

async def execute_before_request_interceptors(
    request_interceptors, ctx, a2a_request
) -> tuple[Union[A2AMessage, Event], ParametersConfig]

async def execute_after_request_interceptors(
    request_interceptors, ctx, a2a_response, event
) -> Optional[Event]

[BREAKS] -- Uses ClientCallContext, A2AMessage, A2AClientEvent.


18. New Integration Extension (a2a/agent/interceptors/new_integration_extension.py)

_NEW_A2A_ADK_INTEGRATION_EXTENSION = 'https://google.github.io/adk-docs/a2a/a2a-extension/'

_new_integration_extension_interceptor = RequestInterceptor(before_request=_before_request)

[BREAKS] -- Uses ClientCallContext, HTTP_EXTENSION_HEADER from a2a.extensions.common, ParametersConfig.


19. Include Artifacts Interceptor (a2a/executor/interceptors/include_artifacts_in_a2a_event.py)

include_artifacts_in_a2a_event_interceptor = ExecuteInterceptor(after_event=_after_agent)

[BREAKS] -- Uses Artifact(...), TaskArtifactUpdateEvent(...) construction, isinstance(a2a_event, (TaskStatusUpdateEvent, TaskArtifactUpdateEvent)).


20. Log Utilities (a2a/logs/log_utils.py)

def build_message_part_log(part: A2APart) -> str
def build_a2a_request_log(req: A2AMessage) -> str
def build_a2a_response_log(resp: A2AClientEvent | A2AMessage) -> str

[BREAKS] -- Accesses part.root (5 sites), uses isinstance(..., A2ATextPart/A2ADataPart) (2 sites), .model_dump_json() on Part (line 113), .model_dump_json() on result (line 247). Also accesses result.status.message.parts[i].root.


21. RemoteA2aAgent (agents/remote_a2a_agent.py)

class RemoteA2aAgent(BaseAgent):
    def __init__(
        self,
        name: str,
        agent_card: Union[AgentCard, str],
        *,
        description: str = "",
        httpx_client: Optional[httpx.AsyncClient] = None,
        timeout: float = DEFAULT_TIMEOUT,
        genai_part_converter: GenAIPartToA2APartConverter = convert_genai_part_to_a2a_part,
        a2a_part_converter: A2APartToGenAIPartConverter = convert_a2a_part_to_genai_part,
        a2a_client_factory: Optional[A2AClientFactory] = None,
        a2a_request_meta_provider: Optional[Callable[...]] = None,
        full_history_when_stateless: bool = False,
        config: Optional[A2aRemoteAgentConfig] = None,
        use_legacy: bool = True,
        **kwargs: Any,
    ) -> None

    async def cleanup(self) -> None

[BREAKS] -- Uses AgentCard (type hint + .url field access lines 296-308), A2AClient, A2ACardResolver, A2AClientFactory, A2AClientConfig, A2AMessage, A2APart, Role, TaskState, A2ATask, A2ATaskStatusUpdateEvent, A2ATaskArtifactUpdateEvent, A2ATransport, MessageSendConfiguration, A2AClientHTTPError, ClientCallContext. Uses .model_dump() on A2A objects (lines 700, 706, 710, 725, 744). Constructs A2AMessage(message_id=..., parts=..., role="user", context_id=...) (line 643).


22. Experimental Decorator (a2a/experimental.py)

a2a_experimental = _make_feature_decorator(...)

[SAFE] -- No A2A types used.


Summary Statistics

Category BREAKS SAFE
Type aliases 10 0
Public functions 15 3
Classes 9 2
Constants 0 9
Total interfaces 34 14

ADK A2A v1.0 Breaking Changes Checklist

Actionable checklist for migrating google/adk-python from a2a-sdk 0.3.x to 1.0. All paths are relative to the repo root. Line numbers reference commit main as of ADK v2.2.0.


1. Dependency Update

  • pyproject.toml line 100: "a2a-sdk>=0.3.4,<0.4.0" -> "a2a-sdk>=1.0,<2"
  • pyproject.toml line 123: "a2a-sdk>=0.3.0,<0.4.0" -> "a2a-sdk>=1.0,<2" (dev extra)

2. Part Construction: Part(root=X) -> Part(x=X) (~11 source sites, ~25 test sites)

Source Files

  • src/google/adk/a2a/converters/part_converter.py line 186: Part(root=a2a_part) -> Part(text=a2a_part) (TextPart case)
  • src/google/adk/a2a/converters/part_converter.py line 189-197: Part(root=FilePart(file=FileWithUri(...))) -> Part(file=FilePart(file=FileWithUri(...)))
  • src/google/adk/a2a/converters/part_converter.py line 206-210: Part(root=DataPart.model_validate_json(...)) -> Part(data=DataPart(...))
  • src/google/adk/a2a/converters/part_converter.py line 229: Part(root=a2a_part) -> Part(file=a2a_part) (FilePart case)
  • src/google/adk/a2a/converters/part_converter.py line 247-254: Part(root=DataPart(data=..., metadata=...)) -> Part(data=DataPart(...))
  • src/google/adk/a2a/converters/part_converter.py line 257-268: Part(root=DataPart(...)) -> Part(data=DataPart(...))
  • src/google/adk/a2a/converters/part_converter.py line 271-282: Part(root=DataPart(...)) -> Part(data=DataPart(...))
  • src/google/adk/a2a/converters/part_converter.py line 285-296: Part(root=DataPart(...)) -> Part(data=DataPart(...))
  • src/google/adk/a2a/converters/from_adk_event.py line 150: A2APart(root=TextPart(text=error_message)) -> A2APart(text=TextPart(text=error_message))
  • src/google/adk/a2a/converters/long_running_functions.py line 206-208: A2APart(root=TextPart(text=...)) -> A2APart(text=TextPart(text=...))

Test Files

  • tests/unittests/a2a/converters/test_part_converter.py line 42: Part(root=TextPart(text=...))
  • tests/unittests/a2a/converters/test_part_converter.py line 520: Part(root=TextPart(text=...))
  • tests/unittests/a2a/converters/test_part_converter.py line 732: Part(root=TextPart(text=...))
  • tests/unittests/a2a/converters/test_event_converter.py lines 577-578: Part(root=TextPart(text=...))
  • tests/unittests/a2a/converters/test_event_converter.py lines 618, 623, 628, 662: Part(root=TextPart(text=...))
  • tests/unittests/a2a/converters/test_from_adk.py line 64: A2APart(root=TextPart(text=...))
  • tests/unittests/a2a/executor/test_task_result_aggregator.py line 33: Part(root=TextPart(text=...))
  • tests/unittests/a2a/executor/test_a2a_agent_executor.py lines 805, 898: Part(root=TextPart(text=...))
  • tests/unittests/a2a/integration/test_client_server.py lines 503, 521, 615: A2APart(root=TextPart(text=...))
  • tests/unittests/a2a/logs/test_log_utils.py lines 63, 74, 85, 107, 151, 152, 239, 240, 276: A2APart(root=...)

3. .root Access: part.root.X -> part.X (~28 source sites, ~54 test sites)

Source Files -- part_converter.py

  • Line 62: part = a2a_part.root -> Access fields directly on a2a_part
  • Line 63: isinstance(part, a2a_types.TextPart) -> field-presence check
  • Line 65: part.metadata -> Use part-level metadata access
  • Line 66: part.metadata.get(...) -> adjusted accessor
  • Line 69: isinstance(part, a2a_types.FilePart) -> field-presence check
  • Line 70: isinstance(part.file, a2a_types.FileWithUri) -> field-presence check
  • Line 79: isinstance(part.file, a2a_types.FileWithBytes) -> field-presence check
  • Line 95: isinstance(part, a2a_types.DataPart) -> field-presence check
  • Line 102: part.metadata access on DataPart
  • Line 160: part.model_dump_json(...) -> protobuf serialization

Source Files -- event_converter.py (legacy)

  • Line 188: isinstance(a2a_part.root, DataPart) -> field-presence check
  • Lines 190-191: a2a_part.root.metadata -> direct metadata access
  • Line 195: a2a_part.root.data.get("id") -> direct data access
  • Lines 197-198: a2a_part.root.metadata[...] -> direct metadata access
  • Lines 325-326: a2a_part.root.metadata -> direct metadata access
  • Lines 496-506: part.root.metadata.get(...) and part.root.data.get(...) -> direct access
  • Lines 510-519: part.root.metadata.get(...) -> direct access

Source Files -- from_adk_event.py

  • (No direct .root access -- delegates to part_converter)

Source Files -- to_adk_event.py

  • Lines 150-151: a2a_part.root.metadata -> direct metadata access
  • Line 155: a2a_part.root.metadata.get(...) -> direct access

Source Files -- long_running_functions.py

  • Line 150: isinstance(a2a_part.root, DataPart) -> field-presence check
  • Lines 151-152: a2a_part.root.metadata -> direct access
  • Lines 157-158: a2a_part.root.metadata[...] -> direct access
  • Line 163: a2a_part.root.metadata.get("name") -> direct access
  • Line 187: isinstance(a2a_part.root, DataPart) -> field-presence check
  • Lines 188-189: a2a_part.root.metadata -> direct access

Source Files -- log_utils.py

  • Line 96: part.root.text[:100] -> part.text.text[:100]
  • Line 97: part.root.text -> part.text.text
  • Line 107: part.root.data.items() -> direct data access
  • Line 117: part.root.metadata -> direct metadata
  • Line 118: part.root.metadata -> direct metadata

Test Files (representative -- update all .root access in each test file)

  • test_part_converter.py: ~20 sites (lines 281, 282, 295-298, 317-318, etc.)
  • test_to_adk.py: ~14 sites (lines 51-52, 79-80, 133-134, 287-288, 343-344, 389-390)
  • test_from_adk.py: ~2 sites (line 65)
  • test_a2a_agent_executor_impl.py: line 420
  • test_log_utils.py: lines 129, 355
  • test_client_server.py: lines 535, 630, 634, 638

4. isinstance Checks -> Field Presence (~12 source sites)

Source Files

  • part_converter.py line 63: isinstance(part, a2a_types.TextPart) -> a2a_part.text is not None or a2a_part.HasField("text")
  • part_converter.py line 69: isinstance(part, a2a_types.FilePart) -> a2a_part.file is not None
  • part_converter.py line 70: isinstance(part.file, a2a_types.FileWithUri) -> part.file.uri is not None or field check
  • part_converter.py line 79: isinstance(part.file, a2a_types.FileWithBytes) -> part.file.bytes_ is not None or field check
  • part_converter.py line 95: isinstance(part, a2a_types.DataPart) -> a2a_part.data is not None
  • event_converter.py line 188: isinstance(a2a_part.root, DataPart) -> field-presence
  • long_running_functions.py line 150: isinstance(a2a_part.root, DataPart) -> field-presence
  • long_running_functions.py line 187: isinstance(a2a_part.root, DataPart) -> field-presence
  • log_utils.py line 72: isinstance(obj, A2ATextPart) -> type check or field check
  • log_utils.py line 80: isinstance(obj, A2ADataPart) -> type check or field check
  • log_utils.py lines 95, 99: _is_a2a_text_part(part.root) / _is_a2a_data_part(part.root) -> update to check flat Part

Test Files

  • test_part_converter.py: isinstance(result.root, ...) checks (~10 sites)
  • test_to_adk.py: Mock(spec=TextPart) on .root (~6 sites)

5. Enum Values: snake_case -> SCREAMING_SNAKE_CASE (~41 source sites, ~106 test sites)

TaskState Enums -- Source Files

  • long_running_functions.py line 54: TaskState.input_required -> TaskState.TASK_STATE_INPUT_REQUIRED
  • long_running_functions.py line 164: TaskState.auth_required -> TaskState.TASK_STATE_AUTH_REQUIRED
  • long_running_functions.py line 166: TaskState.input_required -> TaskState.TASK_STATE_INPUT_REQUIRED
  • long_running_functions.py line 176: TaskState.input_required -> TaskState.TASK_STATE_INPUT_REQUIRED
  • long_running_functions.py line 177: TaskState.auth_required -> TaskState.TASK_STATE_AUTH_REQUIRED
  • task_result_aggregator.py line 30: TaskState.working -> TaskState.TASK_STATE_WORKING
  • task_result_aggregator.py line 42: TaskState.failed -> TaskState.TASK_STATE_FAILED
  • task_result_aggregator.py line 43: TaskState.failed -> TaskState.TASK_STATE_FAILED
  • task_result_aggregator.py line 46: TaskState.auth_required -> TaskState.TASK_STATE_AUTH_REQUIRED
  • task_result_aggregator.py line 47: TaskState.failed -> TaskState.TASK_STATE_FAILED
  • task_result_aggregator.py line 49: TaskState.auth_required -> TaskState.TASK_STATE_AUTH_REQUIRED
  • task_result_aggregator.py line 52: TaskState.input_required -> TaskState.TASK_STATE_INPUT_REQUIRED
  • task_result_aggregator.py line 54: TaskState.failed/auth_required -> SCREAMING_SNAKE_CASE
  • task_result_aggregator.py line 56: TaskState.input_required -> TaskState.TASK_STATE_INPUT_REQUIRED
  • task_result_aggregator.py line 61: TaskState.working -> TaskState.TASK_STATE_WORKING
  • task_result_aggregator.py line 63: TaskState.working -> TaskState.TASK_STATE_WORKING
  • a2a_agent_executor.py line 161: TaskState.submitted -> TaskState.TASK_STATE_SUBMITTED
  • a2a_agent_executor.py line 183: TaskState.failed -> TaskState.TASK_STATE_FAILED
  • a2a_agent_executor.py line 238: TaskState.working -> TaskState.TASK_STATE_WORKING
  • a2a_agent_executor.py line 275: TaskState.working -> TaskState.TASK_STATE_WORKING
  • a2a_agent_executor.py line 296: TaskState.completed -> TaskState.TASK_STATE_COMPLETED
  • a2a_agent_executor_impl.py line 126: TaskState.submitted -> TaskState.TASK_STATE_SUBMITTED
  • a2a_agent_executor_impl.py line 149: TaskState.working -> TaskState.TASK_STATE_WORKING
  • a2a_agent_executor_impl.py line 174: TaskState.failed -> TaskState.TASK_STATE_FAILED
  • a2a_agent_executor_impl.py line 246: TaskState.completed -> TaskState.TASK_STATE_COMPLETED
  • event_converter.py line 449: TaskState.failed -> TaskState.TASK_STATE_FAILED
  • event_converter.py line 488: TaskState.working -> TaskState.TASK_STATE_WORKING
  • event_converter.py line 508: TaskState.auth_required -> TaskState.TASK_STATE_AUTH_REQUIRED
  • event_converter.py line 521: TaskState.input_required -> TaskState.TASK_STATE_INPUT_REQUIRED
  • from_adk_event.py line 146: TaskState.failed -> TaskState.TASK_STATE_FAILED
  • from_adk_event.py line 227: TaskState.working -> TaskState.TASK_STATE_WORKING
  • to_adk_event.py line 322: TaskState.input_required -> TaskState.TASK_STATE_INPUT_REQUIRED
  • remote_a2a_agent.py lines 462-463: TaskState.submitted/working -> SCREAMING_SNAKE_CASE
  • remote_a2a_agent.py lines 480-481: TaskState.submitted/working -> SCREAMING_SNAKE_CASE

Role Enums -- Source Files

  • event_converter.py line 232: Role.agent -> Role.ROLE_AGENT
  • event_converter.py line 375: Role.agent -> Role.ROLE_AGENT
  • event_converter.py line 452: Role.agent -> Role.ROLE_AGENT
  • from_adk_event.py line 149: Role.agent -> Role.ROLE_AGENT
  • from_adk_event.py line 230: Role.agent -> Role.ROLE_AGENT
  • long_running_functions.py line 118: Role.agent -> Role.ROLE_AGENT
  • long_running_functions.py line 204: Role.agent -> Role.ROLE_AGENT
  • a2a_agent_executor.py line 189: Role.agent -> Role.ROLE_AGENT
  • a2a_agent_executor_impl.py line 178: Role.agent -> Role.ROLE_AGENT
  • remote_a2a_agent.py line 364: Role.user -> Role.ROLE_USER

Test Files (all test files that reference TaskState/Role enums -- ~106 total sites)

  • test_event_converter.py: Update all TaskState.xxx and Role.xxx references
  • test_from_adk.py: Update all enum references
  • test_to_adk.py: Update all enum references
  • test_task_result_aggregator.py: Update all TaskState.xxx references
  • test_a2a_agent_executor.py: Update all enum references
  • test_a2a_agent_executor_impl.py: Update all enum references
  • test_client_server.py: Update all enum references
  • test_remote_a2a_agent.py: Update all enum references

6. model_dump / model_validate on A2A Types (~15 source sites)

Serialization: .model_dump() / .model_dump_json() on A2A objects

  • part_converter.py line 160: part.model_dump_json(by_alias=True, exclude_none=True) -> protobuf serialization
  • part_converter.py line 249: part.function_call.model_dump(...) (GenAI type -- [SAFE])
  • part_converter.py line 259: part.function_response.model_dump(...) (GenAI type -- [SAFE])
  • part_converter.py line 273: part.code_execution_result.model_dump(...) (GenAI type -- [SAFE])
  • part_converter.py line 287: part.executable_code.model_dump(...) (GenAI type -- [SAFE])
  • log_utils.py line 113: part.model_dump_json(exclude_none=True, ...) -> protobuf serialization
  • log_utils.py line 247: result.model_dump_json() -> protobuf serialization
  • remote_a2a_agent.py line 700: a2a_request.model_dump(...) -> protobuf MessageToDict or similar
  • remote_a2a_agent.py line 706: a2a_response[0].model_dump(...) -> protobuf serialization
  • remote_a2a_agent.py line 710: a2a_response.model_dump(...) -> protobuf serialization
  • remote_a2a_agent.py line 725: a2a_request.model_dump(...) -> protobuf serialization
  • remote_a2a_agent.py line 744: a2a_request.model_dump(...) -> protobuf serialization

Deserialization: .model_validate() / .model_validate_json()

  • part_converter.py line 125: FunctionCall.model_validate(part.data, ...) (GenAI type -- [SAFE])
  • part_converter.py line 135: FunctionResponse.model_validate(part.data, ...) (GenAI type -- [SAFE])
  • part_converter.py line 144: CodeExecutionResult.model_validate(part.data, ...) (GenAI type -- [SAFE])
  • part_converter.py line 153: ExecutableCode.model_validate(part.data, ...) (GenAI type -- [SAFE])
  • part_converter.py line 207: DataPart.model_validate_json(...) -> protobuf deserialization
  • part_converter.py line 226: part.video_metadata.model_dump(...) (GenAI type -- [SAFE])

Note: model_dump/model_validate on genai_types objects (GenAI SDK) are [SAFE] -- those remain Pydantic. Only calls on a2a.types objects need migration.


7. AgentCard.url Removal (~3 source sites)

  • agent_card_builder.py line 82: url=f"{self._rpc_url.rstrip('/')}" -> supported_interfaces=...
  • remote_a2a_agent.py line 296: if not agent_card.url: -> Check supported_interfaces
  • remote_a2a_agent.py line 303: urlparse(str(agent_card.url)) -> Parse from supported_interfaces
  • remote_a2a_agent.py line 308: agent_card.url in error message -> Update

8. Server Architecture Changes

  • agent_to_a2a.py line 24: from a2a.server.apps import A2AStarletteApplication -> Updated import
  • agent_to_a2a.py line 181: A2AStarletteApplication(agent_card=..., http_handler=...) -> New pattern
  • agent_to_a2a.py line 187: a2a_app.add_routes_to_app(app) -> New route registration
  • agent_to_a2a.py line 26: from a2a.server.request_handlers import DefaultRequestHandler -> Updated import
  • agent_to_a2a.py line 27: from a2a.server.tasks import InMemoryPushNotificationConfigStore -> Updated import
  • agent_to_a2a.py line 28: from a2a.server.tasks import InMemoryTaskStore -> Updated import
  • agent_to_a2a.py line 69: AgentCard(**agent_card_data) -> Protobuf construction

9. Client API Changes

  • remote_a2a_agent.py line 29: from a2a.client import Client as A2AClient -> Verify import path
  • remote_a2a_agent.py line 30: from a2a.client import ClientEvent as A2AClientEvent -> Verify type
  • remote_a2a_agent.py line 31: from a2a.client.card_resolver import A2ACardResolver -> Verify import
  • remote_a2a_agent.py line 32: from a2a.client.client import ClientConfig as A2AClientConfig -> Verify import
  • remote_a2a_agent.py line 33: from a2a.client.client_factory import ClientFactory as A2AClientFactory -> Verify import
  • remote_a2a_agent.py line 34: from a2a.client.errors import A2AClientHTTPError -> Verify import
  • remote_a2a_agent.py line 35: from a2a.client.middleware import ClientCallContext -> Verify import
  • remote_a2a_agent.py line 44: from a2a.types import TransportProtocol as A2ATransport -> Verify import
  • remote_a2a_agent.py line 669: self._a2a_client.send_message(request=..., ...) -> Verify API

10. Message/Event Construction (~20 source sites)

Message(...) Construction

  • event_converter.py line 232: Message(message_id="", role=Role.agent, parts=...)
  • event_converter.py lines 409-411: Message(message_id=..., role=role, parts=output_parts)
  • event_converter.py lines 450-459: Message(message_id=..., role=Role.agent, parts=[TextPart(text=...)])
  • from_adk_event.py lines 148-150: Message(message_id=..., role=Role.agent, parts=[A2APart(root=TextPart(...))])
  • from_adk_event.py lines 229-231: Message(message_id=..., role=Role.agent, parts=[])
  • long_running_functions.py lines 117-119: Message(message_id=..., role=Role.agent, parts=a2a_parts)
  • long_running_functions.py lines 203-210: Message(message_id=..., role=Role.agent, parts=[A2APart(root=TextPart(...))])
  • a2a_agent_executor.py lines 187-191: Message(message_id=..., role=Role.agent, parts=[TextPart(text=...)])
  • a2a_agent_executor_impl.py lines 177-179: Message(message_id=..., role=Role.agent, parts=[TextPart(text=...)])
  • remote_a2a_agent.py lines 643-648: A2AMessage(message_id=..., parts=..., role="user", context_id=...)

TaskStatusUpdateEvent(...) Construction

  • event_converter.py lines 444-465: Error status event
  • event_converter.py lines 523-529: Running status event
  • from_adk_event.py lines 142-156: Error status event
  • from_adk_event.py lines 222-237: Working status event
  • long_running_functions.py lines 111-124: Long-running function call event
  • long_running_functions.py lines 196-218: Missing user input event
  • a2a_agent_executor.py lines 157-170: Task submitted event
  • a2a_agent_executor.py lines 179-200: Task failed event
  • a2a_agent_executor.py lines 234-250: Task working event
  • a2a_agent_executor.py lines 293-303: Task completed event
  • a2a_agent_executor.py lines 305-316: Final status event
  • a2a_agent_executor_impl.py lines 145-156: Task working event
  • a2a_agent_executor_impl.py lines 170-189: Task failed event
  • a2a_agent_executor_impl.py lines 243-251: Task completed event

TaskArtifactUpdateEvent(...) Construction

  • a2a_agent_executor.py lines 281-291: Artifact update event
  • from_adk_event.py lines 209-220: Artifact update event

Task(...) Construction

  • a2a_agent_executor_impl.py lines 122-134: Task(id=..., status=..., context_id=..., history=..., metadata=...)

TaskStatus(...) Construction

  • All TaskStatusUpdateEvent sites above include TaskStatus(state=..., ...) construction

Artifact(...) Construction

  • a2a_agent_executor.py lines 288-290: Artifact(artifact_id=..., parts=...)
  • from_adk_event.py lines 215-218: Artifact(artifact_id=..., parts=...)
  • include_artifacts_in_a2a_event.py lines 48-52: Artifact(artifact_id=..., name=..., parts=[...])

11. Sample Code Updates

  • contributing/samples/a2a_basic/agent.py: Update A2A types
  • contributing/samples/a2a_auth/agent.py: Update A2A types
  • contributing/samples/a2a_human_in_loop/agent.py: Update A2A types
  • contributing/samples/a2a_root/agent.py: Update A2A types
  • contributing/samples/adk_triaging_agent/agent.py: Update A2A types (if uses A2A directly)

12. Protobuf Serialization Helpers (new code to write)

  • Create a utility function to replace model_dump() on A2A protobuf objects
  • Create a utility function to replace model_dump_json() on A2A protobuf objects
  • Create a utility function to replace model_validate_json() on A2A protobuf objects
  • Verify model_copy(deep=True) replacement for protobuf (line 69 in long_running_functions.py)

13. Test Infrastructure

  • Verify Mock(spec=TextPart) still works with protobuf classes (test_to_adk.py)
  • Verify assertion patterns for protobuf equality (protobuf == may behave differently)
  • Update test fixtures that construct A2A Pydantic objects to use protobuf constructors

Progress Tracking

Phase Status Files Done Files Total
Phase 0: Preparation Not started 0 N/A
Phase 1: Compat layer Not started 0 ~20
Phase 2: Migrate tests Not started 0 15
Phase 3: Migrate impl Not started 0 20
Phase 4: Integration Not started 0 N/A
Phase 5: Cleanup Not started 0 ~20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment