Audience: application developers using the
eventmesh-sdk-javaCloudEventsClientandA2AClientto publish / subscribe / stream / dispatch A2A tasks.Backend-agnostic. This guide covers the full API surface and is the single source of truth for what the client SDK does. The capability status table in the project README is the source of truth for GA / Beta / Experimental / Legacy tags; backend-specific configuration is in docs/eventmesh-configuration.md; architectural context is in docs/eventmesh-architecture.md.
The new architecture ships with one client SDK that exposes two surface APIs:
CloudEventsClient— HTTP + CloudEvents 1.0 pub/sub, request-reply, SSE / WebSocket push, RocketMQ 5.x Lite Topic, LLM streaming. This is the primary user path and the recommended way to integrate with EventMesh.A2AClient— Agent-to-Agent task dispatch on top of the same Runtime (docs/eventmesh-a2a-protocol.md). Used by multi-agent systems that need a durable task lifecycle.
Both clients talk only to the EventMesh Runtime (HTTP). The underlying
storage backend — RocketMQ 4.x, RocketMQ 5.x, Kafka, or any other
MeshStoragePlugin implementation — is completely transparent to the client.
Switching backends is a server-side configuration change, not a client change.
Legacy
EventMeshHttpClient/EventMeshTCPClientare kept for protocol compatibility but are no longer extended. New integrations must useCloudEventsClient(orA2AClientfor A2A workloads). See the migration notes at the end of this guide.
- Quick orientation
CloudEventsClientAPI reference- Builder and configuration
- Publish / subscribe patterns
- Request-reply (synchronous RPC)
- Subscriber transports: long-poll / SSE / WebSocket
- RocketMQ 5.x Lite Topic
- LLM streaming call (Mode 1 / Mode 2)
- Security: tokens, signatures, the unified gate
- Reliability: ACK, retries, dead-letter, idempotency
A2AClientfor agent workloads- Backend selection: RocketMQ 4.x / 5.x / Kafka
- End-to-end example
- Operational checklist
- Legacy compatibility
- Code locations
A typical client looks like this:
CloudEventsClient client = CloudEventsClient.builder()
.runtimeUrl("http://localhost:8080") // EventMesh Runtime HTTP endpoint
.clientId("order-svc") // unique per JVM
.pollIntervalMs(500L) // long-poll cadence
.build();
client.subscribe("orders", "BROADCAST", event -> {
System.out.println("got " + event.getId() + " type=" + event.getType());
});
CloudEvent e = CloudEventsClient.event(
"evt-1", "order-svc", "order.created", "{\"amt\":99}".getBytes(StandardCharsets.UTF_8));
client.publish("orders", e);That's the entire model. The rest of this document covers edge cases and the optional surfaces (A2A, streaming, security, Lite Topic).
org.apache.eventmesh.client.cloudevents.CloudEventsClient| Method | Returns | Notes |
|---|---|---|
builder() |
CloudEventsClientBuilder |
Entry point — see §3 |
publish(topic, CloudEvent) |
boolean |
Single publish; 202 on success |
publish(topic, List<CloudEvent>) |
boolean |
Batched publish (HTTP body batching) |
request(topic, CloudEvent, timeoutMs) |
CloudEvent |
Blocking request-reply; null on timeout; late replies are dropped |
reply(correlationId, CloudEvent) |
boolean |
Reply side of request-reply; uses the emcorrelationid extension |
subscribe(topic, mode, Consumer<CloudEvent>) |
— | Long-poll subscribe; handler return ⇒ auto-ACK |
subscribeWithAck(topic, mode, Predicate<CloudEvent>) |
— | Long-poll subscribe; Predicate returns true = ACK, false = no-ACK (at-least-once re-delivery on dispatcher timeout) |
subscribeSse(topic, mode, Consumer<CloudEvent>) |
— | SSE push subscribe; runs over HTTP on /events/stream |
subscribeWs(topic, mode, Consumer<CloudEvent>) |
— | WebSocket push subscribe; needs wsUrl (separate port) |
unsubscribe(topic) |
— | Unsubscribe one topic; stops the long-poll loop if no topics remain |
unsubscribe() |
— | Unsubscribe all; stop all loops / pushes |
createLiteTopic(parent, lite) |
boolean |
RocketMQ 5.x only. Idempotent create of a Lite Topic (RIP-83) |
publishLite(parent, lite, CloudEvent) |
boolean |
5.x only. Publish to LMQ inside the Lite Topic |
subscribeLite(parent, lite, Consumer<CloudEvent>) |
— | 5.x only. Background poll loop; offset managed inside the storage plugin (no ACK / no DLQ) |
unsubscribeLite(parent, lite) |
— | 5.x only. Stop the background poll loop for one Lite Topic |
streaming() |
StreamingOperations |
Entry point for LLM streaming — see §8 |
shutdown() |
— | Stop everything: long-poll, SSE, WS, Lite loops, streaming sessions |
static event(id, source, type, byte[] data) |
CloudEvent |
Convenience constructor |
mode is one of the string constants from
org.apache.eventmesh.runtime.subscription.DistributionMode:
| Mode | Semantics |
|---|---|
BROADCAST |
Every subscriber gets every message |
LOAD_BALANCE |
Each message goes to exactly one subscriber in the group |
MULTICAST |
Multi-cast delivery |
LOAD_BALANCE_STICKY |
Hash by partitionKey extension for stable affinity (preserves per-key order) |
CloudEventsClient.builder()
.runtimeUrl("http://localhost:8080") // required — Runtime HTTP endpoint
.clientId("my-service") // required — unique per JVM
.pollIntervalMs(500L) // long-poll cadence (default: builder default)
.wsUrl("http://localhost:8082") // optional — required for subscribeWs
.build();| Builder key | Required | Default | Notes |
|---|---|---|---|
runtimeUrl |
yes | — | HTTP(S) base URL of the Runtime |
clientId |
yes | — | Used in subscription registration, SubscriptionStore, quota key |
pollIntervalMs |
no | builder default | Long-poll cadence. Larger value = more idle time per round; smaller = more requests |
wsUrl |
no (yes for WS) | — | WebSocket endpoint. Runtime exposes WS on a separate port (configured at server start) |
Environment variables are honored via System.getProperty for tests:
CloudEventsClient client = CloudEventsClient.builder()
.runtimeUrl(System.getProperty("eventmesh.runtime.url", "http://localhost:8080"))
.clientId("demo-" + System.currentTimeMillis())
.build();TLS / mTLS is configured at the Runtime (server side), not the client. The
client just talks to https://... once TLS is enabled. See
docs/eventmesh-configuration.md.
client.subscribe("orders", "BROADCAST", event -> {
System.out.println("got " + event.getId());
// any thrown exception still counts as ACK — use subscribeWithAck for at-least-once
});client.subscribeWithAck("orders", "LOAD_BALANCE", event -> {
try {
process(event); // your business logic
return true; // ACK → offset advances
} catch (Exception ex) {
return false; // no-ACK → re-delivery on dispatcher timeout
}
});Business idempotency is your responsibility. EventMesh guarantees at-least-once, not exactly-once.
List<CloudEvent> batch = ...;
boolean ok = client.publish("orders", batch);The Runtime splits the batch into per-partition writes inside the storage plugin.
A single failure inside the batch surfaces as false and the Runtime returns
502 for that call.
The request-reply pattern uses the emcorrelationid CloudEvents extension
(all-lowercase, no hyphens — CloudEvents disallows hyphens in extension names).
// requester
CloudEvent req = CloudEventsClient.event("req-1", "caller", "query.price", payload);
CloudEvent reply = client.request("price-req", req, 10_000L); // up to 10s
if (reply != null) { /* use reply */ }
// replier
responder.subscribe("price-req", "LOAD_BALANCE", event -> {
Object corr = event.getExtension("emcorrelationid");
if (corr != null) {
CloudEvent r = CloudEventsClient.event("reply-1", "price-svc", "query.price.reply",
priceJson(event).getBytes(StandardCharsets.UTF_8));
responder.reply(corr.toString(), r);
}
});request(...) is blocking on the client thread. Late replies arriving after
timeoutMs are dropped at the Runtime. Use subscribeSse or subscribeWs
when you need to keep the channel open.
All three transports produce the same CloudEvent payload to the handler; the
only difference is the push direction.
| Transport | Endpoint | Push direction | Port |
|---|---|---|---|
| Long-poll | POST /events/subscribe |
client-driven | Runtime HTTP port (default 8080) |
| SSE | GET /events/stream (text/event-stream) |
server push | Runtime HTTP port (default 8080) |
| WebSocket | runtime WS endpoint | server push, bi-directional | Runtime WS port (default 8082, configurable) |
WebSocket needs a separate port because the WS upgrade is a different protocol
negotiation than plain HTTP. The Runtime starts the WS server on its own port
(server-side configuration), and the client must configure wsUrl
explicitly. Pointing wsUrl at the HTTP port will fail the WS handshake.
// SSE — same port as HTTP
client.subscribeSse("orders", "BROADCAST", event -> { /* server-push */ });
// WebSocket — separate port
CloudEventsClient wsClient = CloudEventsClient.builder()
.runtimeUrl("http://localhost:8080") // HTTP (publish / long-poll / SSE)
.wsUrl("http://localhost:8082") // WS push
.clientId("ws-sub").build();
wsClient.subscribeWs("orders", "BROADCAST", event -> { /* WS push */ });All three transports auto-ACK on handler return (like subscribe). Use the
manual-ACK variant only for the long-poll transport.
Lite Topic (RIP-83) is RocketMQ 5.5+'s hierarchical message container. A Lite
Topic lives inside a normal parent topic; the parent must be declared LITE
type, then individual lite queues inside it share the parent's storage
budget. Useful for session / sub-class fan-out at very high cardinality.
Backend-only feature.
createLiteTopic/publishLite/subscribeLitereturnfalse(or no callbacks fire) on RocketMQ 4.x, Kafka, or any non-LiteTopicCapablestorage backend. The Runtime returns501 Not Implementedfor the corresponding endpoints.
// 1. Declare — idempotent, call once at startup
client.createLiteTopic("orders", "user-42");
// 2. Subscribe — background poll loop, push-style callback (no ACK, no DLQ)
client.subscribeLite("orders", "user-42", event -> { /* process lite event */ });
// 3. Publish — routes to LMQ via __LITE_TOPIC property
client.publishLite("orders", "user-42",
CloudEventsClient.event("lt-1", "order-svc", "order.lite", payload));Differences from ordinary topic subscribe:
- No ACK / no DLQ — offset is managed inside the storage plugin. At-least-once is best-effort.
- Client-driven polling —
subscribeLiteruns aGET /events/lite/pollloop on the client side. UseunsubscribeLite(parent, lite)to stop one Lite subscription;unsubscribe()/shutdown()stop everything.
EventMesh provides two streaming patterns for LLM-style use cases (token chunks flowing back, multi-turn conversation context).
| Mode | Use case | Direction | Entry |
|---|---|---|---|
| Mode 1 — streaming call | client → agent (LLM), agent streams tokens back | request/response, push | client.streaming().openSession(...) |
| Mode 2 — pub/sub on a session | producer writes chunks; consumer reads via SSE | publish/subscribe | client.subscribeSession(sessionId) / client.openSessionPublisher(sessionId) |
CloudEventsClient client = CloudEventsClient.builder()
.runtimeUrl("http://localhost:8080").clientId("my-app").build();
try (StreamingResponse r = client.streaming()
.openSession(OpenSession.builder().clientId(client.clientId()).build())
.call("Introduce EventMesh in three sentences")) {
r.forEach(chunk -> System.out.print(chunk.getChunk())).join();
}forEach fires once per token (or delta). .join() blocks until the stream
ends. Closing the StreamingResponse ends one round; it does not close
the session.
StreamingSession session = client.streaming()
.openSession(OpenSession.builder().clientId("my-app").build());
try {
try (StreamingResponse r1 = session.call("I'm Zhang San, a Java engineer")) {
r1.forEach(c -> System.out.print(c.getChunk())).join();
}
// session remembers the previous round
try (StreamingResponse r2 = session.call("What's my name and job?")) {
r2.forEach(c -> System.out.print(c.getChunk())).join();
}
} finally {
session.close();
}Multi-turn context is owned by the agent's ConversationStore, keyed by
sessionId.
Useful when chunks need to be persistent (durable across process restarts) or fan-out to multiple consumers. Internally uses the storage plugin's Lite Topic.
// consumer side
StreamingResponse sub = client.subscribeSession("my-session-id");
sub.forEach(chunk -> System.out.println("[" + chunk.getSeq() + "] " + chunk.getChunk())).join();
sub.close();
// producer side
SessionPublisher pub = client.openSessionPublisher("my-session-id");
pub.publish("Hello", false); // non-terminal frame
pub.publish(" world", false);
pub.publish("", true); // terminal frame — consumer's forEach completes
pub.close();An agent that participates in Mode 1 follows a four-step contract:
- parse
sessionId,prompt,replyTofrom the inbound CloudEvent - on each LLM token → emit a non-terminal frame
{chunk: token, done: false} - on normal completion → emit a terminal frame
{chunk: "", done: true} - on error → emit a terminal error frame
{chunk: "", done: true, error: "..."}
Reference implementation: eventmesh-agent/.../StreamingAgent.java
(instantiate with an LLM client, an agentParent topic, the agent's agentId,
and a ConversationStore).
The Runtime pre-creates the agent / client parent topics. For Mode 2 also
pre-create sessionStreamParent. The 6-arg SessionRouter enables
sessionTtlMs + sessionStreamParent; the 4-arg variant is Mode 1 only.
By default, the Runtime is open. Production deployments must enable the unified security gate (issue #5304) on the server. From the client side, the only practical change is that you may need to attach credentials as HTTP headers / CloudEvents extensions:
Authorization: Bearer <token>— picked up by the built-inTokenAuthFilterand recorded into theRequestContextasprincipal/scopes- CloudEvents extension
emtenantid— drives per-tenant quota inTenantQuotaManager - CloudEvents extension
emcorrelationid— request-reply correlation
The gate runs FilterChain (TokenAuth → SignatureVerifier → Acl) →
QuotaManager (per-Resource counter, default UnlimitedQuotaManager) →
AuditSink (default LoggingAuditSink) on every ingress. The
Operation enum is recorded in the context, so quota can distinguish a
publish from a subscribe from an A2A call.
For configuration and the three wiring points
(UniHttpServer.withSecurityGate, A2AGatewayHttpHandler.withSecurityGate,
ConnectorScheduler.withSecurityGate) see
docs/eventmesh-configuration.md and
docs/eventmesh-architecture.md §4.
The client SDK does not need to "know" about the gate. A deployment that enables the gate is a server-side change. The client sends the same CloudEvent and the Runtime decides. If the Runtime requires auth, it returns
401and your handler can re-authenticate and retry.
| Concept | Where it lives | Client responsibility |
|---|---|---|
| At-least-once | Runtime DeliveryStateStore |
Use subscribeWithAck and return true only after success |
| Retries | Runtime retry policy | false from your predicate triggers a re-delivery after the dispatcher timeout |
| Dead-letter | Runtime DeadLetterStore |
Inspect / replay via admin endpoints (see eventmesh-configuration.md) |
| Idempotency | — | You. Use event.getId() as the dedup key. The Runtime does not deduplicate. |
| Offset | Runtime OffsetStore (L1) |
— |
| Subscription state | Runtime SubscriptionStore (L2) |
Survives Runtime restart via the meta store |
| Task state (A2A) | Runtime TaskStore (L3) |
— |
For the storage-state taxonomy (L1 / L2 / L3) and the
MeshStoragePlugin / MeshStoragePluginTCK contract, see
docs/eventmesh-architecture.md §3.
Configuration knobs: see
docs/eventmesh-configuration.md
(eventmesh.runtime.delivery.*).
For multi-agent systems, the A2A protocol gives you a
durable task lifecycle (submitted → working → completed | failed | canceled) on top of the same storage substrate. The client side is
org.apache.eventmesh.protocol.a2a.A2AClient (in the
eventmesh-protocol-a2a module).
A2AClient client = A2AClient.builder()
.gatewayUrl("http://localhost:8080") // Runtime A2A gateway (port 8080 by default)
.namespace("default")
.agentName("order-agent")
.heartbeatInterval(30_000L)
.build();| Builder key | Required | Notes |
|---|---|---|
gatewayUrl |
yes | Runtime HTTP base URL (A2A is served on the same HTTP port) |
namespace |
recommended | A2A namespace for topic isolation |
agentName |
recommended | Local agent identity; used in topic factory and AgentCard |
heartbeatInterval |
no | Heartbeat to the Runtime; default 30s |
socketTimeoutMs |
no | Underlying HTTP client socket timeout |
| Method | Returns | Notes |
|---|---|---|
sendTask(task) |
TaskResult |
Submit a task; returns immediately with taskId + initial state |
sendTaskSync(task, timeoutMs) |
TaskResult |
Submit and block until terminal state (or timeout) |
sendTaskAsync(task, Consumer<TaskResult>) |
— | Submit and stream intermediate states via callback |
getTaskStatus(taskId) |
TaskResult |
Re-query the current state of a task |
cancelTask(taskId) |
boolean |
Request cancellation; the target agent stops work if it can |
streamTaskStatus(taskId, Consumer<TaskResult>) |
— | SSE push of state transitions until terminal |
listAgents() |
List<AgentCard> |
Browse the agent registry |
registerAgentCard(AgentCard) |
boolean |
Publish this agent's capability description |
TaskResult exposes taskId, state, data, error, and targetAgent.
Each task has a taskEpoch field that is set at creation and never reset.
Stale writes with a taskEpoch different from the create value are rejected by
the Runtime. Use the same taskEpoch across retries so the same logical task
always lands in the same slot.
The A2A gateway is enabled at the Runtime by booting the A2AGatewayServer
(Netty) on a configurable port (defaults to the main HTTP port). The endpoint
surface is:
POST /a2a/tasks/send— submit a taskPOST /a2a/tasks/sync— submit and blockGET /a2a/tasks/{id}— query statePOST /a2a/tasks/{id}/cancel— cancelGET /a2a/tasks/{id}/stream— SSE stream of state transitionsGET /a2a/agents— list agentsPOST /a2a/agents— register an agent card
See docs/eventmesh-a2a-protocol.md for the wire contract and docs/eventmesh-architecture.md §5 for the runtime architecture.
The client code is identical across backends. Switching from one storage
backend to another is a Runtime configuration change; the same
CloudEventsClient (and A2AClient) bytes run unchanged.
| RocketMQ 4.x | RocketMQ 5.x | Kafka | |
|---|---|---|---|
| Plugin SPI key | rocketmq |
rocketmq5 |
kafka |
| Storage module | eventmesh-storage-plugin/eventmesh-storage-rocketmq |
eventmesh-storage-plugin/eventmesh-storage-rocketmq5 |
eventmesh-storage-plugin/eventmesh-storage-kafka |
| Connection | NettyRemotingClient direct (no rocketmq-client JAR) |
Same — pure 5.5 remoting | kafka-clients (assign+seek+poll, no consumer group; EventMesh owns offsets) |
| Auth | ACL (optional) | ACL (optional) | SASL/SSL pass-through (security.protocol / sasl.mechanism / sasl.jaas.config are passed verbatim to kafka-clients) |
| Lite Topic | — | yes (LiteTopicCapable) |
— |
Per-backend keys are listed in
docs/eventmesh-configuration.md.
Pick one eventmesh.storage.type at Runtime startup:
# 4.x
EVENTMESH_STORAGE_TYPE=rocketmq EVENTMESH_ROCKETMQ_NAMESRV=127.0.0.1:9876 bin/start.sh
# 5.x
EVENTMESH_STORAGE_TYPE=rocketmq5 EVENTMESH_ROCKETMQ5_NAMESRV=127.0.0.1:9876 bin/start.sh
# Kafka
EVENTMESH_STORAGE_TYPE=kafka bin/start.sh # eventmesh.properties has bootstrap + SASLThe HTTP contract is the same — but the storage-plugin choices have client-visible consequences for subscription semantics under failure:
| Dimension | RocketMQ 4.x | RocketMQ 5.x | Kafka |
|---|---|---|---|
| Consumption model | Classic PULL (EventMesh owns offset + partition ownership) | POP (broker allocates queues + lease gate) | assign + seek + poll (no consumer group; EventMesh owns offset) |
| Multi-instance de-dup | EventMesh PartitionOwnership |
broker POP + lease | EventMesh PartitionOwnership (Kafka assign) |
| Offset ACK semantics | offset advances only on ACK | same | same (Kafka offset not committed; EventMesh-managed) |
publish / subscribe / request / reply |
consistent | consistent | consistent |
| Lite Topic | not supported | supported | not supported |
The publish / subscribe / subscribeWithAck / request / reply API
contract is identical across all three backends — that is the point of the
abstraction.
For SASL-enabled Kafka clusters (e.g. wemq-kafka), set in eventmesh.properties:
eventMesh.server.kafka.namesrvAddr=127.0.0.1:9094
security.protocol=SASL_PLAINTEXT
sasl.mechanism=PLAIN
sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="<user>" password="<pass>";The KafkaMeshStoragePlugin forwards security.* / sasl.* / ssl.* keys
verbatim to the underlying KafkaProducer / KafkaConsumer / AdminClient.
Plain-text Kafka clusters need none of these.
public class Demo {
public static void main(String[] args) throws Exception {
CloudEventsClient client = CloudEventsClient.builder()
.runtimeUrl(System.getProperty("eventmesh.runtime.url", "http://localhost:8080"))
.clientId("demo-" + System.currentTimeMillis())
.pollIntervalMs(500L)
.build();
client.subscribeWithAck("demo-topic", "LOAD_BALANCE", event -> {
System.out.println("processing: " + event.getId() + " type=" + event.getType());
return true; // ACK
});
for (int i = 0; i < 10; i++) {
CloudEvent e = CloudEventsClient.event(
"e" + i, "demo", "demo.tick",
("tick-" + i).getBytes(StandardCharsets.UTF_8));
client.publish("demo-topic", e);
}
Thread.sleep(60_000L);
client.shutdown();
}
}Switching the backend is a server-side change only:
# 4.x
EVENTMESH_STORAGE_TYPE=rocketmq EVENTMESH_ROCKETMQ_NAMESRV=127.0.0.1:9876 bin/start.sh
# 5.x (Lite Topic capable)
EVENTMESH_STORAGE_TYPE=rocketmq5 EVENTMESH_ROCKETMQ5_NAMESRV=127.0.0.1:9876 bin/start.sh
# Kafka (SASL in eventmesh.properties)
EVENTMESH_STORAGE_TYPE=kafka bin/start.shThe same Demo class runs unchanged on all three.
| Check | Where | What to look for |
|---|---|---|
| Runtime reachable | client log | First publish returns true; first subscribe callback fires within pollIntervalMs |
clientId uniqueness |
Runtime log | A clientId collision prints a warning; use a different clientId per JVM |
| Backend connection | Runtime startup log | [storage] connected to <backend> line; otherwise no subscriptions will fire |
| Security gate | Runtime response | 401 on first request → auth header missing; 429 → quota exhausted; 403 → ACL denied |
| Quota exhaustion | Runtime metrics | eventmesh_security_gate_quota_* per-tenant counters |
| Dead-letter inspection | admin HTTP (port 8081) | GET /admin/dlq?topic=<topic> |
| A2A agent registry | A2AClient.listAgents() |
Should return at least one AgentCard for agentName you registered |
See docs/production-readiness.md for SLOs and runbooks.
The legacy EventMeshHttpClient and EventMeshTCPClient continue to work
against the current Runtime, but they are legacy-compatible in the
capability status table and are not extended.
| Old client | New client | Migration |
|---|---|---|
EventMeshHttpClient.publish(CloudEventMessage) |
CloudEventsClient.publish(topic, CloudEvent) |
Switch the event from CloudEventMessage to CloudEvent; topic is a string |
EventMeshTCPClient.subscribe(topic, EventListener) |
CloudEventsClient.subscribe(topic, mode, Consumer<CloudEvent>) |
Add a mode; switch the callback to Consumer<CloudEvent> |
TCP subscribe with custom Session |
WebSocket | WebSocket is the modern bi-directional transport |
| OpenMessaging SDK | CloudEventsClient | The OpenMessaging wire is not supported in the new Runtime; use the HTTP + CloudEvents path |
For TCP / gRPC SDK migration details, see the legacy-compat section of this guide (§15) — this document is the authoritative home for the new client API; the old guide is preserved in git history for the migration notes.
CloudEventsClient—eventmesh-sdks/eventmesh-sdk-java/.../cloudevents/CloudEventsClient.javaCloudEventsClientBuilder
- Streaming —
eventmesh-sdks/eventmesh-sdk-java/.../cloudevents/stream/(operations, response, session, request, publisher, exception) A2AClient—eventmesh-protocol-plugin/eventmesh-protocol-a2a/.../A2AClient.javaA2ATopicFactory—eventmesh-protocol-plugin/eventmesh-protocol-a2a/.../A2ATopicFactory.java(agentInbox(agentId),gatewayResponseTopic(ns, gw, taskId),+wildcard)- Runtime HTTP entry —
eventmesh-runtime/.../http/UniHttpServer.java(/events/*endpoints;withSecurityGate(...)wiring point) - A2A HTTP handler —
eventmesh-runtime/.../a2a/A2AGatewayHttpHandler.java(/a2a/*endpoints;withSecurityGate(...)wiring point) - Streaming agent —
eventmesh-agent/.../StreamingAgent.java - Storage plugins —
eventmesh-storage-plugin/eventmesh-storage-rocketmq/(SPI keyrocketmq)eventmesh-storage-plugin/eventmesh-storage-rocketmq5/(SPI keyrocketmq5,LiteTopicCapable)eventmesh-storage-plugin/eventmesh-storage-kafka/(SPI keykafka, assign+seek+poll, SASL pass-through)
- Security gate —
eventmesh-runtime/.../security/gate/(SecurityGate,RequestContext,QuotaManager,AuditSink,GateDecision) - Architecture —
eventmesh-architecture-guard/.../guard/ArchitectureRules.java(ArchUnit layered-architecture enforcement)
See also:
- docs/eventmesh-architecture.md — system architecture, control / data / agent planes
- docs/eventmesh-features.md — feature-by-feature guide
- docs/eventmesh-configuration.md — every runtime key
- docs/eventmesh-getting-started.md — zero-to-running guide
- docs/eventmesh-a2a-protocol.md — A2A wire contract
- docs/production-readiness.md — SLOs, runbooks