Skip to content

fix(compat): apply v1.0 history_length semantics to v0.3 results - #1340

Open
Winoren wants to merge 1 commit into
a2aproject:mainfrom
Winoren:fix/compat-history-length-zero
Open

Winoren wants to merge 1 commit into
a2aproject:mainfrom
Winoren:fix/compat-history-length-zero

Conversation

@Winoren

@Winoren Winoren commented Oct 15, 2026

Copy link
Copy Markdown

A v0.3 agent cannot be asked for an empty history, so the compat transports have to apply the v1.0 history_length semantics to the task they return.

Background

The two protocol versions disagree on history_length = 0:

  • v1.0 - 0 truncates the history to zero items; unlimited requires the field to be left unset (apply_history_length in src/a2a/utils/task.py).
  • v0.3 - 0 means unlimited. The repo already encodes this rule and tests it in tests/compat/v0_3/test_proto_utils.py::test_message_send_configuration_history_length ("A v0.3 history_length of 0 means unlimited, as in task_query_params"), and proto_utils.FromProto.task_query_params / FromProto.message_send_configuration already normalise 0 to None.

The gap

The compat client transports forward the caller's history_length to the v0.3 agent verbatim and then return whatever comes back:

  • rest_transport.get_task sends params['historyLength'] = request.history_length
  • jsonrpc_transport.get_task / send_message send the value produced by to_compat_get_task_request / to_compat_send_message_configuration
  • grpc_transport.get_task sends history_length=request.history_length

So a v1.0 caller that asks for history_length = 0 (i.e. no history) gets the entire history back from a v0.3 agent - the exact opposite of the request. history_length = N > 0 happens to work only because the v0.3 agent also does the truncation.

This is the outbound direction (v1.0 caller -> v0.3 agent). The inbound direction (v0.3 request -> v1.0 handler) is a separate fix in to_core_get_task_request / to_core_send_message_configuration, already covered by #1312, so this PR deliberately does not touch it.

Fix

Apply the existing apply_history_length helper to the converted task in get_task and send_message of the three compat transports. This is done on the result rather than on the request because v0.3 has no representation for "no history" - the transports ask for everything and then honour the v1.0 contract locally.

caller's history_length before after
unset full history full history (unchanged)
0 full history empty history
N > 0 last N last N (unchanged)

Tests

tests/compat/v0_3/test_history_length_compat.py covers get_task for all three transports with history_length of 0, 1 and unset, plus send_message for REST and JSON-RPC. Reverting the source change fails 8 of the 11 new tests; the 3 "unset keeps history" cases pass both ways and act as the control.

Full suite: uv run pytest -> 2286 passed, 183 skipped, 3 xfailed, 1 xpassed. The 4 errors under tests/integration/cross_version/client_server/ are environmental: they pass in isolation both with and without this change. ruff check and ruff format --check are clean.

AI disclosure

This change was produced with AI assistance and was checked against the specification and the existing tests cited above.

@Winoren
Winoren requested a review from a team as a code owner October 15, 2026 01:54
@github-actions

Copy link
Copy Markdown

🧪 Code Coverage (vs main)

⬇️ Download Full Report

Base PR Delta
src/a2a/compat/v0_3/grpc_transport.py 63.64% 65.73% 🟢 +2.10%
src/a2a/compat/v0_3/jsonrpc_transport.py 75.23% 75.68% 🟢 +0.45%
src/a2a/compat/v0_3/proto_utils.py 90.57% 90.94% 🟢 +0.37%
src/a2a/server/cluster/database_event_stream.py 96.94% 92.86% 🔴 -4.08%
Total 93.17% 93.19% 🟢 +0.02%

Generated by coverage-comment.yml

@Iwaniukooo11 Iwaniukooo11 self-assigned this Oct 15, 2026
@@ -0,0 +1,167 @@
"""A v0.3 agent cannot be asked for an empty history, so the compat transports

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please split the tests into existing test_{grpc,jsonrpc,rest}_transport.py instead of creating test_history_length_compat.py

return {'id': '0', 'jsonrpc': '2.0', 'result': result}


def _rest_transport() -> CompatRestTransport:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When working on test_{grpc,jsonrpc,rest}_transport.py, please re-use existing fixtures, to avoid duplication of setup

TRANSPORTS = [_rest_transport, _jsonrpc_transport, _grpc_transport]


class TestCompatGetTaskHistoryLength:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's worth adding a test for edge case N >= len(history)

This branch has not been deployed

No deployments
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