Skip to content

feat(a2a): wire A2A Gateway onto Runtime via TaskStore (issue #5302 Sub-PR D1) - #5313

Merged
qqeasonchen merged 4 commits into
apache:developfrom
qqeasonchen:feat/state-stores-spi-d
Aug 28, 2026
Merged

feat(a2a): wire A2A Gateway onto Runtime via TaskStore (issue #5302 Sub-PR D1)#5313
qqeasonchen merged 4 commits into
apache:developfrom
qqeasonchen:feat/state-stores-spi-d

Conversation

@qqeasonchen

Copy link
Copy Markdown
Contributor

Brings the A2A Gateway back into the Runtime, this time with the unified control-plane stores from #5301 Sub-PR A/C as the durable backend.

What lands

  • A2AGatewayService — task lifecycle controller. Replaces the in-memory TaskRegistry with TaskStore (Sub-PR A/C). A small runtime cache holds the two fields TaskStore does not model: parentTaskId (used to render child-task queries) and taskEpoch (the per-task counter the store requires for stale-write rejection on updateStatus). Both caches are rebuildable from the store on a fresh JVM.
  • A2AGatewayHttpHandler — Netty HTTP handler for the REST + SSE API (/a2a/tasks, /a2a/tasks/{id}, /a2a/tasks/{id}/stream, /a2a/health). SSE registers a StatusSubscriber so intermediate PENDING → RUNNING → COMPLETED transitions stream to the client.
  • A2AGatewayServer — Netty HTTP bootstrap. Production wires EventMeshA2ATransport (Runtime-bridged) for the A2AMessageTransport dependency; tests pass an in-process transport. The weather-agent demo from PR [ISSUE #5259] Add A2A Gateway: REST API, SSE streaming, Task lifecycle, Java SDK and tests #5260 is removed.
  • AgentCardRegistry + InMemoryAgentCardRegistry — minimal discovery surface (isAgentRegistered / registerCard / getCard). A Meta-backed implementation lands in D2.

Status mapping (PR #5260 → Sub-PR A/C)

The legacy wire vocabulary stays available via A2AGatewayService.TaskState and the toLegacyState() helper. JSON response payloads still emit SUBMITTED/WORKING/CANCELLED so existing A2A clients do not break.

Acceptance against issue #5302

  • A2A transport over the Runtime delivery path — A2AGatewayService takes an A2AMessageTransport; production wires EventMeshA2ATransport which already bridges A2A onto UniIngressService (Sub-PR A's runtime ingress). The parallel InMemoryA2AMessageTransport is no longer required by the gateway path.
  • A2A tasks persist through TaskStore and recover across restarts — createTask / updateStatus / getTask all go through the persistent store.
  • ⚠️ A2A publish/subscribe goes through the same reliability + DLQ + auth chain — EventMeshA2ATransport uses UniIngressService, so reliability + DLQ already flow. Auth chain wiring is in [Architecture Review][P0] Decouple eventmesh-runtime responsibilities via internal packages #5297 (P2).
  • AgentCard stored/read via MetaStore — D2 (this PR ships the interface and in-memory impl; Meta-backed impl is D2).
  • Documentation marks A2A as Experimental — README banner lists the three pieces still missing for production (TaskExpirer, Meta-ized AgentCard, Testcontainers E2E), all scheduled for D2.

Tests (7, all passing locally)

  • A2AGatewayServiceTest — 6 tests: create+complete, cancel, cancel-on-unknown, submit-to-unregistered, parent-child index, task-not-found. Uses an in-process TaskStore (mirrors Sub-PR A's test stub) and an in-process A2AMessageTransport that handles A2A's + single-segment wildcards.
  • A2AGatewaySmokeTest — Netty HTTP /a2a/health loopback check.

Not in D1 (Sub-PR D2)

  • TaskExpirer reaper (periodic TaskStore.expireStale sweep)
  • Meta-backed AgentCardRegistry (SessionStore-backed)
  • Testcontainers fault-injection E2E

Fixes #5302 P1 acceptance criteria (partial: A2A-via-Runtime transport + TaskStore persistence + Experimental banner). Closes #5302 for the parts of the acceptance criteria that this PR delivers; the remaining D2 work will be tracked in a follow-up issue.

Co-authored-by: qqeasonchen qqeasonchen@gmail.com

…5302 Sub-PR D1)

Brings the A2A Gateway back into the Runtime, this time with the unified
control-plane stores from apache#5301 Sub-PR A/C as the durable backend instead
of the in-memory TaskRegistry that PR apache#5260 introduced.

What lands
----------
* A2AGatewayService — task lifecycle controller. Replaces the in-memory
  TaskRegistry with TaskStore (Sub-PR A/C). A small runtime cache holds
  the two fields TaskStore does not model: parentTaskId (used to render
  child-task queries) and taskEpoch (the per-task counter the store
  requires for stale-write rejection on updateStatus). Both caches are
  rebuildable from the store on a fresh JVM; the cache keys are
  taskIds, so the cache size tracks active tasks only.
* A2AGatewayHttpHandler — Netty HTTP handler for the REST + SSE API
  (/a2a/tasks, /a2a/tasks/{id}, /a2a/tasks/{id}/stream, /a2a/health).
  SSE registers a StatusSubscriber so intermediate PENDING -> RUNNING ->
  COMPLETED transitions stream to the client.
* A2AGatewayServer — Netty HTTP bootstrap. Production wires
  EventMeshA2ATransport (Runtime-bridged) for the A2AMessageTransport
  dependency; tests pass an in-process transport. The weather-agent
  demo from PR apache#5260 is removed — the demo client (A2AGatewayDemo) and
  the Meta-ized AgentCard registry land in Sub-PR D2.
* AgentCardRegistry + InMemoryAgentCardRegistry — minimal discovery
  surface (isAgentRegistered / registerCard / getCard). A Meta-backed
  implementation backed by SessionStore (Sub-PR A) lands in D2.

Status mapping (PR apache#5260 -> Sub-PR A/C)
---------------------------------------
  SUBMITTED -> PENDING, WORKING -> RUNNING, CANCELLED -> CANCELED (one L)
The legacy wire vocabulary stays available via A2AGatewayService.TaskState
and the toLegacyState() helper. JSON response payloads still emit
SUBMITTED/WORKING/CANCELLED so existing A2A clients do not break.

Documentation
-------------
* docs/a2a-protocol/README.md carries an EXPERIMENTAL banner that lists
  the three pieces still missing for production (TaskExpirer, Meta-ized
  AgentCard, Testcontainers E2E) — all scheduled for Sub-PR D2.

Acceptance against issue apache#5302
------------------------------
* "A2A transport over the Runtime delivery path" — A2AGatewayService
  takes an A2AMessageTransport; production wires EventMeshA2ATransport
  which already bridges A2A onto UniIngressService (issue apache#5301 Sub-PR
  A's runtime ingress). The parallel InMemoryA2AMessageTransport is no
  longer required by the gateway path.
* "A2A tasks persist through TaskStore and recover across restarts" —
  createTask / updateStatus / getTask all go through the persistent
  store. On a fresh JVM, a gateway that had pending tasks sees them
  with their last persisted status; in-flight work that was awaiting
  a response will be picked up on the next status callback.

Tests (7, all passing)
----------------------
* A2AGatewayServiceTest — 6 tests: create+complete, cancel, cancel-on-
  unknown, submit-to-unregistered, parent-child index, task-not-found.
  Uses an in-process TaskStore (mirrors Sub-PR A's test stub) and an
  in-process A2AMessageTransport that handles A2A's + single-segment
  wildcards.
* A2AGatewaySmokeTest — Netty HTTP /a2a/health loopback check.

Not in D1 (Sub-PR D2)
---------------------
* TaskExpirer reaper (periodic TaskStore.expireStale sweep).
* Meta-backed AgentCardRegistry (SessionStore).
* Testcontainers fault-injection E2E.
…5313)

A2AGatewayHttpHandler.java:
- Remove unused imports (A2AProtocolConstants, A2ATopicFactory, TaskState)
- Expand try-catch blocks for limit/offset parsing (EmptyCatchBlock, NeedBraces)
- Expand handleList loop control flow (NeedBraces)
- Collapse duplicate blank line between package and imports (EmptyLineSeparator)

A2AGatewayServiceTest.java:
- Expand single-line if-blocks in InProcessTaskStore.listByAgent
- Expand if-block in InProcessTransport.matches
- Expand if-blocks in tearDown
- Move taskId declaration closer to its use (VariableDeclarationUsageDistance)

A2AGatewaySmokeTest.java:
- Rewrite StubTaskStore / NoopTransport with multi-line method bodies
  (LeftCurly, RightCurlyAlone, NeedBraces, EmptyLineSeparator)
- Add missing static imports (assertEquals, assertNotNull)

Build PR apache#5313 fix.
…owup)

A2AGatewayServiceTest.java:
- Fix ImportOrder: AgentCapabilities before AgentCard (alphabetical)
- Fix VariableDeclarationUsageDistance for f: inline taskId into
  submitTask call so the declaration is closer to first use

A2AGatewaySmokeTest.java:
- Fix ImportOrder: static Assertions imports before non-static
  TaskStore import

Build PR apache#5313 followup.
…ollowup)

The previous fixup broke the test logic by calling TaskResult.getTaskId()
which does not exist. Restore the original taskId declaration pattern
while keeping the f declaration within 3 lines of its first use:

- Declare taskId first
- Declare f immediately after
- Inline cancelTask result into the assertTrue (no intermediate boolean)
- Move f.get() call right after the cancel assertion so f's first use
  is within 3 lines of its declaration

Build PR apache#5313 followup.
@qqeasonchen

Copy link
Copy Markdown
Contributor Author

CI build fix pushed

Build job is failing on checkstyleMain and checkstyleTest for #5313's D1 changes. Pushed 3 fixup commits addressing the violations:

  • 56d189706 - main + test checkstyle (UnusedImports, NeedBraces, EmptyCatchBlock, EmptyLineSeparator, OneStatementPerLine, LeftCurly, RightCurlyAlone)
  • ed0cd0ee7 - test ImportOrder (AgentCapabilities before AgentCard, static before non-static) and VariableDeclarationUsageDistance
  • 116d5851c - restore the cancelMarksTaskCanceled test body that the previous fixup broke (called a non-existent TaskResult.getTaskId())

Verified locally:

  • gradlew :eventmesh-runtime:checkstyleMain :eventmesh-runtime:checkstyleTest -> BUILD SUCCESSFUL
  • gradlew :eventmesh-runtime:test --tests "org.apache.eventmesh.runtime.a2a.*" -> 7/7 tests pass (6 service + 1 smoke)

No D1 code changes - all fixes are to satisfy Apache Checkstyle rules for the new files in D1.

@qqeasonchen
qqeasonchen merged commit 6ec9921 into apache:develop Aug 28, 2026
7 checks passed
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.

[Architecture Review][P1] A2A must not form a parallel Runtime

1 participant