A2A Async
Non-blocking A2A communication for Agent Zero. Dispatch messages to remote FastA2A-compatible agents and receive a task_id instantly instead of waiting for the answer, then collect results later with a2a_poll, cancel with a2a_cancel, and track pending tasks through [EXTRAS] reminders between turns. Solves a2a_chat timeouts on long-running remote agents. No framework modifications; httpx and fasta2a ship with the runtime.
README
a2a_async
Asynchronous send-and-poll A2A communication between Agent Zero agents. The plugin tools send a message to a remote FastA2A-compatible agent and return immediately with the created task id, so the agent turn is never blocked — unlike the core a2a_chat tool, which waits for the remote answer (up to its wait_for_completion limit) and typically times out on long-running remote agents.
How it works
a2a_send— sendsmessage/sendthroughhelpers.fasta2a_client.AgentConnection.send_messageand returnstask_id,context_idand the initial state (submitted). The FastA2A server creates the task immediately and does not wait for the result. The task is registered in the pending registry.a2a_poll— pollstasks/getfor the giventask_id(or the latest pending task for anagent_url).submitted/working→ a "still running" notice;completed→ the full response text and artifacts (entry removed);failed/canceled/rejected→ a notice and entry removal; unknown task (remote restart, error-32001) → a readable notice and entry removal.a2a_cancel— sends the JSON-RPCtasks/cancelmethod. The framework client (fasta2a.client.A2AClient) does not expose it, so the plugin sends its own POST request throughhttpx(plugin-local helperhelpers/cancel.py), following the library'sclient.pypayload pattern. No framework files are modified.[EXTRAS]reminders — between turns the agent sees the list of pending tasks (task_id, agent_url, state, age) with a hint to usea2a_poll. Mechanism: implicit@extensiblehookAgent.prepare_prompt(decorator inagent.py), layoutextensions/python/_functions/agent/Agent/prepare_prompt/start/. Thestarthook runs before the[EXTRAS]block is rendered, so the entry lands in the current turn's prompt — the same information source as the coremessage_loop_prompts_after→_72_include_parallel_jobs.py.
Task registry
Entries live in the agent data slot: agent.get_data/agent.set_data("_a2a_async_tasks") — the same pattern as _a2a_sessions in the core a2a_chat tool. Each entry (dataclass AsyncTaskEntry) tracks: task_id, agent_url, context_id, state, created_at, last_polled_at, poll_count.
Design decision: the entry is removed on every terminal state (completed, failed, canceled, rejected) and when the remote agent reports -32001 (task unknown). The registry only keeps tasks that still need polling; the response returns into the conversation history, so it does not need to be stored twice. Connection errors do NOT remove the entry — the remote agent may be temporarily unavailable.
Installation
No external dependencies — httpx and fasta2a already ship with the Agent Zero framework runtime.
Install from Git (Settings → Plugins → Install from Git):
https://github.com/momo2002/a0-a2a_async.git
Alternatively, clone or copy the repository into usr/plugins/a2a_async/ and enable it in the Plugins UI (Settings → Plugins) or via the management API. install() in hooks.py only validates imports (idempotent).
Usage (model workflow)
a2a_sendwithagent_urlandmessage→ you get atask_id.- Do other work; pending tasks are visible in
[EXTRAS]between turns. a2a_pollwithtask_id(oragent_url) → the result once it is ready.- Optionally
a2a_cancelwhen the task is no longer needed.
Limitations
- No persistence across remote restarts: FastA2A tasks live in the server storage (default
InMemoryStorage); after a restarttasks/getreturns-32001— the plugin reacts with a readable notice and removes the entry. Resend witha2a_send. - Sequential server queue: FastA2A executes tasks from the broker queue; a long queue extends the time to
completedbut never blocks the local agent turn. - SSE/streaming unavailable:
fasta2a0.5.0 does not implementmessage/stream(NotImplementedErrorintask_manager.py), so polling is the only path. blocking: trueignored by the server: the core client sendsblocking=True, but thefasta2aserver only readshistory_length— send-and-poll has the same semantics asa2a_chat.- Cancel is best-effort:
tasks/cancelis accepted by the server, but the final state depends on the broker; verify witha2a_pollafter cancelling. - HTTP timeout: the framework client hardcodes a 30 s per-request timeout; on slower networks a connection error is returned as a readable message.
Repository layout
a2a_async/
├── plugin.yaml
├── hooks.py
├── helpers/
│ ├── registry.py # pending-task registry (dataclass, pure logic)
│ ├── response_extract.py # text/artifact extraction from JSON-RPC
│ └── cancel.py # plugin-local JSON-RPC tasks/cancel (httpx)
├── tools/
│ ├── a2a_send.py
│ ├── a2a_poll.py
│ └── a2a_cancel.py
├── extensions/python/_functions/agent/Agent/prepare_prompt/start/
│ └── _76_a2a_async_pending.py # [EXTRAS] reminders
├── prompts/
│ ├── agent.system.tool.a2a_send.md
│ ├── agent.system.tool.a2a_poll.md
│ └── agent.system.tool.a2a_cancel.md
└── tests/
├── test_plugin.py # assert-based unit tests (no pytest needed)
└── e2e_local.py # end-to-end test against a real FastA2A server
Tests
Run with the framework runtime (Docker layout):
cd /a0 && /opt/venv-a0/bin/python usr/plugins/a2a_async/tests/test_plugin.py
cd /a0 && /opt/venv-a0/bin/python usr/plugins/a2a_async/tests/e2e_local.py
License
MIT — see LICENSE.