Skip to content

[ISSUE #5405] A2A gateway production wiring: main-process boot, RocksDB task store, auth, metrics, contextId - #5406

Merged
qqeasonchen merged 2 commits into
apache:developfrom
qqeasonchen:a2a/production-wiring
Sep 21, 2026
Merged

qqeasonchen merged 2 commits into
apache:developfrom
qqeasonchen:a2a/production-wiring

Conversation

@qqeasonchen

@qqeasonchen qqeasonchen commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5405

What

Two planes in one PR — the A2A gateway (items 1-6 of the issue) and the v2 agent process (items 7-11).

A2A gateway (production wiring)

  1. EventMeshApplication.withA2aGateway(...) + -Deventmesh.a2a.enabled=true (port 10108, EVENTMESH_A2A_* envs) — wired onto the REAL transport (EventMeshA2ATransport → UniIngressService), with TaskExpirer and graceful shutdown. Previously this transport class existed unused.
  2. RocksDBTaskStore — local durable task store under <data>/a2a-tasks, wire-format compatible with the MetaBacked v1 envelope; -Deventmesh.a2a.taskstore=meta selects the cluster-shared store.
  3. Bearer-token auth (-Deventmesh.a2a.token, constant-time; empty = open dev mode, logged).
  4. A2A metrics (submitted/completed/failed/canceled/expired + active gauge) in /admin/metrics and the /metrics Prometheus scrape.
  5. Traffic-port guidance for [Bug] The a2a protocol layer is not working #5225-style misposts: a JSON-RPC body on /events/publish gets a 400 pointing at the A2A gateway.
  6. contextId conversation linkage: submitted, persisted (10th wire field, older readers ignore), echoed in snapshots.

Agent process (hardening)

  1. Default agent.runtime.url 8080 → 10105 (port-migration leftover — the agent could not reach a default runtime).
  2. New bin/start-agent.sh launcher — the dist-agent gradle task referenced an eventmesh-agent/bin/ that never existed; the distribution had no launcher while agent.properties documented one.
  3. ConversationStore bounded conversation count with access-order LRU eviction (agent.conversation.maxConversations, default 1000) — the per-conversation window existed but session count grew without bound. get() of an unknown id no longer resurrects the key (admit vs refresh semantics).
  4. Fail-fast on an empty llm.api.key (opt out via llm.api.key.optional=true for mock gateways) — a READY agent without a key failed every routed request.
  5. Heartbeat consecutive-failure limit (agent.heartbeat.failLimit, default 6) exits the process for supervisor restart instead of serving as a zombie after the runtime TTL eviction.

Bug fix found by the new wiring test: A2AGatewayHttpHandler lacked @Sharable — the gateway could only ever serve ONE HTTP connection (the second channel hit ChannelPipelineException; existing tests all used a single request). A2AGatewayServer.getPort() resolves the auto-selected port (0).

Also normalizes nonstandard license headers on 8 deploy/kubernetes/*.yaml (#5398 leftovers) so the license check passes.

New docs: docs/feature/agent.md (boot sequence, config table, reliability behavior).

Testing (local, full gradle gates)

  • RocksDBTaskStoreTest 7/7; A2AGatewayWiringTest 3/3
  • Agent: ConversationStoreTest 7/7 (incl. LRU eviction + unbounded-compat), OpenAiLlmClientTest 3/3
  • Existing a2a suite green (SmokeTest, FailureModeTest, ServiceTest, ExpirerTest); checkstyleMain/Test clean on both modules

… RocksDB task store, auth, metrics, contextId

The A2A gateway was component-complete but never wired for production:
EventMeshA2ATransport (the real transport bridged onto UniIngressService)
existed unused, EventMeshApplication never booted the gateway, and the only
TaskStore backend required Nacos.

- EventMeshApplication.withA2aGateway(...): boots the Netty REST plane
  (/a2a/*) with the main process, wired onto the REAL transport, plus a
  TaskExpirer reaper. Opt-in via -Deventmesh.a2a.enabled=true, port
  -Deventmesh.a2a.port=10108, graceful shutdown included.
- RocksDBTaskStore: local durable TaskStore under <data>/a2a-tasks (same
  dir layout as offsets/delivery-state), wire-format compatible with the
  MetaBacked v1 envelope. Default when no Meta is configured;
  -Deventmesh.a2a.taskstore=meta selects the cluster-shared store.
- Bearer-token auth on the gateway REST plane
  (-Deventmesh.a2a.token, constant-time compare, same pattern as the admin
  token guard apache#5364); without a token the gateway is open (dev mode,
  logged at boot).
- A2A metrics: submitted/completed/failed/canceled/expired counters plus
  an active-task gauge, surfaced via /admin/metrics JSON and the
  /metrics Prometheus scrape.
- Traffic-port guidance (apache#5225 class confusion): a JSON-RPC body posted
  to /events/publish now returns a 400 pointing at the A2A gateway
  endpoints instead of an opaque CloudEvent transform error.
- contextId passthrough (issue apache#5405): optional conversation linkage on
  task submit, persisted in TaskRecord (10th wire field, v1 readers
  ignore the suffix), echoed in snapshots.

Tests: RocksDBTaskStoreTest (round-trip, epoch CAS, duplicate rejection,
contextId persistence, expiry, reopen durability) and
A2AGatewayWiringTest (main-process boot + health, token 401/200,
contextId through the REST round-trip into the durable store).
@qqeasonchen
qqeasonchen force-pushed the a2a/production-wiring branch 3 times, most recently from 7b492db to d4fda7b Compare September 21, 2026 02:30
…dening

Gateway plane (items 1-6): main-process boot via withA2aGateway +
-Deventmesh.a2a.enabled=true (port 10108) on the REAL transport
(EventMeshA2ATransport -> UniIngressService); RocksDBTaskStore local default
(<data>/a2a-tasks, MetaBacked-compatible v1 wire format); bearer-token auth
(-Deventmesh.a2a.token, constant-time); A2A metrics in /admin/metrics and the
/metrics Prometheus scrape; traffic-port guidance for apache#5225-style JSON-RPC
misposts; contextId conversation linkage persisted and echoed.

Agent plane (items 7-11): default agent.runtime.url 8080 -> 10105; new
bin/start-agent.sh launcher (the dist-agent task referenced a bin/ that never
existed); ConversationStore bounded conversation count with access-order LRU
eviction (agent.conversation.maxConversations); fail-fast on an empty
llm.api.key (llm.api.key.optional=true opts out for mock gateways);
heartbeat consecutive-failure limit (agent.heartbeat.failLimit, default 6)
exits the process for supervisor restart instead of serving as a zombie;
new docs/feature/agent.md.

Bug fix found by the new wiring test: A2AGatewayHttpHandler lacked @sharable -
the gateway could only ever serve ONE HTTP connection (the second channel hit
ChannelPipelineException; all existing tests used a single request).
A2AGatewayServer.getPort() now resolves the auto-selected port (0).

Also normalizes the nonstandard license headers on 8 deploy/kubernetes/*.yaml
files (apache#5398 leftovers) so the license check passes.

Tests: RocksDBTaskStoreTest (7), A2AGatewayWiringTest (3), agent
ConversationStoreTest (+2: LRU eviction, unbounded-compat), existing a2a
suite green; checkstyleMain/Test clean; dependency-review green.
@qqeasonchen
qqeasonchen merged commit 2d1dc1a into apache:develop Sep 21, 2026
9 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.

[Feature] A2A gateway production wiring: main-process boot, RocksDB task store, auth, metrics, contextId

1 participant