Skip to content

fix(server): release the ActiveTask after a direct Message response - #1297

Merged
Iwaniukooo11 merged 2 commits into
a2aproject:mainfrom
christophstach:fix/release-active-task-after-message
Oct 14, 2026
Merged

Iwaniukooo11 merged 2 commits into
a2aproject:mainfrom
christophstach:fix/release-active-task-after-message

Conversation

@christophstach

@christophstach christophstach commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Description

A direct Message is a complete response for interactions that do not need task tracking (spec §3.1.1: the agent "MAY return a direct Message response for simple interactions"; §3.1.2: a message-only stream provides "No task tracking or updates"). No follow-up can continue such a request.

DefaultRequestHandlerV2 still kept the request's ActiveTask alive after a Message response:

  • the producer stays parked on self._request_queue.get();
  • the consumer and both EventQueueSource._dispatch_loop tasks wait with it;
  • the entry stays in the ActiveTaskRegistry.

Every message-only request therefore leaves four pending asyncio tasks behind until the process exits. In a long-running server, the pending task count grows linearly with traffic.

Fix

After the consumer forwards the Message to subscribers, it finishes the ActiveTask the same way it already does for a terminal task state: it sets _is_finished and shuts down the request queue (_handle_terminal_state). The producer then leaves its loop and closes the event queues, the consumer drains and exits, and _maybe_cleanup removes the registry entry.

The Message is enqueued to subscribers before the shutdown, so on_message_send and on_message_send_stream still receive it.

Out of scope: input-required tasks stay parked for a follow-up, as covered by test_cancel_of_input_required_task_cannot_be_undone. This PR does not change that.

Tests

Two new tests in tests/server/request_handlers/test_default_request_handler_v2.py drive the real handler with an executor that answers with a single Message:

  • test_on_message_send_message_response_releases_active_task: 3 × on_message_send, then no pending asyncio tasks and an empty registry;
  • test_on_message_send_stream_message_response_releases_active_task: a message-only stream yields exactly one Message, then the same assertions.

Both fail on main (leftover ActiveTask._run_producer, ActiveTask._run_consumer and EventQueueSource._dispatch_loop) and pass with the fix.

Standalone repro, 50 requests each, counting pending asyncio tasks afterwards:

                main (ddce6b8)   this PR
completed             0              0
message             200              0
input-required      200            200   (unchanged, by design)

Checks

  • ./scripts/lint.sh: passed. The 3 ty warnings are pre-existing, in client_factory.py and client/transports/__init__.py.
  • uv run pytest: 2173 passed, 181 skipped, 3 xfailed, 1 xpassed.
  • uv run pytest --cov=src: total 93 %, active_task.py 95 %; the new lines are covered.

Part of #1296 (the direct-Message case; the input-required case stays open)

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

🧪 Code Coverage (vs main)

⬇️ Download Full Report

Base PR Delta
src/a2a/server/agent_execution/active_task.py 95.05% 95.14% 🟢 +0.08%
Total 93.11% 93.12% 🟢 +0.01%

Generated by coverage-comment.yml

Comment thread src/a2a/server/agent_execution/active_task.py Outdated
@Iwaniukooo11 Iwaniukooo11 added status:awaiting response component: server Issues related to frameworks for agent execution, HTTP/event handling, database persistence logic. labels Oct 12, 2026
A direct Message is a complete response without task tracking (spec
3.1.1, 3.1.2), so no follow-up can continue it. DefaultRequestHandlerV2
still kept the request's ActiveTask alive: the producer stayed parked
on the request queue and the consumer and both dispatchers waited with
it, so every message-only request left four pending asyncio tasks and a
registry entry behind until the process exited.

After forwarding the Message to subscribers, the consumer now finishes
the ActiveTask the same way it does for a terminal task state, which
releases the producer, consumer, queues and registry entry.
@christophstach
christophstach force-pushed the fix/release-active-task-after-message branch from 24e19a2 to 8a260c4 Compare October 14, 2026 11:45

@Iwaniukooo11 Iwaniukooo11 left a comment

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.

LGTM

@Iwaniukooo11
Iwaniukooo11 merged commit 49ebce6 into a2aproject:main Oct 14, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component: server Issues related to frameworks for agent execution, HTTP/event handling, database persistence logic. status:awaiting response

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants