Skip to content

feat(a2a): initial implementation#160

Open
dominic-mcallister-logdna wants to merge 5 commits into
mainfrom
dominic/LOG-23822-v2
Open

feat(a2a): initial implementation#160
dominic-mcallister-logdna wants to merge 5 commits into
mainfrom
dominic/LOG-23822-v2

Conversation

@dominic-mcallister-logdna
Copy link
Copy Markdown
Contributor

summary: codes A2A processing against the official a2a project rust implementation. An official crate has yet to be released, so directly imports the Github projects pinned to the latest version as of 13 May 2026. Currently handles task generation
calling internal aura SSE processing. Uses an in-memory store to keep dependencies down.

ref: LOG-23822

@dominic-mcallister-logdna dominic-mcallister-logdna requested a review from a team May 18, 2026 20:02
@promptless
Copy link
Copy Markdown

promptless Bot commented May 18, 2026

Promptless prepared a documentation update related to this change.

Triggered by mezmo/aura#160

This PR adds A2A protocol support but the README.md doesn't mention this capability. I've drafted a docs update that adds A2A to the key capabilities list, includes API examples for agent card discovery and messaging, and links to the detailed implementation docs.

Review: Document A2A protocol support

Copy link
Copy Markdown
Collaborator

@justintime4tea justintime4tea left a comment

Choose a reason for hiding this comment

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

I'm still working through this a bit but wanted to relay some initial feedback.

Comment thread crates/aura-web-server/Cargo.toml Outdated
Comment thread crates/aura-web-server/src/a2a/agent_executor.rs Outdated
Comment thread crates/aura-web-server/src/a2a/agent_executor.rs
Comment thread crates/aura-web-server/src/a2a/agent_executor.rs
Comment thread crates/aura-web-server/src/a2a/agent_executor.rs
Comment thread crates/aura-web-server/src/a2a/agent_executor.rs Outdated
Comment thread crates/aura-web-server/src/a2a/agent_executor.rs
Comment thread crates/aura-web-server/src/a2a/agent_executor.rs Outdated
@justintime4tea

This comment was marked as resolved.

Comment thread crates/aura-web-server/src/a2a/agent_executor.rs Outdated
Comment thread crates/aura-web-server/src/a2a/agent_executor.rs
Comment thread crates/aura-web-server/src/a2a/agent_executor.rs
Comment thread crates/aura-web-server/src/a2a/agent_executor.rs
Comment thread crates/aura-web-server/src/main.rs Outdated
Comment thread crates/aura-web-server/src/a2a/agent_executor.rs
dominic-mcallister-logdna and others added 3 commits May 26, 2026 16:22
summary: codes A2A processing against the official
a2a project rust implementation. An official crate
has yet to be released, so directly imports the
github projects pinned to the latest version as of
13 May 2026. Currently handles task generation
calling internal aura SSE processing. Uses an
in-memory store to keep dependencies down.

ref: LOG-23822
The cancel hook cancels a token that nothing observes:
`Agent::stream(...)` accepts the token as `_cancel_token` and ignores
it, and the real MCP cancellation path is keyed off `request_id` and
triggered by an explicit `agent.cancel_and_close_mcp(request_id,
reason)` — which the executor never called from either the cancel
hook or the server-shutdown post-loop. Result: CancelTask returned
`Canceled` but the agent kept streaming, in-flight MCP tool calls on
remote servers never received `notifications/cancelled` (whether the
trigger was a client cancel or a graceful shutdown), and the trailing
`if success { yield Completed }` block could clobber the Canceled
state back to Completed. The executor also hand-rolled
increment/decrement of ActiveRequestTracker in two places, which
could double-decrement (or decrement before increment on a fast
cancel) and underflow the counter, breaking shutdown drain.

Cancellation propagation:
- Register per-task `request_id` with `aura::RequestCancellation` for
  parity with the OpenAI handler, and unregister on every exit path.
- Stash an `Arc<dyn StreamingAgent>` in the per-task cancellation
  entry so `cancel()` can call `agent.cancel_and_close_mcp(...)` to
  send `notifications/cancelled` to in-flight MCP tool calls.
- Wrap `stream.next()` in `tokio::select!` against
  `cancel_token.cancelled()` so the executor stops emitting artifacts
  the moment cancel fires.
- Gate the final `Completed` yield on `!cancel_token.is_cancelled()`
  so a cancel that lands while the stream is producing its final
  chunk doesn't get overwritten.
- Drive MCP cleanup on server shutdown. `cancel_token` is a child of
  `stream_shutdown_token`, so shutdown wakes the select! and breaks
  the loop — but nothing was notifying MCP. The post-loop now
  distinguishes the two cancel sources by checking whether the
  cancel-map entry is still present (cancel() removes its entry
  before firing the token, so a still-present entry means the
  parent shutdown token fired), calls
  `agent.cancel_and_close_mcp(request_id, "server shutdown")` in
  that branch, and yields a terminal `Canceled` status so the
  client sees a clean end instead of a silent stream close.
  Mirrors the OpenAI handler's `StreamTermination::Shutdown` arm.

Active-request tracking (closes underflow race):
- Extract `ActiveRequestGuard` from `handlers.rs` (private) to
  `types.rs` (`pub`) so both handlers can share it. No behavior
  change on the OpenAI side — the local duplicate is removed in
  favor of the shared type.
- Replace the manual `active_request_tracker.increment()` /
  `.decrement()` pairs in the A2A executor with the RAII guard.
  Drop on generator exit (loop break, early return, panic, consumer
  drop) produces exactly one decrement, regardless of which side
  initiates termination.
- `cancel()` no longer touches the tracker — it only fires the token
  and cleans up the cancel-map entry. The in-flight execute() loop
  catches the cancel via select!, breaks, drops the guard,
  decrements once.

Test:
- Extend `test_cancel_task` to poll `GetTask` until the task quiesces
  (same state + artifact count across two reads). State is asserted
  `Canceled` on every poll, so a Completed-clobber is caught the
  instant it happens; an executor that keeps emitting past cancel
  never stabilizes and trips the 10s deadline.

Ref: LOG-23822
summary: bumping the a2a-rs package so that
included headers are indeed forwarded to
aura as expected.

ref: LOG-23822
@Shearerbeard
Copy link
Copy Markdown
Collaborator

I've spent some time with the code but found the tooling for manually testing A2A to be a bit lacking. https://github.com/a2aproject/a2a-rs has a basic CLI bundled for trying these things that I'll vet in a few for better verification. I've also looked at putting together with a claude a light internal tool on top to get a bit better visibility as the CLI is a bit thin in functionality.

@justintime4tea
Copy link
Copy Markdown
Collaborator

justintime4tea commented May 27, 2026

While testing with a2acli from a2aproject, used to debug/test a2a impls, I was unable to do anything other than request the agent card. This is because the agent card, which drives a2a clients, needs to have absolute URL's presented in the card.

Here's a PR that should resolve the issue and make the A2A impl work with a2acli and other A2A clients:
#173

Something else I spotted, when you do connect using a2acli, run send to interact with AURA and then run list-tasks to see the tasks, the token counts in metadata are showing 0's:

image

@justintime4tea
Copy link
Copy Markdown
Collaborator

Can we cargo-feature-gate A2A so that AURA Web Server can be built without A2A? There may be deployments where people don't want to serve A2A at all and I feel it should be an "opt-in". We can include it in the docker image for AURA but people should be able to build/deploy AURA without A2A.

A2A clients read the agent card's interface URLs and pass them
straight to their HTTP layer, which rejects relative paths. The
card advertised "/a2a/v1" and "/a2a/v1/rpc", so message/send
failed with a reqwest "builder error" while the card fetch
worked.

Build the interface URLs from a canonical base. Add the
--server-url flag (AURA_SERVER_URL), falling back to host/port
with 0.0.0.0 mapped to 127.0.0.1.

Re-enable the integration-a2a cfg gate on a2a_test (it was
commented out, leaking the card test into suites with a
different agent name) and update its expected URLs to absolute.

Wire AURA_SERVER_URL per topology in compose so the server
publishes the address the test runner reaches it on:
aura-web-server:8080 in CI, localhost:8080 for local dev.

Ref: LOG-23822
@dominic-mcallister-logdna
Copy link
Copy Markdown
Contributor Author

dominic-mcallister-logdna commented May 28, 2026

unts in metadata are showing 0's:

Non-issue. Tokens are only included if provided from aura itself in the final events and when tokens are used. That prompt has no bones.

summary: there are potentially some hosts of
aura which do not prefer to expose A2A endpoints.
Add new --enable-a2a flag so its an opt-in
feature

ref: LOG-23822
Copy link
Copy Markdown
Collaborator

@justintime4tea justintime4tea left a comment

Choose a reason for hiding this comment

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

Looking good @dominic-mcallister-logdna!

@Shearerbeard did you get a chance to run this through a2acli and your A2A inspection tool you mentioned?

@Shearerbeard
Copy link
Copy Markdown
Collaborator

Looking good @dominic-mcallister-logdna!

@Shearerbeard did you get a chance to run this through a2acli and your A2A inspection tool you mentioned?

Not yet, I'll see if I can between flights in a few.

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.

4 participants