feat(server): implement SendLiveMessage bidi streaming RPC by Tehsmash · Pull Request #1 · Tehsmash/a2a-python · GitHub
Skip to content

feat(server): implement SendLiveMessage bidi streaming RPC - #1

Open
Tehsmash wants to merge 373 commits into
mainfrom
feat/send-live-message
Open

Tehsmash wants to merge 373 commits into
mainfrom
feat/send-live-message

Conversation

@Tehsmash

@Tehsmash Tehsmash commented Sep 3, 2026 •

Copy link
Copy Markdown
Owner

Description

Implements the SendLiveMessage bidirectional streaming RPC from A2A spec PR #2153.

SendLiveMessage allows a client to push a stream of StreamRequest messages (each containing a Message or TaskArtifactUpdateEvent) while receiving a stream of StreamResponse events. This enables lower-latency real-time interaction and multi-turn in-flight agent conversations.

Changes

  • Proto: Update buf.gen.yaml ref to the herczyn:feat-bidi commit and regenerate types. New types include StreamRequest, TaskMessageUpdateEvent, TimelineEntry; updated StreamResponse, Task, Artifact, SendMessageConfiguration.
  • Server RequestHandler ABC: Add non-abstract on_live_message_send that raises UnsupportedOperationError so existing subclasses don't break.
  • DefaultRequestHandlerV2: Full implementation — bootstrap the task from the first StreamRequest, spawn a background feeder task to enqueue follow-up messages via active_task.enqueue_request(), and subscribe without a request so the stream stays open until the task finishes naturally.
  • GrpcHandler: Add SendLiveMessage bidi method matching the generated servicer interface.
  • REST dispatcher: Stub /live endpoint that returns UnsupportedOperationError — REST does not support bidi primitives.
  • REST routes: Register /live POST (and /{tenant}/live via the existing mount).
  • Client: ClientTransport base raises UnsupportedOperationError by default; GrpcTransport implements full bidi; BaseClient and Client ABC expose send_live_message.
  • artifact_update StreamRequests: Client-supplied artifact updates are now fully handled. They are persisted via ActiveTask.enqueue_artifact_update() (injected into _event_queue_agent → EventConsumer → append_artifact_to_task(), identical path to agent-emitted artifacts) and also delivered to the running executor via input_queue.get() as a RequestContext with artifact_update set, so the agent can react. RequestContext gains an artifact_update field for this purpose.
  • Integration tests: tests/integration/test_live_message.py covers the gRPC bidi path end-to-end: 3-message exchange, single-burst, empty stream error, and client artifact_update persisted and visible via GetTask.

Note: Task.timeline population and timeline_length query wiring are deferred to a follow-up.

a2a-bot and others added 30 commits March 10, 2026 14:07
🤖 I have created a release *beep* *boop*
---


##
[0.3.25](a2aproject/a2a-python@v0.3.24...v0.3.25)
(2026-03-10)


### Features

* Implement a vertex based task store
([a2aproject#752](a2aproject#752))
([fa14dbf](a2aproject@fa14dbf))


### Bug Fixes

* return background task from consume_and_break_on_interrupt to prevent
GC ([a2aproject#775](a2aproject#775))
([a236d4d](a2aproject@a236d4d))
* use default_factory for mutable field defaults in ServerCallContext
([a2aproject#744](a2aproject#744))
([22b25d6](a2aproject@22b25d6))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
# Description

This PR a2aproject/A2A#1507 introduces a
breaking change in the specification which is going to be addressed
separately.
…2aproject#795)

* Unified v0.3 Handling (grpc_handler.py): Refactored the
CompatGrpcHandler to delegate directly to
RequestHandler03, removing ad-hoc data transformations and unifying
translation logic across all transports.
* REST & JSONRPC Adapters: Implemented and tested legacy HTTP adapters.
Added comprehensive tests in
test_rest_handler.py and test_rest_fastapi_app_compat.py to validate
exact JSON payload shapes and legacy
     routing (e.g., /v0.3/v1/message:send).
* Feature Flag Integration: Introduced the enable_v0_3_compat flag for
A2AStarletteApplication and
A2ARESTFastAPIApplication. When disabled, legacy requests are securely
rejected with standard errors to
     prevent overhead.
* Cross-Version Integration: Upgraded the v1.0 reference test server
(server_1_0.py) to announce and mount
legacy REST interfaces. The automated test matrix now explicitly
validates jsonrpc and rest paths alongside
     grpc.

Fixes a2aproject#742
Source: [11.5. Query Parameter Naming for Request
Parameters](https://a2a-protocol.org/latest/specification/#115-query-parameter-naming-for-request-parameters):

> **Field Type Handling:**
>
> - **Strings**: Passed directly as query parameter values
> - **Booleans**: Represented as lowercase strings (`true`, `false`)
> - **Numbers**: Represented as decimal strings
> - **Enums**: Represented using their string values (e.g.,
`status=working`)
> - **Repeated Fields**: Multiple values **MAY** be passed by repeating
the parameter name (e.g., `?tag=value1&tag=value2`) or as
comma-separated values (e.g., `?tag=value1,value2`)
> - **Nested Objects**: Not supported in query parameters; operations
requiring nested objects **MUST** use POST with a request body
> - **Datetimes/Timestamps**: Represented as ISO 8601 strings (e.g.,
`2025-11-09T10:30:00Z`)

1. Using `MessageToDict` in combination with `httpx` client produces
correct output (test is added).
2. Parsing has an extra addition on top of `ParseDict` to support
repeated fields in both formats (`?tag=value1&tag=value2` and
`?tag=value1,value2`).
3. `test_client_server_integration.py` is updated to dedupe gRPC and
HTTP based tests and it also asserts params now.
…roject#783)

## Description
Implements a mechanism to handle legacy v0.3 data stored in the
database. When a `Task` or a `TaskPushNotificationConfig` does not have
a `protocol_version` set to `0.1`, it validates and converts the data
using v0.3 Pydantic models and conversion utilities. This ensures
backward compatibility for existing records containing string-based
enums and old field structures.

## Changes
- The `status`, `artifacts`, and `history` fields in `TaskMixin` now use
standard SQLAlchemy JSON columns with explicit Python type hints
(Mapped[Any], Mapped[list[Any]]).
- Removed `PydanticType` and `PydanticListType`: These custom SQLAlchemy
types are no longer needed as serialization is now handled at the Store
level.
- Updated `_to_orm` to use MessageToDict on the entire Task object
- Updated `_from_orm`:
   - v1.0: Uses ParseDict
- Legacy (v0.3): Uses Pydantic's model_validate to reconstruct the
legacy Task tree before converting to core types.
 - Updated Tests: 
    - Removed obsolete tests for the deleted Pydantic type 
- updated the Task Store integration tests to verify the new `0.3 type
to 1.0 type` conversion logic


## Contributing Guide
- [x] Follow the [`CONTRIBUTING`
Guide](https://github.com/a2aproject/a2a-python/blob/main/CONTRIBUTING.md).
- [x] Make your Pull Request title in the
<https://www.conventionalcommits.org/> specification.
- Important Prefixes for
[release-please](https://github.com/googleapis/release-please):
- `fix:` which represents bug fixes, and correlates to a
[SemVer](https://semver.org/) patch.
- `feat:` represents a new feature, and correlates to a SemVer minor.
- `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking
change (indicated by the `!`) and will result in a SemVer major.
- [x] Ensure the tests and linter pass (Run `bash scripts/format.sh`
from the repository root to format)
- [x] Appropriate docs were updated (if necessary)

Fixes a2aproject#715 🦕
Add tests and update REST error handler to catch `ParseError` thrown by ProtoJSON, currently it produces 500.
# Description

This PR introduces a check for the jsonrpc version, which for tck tests
and
[specifications](https://a2a-protocol.org/latest/specification/#91-protocol-requirements)
must be 2.0.
1. The script invokes Python linters from the CI, JSCPD is not invoked.
2. GitHub action is updated to run pyright using local env to match
versions and make results reproduciable.

<img width="870" height="564" alt="image"
src="https://github.com/user-attachments/assets/ece92c55-b1c3-4475-9cc7-2b9b93cc99f1"
/>
To make tests a bit higher level and closer to how it's used externally.
…ly) (a2aproject#810)

Original spec PR: a2aproject/A2A#1507.

Required a bunch of inversions including compat conversion updates. The
gotcha here is that 0.3 SDK **already** uses blocking mode by default
(although it's not according to the spec), so this spec change is about
applying a de-facto standard.
…2aproject#813)

Removed redundant type casting in database_task_store.py.
1. Run release-please on 1.0-dev and set `target-branch` in the GitHub
Action.
2. Use advanced configuration to specify pre-release versioning
(`"prerelease": true` and `"prerelease-type": "alpha"`), this is going
to result in `1.0.0a0` (see
[here](https://packaging.python.org/en/latest/specifications/version-specifiers/#pre-releases)
package for `v1.0.0-alpha.0` tag.
3.
[python-publish.yml](https://github.com/a2aproject/a2a-python/blob/main/.github/workflows/python-publish.yml)
workflow was disabled as a safety measure in case release-please goes
crazy with this config.

For now do not specify `last-release-sha` explicitly
([docs](https://github.com/googleapis/release-please/blob/main/docs/manifest-releaser.md#configfile)),
will check the behavior first, changelog will have to be created
manually most likely either way.
Release-As: v1.0.0-alpha.0
From

```bash
git log origin/main..1.0-dev --oneline --reverse | head -n 1
```
…aproject#812)

# Description
Old version is not able to write in databases which are `1.0` compatible
because the new `owner` field is mandatory. This change makes the
`owner` field `optional` to maintain forward compatibility.

## Tested 
### Before change (owner field mandatory)
I created a database with tables `tasks` and `push_notification_configs`
using `1.0` spec. I then tested it against `0.3` spec by trying to
**write** in it. It failed returning an error: `NOT NULL constraint
failed: tasks.owner`.

Also, I created a database with `0.3` spec `Task` and
`PushNotificationConfigs` entries but then made it `1.0` compatible via
migration CLI command `uv run a2a-db`. **Reading** such data using `0.3`
spec worked, but **writing** failed for the same reason as before.
### After change (owner field optional)
I repeated the testing from before. Now writing in `1.0` compatible
databases worked.

## Notes
Writing in database which was created using `1.0` spec without running
the migration `a2a-db` CLI command, created entries which have
`owner=NULL` values, contrary to databases that underwent the migration
using `a2a-db` which have `owner` field populated with the default
value. In my case with `legacy_v03_no_user_info` since I didn't use the
`--add_columns_owner_last_updated-default-owner` flag to pass my own
custom default value.


## Contributing Guide
- [x] Follow the [`CONTRIBUTING`
Guide](https://github.com/a2aproject/a2a-python/blob/main/CONTRIBUTING.md).
- [x] Make your Pull Request title in the
<https://www.conventionalcommits.org/> specification.
- Important Prefixes for
[release-please](https://github.com/googleapis/release-please):
- `fix:` which represents bug fixes, and correlates to a
[SemVer](https://semver.org/) patch.
- `feat:` represents a new feature, and correlates to a SemVer minor.
- `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking
change (indicated by the `!`) and will result in a SemVer major.
- [x] Ensure the tests and linter pass (Run `bash scripts/format.sh`
from the repository root to format)
- [x] Appropriate docs were updated (if necessary)

Fixes a2aproject#811 🦕
……s`, `artifacts`, and `history`. (a2aproject#819)

This is a rollback of a change made in:
a2aproject#783
…a2aproject#814)

# Description

This PR addresses two main issues related to the list_tasks JSON-RPC
endpoint:

- Fixed how list_tasks formats the ListTasksResponse to ensure all
fields are explicitly printed, even when they carry default values (such
as an empty next_page_token).
- Replaced generic ValueError exceptions with A2A SDK's standard
InvalidParamsError when handling malformed or invalid pagination tokens.
This correctly surfaces -32602 Invalid params via JSON-RPC.
…ts (a2aproject#820)

- Removed the `/v0.3` prefix from v0.3 compatibility REST routes in
`A2ARESTFastAPIApplication`, unifying the URL routing structure.
- Expanded cross-version integration tests (`client_0_3`, `client_1_0`,
`server_0_3`, `server_1_0`) to validate complex message parts, including
URIs, raw bytes, and structured data parts.
- Added cross-version test coverage for the push notification config
lifecycle (create, get, list, delete) and task listing.
- Improved subprocess cleanup and lifecycle management in
`test_client_server.py` using process groups (`os.killpg`) to prevent
dangling server processes on timeout.
- Added a custom logging middleware for easier debugging during
cross-version integration tests.
…#799) (a2aproject#800)

Made a minor change to A2ACardResolver.get_agent_card() so it doesn't
introduce an spurious trailing slash when agent_card_path is empty. This
allows one to get the card from a card URL without having to break it
into a base and relative card path component, it's arguably a bit odd
but the empty agent_card_path is technically allowed and the result of
that isn't right so I think it's a reasonable improvement, and one that
as far as I can see can't reasonably be not backwards compatible for
anyone.

I changed one existing test, but only in a fashion such that it still
tests the thing that it says it is testing... It is fine IF I understood
the point of the test...

# Description

Thank you for opening a Pull Request!
Before submitting your PR, there are a few things you can do to make
sure it goes smoothly:

- [X] Follow the [`CONTRIBUTING`
Guide](https://github.com/a2aproject/a2a-python/blob/main/CONTRIBUTING.md).
- [X] Make your Pull Request title in the
<https://www.conventionalcommits.org/> specification.
- Important Prefixes for
[release-please](https://github.com/googleapis/release-please):
- `fix:` which represents bug fixes, and correlates to a
[SemVer](https://semver.org/) patch.
- `feat:` represents a new feature, and correlates to a SemVer minor.
- `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking
change (indicated by the `!`) and will result in a SemVer major.
- [X] Ensure the tests and linter pass (Run `bash scripts/format.sh`
from the repository root to format)
- [X] Appropriate docs were updated (if necessary)

Fixes a2aproject#799

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Iva Sokolaj <102302011+sokoliva@users.noreply.github.com>
…#790)

# Description
This PR implements standard gRPC rich error handling using
`google.rpc.Status` and `google.rpc.ErrorInfo`, bringing the SDK's gRPC
transport fully in line with the A2A v1.0 specification.

Previously, the gRPC server appended the exception name to the string
message (e.g., "TaskNotFoundError: task not found"), and the client
relied on string splitting to parse the error back into a domain
exception. This approach was brittle and not interoperable with standard
gRPC ecosystems (proxies, gateways, etc.).

This PR replaces the legacy string-parsing heuristic entirely with
strongly-typed binary metadata (`grpc-status-details-bin`).

- [X] Follow the [`CONTRIBUTING`
Guide](https://github.com/a2aproject/a2a-python/blob/main/CONTRIBUTING.md).
- [X] Make your Pull Request title in the
<https://www.conventionalcommits.org/> specification.
- Important Prefixes for
[release-please](https://github.com/googleapis/release-please):
- `fix:` which represents bug fixes, and correlates to a
[SemVer](https://semver.org/) patch.
- `feat:` represents a new feature, and correlates to a SemVer minor.
- `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking
change (indicated by the `!`) and will result in a SemVer major.
- [X] Ensure the tests and linter pass (Run `bash scripts/format.sh`
from the repository root to format)
- [X] Appropriate docs were updated (if necessary)

Fixes a2aproject#723 🦕

---------

Co-authored-by: Ivan Shymko <ishymko@google.com>
…a2aproject#821)

# Description
Fix the database PushNotificationConfig store when reading legacy data
from version 0.3. The store now correctly handles cases when legacy
PushNotificationConfig was stored.
It's not fully transparent for non-release versions (like alpha or rc)
how git tag names (i.e. `v1.0.0-alpha.0`) are converted to [Python
version
specifiers](https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers)
(i.e. `1.0.0a0`). This can be checked only during [publish GitHub
action](https://github.com/a2aproject/a2a-python/actions/workflows/python-publish.yml)
execution which both builds and pushes PyPi package.

`release-please` supports updating `pyproject.toml` so we will have the
same level of automation.

Current version is set to `0.3.25` - the latest release from `main`.
…2aproject#827)

Turns out it creates more problems than solves and requires more
intervention into release-please to also update `uv.lock`.

release-please still uses SemVer values in its PRs, but they are
normalized during build (see
[here](https://packaging.python.org/en/latest/specifications/version-specifiers/#normalization)).
a2a-bot and others added 30 commits July 16, 2026 15:29
🤖 I have created a release *beep* *boop*
---


##
[1.1.1](a2aproject/a2a-python@v1.1.0...v1.1.1)
(2026-07-15)


### Bug Fixes

* **server:** improve Swagger /docs example annotations for A2A routes
([a2aproject#1092](a2aproject#1092))
([46b292b](a2aproject@46b292b))

### Miscellaneous Chores
* **grpc:** eliminate third-party grpcio-status dependency
([a2aproject#1130](a2aproject#1130))
([723880e](a2aproject@723880e))
* **http:** eliminate httpx-sse dependency
([a2aproject#1132](a2aproject#1132))
([0d82ab9](a2aproject@0d82ab9))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: Iwaniukooo11 <mateusz.iwaniuk11@gmail.com>
a2aproject#1106)

Fixes a2aproject#1067

## Summary
- seed the originating message into V2 ActiveTask TaskManager creation
so newly created fallback tasks retain request history
- synchronously persist TASK_STATE_FAILED from _run_producer when
producer setup/execution raises, so persistence does not depend on the
closing event queue
- avoid appending the seeded originating message a second time when the
first task update also carries it

## Tests
- ./scripts/lint.sh
- uv run pytest
tests/server/request_handlers/test_default_request_handler_v2.py::test_on_message_send_early_producer_exception_marks_task_failed
tests/server/request_handlers/test_default_request_handler_v2.py::test_on_message_send_early_producer_exception_preserves_originating_message
- uv run pytest
tests/server/request_handlers/test_default_request_handler_v2.py

---------

Co-authored-by: mykytanetipa <mykytanetipa@google.com>
…ing during teardown (a2aproject#1122)

Fixes a2aproject#1121. This PR resolves the issue where `ActiveTask` would throw
an asyncio warning when being garbage collected, by ensuring the
`_producer_task` is properly awaited during the `_run_consumer` teardown
path, allowing queues to properly close without leaving dangling pending
background tasks.

---------

Co-authored-by: mykytanetipa <mykytanetipa@google.com>
…roject#1101) (a2aproject#1105)

## Summary

Adds a public `aclose()` teardown to `ActiveTask`, `ActiveTaskRegistry`,
and
`DefaultRequestHandlerV2` that force-drains the SDK-owned producer,
consumer,
and dispatcher `asyncio.Task`s so none are left pending at event-loop
shutdown.

- `ActiveTask.aclose()` force-closes both event queues and cancels the
producer
and consumer tasks, then awaits them. It sets `_is_finished` under
`_lock`, so
  it is mutually exclusive with `start()` (which refuses to spawn once
  `_is_finished` is set).
- `ActiveTaskRegistry.aclose()` marks the registry closed so
`get_or_create`
  refuses new work, drains every active task, then awaits the in-flight
`_remove_task` cleanup tasks. The lock is released before awaiting
because
  `_remove_task` re-acquires it.
- `DefaultRequestHandlerV2.aclose()` delegates to the registry drain,
for wiring
  into an ASGI `lifespan` / `on_shutdown` hook.

## Why

Fixes a2aproject#1101. At shutdown the `ActiveTask` producer can stay pending and
surface
as `Task was destroyed but it is pending!`. The producer's `finally`
calls
`_event_queue_subscribers.close(immediate=False)`, which awaits `join()`
on
every subscriber sink; an abandoned subscriber leaves an undrained sink,
so the
`join()` never returns and the producer hangs. There is no public way to
drain
these background tasks today. `aclose()` closes the queues with
`immediate=True`,
which releases the wedged producer, and reaps the tasks.

The teardown always forces rather than exposing a graceful
`immediate=False`
option, because that path inherits the documented
`close(immediate=False)`
deadlock and a shutdown hook must be bounded.

## Test plan

- [x] `uv run pytest tests/server/agent_execution/
tests/server/request_handlers/ tests/server/events/` — pass
- [x] `uv run pytest --cov=a2a --cov-fail-under=88` — pass
- [x] `./scripts/lint.sh` — ruff, ruff-format, and ty clean

New tests cover the registry drain, idempotency, empty registry,
new-work
rejected after close, and an errored task being logged rather than
propagated;
`ActiveTask` reaping a running producer and force-closing past an
undrained
subscriber (the a2aproject#1101 repro); and the handler drain.

Fixes a2aproject#1101 🦕
🤖 I have created a release *beep* *boop*
---


##
[1.1.2](a2aproject/a2a-python@v1.1.1...v1.1.2)
(2026-07-20)


### Features

* **server:** add aclose() to drain ActiveTask background tasks
([a2aproject#1101](a2aproject#1101))
([a2aproject#1105](a2aproject#1105))
([9801f46](a2aproject@9801f46))


### Bug Fixes

* **agent_execution:** resolve ActiveTask 'destroyed but pending'
warning during teardown
([a2aproject#1122](a2aproject#1122))
([d19c4d2](a2aproject@d19c4d2))
* persist early producer failure as FAILED with originating message
([a2aproject#1106](a2aproject#1106))
([4e3d724](a2aproject@4e3d724))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Safety net pipeline to protect from accidental release creation which
recognizes `release-please` PRs based on the `a2a-bot` author and
`release-please--` branch name prefix and requires **2 approvers** with
at least `write` level access. This is **an addition** on top of
built-in GitHub write access check. Release PRs could never be merged by
non-maintainers, but now the requirement is 2 maintainers.

Published via a commit status instead of a workflow failure to avoid
"spam" emails of workflow failures, this check will be added as
mandatory. For non-release PRs passes immediately.

Tested on fork:
1. "release" PR: ishymko#7
<img width="784" height="94" alt="image"
src="https://github.com/user-attachments/assets/8fa922d5-cd04-46ca-a0ca-1de099fd7a46"
/>

2. "normal" PR: ishymko#6
<img width="678" height="102" alt="image"
src="https://github.com/user-attachments/assets/e7ef3242-9aa6-454c-bf53-d6d1acddec1f"
/>
Follow-up for a2aproject#1144.

Release please PRs are never created from forks, so running on
`pull_request_review` (which has only readonly access for PRs from
forks) will never need to post a commit status. Such cases are skipped
now instead of failing (i.e.
[here](https://github.com/a2aproject/a2a-js/actions/runs/30247915633/job/89919011242?pr=602)).
…1155)

# Description

Adds itk/uv.lock so the ITK agent resolves the same
dependency set every run.
Inert until [the harness passes
--locked](a2aproject/a2a-itk#27).
Bumps [pyasn1](https://github.com/pyasn1/pyasn1) from 0.6.3 to 0.6.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pyasn1/pyasn1/releases">pyasn1's
releases</a>.</em></p>
<blockquote>
<h2>Release 0.6.4</h2>
<p>This is a security release.</p>
<ul>
<li>CVE-2026-59885 (GHSA-8ppf-4f7h-5ppj): Fixed quadratic time
complexity in the OBJECT IDENTIFIER and RELATIVE-OID decoders. A small
crafted substrate encoding many arcs could consume excessive CPU.</li>
<li>CVE-2026-59884 (GHSA-m4p7-r5rc-7g4j): Limited BER long-form tag IDs
to 20 octets (140 bits). Unbounded tag IDs allowed a crafted substrate
to consume excessive CPU and memory.</li>
<li>CVE-2026-59886 (GHSA-hm4w-wwcw-mr6r): Fixed excessive memory and CPU
consumption in <code>Real.__float__()</code> for values with large
base-10 exponents.</li>
<li>Pinned PyPI publish GitHub Action to an immutable commit.</li>
</ul>
<p>All changes are noted in the <a
href="https://github.com/pyasn1/pyasn1/blob/main/CHANGES.rst">CHANGELOG</a>.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pyasn1/pyasn1/blob/main/CHANGES.rst">pyasn1's
changelog</a>.</em></p>
<blockquote>
<h2>Revision 0.6.4, released 08-07-2026</h2>
<ul>
<li>CVE-2026-59885 (GHSA-8ppf-4f7h-5ppj): Fixed quadratic time
complexity in the OBJECT IDENTIFIER and RELATIVE-OID decoders.
A small crafted substrate encoding many arcs could consume
excessive CPU. Arcs are now accumulated in linear time; decoded
values are unchanged (thanks for reporting, tynus2)</li>
<li>CVE-2026-59884 (GHSA-m4p7-r5rc-7g4j): Limited BER long-form tag
IDs to 20 octets (140 bits), matching the OID arc limit introduced
in 0.6.2. Unbounded tag IDs allowed a crafted substrate to consume
excessive CPU and memory; longer tag IDs are now rejected with
PyAsn1Error. Also fixed Tag and TagSet repr() failing on huge tag
(thanks for reporting, mikeappsec)
IDs due to the integer-to-string conversion limit (Python 3.11+)</li>
<li>CVE-2026-59886 (GHSA-hm4w-wwcw-mr6r): Fixed excessive memory and
CPU consumption in Real.<strong>float</strong>() for values with large
base-10
exponents. Conversion no longer materializes huge intermediate
integers; values too large to represent as a Python float raise
OverflowError promptly, and prettyPrint() renders them as
'<!-- raw HTML omitted -->' as before. Also fixed base-10 mantissa
normalization
to use exact integer arithmetic; mantissas larger than 2**53
could previously lose precision through float division
(thanks for reporting, gvozdila)</li>
<li>Pinned PyPI publish GitHub Action to an immutable commit
[pr <a
href="https://redirect.github.com/pyasn1/pyasn1/issues/113">#113</a>](<a
href="https://redirect.github.com/pyasn1/pyasn1/pull/113">pyasn1/pyasn1#113</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pyasn1/pyasn1/commit/72e4803405816c371ed3b2cb4be181c47f038406"><code>72e4803</code></a>
Prepare release 0.6.4</li>
<li><a
href="https://github.com/pyasn1/pyasn1/commit/0c19eeb853731db1c717ff125ea001a1e558332d"><code>0c19eeb</code></a>
Pin PyPI publish action to immutable commit (<a
href="https://redirect.github.com/pyasn1/pyasn1/issues/113">#113</a>)</li>
<li><a
href="https://github.com/pyasn1/pyasn1/commit/45bdb19eb7df4b3780fe9c912c63e99bffc39dd9"><code>45bdb19</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/pyasn1/pyasn1/commit/628e36ecbb5277a3f01572ce418ef54271b165a5"><code>628e36e</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/pyasn1/pyasn1/commit/e60c691cb91addb8fcefa2f537e85ede6fb1e886"><code>e60c691</code></a>
Merge commit from fork</li>
<li>See full diff in <a
href="https://github.com/pyasn1/pyasn1/compare/v0.6.3...v0.6.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=pyasn1&package-manager=uv&previous-version=0.6.3&new-version=0.6.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/a2aproject/a2a-python/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Iva Sokolaj <102302011+sokoliva@users.noreply.github.com>
Bumps [setuptools](https://github.com/pypa/setuptools) from 82.0.1 to
83.0.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pypa/setuptools/blob/main/NEWS.rst">setuptools's
changelog</a>.</em></p>
<blockquote>
<h1>v83.0.0</h1>
<h2>Features</h2>
<ul>
<li>Require Python 3.10 or later.</li>
</ul>
<h2>Bugfixes</h2>
<ul>
<li><code>MANIFEST.in</code> matching (via <code>FileList</code>) is now
insensitive to Unicode
normalization form. A pattern authored in one form (e.g. NFC, as
typically
saved by editors) now matches a file whose name is stored on disk in
another
(e.g. NFD, as produced by macOS APFS/HFS+). Previously an
<code>exclude</code>,
<code>global-exclude</code>, <code>recursive-exclude</code>, or
<code>prune</code> rule could silently
fail to drop a non-ASCII-named file from the source distribution,
publishing
it despite the exclusion -- see GHSA-h35f-9h28-mq5c.</li>
</ul>
<h2>Deprecations and Removals</h2>
<ul>
<li><code>pypa/distutils#334</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pypa/setuptools/commit/6519f728c6f23c992df81a5691ef7655184a20eb"><code>6519f72</code></a>
Bump version: 82.0.1 → 83.0.0</li>
<li><a
href="https://github.com/pypa/setuptools/commit/d1151b125b29a6ee1c7db860f7ee6c365d525b5f"><code>d1151b1</code></a>
Merge pull request <a
href="https://redirect.github.com/pypa/setuptools/issues/5250">#5250</a>
from pypa/feature/distutils-d7633fbed</li>
<li><a
href="https://github.com/pypa/setuptools/commit/a2df31e6a741b0fe775969618fe6a3a3d032e900"><code>a2df31e</code></a>
Capture removal of dry_run parameter in changelog.</li>
<li><a
href="https://github.com/pypa/setuptools/commit/00144dc3fb6d023fd81cdc26c380a012b084df9b"><code>00144dc</code></a>
Moved newsfragment to the release where it occurred.</li>
<li><a
href="https://github.com/pypa/setuptools/commit/a4a5a2b160357be77626aa077ddd1d8ee53be50f"><code>a4a5a2b</code></a>
Add news fragment.</li>
<li><a
href="https://github.com/pypa/setuptools/commit/77470c23c35031c9e37d7512694ac5ca52bfcaac"><code>77470c2</code></a>
Merge <a
href="https://github.com/pypa/distutils">https://github.com/pypa/distutils</a>
into feature/distutils-d7633fbed</li>
<li><a
href="https://github.com/pypa/setuptools/commit/3c43897420f25a1be1afe6eaa905d056009cc736"><code>3c43897</code></a>
Merge pull request <a
href="https://redirect.github.com/pypa/setuptools/issues/5247">#5247</a>
from pypa/copilot/fix-pypy-version-issue</li>
<li><a
href="https://github.com/pypa/setuptools/commit/bb6ea66b4bd01cb35d7c68a1bc61b96d59190269"><code>bb6ea66</code></a>
Bump PyPy from 3.10 to 3.11 in CI workflow</li>
<li><a
href="https://github.com/pypa/setuptools/commit/a2bc3aca2f55eb9b93ea633e58ba68170ce14451"><code>a2bc3ac</code></a>
Fix broken intersphinx reference to build's installation docs</li>
<li><a
href="https://github.com/pypa/setuptools/commit/2d6a739c64cfedc65e1f635af7b52340aac8d99b"><code>2d6a739</code></a>
Use stacked parametrize decorators instead of itertools.product</li>
<li>Additional commits viewable in <a
href="https://github.com/pypa/setuptools/compare/v82.0.1...v83.0.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=setuptools&package-manager=uv&previous-version=82.0.1&new-version=83.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/a2aproject/a2a-python/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Iva Sokolaj <102302011+sokoliva@users.noreply.github.com>
…ect#1160)

## Summary

- Updates `SECURITY.md` to direct security reports to GitHub Security
Advisories on this repository instead of `g.co/vulnz`.
- Mirrors the updated policy in
[`a2aproject/A2A`](https://github.com/a2aproject/A2A/blob/main/SECURITY.md),
pointing to [Security Advisories ·
a2aproject/a2a-python](https://github.com/a2aproject/a2a-python/security/advisories).
# Description

Update the A2A logo in README.md

To preview changes go to
[Link](https://github.com/a2aproject/a2a-python/blob/da02b3c690552094e04ac3d27f4ff8e557b4561c/README.md).

- [X] Merge PR after approval

Co-authored-by: Ivan Shymko <ishymko@google.com>
# Description

Switches itk scripts to use new version of launcher

Confirmation and testing done in fork:
https://github.com/JakubWorek/a2a-python/actions/runs/31666233084
…1162)

The in-memory server singletons guard their state with an asyncio.Lock,
which binds to the first event loop that awaits it. A server that uses
these singletons from more than one event loop then hits "RuntimeError:
... is bound to a different event loop" (or a cross-loop hang).

Replace the asyncio.Lock with a loop-agnostic threading.RLock in
InMemoryQueueManager, ActiveTaskRegistry, InMemoryTaskStore,
InMemoryPushNotificationConfigStore, and the request handler's
_running_agents guard, moving awaited calls outside the lock so each
critical section is a pure in-memory dict operation.

Behavior is unchanged for single-loop servers; only the private lock
attribute's type changes.

Tests: added tests/server/test_cross_event_loop_locks.py (cross-loop
regression) and updated the test_init lock-type assertion.

Co-authored-by: Ivan Shymko <ishymko@google.com>
🤖 I have created a release *beep* *boop*
---


##
[1.1.3](a2aproject/a2a-python@v1.1.2...v1.1.3)
(2026-08-18)


### Bug Fixes

* use threading.RLock for in-memory server singletons
([a2aproject#1162](a2aproject#1162))
([5bae35b](a2aproject@5bae35b))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
…patch (a2aproject#1137)

Fixes a2aproject#1136 (the runtime issue split out of a2aproject#1101).

### Problem

One undrained sink stalls `EventQueueSource._dispatch_loop` for every
sink. The dispatcher's asyncio.gather awaits a blocking put on each
sink, so a single full queue (default cap 1024) whose consumer went away
blocks the gather forever. The incoming queue then fills, producers
wedge in enqueue_event, and the task's event flow never recovers; see
the task dumps and repro in a2aproject#1101.

### Change: split the sink semantics

The two kinds of sink want different fullness behavior.

The default sink is flow control. Its consumer drives task state, so a
full queue that back-pressures the dispatcher (and, transitively, the
producer) is correct, and it stays exactly as it is. A slow task store
slows the pipeline down rather than losing events.

Tapped subscriber sinks are broadcast observers. Their consumers are
remote and can be abandoned, most simply by a non-blocking message/send
whose HTTP response has already returned. So `tap()` gains
`evict_on_full: bool = False`. When an evict-on-full sink is full at
delivery time, the dispatcher force-closes it (close(immediate=True),
warning logged) and it detaches through the existing remove_sink path;
dispatch to the remaining sinks continues, and any producer parked on
the incoming queue recovers. The consumer of an evicted sink sees
`QueueShutDown` on its next dequeue, same as any closed queue. There is
no new exception type and no API surface beyond the keyword.

The test is fullness at delivery time and nothing else. An abandoned
consumer is the case that motivated this, but a consumer that is alive
and reading and has simply fallen `max_queue_size` events behind hits
the same predicate and is evicted the same way. At the production
default that is 1024 events behind, and at max_queue_size=2 a consumer
sleeping five milliseconds between reads is evicted after draining one
event. The warning text and the `_deliver_to_sink` docstring currently
say the consumer "is not draining it" and call it an abandoned
subscriber, which claims something the check never tested, and a reword
is coming that names the queue bound instead and makes no claim about
the consumer.

A sink that already closed since the dispatch snapshot was taken is
skipped before the fullness check, so a graceful close(immediate=False)
whose consumer is still draining is never upgraded to immediate=True and
keeps its trailing events.

The full() pre-check is race-free: the dispatcher is the only writer to
a sink queue and consumers only read, so a queue observed non-full
cannot become full before the put.

`tap()` defaults to False, so the documented blocking contract is
unchanged for existing callers. ActiveTask's subscriber tap opts in
explicitly, and that is the one production site that wedges, so the fix
reaches existing deployments without changing the default of the public
primitive.

### Tests

Five tests. test_evict_on_full_sink_is_evicted_and_dispatch_continues
puts a full capacity-1 evict sink through dispatch and asserts it is
closed and detached while the default sink still receives every event.
test_producer_unblocked_after_evict_on_full is the a2aproject#1101 runtime shape
in miniature: producers blocked on a full incoming queue recover once
the sink is evicted. test_default_tap_keeps_backpressure pins the
existing flow-control contract, a default tap still blocking dispatch
when full. test_graceful_close_not_upgraded_to_immediate_by_eviction
covers the skip above, a gracefully closing full sink keeping its
trailing events instead of being force-closed.
test_subscriber_taps_are_evict_on_full asserts that ActiveTask.subscribe
taps its subscriber sinks with the keyword set.

The eviction and producer-unblock tests fail without the fix. Full
suite: 1761 passed, 90 skipped, 3 xfailed, 1 xpassed.

### Out of scope

The deeper root cause is subscriber-sink lifecycle: a sink tapped for a
consumer that has gone away should be closed by its owner at connection
teardown, not discovered full later. That needs its own look at the
request-handler layer. This PR keeps the dispatch layer from letting any
such sink take the task down in the meantime.

Edited 2026-08-20 to say what the eviction check actually tests. The
description read as though only an abandoned consumer is evicted; the
predicate is queue fullness, so a live consumer more than
`max_queue_size` events behind is evicted too, and the warning wording
that claims otherwise is being reworded.

---------

Co-authored-by: mykytanetipa <mykytanetipa@google.com>
…2aproject#1159, a2aproject#1170) (a2aproject#1172)

## Summary

Two related fixes to `DefaultRequestHandlerV2`, shipped together because
the first is a prerequisite for the second to be safe.

**Fixes a2aproject#1159 (owner-scoping, the security base).** `on_cancel_task` and
`on_subscribe_to_task` resolved a live task via
`ActiveTaskRegistry.get_or_create(task_id)` by `task_id` alone, skipping
the owner-aware `task_store.get(task_id, context)` that `on_get_task`
and the send path already perform. A caller who knows another tenant's
`task_id` could subscribe to its live stream and cancel it (CWE-639).
Both handlers now consult the owner-aware store first and fail closed
with `TaskNotFoundError` (masking existence, matching `on_get_task`).

**Fixes a2aproject#1170 (cancel writes a terminal state).** `ActiveTask.cancel`
cancelled the producer before `AgentExecutor.cancel`, so the component
that owns the terminal state could not write it; and a task parked
non-terminal (e.g. `input-required`) reported cancel-success without
transitioning. The executor cancel now runs before the producer cancel
(producer still cancelled on the error path), and cancellation that
leaves the task non-terminal is closed out as `CANCELED`.

## Why one PR

a2aproject#1170 alone makes a2aproject#1159 worse: for a parked task, a cross-tenant cancel
via the a2aproject#1159 bypass changes from a harmless no-op into an actual
cross-tenant `CANCELED` write. Landing a2aproject#1159's owner check together (as
the base commit) removes that window. Merging them together, or a2aproject#1159
before a2aproject#1170, is safe; a2aproject#1170 must not land first.

## Tests

- Owner-scope regressions: non-owner cancel and non-owner subscribe are
rejected (`TaskNotFoundError`), including a parked task; the owner still
succeeds.
- Cancel-terminal regressions: mid-run cancel reaches a terminal state;
cancel of a parked task does not silently succeed (the maintainer repros
from a2aproject#1171, converted from xfail to passing).

## Validation

- Owner-scope tests: reverting the a2aproject#1159 handler change fails all three;
restore passes. Full request-handler module: 62 passed.
- Cancel-terminal tests: reverting the a2aproject#1170 change fails both
scenario_19 tests; restore passes. Full integration module: no
regression.
- Combined: with both applied, a cross-tenant cancel of a parked task is
rejected rather than writing `CANCELED`.
- `ruff` clean at the pinned version.

---------

Signed-off-by: AlgoVoi <chopmob@gmail.com>
Co-authored-by: Josh Nichols <joshua.nichols@gmail.com>
…lerV2 (a2aproject#1153)

## Summary

Fixes a2aproject#1135.

`DefaultRequestHandlerV2.__init__` accepts a `queue_manager` argument
(kept for signature compatibility with `DefaultRequestHandler`) but
never stores or uses it. As @rohityan confirmed on the issue, v2
delegates event streaming to an in-memory `ActiveTaskRegistry`, so any
custom or distributed `QueueManager` — e.g. a Redis-backed one used for
multi-replica stream reconnection — is silently dropped. After
upgrading, multi-replica streaming breaks with no error or warning.

## Fix

Emit a `logger.warning` at construction when a non-`None`
`queue_manager` is passed, explaining that it is ignored in v2 and
pointing at the documented workarounds (switch to
`LegacyRequestHandler`, or route `/tasks/{id}:subscribe` to the replica
holding the `ActiveTask`). This turns a silent misconfiguration into a
visible one without changing v2's architecture. Also corrected the
now-inaccurate inline comment on the parameter.

## Evidence

- The parameter is dropped: `default_request_handler_v2.py` `__init__`
binds `agent_executor`/`task_store`/etc. but has no `self._queue_manager
= queue_manager` (contrast `default_request_handler.py:123`, which does
store it and uses it at lines 209/313/503/611).
- v2 uses the registry instead: `self._active_task_registry =
ActiveTaskRegistry(...)`.

## Testing

Added two tests to
`tests/server/request_handlers/test_default_request_handler_v2.py`:
- `test_init_warns_when_queue_manager_passed` — asserts a WARNING
mentioning `queue_manager` is logged (fails on `main`, passes with the
fix).
- `test_init_no_warning_without_queue_manager` — asserts no such warning
in the default case.

```
uv run pytest tests/server/request_handlers/            # 195 passed
```
`ruff check`, `ruff format --check`, and `ty check` all clean on the
changed files.

This change was developed with AI assistance; I verified the root cause
and behavior against the code paths cited above, reviewed every line,
and ran the tests.

---------

Co-authored-by: mykytanetipa <mykytanetipa@google.com>
)

## Summary

`_InMemoryTaskStoreImpl.save` and
`InMemoryPushNotificationConfigStore.set_info` created owner buckets
before acquiring their existing `RLock`. Two threads could both observe
a missing owner, create separate buckets, and overwrite the bucket
containing the first completed write.

This moves owner-bucket creation inside the lock with `setdefault` in
both stores. Threaded regression tests cover concurrent first writes and
confirm that both records remain available through the public retrieval
methods.

## Testing

`uv run pytest tests/server/tasks/test_inmemory_task_store.py
tests/server/tasks/test_inmemory_push_notifications.py -q`

Result: 40 passed.

Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
Co-authored-by: mykytanetipa <mykytanetipa@google.com>
Fixes a2aproject#1174.

The `_canonicalize_agent_card` function builds the bytes that a
signature covers, so whatever it produces is what a verifier in another
SDK has to reproduce exactly. Today it ends in one call:

```
json.dumps(cleaned_dict, separators=(',', ':'), sort_keys=True)
```

That does not produce RFC 8785 on any of the three axes the scheme
exists to fix, and I ran each one against the code as it stands before
changing it rather than reasoning about it. A card named Café Agent
serializes its name as `{"name":"Caf\u00e9 Agent"}`, where JCS section
3.2.2.2 asks for the literal UTF-8 bytes `{"name":"Café Agent"}` and
escapes only the C0 range, the quote and the backslash. sort_keys=True
orders keys by code point, where section 3.2.3 asks for UTF-16 code unit
order, so a card whose extension params carry the keys U+1F600 and
U+FF01 comes out in the opposite order from every conforming
implementation. And repr formats numbers, where section 3.2.2.3 asks for
the ECMAScript Number::toString algorithm, so an extension param of
0.000001 is written 1e-06 and an integral param of 1 is written 1.0.
Every one of those four canonical forms disagrees with an independent
implementation of the RFC; after the change all four agree byte for
byte.

There is a fourth divergence underneath the three in the issue, and it
is the one that would have made a fix on the first three ineffective.
The signer parsed the canonical string back into a dict and handed the
dict to jwt.encode, which serializes its payload with its own json.dumps
and therefore re-applies ensure_ascii=True to bytes that had just been
canonicalized. The canonical form would have been correct and the bytes
actually signed would still have been escaped. The signer now hands the
canonical bytes to the JWS layer directly. The protected header is
byte-identical either way, which the tests assert against a jwt.encode
reference so that this cannot regress into a silent compatibility break
in the header a verifier reads kid and alg out of.

On the question you left open, of vendoring a package versus writing the
algorithm here: I wrote it here, and I put the package in as a test-only
oracle instead, because that is strictly stronger than either option on
its own. The `rfc8785` package on PyPI is Trail of Bits' implementation,
Apache-2.0, a pure-Python py3-none-any wheel with no runtime
dependencies of its own and requires-python >=3.8, so it installs
cleanly across the whole 3.10 to 3.14 matrix; its most recent release is
0.1.4 from 2024-09-27, which for a finished implementation of a frozen
RFC is a reasonable place to sit rather than a warning sign. The reason
not to make it a runtime dependency of `a2a-sdk[signing]` is that the
signing extra is the code that decides whether a card is trustworthy,
and a dependency there is one more package an attacker has to compromise
to change what a signature means. The reason to use it anyway is that if
it were the implementation, nothing would be checking it. As the oracle
it checks this implementation on every accepted vector, alongside a
corpus whose expected values two other implementations already agree on,
so the change lands with three-way agreement instead of one-way trust.
The expensive half is number formatting and I did not write that from
scratch either: it is adapted from the reference implementation at
`cyberphone/json-canonicalization`, Apache-2.0, credited in the module
docstring, and then checked against the oracle over twenty thousand
seeded random doubles and every mantissa-and-exponent pair near the
notation thresholds. If you prefer that the SDK depend on rfc8785 at
runtime, say so and I will make that swap; it is a one-line change to
_canonicalize_agent_card and the entire test suite here stays valid,
because the tests are written against the RFC rather than against this
implementation.

The tests are lifted rather than invented. The 57 vectors in
`tests/utils/jcs_vectors.json` come verbatim from the language-neutral
`a2a-jcs-v01` corpus proposed in a2aproject/a2a-tck#228, which is 47
accept, 10 reject, and five groups covering signature exclusion, key
ordering, string serialization, number serialization, and arrays and
nesting. Each vector carries its expected canonical form as a UTF-8 hex
string, so a transcription mistake cannot hide inside an editor's
encoding, and each expected value was produced by two independent
implementations written by neither SDK author which agree byte for byte.
I verified the lift programmatically rather than by eye: all 57 ids
present, no extras, and zero mismatches on disposition, input and
expected hex against the vector files in that PR. On top of the corpus
the suite adds the cases this ecosystem has already found
implementations disagreeing on: unpaired surrogates in keys and in
values, noncharacters, characters above the BMP, U+2028 and U+2029,
negative zero, denormals, the 1e21 and 1e-7 notation boundaries,
integers past the exact-integer range, and nesting on both sides of the
depth bound. That is 165 tests, and the full suite is 1950 passed with
ruff check, ruff format --check and ty check all clean.

Then I tried to break it, which is where the depth bound comes from.
Nesting is attacker-controlled: `AgentExtension.params` is a
`google.protobuf.Struct` and nests arbitrarily, and an unbounded
recursive serializer turns a hostile card into a stack exhaustion in
whoever verifies it rather than a rejected signature. Serialization
stops at 128 levels, and the `_clean_empty` helper needed the same bound
because it runs first and would otherwise be the crash site while the
serializer's bound sat unreachable behind it. A card past the bound now
fails as `InvalidSignaturesError` on the verify path rather than as a
new exception type at callers. I also checked the mutations: reversing
the key order to code point, restoring ensure_ascii, restoring repr for
numbers, removing the depth cap, emitting -0, and removing the surrogate
rejection each turn the suite red, at 7, 35, 19, 4, 4 and 6 failures
respectively. These tests fail for the reasons they claim to exist.

Two things worth knowing that are not this change. The compatibility
break is real but narrow, and I measured its edges rather than
describing them: a pure-ASCII card with no extension params
canonicalizes identically before and after, and so does one whose
extension params are ASCII strings, so those signatures keep verifying.
A card with any non-ASCII text anywhere, or any numeric extension param,
produces different bytes and its existing signatures will not verify,
and that is the point: those are exactly the cards that do not verify in
other implementations today. Separately, while attacking the signing
path I found that _clean_empty lets a card carry content the signature
does not cover. A card whose extension params include `"policy": ""`
canonicalizes to the same bytes as a card with no policy key at all, so
a signature made over the second verifies the first. That behaviour
predates this change and fixing it would be a second and larger break,
so I have deliberately left it alone here; I have a reproducer and I am
happy to open it as its own issue if you want it tracked.

---------

Signed-off-by: Sankalp Gilda <sankalp.gilda@gmail.com>
Co-authored-by: mykytanetipa <mykytanetipa@google.com>
# Description

regenerate itk uv lock to add dependency added in
a2aproject#1193
… update dependency version markers (a2aproject#1203)

# Description

one lock file to avoid a2aproject#1202 problem in the future
# Description

Thank you for opening a Pull Request!

This pull request changed `EventQueueSource.remove_sink()` to use
`set.discard()`, so removing an already-absent sink completes without
raising
`KeyError`. `EventQueueSink.close()` now delegates directly to that
idempotent
operation instead of suppressing the exception after telemetry has
recorded it.
A regression test closes the same sink twice and verifies that the
mocked
OpenTelemetry span records neither an exception nor an error
description.

Before submitting your PR, there are a few things you can do to make
sure it goes smoothly:

- [x] Follow the [`CONTRIBUTING`
Guide](https://github.com/a2aproject/a2a-python/blob/main/CONTRIBUTING.md).
- [x] Make your Pull Request title in the
<https://www.conventionalcommits.org/> specification.
- Important Prefixes for
[release-please](https://github.com/googleapis/release-please):
- `fix:` which represents bug fixes, and correlates to a
[SemVer](https://semver.org/) patch.
- `feat:` represents a new feature, and correlates to a SemVer minor.
- `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking
change (indicated by the `!`) and will result in a SemVer major.
- [x] Ensure the tests and linter pass (Run `bash scripts/format.sh`
from the repository root to format)
- [x] Appropriate docs were updated (if necessary)

Fixes a2aproject#1133 🦕

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: mykytanetipa <mykytanetipa@google.com>
Copilot-Session: 42ca73c6-08fb-4954-96f4-b7e32e46613a
# Description

Making use of shared scenarios and run_itk script from itk repository
…roject#1173)

## Summary

The `on_create_task_push_notification_config` handlers (v1 and v2)
stored client-supplied URLs without any validation. A caller who can
create a push notification config can point the server at loopback,
private-network, link-local, or cloud-metadata hosts, and the server
will POST task events to that URL on every state change.

## Root cause

`src/a2a/server/request_handlers/default_request_handler.py` and
`default_request_handler_v2.py` call
`push_config_store.set_info(task_id, params, context)` without checking
`params.url`. The dispatch path
(`BasePushNotificationSender._dispatch_notification`) is covered by
a2aproject#1164; this PR closes the **write path**.

## Fix

- Added `push_url_validation_error` to
`base_push_notification_sender.py` (same logic as a2aproject#1164: blocks
non-http(s) schemes, loopback, private, link-local, multicast, reserved,
and unresolvable hosts).
- Called it in both `on_create_task_push_notification_config` handlers
before storing the config.
- Raises `InvalidParamsError` with a descriptive message on rejection.

## Testing

- `uv run pytest tests/server/request_handlers/ -k push_notification` —
55 passed.
- New test:
`test_on_create_task_push_notification_config_rejects_invalid_url`
covers loopback and `file:` scheme rejection.
- Updated existing tests that used unresolvable fixture URLs
(`1.example.com`, `callback.com`) to use `example.com` (resolvable in
CI).

## Relation to a2aproject#1164

a2aproject#1164 validates at dispatch time (read path). This PR validates at
config creation time (write path). Both are needed: write-time
validation fails fast and gives the client immediate feedback;
dispatch-time validation is a defense-in-depth backstop.

Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Iva Sokolaj <102302011+sokoliva@users.noreply.github.com>
# Description

The A2A specification requires each Task in a ListTasks response to omit
the `artifacts` field when `includeArtifacts` is false or unset. The
protobuf response already excludes artifact content, but JSON
serialization with default fields enabled emitted `artifacts: []`.

This change centralizes ListTasks wire serialization for REST and
JSON-RPC. It preserves the existing pagination fields while removing the
`artifacts` key unless the request explicitly enables it. When enabled,
the field remains present with its actual content, including an empty
array for tasks without artifacts.

Regression tests cover both the default behavior and
`includeArtifacts=true` across both protocol bindings.

- [x] Follow the [`CONTRIBUTING`
Guide](https://github.com/a2aproject/a2a-python/blob/main/CONTRIBUTING.md).
- [x] Make the Pull Request title in the
<https://www.conventionalcommits.org/> specification.
- [x] Ensure the tests and linter pass (`bash scripts/format.sh` /
project lint checks).
- [x] Appropriate docs were updated (not required; this aligns
serialization with the existing protocol contract).

Validation:

- Route suites: 40 passed
- Full suite: 1974 passed, 90 skipped, 3 xfailed, 1 xpassed
- Coverage suite: 93% total coverage (88% required)
- `./scripts/lint.sh` (Ruff, format, and ty)

Fixes a2aproject#1211 🦕

Co-authored-by: mykytanetipa <mykytanetipa@google.com>
…ject#1209)

Follow-up to a2aproject#1137, documentation only. No behavior change: the diff
touches two docstrings, one comment and one log format string. Ruff's
linter and formatter are both clean on the two files, and the 73 tests
in the event-queue and active-task modules pass.

a2aproject#1137 added eviction so one sink whose queue has filled cannot wedge the
dispatcher for every other subscriber. The behavior is right and the
words around it are not. The guard is `if sink._evict_on_full and
sink.queue.full():`, which tests queue fullness at delivery time and
says nothing at all about the consumer. The docstring and the warning
both described it as an abandoned subscriber whose consumer had stopped
draining, which is a claim the check never makes. A consumer that is
alive and reading steadily, merely `max_queue_size` events behind the
dispatcher, satisfies the same predicate and is evicted the same way.

That gap costs an operator real time. The warning as written sends
someone looking for a dead consumer, and what they will find is a live
one that is slow, with nothing in the message saying that is expected.
So the warning now says the queue was full at delivery time and that the
sink is therefore at least max_queue_size events behind, and it states
outright that this does not mean the consumer stopped reading.

Alongside it, the delivery docstring gains one paragraph saying what the
test is and what it is not, the evict_on_full parameter doc names the
queue bound rather than abandonment, and the comment at the subscriber
tap site says the same thing. Abandonment stays where it is genuinely
the subject: the teardown docstring still describes a producer wedged
closing an abandoned sink, and the regression test still calls its
never-drained sink abandoned, because in both of those cases that is
what is happening.

---------

Signed-off-by: Sankalp Gilda <sankalp.gilda@gmail.com>
Co-authored-by: mykytanetipa <mykytanetipa@google.com>
…etime

Introduces AgentInputQueue and changes AgentExecutor.execute() to accept
it as a third parameter. All user messages — including the first — are now
delivered via await input_queue.get(), which returns a RequestContext per
turn. The context parameter retains call-level metadata (task_id,
context_id, auth) but carries no message.

ActiveTask._run_producer() calls execute() exactly once per task lifetime.
Subsequent messages for the same task_id are enqueued to the running
execute()'s input queue rather than spawning a second execute() call.
The _request_lock is removed (serialisation was only needed for the
former multi-execute design).

LegacyRequestHandler (DefaultRequestHandler) gets a minimal single-item
AgentInputQueue stub so agents using the new signature still work there
without any multi-turn routing being added to the legacy path.

All AgentExecutor implementations in tests and samples are updated to the
new signature. Agents that previously returned after emitting INPUT_REQUIRED
now loop on input_queue.get() and only return when the task reaches a
terminal state, matching the single-execute contract.

Signed-off-by: Sam Betts <1769706+Tehsmash@users.noreply.github.com>
Implements the SendLiveMessage operation from A2A spec PR #2153:
bidirectional streaming where the client pushes a stream of
StreamRequest messages (Message or TaskArtifactUpdateEvent) while
receiving a stream of StreamResponse events.

- Update buf.gen.yaml to proto commit from PR #2153 and regenerate
  types (StreamRequest, TaskMessageUpdateEvent, TimelineEntry, etc.)
- Add on_live_message_send to RequestHandler ABC with UnsupportedOperationError default
- Implement on_live_message_send in DefaultRequestHandlerV2: bootstrap
  task from first StreamRequest, feed follow-up messages via enqueue_request,
  subscribe without request so stream stays open until task finishes
- Add SendLiveMessage bidi method to GrpcHandler
- Add REST /live endpoint that returns UnsupportedOperationError (REST
  does not support bidi primitives)
- Add send_live_message to ClientTransport base (UnsupportedOperationError default),
  GrpcTransport (full bidi), BaseClient, and Client ABC
- Add integration tests for the gRPC bidi path

Signed-off-by: Sam Betts <1769706+Tehsmash@users.noreply.github.com>
Client-supplied artifact_update payloads in SendLiveMessage were
previously silently dropped. Now they are:

1. Persisted via the task event pipeline (enqueue_artifact_update injects
   the TaskArtifactUpdateEvent into _event_queue_agent, flowing through
   EventConsumer -> TaskManager.process() -> append_artifact_to_task())
   so subscribers see the artifact update event automatically.

2. Delivered to the running agent executor via input_queue.get() as a
   RequestContext with artifact_update set, so the agent can react
   (inspect content, respond, etc.) without needing to re-persist.

Adds artifact_update field to RequestContext and enqueue_artifact_update
to ActiveTask. Integration test covers the full end-to-end path.

Signed-off-by: Sam Betts <1769706+Tehsmash@users.noreply.github.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.