Skip to content

[DRAFT] Upgrade a2a v0.2.3 #2144

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

physicsrob
Copy link

Summary

This PR upgrades the fasta2a implementation from A2A protocol v0.1 to v0.2.3. The upgrade introduces significant improvements to conversation continuity through the new A2A context_id concept and implements a dual-purpose storage architecture for efficient multi-turn conversations.

Note: This is a breaking change and is not backward compatible. However, given the limited adoption of the previous protocol version, this change prioritizes alignment with the current A2A specification.

Key Changes

Protocol Updates

  • Renamed endpoint from tasks/send to message/send
  • Replaced type fields with kind throughout the schema
  • Switched from client-generated to server-generated task IDs
  • Updated Message and Task structures to v0.2.3 format
  • Replaced session_id with context_id for conversation continuity and adherence to A2A spec

Conversation Model Enhancements

  • Context-based conversations: Introduced context_id to group related messages across multiple tasks
  • Dual-purpose storage:
    • Task storage maintains A2A protocol compliance
    • Context storage preserves rich agent state (tool calls, thinking, etc.)
  • Conversation continuity: Multi-turn conversations now span multiple task executions
  • Storage API improvements:
    • Added update_context() and get_context() methods for agent-specific state
    • Modified update_task() to accept new_messages and new_artifacts lists
    • Removed deprecated message parameter from update_task()

Schema Enhancements

  • Added id field to PushNotificationConfig for server-assigned identifiers
  • Added new task states: rejected and auth-required
  • Implemented proper file handling with FileWithBytes and FileWithUri
  • Added type guards (is_task(), is_message()) for better type safety

Artifact Improvements

  • Support for both TextPart and DataPart in artifacts based on output type
  • Added unique artifact IDs for better tracking
  • Dual output approach:
    • Agent results stored as Messages (for conversation history)
    • Agent results stored as Artifacts (for durable outputs)
  • Enhanced metadata including JSON schema for Pydantic models

AgentWorker Implementation

  • Loads full pydantic-ai message history from context storage
  • Preserves complete conversation state including tool calls and responses
  • Converts between A2A and pydantic-ai message formats as needed
  • Ensures task state validation (only processes 'submitted' tasks)

Test Changes

  • Updated all tests to work with the new protocol format
  • Added test for Pydantic model outputs with JSON schema metadata
  • Added test for monotonic message history growth across contexts
  • Renamed test directory from tests/fasta2a/ to tests/test_fasta2a/ to avoid import conflicts

Breaking Changes

  1. Storage API changes:

    • New required methods: update_context() and get_context()
    • Task submission now requires context_id
    • update_task() signature changed
  2. Message format changes:

    • typekind throughout
    • session_idcontext_id
    • New required fields in various message types
  3. Endpoint changes:

    • tasks/sendmessage/send

- Update protocol methods: tasks/send → message/send
- Replace 'type' with 'kind' throughout schema
- Replace 'session_id' with 'context_id' for conversation tracking
- Add Message and Part types (TextPart, FilePart, DataPart)
- Implement dual message/artifact approach for agent outputs
- Add metadata to artifacts including type info and JSON schema
- Add proper error handling with task state updates
- Add NotImplementedError stubs for streaming methods
- Rename test directory to avoid import conflicts
- Test that Pydantic model outputs are correctly serialized as DataPart
- Verify metadata includes type name and JSON schema
- Ensure dual message/artifact approach works for complex types
- Confirm that both message history and artifacts contain the data
- Add update_context() and get_context() methods to Storage
- Store full pydantic-ai message history (including tool calls) in context
- Preserve conversation state across multiple tasks with same context_id
- Update docs to explain task vs context distinction
- Add test for monotonic message history growth
- Clean up run_task: remove history_length, add state check, fix comments
Copy link
Contributor

hyperlint-ai bot commented Jul 7, 2025

PR Change Summary

Upgraded the fasta2a implementation to A2A protocol v0.2.3, introducing significant enhancements for conversation continuity and a dual-purpose storage architecture.

  • Renamed endpoint from tasks/send to message/send and replaced type fields with kind in the schema.
  • Introduced context_id for improved conversation continuity and updated storage architecture for multi-turn conversations.
  • Implemented new methods in the Storage API and made breaking changes to message formats and task submissions.

Modified Files

  • docs/a2a.md

How can I customize these reviews?

Check out the Hyperlint AI Reviewer docs for more information on how to customize the review.

If you just want to ignore it on this PR, you can add the hyperlint-ignore label to the PR. Future changes won't trigger a Hyperlint review.

Note specifically for link checks, we only check the first 30 links in a file and we cache the results for several hours (for instance, if you just added a page, you might experience this). Our recommendation is to add hyperlint-ignore to the PR to ignore the link check for this PR.

@physicsrob
Copy link
Author

@Kludex
I noticed your message "Working on this" -- just FYI I spend a bunch of time on yesterday working on pulling out just the upgrade. Here's a draft PR:.

I was going to spend a couple more hours cleaning up before submitting a PR, but it's fairly close. I did end up changing the approach pretty significantly to conversation continuity. There was a pretty big fundamental limitation in the previous incarnation: Since only A2A messages were persisted / retrieved that meant that follow-up tasks could only see messages that were converted to/from A2A's format, and that's fundamentally going to be lossy. In particular tool call results from previous tasks are going to be invisible to the agent for subsequent calls. I personally think that's a big limitation worth addressing.

The one area I'm very much less convinced of my approach: Whether final results should be an artifact or both an artifact and a message. Right now in the PR it generates both. But I was probably going to make it just an artifact

Comment on lines +128 to +130
elif a2a_request['method'] == 'tasks/send': # type: ignore[comparison-overlap]
# Legacy method - no longer supported
raise NotImplementedError('tasks/send is deprecated. Use message/send instead.')
Copy link
Member

Choose a reason for hiding this comment

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

There's no need for this. Just drop it.

Copy link
Author

Choose a reason for hiding this comment

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

👍


payload = SendTaskRequest(jsonrpc='2.0', id=None, method='tasks/send', params=task)
content = a2a_request_ta.dump_json(payload, by_alias=True)
request_id = str(uuid.uuid4())
Copy link
Member

Choose a reason for hiding this comment

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

What is this ID? Is it the request_id?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, this is the request_id for JSON-RPC. Let me know if you think a clarifying comment would be helpful, or if there's something off about the variable names

Comment on lines +315 to +316
description: NotRequired[str]
"""A description of the data."""
Copy link
Member

Choose a reason for hiding this comment

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

I don't see any description on the DataPart on the specification.

Suggested change
description: NotRequired[str]
"""A description of the data."""

Copy link
Author

Choose a reason for hiding this comment

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

yeah not sure how that ended up there. Fixed. thx.

"""A fully formed piece of content exchanged between a client and a remote agent as part of a Message or an Artifact.
Each Part has its own content type and metadata.
"""

TaskState: TypeAlias = Literal['submitted', 'working', 'input-required', 'completed', 'canceled', 'failed', 'unknown']
TaskState: TypeAlias = Literal[
'submitted', 'working', 'input-required', 'completed', 'canceled', 'failed', 'rejected', 'auth-required'
Copy link
Member

Choose a reason for hiding this comment

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

Unknown is still in the specification.

Suggested change
'submitted', 'working', 'input-required', 'completed', 'canceled', 'failed', 'rejected', 'auth-required'
'submitted', 'working', 'input-required', 'completed', 'canceled', 'failed', 'rejected', 'auth-required', 'unknown'

Copy link
Author

Choose a reason for hiding this comment

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

👍

Comment on lines +650 to +657
def is_task(response: Task | Message) -> TypeGuard[Task]:
"""Type guard to check if a response is a Task."""
return 'id' in response and 'status' in response and 'context_id' in response and response.get('kind') == 'task'


def is_message(response: Task | Message) -> TypeGuard[Message]:
"""Type guard to check if a response is a Message."""
return 'role' in response and 'parts' in response and response.get('kind') == 'message'
Copy link
Member

Choose a reason for hiding this comment

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

This seems to be for the tests. There's no need to include those in the schema.

Also, on the tests, just check the kind is task or message. It's enough to infer the type for the type checker.

Copy link
Author

Choose a reason for hiding this comment

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

👍

async def send_message(self, request: SendMessageRequest) -> SendMessageResponse:
"""Send a message using the A2A v0.2.3 protocol."""
request_id = request['id']
task_id = str(uuid.uuid4())
Copy link
Member

Choose a reason for hiding this comment

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

This should be generated in the submit_task - so we have the task id in the task object after.

Comment on lines +168 to +169
"""Stream messages using Server-Sent Events. Not implemented."""
raise NotImplementedError('message/stream method is not implemented yet.')
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"""Stream messages using Server-Sent Events. Not implemented."""
raise NotImplementedError('message/stream method is not implemented yet.')
"""Stream messages using Server-Sent Events."""
raise NotImplementedError('message/stream method is not implemented yet.')

physicsrob and others added 6 commits July 7, 2025 21:15
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants