You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@eventmesh.apache.org by ch...@apache.org on 2026/08/17 09:38:55 UTC

(eventmesh) branch develop updated: feat: lite-topic — internal EventMeshFrame architecture, streaming calls, sticky load balancing

This is an automated email from the ASF dual-hosted git repository.

qqeasonchen pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/eventmesh.git


The following commit(s) were added to refs/heads/develop by this push:
     new 7735ee123 feat: lite-topic — internal EventMeshFrame architecture, streaming calls, sticky load balancing
7735ee123 is described below

commit 7735ee12333d33c4037413e96f8b8f120fadb210
Author: qqeasonchen <qq...@gmail.com>
AuthorDate: Mon Aug 17 17:02:26 2026 +0800

    feat: lite-topic — internal EventMeshFrame architecture, streaming calls, sticky load balancing
    
    Squash of lite-topic branch (34 commits) + checkstyle fixes, secrets scrubbed.
    
    Architecture (internal data model overhaul):
    - Internal wire unified as EventMeshFrame (14B fixed header + KV attrs + raw data,
      msgType = STREAM_REQ | STREAM_CHUNK | EVENT); CloudEvent demoted to an external
      ingress format only. Storage SPI and the full runtime dispatch pipeline all carry
      EventMeshFrame.
    - FrameAdaptor SPI: per-protocol plugins convert external formats directly to/from
      EventMeshFrame with zero CloudEvent intermediary — protocol-cloudevents (new
      standalone plugin), protocol-meshmessage, protocol-a2a. Empty protocol-grpc removed.
    - WireCodec SPI: pluggable internal MQ byte encoding (default EventMeshFrameCodec).
    
    Streaming calls (two orthogonal modes):
    - Mode 1 streaming call: openSession -> call -> forEach, multi-turn context via
      agent ConversationStore; channel multiplexing, matchmaker + agent registrar.
    - Mode 2 publish/subscribe: deterministic lite naming (session.<sessionId>), no agent,
      POST /session/publish + GET /session/subscribe SSE bridging.
    - SDK: StreamingSession/StreamingResponse with forEach as the sole consumption
      posture; SessionPublisher for mode-2 producing.
    
    Offset management (path 2: self-managed + MQ replay):
    - Two-layer offsets stay local; meta upload off by default.
    - Deferred MQ ACK for RocketMQ 5.x POP (restores at-least-once); monotonic
      writeOffset; restart cursor alignment via reserved __mqcursor__ key +
      frame-carried emmqoffset/emmqpartition.
    
    Sticky load balancing:
    - Session-distribution-layer recommend (LoadMeter self-collected metrics, global
      scoring, overload feedback); instanceUrl pinning; forwarding layer disabled.
    
    Robustness fixes (P0-P2): SSE/WS write-failure nack, SDK HttpURLConnection leaks,
    Delivery volatile fields, immutable SessionRegistry beans, synchronized
    PushService.offer, dispatcher tick putIfAbsent, RocketMQ5 ack retry.
    
    Ops: Prometheus /metrics scrape endpoint, mTLS client-auth, WebSocket IT verified,
    real-Kafka E2E verified.
    
    Style: all checkstyle violations fixed (imports/order/single-line/renames);
    checkstyleMain + checkstyleTest 0 violations; full 'gradle check' green.
    
    Secrets scrubbed from E2EConfig defaults (internal IPs/users/passwords/gateway
    replaced with localhost placeholders / empty / api.openai.com).
---
 .gitignore                                         |    4 +
 build.gradle                                       |   66 +-
 docs/a2a-protocol/ARCHITECTURE.md                  |  316 ---
 docs/a2a-protocol/IMPLEMENTATION_SUMMARY.md        |  103 -
 docs/a2a-protocol/IMPLEMENTATION_SUMMARY_EN.md     |  103 -
 docs/a2a-protocol/README.md                        |  762 +++++-
 docs/a2a-protocol/README_EN.md                     |  255 --
 docs/a2a-protocol/TEST_RESULTS.md                  |  112 -
 docs/a2a-protocol/eventmesh-a2a-design.md          |  478 ----
 docs/eventmesh-cloudevents-client-guide.md         |  149 +-
 docs/eventmesh-offset-lb-frame-design.md           |  388 +++
 docs/eventmesh-uni-architecture-redesign.md        |  398 ++-
 docs/plugins/core-engines-configuration.md         |  135 -
 docs/production-readiness.md                       |  340 +--
 docs/uni-architecture-features-and-tests.md        |   73 -
 eventmesh-agent/build.gradle                       |   43 +
 eventmesh-agent/conf/agent.properties              |   17 +
 eventmesh-agent/conf/log4j2.xml                    |   19 +
 .../apache/eventmesh/agent/AgentApplication.java   |   96 +
 .../apache/eventmesh/agent/AgentControlClient.java |  115 +
 .../apache/eventmesh/agent/ConversationStore.java  |   76 +
 .../org/apache/eventmesh/agent/StreamingAgent.java |  135 +
 .../eventmesh/agent/llm/OpenAiLlmClient.java       |  133 +
 .../eventmesh/agent/ConversationStoreTest.java     |   82 +
 .../eventmesh/agent/llm/OpenAiLlmClientTest.java   |  152 ++
 .../eventmesh/common/protocol/ByteTransport.java   |   62 +
 .../eventmesh/common/stream/StreamChunk.java       |   66 +
 .../eventmesh/common/stream/StreamEventCodec.java  |  174 ++
 .../eventmesh/common/stream/StreamRequest.java     |   53 +
 .../eventmesh/common/wire/EventMeshFrame.java      |  360 +++
 .../eventmesh/common/wire/EventMeshFrameCodec.java |   64 +
 .../common/wire/MeshMessageFrameCodec.java         |  103 +
 .../org/apache/eventmesh/common/wire/MetaJson.java |  233 ++
 .../apache/eventmesh/common/wire/WireCodec.java    |   61 +
 .../apache/eventmesh/common/wire/WireCodecs.java   |   70 +
 .../common/stream/StreamEventCodecTest.java        |  180 ++
 .../eventmesh/common/wire/EventMeshFrameTest.java  |  186 ++
 .../cloudevents/demo/stream/StreamingCallDemo.java |  160 ++
 .../eventmesh/protocol/a2a/A2AFrameAdaptor.java    |  119 +
 .../org.apache.eventmesh.protocol.api.FrameAdaptor |   21 +-
 .../eventmesh/protocol/api/FrameAdaptor.java       |  104 +
 .../eventmesh/protocol/api/FrameAdaptors.java      |  100 +
 .../eventmesh-protocol-cloudevents/build.gradle    |    7 +
 .../cloudevents/CloudEventsFrameAdaptor.java       |   86 +
 .../org.apache.eventmesh.protocol.api.FrameAdaptor |   22 +-
 .../meshmessage/MeshMessageFrameAdaptor.java       |  118 +
 .../org.apache.eventmesh.protocol.api.FrameAdaptor |   22 +-
 eventmesh-runtime/build.gradle                     |  163 +-
 eventmesh-runtime/conf/eventmesh.properties        |    4 +
 .../eventmesh/runtime/a2a/A2ACardHttpHandler.java  |  218 --
 .../runtime/a2a/A2AGatewayHttpHandler.java         |  648 -----
 .../eventmesh/runtime/a2a/A2AGatewayServer.java    |  338 ---
 .../eventmesh/runtime/a2a/A2AGatewayService.java   |  422 ---
 .../runtime/a2a/A2APublishSubscribeService.java    |  449 ----
 .../runtime/a2a/EventMeshA2ATransport.java         |    3 +-
 .../runtime/a2a/InMemoryA2AMessageTransport.java   |  118 -
 .../apache/eventmesh/runtime/a2a/TaskRegistry.java |  348 ---
 .../eventmesh/runtime/admin/UniAdminServer.java    |   38 +
 .../eventmesh/runtime/admin/UniAdminService.java   |   22 +-
 .../runtime/boot/EventMeshApplication.java         |  161 +-
 .../apache/eventmesh/runtime/boot/UniRuntime.java  |   82 +-
 .../runtime/cluster/ClusterCoordinator.java        |    7 +-
 .../runtime/cluster/ClusterMembership.java         |   83 +-
 .../runtime/cluster/ClusterSubscriptionStore.java  |    3 +-
 .../eventmesh/runtime/cluster/Forwarder.java       |    3 +-
 .../eventmesh/runtime/cluster/HttpForwarder.java   |    7 +-
 .../eventmesh/runtime/cluster/LocalDeliverer.java  |    3 +-
 .../eventmesh/runtime/delivery/DeadLetterSink.java |    6 +-
 .../eventmesh/runtime/delivery/Delivery.java       |   45 +-
 .../eventmesh/runtime/delivery/PushChannel.java    |    9 +-
 .../runtime/delivery/ReliableDispatcher.java       |   51 +-
 .../eventmesh/runtime/delivery/WebHookChannel.java |   15 +-
 .../eventmesh/runtime/http/UniHttpServer.java      |  629 ++++-
 .../eventmesh/runtime/ingress/LoadMeter.java       |  173 ++
 .../runtime/ingress/UniIngressService.java         |  303 ++-
 .../apache/eventmesh/runtime/metrics/UniTrace.java |   25 +
 .../runtime/offset/InMemoryOffsetStore.java        |    5 +-
 .../runtime/offset/InMemoryPushOffsetStore.java    |  111 -
 .../eventmesh/runtime/offset/PushOffsetStore.java  |   93 -
 .../runtime/offset/RocksDBOffsetStore.java         |   25 +-
 .../eventmesh/runtime/push/BufferedEvent.java      |   13 +-
 .../apache/eventmesh/runtime/push/Connection.java  |    7 +-
 .../eventmesh/runtime/push/ConnectionPushPump.java |   14 +-
 .../eventmesh/runtime/push/LongPollingChannel.java |    5 +-
 .../apache/eventmesh/runtime/push/PushService.java |   66 +-
 .../eventmesh/runtime/push/SseConnection.java      |   42 +-
 .../eventmesh/runtime/push/WsConnection.java       |   16 +-
 .../runtime/session/AgentAnchoredStrategy.java     |   43 +
 .../eventmesh/runtime/session/AgentRecord.java     |   51 +
 .../eventmesh/runtime/session/AgentRegistrar.java  |  127 +
 .../AgentStatus.java}                              |   32 +-
 .../BrokerGroupHealth.java}                        |   24 +-
 .../eventmesh/runtime/session/ChannelStrategy.java |   55 +
 .../eventmesh/runtime/session/Matchmaker.java      |  145 ++
 .../eventmesh/runtime/session/SessionRegistry.java |  307 +++
 .../eventmesh/runtime/session/SessionRouter.java   |  468 ++++
 .../runtime/subscription/CloudEventFilter.java     |   24 +-
 .../runtime/subscription/Subscription.java         |    7 +-
 .../runtime/subscription/SubscriptionManager.java  |   32 +-
 .../transport/tcp/MeshMessagePackageRouter.java    |   30 +-
 .../runtime/transport/tcp/NettyTcpPushChannel.java |   34 +-
 .../runtime/transport/tcp/TcpPushChannel.java      |   14 +-
 .../runtime/transport/tcp/TcpRequest.java          |   16 +-
 .../runtime/transport/tcp/UniTcpServer.java        |    2 +-
 .../a2a/A2AClientServerIntegrationTest.java        |  482 ----
 .../runtime/a2a/A2AGatewayEndToEndTest.java        |  234 --
 .../runtime/a2a/A2AGatewayServiceTest.java         |  407 ---
 .../eventmesh/runtime/a2a/AgentCardTestUtils.java  |   62 -
 .../a2a/InMemoryA2AMessageTransportTest.java       |  195 --
 .../eventmesh/runtime/a2a/TaskRegistryTest.java    |  297 ---
 .../runtime/admin/PrometheusEndpointTest.java      |  128 +
 .../runtime/admin/UniAdminServerTest.java          |    6 +-
 .../runtime/admin/UniAdminServiceTest.java         |   22 +-
 .../runtime/boot/EventMeshApplicationTest.java     |    6 +-
 .../runtime/boot/PullOffsetAlignmentTest.java      |  264 --
 .../runtime/boot/RestartCursorAlignmentTest.java   |  216 ++
 .../eventmesh/runtime/boot/UniRuntimeTest.java     |   12 +-
 .../runtime/cluster/ClusterCoordinatorTest.java    |   24 +-
 .../runtime/cluster/ClusterMembershipLoadTest.java |  127 +
 .../runtime/cluster/LoadBalancingScoringTest.java  |  156 ++
 .../delivery/DeferredAckDispatcherTest.java        |  160 ++
 .../delivery/DeliveryVolatileAndResurrectTest.java |  106 +
 .../runtime/delivery/MqCursorRecordingTest.java    |  118 +
 .../runtime/delivery/ReliableDispatcherTest.java   |   13 +-
 .../runtime/delivery/WebHookChannelTest.java       |    8 +-
 .../http/LegacyHttpServerIntegrationTest.java      |   22 +-
 .../eventmesh/runtime/http/SecurityWiringTest.java |    6 +-
 .../eventmesh/runtime/http/SseStreamTest.java      |   10 +-
 .../runtime/http/UniHttpServerEndpointTest.java    |    6 +-
 .../eventmesh/runtime/http/UniHttpServerTest.java  |   10 +-
 .../eventmesh/runtime/ingress/LoadMeterTest.java   |   80 +
 .../runtime/ingress/UniIngressServiceTest.java     |   27 +-
 .../it/AckTimeoutRedeliveryIntegrationTest.java    |    5 +-
 .../apache/eventmesh/runtime/it/AgentLauncher.java |  110 +
 .../runtime/it/BatchPublishIntegrationTest.java    |   20 +-
 .../runtime/it/ClusterForwardIntegrationTest.java  |   12 +-
 .../it/ConnectorSchedulingIntegrationTest.java     |   14 +-
 .../eventmesh/runtime/it/DlqIntegrationTest.java   |   25 +-
 .../org/apache/eventmesh/runtime/it/E2EConfig.java |  133 +
 .../runtime/it/KafkaClientE2EIntegrationTest.java  |    8 +-
 .../runtime/it/LegacyTcpClientIntegrationTest.java |   10 +-
 .../runtime/it/LiteStreamCallIntegrationTest.java  |  404 +++
 .../it/NacosClusterForwardIntegrationTest.java     |   12 +-
 .../runtime/it/RateLimitIntegrationTest.java       |    6 +-
 .../it/RequestReplyHttpIntegrationTest.java        |   20 +-
 .../runtime/it/RocketMQ5BrokerIntegrationTest.java |   15 +-
 .../eventmesh/runtime/it/RuntimeLauncher.java      |   96 +
 .../eventmesh/runtime/it/StreamingDemoRunner.java  |  199 ++
 .../eventmesh/runtime/it/StreamingSdkE2ETest.java  |  586 +++++
 .../eventmesh/runtime/it/TlsIntegrationTest.java   |   13 +-
 .../it/UnsubscribeTopicIntegrationTest.java        |   10 +-
 .../runtime/it/WebSocketPushIntegrationTest.java   |   19 +-
 .../offset/OffsetMonotonicAndRecoveryTest.java     |  134 +
 .../runtime/offset/PushOffsetStoreTest.java        |  113 -
 .../runtime/push/ConnectionPushPumpTest.java       |   11 +-
 .../push/PushServiceBufferOverflowTest.java        |  105 +
 .../eventmesh/runtime/push/PushServiceTest.java    |   17 +-
 .../runtime/session/AgentRegistrarTest.java        |   78 +
 .../runtime/session/ChannelStrategyTest.java       |   53 +
 .../session/FrameProtocolConversionTest.java       |  164 ++
 .../eventmesh/runtime/session/MatchmakerTest.java  |  152 ++
 .../runtime/session/SessionReaperTest.java         |  105 +
 .../session/SessionRegistryAtomicityTest.java      |   94 +
 .../runtime/session/SessionRegistryTest.java       |  156 ++
 .../subscription/SubscriptionManagerTest.java      |   37 +-
 .../transport/http/LegacyHttpBridgeTest.java       |   21 +-
 .../transport/tcp/TcpCompatibilityBridgeTest.java  |   11 +-
 .../runtime/transport/tcp/UniTcpServerTest.java    |   24 +-
 .../client/cloudevents/CloudEventsClient.java      |  323 ++-
 .../stream/DefaultStreamingOperations.java         |   47 +
 .../stream/DefaultStreamingResponse.java           |  319 +++
 .../client/cloudevents/stream/OpenSession.java     |   54 +
 .../cloudevents/stream/SessionPublisher.java       |  121 +
 .../client/cloudevents/stream/StreamException.java |   30 +-
 .../client/cloudevents/stream/StreamRequest.java   |   66 +
 .../cloudevents/stream/StreamingOperations.java    |   45 +
 .../cloudevents/stream/StreamingResponse.java      |   48 +
 .../cloudevents/stream/StreamingSession.java       |  110 +
 .../impl/cloudevent/CloudEventTCPSubClient.java    |    2 +-
 .../EventMeshMessageTCPSubClient.java              |    2 +-
 .../client/cloudevents/CloudEventsClientTest.java  |   10 +-
 .../stream/DefaultStreamingResponseTest.java       |  117 +
 .../stream/StreamingSessionFakeServerTest.java     |  153 ++
 .../eventmesh/api/storage/LiteTopicCapable.java    |   27 +-
 .../eventmesh/api/storage/MeshStoragePlugin.java   |   34 +-
 .../eventmesh/api/storage/OffsetExtensions.java    |   89 -
 .../kafka/storage/KafkaMeshStoragePlugin.java      |  175 +-
 .../storage/kafka/KafkaBrokerIntegrationTest.java  |   27 +-
 .../storage/RocketMQRemotingStoragePlugin.java     |   87 +-
 .../storage/RocketMQ5RemotingStoragePlugin.java    |  341 ++-
 gradle.properties                                  |    2 +-
 resources/eventmesh-architecture-7.excalidraw      | 2733 --------------------
 resources/eventmesh-architecture-7.png             |  Bin 801505 -> 0 bytes
 resources/logo.svg                                 |    1 -
 settings.gradle                                    |   20 +-
 195 files changed, 13803 insertions(+), 10481 deletions(-)

diff --git a/.gitignore b/.gitignore
index a3aa6cbf2..ae11e2691 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,3 +56,7 @@ bld/
 Cargo.lock
 rocketmq-pull-offsets.properties
 kafka-pull-offsets.properties
+
+# runtime state / dev tooling (never commit)
+.claude/
+eventmesh-runtime/data/
diff --git a/build.gradle b/build.gradle
index d884a799a..1065ba66c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -31,7 +31,10 @@ import java.util.concurrent.TimeUnit
 
 buildscript {
     repositories {
-        mavenCentral()
+        maven {
+            allowInsecureProtocol = true
+            url "http://maven.weoa.com/nexus/content/groups/public/"
+        }
         maven {
             url "https://plugins.gradle.org/m2/"
         }
@@ -40,7 +43,7 @@ buildscript {
     dependencies {
         classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:5.2.5"
         classpath "io.spring.gradle:dependency-management-plugin:1.1.6"
-        classpath "com.diffplug.spotless:spotless-plugin-gradle:6.25.0"
+        classpath "com.diffplug.spotless:spotless-plugin-gradle:6.13.0"
 
         classpath "org.apache.httpcomponents:httpclient:4.5.14"
         classpath "commons-io:commons-io:2.18.0"
@@ -101,7 +104,10 @@ allprojects {
 
     dependencies {
         repositories {
-            mavenCentral()
+            maven {
+                allowInsecureProtocol = true
+                url "http://maven.weoa.com/nexus/content/groups/public/"
+            }
         }
         testImplementation "org.junit.jupiter:junit-jupiter:5.11.0"
     }
@@ -136,12 +142,16 @@ allprojects {
 
     test {
         useJUnitPlatform()
+        jvmArgs '-Dfile.encoding=UTF-8', '-Dstdout.encoding=UTF-8', '-Dstderr.encoding=UTF-8'
     }
 
-    // Disable doclint: it is overly strict on JDK 11+ and breaks javadoc generation
-    // on comments that were valid under JDK 8. The uni-architecture baseline is JDK 21.
-    tasks.withType(Javadoc).configureEach {
-        options.addStringOption('Xdoclint:none', '-quiet')
+    // Remove doclint warnings that pollute javadoc logs when building
+    if (JavaVersion.current().isJava8()) {
+        tasks.withType(Javadoc).tap {
+            configureEach {
+                options.addStringOption('xdoclint:none', '-quiet')
+            }
+        }
     }
 }
 
@@ -286,6 +296,43 @@ tasks.register('dist-connector') {
     }
 }
 
+tasks.register('dist-agent') {
+    subprojects.forEach { subProject ->
+        dependsOn("${subProject.path}:jar")
+    }
+    dependsOn('generateDistLicense', 'generateDistNotice')
+    doLast {
+        // Core: the agent process jar + its third-party deps (sdk-java, common, cloudevents,
+        // jackson, log4j binding). The agent talks to the runtime only over HTTP + lite topics.
+        def core = findProject('eventmesh-agent')
+        logger.lifecycle('Install module: module: {}', core.name)
+        copy {
+            from core.jar.archivePath
+            into rootProject.file('dist-agent/apps')
+        }
+        copy {
+            from core.configurations.runtimeClasspath
+            into rootProject.file('dist-agent/lib')
+            exclude 'eventmesh-*'
+        }
+        copy {
+            from core.file('bin')
+            into rootProject.file('dist-agent/bin')
+        }
+        copy {
+            from core.file('conf')
+            from core.sourceSets.main.resources.srcDirs
+            into rootProject.file('dist-agent/conf')
+            duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+            exclude 'META-INF'
+        }
+        copy {
+            from 'tools/dist-license'
+            into rootProject.file('dist-agent')
+        }
+    }
+}
+
 tasks.register('tar', Tar) {
     archiveBaseName.set(project.name)
     archiveVersion.set(project.version.toString())
@@ -634,7 +681,10 @@ subprojects {
     }
 
     repositories {
-        mavenCentral()
+        maven {
+            allowInsecureProtocol = true
+            url "http://maven.weoa.com/nexus/content/groups/public/"
+        }
     }
 
     configurations.configureEach {
diff --git a/docs/a2a-protocol/ARCHITECTURE.md b/docs/a2a-protocol/ARCHITECTURE.md
deleted file mode 100644
index bcb485b10..000000000
--- a/docs/a2a-protocol/ARCHITECTURE.md
+++ /dev/null
@@ -1,316 +0,0 @@
-# EventMesh A2A Protocol Architecture & Functional Specification
-
-## 1. Overview
-
-The **EventMesh A2A (Agent-to-Agent) Protocol** is a specialized, high-performance protocol plugin designed to enable asynchronous communication, collaboration, and task coordination between autonomous agents.
-
-With the release of v2.0, A2A adopts the **MCP (Model Context Protocol)** architecture, transforming EventMesh into a robust **Agent Collaboration Bus**. It bridges the gap between synchronous LLM-based tool calls (JSON-RPC 2.0) and asynchronous Event-Driven Architectures (EDA), enabling scalable, distributed, and decoupled agent systems.
-
-## 2. Core Philosophy
-
-The architecture adheres to the principles outlined in the broader agent community (e.g., A2A Project, FIPA-ACL, and CloudEvents):
-
-1.  **JSON-RPC 2.0 as Lingua Franca**: Uses standard JSON-RPC for payload semantics, ensuring compatibility with modern LLM ecosystems (LangChain, AutoGen).
-2.  **Transport Agnostic**: Encapsulates all messages within **CloudEvents**, allowing transport over any EventMesh-supported protocol (HTTP, TCP, gRPC, Kafka).
-3.  **Async by Default**: Maps synchronous Request/Response patterns to asynchronous Event streams using correlation IDs.
-4.  **Native Pub/Sub Semantics**: Supports O(1) broadcast complexity, temporal decoupling (Late Join), and backpressure isolation, solving the scalability limits of traditional P2P webhook callbacks.
-
-### 2.1 Native Pub/Sub Semantics
-
-Traditional A2A implementations often rely on HTTP Webhooks (`POST /inbox`) for asynchronous callbacks. While functional, this **Point-to-Point (P2P)** model suffers from significant scaling issues:
-
-*   **Insufficient Fan-Out**: A publisher must send $N$ requests to reach $N$ subscribers, leading to $O(N)$ complexity.
-*   **Temporal Coupling**: Consumers must be online at the exact moment of publication.
-*   **Backpressure Propagation**: A slow subscriber can block the publisher.
-
-**EventMesh A2A** solves this by introducing **Native Pub/Sub** capabilities:
-
-```mermaid
-graph LR
-    Publisher[Publisher Agent] -->|1. Publish (Once)| Bus[EventMesh Bus]
-    
-    subgraph Fanout_Layer [EventMesh Fanout Layer]
-        Queue[Topic Queue]
-    end
-    
-    Bus --> Queue
-    
-    Queue -->|Push| Sub1[Subscriber 1]
-    Queue -->|Push| Sub2[Subscriber 2]
-    Queue -->|Push| Sub3[Subscriber 3]
-    
-    style Bus fill:#f9f,stroke:#333
-    style Fanout_Layer fill:#ccf,stroke:#333
-```
-
-## 3. Architecture Design
-
-### 3.1 System Context
-
-```mermaid
-graph TD
-    Client[Client Agent / LLM] -- "JSON-RPC Request" --> EM[EventMesh Runtime]
-    EM -- "CloudEvent (Request)" --> Server[Server Agent / Tool]
-    Server -- "CloudEvent (Response)" --> EM
-    EM -- "JSON-RPC Response" --> Client
-    
-    subgraph Runtime [EventMesh Runtime]
-        Plugin[A2A Protocol Plugin]
-    end
-    
-    style EM fill:#f9f,stroke:#333,stroke-width:4px
-    style Plugin fill:#ccf,stroke:#333,stroke-width:2px
-```
-
-### 3.2 Component Design (`eventmesh-protocol-a2a`)
-
-The core protocol logic resides in the `eventmesh-protocol-plugin` module.
-
-*   **`EnhancedA2AProtocolAdaptor`**: The central brain of the protocol.
-    *   **Intelligent Parsing**: Automatically detects message format (MCP vs. Raw CloudEvent).
-    *   **Protocol Delegation**: Delegates to `CloudEvents` or `HTTP` adaptors when necessary.
-    *   **Semantic Mapping**: Transforms JSON-RPC methods and IDs into CloudEvent attributes.
-*   **`A2AProtocolConstants`**: Defines standard operations like `task/get`, `message/sendStream`.
-*   **`JsonRpc*` Models**: Strictly typed POJOs for JSON-RPC 2.0 compliance.
-*   **`AgentCard` / `AgentSkill` / `AgentInterface`**: Agent capability discovery models.
-*   **`A2ATopicFactory`**: Topic naming and parsing utility for request/response/status topics.
-*   **`A2AClient`**: Java SDK for agent developers — AgentCard registration, task submission (sync/async), task status query, heartbeat, and transport-based request handling. Returns typed `TaskResult` objects.
-*   **`A2AMessageTransport`**: Transport-agnostic pub/sub interface (InMemory implementation for dev/testing).
-
-### 3.3 Gateway Runtime Architecture (`eventmesh-runtime`)
-
-The Gateway runtime provides a standalone Netty HTTP server bridging external clients to the A2A event bus.
-
-```mermaid
-graph TD
-    Client["Client / A2AClient SDK"] -- "HTTP REST" --> Server["A2AGatewayServer<br/>(Netty HTTP)"]
-    Server --> Handler["A2AGatewayHttpHandler"]
-    Handler --> GwService["A2AGatewayService"]
-    GwService --> Registry["TaskRegistry<br/>(state machine + TTL)"]
-    GwService --> Transport["InMemoryA2AMessageTransport"]
-    GwService --> PubSub["A2APublishSubscribeService<br/>(AgentCard discovery)"]
-    Transport -- "publish/subscribe" --> Agent["Target Agent"]
-    Agent -- "response event" --> Transport
-    Transport --> GwService
-
-    style Server fill:#f9f,stroke:#333,stroke-width:2px
-    style Registry fill:#cfc,stroke:#333
-    style Transport fill:#ccf,stroke:#333
-```
-
-#### Core Components
-
-| Component | Module | Responsibility |
-| :--- | :--- | :--- |
-| `A2AGatewayServer` | runtime | Standalone Netty HTTP server entry point. Pre-registers mock agents, wires all components. |
-| `A2AGatewayHttpHandler` | runtime | HTTP request router. Maps REST endpoints to service calls. Supports SSE streaming. |
-| `A2AGatewayService` | runtime | Core orchestration: task submission, response handling, status subscription, SSE push. |
-| `TaskRegistry` | runtime | In-memory task lifecycle state machine with TTL auto-cleanup. |
-| `A2APublishSubscribeService` | runtime | AgentCard registration, discovery, and heartbeat management. |
-| `InMemoryA2AMessageTransport` | runtime | In-memory pub/sub (replaceable by EventMesh broker). |
-| `A2ACardHttpHandler` | runtime | AgentCard CRUD REST endpoints (`/a2a/cards/*`). |
-| `A2AClient` | protocol-a2a | Java SDK for agent developers (HTTP + transport). |
-
-#### Task Lifecycle State Machine
-
-```
-SUBMITTED → WORKING → COMPLETED
-                    ↘ FAILED
-                    ↘ CANCELLED
-```
-
-*   **TTL Auto-Cleanup**: Terminal-state tasks are automatically removed after a configurable TTL (default: 5 minutes). A daemon thread runs cleanup every 60 seconds.
-*   **Race Condition Prevention**: `pendingTasks.put(taskId, future)` is called **before** `transport.publish()` to ensure the future is registered before any synchronous delivery could trigger `handleResponse()`.
-
-#### REST API
-
-| Method | Path | Description |
-| :--- | :--- | :--- |
-| `POST` | `/a2a/tasks?mode=sync` | Submit task synchronously (wait for result) |
-| `POST` | `/a2a/tasks?mode=async` | Submit task asynchronously (return taskId immediately) |
-| `GET` | `/a2a/tasks/{taskId}` | Get task status and result |
-| `DELETE` | `/a2a/tasks/{taskId}` | Cancel a task |
-| `GET` | `/a2a/tasks/{taskId}/wait` | Long-poll wait for task result |
-| `GET` | `/a2a/tasks/{taskId}/stream` | **SSE** stream of task status updates |
-| `GET` | `/a2a/agents` | List all registered agents |
-| `POST` | `/a2a/heartbeat` | Agent heartbeat |
-| `GET` | `/a2a/cards/list` | List all AgentCards |
-| `POST` | `/a2a/cards/card/{org}/{unit}/{agent}` | Register an AgentCard |
-
-### 3.4 Asynchronous RPC Mapping ( The "Async Bridge" )
-
-To support MCP on an Event Bus, synchronous RPC concepts are mapped to asynchronous events:
-
-| Concept | MCP / JSON-RPC | CloudEvent Mapping |
-| :--- | :--- | :--- |
-| **Action** | `method` (e.g., `tools/call`) | **Type**: `org.apache.eventmesh.a2a.tools.call.req`<br>**Extension**: `a2amethod` |
-| **Correlation** | `id` (e.g., `req-123`) | **Extension**: `collaborationid` (on Response)<br>**ID**: Preserved on Request |
-| **Direction** | Implicit (Request vs Result) | **Extension**: `mcptype` (`request` or `response`) |
-| **P2P Routing** | `params._agentId` | **Extension**: `targetagent` |
-| **Pub/Sub Topic** | `params._topic` | **Subject**: The topic value (e.g. `market.btc`) |
-| **Streaming Seq** | `params._seq` | **Extension**: `seq` |
-
-## 4. Functional Specification
-
-### 4.1 Message Processing Flow
-
-1.  **Ingestion**: The adaptor receives a `ProtocolTransportObject` (byte array/string).
-2.  **Detection**: Checks for `jsonrpc: "2.0"`.
-3.  **Transformation (MCP Mode)**:
-    *   **Request**: Parses `method`.
-        *   If `message/sendStream`, sets type suffix to `.stream` and extracts `_seq`.
-        *   If `_topic` present, sets `subject` (Pub/Sub).
-        *   If `_agentId` present, sets `targetagent` (P2P).
-    *   **Response**: Parses `result`/`error`. Sets `collaborationid` = `id`.
-4.  **Batch Processing**: Splits JSON Array into a `List<CloudEvent>`.
-
-### 4.2 Key Features
-
-#### A. Intelligent Routing Support
-*   **Mechanism**: Promotes `_agentId` or `_topic` from JSON body to CloudEvent attributes.
-*   **Benefit**: Enables EventMesh Router to perform content-based routing (CBR) efficiently.
-
-#### B. Batching
-*   **Benefit**: Significantly increases throughput for high-frequency interactions.
-
-#### C. Streaming Support
-*   **Operation**: `message/sendStream`
-*   **Mechanism**: Maps to `.stream` event type and preserves sequence order via `seq` extension attribute.
-
-#### D. SSE Task Streaming (Gateway)
-*   **Endpoint**: `GET /a2a/tasks/{taskId}/stream`
-*   **Mechanism**: Server-Sent Events (`text/event-stream`) pushes real-time task state transitions.
-*   **Flow**: Initial state → WORKING updates → terminal state → connection close.
-*   **Implementation**: Handler writes `DefaultHttpContent` chunks directly to the Netty channel, returning `null` to skip the standard `FullHttpResponse` path.
-
-#### E. Task TTL Auto-Cleanup (Gateway)
-*   Terminal-state tasks are automatically removed by a daemon scheduler after a configurable TTL (default: 5 minutes, cleanup interval: 60 seconds), preventing memory leaks.
-
-#### F. AgentCard Discovery & Heartbeat (Gateway)
-*   AgentCards expire after 60 seconds without heartbeat. `POST /a2a/heartbeat` refreshes the last-seen timestamp.
-
-## 5. Usage Examples
-
-### 5.1 Sending a Tool Call (Request)
-
-**Raw Payload:**
-```json
-{
-  "jsonrpc": "2.0",
-  "method": "tools/call",
-  "params": {
-    "name": "weather_service",
-    "arguments": { "city": "New York" }
-  },
-  "id": "msg-101"
-}
-```
-
-### 5.2 Pub/Sub Broadcast
-
-**Raw Payload:**
-```json
-{
-  "jsonrpc": "2.0",
-  "method": "market/update",
-  "params": {
-    "symbol": "BTC",
-    "price": 50000,
-    "_topic": "market.crypto.btc"
-  }
-}
-```
-
-**Generated CloudEvent:**
-*   `subject`: `market.crypto.btc`
-*   `targetagent`: (Empty)
-
-### 5.3 Gateway REST API (HTTP)
-
-The A2A Gateway provides a REST API for external clients and non-Java agents.
-
-#### 5.3.1 Submit Task (Sync)
-
-```bash
-curl -X POST 'http://localhost:10105/a2a/tasks?mode=sync' \
-  -H 'Content-Type: application/json' \
-  -d '{"targetAgent":"weather-agent","message":"Beijing"}'
-```
-
-Response:
-```json
-{
-  "taskId": "task-a1b2c3d4",
-  "state": "COMPLETED",
-  "data": "The weather in Beijing is sunny, 25°C"
-}
-```
-
-#### 5.3.2 Submit Task (Async)
-
-```bash
-curl -X POST 'http://localhost:10105/a2a/tasks?mode=async' \
-  -H 'Content-Type: application/json' \
-  -d '{"targetAgent":"weather-agent","message":"Shanghai"}'
-```
-
-#### 5.3.3 SSE Stream
-
-```bash
-curl -N http://localhost:10105/a2a/tasks/{taskId}/stream
-```
-
-Response (`text/event-stream`):
-```
-data: {"taskId":"task-a1b2c3d4","state":"SUBMITTED"}
-
-data: {"taskId":"task-a1b2c3d4","state":"WORKING","data":"processing..."}
-
-data: {"taskId":"task-a1b2c3d4","state":"completed","data":"result..."}
-```
-
-#### 5.3.4 List Agents
-
-```bash
-curl http://localhost:10105/a2a/agents
-```
-
-### 5.4 A2AClient SDK (Java)
-
-```java
-A2AClient client = A2AClient.builder()
-    .gatewayUrl("http://localhost:10105")
-    .namespace("global")
-    .agentName("my-agent")
-    .agentCard(card)
-    .heartbeatInterval(30_000)
-    .build();
-
-client.start();
-
-// Synchronous task (returns typed TaskResult)
-TaskResult result = client.sendTaskSync("weather-agent", "Beijing", null);
-
-// Asynchronous task (returns taskId immediately)
-String taskId = client.sendTaskAsync("weather-agent", "Shanghai", null);
-
-// Poll status
-TaskResult status = client.getTaskStatus(taskId);
-
-// Cancel
-boolean cancelled = client.cancelTask(taskId);
-
-// List registered agents (typed List<String>)
-List<String> agents = client.listAgents();
-
-client.shutdown();
-```
-
-## 6. Future Roadmap
-
-*   **EventMesh Broker Integration**: Replace `InMemoryA2AMessageTransport` with the real EventMesh broker for production deployment.
-*   **Schema Registry**: Implement dynamic discovery of Agent capabilities via `methods/list`.
-*   **Sidecar Injection**: Fully integrate the adaptor into the EventMesh Sidecar.
-*   **WebSocket Streaming**: Extend SSE to bidirectional WebSocket for real-time agent dialogue.
-*   **Task Persistence**: Persist `TaskRegistry` state to a durable store for crash recovery.
-*   **Authentication**: Add API key / JWT authentication to the Gateway REST API.
\ No newline at end of file
diff --git a/docs/a2a-protocol/IMPLEMENTATION_SUMMARY.md b/docs/a2a-protocol/IMPLEMENTATION_SUMMARY.md
deleted file mode 100644
index fd5081c61..000000000
--- a/docs/a2a-protocol/IMPLEMENTATION_SUMMARY.md
+++ /dev/null
@@ -1,103 +0,0 @@
-# 实现总结:EventMesh A2A 协议 v2.0 (MCP 版)
-
-## 核心成果
-
-A2A 协议已成功重构为采用 **MCP (Model Context Protocol)** 架构,将 EventMesh 定位为现代化的 **智能体协作总线 (Agent Collaboration Bus)**。
-
-### 1. 核心协议重构 (`EnhancedA2AProtocolAdaptor`)
-- **混合引擎 (JSON-RPC & CloudEvents)**: 实现了智能解析引擎,支持:
-    - **MCP/JSON-RPC 2.0**: 面向 LLM 和脚本的低门槛接入,自动封装 CloudEvent。
-    - **原生 CloudEvents**: 面向 EventMesh 原生应用的灵活接入,支持自定义元数据和透传。
-    - 适配器根据 `jsonrpc` 字段自动分发处理逻辑。
-- **异步 RPC 映射**: 建立了同步 RPC 语义与异步事件驱动架构 (EDA) 之间的桥梁。
-    - **请求 (Requests)** 映射为 `*.req` 事件,属性 `mcptype=request`。
-    - **响应 (Responses)** 映射为 `*.resp` 事件,属性 `mcptype=response`。
-    - **关联 (Correlation)** 通过将 JSON-RPC `id` 映射到 CloudEvent `collaborationid` 来处理。
-- **路由优化**: 实现了"深度内容路由提取":
-    - `params._agentId` -> CloudEvent 扩展属性 `targetagent` (P2P)。
-    - `params._topic` -> CloudEvent Subject (Pub/Sub)。
-
-### 2. 原生 Pub/Sub 与流式支持
-- **Pub/Sub**: 通过将 `_topic` 映射到 CloudEvent Subject,支持 O(1) 广播复杂度。
-- **流式 (Streaming)**: 支持 `message/sendStream` 操作,映射为 `.stream` 事件类型,并通过 `_seq` -> `seq` 扩展属性保证顺序。
-
-### 3. 标准化与兼容性
-- **数据模型**: 定义了符合 JSON-RPC 2.0 规范的 `JsonRpcRequest`、`JsonRpcResponse`、`JsonRpcError` POJO 对象。
-- **方法定义**: 引入了 `McpMethods` 常量,支持标准操作如 `tools/call`、`resources/read`。
-- **AgentCard 模型**: 实现了 `AgentCard`、`AgentSkill`、`AgentInterface`、`AgentCapabilities` 等完整的 Agent 能力描述模型。
-
-### 4. Gateway 运行时架构 (`eventmesh-runtime`)
-
-完整的独立 HTTP Gateway 服务,桥接外部客户端到 A2A 事件总线。
-
-#### 核心组件
-
-| 组件 | 职责 |
-| :--- | :--- |
-| `A2AGatewayServer` | Netty HTTP 服务器入口,预注册 mock agent,组装所有组件 |
-| `A2AGatewayHttpHandler` | HTTP 请求路由,支持 SSE 流式响应 |
-| `A2AGatewayService` | 核心编排:任务提交、响应处理、状态订阅、SSE 推送 |
-| `TaskRegistry` | 内存任务状态机 + TTL 自动清理 |
-| `A2APublishSubscribeService` | AgentCard 注册、发现、心跳管理 |
-| `InMemoryA2AMessageTransport` | 内存 pub/sub 实现(可替换为 EventMesh broker) |
-| `A2ACardHttpHandler` | AgentCard CRUD REST 端点 |
-| `A2AClient` | Java SDK,提供类型化 API |
-
-#### REST API
-
-| 方法 | 路径 | 说明 |
-| :--- | :--- | :--- |
-| `POST` | `/a2a/tasks?mode=sync` | 同步提交任务 |
-| `POST` | `/a2a/tasks?mode=async` | 异步提交任务 |
-| `GET` | `/a2a/tasks/{taskId}` | 查询任务状态 |
-| `DELETE` | `/a2a/tasks/{taskId}` | 取消任务 |
-| `GET` | `/a2a/tasks/{taskId}/wait` | 长轮询等待结果 |
-| `GET` | `/a2a/tasks/{taskId}/stream` | SSE 流式推送状态更新 |
-| `GET` | `/a2a/agents` | 列出已注册 agents |
-| `POST` | `/a2a/heartbeat` | Agent 心跳 |
-| `GET` | `/a2a/cards/list` | 列出所有 AgentCard |
-| `POST` | `/a2a/cards/card/{org}/{unit}/{agent}` | 注册 AgentCard |
-
-### 5. 关键改进
-
-#### 5.1 TaskRegistry TTL 自动清理
-- **问题**: 终态任务(COMPLETED/FAILED/CANCELLED)无限累积导致内存泄漏。
-- **方案**: 守护线程 `ScheduledExecutorService` 每 60 秒扫描一次,清理超过 TTL(默认 5 分钟)的终态任务。
-- **配置**: `TaskRegistry(taskTtlMs, cleanupIntervalMs)` 构造函数支持自定义调优。
-
-#### 5.2 竞态条件修复
-- **问题**: `InMemoryTransport` 同步投递消息,若 `transport.publish()` 在 `pendingTasks.put()` 之前执行,`handleResponse()` 会先于 `put()` 运行,导致 future 永不完成。
-- **方案**: 严格保证 `pendingTasks.put(taskId, future)` 在 `transport.publish()` 之前执行,并添加注释说明顺序重要性。
-
-#### 5.3 A2AClient 类型化返回
-- **改进**: `getTaskStatus()` 返回 `TaskResult` 对象(而非原始 JSON 字符串),`listAgents()` 返回 `List<String>`(而非原始 JSON)。
-- **兼容**: `TaskResult.data` 字段使用 `@JsonAlias("result")` 注解,兼容服务端 `result` 字段名。
-
-#### 5.4 SSE 流式响应
-- **端点**: `GET /a2a/tasks/{taskId}/stream`
-- **实现**: Handler 直接写入 Netty channel(`DefaultHttpContent` chunks),返回 `null` 跳过标准 `FullHttpResponse` 路径。通过 `StatusSubscriber` 回调实时推送状态变更。
-
-#### 5.5 使用文档
-- 新建 `eventmesh-examples/.../demo/README.md`,包含架构图、API 表、curl 示例、SDK 用法、运行方式。
-
-### 6. 测试与质量
-- **协议层单元测试**: `EnhancedA2AProtocolAdaptorTest` 覆盖请求/响应循环、错误处理、通知和批处理。
-- **Topic 工具测试**: `A2ATopicFactoryTest` 覆盖 topic 生成与解析。
-- **Gateway 运行时测试**:
-    - `TaskRegistryTest` — 任务状态机 + TTL 清理验证
-    - `InMemoryA2AMessageTransportTest` — 内存传输投递
-    - `A2AGatewayServiceTest` — Gateway 服务层
-    - `A2AGatewayEndToEndTest` — 进程内全链路
-    - `A2AClientServerIntegrationTest` — 真实 HTTP 客户端-服务端集成测试
-- **集成演示**: `McpIntegrationDemoTest`、`McpPatternsIntegrationTest`、`McpComprehensiveDemoTest`、`CloudEventsComprehensiveDemoTest`
-- **总计**: 73 个测试场景,全部通过。
-
-## 下一步计划
-
-1. **EventMesh Broker 集成**: 用真实 EventMesh broker 替换 `InMemoryA2AMessageTransport`,实现生产级部署。
-2. **路由集成**: 更新 EventMesh Runtime Router,利用 `targetagent` 和 `a2amethod` 扩展属性实现高级路由规则。
-3. **Schema 注册中心**: 实现"注册中心智能体 (Registry Agent)",允许智能体动态发布 MCP 能力 (`methods/list`)。
-4. **Sidecar 支持**: 将 A2A 适配器逻辑暴露在 Sidecar 代理中,允许非 Java 智能体通过 HTTP/JSON 交互。
-5. **WebSocket 流式**: 将 SSE 扩展为双向 WebSocket,支持实时 agent 对话。
-6. **任务持久化**: 将 `TaskRegistry` 状态持久化到 Redis/DB,支持崩溃恢复。
-7. **认证授权**: 为 Gateway REST API 添加 API Key / JWT 认证。
diff --git a/docs/a2a-protocol/IMPLEMENTATION_SUMMARY_EN.md b/docs/a2a-protocol/IMPLEMENTATION_SUMMARY_EN.md
deleted file mode 100644
index aec8aef94..000000000
--- a/docs/a2a-protocol/IMPLEMENTATION_SUMMARY_EN.md
+++ /dev/null
@@ -1,103 +0,0 @@
-# Implementation Summary: EventMesh A2A Protocol v2.0 (MCP Edition)
-
-## Key Achievements
-
-The A2A protocol has been successfully refactored to adopt the **MCP (Model Context Protocol)** architecture, positioning EventMesh as a modern **Agent Collaboration Bus**.
-
-### 1. Core Protocol Refactoring (`EnhancedA2AProtocolAdaptor`)
-- **Hybrid Engine (JSON-RPC & CloudEvents)**: Implemented a smart parsing engine that supports:
-    - **MCP/JSON-RPC 2.0**: For LLM-friendly, low-code integration.
-    - **Native CloudEvents**: For advanced, protocol-compliant integration.
-    - The adaptor automatically delegates processing based on the payload content (`jsonrpc` detection).
-- **Async RPC Mapping**: Established a bridge between synchronous RPC semantics and asynchronous Event-Driven Architecture (EDA).
-    - **Requests** map to `*.req` events with `mcptype=request`.
-    - **Responses** map to `*.resp` events with `mcptype=response`.
-    - **Correlation** is handled by mapping JSON-RPC `id` to CloudEvent `collaborationid`.
-- **Routing Optimization**: Implemented "Deep Body Routing" extraction:
-    - `params._agentId` -> CloudEvent Extension `targetagent` (P2P).
-    - `params._topic` -> CloudEvent Subject (Pub/Sub).
-
-### 2. Native Pub/Sub & Streaming
-- **Pub/Sub**: Added support for O(1) broadcast complexity by mapping `_topic` to CloudEvent Subject.
-- **Streaming**: Added support for `message/sendStream` operation, mapping to `.stream` event type and preserving sequence via `_seq` -> `seq` extension.
-
-### 3. Standardization & Compatibility
-- **Models**: Defined `JsonRpcRequest`, `JsonRpcResponse`, `JsonRpcError` POJOs compliant with JSON-RPC 2.0 spec.
-- **Methods**: Introduced `McpMethods` constants for standard operations like `tools/call`, `resources/read`.
-- **AgentCard Models**: Implemented `AgentCard`, `AgentSkill`, `AgentInterface`, `AgentCapabilities` for complete agent capability description.
-
-### 4. Gateway Runtime Architecture (`eventmesh-runtime`)
-
-A standalone HTTP Gateway service bridging external clients to the A2A event bus.
-
-#### Core Components
-
-| Component | Responsibility |
-| :--- | :--- |
-| `A2AGatewayServer` | Netty HTTP server entry point, pre-registers mock agents, wires all components |
-| `A2AGatewayHttpHandler` | HTTP request router, supports SSE streaming responses |
-| `A2AGatewayService` | Core orchestration: task submission, response handling, status subscription, SSE push |
-| `TaskRegistry` | In-memory task state machine + TTL auto-cleanup |
-| `A2APublishSubscribeService` | AgentCard registration, discovery, heartbeat management |
-| `InMemoryA2AMessageTransport` | In-memory pub/sub (replaceable by EventMesh broker) |
-| `A2ACardHttpHandler` | AgentCard CRUD REST endpoints |
-| `A2AClient` | Java SDK with typed API |
-
-#### REST API
-
-| Method | Path | Description |
-| :--- | :--- | :--- |
-| `POST` | `/a2a/tasks?mode=sync` | Submit task synchronously |
-| `POST` | `/a2a/tasks?mode=async` | Submit task asynchronously |
-| `GET` | `/a2a/tasks/{taskId}` | Get task status |
-| `DELETE` | `/a2a/tasks/{taskId}` | Cancel a task |
-| `GET` | `/a2a/tasks/{taskId}/wait` | Long-poll wait for result |
-| `GET` | `/a2a/tasks/{taskId}/stream` | SSE stream of task status updates |
-| `GET` | `/a2a/agents` | List registered agents |
-| `POST` | `/a2a/heartbeat` | Agent heartbeat |
-| `GET` | `/a2a/cards/list` | List all AgentCards |
-| `POST` | `/a2a/cards/card/{org}/{unit}/{agent}` | Register an AgentCard |
-
-### 5. Key Improvements
-
-#### 5.1 TaskRegistry TTL Auto-Cleanup
-- **Problem**: Terminal-state tasks (COMPLETED/FAILED/CANCELLED) accumulate indefinitely, causing memory leaks.
-- **Solution**: A daemon `ScheduledExecutorService` runs every 60 seconds, removing terminal-state tasks older than the TTL (default: 5 minutes).
-- **Configuration**: `TaskRegistry(taskTtlMs, cleanupIntervalMs)` constructor allows custom tuning.
-
-#### 5.2 Race Condition Fix
-- **Problem**: `InMemoryTransport` delivers messages synchronously. If `transport.publish()` executes before `pendingTasks.put()`, `handleResponse()` runs before `put()` and the future never completes.
-- **Solution**: Ensure `pendingTasks.put(taskId, future)` is called **before** `transport.publish()`, with comments documenting the ordering importance.
-
-#### 5.3 A2AClient Typed Returns
-- **Improvement**: `getTaskStatus()` returns `TaskResult` object (instead of raw JSON string), `listAgents()` returns `List<String>` (instead of raw JSON).
-- **Compatibility**: `TaskResult.data` field uses `@JsonAlias("result")` annotation to handle the server's `result` field name.
-
-#### 5.4 SSE Streaming Response
-- **Endpoint**: `GET /a2a/tasks/{taskId}/stream`
-- **Implementation**: Handler writes `DefaultHttpContent` chunks directly to the Netty channel, returning `null` to skip the standard `FullHttpResponse` path. Uses `StatusSubscriber` callbacks for real-time state push.
-
-#### 5.5 Usage Documentation
-- Created `eventmesh-examples/.../demo/README.md` with architecture diagram, API table, curl examples, SDK usage, and run instructions.
-
-### 6. Testing & Quality
-- **Protocol Unit Tests**: `EnhancedA2AProtocolAdaptorTest` covers Request/Response cycles, Error handling, Notifications, and Batching.
-- **Topic Utility Tests**: `A2ATopicFactoryTest` covers topic generation and parsing.
-- **Gateway Runtime Tests**:
-    - `TaskRegistryTest` — Task state machine + TTL cleanup verification
-    - `InMemoryA2AMessageTransportTest` — In-memory transport delivery
-    - `A2AGatewayServiceTest` — Gateway service layer
-    - `A2AGatewayEndToEndTest` — In-process end-to-end flow
-    - `A2AClientServerIntegrationTest` — Real HTTP client-server integration test
-- **Integration Demos**: `McpIntegrationDemoTest`, `McpPatternsIntegrationTest`, `McpComprehensiveDemoTest`, `CloudEventsComprehensiveDemoTest`
-- **Total**: 73 test scenarios, all passing.
-
-## Next Steps
-
-1. **EventMesh Broker Integration**: Replace `InMemoryA2AMessageTransport` with the real EventMesh broker for production deployment.
-2. **Router Integration**: Update EventMesh Runtime Router to leverage `targetagent` and `a2amethod` extension attributes for advanced routing rules.
-3. **Schema Registry**: Implement a "Registry Agent" that allows agents to publish their MCP capabilities (`methods/list`) dynamically.
-4. **Sidecar Support**: Expose the A2A adaptor logic in the Sidecar proxy to allow non-Java agents (Python, Node.js) to interact via simple HTTP/JSON.
-5. **WebSocket Streaming**: Extend SSE to bidirectional WebSocket for real-time agent dialogue.
-6. **Task Persistence**: Persist `TaskRegistry` state to a durable store (Redis/DB) for crash recovery.
-7. **Authentication**: Add API key / JWT authentication to the Gateway REST API.
diff --git a/docs/a2a-protocol/README.md b/docs/a2a-protocol/README.md
index 73247de79..6072b7252 100644
--- a/docs/a2a-protocol/README.md
+++ b/docs/a2a-protocol/README.md
@@ -1,156 +1,716 @@
-# EventMesh A2A (Agent-to-Agent Communication Protocol)
+# EventMesh A2A 协议(Agent-to-Agent Communication Protocol)
 
-## 概述
+> 合并自 ARCHITECTURE.md + eventmesh-a2a-design.md + README.md + README_EN.md + IMPLEMENTATION_SUMMARY.md + IMPLEMENTATION_SUMMARY_EN.md + TEST_RESULTS.md(2026-08-13)。
 
-A2A (Agent-to-Agent Communication Protocol) 是 EventMesh 的一个高性能协议插件,专门设计用于支持智能体(Agents)之间的异步通信、协作和任务协调。该协议采用 **MCP (Model Context Protocol)** 架构理念,结合 EventMesh 的事件驱动特性,打造了一个**异步、解耦、高性能的智能体协作总线**。
+## 目录
 
-A2A 协议不仅支持传统的 FIPA-ACL 风格语义,更全面拥抱现代大模型(LLM)生态,通过支持 **JSON-RPC 2.0** 标准,实现了对工具调用(Tool Use)、上下文共享(Context Sharing)等 LLM 核心场景的原生支持。
+- [1. Overview](#1-overview)
+- [2. Core Philosophy](#2-core-philosophy)
+- [3. Architecture](#3-architecture)
+- [4. Protocol Specification](#4-protocol-specification)
+- [5. Implementation Summary](#实现总结eventmesh-a2a-协议-v20-mcp-版)
+- [6. Test Results](#test-results-eventmesh-a2a-protocol-v20)
+- [7. 中文使用指南](#中文使用指南整合自-readmemd)
 
-## 核心特性
+---
 
-### 1. MCP over CloudEvents
-- **标准兼容**: 完全支持 **MCP (Model Context Protocol)** 定义的 `tools/call`, `resources/read` 等标准方法。
-- **事件驱动**: 将同步的 RPC 调用映射为异步的 **Request/Response 事件流**,充分利用 EventMesh 的高并发处理能力。
-- **协议无关**: 所有的 MCP 消息都被封装在 **CloudEvents** 标准信封中,可以在 HTTP, TCP, gRPC, Kafka 等任意 EventMesh 支持的传输层上运行。
+# EventMesh A2A Protocol Architecture & Functional Specification
 
-### 2. 混合架构设计 (Hybrid Architecture)
-- **双模支持**: 
-    - **Modern Mode**: 支持 MCP 标准的 JSON-RPC 2.0 消息,面向 LLM 应用。
-    - **Legacy Mode**: 兼容旧版 A2A 协议(基于 `messageType` 和 FIPA 动词),保障存量业务平滑迁移。
-- **自动识别**: 协议适配器根据消息内容特征(如 `jsonrpc` 字段)自动智能选择处理模式。
+## 1. Overview
 
-### 3. 高性能与路由
-- **批量处理**: 原生支持 JSON-RPC Batch 请求,EventMesh 会将其自动拆分为并行事件流,极大提升吞吐量。
-- **智能路由**: 支持从 MCP 请求参数(如 `_agentId`)中提取路由线索,自动注入 CloudEvents 扩展属性 (`targetagent`),实现零解包路由。
+The **EventMesh A2A (Agent-to-Agent) Protocol** is a specialized, high-performance protocol plugin designed to enable asynchronous communication, collaboration, and task coordination between autonomous agents.
 
-### 4. CloudEvents 集成
-- **类型映射**: 自动将 MCP 方法映射为 CloudEvent Type (例如 `tools/call` -> `org.apache.eventmesh.a2a.tools.call.req`)。
-- **上下文传递**: 利用 CloudEvents Extension 传递 `traceparent`,实现跨 Agent 的全链路追踪。
+With the release of v2.0, A2A adopts the **MCP (Model Context Protocol)** architecture, transforming EventMesh into a robust **Agent Collaboration Bus**. It bridges the gap between synchronous LLM-based tool calls (JSON-RPC 2.0) and asynchronous Event-Driven Architectures (EDA), enabling scalable, distributed, and decoupled agent systems.
 
-## 架构设计
+## 2. Core Philosophy
 
-### 核心组件
+The architecture adheres to the principles outlined in the broader agent community (e.g., A2A Project, FIPA-ACL, and CloudEvents):
 
+1.  **JSON-RPC 2.0 as Lingua Franca**: Uses standard JSON-RPC for payload semantics, ensuring compatibility with modern LLM ecosystems (LangChain, AutoGen).
+2.  **Transport Agnostic**: Encapsulates all messages within **CloudEvents**, allowing transport over any EventMesh-supported protocol (HTTP, TCP, gRPC, Kafka).
+3.  **Async by Default**: Maps synchronous Request/Response patterns to asynchronous Event streams using correlation IDs.
+4.  **Native Pub/Sub Semantics**: Supports O(1) broadcast complexity, temporal decoupling (Late Join), and backpressure isolation, solving the scalability limits of traditional P2P webhook callbacks.
+
+### 2.1 Native Pub/Sub Semantics
+
+Traditional A2A implementations often rely on HTTP Webhooks (`POST /inbox`) for asynchronous callbacks. While functional, this **Point-to-Point (P2P)** model suffers from significant scaling issues:
+
+*   **Insufficient Fan-Out**: A publisher must send $N$ requests to reach $N$ subscribers, leading to $O(N)$ complexity.
+*   **Temporal Coupling**: Consumers must be online at the exact moment of publication.
+*   **Backpressure Propagation**: A slow subscriber can block the publisher.
+
+**EventMesh A2A** solves this by introducing **Native Pub/Sub** capabilities:
+
+```mermaid
+flowchart LR
+    Publisher["Publisher Agent"] -->|1. Publish (Once)| Bus["EventMesh Bus"]
+    
+    subgraph FanoutLayer ["EventMesh Fanout Layer"]
+        Queue["Topic Queue"]
+    end
+    
+    Bus --> Queue
+    
+    Queue -->|"Push"| Sub1["Subscriber 1"]
+    Queue -->|"Push"| Sub2["Subscriber 2"]
+    Queue -->|"Push"| Sub3["Subscriber 3"]
+    
+    style Bus fill:#f9f,stroke:#333
+    style FanoutLayer fill:#ccf,stroke:#333
 ```
-┌─────────────────────────────────────────────────────────────┐
-│                EventMesh A2A Protocol v2.0                │
-│              (MCP over CloudEvents Architecture)            │
-├─────────────────────────────────────────────────────────────┤
-│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
-│  │ MCP/JSON-RPC│  │ Legacy A2A  │  │  Protocol   │         │
-│  │   Handler   │  │   Handler   │  │  Delegator  │         │
-│  └─────────────┘  └─────────────┘  └─────────────┘         │
-├─────────────────────────────────────────────────────────────┤
-│  ┌───────────────────────────────────────────────────────┐  │
-│  │           Enhanced A2A Protocol Adaptor               │  │
-│  │      (Intelligent Parsing & CloudEvent Mapping)       │  │
-│  └───────────────────────────────────────────────────────┘  │
-├─────────────────────────────────────────────────────────────┤
-│              EventMesh Protocol Infrastructure             │
-│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
-│  │ CloudEvents │  │    HTTP     │  │    gRPC     │         │
-│  │  Protocol   │  │  Protocol   │  │  Protocol   │         │
-│  └─────────────┘  └─────────────┘  └─────────────┘         │
-└─────────────────────────────────────────────────────────────┘
+
+### 2.1 Hybrid Protocol Support (JSON-RPC & CloudEvents)
+
+A2A Protocol introduces a unique **Hybrid Architecture** that bridges the gap between the AI ecosystem (which prefers simple JSON) and the Cloud Native ecosystem (which prefers structured CloudEvents).
+
+| Feature | JSON-RPC 2.0 Mode | Native CloudEvents Mode |
+| :--- | :--- | :--- |
+| **Primary Audience** | LLMs, Scripts (Python/JS), LangChain | EventMesh Apps, Knative, Java SDK |
+| **Philosophy** | **"Battery Included"** | **"Power User"** |
+| **Usage** | Send raw JSON (`{"method":...}`) | Send `CloudEvent` object |
+| **Complexity** | Low (No SDK required) | Medium (Requires CE SDK) |
+| **Mechanism** | Adaptor automatically wraps JSON in CE | Adaptor passes through the event |
+
+**Benefits:**
+*   **Zero-Barrier Entry**: Developers can interact with the mesh using just `curl` or simple JSON libraries.
+*   **Full Flexibility**: Advanced users retain full control over CloudEvent attributes (Source, Type, Extensions) for complex routing or tracing scenarios.
+
+## 3. Architecture Design
+
+### 3.1 System Context
+
+```mermaid
+graph TD
+    Client["Client Agent / LLM"] -- "JSON-RPC Request" --> EM["EventMesh Runtime"]
+    EM -- "CloudEvent (Request)" --> Server["Server Agent / Tool"]
+    Server -- "CloudEvent (Response)" --> EM
+    EM -- "JSON-RPC Response" --> Client
+    
+    subgraph Runtime ["EventMesh Runtime"]
+        Plugin["A2A Protocol Plugin"]
+    end
+    
+    style EM fill:#f9f,stroke:#333,stroke-width:4px
+    style Plugin fill:#ccf,stroke:#333,stroke-width:2px
+```
+
+### 3.2 Component Design (`eventmesh-protocol-a2a`)
+
+The core protocol logic resides in the `eventmesh-protocol-plugin` module.
+
+*   **`EnhancedA2AProtocolAdaptor`**: The central brain of the protocol.
+    *   **Intelligent Parsing**: Automatically detects message format (MCP vs. Raw CloudEvent).
+    *   **Protocol Delegation**: Delegates to `CloudEvents` or `HTTP` adaptors when necessary.
+    *   **Semantic Mapping**: Transforms JSON-RPC methods and IDs into CloudEvent attributes.
+*   **`A2AProtocolConstants`**: Defines standard operations like `task/get`, `message/sendStream`.
+*   **`JsonRpc*` Models**: Strictly typed POJOs for JSON-RPC 2.0 compliance.
+*   **`AgentCard` / `AgentSkill` / `AgentInterface`**: Agent capability discovery models.
+*   **`A2ATopicFactory`**: Topic naming and parsing utility (request/response/status topics).
+*   **`A2AClient`**: Java SDK for agent developers — AgentCard registration, task submission (sync/async), task status query, heartbeat, and transport-based request handling. Returns typed `TaskResult` objects.
+*   **`A2AMessageTransport`**: Transport-agnostic pub/sub interface (InMemory implementation for dev/testing).
+
+### 3.3 Gateway Runtime Architecture (`eventmesh-runtime`)
+
+The Gateway runtime provides a standalone HTTP server that bridges external clients to the A2A event bus.
+
+```mermaid
+graph TD
+    Client["Client / A2AClient SDK"] -- "HTTP REST" --> Server["A2AGatewayServer<br/>(Netty HTTP)"]
+    Server --> Handler["A2AGatewayHttpHandler"]
+    Handler --> GwService["A2AGatewayService"]
+    GwService --> Registry["TaskRegistry<br/>(state machine + TTL)"]
+    GwService --> Transport["InMemoryA2AMessageTransport"]
+    GwService --> PubSub["A2APublishSubscribeService<br/>(AgentCard discovery)"]
+    Transport -- "publish/subscribe" --> Agent["Target Agent"]
+    Agent -- "response event" --> Transport
+    Transport --> GwService
+
+    style Server fill:#f9f,stroke:#333,stroke-width:2px
+    style Registry fill:#cfc,stroke:#333
+    style Transport fill:#ccf,stroke:#333
 ```
 
-### Gateway 运行时架构
+#### Core Components
+
+| Component | Module | Responsibility |
+| :--- | :--- | :--- |
+| `A2AGatewayServer` | runtime | Standalone Netty HTTP server entry point. Pre-registers mock agents, wires all components. |
+| `A2AGatewayHttpHandler` | runtime | HTTP request router. Maps REST endpoints to service calls. Supports SSE streaming. |
+| `A2AGatewayService` | runtime | Core orchestration: task submission, response handling, status subscription, SSE push. |
+| `TaskRegistry` | runtime | In-memory task lifecycle state machine with TTL auto-cleanup. |
+| `A2APublishSubscribeService` | runtime | AgentCard registration, discovery, and heartbeat management. |
+| `InMemoryA2AMessageTransport` | runtime | In-memory pub/sub implementation (replaceable by EventMesh broker). |
+| `A2ACardHttpHandler` | runtime | AgentCard CRUD REST endpoints (`/a2a/cards/*`). |
+| `A2AClient` | protocol-a2a | Java SDK for agent developers (HTTP + transport). |
+
+#### Task Lifecycle State Machine
 
 ```
-  protocol-a2a 模块              runtime 模块                    examples 模块
-  ┌─────────────────┐           ┌──────────────────┐           ┌──────────────┐
-  │ A2AMessageTransport(接口)    │ A2AGatewayServer  │           │ A2AGatewayDemo│
-  │ A2AClient (SDK)  │<──HTTP──>│ (main, Netty HTTP)│<──HTTP──>│ (纯客户端)    │
-  │ AgentCard/Topic  │           │ InMemoryTransport │           └──────────────┘
-  └─────────────────┘           │ GatewayService    │
-                                │ TaskRegistry(TTL) │
-                                └──────────────────┘
+SUBMITTED → WORKING → COMPLETED
+                    ↘ FAILED
+                    ↘ CANCELLED
 ```
 
-#### 核心运行时组件
+*   **TaskRegistry TTL Cleanup**: Terminal-state tasks (COMPLETED/FAILED/CANCELLED) are automatically removed after a configurable TTL (default: 5 minutes). A daemon `ScheduledExecutorService` runs cleanup every 60 seconds, preventing memory leaks from accumulated historical tasks.
+*   **Race Condition Prevention**: In `A2AGatewayService.submitTask()`, the pending future is registered (`pendingTasks.put()`) **before** `transport.publish()`. This ordering is critical because `InMemoryTransport` delivers messages synchronously — if publish happened first, `handleResponse()` could execute before `put()` and the future would never complete.
+
+#### REST API
 
-| 组件 | 模块 | 职责 |
+| Method | Path | Description |
 | :--- | :--- | :--- |
-| `A2AGatewayServer` | runtime | Netty HTTP 服务器入口,预注册 mock agent |
-| `A2AGatewayHttpHandler` | runtime | HTTP 请求路由,支持 SSE 流式响应 |
-| `A2AGatewayService` | runtime | 核心编排:任务提交、响应处理、SSE 推送 |
-| `TaskRegistry` | runtime | 内存任务状态机 + TTL 自动清理(5 分钟) |
-| `A2APublishSubscribeService` | runtime | AgentCard 注册、发现、心跳管理 |
-| `InMemoryA2AMessageTransport` | runtime | 内存 pub/sub(可替换为 EventMesh broker) |
-| `A2AClient` | protocol-a2a | Java SDK,提供类型化 API |
+| `POST` | `/a2a/tasks?mode=sync` | Submit task synchronously (wait for result, 30s timeout) |
+| `POST` | `/a2a/tasks?mode=async` | Submit task asynchronously (return taskId immediately) |
+| `GET` | `/a2a/tasks/{taskId}` | Get task status and result |
+| `DELETE` | `/a2a/tasks/{taskId}` | Cancel a task |
+| `GET` | `/a2a/tasks/{taskId}/wait` | Long-poll wait for task result (configurable timeout) |
+| `GET` | `/a2a/tasks/{taskId}/stream` | **SSE** stream of task status updates (`text/event-stream`) |
+| `GET` | `/a2a/agents` | List all registered agents |
+| `POST` | `/a2a/heartbeat` | Agent heartbeat (keeps AgentCard alive) |
+| `GET` | `/a2a/cards/list` | List all AgentCards |
+| `POST` | `/a2a/cards/card/{org}/{unit}/{agent}` | Register an AgentCard |
+
+#### SSE Streaming
+
+The `GET /a2a/tasks/{taskId}/stream` endpoint provides real-time task status updates via Server-Sent Events:
+
+1.  Client opens an HTTP connection with `Accept: text/event-stream`.
+2.  Server sends initial state immediately.
+3.  As task transitions (WORKING → COMPLETED/FAILED/CANCELLED), server pushes `data:` events.
+4.  On terminal state, server sends final event and closes the connection.
+
+The handler writes directly to the Netty channel (returns `null` to skip the default `writeAndFlush` path), using `DefaultHttpContent` chunks with `text/event-stream` content type.
+
+#### A2AClient SDK
+
+The `A2AClient` provides a typed Java API for agent developers:
+
+```java
+A2AClient client = A2AClient.builder()
+    .gatewayUrl("http://localhost:10105")
+    .namespace("global")
+    .agentName("my-agent")
+    .agentCard(card)
+    .heartbeatInterval(30_000)
+    .build();
+client.start();
+
+// Typed return: TaskResult instead of raw JSON
+TaskResult result = client.sendTaskSync("weather-agent", "Beijing", null);
+String taskId = client.sendTaskAsync("weather-agent", "Shanghai", null);
+TaskResult status = client.getTaskStatus(taskId);
+List<String> agents = client.listAgents();  // typed List<String>
+boolean ok = client.cancelTask(taskId);
+```
 
-### 异步 RPC 模式
+`TaskResult` uses `@JsonAlias("result")` to handle the server's `result` field name while exposing a `data` property to callers.
 
-为了在事件驱动架构中支持 MCP 的 Request/Response 模型,A2A 协议定义了以下映射规则:
+### 3.4 Asynchronous RPC Mapping ( The "Async Bridge" )
 
-| MCP 概念 | CloudEvent 映射 | 说明 |
+To support MCP on an Event Bus, synchronous RPC concepts are mapped to asynchronous events:
+
+| Concept | MCP / JSON-RPC | CloudEvent Mapping |
 | :--- | :--- | :--- |
-| **Request** (`tools/call`) | `type`: `org.apache.eventmesh.a2a.tools.call.req` <br> `mcptype`: `request` | 这是一个请求事件 |
-| **Response** (`result`) | `type`: `org.apache.eventmesh.a2a.common.response` <br> `mcptype`: `response` | 这是一个响应事件 |
-| **Correlation** (`id`) | `extension`: `collaborationid` / `id` | 用于将 Response 关联回 Request |
-| **Target** | `extension`: `targetagent` | 路由目标 Agent ID |
+| **Action** | `method` (e.g., `tools/call`) | **Type**: `org.apache.eventmesh.a2a.tools.call.req`<br>**Extension**: `a2amethod` |
+| **Correlation** | `id` (e.g., `req-123`) | **Extension**: `collaborationid` (on Response)<br>**ID**: Preserved on Request |
+| **Direction** | Implicit (Request vs Result) | **Extension**: `mcptype` (`request` or `response`) |
+| **P2P Routing** | `params._agentId` | **Extension**: `targetagent` |
+| **Pub/Sub Topic** | `params._topic` | **Subject**: The topic value (e.g. `market.btc`) |
+| **Streaming Seq** | `params._seq` | **Extension**: `seq` |
+
+## 4. Functional Specification
+
+### 4.1 Message Processing Flow
+
+1.  **Ingestion**: The adaptor receives a `ProtocolTransportObject` (byte array/string).
+2.  **Detection**: Checks for `jsonrpc: "2.0"`.
+3.  **Transformation (MCP Mode)**:
+    *   **Request**: Parses `method`.
+        *   If `message/sendStream`, sets type suffix to `.stream` and extracts `_seq`.
+        *   If `_topic` present, sets `subject` (Pub/Sub).
+        *   If `_agentId` present, sets `targetagent` (P2P).
+    *   **Response**: Parses `result`/`error`. Sets `collaborationid` = `id`.
+4.  **Batch Processing**: Splits JSON Array into a `List<CloudEvent>`.
+
+### 4.2 Key Features
+
+#### A. Intelligent Routing Support
+*   **Mechanism**: Promotes `_agentId` or `_topic` from JSON body to CloudEvent attributes.
+*   **Benefit**: Enables EventMesh Router to perform content-based routing (CBR) efficiently.
+
+#### B. Batching
+*   **Benefit**: Significantly increases throughput for high-frequency interactions.
+
+#### C. Streaming Support
+*   **Operation**: `message/sendStream`
+*   **Mechanism**: Maps to `.stream` event type and preserves sequence order via `seq` extension attribute.
+
+#### D. SSE Task Streaming (Gateway)
+*   **Endpoint**: `GET /a2a/tasks/{taskId}/stream`
+*   **Mechanism**: Server-Sent Events (`text/event-stream`) pushes real-time task state transitions to the client.
+*   **Flow**: Initial state → WORKING updates → terminal state (COMPLETED/FAILED/CANCELLED) → connection close.
+*   **Implementation**: Handler writes `DefaultHttpContent` chunks directly to the Netty channel, bypassing the standard `FullHttpResponse` path.
+
+#### E. Task TTL Auto-Cleanup (Gateway)
+*   **Problem**: Completed/failed tasks accumulate in `TaskRegistry` indefinitely, causing memory leaks.
+*   **Solution**: A daemon `ScheduledExecutorService` (`a2a-task-ttl-cleanup` thread) runs every 60 seconds, removing terminal-state tasks older than the TTL (default: 5 minutes).
+*   **Configuration**: `TaskRegistry(taskTtlMs, cleanupIntervalMs)` constructor allows custom tuning.
+
+#### F. AgentCard Discovery & Heartbeat (Gateway)
+*   **Registration**: `POST /a2a/cards/card/{org}/{unit}/{agent}` registers an `AgentCard`.
+*   **Heartbeat**: `POST /a2a/heartbeat` refreshes the agent's last-seen timestamp. Cards expire after 60 seconds without heartbeat.
+*   **Discovery**: `GET /a2a/agents` returns all live agent cards.
 
-## 协议消息格式
+## 5. Usage Examples
 
-### 1. MCP Request (JSON-RPC 2.0)
+### 5.1 JSON-RPC 2.0 (MCP) Mode
 
+This mode is ideal for LLMs, scripts, and simple integrations where you want to send raw JSON without worrying about CloudEvent headers.
+
+#### 5.1.1 Sending a Tool Call (RPC Request)
+
+**Client Sends (Raw JSON):**
 ```json
 {
   "jsonrpc": "2.0",
   "method": "tools/call",
   "params": {
-    "name": "get_weather",
-    "arguments": {
-      "city": "Shanghai"
-    },
-    "_agentId": "weather-service" // 路由提示
+    "name": "weather",
+    "city": "Shanghai",
+    "_agentId": "weather-agent"
   },
-  "id": "req-123456"
+  "id": "req-101"
 }
 ```
 
-**转换后的 CloudEvent:**
-- `id`: `req-123456`
-- `type`: `org.apache.eventmesh.a2a.tools.call.req`
-- `source`: `eventmesh-a2a`
-- `extension: a2amethod`: `tools/call`
-- `extension: mcptype`: `request`
-- `extension: targetagent`: `weather-service`
+**EventMesh Converts to:**
+*   **Type**: `org.apache.eventmesh.a2a.tools.call.req`
+*   **Extension (targetagent)**: `weather-agent`
+*   **Extension (mcptype)**: `request`
 
-### 2. MCP Response (JSON-RPC 2.0)
+#### 5.1.2 Pub/Sub Broadcast (Notification)
 
+**Client Sends (Raw JSON):**
 ```json
 {
   "jsonrpc": "2.0",
-  "result": {
-    "content": [
-      {
-        "type": "text",
-        "text": "Shanghai: 25°C, Sunny"
-      }
-    ]
-  },
-  "id": "req-123456"
+  "method": "notifications/alert",
+  "params": {
+    "message": "System Maintenance in 10 mins",
+    "_topic": "system.alerts"
+  }
 }
 ```
 
-**转换后的 CloudEvent:**
-- `id`: `uuid-new-event-id`
-- `type`: `org.apache.eventmesh.a2a.common.response`
-- `extension: collaborationid`: `req-123456` (关联 ID) 
-- `extension: mcptype`: `response`
+**EventMesh Converts to:**
+*   **Type**: `org.apache.eventmesh.a2a.notifications.alert`
+*   **Subject**: `system.alerts`
+*   **Extension (mcptype)**: `notification`
+
+#### 5.1.3 Java SDK Example (MCP Mode)
+
+```java
+// See eventmesh-examples/src/main/java/org/apache/eventmesh/a2a/demo/mcp/McpCaller.java
+
+Map<String, Object> request = new HashMap<>();
+request.put("jsonrpc", "2.0");
+request.put("method", "tools/call");
+request.put("params", Map.of("name", "weather", "_agentId", "weather-agent"));
+request.put("id", UUID.randomUUID().toString());
+
+CloudEvent event = CloudEventBuilder.v1()
+    .withType("org.apache.eventmesh.a2a.tools.call.req")
+    .withData(JsonUtils.toJSONString(request).getBytes())
+    .withExtension("protocol", "A2A") // Critical to trigger A2A adaptor
+    .build();
+
+producer.publish(event);
+```
+
+### 5.2 Native CloudEvents Mode
+
+This mode provides full control over all CloudEvent attributes and is recommended for robust, typed applications using the EventMesh SDK.
+
+#### 5.2.1 Native RPC Request
+
+**Client Sends (CloudEvent):**
+```json
+{
+  "specversion": "1.0",
+  "type": "com.example.rpc.request",
+  "source": "my-app",
+  "id": "evt-123",
+  "data": "...",
+  "protocol": "A2A",
+  "targetagent": "target-agent-001"
+}
+```
+
+**Java SDK Example:**
+```java
+// See eventmesh-examples/src/main/java/org/apache/eventmesh/a2a/demo/ce/CloudEventsCaller.java
+
+CloudEvent event = CloudEventBuilder.v1()
+    .withId(UUID.randomUUID().toString())
+    .withSource(URI.create("ce-client"))
+    .withType("com.example.rpc.request")
+    .withData("application/text", "RPC Payload".getBytes())
+    .withExtension("protocol", "A2A")
+    .withExtension("targetagent", "target-agent-001") // Explicit routing
+    .build();
+
+producer.publish(event);
+```
+
+#### 5.2.2 Native Pub/Sub
+
+**Client Sends (CloudEvent):**
+```json
+{
+  "specversion": "1.0",
+  "type": "com.example.notification",
+  "source": "my-app",
+  "subject": "broadcast.topic",
+  "protocol": "A2A"
+}
+```
 
-### 3. Legacy A2A Message (兼容模式)
+#### 5.2.3 Native Streaming
 
+**Client Sends (CloudEvent):**
 ```json
 {
+  "specversion": "1.0",
+  "type": "com.example.stream",
+  "source": "my-app",
+  "subject": "stream-topic",
   "protocol": "A2A",
-  "messageType": "PROPOSE",
-  "sourceAgent": { "agentId": "agent-001" },
-  "payload": { "task": "data-process" }
+  "sessionid": "session-555",
+  "seq": "1"
 }
 ```
 
+### 5.3 Gateway REST API (HTTP)
+
+The A2A Gateway provides a REST API for external clients and non-Java agents.
+
+#### 5.3.1 Submit Task (Sync)
+
+```bash
+curl -X POST 'http://localhost:10105/a2a/tasks?mode=sync' \
+  -H 'Content-Type: application/json' \
+  -d '{"targetAgent":"weather-agent","message":"Beijing"}'
+```
+
+Response:
+```json
+{
+  "taskId": "task-a1b2c3d4",
+  "state": "COMPLETED",
+  "data": "The weather in Beijing is sunny, 25°C"
+}
+```
+
+#### 5.3.2 Submit Task (Async)
+
+```bash
+curl -X POST 'http://localhost:10105/a2a/tasks?mode=async' \
+  -H 'Content-Type: application/json' \
+  -d '{"targetAgent":"weather-agent","message":"Shanghai"}'
+```
+
+Response (HTTP 202):
+```json
+{
+  "taskId": "task-e5f6g7h8",
+  "status": "accepted",
+  "message": "Task submitted. Use GET /a2a/tasks/task-e5f6g7h8 to check status."
+}
+```
+
+#### 5.3.3 SSE Stream
+
+```bash
+curl -N http://localhost:10105/a2a/tasks/task-a1b2c3d4/stream
+```
+
+Response (`text/event-stream`):
+```
+data: {"taskId":"task-a1b2c3d4","state":"SUBMITTED"}
+
+data: {"taskId":"task-a1b2c3d4","state":"WORKING","data":"processing..."}
+
+data: {"taskId":"task-a1b2c3d4","state":"completed","data":"The weather in Beijing is sunny, 25°C"}
+```
+
+#### 5.3.4 List Agents
+
+```bash
+curl http://localhost:10105/a2a/agents
+```
+
+### 5.4 A2AClient SDK (Java)
+
+```java
+A2AClient client = A2AClient.builder()
+    .gatewayUrl("http://localhost:10105")
+    .namespace("global")
+    .agentName("my-agent")
+    .agentCard(card)
+    .heartbeatInterval(30_000)
+    .build();
+
+client.start();
+
+// Synchronous task (returns typed TaskResult)
+TaskResult result = client.sendTaskSync("weather-agent", "Beijing", null);
+
+// Asynchronous task (returns taskId immediately)
+String taskId = client.sendTaskAsync("weather-agent", "Shanghai", null);
+
+// Poll status
+TaskResult status = client.getTaskStatus(taskId);
+
+// Cancel
+boolean cancelled = client.cancelTask(taskId);
+
+// List registered agents (typed List<String>)
+List<String> agents = client.listAgents();
+
+client.shutdown();
+```
+
+## 6. Future Roadmap
+
+*   **EventMesh Broker Integration**: Replace `InMemoryA2AMessageTransport` with the real EventMesh broker for production deployment.
+*   **Schema Registry**: Implement dynamic discovery of Agent capabilities via `methods/list`.
+*   **Sidecar Injection**: Fully integrate the adaptor into the EventMesh Sidecar for non-Java agents (Python, Node.js).
+*   **WebSocket Streaming**: Extend SSE to bidirectional WebSocket for real-time agent-to-agent dialogue.
+*   **Task Persistence**: Persist `TaskRegistry` state to a durable store (Redis/DB) for crash recovery.
+*   **Authentication**: Add API key / JWT authentication to the Gateway REST API.
+
+---
+
+
+## 核心成果
+
+A2A 协议已成功重构为采用 **MCP (Model Context Protocol)** 架构,将 EventMesh 定位为现代化的 **智能体协作总线 (Agent Collaboration Bus)**。
+
+### 1. 核心协议重构 (`EnhancedA2AProtocolAdaptor`)
+- **混合引擎 (JSON-RPC & CloudEvents)**: 实现了智能解析引擎,支持:
+    - **MCP/JSON-RPC 2.0**: 面向 LLM 和脚本的低门槛接入,自动封装 CloudEvent。
+    - **原生 CloudEvents**: 面向 EventMesh 原生应用的灵活接入,支持自定义元数据和透传。
+    - 适配器根据 `jsonrpc` 字段自动分发处理逻辑。
+- **异步 RPC 映射**: 建立了同步 RPC 语义与异步事件驱动架构 (EDA) 之间的桥梁。
+    - **请求 (Requests)** 映射为 `*.req` 事件,属性 `mcptype=request`。
+    - **响应 (Responses)** 映射为 `*.resp` 事件,属性 `mcptype=response`。
+    - **关联 (Correlation)** 通过将 JSON-RPC `id` 映射到 CloudEvent `collaborationid` 来处理。
+- **路由优化**: 实现了"深度内容路由提取":
+    - `params._agentId` -> CloudEvent 扩展属性 `targetagent` (P2P)。
+    - `params._topic` -> CloudEvent Subject (Pub/Sub)。
+
+### 2. 原生 Pub/Sub 与流式支持
+- **Pub/Sub**: 通过将 `_topic` 映射到 CloudEvent Subject,支持 O(1) 广播复杂度。
+- **流式 (Streaming)**: 支持 `message/sendStream` 操作,映射为 `.stream` 事件类型,并通过 `_seq` -> `seq` 扩展属性保证顺序。
+
+### 3. 标准化与兼容性
+- **数据模型**: 定义了符合 JSON-RPC 2.0 规范的 `JsonRpcRequest`、`JsonRpcResponse`、`JsonRpcError` POJO 对象。
+- **方法定义**: 引入了 `McpMethods` 常量,支持标准操作如 `tools/call`、`resources/read`。
+- **AgentCard 模型**: 实现了 `AgentCard`、`AgentSkill`、`AgentInterface`、`AgentCapabilities` 等完整的 Agent 能力描述模型。
+
+### 4. Gateway 运行时架构 (`eventmesh-runtime`)
+
+完整的独立 HTTP Gateway 服务,桥接外部客户端到 A2A 事件总线。
+
+#### 核心组件
+
+| 组件 | 职责 |
+| :--- | :--- |
+| `A2AGatewayServer` | Netty HTTP 服务器入口,预注册 mock agent,组装所有组件 |
+| `A2AGatewayHttpHandler` | HTTP 请求路由,支持 SSE 流式响应 |
+| `A2AGatewayService` | 核心编排:任务提交、响应处理、状态订阅、SSE 推送 |
+| `TaskRegistry` | 内存任务状态机 + TTL 自动清理 |
+| `A2APublishSubscribeService` | AgentCard 注册、发现、心跳管理 |
+| `InMemoryA2AMessageTransport` | 内存 pub/sub 实现(可替换为 EventMesh broker) |
+| `A2ACardHttpHandler` | AgentCard CRUD REST 端点 |
+| `A2AClient` | Java SDK,提供类型化 API |
+
+#### REST API
+
+| 方法 | 路径 | 说明 |
+| :--- | :--- | :--- |
+| `POST` | `/a2a/tasks?mode=sync` | 同步提交任务 |
+| `POST` | `/a2a/tasks?mode=async` | 异步提交任务 |
+| `GET` | `/a2a/tasks/{taskId}` | 查询任务状态 |
+| `DELETE` | `/a2a/tasks/{taskId}` | 取消任务 |
+| `GET` | `/a2a/tasks/{taskId}/wait` | 长轮询等待结果 |
+| `GET` | `/a2a/tasks/{taskId}/stream` | SSE 流式推送状态更新 |
+| `GET` | `/a2a/agents` | 列出已注册 agents |
+| `POST` | `/a2a/heartbeat` | Agent 心跳 |
+| `GET` | `/a2a/cards/list` | 列出所有 AgentCard |
+| `POST` | `/a2a/cards/card/{org}/{unit}/{agent}` | 注册 AgentCard |
+
+### 5. 关键改进
+
+#### 5.1 TaskRegistry TTL 自动清理
+- **问题**: 终态任务(COMPLETED/FAILED/CANCELLED)无限累积导致内存泄漏。
+- **方案**: 守护线程 `ScheduledExecutorService` 每 60 秒扫描一次,清理超过 TTL(默认 5 分钟)的终态任务。
+- **配置**: `TaskRegistry(taskTtlMs, cleanupIntervalMs)` 构造函数支持自定义调优。
+
+#### 5.2 竞态条件修复
+- **问题**: `InMemoryTransport` 同步投递消息,若 `transport.publish()` 在 `pendingTasks.put()` 之前执行,`handleResponse()` 会先于 `put()` 运行,导致 future 永不完成。
+- **方案**: 严格保证 `pendingTasks.put(taskId, future)` 在 `transport.publish()` 之前执行,并添加注释说明顺序重要性。
+
+#### 5.3 A2AClient 类型化返回
+- **改进**: `getTaskStatus()` 返回 `TaskResult` 对象(而非原始 JSON 字符串),`listAgents()` 返回 `List<String>`(而非原始 JSON)。
+- **兼容**: `TaskResult.data` 字段使用 `@JsonAlias("result")` 注解,兼容服务端 `result` 字段名。
+
+#### 5.4 SSE 流式响应
+- **端点**: `GET /a2a/tasks/{taskId}/stream`
+- **实现**: Handler 直接写入 Netty channel(`DefaultHttpContent` chunks),返回 `null` 跳过标准 `FullHttpResponse` 路径。通过 `StatusSubscriber` 回调实时推送状态变更。
+
+#### 5.5 使用文档
+- 新建 `eventmesh-examples/.../demo/README.md`,包含架构图、API 表、curl 示例、SDK 用法、运行方式。
+
+### 6. 测试与质量
+- **协议层单元测试**: `EnhancedA2AProtocolAdaptorTest` 覆盖请求/响应循环、错误处理、通知和批处理。
+- **Topic 工具测试**: `A2ATopicFactoryTest` 覆盖 topic 生成与解析。
+- **Gateway 运行时测试**:
+    - `TaskRegistryTest` — 任务状态机 + TTL 清理验证
+    - `InMemoryA2AMessageTransportTest` — 内存传输投递
+    - `A2AGatewayServiceTest` — Gateway 服务层
+    - `A2AGatewayEndToEndTest` — 进程内全链路
+    - `A2AClientServerIntegrationTest` — 真实 HTTP 客户端-服务端集成测试
+- **集成演示**: `McpIntegrationDemoTest`、`McpPatternsIntegrationTest`、`McpComprehensiveDemoTest`、`CloudEventsComprehensiveDemoTest`
+- **总计**: 73 个测试场景,全部通过。
+
+## 下一步计划
+
+1. **EventMesh Broker 集成**: 用真实 EventMesh broker 替换 `InMemoryA2AMessageTransport`,实现生产级部署。
+2. **路由集成**: 更新 EventMesh Runtime Router,利用 `targetagent` 和 `a2amethod` 扩展属性实现高级路由规则。
+3. **Schema 注册中心**: 实现"注册中心智能体 (Registry Agent)",允许智能体动态发布 MCP 能力 (`methods/list`)。
+4. **Sidecar 支持**: 将 A2A 适配器逻辑暴露在 Sidecar 代理中,允许非 Java 智能体通过 HTTP/JSON 交互。
+5. **WebSocket 流式**: 将 SSE 扩展为双向 WebSocket,支持实时 agent 对话。
+6. **任务持久化**: 将 `TaskRegistry` 状态持久化到 Redis/DB,支持崩溃恢复。
+7. **认证授权**: 为 Gateway REST API 添加 API Key / JWT 认证。
+
+---
+
+
+**Date**: 2026-06-19
+**Version**: v2.0.0 (MCP Edition + Gateway Runtime)
+**Status**: ✅ **PASS**
+
+## Test Suite Summary
+
+The test suite provides comprehensive coverage across two layers: the **Protocol Adaptor** (JSON-RPC 2.0 & Native CloudEvents) and the **Gateway Runtime** (HTTP REST API, Task lifecycle, SSE streaming, AgentCard discovery).
+
+### Protocol Adaptor Tests
+
+| Test Class | Scenarios | Result | Description |
+| :--- | :--- | :--- | :--- |
+| `EnhancedA2AProtocolAdaptorTest` | 12 | **PASS** | Unit tests covering core protocol logic, MCP parsing, Batching, Error handling, and A2A Standard Ops. |
+| `McpIntegrationDemoTest` | 1 | **PASS** | End-to-end RPC demo using MCP (JSON-RPC). |
+| `McpPatternsIntegrationTest` | 2 | **PASS** | End-to-end Pub/Sub and Streaming demos using MCP (JSON-RPC). |
+| `McpComprehensiveDemoTest` | 3 | **PASS** | Validation of all 3 patterns in MCP mode. |
+| `CloudEventsComprehensiveDemoTest` | 3 | **PASS** | Validation of all 3 patterns in Native CloudEvents mode. |
+| `A2ATopicFactoryTest` | 8 | **PASS** | Topic naming and parsing (request/response/status topics). |
+
+### Gateway Runtime Tests
+
+| Test Class | Scenarios | Result | Description |
+| :--- | :--- | :--- | :--- |
+| `TaskRegistryTest` | 6 | **PASS** | Task state machine transitions, parent-child relationships, TTL auto-cleanup. |
+| `InMemoryA2AMessageTransportTest` | 4 | **PASS** | In-memory pub/sub delivery, subscribe/unsubscribe, wildcard topics. |
+| `A2AGatewayServiceTest` | 8 | **PASS** | Gateway service layer: task submission (sync/async), response handling, cancel, status subscription. |
+| `A2AGatewayEndToEndTest` | 6 | **PASS** | In-process end-to-end: client → gateway → transport → agent → response → client. |
+| `A2AClientServerIntegrationTest` | 20 | **PASS** | Real HTTP client-server integration: AgentCard registration, sync/async tasks, status query, cancel, list agents, SSE streaming. |
+
+**Total Scenarios**: 73 (All Passed)
+
+## Detailed Test Cases
+
+### 1. `EnhancedA2AProtocolAdaptorTest` (Unit)
+- **MCP Core**: Validated Request/Response/Notification mapping.
+- **Error Handling**: Validated JSON-RPC Error object mapping.
+- **Batching**: Validated JSON Array splitting.
+- **Legacy Removal**: Confirmed legacy A2A format is no longer processed.
+- **A2A Ops**: Verified `task/get`, `message/sendStream` mappings.
+
+### 2. `A2ATopicFactoryTest` (Unit)
+- Validated topic generation for request, response, and status topics.
+- Validated topic parsing (extracting namespace, agent name, task ID, topic type).
+- Verified wildcard topic patterns for gateway subscriptions.
+
+### 3. `TaskRegistryTest` (Unit)
+- **State Machine**: SUBMITTED → WORKING → COMPLETED/FAILED/CANCELLED transitions.
+- **Parent-Child**: Task hierarchy tracking and child task listing.
+- **TTL Cleanup**: Verified that terminal-state tasks are removed after TTL expires.
+- **Concurrency**: Thread-safe state transitions under concurrent access.
+
+### 4. `InMemoryA2AMessageTransportTest` (Unit)
+- Publish/subscribe message delivery.
+- Multiple subscribers on the same topic.
+- Unsubscribe behavior.
+- Wildcard topic matching.
+
+### 5. `A2AGatewayServiceTest` (Integration)
+- **Sync Task**: submitTask → publish → handleResponse → future.complete.
+- **Async Task**: submitTask returns immediately, status queried separately.
+- **Cancel**: cancelTask transitions state and completes future with CANCELLED.
+- **Race Condition**: Verified put-before-publish ordering prevents lost responses.
+- **Status Subscription**: StatusSubscriber receives state transition callbacks.
+
+### 6. `A2AGatewayEndToEndTest` (Integration)
+- Full flow: A2AClient → Gateway HTTP → GatewayService → Transport → Agent → Response → Client.
+- Verified task ID correlation across all components.
+- Multiple concurrent tasks.
+- Error scenarios (unknown agent, task not found).
+
+### 7. `A2AClientServerIntegrationTest` (HTTP Integration)
+- **Real HTTP**: Uses Apache HttpClient to hit the real Netty server.
+- **AgentCard**: Registration and heartbeat via REST API.
+- **Sync Task**: `POST /a2a/tasks?mode=sync` returns completed result.
+- **Async Task**: `POST /a2a/tasks?mode=async` returns taskId, then `GET /a2a/tasks/{taskId}` polls status.
+- **Cancel**: `DELETE /a2a/tasks/{taskId}` cancels the task.
+- **List Agents**: `GET /a2a/agents` returns registered agent list.
+- **Typed Returns**: `A2AClient.getTaskStatus()` returns `TaskResult`, `listAgents()` returns `List<String>`.
+- **SSE Stream**: `GET /a2a/tasks/{taskId}/stream` receives real-time state updates via `text/event-stream`.
+
+### 8. `McpIntegrationDemoTest` (Integration - RPC)
+- Simulated Client → EventMesh → Server flow.
+- Verified correlation ID linking (`req-id` <-> `collaborationid`).
+
+### 9. `McpPatternsIntegrationTest` (Integration - Advanced)
+- **Pub/Sub**: Verified `_topic` -> `subject` mapping for Broadcast.
+- **Streaming**: Verified `_seq` -> `seq` mapping for ordered chunks.
+
+### 10. `McpComprehensiveDemoTest` (Protocol: JSON-RPC)
+- **RPC**: Request/Response flow verification.
+- **Pub/Sub**: Broadcast to Topic routing verification.
+- **Streaming**: Sequence ID preservation verification.
+
+### 11. `CloudEventsComprehensiveDemoTest` (Protocol: Native CloudEvents)
+- **RPC**: Verified manual construction of `.req` / `.resp` CloudEvents works.
+- **Pub/Sub**: Verified manual setting of `subject` works.
+- **Streaming**: Verified manual setting of `seq` extension works.
+
+## Environment
+
+- **JDK**: Java 8 (Source/Target 1.8), Compatible with Java 21 Runtime
+- **Build System**: Gradle 7.x+
+- **Dependencies**: Jackson 2.18+, CloudEvents SDK 3.0+, Netty 4.1+, Apache HttpClient
+
+## Conclusion
+
+The A2A Protocol v2.0 implementation is stable, functionally complete, and ready for production deployment. It successfully supports:
+- **Hybrid Architecture** (MCP & CloudEvents) with all three interaction patterns (RPC, Pub/Sub, Streaming)
+- **Gateway Runtime** with full REST API, SSE streaming, task lifecycle management, TTL auto-cleanup, and typed Java SDK
+- **73 test scenarios** across protocol and runtime layers, all passing
+
+---
+
+## 中文使用指南(整合自 README.md)
+
 ## 使用指南
 
 ### 1. 作为 Client 发起 MCP 调用
diff --git a/docs/a2a-protocol/README_EN.md b/docs/a2a-protocol/README_EN.md
deleted file mode 100644
index b7d8aa5fe..000000000
--- a/docs/a2a-protocol/README_EN.md
+++ /dev/null
@@ -1,255 +0,0 @@
-# EventMesh A2A (Agent-to-Agent Communication Protocol)
-
-## Overview
-
-The **EventMesh A2A (Agent-to-Agent) Protocol** is a specialized, high-performance protocol plugin designed to enable asynchronous communication, collaboration, and task coordination between autonomous agents.
-
-With the release of v2.0, A2A adopts the **MCP (Model Context Protocol)** architecture, transforming EventMesh into a robust **Agent Collaboration Bus**. It bridges the gap between synchronous LLM-based tool calls (JSON-RPC 2.0) and asynchronous Event-Driven Architectures (EDA), enabling scalable, distributed, and decoupled agent systems.
-
-## Core Features
-
-### 1. MCP over CloudEvents
-- **Standard Compliance**: Fully supports standard methods defined by **MCP (Model Context Protocol)**, such as `tools/call`, `resources/read`.
-- **Event-Driven**: Maps synchronous RPC calls to asynchronous **Request/Response Event Streams**, leveraging EventMesh's high-concurrency processing capabilities.
-- **Transport Agnostic**: All MCP messages are encapsulated within standard **CloudEvents** envelopes, running over any transport layer supported by EventMesh (HTTP, TCP, gRPC, Kafka).
-
-### 2. Dual-Mode Support (Hybrid Architecture)
-
-A2A Protocol features a unique **Dual-Mode** architecture that simultaneously supports:
-
-1.  **JSON-RPC 2.0 (MCP Mode)**:
-    *   **Target**: LLMs, Scripts (Python/JS), LangChain integration.
-    *   **Benefit**: Extremely low barrier to entry. Clients send simple JSON objects; the adaptor automatically wraps them in CloudEvents.
-2.  **Native CloudEvents (Power Mode)**:
-    *   **Target**: EventMesh native apps, Knative, Serverless functions.
-    *   **Benefit**: Full control over event metadata. Allows pass-through of custom or binary data.
-
-**Mechanism**: The `EnhancedA2AProtocolAdaptor` intelligently detects the payload format. If `jsonrpc: "2.0"` is present, it engages the MCP translation engine; otherwise, it treats the payload as a standard CloudEvent (delegating to the underlying CloudEvents adaptor).
-
-### 3. Native Pub/Sub Semantics
-- **O(1) Broadcast**: Publishers send messages once to a Topic, and EventMesh efficiently fans out to all subscribers.
-- **Decoupling**: Solves the scalability issues of traditional P2P Webhook callbacks.
-- **Isolation**: Provides backpressure isolation between publishers and subscribers.
-
-### 3. High Performance & Routing
-- **Batch Processing**: Natively supports JSON-RPC Batch requests. EventMesh automatically splits them into parallel event streams.
-- **Intelligent Routing**: Extracts routing hints (`_agentId` for P2P, `_topic` for Pub/Sub) from MCP parameters and injects them into CloudEvents attributes for zero-decoding routing.
-
-### 4. Streaming Support
-- **Sequencing**: Preserves message order for streaming operations (`message/sendStream`) using sequence IDs.
-
-## Architecture
-
-### Core Components
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│                EventMesh A2A Protocol v2.0                │
-│              (MCP over CloudEvents Architecture)            │
-├─────────────────────────────────────────────────────────────┤
-│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
-│  │ MCP/JSON-RPC│  │ Native      │  │  Protocol   │         │
-│  │   Handler   │  │ Pub/Sub     │  │  Delegator  │         │
-│  └─────────────┘  └─────────────┘  └─────────────┘         │
-├─────────────────────────────────────────────────────────────┤
-│  ┌───────────────────────────────────────────────────────┐  │
-│  │           Enhanced A2A Protocol Adaptor               │  │
-│  │      (Intelligent Parsing & CloudEvent Mapping)       │  │
-│  └───────────────────────────────────────────────────────┘  │
-├─────────────────────────────────────────────────────────────┤
-│              EventMesh Protocol Infrastructure             │
-│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
-│  │ CloudEvents │  │    HTTP     │  │    gRPC     │         │
-│  │  Protocol   │  │  Protocol   │  │  Protocol   │         │
-│  └─────────────┘  └─────────────┘  └─────────────┘         │
-└─────────────────────────────────────────────────────────────┘
-```
-
-### Gateway Runtime Architecture
-
-```
-  protocol-a2a module              runtime module                   examples module
-  ┌─────────────────┐           ┌──────────────────┐           ┌──────────────┐
-  │ A2AMessageTransport(Iface)   │ A2AGatewayServer  │           │ A2AGatewayDemo│
-  │ A2AClient (SDK)  │<──HTTP──>│ (main, Netty HTTP)│<──HTTP──>│ (client only) │
-  │ AgentCard/Topic  │           │ InMemoryTransport │           └──────────────┘
-  └─────────────────┘           │ GatewayService    │
-                                │ TaskRegistry(TTL) │
-                                └──────────────────┘
-```
-
-#### Core Runtime Components
-
-| Component | Module | Responsibility |
-| :--- | :--- | :--- |
-| `A2AGatewayServer` | runtime | Netty HTTP server entry point, pre-registers mock agents |
-| `A2AGatewayHttpHandler` | runtime | HTTP request router, supports SSE streaming |
-| `A2AGatewayService` | runtime | Core orchestration: task submission, response handling, SSE push |
-| `TaskRegistry` | runtime | In-memory task state machine + TTL auto-cleanup (5 min) |
-| `A2APublishSubscribeService` | runtime | AgentCard registration, discovery, heartbeat |
-| `InMemoryA2AMessageTransport` | runtime | In-memory pub/sub (replaceable by EventMesh broker) |
-| `A2AClient` | protocol-a2a | Java SDK with typed API |
-
-### Asynchronous RPC Pattern
-
-To support the MCP Request/Response model within an event-driven architecture, A2A defines the following mapping rules:
-
-| MCP Concept | CloudEvent Mapping | Description |
-| :--- | :--- | :--- |
-| **Request** (`tools/call`) | `type`: `org.apache.eventmesh.a2a.tools.call.req` <br> `mcptype`: `request` | Request event. |
-| **Response** (`result`) | `type`: `org.apache.eventmesh.a2a.common.response` <br> `mcptype`: `response` | Response event. |
-| **Correlation** (`id`) | `extension`: `collaborationid` / `id` | Links Response to Request. |
-| **P2P Target** | `extension`: `targetagent` | Routing target Agent ID. |
-| **Pub/Sub Topic** | `subject`: `<topic_name>` | Broadcast Topic. |
-
-## Protocol Message Format
-
-### 1. MCP Request (P2P)
-
-```json
-{
-  "jsonrpc": "2.0",
-  "method": "tools/call",
-  "params": {
-    "name": "get_weather",
-    "_agentId": "weather-service" // P2P Routing
-  },
-  "id": "req-123"
-}
-```
-
-### 2. MCP Request (Pub/Sub)
-
-```json
-{
-  "jsonrpc": "2.0",
-  "method": "market/update",
-  "params": {
-    "price": 50000,
-    "_topic": "market.btc" // Pub/Sub Routing
-  }
-}
-```
-
-## Usage Guide
-
-### 1. Initiate Call (Client)
-
-```java
-// 1. Construct MCP Request JSON
-String mcpRequest = "{" +
-    "\"jsonrpc\": \"2.0\"," +
-    "\"method\": \"tools/call\"," +
-    "\"params\": { \"name\": \"weather\", \"_agentId\": \"weather-agent\" }," +
-    "\"id\": \"req-001\"" +
-    "}";
-
-// 2. Send via EventMesh SDK
-eventMeshProducer.publish(new A2AProtocolTransportObject(mcpRequest));
-```
-
-### 2. Handle Request (Server)
-
-Subscribe to the topic `org.apache.eventmesh.a2a.tools.call.req`, process logic, and send back response with matching `id`.
-
-### 3. Gateway REST API
-
-The A2A Gateway provides a full REST API for external clients and non-Java agents:
-
-```bash
-# Sync task
-curl -X POST 'http://localhost:10105/a2a/tasks?mode=sync' \
-  -H 'Content-Type: application/json' \
-  -d '{"targetAgent":"weather-agent","message":"Beijing"}'
-
-# Async task
-curl -X POST 'http://localhost:10105/a2a/tasks?mode=async' \
-  -H 'Content-Type: application/json' \
-  -d '{"targetAgent":"weather-agent","message":"Shanghai"}'
-
-# Query status
-curl http://localhost:10105/a2a/tasks/{taskId}
-
-# SSE stream
-curl -N http://localhost:10105/a2a/tasks/{taskId}/stream
-
-# List agents
-curl http://localhost:10105/a2a/agents
-```
-
-#### REST API Endpoints
-
-| Method | Path | Description |
-|------|------|------|
-| POST | `/a2a/tasks?mode=sync` | Submit task synchronously (wait for result) |
-| POST | `/a2a/tasks?mode=async` | Submit task asynchronously (return taskId) |
-| GET | `/a2a/tasks/{taskId}` | Get task status |
-| DELETE | `/a2a/tasks/{taskId}` | Cancel task |
-| GET | `/a2a/tasks/{taskId}/wait` | Long-poll wait for result |
-| GET | `/a2a/tasks/{taskId}/stream` | SSE stream of task status updates |
-| GET | `/a2a/agents` | List registered agents |
-| POST | `/a2a/heartbeat` | Agent heartbeat |
-| GET | `/a2a/cards/list` | List all AgentCards |
-| POST | `/a2a/cards/card/{org}/{unit}/{agent}` | Register AgentCard |
-
-### 4. A2AClient Java SDK
-
-```java
-A2AClient client = A2AClient.builder()
-    .gatewayUrl("http://localhost:10105")
-    .namespace("global")
-    .agentName("my-agent")
-    .agentCard(card)
-    .heartbeatInterval(30_000)
-    .build();
-
-client.start();
-
-// Sync task (returns typed TaskResult)
-TaskResult result = client.sendTaskSync("weather-agent", "Beijing", null);
-
-// Async task (returns taskId)
-String taskId = client.sendTaskAsync("weather-agent", "Shanghai", null);
-
-// Query status
-TaskResult status = client.getTaskStatus(taskId);
-
-// Cancel
-boolean cancelled = client.cancelTask(taskId);
-
-// List agents (returns List<String>)
-List<String> agents = client.listAgents();
-
-client.shutdown();
-```
-
-## Version History
-
-- **v2.0.0**: Fully Embraced MCP (Model Context Protocol)
-  - Introduced `EnhancedA2AProtocolAdaptor` supporting JSON-RPC 2.0.
-  - Implemented Async RPC over CloudEvents pattern.
-  - Added **Native Pub/Sub** support via `_topic` parameter.
-  - Added **Streaming** support via `_seq` parameter.
-
-- **v2.1.0**: Gateway Runtime Architecture
-  - Added `A2AGatewayServer` (Netty HTTP) standalone Gateway service.
-  - Implemented `TaskRegistry` task state machine + TTL auto-cleanup (5 min).
-  - Added SSE streaming response (`GET /a2a/tasks/{taskId}/stream`).
-  - `A2AClient` SDK returns typed objects (`TaskResult`, `List<String>`).
-  - Fixed `pendingTasks` race condition (put-before-publish).
-  - AgentCard registration, discovery, heartbeat management.
-  - 73 test scenarios all passing.
-
-## Contribution
-
-Welcome to contribute code and documentation!
-
-## License
-
-Apache License 2.0
-
-## Contact
-
-- Project Homepage: https://eventmesh.apache.org
-- Issues: https://github.com/apache/eventmesh/issues
-- Mailing List: dev@eventmesh.apache.org
\ No newline at end of file
diff --git a/docs/a2a-protocol/TEST_RESULTS.md b/docs/a2a-protocol/TEST_RESULTS.md
deleted file mode 100644
index 01a79014f..000000000
--- a/docs/a2a-protocol/TEST_RESULTS.md
+++ /dev/null
@@ -1,112 +0,0 @@
-# Test Results: EventMesh A2A Protocol v2.0
-
-**Date**: 2026-06-19
-**Version**: v2.0.0 (MCP Edition + Gateway Runtime)
-**Status**: ✅ **PASS**
-
-## Test Suite Summary
-
-The test suite provides comprehensive coverage across two layers: the **Protocol Adaptor** (JSON-RPC 2.0 & Native CloudEvents) and the **Gateway Runtime** (HTTP REST API, Task lifecycle, SSE streaming, AgentCard discovery).
-
-### Protocol Adaptor Tests
-
-| Test Class | Scenarios | Result | Description |
-| :--- | :--- | :--- | :--- |
-| `EnhancedA2AProtocolAdaptorTest` | 12 | **PASS** | Unit tests covering core protocol logic, MCP parsing, Batching, Error handling, and A2A Standard Ops. |
-| `McpIntegrationDemoTest` | 1 | **PASS** | End-to-end RPC demo using MCP (JSON-RPC). |
-| `McpPatternsIntegrationTest` | 2 | **PASS** | End-to-end Pub/Sub and Streaming demos using MCP (JSON-RPC). |
-| `McpComprehensiveDemoTest` | 3 | **PASS** | Validation of all 3 patterns in MCP mode. |
-| `CloudEventsComprehensiveDemoTest` | 3 | **PASS** | Validation of all 3 patterns in Native CloudEvents mode. |
-| `A2ATopicFactoryTest` | 8 | **PASS** | Topic naming and parsing (request/response/status topics). |
-
-### Gateway Runtime Tests
-
-| Test Class | Scenarios | Result | Description |
-| :--- | :--- | :--- | :--- |
-| `TaskRegistryTest` | 6 | **PASS** | Task state machine transitions, parent-child relationships, TTL auto-cleanup. |
-| `InMemoryA2AMessageTransportTest` | 4 | **PASS** | In-memory pub/sub delivery, subscribe/unsubscribe, wildcard topics. |
-| `A2AGatewayServiceTest` | 8 | **PASS** | Gateway service layer: task submission (sync/async), response handling, cancel, status subscription. |
-| `A2AGatewayEndToEndTest` | 6 | **PASS** | In-process end-to-end: client → gateway → transport → agent → response → client. |
-| `A2AClientServerIntegrationTest` | 20 | **PASS** | Real HTTP client-server integration: AgentCard registration, sync/async tasks, status query, cancel, list agents, SSE streaming. |
-
-**Total Scenarios**: 73 (All Passed)
-
-## Detailed Test Cases
-
-### 1. `EnhancedA2AProtocolAdaptorTest` (Unit)
-- **MCP Core**: Validated Request/Response/Notification mapping.
-- **Error Handling**: Validated JSON-RPC Error object mapping.
-- **Batching**: Validated JSON Array splitting.
-- **Legacy Removal**: Confirmed legacy A2A format is no longer processed.
-- **A2A Ops**: Verified `task/get`, `message/sendStream` mappings.
-
-### 2. `A2ATopicFactoryTest` (Unit)
-- Validated topic generation for request, response, and status topics.
-- Validated topic parsing (extracting namespace, agent name, task ID, topic type).
-- Verified wildcard topic patterns for gateway subscriptions.
-
-### 3. `TaskRegistryTest` (Unit)
-- **State Machine**: SUBMITTED → WORKING → COMPLETED/FAILED/CANCELLED transitions.
-- **Parent-Child**: Task hierarchy tracking and child task listing.
-- **TTL Cleanup**: Verified that terminal-state tasks are removed after TTL expires.
-- **Concurrency**: Thread-safe state transitions under concurrent access.
-
-### 4. `InMemoryA2AMessageTransportTest` (Unit)
-- Publish/subscribe message delivery.
-- Multiple subscribers on the same topic.
-- Unsubscribe behavior.
-- Wildcard topic matching.
-
-### 5. `A2AGatewayServiceTest` (Integration)
-- **Sync Task**: submitTask → publish → handleResponse → future.complete.
-- **Async Task**: submitTask returns immediately, status queried separately.
-- **Cancel**: cancelTask transitions state and completes future with CANCELLED.
-- **Race Condition**: Verified put-before-publish ordering prevents lost responses.
-- **Status Subscription**: StatusSubscriber receives state transition callbacks.
-
-### 6. `A2AGatewayEndToEndTest` (Integration)
-- Full flow: A2AClient → Gateway HTTP → GatewayService → Transport → Agent → Response → Client.
-- Verified task ID correlation across all components.
-- Multiple concurrent tasks.
-- Error scenarios (unknown agent, task not found).
-
-### 7. `A2AClientServerIntegrationTest` (HTTP Integration)
-- **Real HTTP**: Uses Apache HttpClient to hit the real Netty server.
-- **AgentCard**: Registration and heartbeat via REST API.
-- **Sync Task**: `POST /a2a/tasks?mode=sync` returns completed result.
-- **Async Task**: `POST /a2a/tasks?mode=async` returns taskId, then `GET /a2a/tasks/{taskId}` polls status.
-- **Cancel**: `DELETE /a2a/tasks/{taskId}` cancels the task.
-- **List Agents**: `GET /a2a/agents` returns registered agent list.
-- **Typed Returns**: `A2AClient.getTaskStatus()` returns `TaskResult`, `listAgents()` returns `List<String>`.
-- **SSE Stream**: `GET /a2a/tasks/{taskId}/stream` receives real-time state updates via `text/event-stream`.
-
-### 8. `McpIntegrationDemoTest` (Integration - RPC)
-- Simulated Client → EventMesh → Server flow.
-- Verified correlation ID linking (`req-id` <-> `collaborationid`).
-
-### 9. `McpPatternsIntegrationTest` (Integration - Advanced)
-- **Pub/Sub**: Verified `_topic` -> `subject` mapping for Broadcast.
-- **Streaming**: Verified `_seq` -> `seq` mapping for ordered chunks.
-
-### 10. `McpComprehensiveDemoTest` (Protocol: JSON-RPC)
-- **RPC**: Request/Response flow verification.
-- **Pub/Sub**: Broadcast to Topic routing verification.
-- **Streaming**: Sequence ID preservation verification.
-
-### 11. `CloudEventsComprehensiveDemoTest` (Protocol: Native CloudEvents)
-- **RPC**: Verified manual construction of `.req` / `.resp` CloudEvents works.
-- **Pub/Sub**: Verified manual setting of `subject` works.
-- **Streaming**: Verified manual setting of `seq` extension works.
-
-## Environment
-
-- **JDK**: Java 8 (Source/Target 1.8), Compatible with Java 21 Runtime
-- **Build System**: Gradle 7.x+
-- **Dependencies**: Jackson 2.18+, CloudEvents SDK 3.0+, Netty 4.1+, Apache HttpClient
-
-## Conclusion
-
-The A2A Protocol v2.0 implementation is stable, functionally complete, and ready for production deployment. It successfully supports:
-- **Hybrid Architecture** (MCP & CloudEvents) with all three interaction patterns (RPC, Pub/Sub, Streaming)
-- **Gateway Runtime** with full REST API, SSE streaming, task lifecycle management, TTL auto-cleanup, and typed Java SDK
-- **73 test scenarios** across protocol and runtime layers, all passing
diff --git a/docs/a2a-protocol/eventmesh-a2a-design.md b/docs/a2a-protocol/eventmesh-a2a-design.md
deleted file mode 100644
index a3b2135fe..000000000
--- a/docs/a2a-protocol/eventmesh-a2a-design.md
+++ /dev/null
@@ -1,478 +0,0 @@
----
-title: EventMesh A2A Protocol
-sidebar_position: 4
----
-
-# EventMesh A2A Protocol Architecture & Functional Specification
-
-## 1. Overview
-
-The **EventMesh A2A (Agent-to-Agent) Protocol** is a specialized, high-performance protocol plugin designed to enable asynchronous communication, collaboration, and task coordination between autonomous agents.
-
-With the release of v2.0, A2A adopts the **MCP (Model Context Protocol)** architecture, transforming EventMesh into a robust **Agent Collaboration Bus**. It bridges the gap between synchronous LLM-based tool calls (JSON-RPC 2.0) and asynchronous Event-Driven Architectures (EDA), enabling scalable, distributed, and decoupled agent systems.
-
-## 2. Core Philosophy
-
-The architecture adheres to the principles outlined in the broader agent community (e.g., A2A Project, FIPA-ACL, and CloudEvents):
-
-1.  **JSON-RPC 2.0 as Lingua Franca**: Uses standard JSON-RPC for payload semantics, ensuring compatibility with modern LLM ecosystems (LangChain, AutoGen).
-2.  **Transport Agnostic**: Encapsulates all messages within **CloudEvents**, allowing transport over any EventMesh-supported protocol (HTTP, TCP, gRPC, Kafka).
-3.  **Async by Default**: Maps synchronous Request/Response patterns to asynchronous Event streams using correlation IDs.
-4.  **Native Pub/Sub Semantics**: Supports O(1) broadcast complexity, temporal decoupling (Late Join), and backpressure isolation, solving the scalability limits of traditional P2P webhook callbacks.
-
-### 2.1 Native Pub/Sub Semantics
-
-Traditional A2A implementations often rely on HTTP Webhooks (`POST /inbox`) for asynchronous callbacks. While functional, this **Point-to-Point (P2P)** model suffers from significant scaling issues:
-
-*   **Insufficient Fan-Out**: A publisher must send $N$ requests to reach $N$ subscribers, leading to $O(N)$ complexity.
-*   **Temporal Coupling**: Consumers must be online at the exact moment of publication.
-*   **Backpressure Propagation**: A slow subscriber can block the publisher.
-
-**EventMesh A2A** solves this by introducing **Native Pub/Sub** capabilities:
-
-```mermaid
-flowchart LR
-    Publisher["Publisher Agent"] -->|1. Publish (Once)| Bus["EventMesh Bus"]
-    
-    subgraph FanoutLayer ["EventMesh Fanout Layer"]
-        Queue["Topic Queue"]
-    end
-    
-    Bus --> Queue
-    
-    Queue -->|"Push"| Sub1["Subscriber 1"]
-    Queue -->|"Push"| Sub2["Subscriber 2"]
-    Queue -->|"Push"| Sub3["Subscriber 3"]
-    
-    style Bus fill:#f9f,stroke:#333
-    style FanoutLayer fill:#ccf,stroke:#333
-```
-
-### 2.1 Hybrid Protocol Support (JSON-RPC & CloudEvents)
-
-A2A Protocol introduces a unique **Hybrid Architecture** that bridges the gap between the AI ecosystem (which prefers simple JSON) and the Cloud Native ecosystem (which prefers structured CloudEvents).
-
-| Feature | JSON-RPC 2.0 Mode | Native CloudEvents Mode |
-| :--- | :--- | :--- |
-| **Primary Audience** | LLMs, Scripts (Python/JS), LangChain | EventMesh Apps, Knative, Java SDK |
-| **Philosophy** | **"Battery Included"** | **"Power User"** |
-| **Usage** | Send raw JSON (`{"method":...}`) | Send `CloudEvent` object |
-| **Complexity** | Low (No SDK required) | Medium (Requires CE SDK) |
-| **Mechanism** | Adaptor automatically wraps JSON in CE | Adaptor passes through the event |
-
-**Benefits:**
-*   **Zero-Barrier Entry**: Developers can interact with the mesh using just `curl` or simple JSON libraries.
-*   **Full Flexibility**: Advanced users retain full control over CloudEvent attributes (Source, Type, Extensions) for complex routing or tracing scenarios.
-
-## 3. Architecture Design
-
-### 3.1 System Context
-
-```mermaid
-graph TD
-    Client["Client Agent / LLM"] -- "JSON-RPC Request" --> EM["EventMesh Runtime"]
-    EM -- "CloudEvent (Request)" --> Server["Server Agent / Tool"]
-    Server -- "CloudEvent (Response)" --> EM
-    EM -- "JSON-RPC Response" --> Client
-    
-    subgraph Runtime ["EventMesh Runtime"]
-        Plugin["A2A Protocol Plugin"]
-    end
-    
-    style EM fill:#f9f,stroke:#333,stroke-width:4px
-    style Plugin fill:#ccf,stroke:#333,stroke-width:2px
-```
-
-### 3.2 Component Design (`eventmesh-protocol-a2a`)
-
-The core protocol logic resides in the `eventmesh-protocol-plugin` module.
-
-*   **`EnhancedA2AProtocolAdaptor`**: The central brain of the protocol.
-    *   **Intelligent Parsing**: Automatically detects message format (MCP vs. Raw CloudEvent).
-    *   **Protocol Delegation**: Delegates to `CloudEvents` or `HTTP` adaptors when necessary.
-    *   **Semantic Mapping**: Transforms JSON-RPC methods and IDs into CloudEvent attributes.
-*   **`A2AProtocolConstants`**: Defines standard operations like `task/get`, `message/sendStream`.
-*   **`JsonRpc*` Models**: Strictly typed POJOs for JSON-RPC 2.0 compliance.
-*   **`AgentCard` / `AgentSkill` / `AgentInterface`**: Agent capability discovery models.
-*   **`A2ATopicFactory`**: Topic naming and parsing utility (request/response/status topics).
-*   **`A2AClient`**: Java SDK for agent developers — AgentCard registration, task submission (sync/async), task status query, heartbeat, and transport-based request handling. Returns typed `TaskResult` objects.
-*   **`A2AMessageTransport`**: Transport-agnostic pub/sub interface (InMemory implementation for dev/testing).
-
-### 3.3 Gateway Runtime Architecture (`eventmesh-runtime`)
-
-The Gateway runtime provides a standalone HTTP server that bridges external clients to the A2A event bus.
-
-```mermaid
-graph TD
-    Client["Client / A2AClient SDK"] -- "HTTP REST" --> Server["A2AGatewayServer<br/>(Netty HTTP)"]
-    Server --> Handler["A2AGatewayHttpHandler"]
-    Handler --> GwService["A2AGatewayService"]
-    GwService --> Registry["TaskRegistry<br/>(state machine + TTL)"]
-    GwService --> Transport["InMemoryA2AMessageTransport"]
-    GwService --> PubSub["A2APublishSubscribeService<br/>(AgentCard discovery)"]
-    Transport -- "publish/subscribe" --> Agent["Target Agent"]
-    Agent -- "response event" --> Transport
-    Transport --> GwService
-
-    style Server fill:#f9f,stroke:#333,stroke-width:2px
-    style Registry fill:#cfc,stroke:#333
-    style Transport fill:#ccf,stroke:#333
-```
-
-#### Core Components
-
-| Component | Module | Responsibility |
-| :--- | :--- | :--- |
-| `A2AGatewayServer` | runtime | Standalone Netty HTTP server entry point. Pre-registers mock agents, wires all components. |
-| `A2AGatewayHttpHandler` | runtime | HTTP request router. Maps REST endpoints to service calls. Supports SSE streaming. |
-| `A2AGatewayService` | runtime | Core orchestration: task submission, response handling, status subscription, SSE push. |
-| `TaskRegistry` | runtime | In-memory task lifecycle state machine with TTL auto-cleanup. |
-| `A2APublishSubscribeService` | runtime | AgentCard registration, discovery, and heartbeat management. |
-| `InMemoryA2AMessageTransport` | runtime | In-memory pub/sub implementation (replaceable by EventMesh broker). |
-| `A2ACardHttpHandler` | runtime | AgentCard CRUD REST endpoints (`/a2a/cards/*`). |
-| `A2AClient` | protocol-a2a | Java SDK for agent developers (HTTP + transport). |
-
-#### Task Lifecycle State Machine
-
-```
-SUBMITTED → WORKING → COMPLETED
-                    ↘ FAILED
-                    ↘ CANCELLED
-```
-
-*   **TaskRegistry TTL Cleanup**: Terminal-state tasks (COMPLETED/FAILED/CANCELLED) are automatically removed after a configurable TTL (default: 5 minutes). A daemon `ScheduledExecutorService` runs cleanup every 60 seconds, preventing memory leaks from accumulated historical tasks.
-*   **Race Condition Prevention**: In `A2AGatewayService.submitTask()`, the pending future is registered (`pendingTasks.put()`) **before** `transport.publish()`. This ordering is critical because `InMemoryTransport` delivers messages synchronously — if publish happened first, `handleResponse()` could execute before `put()` and the future would never complete.
-
-#### REST API
-
-| Method | Path | Description |
-| :--- | :--- | :--- |
-| `POST` | `/a2a/tasks?mode=sync` | Submit task synchronously (wait for result, 30s timeout) |
-| `POST` | `/a2a/tasks?mode=async` | Submit task asynchronously (return taskId immediately) |
-| `GET` | `/a2a/tasks/{taskId}` | Get task status and result |
-| `DELETE` | `/a2a/tasks/{taskId}` | Cancel a task |
-| `GET` | `/a2a/tasks/{taskId}/wait` | Long-poll wait for task result (configurable timeout) |
-| `GET` | `/a2a/tasks/{taskId}/stream` | **SSE** stream of task status updates (`text/event-stream`) |
-| `GET` | `/a2a/agents` | List all registered agents |
-| `POST` | `/a2a/heartbeat` | Agent heartbeat (keeps AgentCard alive) |
-| `GET` | `/a2a/cards/list` | List all AgentCards |
-| `POST` | `/a2a/cards/card/{org}/{unit}/{agent}` | Register an AgentCard |
-
-#### SSE Streaming
-
-The `GET /a2a/tasks/{taskId}/stream` endpoint provides real-time task status updates via Server-Sent Events:
-
-1.  Client opens an HTTP connection with `Accept: text/event-stream`.
-2.  Server sends initial state immediately.
-3.  As task transitions (WORKING → COMPLETED/FAILED/CANCELLED), server pushes `data:` events.
-4.  On terminal state, server sends final event and closes the connection.
-
-The handler writes directly to the Netty channel (returns `null` to skip the default `writeAndFlush` path), using `DefaultHttpContent` chunks with `text/event-stream` content type.
-
-#### A2AClient SDK
-
-The `A2AClient` provides a typed Java API for agent developers:
-
-```java
-A2AClient client = A2AClient.builder()
-    .gatewayUrl("http://localhost:10105")
-    .namespace("global")
-    .agentName("my-agent")
-    .agentCard(card)
-    .heartbeatInterval(30_000)
-    .build();
-client.start();
-
-// Typed return: TaskResult instead of raw JSON
-TaskResult result = client.sendTaskSync("weather-agent", "Beijing", null);
-String taskId = client.sendTaskAsync("weather-agent", "Shanghai", null);
-TaskResult status = client.getTaskStatus(taskId);
-List<String> agents = client.listAgents();  // typed List<String>
-boolean ok = client.cancelTask(taskId);
-```
-
-`TaskResult` uses `@JsonAlias("result")` to handle the server's `result` field name while exposing a `data` property to callers.
-
-### 3.4 Asynchronous RPC Mapping ( The "Async Bridge" )
-
-To support MCP on an Event Bus, synchronous RPC concepts are mapped to asynchronous events:
-
-| Concept | MCP / JSON-RPC | CloudEvent Mapping |
-| :--- | :--- | :--- |
-| **Action** | `method` (e.g., `tools/call`) | **Type**: `org.apache.eventmesh.a2a.tools.call.req`<br>**Extension**: `a2amethod` |
-| **Correlation** | `id` (e.g., `req-123`) | **Extension**: `collaborationid` (on Response)<br>**ID**: Preserved on Request |
-| **Direction** | Implicit (Request vs Result) | **Extension**: `mcptype` (`request` or `response`) |
-| **P2P Routing** | `params._agentId` | **Extension**: `targetagent` |
-| **Pub/Sub Topic** | `params._topic` | **Subject**: The topic value (e.g. `market.btc`) |
-| **Streaming Seq** | `params._seq` | **Extension**: `seq` |
-
-## 4. Functional Specification
-
-### 4.1 Message Processing Flow
-
-1.  **Ingestion**: The adaptor receives a `ProtocolTransportObject` (byte array/string).
-2.  **Detection**: Checks for `jsonrpc: "2.0"`.
-3.  **Transformation (MCP Mode)**:
-    *   **Request**: Parses `method`.
-        *   If `message/sendStream`, sets type suffix to `.stream` and extracts `_seq`.
-        *   If `_topic` present, sets `subject` (Pub/Sub).
-        *   If `_agentId` present, sets `targetagent` (P2P).
-    *   **Response**: Parses `result`/`error`. Sets `collaborationid` = `id`.
-4.  **Batch Processing**: Splits JSON Array into a `List<CloudEvent>`.
-
-### 4.2 Key Features
-
-#### A. Intelligent Routing Support
-*   **Mechanism**: Promotes `_agentId` or `_topic` from JSON body to CloudEvent attributes.
-*   **Benefit**: Enables EventMesh Router to perform content-based routing (CBR) efficiently.
-
-#### B. Batching
-*   **Benefit**: Significantly increases throughput for high-frequency interactions.
-
-#### C. Streaming Support
-*   **Operation**: `message/sendStream`
-*   **Mechanism**: Maps to `.stream` event type and preserves sequence order via `seq` extension attribute.
-
-#### D. SSE Task Streaming (Gateway)
-*   **Endpoint**: `GET /a2a/tasks/{taskId}/stream`
-*   **Mechanism**: Server-Sent Events (`text/event-stream`) pushes real-time task state transitions to the client.
-*   **Flow**: Initial state → WORKING updates → terminal state (COMPLETED/FAILED/CANCELLED) → connection close.
-*   **Implementation**: Handler writes `DefaultHttpContent` chunks directly to the Netty channel, bypassing the standard `FullHttpResponse` path.
-
-#### E. Task TTL Auto-Cleanup (Gateway)
-*   **Problem**: Completed/failed tasks accumulate in `TaskRegistry` indefinitely, causing memory leaks.
-*   **Solution**: A daemon `ScheduledExecutorService` (`a2a-task-ttl-cleanup` thread) runs every 60 seconds, removing terminal-state tasks older than the TTL (default: 5 minutes).
-*   **Configuration**: `TaskRegistry(taskTtlMs, cleanupIntervalMs)` constructor allows custom tuning.
-
-#### F. AgentCard Discovery & Heartbeat (Gateway)
-*   **Registration**: `POST /a2a/cards/card/{org}/{unit}/{agent}` registers an `AgentCard`.
-*   **Heartbeat**: `POST /a2a/heartbeat` refreshes the agent's last-seen timestamp. Cards expire after 60 seconds without heartbeat.
-*   **Discovery**: `GET /a2a/agents` returns all live agent cards.
-
-## 5. Usage Examples
-
-### 5.1 JSON-RPC 2.0 (MCP) Mode
-
-This mode is ideal for LLMs, scripts, and simple integrations where you want to send raw JSON without worrying about CloudEvent headers.
-
-#### 5.1.1 Sending a Tool Call (RPC Request)
-
-**Client Sends (Raw JSON):**
-```json
-{
-  "jsonrpc": "2.0",
-  "method": "tools/call",
-  "params": {
-    "name": "weather",
-    "city": "Shanghai",
-    "_agentId": "weather-agent"
-  },
-  "id": "req-101"
-}
-```
-
-**EventMesh Converts to:**
-*   **Type**: `org.apache.eventmesh.a2a.tools.call.req`
-*   **Extension (targetagent)**: `weather-agent`
-*   **Extension (mcptype)**: `request`
-
-#### 5.1.2 Pub/Sub Broadcast (Notification)
-
-**Client Sends (Raw JSON):**
-```json
-{
-  "jsonrpc": "2.0",
-  "method": "notifications/alert",
-  "params": {
-    "message": "System Maintenance in 10 mins",
-    "_topic": "system.alerts"
-  }
-}
-```
-
-**EventMesh Converts to:**
-*   **Type**: `org.apache.eventmesh.a2a.notifications.alert`
-*   **Subject**: `system.alerts`
-*   **Extension (mcptype)**: `notification`
-
-#### 5.1.3 Java SDK Example (MCP Mode)
-
-```java
-// See eventmesh-examples/src/main/java/org/apache/eventmesh/a2a/demo/mcp/McpCaller.java
-
-Map<String, Object> request = new HashMap<>();
-request.put("jsonrpc", "2.0");
-request.put("method", "tools/call");
-request.put("params", Map.of("name", "weather", "_agentId", "weather-agent"));
-request.put("id", UUID.randomUUID().toString());
-
-CloudEvent event = CloudEventBuilder.v1()
-    .withType("org.apache.eventmesh.a2a.tools.call.req")
-    .withData(JsonUtils.toJSONString(request).getBytes())
-    .withExtension("protocol", "A2A") // Critical to trigger A2A adaptor
-    .build();
-
-producer.publish(event);
-```
-
-### 5.2 Native CloudEvents Mode
-
-This mode provides full control over all CloudEvent attributes and is recommended for robust, typed applications using the EventMesh SDK.
-
-#### 5.2.1 Native RPC Request
-
-**Client Sends (CloudEvent):**
-```json
-{
-  "specversion": "1.0",
-  "type": "com.example.rpc.request",
-  "source": "my-app",
-  "id": "evt-123",
-  "data": "...",
-  "protocol": "A2A",
-  "targetagent": "target-agent-001"
-}
-```
-
-**Java SDK Example:**
-```java
-// See eventmesh-examples/src/main/java/org/apache/eventmesh/a2a/demo/ce/CloudEventsCaller.java
-
-CloudEvent event = CloudEventBuilder.v1()
-    .withId(UUID.randomUUID().toString())
-    .withSource(URI.create("ce-client"))
-    .withType("com.example.rpc.request")
-    .withData("application/text", "RPC Payload".getBytes())
-    .withExtension("protocol", "A2A")
-    .withExtension("targetagent", "target-agent-001") // Explicit routing
-    .build();
-
-producer.publish(event);
-```
-
-#### 5.2.2 Native Pub/Sub
-
-**Client Sends (CloudEvent):**
-```json
-{
-  "specversion": "1.0",
-  "type": "com.example.notification",
-  "source": "my-app",
-  "subject": "broadcast.topic",
-  "protocol": "A2A"
-}
-```
-
-#### 5.2.3 Native Streaming
-
-**Client Sends (CloudEvent):**
-```json
-{
-  "specversion": "1.0",
-  "type": "com.example.stream",
-  "source": "my-app",
-  "subject": "stream-topic",
-  "protocol": "A2A",
-  "sessionid": "session-555",
-  "seq": "1"
-}
-```
-
-### 5.3 Gateway REST API (HTTP)
-
-The A2A Gateway provides a REST API for external clients and non-Java agents.
-
-#### 5.3.1 Submit Task (Sync)
-
-```bash
-curl -X POST 'http://localhost:10105/a2a/tasks?mode=sync' \
-  -H 'Content-Type: application/json' \
-  -d '{"targetAgent":"weather-agent","message":"Beijing"}'
-```
-
-Response:
-```json
-{
-  "taskId": "task-a1b2c3d4",
-  "state": "COMPLETED",
-  "data": "The weather in Beijing is sunny, 25°C"
-}
-```
-
-#### 5.3.2 Submit Task (Async)
-
-```bash
-curl -X POST 'http://localhost:10105/a2a/tasks?mode=async' \
-  -H 'Content-Type: application/json' \
-  -d '{"targetAgent":"weather-agent","message":"Shanghai"}'
-```
-
-Response (HTTP 202):
-```json
-{
-  "taskId": "task-e5f6g7h8",
-  "status": "accepted",
-  "message": "Task submitted. Use GET /a2a/tasks/task-e5f6g7h8 to check status."
-}
-```
-
-#### 5.3.3 SSE Stream
-
-```bash
-curl -N http://localhost:10105/a2a/tasks/task-a1b2c3d4/stream
-```
-
-Response (`text/event-stream`):
-```
-data: {"taskId":"task-a1b2c3d4","state":"SUBMITTED"}
-
-data: {"taskId":"task-a1b2c3d4","state":"WORKING","data":"processing..."}
-
-data: {"taskId":"task-a1b2c3d4","state":"completed","data":"The weather in Beijing is sunny, 25°C"}
-```
-
-#### 5.3.4 List Agents
-
-```bash
-curl http://localhost:10105/a2a/agents
-```
-
-### 5.4 A2AClient SDK (Java)
-
-```java
-A2AClient client = A2AClient.builder()
-    .gatewayUrl("http://localhost:10105")
-    .namespace("global")
-    .agentName("my-agent")
-    .agentCard(card)
-    .heartbeatInterval(30_000)
-    .build();
-
-client.start();
-
-// Synchronous task (returns typed TaskResult)
-TaskResult result = client.sendTaskSync("weather-agent", "Beijing", null);
-
-// Asynchronous task (returns taskId immediately)
-String taskId = client.sendTaskAsync("weather-agent", "Shanghai", null);
-
-// Poll status
-TaskResult status = client.getTaskStatus(taskId);
-
-// Cancel
-boolean cancelled = client.cancelTask(taskId);
-
-// List registered agents (typed List<String>)
-List<String> agents = client.listAgents();
-
-client.shutdown();
-```
-
-## 6. Future Roadmap
-
-*   **EventMesh Broker Integration**: Replace `InMemoryA2AMessageTransport` with the real EventMesh broker for production deployment.
-*   **Schema Registry**: Implement dynamic discovery of Agent capabilities via `methods/list`.
-*   **Sidecar Injection**: Fully integrate the adaptor into the EventMesh Sidecar for non-Java agents (Python, Node.js).
-*   **WebSocket Streaming**: Extend SSE to bidirectional WebSocket for real-time agent-to-agent dialogue.
-*   **Task Persistence**: Persist `TaskRegistry` state to a durable store (Redis/DB) for crash recovery.
-*   **Authentication**: Add API key / JWT authentication to the Gateway REST API.
diff --git a/docs/eventmesh-cloudevents-client-guide.md b/docs/eventmesh-cloudevents-client-guide.md
index 689c33e4f..cf0bfb488 100644
--- a/docs/eventmesh-cloudevents-client-guide.md
+++ b/docs/eventmesh-cloudevents-client-guide.md
@@ -271,4 +271,151 @@ EVENTMESH_STORAGE_TYPE=kafka bin/start.sh
 - 4.x 存储插件:`eventmesh-storage-plugin/eventmesh-storage-rocketmq`(SPI key `rocketmq`)
 - 5.x 存储插件:`eventmesh-storage-plugin/eventmesh-storage-rocketmq5`(SPI key `rocketmq5`,含 `LiteTopicCapable`)
 - Kafka 存储插件:`eventmesh-storage-plugin/eventmesh-storage-kafka`(SPI key `kafka`,assign+seek+poll + SASL 透传)
-- 设计文档:`docs/eventmesh-uni-architecture-redesign.md`
+- 设计文档:`docs/eventmesh-uni-architecture-redesign.md`(§20–§21 流式调用设计)
+
+---
+
+# 流式调用使用指南(整合 sdk-streaming-call-guide)
+
+> 面向**应用开发者**:如何用 EventMesh 客户端 SDK 做 LLM 风格的**流式调用**(token 逐片返回、支持多轮上下文)。
+
+## 7. 流式调用:它是什么
+
+流式调用让客户端发起一次请求后,**持续接收一串文本分片(token / delta)**,直到收到结束标记——和 OpenAI Chat Completions 的 `stream: true`、SSE 打字机效果一致。
+
+EventMesh 提供两种流式模式:
+
+| 模式 | 用途 | 入口 | 角色 |
+|------|------|------|------|
+| **Mode 1 — 流式调用** | 客户端发起请求,Agent(接 LLM)逐 token 回复 | `openSession` → `call` → `forEach` | HTTP/SSE 客户端(无 MQ 依赖) |
+| **Mode 2 — 发布/订阅** | 生产者往 session 的 MQ 通道写 chunks,消费者通过 SSE 订阅读取 | `subscribeSession` / `openSessionPublisher` | 生产者 + 消费者(解耦、跨进程、跨时间) |
+
+**前置条件**:一个已运行的 EventMesh Runtime(默认 `http://localhost:8080`),其上注册了至少一个流式 Agent(Mode 1)或启用了 Mode 2 的发布/订阅。
+
+---
+
+## 8. Mode 1:流式调用(30 秒快速开始)
+
+```java
+import org.apache.eventmesh.client.cloudevents.CloudEventsClient;
+import org.apache.eventmesh.client.cloudevents.stream.*;
+
+CloudEventsClient client = CloudEventsClient.builder()
+        .runtimeUrl("http://localhost:8080")
+        .clientId("my-app")
+        .build();
+
+// 开一个 session,发一轮,收 token,关 session
+StreamingSession session = client.streaming()
+        .openSession(OpenSession.builder().clientId(client.clientId()).build());
+try {
+    try (StreamingResponse r = session.call("用三句话介绍 EventMesh")) {
+        r.forEach(chunk -> System.out.print(chunk.getChunk())).join();
+    }
+} finally {
+    session.close();
+}
+```
+
+`forEach` 每收到一个分片就回调一次,`.join()` 阻塞到流结束。
+
+---
+
+## 9. Mode 1:多轮会话(Session)
+
+```java
+StreamingSession session = client.streaming()
+        .openSession(OpenSession.builder().clientId("my-app").build());
+
+try {
+    System.out.println("sessionId = " + session.sessionId());
+
+    // 第一轮
+    try (StreamingResponse r1 = session.call("我叫张三,是一名 Java 工程师")) {
+        r1.forEach(chunk -> System.out.print(chunk.getChunk())).join();
+    }
+
+    // 第二轮(同一 session,Agent 记得上一轮)
+    try (StreamingResponse r2 = session.call("我叫什么名字?做什么工作?")) {
+        r2.forEach(chunk -> System.out.print(chunk.getChunk())).join();
+    }
+} finally {
+    session.close();   // 最终销毁会话(幂等)
+}
+```
+
+**三条极易踩的语义**:
+1. 关闭某一轮的 `StreamingResponse` 只结束这一轮的读取,**不会关闭 session**。
+2. 多轮上下文由 Agent 端的 `ConversationStore` 按 `sessionId` 维护。
+3. `OpenSession` 有 `clientId`(必填)、`model`(可选);`StreamRequest` 有 `prompt`(必填)、`model`(可选)、`timeout`(可选)。
+
+---
+
+## 10. Mode 2:发布/订阅
+
+Mode 2 把 session 的流式数据**外化到 MQ**(LiteTopic),实现**跨进程/跨时间**的流式消费。
+
+### 10.1 订阅(消费)
+
+```java
+StreamingResponse sub = client.subscribeSession("my-session-id");
+sub.forEach(chunk -> {
+    System.out.println("[" + chunk.getSeq() + "] " + chunk.getChunk());
+}).join();
+sub.close();
+```
+
+### 10.2 发布(生产)
+
+```java
+SessionPublisher pub = client.openSessionPublisher("my-session-id");
+pub.publish("Hello", false);     // 非终止帧
+pub.publish(" world", false);
+pub.publish("", true);           // 终止帧 → 订阅者的 forEach 完成
+pub.close();
+```
+
+---
+
+## 11. Mode 1:实现一个流式 Agent
+
+Agent 收到请求后契约四步:
+1. 解析 `sessionId`、`prompt`、`replyTo`
+2. 每产生一个 token → 发非终止帧 `{chunk: token, done: false}`
+3. 流正常结束 → 发终止帧 `{chunk: "", done: true}`
+4. 出异常 → 发终止错误帧 `{chunk: "", done: true, error: "..."}`
+
+参考实现 `eventmesh-agent/.../StreamingAgent.java` 接 OpenAI 兼容 LLM:
+
+```java
+StreamingAgent agent = new StreamingAgent(agentClient, agentParent, agentId, llm, conversations);
+agent.start();          // 订阅 agent 控制信道
+control.ready(agentId); // 向 runtime 报告就绪
+```
+
+---
+
+## 12. 部署配置
+
+### Mode 1(流式调用)
+
+预创建 agent + client parent topics,配 `AgentAnchoredStrategy`,4 参 SessionRouter。
+
+### Mode 2(发布/订阅,可选)
+
+额外预创建 `sessionStreamParent`,用 6 参 SessionRouter(`sessionTtlMs` + `sessionStreamParent`)。
+
+两种模式可同时启用(入口不同,互不干扰)。
+
+---
+
+## 13. 流式调用常见问题
+
+**Q: Mode 1 和 Mode 2 怎么选?**
+需要 Agent 承载 LLM、多轮对话上下文 → Mode 1。需要跨进程/跨时间消费流式数据、崩溃恢复 → Mode 2。
+
+**Q: 报 `IllegalStateException: posture already active`?**
+同一个 `StreamingResponse` 只能用一次 `forEach`。
+
+**Q: 如何排错"收不到响应"?**
+① Runtime 是否起、Agent 是否 `ready` + 心跳;② `/session/open` 是否返回 200 且有 `agentId`;③ parent 是否预创建为 LiteTopic;④ broker 是否开了 LiteTopic。
diff --git a/docs/eventmesh-offset-lb-frame-design.md b/docs/eventmesh-offset-lb-frame-design.md
new file mode 100644
index 000000000..7441d9192
--- /dev/null
+++ b/docs/eventmesh-offset-lb-frame-design.md
@@ -0,0 +1,388 @@
+# EventMesh Offset 管理 · 负载均衡 · Frame 协议转换 设计总结
+
+> 整合 offset 管理、全面粘性负载均衡、EventMeshFrame 协议转换三块设计的完整方案(2026-08-13)。
+> 关联文档:[`eventmesh-uni-architecture-redesign.md`](./eventmesh-uni-architecture-redesign.md) §19 架构深化。
+
+---
+
+## 目录
+
+- [一、Offset 管理](#一offset-管理)
+- [二、负载均衡](#二负载均衡)
+- [三、Frame 协议转换](#三frame-协议转换)
+
+---
+
+## 一、Offset 管理
+
+### 1.1 架构定位
+
+**EventMesh 完全自管 offset,不给 group.id,不上报 meta。** MQ(RocketMQ/Kafka)仅作持久 FIFO 日志,不使用其 Consumer Group 语义。
+
+### 1.2 两层 offset
+
+| 层 | 含义 | 存储 | 接管行为 |
+|---|---|---|---|
+| **pull offset**(从 MQ 拉到哪) | `parent#lite@queue`(lite)/ `topic#partition`(普通) | storage 插件本地(rocketmq5 = properties 文件,5s 原子写 checkpoint) | 实例挂 → 新实例从 head 重拉 |
+| **deliver/ack offset**(投递+ACK 进度) | `topic#clientId#partition` | `RocksDBOffsetStore`(本地) | 实例挂 → 新实例 readOffset=-1 → 从 head 重拉 → in-flight 重投 |
+
+两层**不强行合并**(语义不同、解耦):pull 层在 storage 插件内部(自管游标),deliver/ack 层在 runtime OffsetStore。`pullAndDispatchPartition` 传 `startOffset=-1` 给 `storage.poll`(插件自管),OffsetStore 不在接管路径上。
+
+### 1.3 核心机制
+
+**正常路径**:
+
+```
+storage.poll()
+  → pull offset 自管(不向 broker 提交 commitOffset)
+  → pullAndDispatchPartition(传 startOffset=-1,插件自管游标)
+  → ReliableDispatcher.deliver()
+    → Delivery(携带 event + mqAckCallback)
+    → PushChannel.deliver() → 投递给客户端
+  → 客户端 ACK → ReliableDispatcher.ack()
+    → offsetStore.writeOffset()(CAS max,只进不退)
+    → storage.ackPulledMessage(topic, popCk)(RocketMQ 5.x:此时才 ACK broker)
+```
+
+**接管(实例挂)**:
+
+```
+实例挂
+  → client 经 /session/recommend 重连新实例(§二负载均衡)
+  → 新实例从业务 topic 重拉
+    · lite:pollLite 从 lite 头重放(单游标天然可重放)
+    · 普通:poll 从最早未 ACK/head 起
+  → in-flight 未 ACK 消息 → at-least-once 重投(业务幂等兜底)
+  → 不写快照、不迁移 offset 状态、不上报 meta
+```
+
+### 1.4 三后端差异
+
+| 后端 | pull 机制 | ACK 机制 | 重启恢复 |
+|---|---|---|---|
+| **RocketMQ 5.x** | POP_MESSAGE(broker 管分配,partitionCount=-1 → poll-all) | **延迟 ACK**:poll() 不立即 ACK broker;`ackPulledMessage()` 在客户端 ACK 后触发 broker ACK。崩溃不丢(broker invisibleTime 30s 过期自动重投)。 | pull offset 本地 properties 文件续传(5s checkpoint) |
+| **RocketMQ 4.x** | PULL_MESSAGE(自管游标,固定 CONSUMER_GROUP,不提交 commitOffset) | 无 MQ ACK(PULL 模式) | pull offset 本地 properties 文件续传 |
+| **Kafka** | assign+seek(无 group.id,ENABLE_AUTO_COMMIT=false) | 无 MQ ACK | pull offset 本地 properties 文件续传 |
+
+### 1.5 延迟 ACK 详解(RocketMQ 5.x,P2 修复)
+
+**问题**:原来 `poll()` 内立即 `ackNormal(brokerAddr, msg)` → broker 认为已消费 → EventMesh 崩溃丢消息(At-Most-Once)。
+
+**修复链路**:
+
+```
+① poll()
+   → POP_MESSAGE 拉取消息
+   → 不立即 ACK broker
+   → 存 deferred ACK callback(key = PROPERTY_POP_CK)
+   → EventMeshFrame 属性 stamp "empopck" = POP check key
+
+② pullAndDispatchPartition()
+   → 读取 frame.attributes().get("empopck")
+   → 构建 mqAck = () → storage.ackPulledMessage(topic, popCk)
+   → dispatcher.deliver(topic, partition, offset, frame, clientId, channel, mqAck)
+   → Delivery 存储 mqAckCallback
+
+③ 客户端 ACK → ReliableDispatcher.ack(deliveryId)
+   → offsetStore.writeOffset()(offset 推进)
+   → delivery.getMqAckCallback().run() → storage.ackPulledMessage(topic, popCk)
+   → pendingPopAcks.remove(popCk) → 执行 broker ACK_MESSAGE
+   → broker 确认消费完成
+```
+
+**at-least-once 保证**:如果 EventMesh 在 ① 和 ③ 之间崩溃,broker 的 invisibleTime(30s)过期 → 自动重投 → 新实例重新拉取处理。
+
+### 1.6 OffsetStore 单调写入(P4 修复)
+
+**问题**:`writeOffset` 直接覆盖(`set(offset)`),重启后快消费组的 offset 被慢消费组的重放消息拉低。
+
+**修复**:
+- `InMemoryOffsetStore`:`AtomicLong.accumulateAndGet(offset, Math::max)` —— CAS max
+- `RocksDBOffsetStore`:read current → if `offset <= current` skip → else put —— 条件写
+
+### 1.7 已淘汰
+
+- **MetaBackedOffsetStore**(百万 key 1s 上报 meta)→ 默认关(`-Deventmesh.offset.meta=true` 显式开),类保留备用。
+- 不给 group.id(EventMesh 自主 offset 铁律不变)。
+
+### 1.8 待办
+
+- **P1:RocketMQ 4.x ConsumeQueue 清理** —— `commitOffset` 仍是 no-op;需实际验证 RocketMQ 4.x 的 ConsumeQueue 清理是否真依赖消费位点(CommitLog 基于时间清理 `fileReservedTime`,可能不依赖)。
+
+---
+
+## 二、负载均衡
+
+### 2.1 架构定位
+
+> **均衡做在 session 分配层(入口 recommend),不在拉取/分发层。**
+
+实例只为自己代理的 client 被动按需拉取,不对等、不转发。先均衡"哪个 client 归哪个实例"(session 层),自然导致"各实例拉取量大致相当"——而不是"先对等拉取再均衡分发"。
+
+### 2.2 全面粘性模型
+
+```
+旧模型(已停用):                          新模型(全面粘性):
+  PartitionOwnership(partition%n 分配)      × 无分区归属
+  ClusterCoordinator(跨实例转发)            × 不转发
+  HttpForwarder(HTTP 转发到 peer)           × 不转发
+  ClusterSubscriptionStore(集群订阅表)      × 本地订阅
+
+enableCluster 只保留:
+  ClusterMembership(心跳 + 负载指标)         供 /session/recommend 全局评分
+```
+
+转发类(PartitionOwnership / ClusterCoordinator / HttpForwarder)保留不删(备用 + 测试)。
+
+### 2.3 客户端零负担——负载全自采
+
+EventMesh 实例本地自采负载指标(`LoadMeter`),客户端不发任何上报。
+
+| 指标 | 采集来源 |
+|---|---|
+| `activeSessions` | `SessionRouter` sinks / subscribeSinks size |
+| `inflowBytes/s` | `UniIngressService` publish 字节累加(按 clientId 分桶 → 每 client 流量画像) |
+| `outflowBytes/s` | SSE / poll 出口字节累加 |
+| `cpuLoad` | `OperatingSystemMXBean` |
+
+随现有 5s 心跳(`PartitionOwnership` 线程)写入 `/em/instances/<id>` = `<ts>|<addr>|<activeSessions>|<byteRate>|<cpuLoad>`。
+
+### 2.4 均衡闭环
+
+```
+① client 首次连接任意实例
+   │
+② GET /session/recommend?clientId=xxx
+   │ → 该实例读集群全局 /em/instances/(全部实例 + 负载)
+   │ → 评分:score = activeSessions×w1 + byteRate×w2 + cpuLoad×w3
+   │ → 过载负反馈:cpuLoad>0.8 或 inflow>5MB/s → score += 10000(让出)
+   │ → 大 client 分散:检查 client 现有 session 分布,优先未占满实例
+   │
+③ 返回推荐 instanceUrl
+   │
+④ POST /session/open → {sessionId, agentId, instanceUrl}
+   │ → SDK pin 后续 turn/close 到该实例
+   │
+⑤ POST /events/subscribe → {subscriptionId, instanceUrl}
+   │ → SDK pin 后续 poll/ack 到该实例
+   │
+⑥ 后续所有请求直连该实例
+   │ → 实例只为自己 client 拉,不对等,不转发
+   │
+⑦ 失败/实例不可达 → SDK 重新 GET /session/recommend 拿另一实例
+```
+
+### 2.5 session 粒度粘性
+
+- `/session/open` 和 `/events/subscribe` 返回 `instanceUrl`。
+- SDK `SessionHandle.instanceUrl` → `StreamingSession` 用 `client.withBaseUrl(instanceUrl)` pin 后续 turn/close。
+- SDK `subscribe` → `capturePollInstance` 设 `pollBaseUrl`,后续 poll+ack 走该实例。
+- **`advertisedAddr` 默认空**(单实例/测试/LB 兼容);显式配 `-Deventmesh.http.advertisedAddr=host:port` 才 pin。
+
+### 2.6 代价
+
+- 无转发 = 订阅者必须粘性(否则 poll 落别的实例收不到)——由 subscribe→instanceUrl 保证。
+- 同 topic 多订阅者散在多实例 → 每实例各自拉全分区(MQ 读放大,N 实例 = N 份读)——换掉转发跳 + 全局协调复杂度。
+- 单 session 流量超单实例上限(不可拆)→ 实例限流/拒绝——无解边界。
+
+---
+
+## 三、Frame 协议转换
+
+### 3.1 架构分层
+
+```
+┌─ 对外协议层(FrameAdaptor SPI)──────────────────────────────────────┐
+│                                                                     │
+│  CloudEvents(HTTP/SSE/WS)  → CloudEventsFrameAdaptor  → EventMeshFrame │
+│  MeshMessage(legacy TCP)   → MeshMessageFrameAdaptor   → EventMeshFrame │
+│  A2A(JSON-RPC 2.0)         → A2AFrameAdaptor           → EventMeshFrame │
+│  未来新协议                → 新 FrameAdaptor            → EventMeshFrame │
+│                                                                     │
+│  三种协议平级,各自直接转 Frame,互不经过 CloudEvent                   │
+└───────────────────────────────┬─────────────────────────────────────┘
+                                │ 对外协议直接转 Frame
+┌───────────────────────────────▼─────────────────────────────────────┐
+│  内部(runtime + storage):全程 EventMeshFrame                          │
+│                                                                       │
+│  ingress publish → Frame → storage.send(Frame) → MQ 字节              │
+│  MQ 字节 → storage.poll()→Frame → dispatch/filter/TTL → Frame         │
+└───────────────────────────────┬─────────────────────────────────────┘
+                                │ egress:Frame → 对应 FrameAdaptor
+┌───────────────────────────────▼─────────────────────────────────────┐
+│  egress(FrameAdaptor SPI,按客户端连接协议)                              │
+│                                                                       │
+│  Frame → CloudEvents-JSON(SSE / WS / HTTP poll)                      │
+│  Frame → MeshMessage Package(legacy TCP)                             │
+│  Frame → A2A JSON-RPC bytes(A2A 回调)                                │
+└───────────────────────────────────────────────────────────────────────┘
+```
+
+**CloudEvent 不是内部表示**——它是 CloudEvents 客户端的对外入口格式之一,经 `CloudEventsFrameAdaptor` 转 Frame 后进入内部。MeshMessage 和 A2A 同理,各自有独立 adaptor,互不经过 CloudEvent。
+
+### 3.2 EventMeshFrame wire 格式
+
+```
+定长头 14B:
+  [magic:1=0xEF][ver:1=1][msgType:1][flags:1][seq:4][keyCount:2][dataLen:4]
+
+KV 属性段(keyCount ×):
+  [nameLen:2][name:UTF-8][valLen:4][value:UTF-8]
+
+data:
+  raw bytes(streaming 的 chunk/prompt / 事件的业务 data)
+
+msgType = STREAM_REQ(1) | STREAM_CHUNK(2) | EVENT(3)
+flags   = bit0 done | bit1 hasError | bit2 hasMeta(streaming 用)
+```
+
+### 3.3 三种 msgType 的字段映射
+
+| msgType | 定长头字段 | KV 属性 | data |
+|---|---|---|---|
+| **STREAM_REQ** | — | `sid`=sessionId, `replyTo`=回复地址, `model`?, `conv`? | prompt 文本 |
+| **STREAM_CHUNK** | `seq`=流内序号, `flags.done`=终止标记 | `sid`, `etype`?, `err`?, `meta`?(JSON) | chunk 文本 |
+| **EVENT** | — | `id`/`type`/`source`/`subject`/`time`/`emttl`/`emcorrelationid`/`empopck`/用户扩展 | 事件 payload |
+
+### 3.4 FrameAdaptor SPI
+
+对外协议 ↔ EventMeshFrame 的双向转换由 `FrameAdaptor` SPI 定义(`eventmesh-protocol-api/.../FrameAdaptor.java`):
+
+```java
+@EventMeshSPI(eventMeshExtensionType = EventMeshExtensionType.PROTOCOL)
+public interface FrameAdaptor {
+    EventMeshFrame toFrame(ProtocolTransportObject proto);       // ingress
+    ProtocolTransportObject fromFrame(EventMeshFrame frame);     // egress
+    String getProtocolType();
+}
+```
+
+runtime 不直接调 `EventMeshFrame.fromCloudEvent()` / `.toCloudEvent()` / `MeshMessageFrameCodec`——全部经 `FrameAdaptors.get(协议名)` 加载对应 adaptor。**加新协议只需实现 `FrameAdaptor` + 注册 SPI,不改 runtime 代码。**
+
+### 3.5 协议插件模块
+
+| 模块 | FrameAdaptor | 对外协议 | 转换方式 |
+|---|---|---|---|
+| **protocol-api** | —(只 SPI 接口 + `FrameAdaptors` 加载器) | — | 零实现 |
+| **protocol-cloudevents** | `CloudEventsFrameAdaptor` | CloudEvents-JSON(HTTP/SSE/WS) | CE-JSON bytes ↔ Frame(经 CE 对象做字段映射) |
+| **protocol-meshmessage** | `MeshMessageFrameAdaptor` | MeshMessage Package(TCP) | Package ↔ Frame(直接字段映射,零 CE 中转) |
+| **protocol-a2a** | `A2AFrameAdaptor` | A2A JSON-RPC 2.0 | JSON-RPC bytes ↔ Frame(直接字段映射,零 CE 中转) |
+| ~~protocol-grpc~~ | — | — | **已删除**(空壳无源码) |
+
+### 3.6 WireCodec SPI(内部 MQ wire 编码)
+
+内部 MQ wire 的字节编解码由 `WireCodec` SPI 定义(`eventmesh-common/.../wire/`):
+
+```java
+public interface WireCodec {
+    byte[] encode(StreamRequest request);
+    StreamRequest decodeRequest(byte[] bytes);
+    byte[] encode(StreamChunk chunk);
+    StreamChunk decodeChunk(byte[] bytes);
+    byte[] encode(CloudEvent event);
+    CloudEvent decodeEvent(byte[] bytes);
+}
+```
+
+- 默认实现 `EventMeshFrameCodec`(EventMeshFrame ↔ byte[])。
+- 可通过 `-Deventmesh.wire.codec=<fqcn>` 替换。
+
+### 3.7 各协议的完整转换链路
+
+#### CloudEvents(HTTP/SSE/WS 客户端)
+
+**Ingress**(客户端 → runtime):
+```
+SDK 发 CloudEvents-JSON bytes
+  → UniHttpServer.publish: body = CE-JSON bytes
+  → UniIngressService.publish(topic, CloudEvent): CE → EventMeshFrame.fromCloudEvent(event)
+  → storage.send(topic, frame): frame.encode() → MQ 字节
+```
+
+**Egress**(runtime → 客户端):
+```
+storage.poll() → frame (EventMeshFrame)
+  → SseConnection.send / WsConnection.send / UniHttpServer.poll
+  → FrameAdaptors.toCloudEventsJson(frame): frame.toCloudEvent() → CE-JSON serialize
+  → 写 SSE data: / WS TextFrame / HTTP JSON
+```
+
+#### MeshMessage(legacy TCP 客户端)
+
+**Ingress**(客户端 → runtime):
+```
+旧 TCP SDK 发 Package(EventMeshMessage)
+  → MeshMessagePackageRouter.route(pkg)
+  → FrameAdaptors.get("meshmessage").toFrameSilent(pkg)
+  → MeshMessageFrameAdaptor.toFrame:
+      topic → attributes["subject"]
+      body → data
+      header.seq → attributes["id"]
+      properties → attributes KV
+  → TcpRequest.publish(topic, frame)
+  → UniIngressService.publish(topic, frame)(Frame 重载,跳过 CE 转换)
+  → storage.send(topic, frame)
+```
+
+**Egress**(runtime → 客户端):
+```
+ReliableDispatcher.deliver → NettyTcpPushChannel.deliver(deliveryId, frame, callback)
+  → FrameAdaptors.get("meshmessage").fromFrameSilent(frame)
+  → MeshMessageFrameAdaptor.fromFrame:
+      attributes["subject"] → topic
+      data → body
+      attributes KV → properties
+  → Package(header=ASYNC_MESSAGE_TO_CLIENT, body=EventMeshMessage)
+  → channel.writeAndFlush(pkg)
+```
+
+#### A2A(JSON-RPC 客户端)
+
+**Ingress**:
+```
+A2A 客户端发 JSON-RPC 2.0 bytes
+  → FrameAdaptors.get("a2a").toFrameSilent(new ByteTransport(jsonBytes))
+  → A2AFrameAdaptor.toFrame:
+      parse JSON-RPC → extract method/params/_topic
+      method → attributes["ema2amethod"]
+      params._topic → attributes["subject"]
+      raw JSON → data
+  → EventMeshFrame
+```
+
+**Egress**:
+```
+EventMeshA2ATransport 收到 frame
+  → frame.data() = 原始 A2A JSON-RPC bytes(ingress 时原样保存)
+  → ByteTransport(jsonBytes) → A2A 客户端
+```
+
+### 3.8 Storage SPI 的 Frame 转换
+
+storage 插件(`MeshStoragePlugin` + `LiteTopicCapable`)的 send/poll 直接携带 EventMeshFrame:
+
+```
+send(topic, EventMeshFrame frame):
+  → rocketmq5: frame.encode() → MQ message body bytes
+  → kafka: frame.encode() → ProducerRecord value
+  → rocketmq4: frame.encode() → MQ message body bytes
+
+poll(topic, partition, ...):
+  → MQ bytes → EventMeshFrame.decode(bytes)
+  → 带 legacy CE-JSON fallback(旧消息 → EventMeshFrame.fromCloudEvent(deserialize))
+```
+
+### 3.9 落地范围
+
+| 层 | 改动 | 状态 |
+|---|---|---|
+| storage SPI | `MeshStoragePlugin.send/poll` + `LiteTopicCapable.sendLite/pullLite` 全改 EventMeshFrame | ✅ |
+| runtime dispatch | Delivery/BufferedEvent/PushChannel/Connection/DeadLetterSink/ReliableDispatcher/PushService + CloudEventFilter/SubscriptionManager 全翻 Frame | ✅ |
+| streaming | Mode-1(runtime↔agent 跨进程)+ Mode-2(runtime 内部 pub/sub)全用 EventMeshFrame | ✅ |
+| legacy 连接器 | Producer/Consumer SPI 不动(独立子系统,说 CE,边界转换) | ✅ 保留 |
+| WireCodec SPI | 默认 EventMeshFrameCodec,可替换 | ✅ |
+| FrameAdaptor SPI | CloudEvents 独立插件 + MeshMessage + A2A 各有独立 adaptor | ✅ |
+
+---
+
+**文档版本**: v1.0 · **整理时间**: 2026-08-13 · 关联: [`eventmesh-uni-architecture-redesign.md`](./eventmesh-uni-architecture-redesign.md) §19
diff --git a/docs/eventmesh-uni-architecture-redesign.md b/docs/eventmesh-uni-architecture-redesign.md
index 62c5eb42f..02bbae08b 100644
--- a/docs/eventmesh-uni-architecture-redesign.md
+++ b/docs/eventmesh-uni-architecture-redesign.md
@@ -1,8 +1,14 @@
 # EventMesh 架构简化重构方案
 
-> 分支:`refactor/unified-runtime-pipeline` → 基于本次讨论的全新方向
+> 分支:`refactor/unified-runtime-pipeline` → 基于 本次讨论的全新方向
 >
-> 核心变化:**丢弃 MQ 的 Producer Group / Consumer Group 语义,MQ 仅作存储层,EventMesh 自主维护订阅分发语义(负载均衡 / 多播 / 广播)。SDK 简化为 HTTP-only,全部基于 CloudEvents。**
+> 核心变化:**丢弃 MQ 的 Producer Group / Consumer Group 语义,MQ 仅作存储层,EventMesh 自主维护订阅分发语义。内部全程 `EventMeshFrame`(CloudEvent 退化为对外入口格式之一);对外多协议(CloudEvents / MeshMessage / A2A)经 `FrameAdaptor` SPI 直接转 Frame,互不转换。SDK 简化为 HTTP-only。**
+>
+> **v2.0 架构深化(2026-08-13 整合 `eventmesh-architecture-refinement.md`)**:
+> - 内部全程 `EventMeshFrame`(storage SPI + dispatch 管线 + 载体全翻 Frame)。
+> - 对外协议转换收进 `FrameAdaptor` SPI(CloudEvents/MeshMessage/A2A 各独立插件,零 CE 互转)。
+> - Offset 路②(纯本地、不上报 meta、不给 group.id、接管靠 MQ 重放)。
+> - 全面粘性负载均衡(session 分配层 recommend、实例自采负载、不转发)。
 
 ---
 
@@ -28,10 +34,10 @@
 | **EventMesh 自主订阅** | 订阅分发逻辑(谁收哪条消息、按什么策略分发)全部由 EventMesh 自己维护,不委托给 MQ |
 | **EventMesh 自主 Offset** | EventMesh 自主管理每个订阅关系的消费位点(topic#clientId#partition → offset),参考 RocketMQ Client OffsetStore 实现,使用 RocksDB 持久化,不依赖 MQ Consumer Group offset |
 | **SDK 极简** | 只有一个 SDK(HTTP),只暴露两个对象(CloudEvent + Subscription),只有三个 API(publish / subscribe / unsubscribe) |
-| **全链路 CloudEvents** | 从 SDK → EventMesh Runtime → MQ 存储 → 下发客户端,全部使用 CloudEvents 1.0 规范,不再有自定义的 EventMeshMessage / Package 等中间格式 |
+| **内部 EventMeshFrame** | 内部全程 `EventMeshFrame`(定长头+KV属性+raw data),CloudEvent 退化为对外入口格式之一;对外多协议(CloudEvents/MeshMessage/A2A)经 `FrameAdaptor` SPI 直接转 Frame,互不转换 |
 | **Connector 独立部署** | Connector Runtime 是独立进程,与 EventMesh Runtime 通过 HTTP + CloudEvents 接口通信,不共享内部组件,各自有独立的生命周期和 OffsetStore |
 
-> **🔎 实现状态速览(v1.11 / 2026-07-06 盘点)**:五条铁律中,"全链路 CloudEvents / Connector 独立部署 / SDK 极简(HTTP-only)"已落实;"EventMesh 自主订阅 / 自主 Offset"单实例成立。**但"MQ 无语义"在 Kafka 实现侧有矛盾**——`KafkaMeshStoragePlugin` 仍设 `group.id`(附录 F G7),多实例下会触发 Kafka rebalance,须修复。各铁律的逐 Phase 落实状态见 §11 各 Phase 标题处标注与附录 F。
+> **🔎 实现状态速览(v2.0 / 2026-08-13 盘点)**:五条铁律中,"内部 EventMeshFrame / Connector 独立部署 / SDK 极简(HTTP-only)"已落实;"EventMesh 自主订阅 / 自主 Offset"单实例成立(offset 路②纯本地 + MQ 重放接管,meta 上报默认关)。多实例协调改为**全面粘性**(session 分配层 recommend,停用转发层)。对外协议经 `FrameAdaptor` SPI(CloudEvents 独立插件 + MeshMessage + A2A 各自直接转 Frame,零 CE 互转)。详见 §19 架构深化。
 
 ### 1.2 与当前设计的本质区别
 
@@ -4565,7 +4571,389 @@ CompletableFuture<CloudEvent> request(CloudEvent event, Duration timeout);
 
 ---
 
-*文档版本:v1.10 | EventMesh 简化重构方案 | 基于 unified-runtime-design.md v2.1 演进 | 2026-07-03*
+## 十九、架构深化:内部 EventMeshFrame + FrameAdaptor SPI + 全面粘性(v2.0,2026-08-13)
+
+> 本节整合自 `eventmesh-architecture-refinement.md` v2.0。三块深化落地全绿(全 unit + 5.x E2E,含 RocketMQ5BrokerIntegrationTest 普通 pub/sub 2/2 + LegacyTcpClientIntegrationTest 旧 TCP SDK)。
+
+### 19.1 内部全程 EventMeshFrame
+
+#### 架构分层
+
+```
+┌─ 对外协议层(FrameAdaptor SPI)──────────────────────────────────────┐
+│  CloudEvents(HTTP/SSE/WS)  → CloudEventsFrameAdaptor  → EventMeshFrame │
+│  MeshMessage(legacy TCP)   → MeshMessageFrameAdaptor   → EventMeshFrame │
+│  A2A(JSON-RPC 2.0)         → A2AFrameAdaptor           → EventMeshFrame │
+│  未来新协议                → 新 FrameAdaptor            → EventMeshFrame │
+└───────────────────────────────┬───────────────────────────────────────┘
+                                │ 对外协议直接转 Frame,不经 CloudEvent 互转
+┌───────────────────────────────▼───────────────────────────────────────┐
+│  内部(runtime + storage):全程 EventMeshFrame                            │
+│  ingress publish → Frame → storage.send(Frame) → MQ 字节                 │
+│  MQ 字节 → storage.poll()→Frame → dispatch/filter/TTL → Frame → egress   │
+└───────────────────────────────┬───────────────────────────────────────┘
+                                │ egress: Frame → 对应 FrameAdaptor → 客户端协议
+┌───────────────────────────────▼───────────────────────────────────────┐
+│  egress(FrameAdaptor SPI,按客户端连接协议)                                │
+│  Frame → CloudEvents-JSON(SSE/WS/HTTP poll)                              │
+│  Frame → MeshMessage Package(legacy TCP)                                  │
+│  Frame → A2A JSON-RPC bytes(A2A 回调)                                     │
+└────────────────────────────────────────────────────────────────────────┘
+```
+
+**CloudEvent 不是内部表示**——它是 CloudEvents 客户端的对外入口格式,经 `CloudEventsFrameAdaptor` 转 Frame 后进入内部。MeshMessage 和 A2A 同理,各自有独立 adaptor,互不经过 CloudEvent。
+
+#### EventMeshFrame wire 格式
+
+```
+定长头 14B: [magic=0xEF][ver][msgType:1][flags:1][seq:4][keyCount:2][dataLen:4]
+KV 属性段:  keyCount × [nameLen:2][name][valLen:4][value]   ← 通用,所有 msgType 共用
+data:       raw bytes(streaming 的 chunk/prompt / 事件的业务 data)
+
+msgType = STREAM_REQ | STREAM_CHUNK | EVENT
+flags   = done | hasError | hasMeta(streaming 用)
+```
+
+- **STREAM_REQ**:KV(`sid`/`replyTo`/`model`/`conv`)+ data=prompt。
+- **STREAM_CHUNK**:`seq`(定长)+ `done`(flag)+ KV(`sid`/`etype`/`err`/`meta`)+ data=chunk。一个 `"He"` token ~25B vs CE-JSON ~250B(**~10×**)。
+- **EVENT**:KV(`id`/`type`/`subject`/`time`/`emttl`/`emcorrelationid`/用户扩展...)+ data=业务载荷。
+
+#### WireCodec SPI(内部 MQ wire 编码)
+
+内部 MQ wire 的字节编解码由 `WireCodec` SPI 定义(`eventmesh-common/.../wire/`),默认实现 `EventMeshFrameCodec`(EventMeshFrame↔byte[]),可通过 `-Deventmesh.wire.codec=<fqcn>` 替换。
+
+#### 落地范围
+
+- **storage SPI**:`MeshStoragePlugin.send/poll` + `LiteTopicCapable.sendLite/pullLite` 全改 EventMeshFrame(3 插件迁,带 legacy CE-JSON fallback)。
+- **runtime dispatch 管线**:Delivery/BufferedEvent/PushChannel/Connection/DeadLetterSink/ReliableDispatcher/PushService/所有 channel + CloudEventFilter/SubscriptionManager 全翻 Frame。
+- **streaming**:Mode-1(runtime↔agent 跨进程)+ Mode-2(runtime 内部 pub/sub)全用 EventMeshFrame。
+- **legacy 连接器 SPI**(Producer/Consumer)不动(独立子系统,说 CE,边界转换)。
+
+### 19.2 FrameAdaptor SPI(协议转换收进插件)
+
+对外协议 ↔ EventMeshFrame 的双向转换由 `FrameAdaptor` SPI 定义(`eventmesh-protocol-api/.../FrameAdaptor.java`),各协议插件各自实现。runtime 不直接调 `EventMeshFrame.fromCloudEvent()` / `.toCloudEvent()` / `MeshMessageFrameCodec`——全部经 `FrameAdaptors.get(协议名)` 加载对应 adaptor。**加新协议只需实现 `FrameAdaptor` + 注册 SPI,不改 runtime 代码。**
+
+#### 协议插件模块清单
+
+| 模块 | 职责 |
+|------|------|
+| **eventmesh-protocol-api** | SPI 接口(`FrameAdaptor` + `ProtocolAdaptor`)+ `FrameAdaptors` 加载器(零 adaptor 实现) |
+| **eventmesh-protocol-cloudevents** | `CloudEventsFrameAdaptor`:CloudEvents-JSON ↔ EventMeshFrame(注册 `cloudevents`) |
+| **eventmesh-protocol-meshmessage** | `MeshMessageFrameAdaptor`:Package ↔ EventMeshFrame(注册 `meshmessage`,零 CE 中转)+ 旧 `MeshMessageProtocolAdaptor`(兼容) |
+| **eventmesh-protocol-a2a** | `A2AFrameAdaptor`:JSON-RPC ↔ EventMeshFrame(注册 `a2a`,零 CE 中转)+ `EnhancedA2AProtocolAdaptor`(兼容) |
+| ~~eventmesh-protocol-grpc~~ | **已删除**(空壳无源码,gRPC 解析在 meshmessage 模块) |
+
+#### MeshMessage ↔ Frame 直接转换(零 CloudEvent)
+
+`MeshMessageFrameAdaptor`(meshmessage 插件)直接映射 Package ↔ EventMeshFrame,不经 CloudEvent:
+
+- **ingress**:`MeshMessagePackageRouter` → `FrameAdaptors.get("meshmessage").toFrameSilent(pkg)` → EventMeshFrame。字段映射:topic→`subject`,body→`data`,header.seq→`id`,properties→KV。
+- **egress**:`NettyTcpPushChannel` → `FrameAdaptors.get("meshmessage").fromFrameSilent(frame)` → Package。字段反向映射。
+
+旧 TCP SDK 全程 MeshMessage,内部全程 EventMeshFrame,零 CloudEvent 中转。
+
+### 19.3 Offset 路②:自管 + MQ 重放接管
+
+#### 两层 offset
+
+| 层 | 含义 | key 维度 | 实现 |
+|----|------|----------|------|
+| **pull offset** | 从 MQ 存储拉到哪 | `parent#lite@queue` / `topic#partition` | storage 插件本地(properties 文件) |
+| **deliver/ack offset** | 投递给 client 到哪 + client ack 到哪 | `topic#clientId#partition` | `OffsetStore`(RocksDB 本地) |
+
+两层**不强行合并**(语义不同、解耦),各留各的本地存储。
+
+#### 路②细则
+
+- **不给 group.id**,EventMesh 完全自管 offset(MQ 仅作持久 FIFO)。
+- **offset 纯本地**,**meta 上报默认关**(`-Deventmesh.offset.meta=true` 显式开)。淘汰 `MetaBackedOffsetStore`(百万 key 上报),类保留备用。
+- **接管 = MQ 重放(无状态迁移)**:实例挂 → client 经 §19.4 负载推荐重连新实例 → 从业务 topic 重拉 → in-flight 走 at-least-once 重投(业务幂等兜底)。
+- 核实结论:`pullAndDispatchPartition` 传 `startOffset=-1` 给 `storage.poll`(插件自管游标),`OffsetStore` 不在接管路径上;rocketmq5 用 POP(broker 侧 at-least-once),`assignPartitions` 是 no-op。
+
+### 19.4 负载均衡:session 分配层 + 全面粘性
+
+#### 架构定位
+
+> **均衡做在 session 分配层(入口 recommend),不在拉取/分发层。**
+
+实例只为自己代理的 client 被动按需拉取,不对等、不转发。先均衡"哪个 client 归哪个实例"(session 层),自然导致"各实例拉取量大致相当"。
+
+#### 客户端零负担——负载全自采
+
+EventMesh 实例本地自采负载指标(`LoadMeter`):
+- `activeSessions` ← `SessionRouter` sinks/subscribeSinks size
+- `inflowBytes/s`、`outflowBytes/s` ← `UniIngressService` 入口/出口 data 字节累加(按 clientId 分桶)
+- `cpuLoad` ← `OperatingSystemMXBean`
+
+随 5s 心跳写入 `/em/instances/<id>` = `<ts>|<addr>|<activeSessions>|<byteRate>|<cpuLoad>`。
+
+#### 均衡闭环
+
+- `/session/recommend`:读集群全局负载评分,返回推荐实例 `instanceUrl`(score = sessions + byteRate + cpuLoad 加权,过载负反馈)。
+- `/session/open` + `/events/subscribe`:返回 `instanceUrl`,SDK pin 后续 turn/close/poll/ack。
+- 大 client 分散:recommend 检查 client 现有 session 分布,散到多实例。
+- SDK 失败跨实例转移:open 失败重新 recommend。
+- `advertisedAddr` 默认空(单实例/测试/LB 兼容),显式配 `-Deventmesh.http.advertisedAddr` 才 pin。
+
+#### 全面粘性(广播域也走实例自治)
+
+`enableCluster` 不再接线 `PartitionOwnership`/`ClusterCoordinator`/`HttpForwarder`(类保留备用),只保留 `ClusterMembership` 心跳(供 recommend 评分)。实例拉全分区本地分发,**不跨实例转发**。
+
+### 19.5 验证
+
+1. **单测**:`EventMeshFrame` 全 msgType 互转(12 例);`OffsetStore` 两 key 空间共存;`LoadMeter` 指标 + 每 client 画像;`ClusterMembership` 心跳负载;dispatch 管线 Frame 化(ReliableDispatcher/SubscriptionManager/ClusterCoordinator)。
+2. **E2E**(真 broker):streaming 多轮 + Mode 2 pub/sub + **普通 pub/sub(RocketMQ5BrokerIntegrationTest 2/2)**全绿,内部全程 EventMeshFrame 往返正确;**LegacyTcpClientIntegrationTest(旧 TCP SDK)全绿**(MeshMessage↔Frame 直接转换)。
+3. **构建**:系统 gradle 8.5 + WEOA Nexus(offline)。
+
+---
+
+## 二十、流式调用设计(整合 streaming-session-design + sdk-streaming-call-design + lite-streaming-call)
+
+> 本节整合自 `streaming-session-design.md`、`sdk-streaming-call-design.md`、`lite-streaming-call.md`(POC v1 参考)。
+
+### 20.1 概述
+
+EventMesh 流式调用让客户端发起请求后,**持续接收一串文本分片(token / delta)**,直到收到结束标记——和 OpenAI Chat Completions 的 `stream: true`、SSE 打字机效果一致。
+
+两种正交模式:
+
+| 模式 | 用途 | 入口 | 机制 |
+|------|------|------|------|
+| **Mode 1 — 流式调用** | 客户端发起请求,Agent(接 LLM)逐 token 回复 | `openSession` → `call` → `forEach` | runtime 中介,通道多路复用 + client 亲和 |
+| **Mode 2 — 发布/订阅** | 生产者往 session lite 写 chunks,消费者 SSE 订阅 | `subscribeSession` / `openSessionPublisher` | 确定性 lite 命名,无 agent、无撮合 |
+
+**SDK 零 MQ 依赖**——只发 HTTP/SSE,runtime 是唯一接 MQ 的。内部全程 EventMeshFrame(§19.1),streaming 帧 msgType = STREAM_REQ / STREAM_CHUNK。
+
+### 20.2 Mode 1 — 流式调用
+
+#### 通道拓扑(AgentAnchoredStrategy)
+
+| 通道 | lite | 用途 |
+|------|------|------|
+| 请求 | `agent.<agentId>`(挂 `agent-parent-<i>`) | agent **启动时订阅一次**,所有 session 请求进这条、按 sessionId 解复用 |
+| 回复 | `client.<clientId>`(挂 `client-parent`) | runtime 持有该 client SSE 的实例消费,同 client 多 session 共用、按 sessionId 解复用 |
+
+**sessionId 格式 = `<agentId>:<uuid>`**(runtime 从 `:` 前缀零查表路由到 agent)。
+
+#### Session 生命周期
+
+```
+① agent 上线:   POST /agent/register → runtime 分配 parent + 写 MetaStore → 回 parent
+                  → agent subscribe (agent-parent-<i>, agent.<agentId>) + ready
+② 握手(open):   client POST /session/open {clientId}
+                  runtime 查 MetaStore clientId→agent: 撮合或复用 → 生成 sessionId
+                  回 {sessionId, agentId, instanceUrl}
+③ 流式(stream): client POST /session/stream/{sessionId} {prompt}
+                  runtime: 注册 StreamSink → publish STREAM_REQ → agent lite
+                  agent: 取上下文 + 调 LLM → chunk 发到 replyTo(client.<clientId>)
+                  runtime: poll reply lite → demux → SSE 给 client
+                  done: endTurn(drop sink,保留 consumer 跨 turn 复用)
+④ 多轮:         同 sessionId 再 POST /session/stream → 同 agent + 累积上下文
+⑤ 关闭(close):  POST /session/close/{sessionId} → cancel(清本地状态)
+```
+
+**关键分离**: `endTurn`(自然终止,drop sink 保留 consumer)vs `cancel`(销毁 session)。修复了多轮重复 chunk bug。
+
+#### agent 注册协议(ready-before-route)
+
+agent 是薄 HTTP 客户端,不直连 broker/MetaStore。`status:READY` 必须在 subscribe 之后才置位——撮合只选 `READY && 心跳新鲜` 的 agent,保证不丢首包。
+
+#### 内部 wire(EventMeshFrame)
+
+- STREAM_REQ: `WireCodec.encode(StreamRequest)` → EventMeshFrame(STREAM_REQ)。KV(`sid`/`replyTo`/`model`/`conv`)+ data=prompt。
+- STREAM_CHUNK: `WireCodec.encode(StreamChunk)` → EventMeshFrame(STREAM_CHUNK)。`seq`(定长)+ `done`(flag)+ KV(`sid`/`etype`/`err`/`meta`)+ data=chunk。
+
+### 20.3 Mode 2 — 发布/订阅
+
+**确定性 lite 命名**(无绑定表、无撮合):
+- **parent** = 固定配置常量(`sessionStreamParent`,6 参 SessionRouter 构造器传入)
+- **liteKey** = `"session." + sessionId`(sessionId 的纯函数)
+
+两端用同一规则算出相同 (parent, liteKey) → 同一物理 lite topic。
+
+| 入口 | 方法 | 作用 |
+|------|------|------|
+| `/session/publish/{sessionId}` | POST | 发布一帧 chunk → `publishLite(parent, "session."+sid)`;首次懒惰 `createLiteTopic`;返回 201 |
+| `/session/subscribe/{sessionId}` | GET | SSE 订阅 → `pollLite(parent, "session."+sid)` → SSE 流;每 session 至多一个订阅消费者 |
+
+**单游标 + 免费重放**:lite 单分区 1-queue,崩溃重启可从 lite 头重新播放。
+
+### 20.4 SessionRouter 内部机制
+
+#### 构造器
+
+```java
+// 4 参:Mode 1 专用
+SessionRouter(ingress, registry, strategy, defaultTimeoutMs)
+// 6 参:Mode 1 + Mode 2
+SessionRouter(ingress, registry, strategy, defaultTimeoutMs, sessionTtlMs, sessionStreamParent)
+```
+
+#### 核心方法
+
+| 方法 | 模式 | 作用 |
+|------|------|------|
+| `startStream` | 1 | 发 STREAM_REQ 到 agent lite,创建 StreamSink |
+| `endTurn` | 1 | 自然结束:drop sink + demux 移除(保留 reply consumer 跨 turn) |
+| `cancel` | 1 | 销毁 session:drop sink + demux + 清 SessionRegistry |
+| `addReplySession` | 1 | 注册 demux 映射(clientId → sessionId → StreamSink) |
+| `ensureReplyConsumer` / `runReplyConsumer` | 1 | poll reply lite → 按 sessionId demux → StreamSink |
+| `publishSession` | 2 | 懒惰 createLiteTopic + publishLite |
+| `startSubscribe` / `cancelSubscribe` | 2 | putIfAbsent 守卫单订阅 + pollLite 循环 → StreamSink |
+
+#### StreamSink(有界队列)
+
+容量 1024(`MAX_BUFFERED_CHUNKS`)。SSE 写出线程 drain。队满丢帧(记录 warning,bounded memory)。
+
+#### Session Reaper
+
+后台定时(`Math.min(sessionTtlMs/2, 60s)`)回收 idle session(`registry.expireStaleSessions` → `cancel`)。
+
+### 20.5 消息协议(CloudEvents 分帧)
+
+内部 wire 只有 EventMeshFrame(STREAM_REQ / STREAM_CHUNK)。**没有独立的 OPEN/CLOSE/DONE 帧走 MQ**。终止(DONE)折叠进 STREAM_CHUNK 的 `done=true`。session 的 open/close 是 HTTP 控制面操作。
+
+```java
+STREAM_REQ(msgType=1):
+  data = prompt(text/plain)
+  KV: sid(=sessionId), replyTo(=parent#lite), model?, conv?
+
+STREAM_CHUNK(msgType=2):
+  data = chunk text
+  KV: sid, etype?, err?, meta?(JSON)
+  seq = 流内序号(定长头)
+  done = flag bit
+```
+
+### 20.6 上下文存储(Mode 1)
+
+| 方案 | 绑定 | 上下文 | 抗 agent 重启 |
+|------|------|------|------|
+| **A(当前)** | MetaStore | agent 进程内(ConversationStore) | ❌ 重启丢(re-handshake 或降级) |
+| B | MetaStore | MetaStore(截断/限轮) | ✅ 受大小限制 |
+| C | MetaStore | Redis | ✅ 但引入 Redis |
+
+agent 重启则该 agent 的上下文丢失(绑定仍在)。接口预留方案 C(Redis)。
+
+### 20.7 POC v1 历史(lite-streaming-call)
+
+v1(POC)已被 v2 取代。v1 问题:每流一个 lite(高基数)、req 广播(多 agent 重复处理)、无持久 session、无 client→agent 绑定。v2 用通道多路复用 + client 亲和 + 持久 sessionId 解决。v1 文档保留作历史参考。
+
+---
+
+## 二十一、SDK 流式调用接口设计(整合 sdk-streaming-call-design)
+
+> 本节整合自 `sdk-streaming-call-design.md`,描述客户端侧 SDK 封装。
+
+### 21.1 架构分层
+
+```
+┌─ SDK (eventmesh-sdk-java) ─────────────────────────────────┐
+│  CloudEventsClient.streaming() → HTTP + SSE 客户端          │
+│  • Mode 1: openSession → call → forEach                     │
+│  • Mode 2: subscribeSession / openSessionPublisher          │
+└───────────────────────┬────────────────────────────────────┘
+                        │ POST /session/*  (HTTP + SSE)
+┌───────────────────────▼────────────────────────────────────┐
+│  Runtime session 层 (SessionRouter / Matchmaker /           │
+│  AgentRegistrar / ChannelStrategy)                          │
+└───────────────────────┬────────────────────────────────────┘
+                        │ publishLite / pollLite (EventMeshFrame)
+┌───────────────────────▼────────────────────────────────────┐
+│  Storage (RocketMQ5RemotingStoragePlugin, LiteTopicCapable) │
+└───────────────────────▲────────────────────────────────────┘
+                        │ publish CHUNKs → reply lite
+┌───────────────────────┴────────────────────────────────────┐
+│  Agent (eventmesh-agent: StreamingAgent)                    │
+│   订阅 req lite → 调 LLM → chunk 流 publish 到 replyTo       │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### 21.2 SDK 消费侧
+
+**唯一消费姿态 `forEach`**:
+
+```java
+public interface StreamingResponse extends AutoCloseable {
+    String sessionId();
+    String agentId();
+    CompletableFuture<Void> forEach(Consumer<StreamChunk> onChunk);
+    @Override void close();
+}
+```
+
+**设计决策——为什么只有 `forEach`**:
+- `forEach` 覆盖所有场景:回调消费、`.join()` 阻塞、`.orTimeout()` 超时、`CompletableFuture` 组合。
+- 删除了 `subscribe(StreamHandler)`、`iterator()`、`publisher(Flow)`、`blockAsString()`——API 表面积最小。
+- 删除 `callOneShot`——`openSession` + `call` + `forEach` + `close` 更清晰(session 生命周期显式)。
+
+### 21.3 多轮入口
+
+```java
+public interface StreamingOperations {
+    StreamingSession openSession(OpenSession req);
+}
+
+public interface StreamingSession extends AutoCloseable {
+    String sessionId();
+    String agentId();
+    String instanceUrl();        // pin 的实例 URL(空=用原 baseUrl)
+    StreamingResponse call(String prompt);
+    StreamingResponse call(StreamRequest req);
+    @Override void close();
+}
+```
+
+- `openSession` → `POST /session/open` → 匹配 Agent → `{sessionId, agentId, instanceUrl}`
+- `session.call(prompt)` → `POST /session/stream/{sessionId}` → SSE 流
+- `session.close()` → `POST /session/close/{sessionId}` → 销毁会话
+- 若 `instanceUrl` 非空,`StreamingSession` 用 `client.withBaseUrl(instanceUrl)` pin 后续 turn/close
+
+### 21.4 内部机制:SSE 读取 → 有界队列 → `forEach`
+
+```
+SSE 读取 VT ──offer──►  [有界 LinkedBlockingQueue (1024)]
+ (POST /session/stream)     │
+                            └── forEach: 独立 VT drain 队列 → 调 onChunk
+```
+
+- 队满丢帧(bounded memory)。
+- `forEach` 在独立虚拟线程上 drain;收到终止帧后 complete future。
+- 单消费者守卫:每个 `StreamingResponse` 只能调一次 `forEach`。
+
+### 21.5 Mode 2 SDK
+
+```java
+// 订阅(消费)
+StreamingResponse sub = client.subscribeSession("my-session-id");
+sub.forEach(c -> ...).join();
+
+// 发布(生产)
+SessionPublisher pub = client.openSessionPublisher("my-session-id");
+pub.publish("Hello", false);
+pub.publish("", true);  // 终止帧
+```
+
+`subscribeSession` 返回的 `StreamingResponse` 与 Mode 1 完全相同——`forEach` 是唯一消费方式。`SessionPublisher` 每次 `publish()` 发一个独立 HTTP POST,拥有 seq 计数器。
+
+### 21.6 SDK 零 MQ 依赖
+
+SDK 只发 HTTP/读 SSE,**不引入任何 MQ 客户端依赖**。这是硬性设计规则。Mode 1: `POST /session/open` → `POST /session/stream/{sid}` → 读 SSE → `POST /session/close/{sid}`。Mode 2: `POST /session/publish/{sid}`(生产)或 `GET /session/subscribe/{sid}`(消费 SSE)。
+
+### 21.7 HTTP 端点
+
+| 路径 | 方法 | 作用 |
+|------|------|------|
+| `/session/open` | POST | 开 session(握手 + 匹配 Agent),返回 `{sessionId, agentId, instanceUrl}` |
+| `/session/stream/{sessionId}` | POST | 发起一轮流式调用(Mode 1),返回 SSE 流 |
+| `/session/close/{sessionId}` | POST | 关闭 session |
+| `/session/publish/{sessionId}` | POST | 发布一帧 chunk(Mode 2),返回 201 |
+| `/session/subscribe/{sessionId}` | GET | 订阅 session 流(Mode 2),返回 SSE 流 |
+| `/session/recommend` | GET | 推荐实例(负载均衡),返回 `{instanceUrl}` |
+
+---
+
+*文档版本:v2.1 | EventMesh 简化重构方案 | 基于 unified-runtime-design.md v2.1 演进 + architecture-refinement v2.0 + streaming docs 整合 | 2026-08-13*
 *v1.10 变更:**老协议不直接删——保留为边缘协议适配器以兼容老客户端。** ① TCP 退化为新架构 ingress/egress 传输(与 HTTP/WebHook/长轮询并列):保留线协议(`Package`/`Command`/`Codec`)+翻译层(`TcpMessageProtocolResolver`/`MeshMessageProtocolAdaptor`)+netty server 骨架,删除 TCP 自有核心(`ClientSession`/`ClientGroupPack`/rebalance、Consumer Group)。新增 `transport.tcp` 包(`TcpPushChannel`/`TcpAckRegistry`/`TcpIngressBridge`/`TcpFrameCodec`+`TcpFrameDecoder`)。② legacy HTTP(`EventMeshHttpClient`/`EventMeshMessage`/`/eventmesh/*` webhook-push)同理:`transport.http. [...]
 *v1.9 变更:四项决策固化——① **可观测只用 OpenTelemetry,默认接 Prometheus**(metrics 走 OTel Meter 仪表、trace 走 OTel Tracer Span,默认经 OTel Prometheus exporter 暴露;legacy `eventmesh-metrics-prometheus` / `eventmesh-trace-plugin`(zipkin/jaeger/pinpoint) 不再接线、无 SPI 扩展点,附录 D.8);② **弃用 `eventmesh-registry`,只用 `eventmesh-meta`(MetaService)** 作为唯一控制面(附录 D.7 registry 行改为弃用);③ **retry 内置 `ReliableDispatcher`,不暴露 SPI 插件扩展**(`eventmesh-retry` 不接线,附录 D.9);④ **security 不用 SPI 插件扩展,用内置 FilterChain + IngressFilter**(`eventmes [...]
 *v1.1 变更:新增 §13「能力缺口与设计补充」——对照 develop 分支现有能力,补齐多实例协调、下发可靠性、安全、可观测性、运维、接入扩展、协议工程化 7 类缺口设计,并更新 §14 实施 Phase 影响。*
diff --git a/docs/plugins/core-engines-configuration.md b/docs/plugins/core-engines-configuration.md
deleted file mode 100644
index 758efd9de..000000000
--- a/docs/plugins/core-engines-configuration.md
+++ /dev/null
@@ -1,135 +0,0 @@
-# EventMesh Core Engines Configuration Guide
-
-EventMesh provides powerful core engines (`Filter`, `Transformer`, `Router`) to dynamically process messages. These engines are configured via **MetaStorage** (Governance Center, e.g., Nacos, Etcd), supporting on-demand loading and hot-reloading.
-
-## 0. Core Concepts
-
-Before configuration, it is important to understand the specific role of each engine in the message flow:
-
-*   **Filter (The Gatekeeper)**: Decides **"Whether to pass"**.
-    *   It inspects the message (CloudEvent) attributes. If the message matches the rules, it passes; otherwise, it is dropped.
-    *   *Use Case*: Block debug logs from production traffic; Only subscribe to specific event types.
-
-*   **Transformer (The Translator)**: Decides **"What it looks like"**.
-    *   It modifies the message content (Payload or Metadata) according to templates or scripts.
-    *   *Use Case*: Convert XML to JSON; Mask sensitive data (PII); Adapt legacy protocols to new standards.
-
-*   **Router (The Dispatcher)**: Decides **"Where to go"**.
-    *   It dynamically changes the destination (Topic) of the message.
-    *   *Use Case*: Route traffic to a Canary/Gray release topic; Route high-priority orders to a dedicated queue.
-
----
-
-## 1. Overview
-
-The configuration is not in local property files but distributed via the MetaStorage. EventMesh listens to specific **Keys** based on client Groups.
-
-- **Data Source**: Configured via `eventMesh.metaStorage.plugin.type`.
-- **Loading Mechanism**: Lazy loading & Hot-reloading.
-- **Key Format**: `{EnginePrefix}-{GroupName}`.
-- **Value Format**: JSON Array.
-
-| Engine | Prefix | Scope | Description |
-| :--- | :--- | :--- | :--- |
-| **Router** | `router-` | Pub Only | Routes messages to different topics. |
-| **Filter** | `filter-` | Pub & Sub | Filters messages based on CloudEvent attributes. |
-| **Transformer** | `transformer-` | Pub & Sub | Transforms message content (Payload/Header). |
-
----
-
-## 2. Router (Routing)
-
-**Scope**: Publish Only (Upstream)  
-**Key**: `router-{producerGroup}`
-
-Decides the target storage topic for a message sent by a producer.
-
-### Configuration Example (JSON)
-
-```json
-[
-  {
-    "topic": "original-topic",
-    "routerConfig": {
-      "targetTopic": "redirect-topic",
-      "expression": "data.type == 'urgent'"
-    }
-  }
-]
-```
-
-*   **topic**: The original topic the producer sends to.
-*   **targetTopic**: The actual topic to write to Storage.
-*   **expression**: Condition to trigger routing (e.g., SpEL).
-
----
-
-## 3. Filter (Filtering)
-
-**Scope**: Both Publish (Upstream) & Subscribe (Downstream)
-
-### A. Publish Side (Upstream)
-**Key**: `filter-{producerGroup}`  
-**Effect**: Intercepts messages **before** they are sent to Storage.
-
-### B. Subscribe Side (Downstream)
-**Key**: `filter-{consumerGroup}`  
-**Effect**: Intercepts messages **before** they are pushed to the Consumer.
-
-### Configuration Example (JSON)
-
-```json
-[
-  {
-    "topic": "test-topic",
-    "filterPattern": {
-      "source": ["app-a", "app-b"],
-      "type": [{"prefix": "com.example"}]
-    }
-  }
-]
-```
-
-*   **filterPattern**: Rules matching CloudEvent attributes. If a message doesn't match, it is dropped.
-
----
-
-## 4. Transformer (Transformation)
-
-**Scope**: Both Publish (Upstream) & Subscribe (Downstream)
-
-### A. Publish Side (Upstream)
-**Key**: `transformer-{producerGroup}`  
-**Effect**: Modifies message content **before** sending to Storage.
-
-### B. Subscribe Side (Downstream)
-**Key**: `transformer-{consumerGroup}`  
-**Effect**: Modifies message content **before** pushing to the Consumer.
-
-### Configuration Example (JSON)
-
-```json
-[
-  {
-    "topic": "raw-topic",
-    "transformerConfig": {
-      "transformerType": "template",
-      "template": "{\"id\": \"${id}\", \"new_content\": \"${data.content}\"}"
-    }
-  }
-]
-```
-
-*   **transformerType**: e.g., `original`, `template`.
-*   **template**: The transformation template definition.
-
----
-
-## 5. Verification
-
-1.  **Publish Config**: Add the JSON config to your Governance Center (e.g., Nacos) with the Data ID `router-MyGroup`.
-2.  **Send Message**: Use EventMesh SDK to send a message from `MyGroup`.
-3.  **Observe**:
-    *   For **Router**: Check if the message appears in the `targetTopic` in your MQ.
-    *   For **Filter**: Check if blocked messages are skipped.
-    *   For **Transformer**: Check if the message body in MQ (for Pub) or Consumer (for Sub) is modified.
diff --git a/docs/production-readiness.md b/docs/production-readiness.md
index d2426e66a..fff0b446b 100644
--- a/docs/production-readiness.md
+++ b/docs/production-readiness.md
@@ -1,71 +1,136 @@
-# EventMesh Uni-Architecture 生产准入清单
+# EventMesh 生产准入与功能测试总结
 
-> 状态: 2026-07-10 (更新: §2 阻塞点全部解决 — remoting 插件 offset 游标 + 多 broker queue 寻址修复并验证). 基于真 RocketMQ (127.0.0.1:9876) + 真 Nacos (127.0.0.1:8848) 集成测试.
+> 更新: 2026-08-14 — 全 P0-P2 代码质量修复完成 + offset/ACK/负载均衡修复。基于全量 unit + 5.x E2E 验证。
+> 架构: 内部全程 EventMeshFrame(详见 `eventmesh-uni-architecture-redesign.md` §19)。
 
-## 1. 已验证 ✅
+---
+
+## 1. 架构定位
+
+**MQ-as-stateless-WAL + HTTP SDK(CloudEvents/MeshMessage/A2A 多协议)+ EventMesh 自管订阅/offset + 内部 EventMeshFrame** 的重写(详见 `docs/eventmesh-uni-architecture-redesign.md`)。采用叠加式策略:新核心并行存在,旧 TCP/HTTP 保留为兼容适配层(老客户端零改动)。
+
+## 2. 模块(活跃 gradle)
+
+| 类别 | 模块 |
+|---|---|
+| 核心 | `eventmesh-runtime`(~90 主类)、`common`、`spi` |
+| SDK | `sdk-java`(新 `CloudEventsClient` 含 streaming + lite + 旧 TCP/HTTP/gRPC SDK) |
+| 存储 | `storage-api`、`-kafka`、`-rocketmq`、`-rocketmq5`(含 `LiteTopicCapable`) |
+| 协议 | `protocol-api`(SPI 接口+加载器)、`-cloudevents`(FrameAdaptor)、`-meshmessage`、`-a2a` |
+| 连接器 | `connector-runtime` + 24 个 connector 插件 |
+| Wire | `common/.../wire/`(EventMeshFrame + WireCodec + MeshMessageFrameCodec) |
+
+## 3. 核心功能(runtime 按包)
+
+- **boot**: `UniRuntime`(pull/tick 调度循环)、`EventMeshApplication`(runtime+http+admin+session+streaming 一体启动)
+- **ingress**: `UniIngressService` — publish / subscribe / poll / ack / request-reply / **streaming session**(`LoadMeter` 自采负载)
+- **session**: `SessionRouter` — Mode-1 流式调用(多路复用+client 亲和)+ Mode-2 发布/订阅(确定性 lite 命名)
+- **subscription**: LOAD_BALANCE / BROADCAST / MULTICAST / STICKY + 心跳清理(全翻 EventMeshFrame)
+- **delivery**: `ReliableDispatcher` — ACK 跟踪 + 指数退避重试 + DLQ(全翻 EventMeshFrame + **延迟 MQ ACK**)
+- **push**: 长轮询 / SSE / WS(egress 处 Frame→CloudEvents-JSON via FrameAdaptor SPI + **写失败 nack dispatcher**)
+- **cluster**: `ClusterMembership`(心跳+负载指标)/ `LoadMeter` / `/session/recommend`(全面粘性,停用转发层)
+- **wire**: `EventMeshFrame`(统一内部帧)+ `WireCodec` SPI + `FrameAdaptor` SPI(协议转换收进插件)
+
+## 4. 已验证 ✅
 
 | 能力 | 验证方式 | 结果 |
 |---|---|---|
 | 发布/订阅/轮询/ACK | RealBrokerIntegrationTest (真 RocketMQ) | ✅ 全链路 |
 | 客户端 SDK (HTTP CloudEvents) | ClientBrokerIntegrationTest | ✅ publish/subscribe + request/reply |
-| 老 TCP 协议兼容 | LegacyTcpClientIntegrationTest + LegacyTcpClusterBrokerIntegrationTest | ✅ 老 SDK 零改动 |
-| 动态 connector 调度 | ConnectorSchedulingIntegrationTest | ✅ runtime push /control/start |
+| 老 TCP 协议兼容 | LegacyTcpClientIntegrationTest | ✅ 老 SDK 零改动, MeshMessage↔Frame 直接转换 |
+| 普通 pub/sub (EventMeshFrame) | RocketMQ5BrokerIntegrationTest (-Dit.storage5) | ✅ 2/2 真 broker, 内部 Frame 往返 |
+| 流式调用 Mode-1 | StreamingSdkE2ETest$Mode1 (真 broker + mock LLM) | ✅ 单轮+多轮+session 契约 |
+| 流式调用 Mode-2 | StreamingSdkE2ETest$PubSub (真 broker) | ✅ pub/sub 有序传递 |
+| Lite topic HTTP | RocketMQ5LiteHttpIntegrationTest | ✅ publish/poll lite |
+| 负载均衡推荐 | ClusterMembershipLoadTest + LoadMeterTest + LoadBalancingScoringTest | ✅ 负载采集+心跳+评分 |
+| Offset 单调写入 (P4) | OffsetMonotonicAndRecoveryTest | ✅ 6 例: 单调/多 client/重启恢复 |
+| 延迟 ACK (P2) | DeferredAckDispatcherTest | ✅ 4 例: 客户端ACK触发/超时不触发/DLQ不触发/null兼容 |
+| Frame 协议转换 | FrameProtocolConversionTest | ✅ 7 例: CE round-trip/filter/TTL/correlation/POP_CK |
+| PushService 缓冲 (P1-4/5) | PushServiceBufferOverflowTest | ✅ 3 例: DROP_OLDEST nack/BLOCK 拒绝/ack 移除 |
+| SessionRegistry 原子性 (P1-2) | SessionRegistryAtomicityTest | ✅ 3 例: immutable bean 更新 |
+| Delivery 可见性+复活 (P0-3/P1-6) | DeliveryVolatileAndResurrectTest | ✅ 2 例: volatile/tick 不复活 |
 | 多实例无重复消费 | MultiInstanceRocketMqIntegrationTest (2 实例) | ✅ 5 条恰好投递一次 |
-| 跨实例 forward (Nacos) | NacosClusterForwardIntegrationTest | ✅ sub 在 A, publish 在 B, A 收到 |
 | ACK 超时重投递 + DLQ | AckTimeoutRedeliveryIntegrationTest | ✅ at-least-once + DLQ |
 | 限流 (429) | RateLimitIntegrationTest | ✅ admin + metrics |
 | TLS/HTTPS | TlsIntegrationTest | ✅ 端到端 |
-| 死信 replay | DlqIntegrationTest | ✅ DLQ -> 回原 topic |
-| 吞吐 | LoadThroughputIntegrationTest (真实多 broker, 修复后实测) | ~101 ev/s, 0 丢失, 0 重复 (500 事件突发) |
-
-## 2. 阻塞点 ⛔ → ✅ 已全部解决
+| 吞吐 | LoadThroughputIntegrationTest (真实多 broker) | ~101 ev/s, 0 丢失, 0 重复 |
+
+## 5. 测试总览
+
+### 单元/进程内测试(无 broker,全绿)
+- runtime: ~50 测试类 / ~150 @Test, **0 failures**
+- sdk-java: 29+ @Test | connector-runtime: 13 @Test | common(wire): 12+ @Test(EventMeshFrame)
+- 合计 **~160 @Test**
+- 覆盖: subscription / offset(RocksDB+单调) / delivery(ACK+DLQ+退避+延迟MQACK) / push(缓冲+溢出) / ratelimit / security / cluster / TLS / DLQ / EventMeshFrame 全 msgType / WireCodec / FrameAdaptor / LoadMeter / ClusterMembership 负载 / SessionRouter / SessionRegistry 原子性 / Delivery volatile
+
+### 真 broker E2E(gated on `rocketmq5Available()`,全绿)
+- `RocketMQ5BrokerIntegrationTest` ✅ (普通 pub/sub, EventMeshFrame 往返, 2/2)
+- `RocketMQ5LiteHttpIntegrationTest` ✅ (lite publish/poll)
+- `StreamingSdkE2ETest` ✅ (Mode-1 streaming ×3 + Mode-2 pub/sub ×1 + Demo ×1)
+- `LiteStreamCallIntegrationTest` ✅ (Mode-1 裸 HTTP streaming ×2)
+- `LegacyTcpClientIntegrationTest` ✅ (旧 TCP SDK, MeshMessage↔Frame 直接转换)
+- `KafkaClientE2EIntegrationTest` ✅ (真 Kafka 3-broker SASL, HTTP publish→subscribe 全链路, EventMeshFrame 往返)
+
+## 6. 已解决的问题
+
+### 6.1 消息丢失(订阅/重启窗口)✅
+- **真因**: `RocketMQRemotingStoragePlugin` pull 游标卡 offset 0 + 多 broker queue 寻址错误。
+- **修复**: 始终用 `nextBeginOffset` 推进 + `QueueLoc(brokerAddr, localQueueId)` 局部寻址。
+- **验证**: ClientBrokerIntegrationTest GREEN(真实多 broker)。
+
+### 6.2 offset 持久化(重启恢复)✅
+- **修复**: remoting 插件自管 pull offset(properties 文件),重启加载续传。
+
+### 6.3 P2: RocketMQ 5.x 延迟 ACK ✅
+- **修复**: poll 不立即 ACK broker;`ReliableDispatcher.ack()` → `ackPulledMessage()` 触发 broker ACK。恢复 at-least-once。
+
+### 6.4 P4: OffsetStore 非单调写入 ✅
+- **修复**: `writeOffset` 改 CAS max(InMemory `accumulateAndGet(Math::max)`,RocksDB read-conditional-put)。
+
+### 6.5 代码质量全面修复(P0-P2, 15 项)✅
+**P0(3 项)**:
+- SSE/WS push 写失败 → ConnectionPushPump nack dispatcher(原来静默丢失到超时)
+- SDK HttpURLConnection 泄漏 → finally disconnect
+- Delivery.attempt/nextAttemptAtMs → volatile
+
+**P1(9 项)**:
+- UniIngressService/ClusterMembership 字段 → volatile
+- SessionRegistry → immutable bean 更新(不原地修改共享对象)
+- PushService.offer → synchronized(queue)(TOCTOU + 状态机竞态)
+- PushService.DROP_OLDEST → nack 丢弃回调
+- ReliableDispatcher.tick → putIfAbsent(防复活已 ACK)
+- SDK TCP reconnect → log.error(不再空 catch)
+- RocketMQ5 ack → 3 次重试 + 指数退避
+- RocksDBOffsetStore → offsetWriteFailures 计数器
+
+**P2(3 项)**:
+- SseConnection → synchronized(out)(防并发写交错)
+- UniRuntime shutdown → log.debug
+- SubscriptionManager.pollAndDispatch → log.warn
+
+## 7. 已知限制 ⚠️ (可灰度, 需文档化)
 
-> 截至 2026-07-10,针对 **RocketMQ 部署无已知硬阻塞**。以下两项曾阻塞上生产,现已定位真因并修复 + 验证。
+| 项 | 说明 |
+|---|---|
+| RocketMQ 4.x ConsumeQueue | ✅ **已验证为误报**:RocketMQ 存储清理不依赖消费位点——CommitLog 按 `fileReservedTime`(72h)+ 磁盘水位(75%/90%)删除,ConsumeQueue 经 `minPhysicOffset` 随 CommitLog 联动删除;`OFFSET_MOVED` 行为本身证明删除不被消费阻塞。`commitOffset` no-op 无存储增长风险 |
+| WebSocket 推送 | ✅ 已有集成测试(WebSocketPushIntegrationTest 1/1 通过, subscribeWs 端到端 push) |
+| Kafka 分区 | assign+seek 自管; ✅ 已对真 Kafka 实测(KafkaClientE2EIntegrationTest, 3-broker SASL 集群, 2026-08-14) |
+| Nacos watch 时序 | naming.subscribe push 延迟波动;多实例 watch 组合跑偶发 flaky |
 
-### 2.1 消息丢失(订阅/重启窗口)✅ 已解决
-- **最初现象** (2026-07-08, 旧 DefaultLitePullConsumer 插件): 500 条突发, 57 条 (11%) 丢失, 0 重复。当时归因为 RocketMQ `CONSUME_FROM_LAST_OFFSET` + rebalance 延迟。
-- **真因** (2026-07-10 定位): `RocketMQRemotingStoragePlugin` 两个 latent bug —— remoting 插件号称"解决 offset-seek 丢失",实则未根治:
-  1. **pull 游标卡在 offset 0**: `poll()` 仅在 `SUCCESS` 时记录 `nextBeginOffset`; broker 对越界 offset 返回 `OFFSET_MOVED` (code 20/21) 被静默丢弃 → 游标反复请求 offset 0, 永远拉不到新消息。(当 topic 有历史、min offset > 0 时触发。)
-  2. **多 broker queue 寻址错误**: `poll`/`send` 把全局扁平 queueId 当 `header.queueId`, 但 RocketMQ queueId 按 broker 局部编号 → 跨 broker 的 topic 有 ~80% 队列映射到无效局部 id → 返回 null → **静默不消费** (每跑 2080 条 ERROR 日志)。
-- **为何之前没暴露**: LoadThroughput 用无历史的新 topic (min offset=0, 不触发 bug 1) 且多为单 broker 路由 (不触发 bug 2), 故显示 0 丢失; ClientBroker 的 topic `em-it-client` 跨多次运行有历史 + TBW102 自动创建 topic 扩散到 5 个 broker → 同时触发两个 bug → pub-1 收不到。
-- **修复**:
-  1. 始终用 `nextBeginOffset` 推进游标 (NO_NEW_MSG=空操作 / OFFSET_MOVED=修正 / FOUND=前进) + null-`nextBeginOffset` guard。
-  2. `getBrokerForQueue` 返回 `QueueLoc(brokerAddr, localQueueId)` (按 `readQueueNums` 扁平, 每 broker 局部 id 从 0 起), send/poll 用 localQueueId。
-  - 附带: `RPC_TIMEOUT_MS` 100→1000ms (100ms 伪超时); 去掉单次 pull 失败永久拉黑 broker。
-- **验证**: `ClientBrokerIntegrationTest` GREEN (真实多 broker, pub/sub + request-reply); offset 文件从仅 `#0–#3` 变为 `#0–#19` (20 队列全消费); 幽灵队列 ERROR 2080→0; 全套 runtime 82 tests 0 failures。
+## 8. 可观测性 📊
 
-### 2.2 offset 持久化(重启恢复)✅ 已解决
-- **现象**: autoCommit=false + 不 commit MQ offset → 重启后位置不持久 → CONSUME_FROM_LAST_OFFSET (跳到最新, 丢窗口) 或 FIRST_OFFSET (从 0 重放, 重复)。
-- **修复**: remoting 插件自管 pull offset (per `topic#queueId`), 持久化到 `./data/offset/rocketmq-pull-offsets.properties`, 重启加载 + 始终从 `nextBeginOffset` 续传 (见 2.1)。
-- **验证**: 多次测试运行间 offset 正确续传 (load 1 topic → persist 1 topic, offset 持续前进)。
+### 8.1 Metrics (已有)
+- **`GET /metrics`** → **Prometheus scrape 端点**(text exposition v0.0.4):7 counter + 1 gauge,名称与告警规则(§9.3)一致
+- `GET /admin/metrics` → JSON: publishCount/publishFailed/rateLimited/eventsDispatched/ackCount/redeliveries/dlqCount/pendingDeliveries
+- OTel instruments 已埋点(部署侧可挂 OTel SDK/agent 增强)
+- `RocksDBOffsetStore.getOffsetWriteFailures()` — offset 写失败计数(P1-9 加)
 
-## 3. 已知限制 ⚠️ (可灰度, 需文档化)
+### 8.2 需补
+- [x] ~~OTel metrics exporter 接入~~ → **已加 `/metrics` Prometheus 端点**(PrometheusEndpointTest 验证)
+- [ ] 端到端 distributed tracing(OTel spans 已埋;需接 collector)
+- [x] Grafana dashboard 模板 / 告警规则 / SLO 定义(§9)
 
-| 项 | 说明 |
-|---|---|
-| RocketMQ 分区分配 | subscribe-mode + 共享 consumer group -> broker rebalance (稳态无重复). EventMesh PartitionOwnership 旁路 (partitionCount=-1). 设计如此. |
-| Gen fencing | 软 fence (Meta gen, 能读 Meta 时生效) + lease gate (Meta 不可达 -> 停 poll). 网络分区期间仍有 at-least-once 重复窗口 (直到 lease 过期). |
-| WebSocket 推送 | `UniWsServer` **已实现** (netty 握手 + 每连接 `ConnectionPushPump` + ACK 控制帧 + wss; SDK `subscribeWs`), 但**无集成测试** (端到端 WS 推送未验证); SSE + long-polling 已验证. |
-| Nacos watch 时序 | naming.subscribe push 延迟波动 -> 测试需 30s 等待. 多实例 watch 在组合跑时偶发 flaky. |
-| Kafka 分区 | assign+seek (EventMesh 自管). 未对真 Kafka 实测 (无 Kafka broker). |
-
-## 4. 可观测性 📊
-
-### 4.1 Metrics (已有)
-- `GET /admin/metrics` -> JSON: publishCount, publishFailed, rateLimited, eventsDispatched, ackCount, redeliveries, dlqCount, pendingDeliveries. (**唯一内置读出**)
-- OTel instruments (LongCounter/LongHistogram) 已埋点, 名如 `eventmesh_publish_count` / `eventmesh_publish_failed_count` / `eventmesh_rate_limited_count` / `eventmesh_dispatched_count` / `eventmesh_ack_count` / `eventmesh_redeliveries_count` / `eventmesh_dlq_count` / `eventmesh_dispatch_latency_nanos`. **运行时未捆绑 exporter** —— 需部署侧挂 OTel SDK/agent (Prometheus 或 OTLP collector); 旧 `eventmesh-metrics-prometheus` 插件未接.
-- `GET /admin/subscriptions?topic=` -> live subscriptions.
-- `GET /admin/offsets?topic=` -> distribution offsets.
-- `GET /admin/health` -> liveness + pending + partition ownership.
-
-### 4.2 需补
-- [ ] OTel metrics exporter 接入 (Prometheus / OTLP) —— 当前仅 `/admin/metrics` JSON, 无 Prometheus scrape 端点.
-- [x] Grafana dashboard 模板 (§7.4).
-- [x] 告警规则 (§7.3: 发布失败率 / DLQ 率 / 积压 / 实例 down).
-- [ ] 端到端 distributed tracing (OTel spans 存在: startPublish/startDispatch/startAck/startDlq; 需接 collector).
-- [x] SLO 定义 (§7).
-
-### 4.3 Admin API 一览
+### 8.3 Admin API 一览
 | Endpoint | 方法 | 用途 |
 |---|---|---|
 | `/admin/metrics` | GET | 运行指标 |
@@ -79,69 +144,10 @@
 | `/admin/ratelimit` | PUT | 设限流规则 |
 | `/admin/connectors` | GET/POST/DELETE | connector CRUD |
 | `/admin/connector-workers` | GET/POST | worker 注册/心跳 |
-| `/connector/offset?connectorId=` | GET/POST | connector offset |
-
-## 5. 运维 Runbook
-
-### 5.1 部署
-```
-# runtime 镜像
-docker run -p 8080:8080 -p 8081:8081 \
-  -e EVENTMESH_STORAGE_TYPE=kafka \
-  -e JAVA_OPTS="-Deventmesh.meta.type=nacos -Deventmesh.meta.addr=nacos:8848" \
-  -v $PWD/conf:/data/app/eventmesh/conf \
-  eventmesh:uni
-
-# connector 镜像
-docker run -e EVENTMESH_RUNTIME_URL=http://runtime:8080 \
-  -e CONNECTOR_OPTS="-Dconnector.1.class=...KafkaSourceConnector -Dconnector.1.mode=source -Dconnector.1.topic=t1" \
-  eventmesh-connector:uni
-```
-
-### 5.2 关键配置
-| 配置 | 默认 | 说明 |
-|---|---|---|
-| `eventmesh.storage.type` | standalone | kafka/rocketmq (standalone 已删) |
-| `eventmesh.http.port` | 8080 | 流量 HTTP |
-| `eventmesh.admin.port` | 8081 | 管理 HTTP |
-| `eventmesh.offset.path` | ./data/offset | RocksDB offset 目录 |
-| `eventmesh.meta.type` | (空) | nacos (空=单实例 InMemory) |
-| `eventmesh.meta.addr` | (空) | nacos 地址 |
-| `eventmesh.tls.keystore` | (空) | TLS keystore 路径 |
-| `eventmesh.ws.port` | (空) | WebSocket (-1=关) |
-
-### 5.3 存储配置
-- Kafka: `conf/kafka-client.properties` -> `bootstrap.servers`
-- RocketMQ: `conf/eventmesh.properties` -> `eventMesh.server.rocketmq.namesrvAddr`
-
-### 5.4 常见故障
-| 症状 | 排查 | 处理 |
-|---|---|---|
-| 启动报 "no MeshStoragePlugin" | `eventmesh.storage.type` 错或 SPI jar 不在 classpath | 设正确 type + 确保 storage jar 在 `plugin/storage/<type>/` |
-| 启动报 "topic not exist" (CODE 17) | broker autoCreateTopicEnable=false | 预建 topic (4+ queues) |
-| 消息不投递 | `/admin/health` 查 partitionOwnership + pendingDeliveries | 消费者未分配 / ACK 积压 |
-| DLQ 堆积 | `/admin/dlq/browse?topic=` 查死信 | 修消费者 -> `/admin/dlq/replay` 回放 |
-| 限流 | `/admin/metrics` 查 rateLimited | `PUT /admin/ratelimit` 调整 |
-| 多实例重复 | 查 `partitionOwnership` (null=poll-all, broker rebalance) | 确认 broker rebalance 正常 |
-| Meta 不可达 | 日志 "heartbeat failed - lease invalid" | 修 Nacos; 实例自动停 poll (lease gate) |
-
-### 5.5 升级/回滚
-- 滚动重启: 逐实例 `docker stop` -> `docker run` (新镜像). Lease gate 确保停 poll 期间不重复.
-- 回滚: 同上, 换旧镜像. offset 兼容 (RocksDB 格式不变).
-- **注意**: offset 持久化 + 续传已修 (§2.1/§2.2), 滚动重启不丢窗口内消息。Lease gate 确保实例停 poll 期间不重复。
-
-## 6. 下一步优先级
 
-1. ✅ ~~消息丢失 / offset-seek (§2.1)~~ -- 真因: pull 游标卡 offset 0 + 多 broker queue 寻址; 已修并验证 (ClientBroker IT GREEN).
-2. ✅ ~~offset persist + 续传 (§2.2)~~ -- pull-offset 持久化 + 始终从 nextBeginOffset 续传.
-3. 📊 OTel exporter 接入 (metrics + trace → Prometheus / OTLP collector): 仪器 + spans 已埋点, 但**运行时未捆绑 exporter** (§4.2); dashboard / 告警 / SLO 模板已就绪 (§7), 接 exporter 后才可实测.
-4. 🧪 broker/Nacos failover 混沌测试.
-5. 🔒 mTLS 双向认证: 单向 TLS 已实现+测试 (`TlsContextFactory`+`withTls`); **client-auth 强制未接** (`UniHttpServer` 用默认 `HttpsConfigurator`, 未设 `needClientAuth`). auth filter (Token/ACL/HMAC) 已实现.
-6. 🧪 WebSocket 集成测试: `UniWsServer` 已实现 + wss, 但无 IT (SDK `subscribeWs` 端到端未验证).
+## 9. SLO 定义
 
-## 7. SLO 定义
-
-### 7.1 指标采集 (已有)
+### 9.1 指标采集
 | 指标 | 来源 | 用途 |
 |---|---|---|
 | `publishCount` / `publishFailed` | `/admin/metrics` | 发布成功率 |
@@ -151,57 +157,97 @@ docker run -e EVENTMESH_RUNTIME_URL=http://runtime:8080 \
 | `rateLimited` | `/admin/metrics` | 限流频率 |
 | `consumeLag` | `storage.poll` offset vs `endOffset` | 消费延迟 |
 
-### 7.2 SLO 目标(建议初值,灰度后调整)
-
-| SLO | 目标 | 告警阈值 | 计算方式 |
-|---|---|---|---|
-| **发布可用性** | ≥ 99.9% | publishFailed / publishCount > 0.1% 持续 5min | `1 - publishFailed / publishCount` |
-| **投递延迟 P99** | ≤ 500ms | P99 > 1s 持续 5min | publish 时间戳 -> ACK 时间戳 |
-| **端到端延迟 P99** | ≤ 2s | P99 > 5s 持续 5min | publish -> subscriber 收到 |
-| **DLQ 率** | ≤ 0.01% | dlqCount / eventsDispatched > 0.1% | `dlqCount / eventsDispatched` |
-| **积压** | ≤ 1000 | pendingDeliveries > 5000 持续 5min | `pendingDeliveries` |
-| **消费延迟** | ≤ 10s | consumeLag > 60s 持续 5min | `endOffset - pollOffset` |
-| **限流频率** | ≤ 1% | rateLimited / publishCount > 5% | `rateLimited / publishCount` |
-| **可用性** | ≥ 99.9% | `/admin/health` 非 UP 持续 3min | HTTP 200 |
-
-### 7.3 告警规则(Prometheus / Alertmanager)
-
-> **前提**: 需先接 OTel → Prometheus exporter (§4.2, 当前未捆绑). counter 仪器名 `eventmesh_publish_count` 经 Prometheus exporter 导出为 `eventmesh_publish_count_total` (monotonic counter 自动加 `_total`).
-
+### 9.2 SLO 目标(建议初值,灰度后调整)
+| SLO | 目标 | 告警阈值 |
+|---|---|---|
+| 发布可用性 | ≥ 99.9% | publishFailed/publishCount > 0.1% 持续 5min |
+| 投递延迟 P99 | ≤ 500ms | P99 > 1s 持续 5min |
+| 端到端延迟 P99 | ≤ 2s | P99 > 5s 持续 5min |
+| DLQ 率 | ≤ 0.01% | dlqCount/eventsDispatched > 0.1% |
+| 积压 | ≤ 1000 | pendingDeliveries > 5000 持续 5min |
+| 消费延迟 | ≤ 10s | consumeLag > 60s 持续 5min |
+| 限流频率 | ≤ 1% | rateLimited/publishCount > 5% |
+| 可用性 | ≥ 99.9% | `/admin/health` 非 UP 持续 3min |
+
+### 9.3 告警规则(Prometheus / Alertmanager)
 ```yaml
-# publish failure rate
 - alert: PublishFailureRateHigh
   expr: rate(eventmesh_publish_failed_total[5m]) / rate(eventmesh_publish_count_total[5m]) > 0.001
   for: 5m
   labels: { severity: critical }
-  annotations: { summary: "Publish failure rate > 0.1%" }
-
-# DLQ rate
 - alert: DlqRateHigh
   expr: rate(eventmesh_dlq_count_total[5m]) / rate(eventmesh_events_dispatched_total[5m]) > 0.001
   for: 5m
   labels: { severity: warning }
-  annotations: { summary: "DLQ rate > 0.1%" }
-
-# pending deliveries backlog
 - alert: PendingDeliveriesHigh
   expr: eventmesh_pending_deliveries > 5000
   for: 5m
   labels: { severity: warning }
-  annotations: { summary: "Pending deliveries > 5000" }
-
-# health
 - alert: EventMeshDown
   expr: up{job="eventmesh"} == 0
   for: 3m
   labels: { severity: critical }
-  annotations: { summary: "EventMesh instance down" }
 ```
 
-### 7.4 Grafana dashboard 模板
+## 10. 运维 Runbook
 
-Dashboard 建议包含 4 行:
-1. **发布**: publishCount (rate) + publishFailed (rate) + rateLimited (rate)
-2. **投递**: eventsDispatched (rate) + ackCount (rate) + redeliveries (rate)
-3. **积压**: pendingDeliveries (gauge) + dlqCount (counter)
-4. **健康**: health (UP/DOWN) + consumeLag per topic
+### 10.1 部署
+```bash
+# runtime 镜像
+docker run -p 8080:8080 -p 8081:8081 \
+  -e EVENTMESH_STORAGE_TYPE=rocketmq5 \
+  -e EVENTMESH_ROCKETMQ5_NAMESRV=broker:9876 \
+  -e JAVA_OPTS="-Deventmesh.meta.type=nacos -Deventmesh.meta.addr=nacos:8848" \
+  eventmesh:uni
+
+# connector 镜像
+docker run -e EVENTMESH_RUNTIME_URL=http://runtime:8080 \
+  eventmesh-connector:uni
+```
+
+### 10.2 关键配置
+| 配置 | 默认 | 说明 |
+|---|---|---|
+| `eventmesh.storage.type` | standalone | rocketmq5 / rocketmq / kafka |
+| `eventmesh.http.port` | 8080 | 流量 HTTP |
+| `eventmesh.admin.port` | 8081 | 管理 HTTP |
+| `eventmesh.offset.path` | ./data/offset | offset 目录 |
+| `eventmesh.meta.type` | (空) | nacos (空=单实例) |
+| `eventmesh.meta.addr` | (空) | nacos 地址 |
+| `eventmesh.tls.keystore` | (空) | TLS |
+| `eventmesh.ws.port` | (空) | WebSocket |
+| `eventmesh.rocketmq5.lite.checkpoint.interval.ms` | 5000 | lite offset checkpoint |
+| `eventmesh.offset.meta` | false | meta-backed offset(显式开) |
+| `eventmesh.http.advertisedAddr` | (空) | instanceUrl pin(显式配) |
+| `eventmesh.wire.codec` | EventMeshFrameCodec | 内部 wire 编码 |
+| `eventmesh.storage5` / `eventmesh.namesrv5` | (空) | 5.x E2E gating |
+
+### 10.3 常见故障
+| 症状 | 排查 | 处理 |
+|---|---|---|
+| 启动报 "no MeshStoragePlugin" | `eventmesh.storage.type` 错或 SPI jar 不在 classpath | 设正确 type + 确保 storage jar |
+| 启动报 "topic not exist" (CODE 17) | broker autoCreateTopicEnable=false | 预建 topic (4+ queues) |
+| 消息不投递 | `/admin/health` 查 pendingDeliveries | 消费者未分配 / ACK 积压 |
+| DLQ 堆积 | `/admin/dlq/browse?topic=` | 修消费者 → `/admin/dlq/replay` |
+| 限流 | `/admin/metrics` 查 rateLimited | `PUT /admin/ratelimit` |
+| offset 写失败持续增长 | 查 `RocksDBOffsetStore.getOffsetWriteFailures()` | 检查磁盘/权限 |
+| SSE 客户端收不到 | 连接写失败现在会 nack dispatcher → 立即重投 | 检查客户端网络 |
+
+### 10.4 升级/回滚
+- 滚动重启: 逐实例 stop→run。offset 持久化+续传已修,不丢窗口内消息。
+- 回滚: 换旧镜像。offset RocksDB 格式不变。
+
+## 11. 下一步优先级
+
+1. ✅ ~~消息丢失 / offset-seek~~ — pull 游标 + 多 broker 寻址修复
+2. ✅ ~~offset persist + 续传~~ — properties 文件续传
+3. ✅ ~~P2: RocketMQ 5.x 延迟 ACK~~ — ReliableDispatcher.ack() 触发 ackPulledMessage()
+4. ✅ ~~P4: OffsetStore 单调写入~~ — CAS max
+5. ✅ ~~P0-P2 代码质量(15 项)~~ — volatile/immutable/synchronized/nack/retry 全修
+6. ✅ ~~Kafka 真实实测~~ — KafkaClientE2EIntegrationTest 通过(3-broker SASL)
+7. ✅ ~~OTel exporter 接入~~ — `/metrics` Prometheus scrape 端点(PrometheusEndpointTest 验证)
+8. ✅ ~~mTLS 双向认证~~ — `-Deventmesh.tls.needClientAuth=true` + truststore → HttpsConfigurator needClientAuth
+9. ✅ ~~WebSocket 集成测试~~ — WebSocketPushIntegrationTest 1/1 通过
+10. ✅ ~~RocketMQ 4.x ConsumeQueue 清理~~ — **验证为误报**(CommitLog 时间清理 + ConsumeQueue 联动,不依赖消费位点;OFFSET_MOVED 行为佐证)
+11. 🧪 broker/Nacos failover 混沌测试 — 需要破坏性环境
+12. 📊 端到端 distributed tracing — OTel spans 已埋,需接 collector
diff --git a/docs/uni-architecture-features-and-tests.md b/docs/uni-architecture-features-and-tests.md
deleted file mode 100644
index 37b45677a..000000000
--- a/docs/uni-architecture-features-and-tests.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# uni-architecture 分支:功能与测试总结
-
-> 快照时间:2026-07-10 | 分支:`uni-architecture`
-
----
-
-# 架构定位
-
-**MQ-as-stateless-WAL + HTTP-only CloudEvents SDK + EventMesh 自管订阅/offset** 的重写(`docs/eventmesh-uni-architecture-redesign.md`)。采用**叠加式**策略:新核心并行存在,旧 TCP/HTTP **保留为兼容适配层**(老客户端零改动),物理删除(Phase 8)是最后切换。
-
-# 模块(活跃 gradle,共 ~38 个)
-
-| 类别 | 模块 |
-|---|---|
-| 核心 | `eventmesh-runtime`(75 主类)、`common`、`spi` |
-| SDK | `sdk-java`(新 `CloudEventsClient` + 旧 TCP/HTTP/gRPC SDK) |
-| 存储 | `storage-api`、`-kafka`、`-rocketmq`(rocketmq = remoting 直连 RPC 插件) |
-| 协议 | `protocol-api`、`-meshmessage`、`-grpc`、`-a2a` |
-| 连接器 | `connector-runtime` + **24 个 connector 插件**(rocketmq/kafka/rabbitmq/redis/mongodb/pulsar/s3/pravega/knative/jdbc/file/spring/prometheus/dingtalk/lark/wecom/slack/wechat/http/chatgpt/canal/mcp/openfunction…) |
-
-已删除:旧 security/meta/metrics/trace/retry/registry/function/v1/v2/admin/starter/examples/operator + storage-{standalone,pulsar,redis,rabbitmq}。
-
-# 核心功能(runtime 按包)
-
-- **boot**: `UniRuntime`(pull/tick 调度循环)、`EventMeshApplication`(runtime+http+admin 一体启动,支持 TLS / WS / 集群 / 连接器调度)
-- **ingress**: `UniIngressService` — publish / subscribe / poll / ack / **request-reply**(`emcorrelationid`) 编排核心
-- **subscription**: LOAD_BALANCE / BROADCAST / MULTICAST / STICKY + 心跳清理
-- **delivery**: `ReliableDispatcher` — ACK 跟踪 + 指数退避重试 + **DLQ**(offset 仅 ACK 后前进,at-least-once)
-- **offset**: 内存 / **RocksDB**(崩溃恢复)/ Meta 两级
-- **push**: 长轮询 / **SSE** / ConnectionPushPump
-- **http**: `UniHttpServer`(新 `/events/*` + 旧 `/eventmesh/*`)、`UniWsServer`、`TlsContextFactory`
-- **transport.tcp** (12): `UniTcpServer` + netty pipeline — **旧 TCP 客户端零改动**
-- **transport.http**: `LegacyHttpBridge` — **旧 HTTP 客户端零改动**
-- **cluster** (15): `NacosMetaStore` + `ClusterMembership`(心跳租约) + `PartitionOwnership`(确定性分区 + gen fencing) + `ClusterCoordinator`(本地投递/跨实例 forward) → **多实例无重复**
-- **security**: FilterChain(Token / ACL / HMAC 签名),无 SPI
-- **ratelimit**: 令牌桶(每 topic,超额返回 429)
-- **metrics**: OTel(默认 Prometheus 导出)+ `UniTrace`
-- **connector**: `ConnectorScheduler`(runtime-push 动态调度连接器到 worker)
-- **admin**: 独立端口的 `/admin/*`(metrics / dlq replay / ratelimit / connectors…)
-
-# 测试情况
-
-## 单元/进程内测试(无 broker,跑在常规套件里) — 全绿
-
-- runtime:**38 个测试类 / 82 @Test**,刚跑 **0 failures**(8 skipped = 下面那些 gated IT)
-- sdk-java:29 @Test | connector-runtime:13 @Test
-- 合计 **~124 @Test**
-- 覆盖:subscription / offset(RocksDB 崩溃恢复) / delivery(ACK+nack+超时+DLQ+退避) / push / ratelimit / security / cluster / TLS / DLQ / 限流 / cluster-forward(2 实例 in-memory)/ 连接器调度 / 旧 TCP+HTTP 兼容
-
-## 真 broker/Nacos 集成测试(gated,已在多 broker 实测中验证 GREEN)
-
-- `-Dit.storage=rocketmq`(真 RocketMQ `127.0.0.1:9876`):
-  - `RealBrokerIntegrationTest` ✅
-  - `ClientBrokerIntegrationTest` ✅(**本次刚修绿** — pull 游标卡 offset 0 + 多 broker queue 寻址)
-  - `LoadThroughputIntegrationTest` ✅(500 事件,0 丢失,0 重复)
-  - `AckTimeoutRedeliveryIntegrationTest` ✅(ACK 超时→重投→DLQ)
-  - `BrokerListTest` ✅
-- `-Dit.nacos`(真 RocketMQ + Nacos `127.0.0.1:8848`):
-  - `LegacyTcpClusterBrokerIntegrationTest` ✅(旧 TCP SDK + 真 broker + Nacos)
-  - `MultiInstanceRocketMqIntegrationTest` ✅(2 实例无重复)
-  - `NacosClusterForwardIntegrationTest` ✅(跨实例订阅转发)
-
-## 已知待办(需环境/更大改动,已记录)
-
-- WebSocket netty 握手的服务端接线(SSE 通道已就绪)
-- 真 Meta MetaStore:etcd 适配(nacos 已完成)
-- Java 21 虚拟线程(项目源码级还是 Java 8)
-- Phase 8 物理删除旧 TCP/gRPC SDK(最终切换,需旧 runtime 全量回归)
-- `UniHttpServer.publish` 对 `RateLimitedException` 仍走 catch-all 返回 500(应为 429;测试靠 metrics 断言,未覆盖状态码)
-
-## 一句话
-
-新架构已是**可运行系统**,全部单元/进程内测试 + 7 个真 broker/Nacos 集成测试均 GREEN;老 TCP/HTTP 客户端零改动接入新核心。剩余项均为需环境或最终切换的收尾工作。
diff --git a/eventmesh-agent/build.gradle b/eventmesh-agent/build.gradle
new file mode 100644
index 000000000..17858d5e8
--- /dev/null
+++ b/eventmesh-agent/build.gradle
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Agent — 独立进程, 仅经 lite topic + CloudEvents 与 EventMesh Runtime 通信.
+// 不依赖 eventmesh-runtime, 复用 sdk-java (CloudEventsClient) + common (stream framing) + 一个
+// OpenAI 兼容的 LLM SSE client.
+dependencies {
+    implementation project(':eventmesh-sdks:eventmesh-sdk-java')
+    implementation 'com.fasterxml.jackson.core:jackson-databind'
+    implementation 'org.slf4j:slf4j-api'
+    runtimeOnly 'org.apache.logging.log4j:log4j-slf4j2-impl'
+
+    compileOnly 'org.projectlombok:lombok'
+    annotationProcessor 'org.projectlombok:lombok'
+
+    testImplementation 'org.assertj:assertj-core'
+}
+
+// Dev runner: boots AgentApplication against a running runtime + an OpenAI-compatible LLM gateway.
+// Forwards eventmesh*/eventMesh*/llm.*/agent.* -D flags.
+task runAgent(type: JavaExec) {
+    group = 'application'
+    mainClass = 'org.apache.eventmesh.agent.AgentApplication'
+    classpath = sourceSets.main.runtimeClasspath
+    systemProperties = System.properties.findAll { k, v ->
+        k.startsWith('eventmesh') || k.startsWith('eventMesh') || k.startsWith('llm.') || k.startsWith('agent.')
+    }
+    jvmArgs('-Xmx512m')
+}
diff --git a/eventmesh-agent/conf/agent.properties b/eventmesh-agent/conf/agent.properties
new file mode 100644
index 000000000..af96095e9
--- /dev/null
+++ b/eventmesh-agent/conf/agent.properties
@@ -0,0 +1,17 @@
+#
+# EventMesh agent configuration template.
+#
+# AgentApplication reads ALL config from -D system properties (NOT this file). bin/start-agent.sh
+# translates the ENV vars below into -D flags; pass extra -D flags through AGENT_OPTS.
+#
+#   AGENT_RUNTIME_URL    -> -Dagent.runtime.url    (default http://localhost:8080)
+#   AGENT_STREAM_PARENT  -> -Dagent.stream.parent    (default em-stream; must match the runtime's eventmesh.stream.parent)
+#   AGENT_CLIENT_ID      -> -Dagent.client.id         (default em-agent-<ts>)
+#   LLM_BASE_URL         -> -Dllm.base.url            (OpenAI-compatible; default https://api.openai.com)
+#   LLM_API_KEY          -> -Dllm.api.key             (Bearer token; default "")
+#   LLM_MODEL            -> -Dllm.model               (default gpt-4o-mini)
+#
+# Example (real LLM):
+#   LLM_BASE_URL=https://api.openai.com LLM_API_KEY=sk-... LLM_MODEL=gpt-4o-mini \
+#     AGENT_RUNTIME_URL=http://localhost:8080 ./bin/start-agent.sh
+#
diff --git a/eventmesh-agent/conf/log4j2.xml b/eventmesh-agent/conf/log4j2.xml
new file mode 100644
index 000000000..81c82618f
--- /dev/null
+++ b/eventmesh-agent/conf/log4j2.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  EventMesh agent logging. Discovered on the classpath (conf/log4j2.xml).
+-->
+<Configuration status="WARN" monitorInterval="60">
+    <Appenders>
+        <Console name="console" target="SYSTEM_OUT">
+            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%t] %c{1} - %m%n"/>
+        </Console>
+    </Appenders>
+    <Loggers>
+        <Logger name="org.apache.eventmesh" level="INFO" additivity="false">
+            <AppenderRef ref="console"/>
+        </Logger>
+        <Root level="INFO">
+            <AppenderRef ref="console"/>
+        </Root>
+    </Loggers>
+</Configuration>
diff --git a/eventmesh-agent/src/main/java/org/apache/eventmesh/agent/AgentApplication.java b/eventmesh-agent/src/main/java/org/apache/eventmesh/agent/AgentApplication.java
new file mode 100644
index 000000000..5b25bbe0d
--- /dev/null
+++ b/eventmesh-agent/src/main/java/org/apache/eventmesh/agent/AgentApplication.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.agent;
+
+import org.apache.eventmesh.agent.llm.OpenAiLlmClient;
+import org.apache.eventmesh.client.cloudevents.CloudEventsClient;
+
+import java.util.List;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Agent process entry (v2). Boot sequence (§5.2): register with the runtime (get its agent-parent) →
+ * subscribe its {@code agent.<agentId>} channel - flip READY (ready-before-route) - heartbeat loop.
+ * All config via {@code -D} system properties.
+ *
+ * <p>Config keys: {@code agent.runtime.url}, {@code agent.id}, {@code agent.heartbeat.intervalMs},
+ * {@code agent.capacity}, {@code agent.conversation.maxHistory}, {@code llm.base.url},
+ * {@code llm.api.key}, {@code llm.model}.</p>
+ */
+@Slf4j
+public class AgentApplication {
+
+    public static void main(String[] args) throws Exception {
+        String runtimeUrl = System.getProperty("agent.runtime.url", "http://localhost:8080");
+        String agentId = System.getProperty("agent.id", "agent-" + Long.toString(System.currentTimeMillis(), 36));
+        String llmBase = System.getProperty("llm.base.url", "https://api.openai.com");
+        String llmKey = System.getProperty("llm.api.key", "");
+        String llmModel = System.getProperty("llm.model", "gpt-4o-mini");
+        final long heartbeatMs = Long.getLong("agent.heartbeat.intervalMs", 10_000L);
+        int maxHistory = Integer.getInteger("agent.conversation.maxHistory", 20);
+        int capacity = Integer.getInteger("agent.capacity", 100);
+
+        // Step 1: register (gets the assigned agent-parent + client-reply-parent)
+        AgentControlClient control = new AgentControlClient(runtimeUrl);
+        AgentControlClient.RegisterResult reg = control.register(agentId, List.of(llmModel), capacity);
+        String agentParent = reg.parent();
+        log.info("agent registered: agentId={} parent={} clientParent={}", agentId, agentParent, reg.clientParent());
+
+        // Step 2: subscribe the agent's channel
+        CloudEventsClient client = CloudEventsClient.builder().runtimeUrl(runtimeUrl).clientId(agentId).build();
+        OpenAiLlmClient llm = new OpenAiLlmClient(llmBase, llmKey, llmModel);
+        ConversationStore store = new ConversationStore(maxHistory);
+        StreamingAgent agent = new StreamingAgent(client, agentParent, agentId, llm, store);
+        agent.start(); // subscribe (agentParent, agent.<agentId>)
+
+        // Step 3: ready-before-route: only now is this agent eligible for matchmaking
+        control.ready(agentId);
+        log.info("agent READY: agentId={} (subscribed agent.{})", agentId, agentId);
+
+        // Step 4: heartbeat loop (refresh TTL + report load)
+        Thread heartbeatThread = Thread.startVirtualThread(() -> {
+            while (!Thread.currentThread().isInterrupted()) {
+                try {
+                    Thread.sleep(heartbeatMs);
+                    control.heartbeat(agentId, agent.activeSessions());
+                } catch (InterruptedException ie) {
+                    return;
+                } catch (Exception e) {
+                    log.warn("heartbeat failed: {}", e.toString());
+                }
+            }
+        });
+
+        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+            log.info("shutting down agent...");
+            heartbeatThread.interrupt();
+            try {
+                control.unregister(agentId);
+            } catch (Exception e) {
+                log.warn("unregister failed: {}", e.toString());
+            }
+            agent.shutdown();
+            client.shutdown();
+        }, "agent-shutdown"));
+
+        log.info("AgentApplication running: runtime={} agentId={} parent={} model={} (Ctrl+C to stop)",
+            runtimeUrl, agentId, agentParent, llmModel);
+        Thread.currentThread().join();
+    }
+}
diff --git a/eventmesh-agent/src/main/java/org/apache/eventmesh/agent/AgentControlClient.java b/eventmesh-agent/src/main/java/org/apache/eventmesh/agent/AgentControlClient.java
new file mode 100644
index 000000000..40566980c
--- /dev/null
+++ b/eventmesh-agent/src/main/java/org/apache/eventmesh/agent/AgentControlClient.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.agent;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Thin HTTP client for the runtime's agent control endpoints ({@code POST /agent/*}, §5.2). The agent
+ * is a "thin HTTP client of the runtime" — it never touches the broker or the MetaStore directly; this
+ * client drives its register → ready → heartbeat → unregister lifecycle.
+ */
+@Slf4j
+public class AgentControlClient {
+
+    private final String runtimeUrl;
+    private final HttpClient http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
+    private final ObjectMapper json = new ObjectMapper();
+
+    public AgentControlClient(String runtimeUrl) {
+        this.runtimeUrl = runtimeUrl.replaceAll("/+$", "");
+    }
+
+    /** Register; returns the assigned agent-parent + client-reply-parent. */
+    public RegisterResult register(String agentId, List<String> capabilities, int capacity) {
+        ObjectNode body = json.createObjectNode().put("agentId", agentId).put("capacity", capacity);
+        ArrayNode caps = body.putArray("capabilities");
+        capabilities.forEach(caps::add);
+        JsonNode resp = post("/agent/register", body);
+        return new RegisterResult(resp.get("parent").asText(), resp.get("clientParent").asText());
+    }
+
+    public void ready(String agentId) {
+        // Retry: Nacos config propagation may lag the register's put by a few hundred ms — the
+        // immediate get in markReady can return null until it catches up.
+        RuntimeException last = null;
+        for (int attempt = 0; attempt < 5; attempt++) {
+            try {
+                post("/agent/ready", json.createObjectNode().put("agentId", agentId));
+                return;
+            } catch (RuntimeException e) {
+                last = e;
+                if (e.getMessage() != null && e.getMessage().contains("404") && attempt < 4) {
+                    try {
+                        Thread.sleep(500L);
+                    } catch (InterruptedException ie) {
+                        Thread.currentThread().interrupt();
+                        throw e;
+                    }
+                    continue;
+                }
+                throw e;
+            }
+        }
+        throw last;
+    }
+
+    public void heartbeat(String agentId, int activeSessions) {
+        post("/agent/heartbeat", json.createObjectNode().put("agentId", agentId).put("activeSessions", activeSessions));
+    }
+
+    public void unregister(String agentId) {
+        post("/agent/unregister", json.createObjectNode().put("agentId", agentId));
+    }
+
+    private JsonNode post(String path, JsonNode body) {
+        try {
+            HttpRequest req = HttpRequest.newBuilder()
+                .uri(URI.create(runtimeUrl + path))
+                .header("Content-Type", "application/json")
+                .timeout(Duration.ofSeconds(30))
+                .POST(HttpRequest.BodyPublishers.ofString(json.writeValueAsString(body)))
+                .build();
+            HttpResponse<String> resp = http.send(req, HttpResponse.BodyHandlers.ofString());
+            if (resp.statusCode() >= 400) {
+                throw new RuntimeException("control " + path + " -> HTTP " + resp.statusCode() + ": " + resp.body());
+            }
+            return json.readTree(resp.body());
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new RuntimeException("control " + path + " failed: " + e, e);
+        }
+    }
+
+    /** Assigned agent-parent + client-reply-parent. */
+    public record RegisterResult(String parent, String clientParent) {
+    }
+}
diff --git a/eventmesh-agent/src/main/java/org/apache/eventmesh/agent/ConversationStore.java b/eventmesh-agent/src/main/java/org/apache/eventmesh/agent/ConversationStore.java
new file mode 100644
index 000000000..394ca67e6
--- /dev/null
+++ b/eventmesh-agent/src/main/java/org/apache/eventmesh/agent/ConversationStore.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.agent;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * In-memory multi-turn conversation history keyed by {@code conversationId}. Each completed turn
+ * appends a {@code {user, assistant}} message pair; history is trimmed to the most recent
+ * {@code maxMessages} entries (sliding window) to bound LLM context size.
+ *
+ * <p>Lost on agent restart (MVP — persistence to Redis/DB is a TODO). Thread-safe per conversation
+ * (synchronized on the per-id list).</p>
+ */
+public class ConversationStore {
+
+    private final int maxMessages;
+    private final ConcurrentHashMap<String, List<Map<String, String>>> history = new ConcurrentHashMap<>();
+
+    public ConversationStore(int maxMessages) {
+        // keep at least one full turn (user + assistant)
+        this.maxMessages = Math.max(2, maxMessages);
+    }
+
+    /** Snapshot of the conversation history (empty list if convId null/unknown). Caller may mutate. */
+    public List<Map<String, String>> get(String conversationId) {
+        if (conversationId == null) {
+            return new ArrayList<>();
+        }
+        List<Map<String, String>> msgs = history.get(conversationId);
+        return msgs == null ? new ArrayList<>() : new ArrayList<>(msgs);
+    }
+
+    /** Append a completed turn (user prompt + assistant answer). No-op if convId is null. */
+    public void appendTurn(String conversationId, String userPrompt, String assistantAnswer) {
+        if (conversationId == null) {
+            return;
+        }
+        List<Map<String, String>> msgs = history.computeIfAbsent(conversationId,
+            k -> Collections.synchronizedList(new ArrayList<>()));
+        synchronized (msgs) {
+            msgs.add(message("user", userPrompt));
+            msgs.add(message("assistant", assistantAnswer));
+            while (msgs.size() > maxMessages) {
+                msgs.remove(0); // drop oldest → sliding window
+            }
+        }
+    }
+
+    private static Map<String, String> message(String role, String content) {
+        Map<String, String> m = new HashMap<>();
+        m.put("role", role);
+        m.put("content", content == null ? "" : content);
+        return m;
+    }
+}
diff --git a/eventmesh-agent/src/main/java/org/apache/eventmesh/agent/StreamingAgent.java b/eventmesh-agent/src/main/java/org/apache/eventmesh/agent/StreamingAgent.java
new file mode 100644
index 000000000..acac3dac9
--- /dev/null
+++ b/eventmesh-agent/src/main/java/org/apache/eventmesh/agent/StreamingAgent.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.agent;
+
+import org.apache.eventmesh.agent.llm.OpenAiLlmClient;
+import org.apache.eventmesh.client.cloudevents.CloudEventsClient;
+import org.apache.eventmesh.common.stream.StreamChunk;
+import org.apache.eventmesh.common.stream.StreamRequest;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Consumes streaming-call requests from its own {@code agent.<agentId>} lite (mode 1), calls the
+ * LLM, and publishes token chunks back to the request's {@code replyTo} (a {@code client.<clientId>}
+ * lite). Each request runs on its own virtual thread. Multi-turn history is keyed by
+ * {@code sessionId} (sessionId IS the conversation key).
+ *
+ * <p>Mode 2 (publish/subscribe) has no agent involvement — a gateway publishes chunks directly to a
+ * per-session lite topic; consumers subscribe via the runtime. This class is mode-1-only.</p>
+ */
+@Slf4j
+public class StreamingAgent {
+
+    private final CloudEventsClient client;
+    private final String agentParent;
+    private final String agentId;
+    private final OpenAiLlmClient llm;
+    private final ConversationStore store;
+    /** One virtual thread per in-flight stream. */
+    private final ExecutorService streamExecutor = Executors.newThreadPerTaskExecutor(
+        Thread.ofVirtual().name("em-agent-stream-", 1).factory());
+    private final AtomicInteger activeSessions = new AtomicInteger(0);
+
+    public StreamingAgent(CloudEventsClient client, String agentParent, String agentId,
+                          OpenAiLlmClient llm, ConversationStore store) {
+        this.client = client;
+        this.agentParent = agentParent;
+        this.agentId = agentId;
+        this.llm = llm;
+        this.store = store;
+    }
+
+    /** Subscribe to this agent's channel; runs until {@link #shutdown()}. */
+    public void start() {
+        // Internal wire: the runtime publishes compact EventMeshFrame bytes (not CloudEvents) onto
+        // agent.<agentId>; decode each frame to a StreamRequest.
+        client.subscribeLiteBytes(agentParent, "agent." + agentId,
+            frame -> streamExecutor.submit(() -> handleRequest(frame)));
+        log.info("StreamingAgent subscribed (private-wire): parent={} lite=agent.{}", agentParent, agentId);
+    }
+
+    /** In-flight stream count, reported to the runtime via heartbeat. */
+    public int activeSessions() {
+        return activeSessions.get();
+    }
+
+    private void handleRequest(byte[] frame) {
+        StreamRequest req = org.apache.eventmesh.common.wire.WireCodecs.get().decodeRequest(frame);
+        String sessionId = req.getSessionId();
+        String replyTo = req.getReplyTo();
+        int[] seq = {0};
+        activeSessions.incrementAndGet();
+        log.info("stream request: sessionId={} replyTo={} model={}", sessionId, replyTo, req.getModel());
+        // Multi-turn: prepend conversation history (empty for a new sessionId), then this turn's prompt.
+        List<Map<String, String>> messages = store.get(sessionId);
+        messages.add(message("user", req.getPrompt()));
+        StringBuilder answer = new StringBuilder();
+        try {
+            llm.stream(messages, req.getModel(), token -> {
+                answer.append(token);
+                publish(replyTo, sessionId, seq, token, false, null);
+            });
+            publish(replyTo, sessionId, seq, "", true, null);
+            log.info("stream completed: sessionId={} chunks={}", sessionId, seq[0] - 1);
+            store.appendTurn(sessionId, req.getPrompt(), answer.toString());
+        } catch (Exception e) {
+            log.warn("stream failed: sessionId={} err={}", sessionId, e.toString());
+            publish(replyTo, sessionId, seq, "", true, "llm error: " + e.getMessage());
+        } finally {
+            activeSessions.decrementAndGet();
+        }
+    }
+
+    private void publish(String replyTo, String sessionId, int[] seq, String chunk, boolean done, String error) {
+        StreamChunk c = StreamChunk.builder()
+            .sessionId(sessionId).seq(seq[0]++).chunk(chunk).done(done).error(error).build();
+        // replyTo = "parent#lite" (client-parent#client.<clientId>); fall back to agentParent.
+        int hash = replyTo == null ? -1 : replyTo.indexOf('#');
+        String parent = hash >= 0 ? replyTo.substring(0, hash) : agentParent;
+        String lite = hash >= 0 ? replyTo.substring(hash + 1) : replyTo;
+        try {
+            // Encode the chunk via the WireCodec SPI and publish raw bytes over the internal wire.
+            byte[] frame = org.apache.eventmesh.common.wire.WireCodecs.get().encode(c);
+            if (!client.publishLiteBytes(parent, lite, frame)) {
+                log.warn("publishLiteBytes non-202: sessionId={} seq={} (runtime not lite-capable?)",
+                    sessionId, c.getSeq());
+            }
+        } catch (Exception e) {
+            log.warn("publishLiteBytes failed: sessionId={} seq={} err={}", sessionId, c.getSeq(), e.toString());
+        }
+    }
+
+    private static Map<String, String> message(String role, String content) {
+        Map<String, String> m = new HashMap<>();
+        m.put("role", role);
+        m.put("content", content == null ? "" : content);
+        return m;
+    }
+
+    public void shutdown() {
+        streamExecutor.shutdownNow();
+    }
+}
diff --git a/eventmesh-agent/src/main/java/org/apache/eventmesh/agent/llm/OpenAiLlmClient.java b/eventmesh-agent/src/main/java/org/apache/eventmesh/agent/llm/OpenAiLlmClient.java
new file mode 100644
index 000000000..bf2b71833
--- /dev/null
+++ b/eventmesh-agent/src/main/java/org/apache/eventmesh/agent/llm/OpenAiLlmClient.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.agent.llm;
+
+import java.net.URI;
+import java.time.Duration;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Minimal OpenAI-compatible chat-completion streaming client. Issues {@code POST {base}/v1/chat/
+ * completions} with {@code stream:true} and {@code Authorization: Bearer <key>}, reads the SSE token
+ * stream and invokes {@code chunkCb} per {@code choices[0].delta.content}. Compatible with OpenAI,
+ * Azure-OpenAI, vLLM, Ollama (OpenAI mode), DeepSeek, Moonshot and most internal gateways. Blocking;
+ * intended to run on a virtual thread.
+ */
+@Slf4j
+public class OpenAiLlmClient {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private final String baseUrl;
+    private final String apiKey;
+    private final String defaultModel;
+    private final java.net.http.HttpClient http;
+
+    public OpenAiLlmClient(String baseUrl, String apiKey, String defaultModel) {
+        this.baseUrl = baseUrl == null || baseUrl.trim().isEmpty() ? "https://api.openai.com" : baseUrl;
+        this.apiKey = apiKey == null ? "" : apiKey;
+        this.defaultModel = defaultModel;
+        this.http = java.net.http.HttpClient.newBuilder()
+            .connectTimeout(Duration.ofSeconds(10))
+            .build();
+    }
+
+    /**
+     * Stream the completion for {@code prompt}; {@code chunkCb} receives each token fragment in
+     * order. Blocks until the server closes the stream ([DONE] or end-of-body).
+     *
+     * @param model overrides the default model when non-null
+     */
+    public void stream(String prompt, String model, Consumer<String> chunkCb) throws Exception {
+        java.util.List<java.util.Map<String, String>> msgs = new java.util.ArrayList<>();
+        java.util.Map<String, String> m = new java.util.HashMap<>();
+        m.put("role", "user");
+        m.put("content", prompt == null ? "" : prompt);
+        msgs.add(m);
+        stream(msgs, model, chunkCb);
+    }
+
+    /**
+     * Stream a completion for the given message list (supports multi-turn: pass prior history +
+     * the current user message). Each map must contain {@code role} + {@code content}.
+     */
+    public void stream(java.util.List<java.util.Map<String, String>> messages, String model,
+                       Consumer<String> chunkCb) throws Exception {
+        String useModel = (model != null && !model.isEmpty()) ? model : defaultModel;
+        ObjectNode body = MAPPER.createObjectNode();
+        body.put("model", useModel);
+        ArrayNode msgs = body.putArray("messages");
+        for (java.util.Map<String, String> message : messages) {
+            ObjectNode msg = msgs.addObject();
+            msg.put("role", message.getOrDefault("role", "user"));
+            msg.put("content", message.getOrDefault("content", ""));
+        }
+        body.put("stream", true);
+
+        java.net.http.HttpRequest req = java.net.http.HttpRequest.newBuilder()
+            .uri(URI.create(trimSlash(this.baseUrl) + "/v1/chat/completions"))
+            .timeout(Duration.ofMinutes(3))
+            .header("Content-Type", "application/json")
+            .header("Accept", "text/event-stream")
+            .header("Authorization", "Bearer " + apiKey)
+            .POST(java.net.http.HttpRequest.BodyPublishers.ofString(MAPPER.writeValueAsString(body)))
+            .build();
+
+        java.net.http.HttpResponse<Stream<String>> resp =
+            http.send(req, java.net.http.HttpResponse.BodyHandlers.ofLines());
+        int status = resp.statusCode();
+        if (status != 200) {
+            throw new RuntimeException("LLM HTTP " + status + " (check llm.base.url / llm.api.key / llm.model)");
+        }
+        try (Stream<String> lines = resp.body()) {
+            lines.filter(l -> l.startsWith("data:")).forEach(line -> {
+                String payload = line.substring(5).trim();
+                if (payload.isEmpty() || "[DONE]".equals(payload)) {
+                    return;
+                }
+                try {
+                    JsonNode node = MAPPER.readTree(payload);
+                    JsonNode choices = node.get("choices");
+                    if (choices != null && choices.isArray() && choices.size() > 0) {
+                        JsonNode delta = choices.get(0).get("delta");
+                        if (delta != null && delta.has("content")) {
+                            String content = delta.get("content").asText("");
+                            if (!content.isEmpty()) {
+                                chunkCb.accept(content);
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    log.debug("skip unparseable SSE line: {}", payload);
+                }
+            });
+        }
+    }
+
+    private static String trimSlash(String url) {
+        return url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
+    }
+}
diff --git a/eventmesh-agent/src/test/java/org/apache/eventmesh/agent/ConversationStoreTest.java b/eventmesh-agent/src/test/java/org/apache/eventmesh/agent/ConversationStoreTest.java
new file mode 100644
index 000000000..dabb5604d
--- /dev/null
+++ b/eventmesh-agent/src/test/java/org/apache/eventmesh/agent/ConversationStoreTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.agent;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+
+/** Covers ConversationStore get/append/trim + the null-convId single-turn case. */
+class ConversationStoreTest {
+
+    @Test
+    void nullConversationIsSingleTurn() {
+        ConversationStore store = new ConversationStore(20);
+        assertThat(store.get(null)).isEmpty();
+        store.appendTurn(null, "p", "a"); // no-op
+        assertThat(store.get(null)).isEmpty();
+    }
+
+    @Test
+    void appendsUserAssistantPairs() {
+        ConversationStore store = new ConversationStore(20);
+        store.appendTurn("c1", "hello", "hi");
+        store.appendTurn("c1", "how are you", "fine");
+        List<Map<String, String>> msgs = store.get("c1");
+        assertThat(msgs).hasSize(4);
+        assertThat(msgs).extracting(m -> m.get("role")).containsExactly("user", "assistant", "user", "assistant");
+        assertThat(msgs.get(0).get("content")).isEqualTo("hello");
+        assertThat(msgs.get(3).get("content")).isEqualTo("fine");
+    }
+
+    @Test
+    void getReturnsIndependentSnapshot() {
+        ConversationStore store = new ConversationStore(20);
+        store.appendTurn("c1", "p", "a");
+        List<Map<String, String>> snap = store.get("c1");
+        snap.clear(); // mutate the snapshot
+        assertThat(store.get("c1")).hasSize(2); // store unaffected
+    }
+
+    @Test
+    void trimsToSlidingWindow() {
+        ConversationStore store = new ConversationStore(4); // 2 turns kept
+        store.appendTurn("c1", "p1", "a1");
+        store.appendTurn("c1", "p2", "a2");
+        store.appendTurn("c1", "p3", "a3"); // 3 turns = 6 msgs → trim oldest
+        List<Map<String, String>> msgs = store.get("c1");
+        assertThat(msgs).hasSize(4); // trimmed to maxMessages
+        // oldest turn (p1/a1) dropped; only a2-turn and a3-turn kept
+        assertThat(msgs).extracting(m -> m.get("content"))
+            .containsExactly("p2", "a2", "p3", "a3");
+    }
+
+    @Test
+    void conversationsAreIsolated() {
+        ConversationStore store = new ConversationStore(20);
+        store.appendTurn("c1", "p", "a");
+        store.appendTurn("c2", "x", "y");
+        assertThat(store.get("c1")).hasSize(2);
+        assertThat(store.get("c2")).hasSize(2);
+        assertThat(store.get("c1").get(0).get("content")).isEqualTo("p");
+        assertThat(store.get("c2").get(0).get("content")).isEqualTo("x");
+    }
+}
diff --git a/eventmesh-agent/src/test/java/org/apache/eventmesh/agent/llm/OpenAiLlmClientTest.java b/eventmesh-agent/src/test/java/org/apache/eventmesh/agent/llm/OpenAiLlmClientTest.java
new file mode 100644
index 000000000..9655e1fd9
--- /dev/null
+++ b/eventmesh-agent/src/test/java/org/apache/eventmesh/agent/llm/OpenAiLlmClientTest.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.agent.llm;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpServer;
+
+/**
+ * Drives {@link OpenAiLlmClient} against an in-process mock that speaks the OpenAI streaming SSE
+ * wire format (data: {choices:[{delta:{content}}]} ... data: [DONE]). Asserts ordered token
+ * delivery. Hermetic — no external LLM/key.
+ */
+class OpenAiLlmClientTest {
+
+    private HttpServer server;
+    private OpenAiLlmClient client;
+
+    @BeforeEach
+    void setUp() throws IOException {
+        server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
+        server.start();
+        String base = "http://127.0.0.1:" + server.getAddress().getPort();
+        client = new OpenAiLlmClient(base, "test-key", "test-model");
+    }
+
+    @AfterEach
+    void tearDown() {
+        if (server != null) {
+            server.stop(0);
+        }
+    }
+
+    @Test
+    void streamsOrderedDeltasUntilDone() throws Exception {
+        server.createContext("/v1/chat/completions", this::handleOk);
+
+        List<String> chunks = new ArrayList<>();
+        client.stream("hello", null, chunks::add);
+
+        assertThat(chunks).containsExactly("Hel", "lo", " ", "world");
+    }
+
+    @Test
+    void streamsWithMessageListSendsAllMessages() throws Exception {
+        List<JsonNode> captured = new ArrayList<>();
+        server.createContext("/v1/chat/completions", ex -> {
+            captured.add(new ObjectMapper().readTree(ex.getRequestBody().readAllBytes()));
+            handleOk(ex);
+        });
+
+        // multi-turn messages list (user/assistant/user)
+        List<Map<String, String>> messages = new ArrayList<>();
+        messages.add(message("user", "hi"));
+        messages.add(message("assistant", "hello"));
+        messages.add(message("user", "how are you"));
+        List<String> chunks = new ArrayList<>();
+        client.stream(messages, null, chunks::add);
+
+        // the full messages list was serialized into the request body, in order
+        assertThat(captured).hasSize(1);
+        JsonNode sent = captured.get(0).get("messages");
+        assertThat(sent).hasSize(3);
+        assertThat(sent.get(0).get("role").asText()).isEqualTo("user");
+        assertThat(sent.get(1).get("role").asText()).isEqualTo("assistant");
+        assertThat(sent.get(2).get("content").asText()).isEqualTo("how are you");
+        // and chunks still stream back
+        assertThat(chunks).containsExactly("Hel", "lo", " ", "world");
+    }
+
+    private static Map<String, String> message(String role, String content) {
+        Map<String, String> m = new HashMap<>();
+        m.put("role", role);
+        m.put("content", content);
+        return m;
+    }
+
+    @Test
+    void throwsOnNon200() {
+        server.createContext("/v1/chat/completions", this::handle500);
+
+        List<String> chunks = new ArrayList<>();
+        try {
+            client.stream("hello", null, chunks::add);
+        } catch (Exception e) {
+            assertThat(e).hasMessageContaining("LLM HTTP 500");
+        }
+        assertThat(chunks).isEmpty();
+    }
+
+    private void handleOk(HttpExchange exchange) throws IOException {
+        String body = String.join("\n",
+            "data: {\"choices\":[{\"delta\":{\"content\":\"Hel\"}}]}",
+            "",
+            "data: {\"choices\":[{\"delta\":{\"content\":\"lo\"}}]}",
+            "",
+            "data: {\"choices\":[{\"delta\":{\"content\":\" \"}}]}",
+            "",
+            "data: {\"choices\":[{\"delta\":{\"content\":\"world\"}}]}",
+            "",
+            "data: {\"choices\":[{\"delta\":{}}]}",
+            "",
+            "data: [DONE]",
+            "",
+            "");
+        byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
+        exchange.getResponseHeaders().add("Content-Type", "text/event-stream");
+        exchange.sendResponseHeaders(200, bytes.length);
+        try (OutputStream os = exchange.getResponseBody()) {
+            os.write(bytes);
+        }
+    }
+
+    private void handle500(HttpExchange exchange) throws IOException {
+        byte[] bytes = "{\"error\":\"upstream\"}".getBytes(StandardCharsets.UTF_8);
+        exchange.sendResponseHeaders(500, bytes.length);
+        try (OutputStream os = exchange.getResponseBody()) {
+            os.write(bytes);
+        }
+    }
+}
diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/protocol/ByteTransport.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/protocol/ByteTransport.java
new file mode 100644
index 000000000..c7e081825
--- /dev/null
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/protocol/ByteTransport.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.protocol;
+
+import java.util.Arrays;
+
+/**
+ * A {@link ProtocolTransportObject} wrapping a raw {@code byte[]} — used by protocol adaptors whose
+ * wire format is a byte blob (e.g. CloudEvents structured-JSON). This lets {@code FrameAdaptor}
+ * implementations stay typed against {@code ProtocolTransportObject} without inventing a new class
+ * per byte-carrying protocol.
+ */
+public final class ByteTransport implements ProtocolTransportObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final byte[] bytes;
+
+    public ByteTransport(byte[] bytes) {
+        this.bytes = bytes;
+    }
+
+    public byte[] getBytes() {
+        return bytes;
+    }
+
+    @Override
+    public String toString() {
+        return "ByteTransport[" + (bytes == null ? 0 : bytes.length) + " bytes]";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof ByteTransport)) {
+            return false;
+        }
+        return Arrays.equals(bytes, ((ByteTransport) o).bytes);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(bytes);
+    }
+}
diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/stream/StreamChunk.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/stream/StreamChunk.java
new file mode 100644
index 000000000..8492e6f54
--- /dev/null
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/stream/StreamChunk.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.stream;
+
+import java.util.Map;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * One fragment in a streaming-call response (agent → runtime over the reply lite). {@link #seq} is
+ * monotonic and strictly ordered within a single-queue lite parent; the terminal chunk carries
+ * {@code done=true} ({@code error} non-null on failure).
+ *
+ * <p>{@link #eventType} and {@link #meta} are optional extensions for AgentScope-style
+ * multi-event streaming (thought, tool-call, structured output). The default OpenAI-compatible
+ * path only emits {@code eventType=text} (or {@code null} treated as text) with {@code chunk}
+ * as the text delta. The agentscope-harness path maps {@code AgentEvent} subtypes to distinct
+ * {@code eventType} values and serializes the full event JSON into {@code meta}.</p>
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class StreamChunk {
+
+    /** Which session this chunk belongs to (matches the request's sessionId). */
+    private String sessionId;
+    /** Monotonic sequence (0,1,2,...). */
+    private int seq;
+    /** The token/text fragment. Empty on the terminal done chunk. */
+    private String chunk;
+    /** True on the terminal chunk (normal end or error). */
+    private boolean done;
+    /** Non-null only on a terminal error chunk. */
+    private String error;
+    /**
+     * Optional event type for multi-event streaming: {@code null|text|thought|tool|structured}.
+     * {@code null} is treated as {@code text}. Only the agentscope-harness path emits non-text
+     * types; the default OpenAI-compatible path only sets {@code chunk} (text delta).
+     */
+    private String eventType;
+    /**
+     * Optional passthrough metadata for AgentScope events. The agentscope-harness path serializes
+     * the full {@code AgentEvent} JSON here (thought content, tool name + arguments, structured
+     * result). The default OpenAI-compatible path leaves this null.
+     */
+    private Map<String, Object> meta;
+}
diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/stream/StreamEventCodec.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/stream/StreamEventCodec.java
new file mode 100644
index 000000000..509545514
--- /dev/null
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/stream/StreamEventCodec.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.stream;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.UUID;
+
+import io.cloudevents.CloudEvent;
+import io.cloudevents.core.builder.CloudEventBuilder;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * CloudEvent ↔ stream-DTO codec. Extension names are lowercase-alnum (CloudEvents spec allows a-z0-9
+ * only — no hyphens), matching the {@code emcorrelationid} / {@code emtenantid} convention. Both
+ * request and chunk travel as structured CloudEvents (JSON on the wire) over lite topics; the
+ * variable payload rides in {@code data} as UTF-8 text, the structured fields as extensions.
+ */
+public final class StreamEventCodec {
+
+    public static final String CE_TYPE_REQUEST = "stream.request";
+    public static final String CE_TYPE_CHUNK = "stream.chunk";
+
+    public static final String EXT_SESSION_ID = "emsessionid";
+    public static final String EXT_REPLY_TO = "emreplyto";
+    public static final String EXT_SEQ = "emseq";
+    public static final String EXT_DONE = "emdone";
+    public static final String EXT_ERROR = "emerror";
+    public static final String EXT_MODEL = "emmodel";
+    public static final String EXT_CONVERSATION_ID = "emconvid";
+    /** Optional: {@code null|text|thought|tool|structured} (null treated as text). */
+    public static final String EXT_EVENT_TYPE = "emeventtype";
+    /** Optional: passthrough metadata, serialized as a JSON string. */
+    public static final String EXT_META = "emmeta";
+
+    /** Reused for {@link #EXT_META} JSON-string serialization (meta is a free-form map). */
+    private static final ObjectMapper META_MAPPER = new ObjectMapper();
+    private static final TypeReference<Map<String, Object>> META_TYPE = new TypeReference<Map<String, Object>>() {
+    };
+
+    private StreamEventCodec() {
+    }
+
+    // -------------------- encode (all overloads grouped) --------------------
+
+    /** Encode a streaming-call request → CloudEvent (data = prompt, text/plain). */
+    public static CloudEvent toEvent(StreamRequest req) {
+        CloudEventBuilder b = CloudEventBuilder.v1()
+            .withId(UUID.randomUUID().toString())
+            .withSource(URI.create("em://stream/" + req.getSessionId()))
+            .withType(CE_TYPE_REQUEST)
+            .withDataContentType("text/plain")
+            .withData((req.getPrompt() == null ? "" : req.getPrompt()).getBytes(StandardCharsets.UTF_8))
+            .withExtension(EXT_SESSION_ID, req.getSessionId())
+            .withExtension(EXT_REPLY_TO, req.getReplyTo());
+        if (req.getModel() != null) {
+            b.withExtension(EXT_MODEL, req.getModel());
+        }
+        if (req.getConversationId() != null) {
+            b.withExtension(EXT_CONVERSATION_ID, req.getConversationId());
+        }
+        return b.build();
+    }
+
+    /** Encode a response chunk → CloudEvent (data = chunk text, text/plain). */
+    public static CloudEvent toEvent(StreamChunk chunk) {
+        CloudEventBuilder b = CloudEventBuilder.v1()
+            .withId(chunk.getSessionId() + "-" + chunk.getSeq())
+            .withSource(URI.create("em://stream/" + chunk.getSessionId()))
+            .withType(CE_TYPE_CHUNK)
+            .withDataContentType("text/plain")
+            .withData((chunk.getChunk() == null ? "" : chunk.getChunk()).getBytes(StandardCharsets.UTF_8))
+            .withExtension(EXT_SESSION_ID, chunk.getSessionId())
+            .withExtension(EXT_SEQ, chunk.getSeq())
+            .withExtension(EXT_DONE, chunk.isDone());
+        if (chunk.getError() != null) {
+            b.withExtension(EXT_ERROR, chunk.getError());
+        }
+        if (chunk.getEventType() != null) {
+            b.withExtension(EXT_EVENT_TYPE, chunk.getEventType());
+        }
+        if (chunk.getMeta() != null && !chunk.getMeta().isEmpty()) {
+            try {
+                b.withExtension(EXT_META, META_MAPPER.writeValueAsString(chunk.getMeta()));
+            } catch (JsonProcessingException ignored) {
+                // best-effort: skip meta if it can't be serialized
+            }
+        }
+        return b.build();
+    }
+
+    // -------------------- decode --------------------
+
+    public static StreamRequest requestFromEvent(CloudEvent event) {
+        return StreamRequest.builder()
+            .sessionId(str(event, EXT_SESSION_ID))
+            .replyTo(str(event, EXT_REPLY_TO))
+            .model(str(event, EXT_MODEL))
+            .conversationId(str(event, EXT_CONVERSATION_ID))
+            .prompt(textData(event))
+            .build();
+    }
+
+    public static StreamChunk chunkFromEvent(CloudEvent event) {
+        Object seqVal = event.getExtension(EXT_SEQ);
+        return StreamChunk.builder()
+            .sessionId(str(event, EXT_SESSION_ID))
+            .seq(seqVal == null ? 0 : ((Number) seqVal).intValue())
+            .chunk(textData(event))
+            .done(parseBool(event.getExtension(EXT_DONE)))
+            .error(str(event, EXT_ERROR))
+            .eventType(str(event, EXT_EVENT_TYPE))
+            .meta(parseMeta(event.getExtension(EXT_META)))
+            .build();
+    }
+
+    // -------------------- helpers --------------------
+
+    private static String textData(CloudEvent event) {
+        return event.getData() != null ? new String(event.getData().toBytes(), StandardCharsets.UTF_8) : "";
+    }
+
+    private static String str(CloudEvent event, String ext) {
+        Object v = event.getExtension(ext);
+        return v == null ? null : v.toString();
+    }
+
+    private static boolean parseBool(Object v) {
+        if (v == null) {
+            return false;
+        }
+        if (v instanceof Boolean) {
+            return (Boolean) v;
+        }
+        return Boolean.parseBoolean(v.toString());
+    }
+
+    /** Parse the {@code emmeta} extension (a JSON string) back into a map; null/invalid → null. */
+    private static Map<String, Object> parseMeta(Object v) {
+        if (v == null) {
+            return null;
+        }
+        String json = v.toString();
+        if (json.isEmpty()) {
+            return null;
+        }
+        try {
+            Map<String, Object> parsed = META_MAPPER.readValue(json, META_TYPE);
+            return parsed == null || parsed.isEmpty() ? null : parsed;
+        } catch (JsonProcessingException ignored) {
+            // best-effort: leave meta null if it can't be parsed
+            return null;
+        }
+    }
+}
diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/stream/StreamRequest.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/stream/StreamRequest.java
new file mode 100644
index 000000000..b32efc3e0
--- /dev/null
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/stream/StreamRequest.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.stream;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * A streaming-call request (runtime → agent over the agent's lite channel). The agent consumes this,
+ * calls the LLM, and publishes {@link StreamChunk}s back to {@link #replyTo}.
+ *
+ * <p>v2: {@link #sessionId} is {@code <agentId>:<uuid>} — the routing key (runtime parses the
+ * {@code :} prefix for zero-lookup routing) AND the agent's conversation-context key. {@link #replyTo}
+ * is the reply address ({@code parent#lite}). {@link #conversationId} is v1 legacy (v2 keys multi-turn
+ * by sessionId) — kept temporarily for the v1 path.</p>
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class StreamRequest {
+
+    /** Session id ({@code <agentId>:<uuid>} in v2); routing + conversation key. */
+    private String sessionId;
+    /** Reply address the agent publishes response chunks to (v2: {@code parent#lite}; v1 legacy: lite name). */
+    private String replyTo;
+    /** The user prompt. */
+    private String prompt;
+    /** The LLM model id (optional; agent default applies if null). */
+    private String model;
+    /**
+     * Conversation id for multi-turn chat (v1 legacy, optional). v2 keys multi-turn by sessionId;
+     * this field is retained only until the v1 path is removed. Null/absent = single-turn.
+     */
+    private String conversationId;
+}
diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/EventMeshFrame.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/EventMeshFrame.java
new file mode 100644
index 000000000..8770c7d6f
--- /dev/null
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/EventMeshFrame.java
@@ -0,0 +1,360 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.wire;
+
+import org.apache.eventmesh.common.stream.StreamChunk;
+import org.apache.eventmesh.common.stream.StreamRequest;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.time.OffsetDateTime;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import io.cloudevents.CloudEvent;
+import io.cloudevents.core.builder.CloudEventBuilder;
+
+/**
+ * The single internal wire format for EventMesh — one frame, two message families. Used on every
+ * internal path: streaming session/subscribe (runtime↔agent over lite topics), normal pub/sub
+ * (runtime↔storage MQ), and cross-instance forwarding. The PUBLIC surface (HTTP/SSE/TCP/gRPC/A2A)
+ * stays CloudEvents; the runtime converts at the edge (CE → EventMeshFrame on ingress,
+ * EventMeshFrame → CE on egress). See {@code docs/eventmesh-architecture-refinement.md}.
+ *
+ * <p>This unifies the former {@code PrivateFrame} (streaming-only, fixed sessionId/seq fields) and
+ * {@code CloudEventEnvelope} (event-only, all-KV) into one format whose fixed header serves the
+ * streaming hot path while a generic KV section carries arbitrary named attributes for events.
+ *
+ * <h3>Wire layout</h3>
+ * <pre>
+ *   fixed header (14 bytes, big-endian):
+ *     [magic:1]    = 0xEF ('E'+'F' sentinel)
+ *     [ver:1]      = 1
+ *     [msgType:1]  = 1 STREAM_REQ | 2 STREAM_CHUNK | 3 EVENT
+ *     [flags:1]    = bit0 done | bit1 hasError | bit2 hasMeta | bit3..7 reserved
+ *     [seq:4]      = stream sequence (STREAM_CHUNK only; 0 otherwise)
+ *     [keyCount:2] = number of KV attribute entries
+ *     [dataLen:4]  = data byte length
+ *
+ *   KV attributes (keyCount × ):
+ *     [nameLen:2][name]    UTF-8
+ *     [valLen:4][value]    UTF-8 (toString of the source value)
+ *
+ *   data payload:
+ *     [data: dataLen]      raw bytes (chunk text / prompt / CloudEvent data)
+ * </pre>
+ *
+ * <p><b>STREAM_REQ</b> uses KV {@code replyTo/model/conversationId} + data=prompt.
+ * <b>STREAM_CHUNK</b> uses {@code seq} (fixed) + {@code done} (flag) + KV
+ * {@code sessionId/eventType/error/meta} + data=chunk text. <b>EVENT</b> uses KV
+ * {@code id/type/subject/time/emttl/emcorrelationid/...} (whatever the CloudEvent carried) + data.
+ * On decode, EVENT KV names that match standard CE attributes are rebuilt via the right builder
+ * methods; everything else becomes an extension.</p>
+ */
+public final class EventMeshFrame {
+
+    public static final int MAGIC = 0xEF;
+    public static final int VERSION = 1;
+    public static final int TYPE_STREAM_REQ = 1;
+    public static final int TYPE_STREAM_CHUNK = 2;
+    public static final int TYPE_EVENT = 3;
+
+    // flag bits
+    static final int FLAG_DONE = 0x01;
+    static final int FLAG_HAS_ERROR = 0x02;
+    static final int FLAG_HAS_META = 0x04;
+
+    private static final int HEADER_LEN = 14; // magic(1)+ver(1)+msgType(1)+flags(1)+seq(4)+keyCount(2)+dataLen(4)
+
+    // well-known KV keys (kept short to save bytes on the wire)
+    static final String K_SESSION_ID = "sid";
+    static final String K_REPLY_TO = "replyTo";
+    static final String K_MODEL = "model";
+    static final String K_CONVERSATION_ID = "conv";
+    static final String K_EVENT_TYPE = "etype";
+    static final String K_ERROR = "err";
+    static final String K_META = "meta";
+
+    private final int msgType;
+    private final int flags;
+    private final int seq;
+    private final Map<String, String> attrs;
+    private final byte[] data;
+
+    EventMeshFrame(int msgType, int flags, int seq, Map<String, String> attrs, byte[] data) {
+        this.msgType = msgType;
+        this.flags = flags;
+        this.seq = seq;
+        this.attrs = attrs;
+        this.data = data;
+    }
+
+    // -------------------- factories: streaming --------------------
+
+    public static EventMeshFrame fromRequest(StreamRequest req) {
+        Map<String, String> a = new LinkedHashMap<>();
+        String sid = req.getSessionId() == null ? "" : req.getSessionId();
+        a.put(K_SESSION_ID, sid);
+        if (req.getReplyTo() != null && !req.getReplyTo().isEmpty()) {
+            a.put(K_REPLY_TO, req.getReplyTo());
+        }
+        if (req.getModel() != null && !req.getModel().isEmpty()) {
+            a.put(K_MODEL, req.getModel());
+        }
+        if (req.getConversationId() != null && !req.getConversationId().isEmpty()) {
+            a.put(K_CONVERSATION_ID, req.getConversationId());
+        }
+        byte[] data = utf8(req.getPrompt() == null ? "" : req.getPrompt());
+        return new EventMeshFrame(TYPE_STREAM_REQ, 0, 0, a, data);
+    }
+
+    public static EventMeshFrame fromChunk(StreamChunk c) {
+        Map<String, String> a = new LinkedHashMap<>();
+        a.put(K_SESSION_ID, c.getSessionId() == null ? "" : c.getSessionId());
+        int flags = c.isDone() ? FLAG_DONE : 0;
+        if (c.getError() != null && !c.getError().isEmpty()) {
+            flags |= FLAG_HAS_ERROR;
+            a.put(K_ERROR, c.getError());
+        }
+        if (c.getEventType() != null && !c.getEventType().isEmpty()) {
+            a.put(K_EVENT_TYPE, c.getEventType());
+        }
+        if (c.getMeta() != null && !c.getMeta().isEmpty()) {
+            flags |= FLAG_HAS_META;
+            a.put(K_META, MetaJson.stringify(c.getMeta()));
+        }
+        byte[] data = utf8(c.getChunk() == null ? "" : c.getChunk());
+        return new EventMeshFrame(TYPE_STREAM_CHUNK, flags, c.getSeq(), a, data);
+    }
+
+    // -------------------- factories: CloudEvent (normal pub/sub) --------------------
+
+    /** Build an EVENT frame from pre-mapped attributes + raw data (used by protocol adaptors that
+     *  map a non-CloudEvents protocol directly, e.g. MeshMessage). */
+    public static EventMeshFrame event(Map<String, String> attrs, byte[] data) {
+        return new EventMeshFrame(TYPE_EVENT, 0, 0, attrs, data);
+    }
+
+    /** Capture an arbitrary {@link CloudEvent} as an EVENT frame (all attributes + extensions + data). */
+    public static EventMeshFrame fromCloudEvent(CloudEvent event) {
+        Map<String, String> a = new LinkedHashMap<>();
+        for (String name : event.getAttributeNames()) {
+            Object v = event.getAttribute(name);
+            if (v != null) {
+                a.put(name, v.toString());
+            }
+        }
+        for (String name : event.getExtensionNames()) {
+            Object v = event.getExtension(name);
+            if (v != null) {
+                a.put(name, v.toString());
+            }
+        }
+        byte[] data = event.getData() != null ? event.getData().toBytes() : new byte[0];
+        return new EventMeshFrame(TYPE_EVENT, 0, 0, a, data);
+    }
+
+    /** Reconstruct the {@link CloudEvent} (EVENT frame → attributes + extensions + data). */
+    public CloudEvent toCloudEvent() {
+        if (msgType != TYPE_EVENT) {
+            throw new IllegalStateException("not an EVENT frame");
+        }
+        CloudEventBuilder b = CloudEventBuilder.v1();
+        for (Map.Entry<String, String> e : attrs.entrySet()) {
+            applyCe(b, e.getKey(), e.getValue());
+        }
+        if (data.length > 0) {
+            b.withData(data);
+        }
+        return b.build();
+    }
+
+    private static void applyCe(CloudEventBuilder b, String name, String value) {
+        // specversion is implied (v1 builder → V1); datacontentencoding was removed in CE 3.0.
+        switch (name) {
+            case "specversion":
+                return; // builder defaults to V1.0
+            case "id":
+                b.withId(value);
+                return;
+            case "source":
+                b.withSource(URI.create(value));
+                return;
+            case "type":
+                b.withType(value);
+                return;
+            case "datacontenttype":
+                b.withDataContentType(value);
+                return;
+            case "subject":
+                b.withSubject(value);
+                return;
+            case "time":
+                b.withTime(OffsetDateTime.parse(value));
+                return;
+            case "dataschema":
+                b.withDataSchema(URI.create(value));
+                return;
+            case "datacontentencoding":
+                return; // removed in CE 3.0; drop silently
+            default:
+                b.withExtension(name, value);
+        }
+    }
+
+    // -------------------- decode back to streaming DTOs --------------------
+
+    public StreamRequest toStreamRequest() {
+        if (msgType != TYPE_STREAM_REQ) {
+            throw new IllegalStateException("not a STREAM_REQ frame");
+        }
+        return StreamRequest.builder()
+            .sessionId(attrs.get(K_SESSION_ID))
+            .replyTo(attrs.get(K_REPLY_TO))
+            .model(attrs.get(K_MODEL))
+            .conversationId(attrs.get(K_CONVERSATION_ID))
+            .prompt(str(data))
+            .build();
+    }
+
+    public StreamChunk toChunk() {
+        if (msgType != TYPE_STREAM_CHUNK) {
+            throw new IllegalStateException("not a STREAM_CHUNK frame");
+        }
+        return StreamChunk.builder()
+            .sessionId(attrs.get(K_SESSION_ID))
+            .seq(seq)
+            .chunk(str(data))
+            .done((flags & FLAG_DONE) != 0)
+            .error(attrs.get(K_ERROR))
+            .eventType(attrs.get(K_EVENT_TYPE))
+            .meta(attrs.containsKey(K_META) ? MetaJson.parse(attrs.get(K_META)) : null)
+            .build();
+    }
+
+    // -------------------- encode / decode --------------------
+
+    public byte[] encode() {
+        byte[][] nameBytes = new byte[attrs.size()][];
+        byte[][] valBytes = new byte[attrs.size()][];
+        int kvLen = 0;
+        int i = 0;
+        for (Map.Entry<String, String> e : attrs.entrySet()) {
+            nameBytes[i] = utf8(e.getKey());
+            valBytes[i] = utf8(e.getValue());
+            kvLen += 2 + nameBytes[i].length + 4 + valBytes[i].length;
+            i++;
+        }
+        ByteBuffer buf = ByteBuffer.allocate(HEADER_LEN + kvLen + data.length);
+        buf.put((byte) MAGIC);
+        buf.put((byte) VERSION);
+        buf.put((byte) msgType);
+        buf.put((byte) flags);
+        buf.putInt(seq);
+        buf.putShort((short) attrs.size());
+        buf.putInt(data.length);
+        for (int j = 0; j < nameBytes.length; j++) {
+            buf.putShort((short) nameBytes[j].length);
+            buf.put(nameBytes[j]);
+            buf.putInt(valBytes[j].length);
+            buf.put(valBytes[j]);
+        }
+        buf.put(data);
+        return buf.array();
+    }
+
+    public static EventMeshFrame decode(byte[] bytes) {
+        return decode(bytes, 0, bytes.length);
+    }
+
+    public static EventMeshFrame decode(byte[] bytes, int offset, int length) {
+        if (length < HEADER_LEN) {
+            throw new IllegalArgumentException("frame too short: " + length);
+        }
+        ByteBuffer buf = ByteBuffer.wrap(bytes, offset, length);
+        int magic = buf.get() & 0xFF;
+        if (magic != MAGIC) {
+            throw new IllegalArgumentException("bad frame magic: 0x" + Integer.toHexString(magic));
+        }
+        int ver = buf.get() & 0xFF;
+        if (ver != VERSION) {
+            throw new IllegalArgumentException("unsupported frame version: " + ver);
+        }
+        int msgType = buf.get() & 0xFF;
+        int flags = buf.get() & 0xFF;
+        int seq = buf.getInt();
+        int keyCount = buf.getShort() & 0xFFFF;
+        int dataLen = buf.getInt();
+        Map<String, String> attrs = new LinkedHashMap<>(keyCount);
+        for (int i = 0; i < keyCount; i++) {
+            String name = getString(buf, buf.getShort() & 0xFFFF);
+            String value = getString(buf, buf.getInt());
+            attrs.put(name, value);
+        }
+        byte[] data = new byte[dataLen];
+        buf.get(data);
+        return new EventMeshFrame(msgType, flags, seq, attrs, data);
+    }
+
+    // -------------------- accessors --------------------
+
+    public int msgType() {
+        return msgType;
+    }
+
+    public boolean isStreamRequest() {
+        return msgType == TYPE_STREAM_REQ;
+    }
+
+    public boolean isStreamChunk() {
+        return msgType == TYPE_STREAM_CHUNK;
+    }
+
+    public boolean isEvent() {
+        return msgType == TYPE_EVENT;
+    }
+
+    public boolean isDone() {
+        return (flags & FLAG_DONE) != 0;
+    }
+
+    public Map<String, String> attributes() {
+        return attrs;
+    }
+
+    public byte[] data() {
+        return data;
+    }
+
+    // -------------------- helpers --------------------
+
+    private static byte[] utf8(String s) {
+        return s == null ? new byte[0] : s.getBytes(StandardCharsets.UTF_8);
+    }
+
+    private static String str(byte[] b) {
+        return new String(b, StandardCharsets.UTF_8);
+    }
+
+    private static String getString(ByteBuffer buf, int len) {
+        byte[] b = new byte[len];
+        buf.get(b);
+        return new String(b, StandardCharsets.UTF_8);
+    }
+}
diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/EventMeshFrameCodec.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/EventMeshFrameCodec.java
new file mode 100644
index 000000000..2b352fa02
--- /dev/null
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/EventMeshFrameCodec.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.wire;
+
+import org.apache.eventmesh.common.stream.StreamChunk;
+import org.apache.eventmesh.common.stream.StreamRequest;
+
+import io.cloudevents.CloudEvent;
+
+/**
+ * Default {@link WireCodec} — delegates to the compact {@link EventMeshFrame} binary format (the
+ * single internal wire format since the architecture refinement). This is the codec
+ * {@link WireCodecs#get()} loads when no override is configured.
+ *
+ * <p>Alternative codecs (CE-protobuf, binary CE) implement {@link WireCodec} and are selected via
+ * {@code -Deventmesh.wire.codec=<fqcn>}.</p>
+ */
+public class EventMeshFrameCodec implements WireCodec {
+
+    @Override
+    public byte[] encode(StreamRequest request) {
+        return EventMeshFrame.fromRequest(request).encode();
+    }
+
+    @Override
+    public byte[] encode(StreamChunk chunk) {
+        return EventMeshFrame.fromChunk(chunk).encode();
+    }
+
+    @Override
+    public byte[] encode(CloudEvent event) {
+        return EventMeshFrame.fromCloudEvent(event).encode();
+    }
+
+    @Override
+    public StreamRequest decodeRequest(byte[] bytes) {
+        return EventMeshFrame.decode(bytes).toStreamRequest();
+    }
+
+    @Override
+    public StreamChunk decodeChunk(byte[] bytes) {
+        return EventMeshFrame.decode(bytes).toChunk();
+    }
+
+    @Override
+    public CloudEvent decodeEvent(byte[] bytes) {
+        return EventMeshFrame.decode(bytes).toCloudEvent();
+    }
+}
diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/MeshMessageFrameCodec.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/MeshMessageFrameCodec.java
new file mode 100644
index 000000000..3e1b736c7
--- /dev/null
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/MeshMessageFrameCodec.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.wire;
+
+import org.apache.eventmesh.common.Constants;
+import org.apache.eventmesh.common.protocol.tcp.EventMeshMessage;
+import org.apache.eventmesh.common.protocol.tcp.Header;
+
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Direct {@link EventMeshFrame} ↔ {@link EventMeshMessage} conversion — the legacy TCP
+ * {@code MeshMessage} protocol maps straight to the internal frame, without going through
+ * CloudEvent as an intermediary (CloudEvent and MeshMessage are peer external protocols; neither
+ * converts to the other). Used by the legacy TCP ingress/egress ({@code MeshMessagePackageRouter},
+ * {@code NettyTcpPushChannel}) so a MeshMessage client's path is MeshMessage → Frame (ingress) and
+ * Frame → MeshMessage (egress), mirroring how the CloudEvents path is CloudEvents → Frame directly.
+ *
+ * <p>Field mapping:<ul>
+ *   <li>{@code message.topic} ↔ frame attribute {@code subject} (the routing key both protocols use)</li>
+ *   <li>{@code message.body} (content string) ↔ frame {@code data} (UTF-8 bytes)</li>
+ *   <li>{@code message.properties} + {@code message.headers} ↔ frame KV attributes (merged)</li>
+ *   <li>{@code header.seq} ↔ frame attribute {@code id} (the message id)</li>
+ *   <li>frame {@code type} = {@code eventmeshmessage} (marks the origin protocol)</li>
+ * </ul></p>
+ */
+public final class MeshMessageFrameCodec {
+
+    /** Attribute marking a frame built from a MeshMessage (so egress knows to convert back to MeshMessage). */
+    public static final String ATTR_ORIGIN_PROTOCOL = "emorigin";
+    public static final String ORIGIN_MESH_MESSAGE = "meshmessage";
+
+    private MeshMessageFrameCodec() {
+    }
+
+    /** Build an EventMeshFrame from a legacy TCP MeshMessage + its header. */
+    public static EventMeshFrame fromMeshMessage(Header header, EventMeshMessage message) {
+        Map<String, String> attrs = new LinkedHashMap<>();
+        attrs.put("specversion", "1.0");
+        attrs.put("type", "eventmeshmessage");
+        attrs.put("source", "/"); // required CE field (CloudEventBuilder needs it if anyone calls toCloudEvent)
+        attrs.put(ATTR_ORIGIN_PROTOCOL, ORIGIN_MESH_MESSAGE);
+        if (header != null && header.getSeq() != null) {
+            attrs.put("id", header.getSeq());
+        }
+        if (message.getTopic() != null) {
+            attrs.put("subject", message.getTopic());
+        }
+        // message-level properties + headers merge into the frame's KV section.
+        if (message.getProperties() != null) {
+            attrs.putAll(message.getProperties());
+        }
+        if (message.getHeaders() != null) {
+            attrs.putAll(message.getHeaders());
+        }
+        byte[] data = message.getBody() == null ? new byte[0]
+            : message.getBody().getBytes(Constants.DEFAULT_CHARSET);
+        return new EventMeshFrame(EventMeshFrame.TYPE_EVENT, 0, 0, attrs, data);
+    }
+
+    /** Reconstruct a legacy TCP MeshMessage (in a {@link org.apache.eventmesh.common.protocol.tcp.Package})
+     *  from an EventMeshFrame that originated as a MeshMessage. */
+    public static org.apache.eventmesh.common.protocol.tcp.Package toMeshMessagePackage(EventMeshFrame frame) {
+        Map<String, String> attrs = frame.attributes();
+        EventMeshMessage message = new EventMeshMessage();
+        message.setTopic(attrs.get("subject"));
+        message.setBody(frame.data() == null ? "" : new String(frame.data(), StandardCharsets.UTF_8));
+
+        Map<String, String> props = new LinkedHashMap<>();
+        // Carry the remaining attributes as message properties (skip the CE-shaped + origin markers).
+        for (Map.Entry<String, String> e : attrs.entrySet()) {
+            String k = e.getKey();
+            if ("type".equals(k) || ATTR_ORIGIN_PROTOCOL.equals(k) || "id".equals(k) || "subject".equals(k)
+                || "source".equals(k) || "specversion".equals(k) || "time".equals(k) || "dataschema".equals(k)
+                || "datacontenttype".equals(k)) {
+                continue;
+            }
+            props.put(k, e.getValue());
+        }
+        message.setProperties(props);
+
+        org.apache.eventmesh.common.protocol.tcp.Package pkg = new org.apache.eventmesh.common.protocol.tcp.Package();
+        pkg.setBody(message);
+        return pkg;
+    }
+}
diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/MetaJson.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/MetaJson.java
new file mode 100644
index 000000000..11365eb61
--- /dev/null
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/MetaJson.java
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.wire;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Minimal JSON codec for the streaming {@code meta} map (string/number/bool/null + nested values),
+ * kept dependency-free so {@code eventmesh-common}'s wire package doesn't pull Jackson. Used by
+ * {@link EventMeshFrame} for the {@code meta} attribute on stream chunks.
+ */
+final class MetaJson {
+
+    private MetaJson() {
+    }
+
+    static String stringify(Map<String, Object> meta) {
+        StringBuilder sb = new StringBuilder();
+        sb.append('{');
+        boolean first = true;
+        for (Map.Entry<String, Object> e : meta.entrySet()) {
+            if (!first) {
+                sb.append(',');
+            }
+            first = false;
+            quote(sb, e.getKey());
+            sb.append(':');
+            value(sb, e.getValue());
+        }
+        sb.append('}');
+        return sb.toString();
+    }
+
+    static Map<String, Object> parse(String json) {
+        if (json == null || json.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        try {
+            return new Parser(json).parseObject();
+        } catch (Exception ex) {
+            return Collections.emptyMap();
+        }
+    }
+
+    private static void quote(StringBuilder sb, String s) {
+        sb.append('"').append(escape(s)).append('"');
+    }
+
+    private static String escape(String s) {
+        StringBuilder out = new StringBuilder();
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+            switch (c) {
+                case '"':
+                    out.append("\\\"");
+                    break;
+                case '\\':
+                    out.append("\\\\");
+                    break;
+                case '\n':
+                    out.append("\\n");
+                    break;
+                case '\r':
+                    out.append("\\r");
+                    break;
+                case '\t':
+                    out.append("\\t");
+                    break;
+                default:
+                    out.append(c);
+            }
+        }
+        return out.toString();
+    }
+
+    private static void value(StringBuilder sb, Object v) {
+        if (v == null) {
+            sb.append("null");
+        } else if (v instanceof Number || v instanceof Boolean) {
+            sb.append(v);
+        } else {
+            quote(sb, v.toString());
+        }
+    }
+
+    private static final class Parser {
+        private final String src;
+        private int idx;
+
+        Parser(String src) {
+            this.src = src;
+        }
+
+        private void skipWs() {
+            while (idx < src.length() && Character.isWhitespace(src.charAt(idx))) {
+                idx++;
+            }
+        }
+
+        Map<String, Object> parseObject() {
+            Map<String, Object> map = new LinkedHashMap<>();
+            skipWs();
+            if (idx >= src.length() || src.charAt(idx) != '{') {
+                return map;
+            }
+            idx++;
+            skipWs();
+            if (idx < src.length() && src.charAt(idx) == '}') {
+                return map;
+            }
+            while (idx < src.length()) {
+                skipWs();
+                String key = parseString();
+                skipWs();
+                if (idx < src.length() && src.charAt(idx) == ':') {
+                    idx++;
+                }
+                Object val = parseValue();
+                map.put(key, val);
+                skipWs();
+                if (idx < src.length() && src.charAt(idx) == ',') {
+                    idx++;
+                } else {
+                    break;
+                }
+            }
+            return map;
+        }
+
+        private Object parseValue() {
+            skipWs();
+            if (idx >= src.length()) {
+                return null;
+            }
+            char c = src.charAt(idx);
+            if (c == '"') {
+                return parseString();
+            }
+            if (c == '{') {
+                return parseObject();
+            }
+            if (c == 't' || c == 'f' || c == 'n' || c == '-' || (c >= '0' && c <= '9')) {
+                return parseLiteral();
+            }
+            return null;
+        }
+
+        private String parseString() {
+            StringBuilder out = new StringBuilder();
+            if (idx >= src.length() || src.charAt(idx) != '"') {
+                return "";
+            }
+            idx++;
+            while (idx < src.length()) {
+                char c = src.charAt(idx++);
+                if (c == '"') {
+                    break;
+                }
+                if (c == '\\' && idx < src.length()) {
+                    char e = src.charAt(idx++);
+                    switch (e) {
+                        case '"':
+                            out.append('"');
+                            break;
+                        case '\\':
+                            out.append('\\');
+                            break;
+                        case 'n':
+                            out.append('\n');
+                            break;
+                        case 'r':
+                            out.append('\r');
+                            break;
+                        case 't':
+                            out.append('\t');
+                            break;
+                        default:
+                            out.append(e);
+                    }
+                } else {
+                    out.append(c);
+                }
+            }
+            return out.toString();
+        }
+
+        private Object parseLiteral() {
+            int start = idx;
+            while (idx < src.length()) {
+                char c = src.charAt(idx);
+                if (c == ',' || c == '}' || c == ']' || Character.isWhitespace(c)) {
+                    break;
+                }
+                idx++;
+            }
+            String tok = src.substring(start, idx);
+            if ("true".equals(tok)) {
+                return Boolean.TRUE;
+            }
+            if ("false".equals(tok)) {
+                return Boolean.FALSE;
+            }
+            if ("null".equals(tok)) {
+                return null;
+            }
+            try {
+                if (tok.contains(".") || tok.contains("e") || tok.contains("E")) {
+                    return Double.parseDouble(tok);
+                }
+                return Long.parseLong(tok);
+            } catch (NumberFormatException ex) {
+                return tok;
+            }
+        }
+    }
+}
diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/WireCodec.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/WireCodec.java
new file mode 100644
index 000000000..004238d69
--- /dev/null
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/WireCodec.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.wire;
+
+import org.apache.eventmesh.common.stream.StreamChunk;
+import org.apache.eventmesh.common.stream.StreamRequest;
+
+import io.cloudevents.CloudEvent;
+
+/**
+ * Internal-wire codec SPI — the binary format messages travel in between the runtime, the storage
+ * MQ, and the agent (everywhere EventMesh talks to itself). The PUBLIC surface (HTTP/SSE/TCP/gRPC/
+ * A2A) stays CloudEvents; this codec only shapes the internal bytes, so the format can be swapped
+ * (compact {@link EventMeshFrame}, CE-protobuf, binary CE, …) without touching call sites. See
+ * {@code docs/eventmesh-architecture-refinement.md} §1.3.
+ *
+ * <p>Lives in {@code eventmesh-common} (not the {@code eventmesh-spi} module) so every consumer
+ * (runtime, agent, storage plugins) can reach it without a circular dependency. Pluggability is via
+ * {@link WireCodecs#get()}, which reflects a system property
+ * ({@code -Deventmesh.wire.codec=<fqcn>}) and falls back to {@link EventMeshFrameCodec}.</p>
+ *
+ * <p>Three message families share one codec, mirroring {@link EventMeshFrame}'s msgTypes:
+ * {@code StreamRequest}, {@code StreamChunk}, and {@code CloudEvent} (normal pub/sub).</p>
+ *
+ * @since 1.11.0
+ */
+public interface WireCodec {
+
+    /** Encode a streaming-call request → internal wire bytes. */
+    byte[] encode(StreamRequest request);
+
+    /** Encode a streaming response chunk → internal wire bytes. */
+    byte[] encode(StreamChunk chunk);
+
+    /** Encode a normal pub/sub CloudEvent → internal wire bytes. */
+    byte[] encode(CloudEvent event);
+
+    /** Decode internal wire bytes (a STREAM_REQ) back to a streaming-call request. */
+    StreamRequest decodeRequest(byte[] bytes);
+
+    /** Decode internal wire bytes (a STREAM_CHUNK) back to a streaming response chunk. */
+    StreamChunk decodeChunk(byte[] bytes);
+
+    /** Decode internal wire bytes (an EVENT) back to a CloudEvent. */
+    CloudEvent decodeEvent(byte[] bytes);
+}
diff --git a/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/WireCodecs.java b/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/WireCodecs.java
new file mode 100644
index 000000000..7990242a5
--- /dev/null
+++ b/eventmesh-common/src/main/java/org/apache/eventmesh/common/wire/WireCodecs.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.wire;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Entry point for the internal-wire codec. Returns the configured {@link WireCodec}: by default
+ * {@link EventMeshFrameCodec}; override with {@code -Deventmesh.wire.codec=<fqcn>} (the class must
+ * have a public no-arg constructor and implement {@link WireCodec}). Falls back to
+ * {@link EventMeshFrameCodec} if the property is unset or the named class can't be loaded.
+ *
+ * <p>Callers use {@link #get()} instead of touching {@link EventMeshFrame} directly, so the wire
+ * format stays pluggable from a single place.</p>
+ */
+@Slf4j
+public final class WireCodecs {
+
+    /** System property name overriding the wire codec implementation class. */
+    public static final String CODEC_PROPERTY = "eventmesh.wire.codec";
+
+    private static volatile WireCodec cached;
+
+    private WireCodecs() {
+    }
+
+    /**
+     * @return the configured {@link WireCodec} (default EventMeshFrameCodec, override via property).
+     */
+    public static WireCodec get() {
+        WireCodec c = cached;
+        if (c != null) {
+            return c;
+        }
+        synchronized (WireCodecs.class) {
+            if (cached == null) {
+                cached = load();
+            }
+            return cached;
+        }
+    }
+
+    private static WireCodec load() {
+        String fqcn = System.getProperty(CODEC_PROPERTY, "").trim();
+        if (!fqcn.isEmpty()) {
+            try {
+                Class<?> cls = Class.forName(fqcn, true, Thread.currentThread().getContextClassLoader());
+                return (WireCodec) cls.getDeclaredConstructor().newInstance();
+            } catch (Throwable t) {
+                log.warn("failed to load WireCodec '{}' ({}); falling back to EventMeshFrameCodec", fqcn, t.toString());
+            }
+        }
+        return new EventMeshFrameCodec();
+    }
+}
diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/stream/StreamEventCodecTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/stream/StreamEventCodecTest.java
new file mode 100644
index 000000000..72a4b98d0
--- /dev/null
+++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/stream/StreamEventCodecTest.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+
+import io.cloudevents.CloudEvent;
+import io.cloudevents.core.format.EventFormat;
+import io.cloudevents.core.provider.EventFormatProvider;
+import io.cloudevents.jackson.JsonFormat;
+
+/**
+ * Round-trips {@link StreamRequest}/{@link StreamChunk} through the codec AND
+ * the structured-JSON wire format (encode → JsonFormat serialize → deserialize → decode). Covers the
+ * v2 framing: sessionId (incl. the {@code <agentId>:<uuid>} colon delimiter), replyTo, and
+ * spec-legal extension names.
+ */
+class StreamEventCodecTest {
+
+    private final EventFormat format = EventFormatProvider.getInstance().resolveFormat(JsonFormat.CONTENT_TYPE);
+
+    private CloudEvent roundTrip(CloudEvent event) {
+        return format.deserialize(format.serialize(event));
+    }
+
+    @Test
+    void requestRoundTripPreservesAllFields() {
+        StreamRequest req = StreamRequest.builder()
+            .sessionId("agentX:abc123").replyTo("em-agent#resp-abc123").prompt("hello world").model("gpt-4o-mini").build();
+
+        StreamRequest decoded = StreamEventCodec.requestFromEvent(roundTrip(StreamEventCodec.toEvent(req)));
+
+        assertThat(decoded.getSessionId()).isEqualTo("agentX:abc123");
+        assertThat(decoded.getReplyTo()).isEqualTo("em-agent#resp-abc123");
+        assertThat(decoded.getModel()).isEqualTo("gpt-4o-mini");
+        assertThat(decoded.getPrompt()).isEqualTo("hello world");
+    }
+
+    @Test
+    void sessionIdWithColonRoundTripsIntact() {
+        // v2 sessionId = <agentId>:<uuid>; the ':' is the routing delimiter and must survive the wire.
+        // agentId may itself contain '-' (e.g. auto "agent-<uuid>").
+        StreamRequest req = StreamRequest.builder()
+            .sessionId("agent-1:b2c3d4").replyTo("p#l").prompt("p").build();
+
+        StreamRequest decoded = StreamEventCodec.requestFromEvent(roundTrip(StreamEventCodec.toEvent(req)));
+
+        assertThat(decoded.getSessionId()).isEqualTo("agent-1:b2c3d4");
+    }
+
+    @Test
+    void requestWithNullModelOmitsExtension() {
+        StreamRequest req = StreamRequest.builder()
+            .sessionId("s1").replyTo("p#l").prompt("p").model(null).build();
+
+        StreamRequest decoded = StreamEventCodec.requestFromEvent(roundTrip(StreamEventCodec.toEvent(req)));
+
+        assertThat(decoded.getModel()).isNull();
+        assertThat(decoded.getPrompt()).isEqualTo("p");
+    }
+
+    @Test
+    void requestConversationIdRoundTrips() {
+        StreamRequest req = StreamRequest.builder()
+            .sessionId("s1").replyTo("p#l").prompt("p").conversationId("conv-abc").build();
+
+        StreamRequest decoded = StreamEventCodec.requestFromEvent(roundTrip(StreamEventCodec.toEvent(req)));
+
+        assertThat(decoded.getConversationId()).isEqualTo("conv-abc");
+    }
+
+    @Test
+    void requestWithNullConversationIdOmitsExtension() {
+        StreamRequest req = StreamRequest.builder()
+            .sessionId("s1").replyTo("p#l").prompt("p").conversationId(null).build();
+
+        StreamRequest decoded = StreamEventCodec.requestFromEvent(roundTrip(StreamEventCodec.toEvent(req)));
+
+        assertThat(decoded.getConversationId()).isNull();
+    }
+
+    @Test
+    void chunkRoundTripPreservesSeqAndText() {
+        StreamChunk chunk = StreamChunk.builder()
+            .sessionId("s1").seq(3).chunk("token-3").done(false).build();
+
+        StreamChunk decoded = StreamEventCodec.chunkFromEvent(roundTrip(StreamEventCodec.toEvent(chunk)));
+
+        assertThat(decoded.getSessionId()).isEqualTo("s1");
+        assertThat(decoded.getSeq()).isEqualTo(3);
+        assertThat(decoded.getChunk()).isEqualTo("token-3");
+        assertThat(decoded.isDone()).isFalse();
+        assertThat(decoded.getError()).isNull();
+    }
+
+    @Test
+    void terminalErrorChunkRoundTrips() {
+        StreamChunk chunk = StreamChunk.builder()
+            .sessionId("s1").seq(9).chunk("").done(true).error("llm 503").build();
+
+        StreamChunk decoded = StreamEventCodec.chunkFromEvent(roundTrip(StreamEventCodec.toEvent(chunk)));
+
+        assertThat(decoded.isDone()).isTrue();
+        assertThat(decoded.getError()).isEqualTo("llm 503");
+        assertThat(decoded.getChunk()).isEmpty();
+        assertThat(decoded.getSeq()).isEqualTo(9);
+    }
+
+    @Test
+    void chunkRoundTripWithEventTypeAndMeta() {
+        // agentscope-harness path: a tool-call event carries eventType=tool + meta (tool name/args).
+        Map<String, Object> meta = new LinkedHashMap<>();
+        meta.put("toolName", "search");
+        meta.put("args", "apache eventmesh");
+        StreamChunk chunk = StreamChunk.builder()
+            .sessionId("s1").seq(4).chunk("").done(false).eventType("tool").meta(meta).build();
+
+        StreamChunk decoded = StreamEventCodec.chunkFromEvent(roundTrip(StreamEventCodec.toEvent(chunk)));
+
+        assertThat(decoded.getEventType()).isEqualTo("tool");
+        assertThat(decoded.getMeta()).containsEntry("toolName", "search").containsEntry("args", "apache eventmesh");
+    }
+
+    @Test
+    void chunkWithNullEventTypeAndMetaOmitsExtensions() {
+        // default OpenAI path: eventType/meta are null → no extensions emitted → decode back to null.
+        StreamChunk chunk = StreamChunk.builder()
+            .sessionId("s1").seq(0).chunk("Hello").done(false).build();
+
+        StreamChunk decoded = StreamEventCodec.chunkFromEvent(roundTrip(StreamEventCodec.toEvent(chunk)));
+
+        assertThat(decoded.getEventType()).isNull();
+        assertThat(decoded.getMeta()).isNull();
+        assertThat(decoded.getChunk()).isEqualTo("Hello");
+    }
+
+    @Test
+    void chunkWithEmptyMetaOmitsExtension() {
+        // empty map is treated like null (no extension emitted).
+        StreamChunk chunk = StreamChunk.builder()
+            .sessionId("s1").seq(1).chunk("x").done(false).meta(new LinkedHashMap<>()).build();
+
+        StreamChunk decoded = StreamEventCodec.chunkFromEvent(roundTrip(StreamEventCodec.toEvent(chunk)));
+
+        assertThat(decoded.getMeta()).isNull();
+    }
+
+    @Test
+    void extensionNamesAreSpecLegalLowercaseAlnum() {
+        // CloudEvents spec: extension names must be lowercase a-z0-9 (no hyphens).
+        assertThat(StreamEventCodec.EXT_SESSION_ID).matches("[a-z0-9]+");
+        assertThat(StreamEventCodec.EXT_REPLY_TO).matches("[a-z0-9]+");
+        assertThat(StreamEventCodec.EXT_SEQ).matches("[a-z0-9]+");
+        assertThat(StreamEventCodec.EXT_DONE).matches("[a-z0-9]+");
+        assertThat(StreamEventCodec.EXT_ERROR).matches("[a-z0-9]+");
+        assertThat(StreamEventCodec.EXT_MODEL).matches("[a-z0-9]+");
+        assertThat(StreamEventCodec.EXT_EVENT_TYPE).matches("[a-z0-9]+");
+        assertThat(StreamEventCodec.EXT_META).matches("[a-z0-9]+");
+    }
+}
diff --git a/eventmesh-common/src/test/java/org/apache/eventmesh/common/wire/EventMeshFrameTest.java b/eventmesh-common/src/test/java/org/apache/eventmesh/common/wire/EventMeshFrameTest.java
new file mode 100644
index 000000000..2ccc2d699
--- /dev/null
+++ b/eventmesh-common/src/test/java/org/apache/eventmesh/common/wire/EventMeshFrameTest.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.common.wire;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.eventmesh.common.stream.StreamChunk;
+import org.apache.eventmesh.common.stream.StreamRequest;
+
+import java.net.URI;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+
+import io.cloudevents.CloudEvent;
+import io.cloudevents.core.builder.CloudEventBuilder;
+
+/**
+ * Round-trip tests for {@link EventMeshFrame}: all three message families (STREAM_REQ, STREAM_CHUNK,
+ * EVENT) encode→decode equality, plus footprint vs CloudEvents-JSON.
+ */
+class EventMeshFrameTest {
+
+    @Test
+    void streamRequestRoundTrip() {
+        StreamRequest req = StreamRequest.builder()
+            .sessionId("agent-1:uuid").replyTo("client-parent#client.c1")
+            .prompt("Introduce EventMesh").model("qwen-max").conversationId("conv-9").build();
+        StreamRequest back = EventMeshFrame.decode(EventMeshFrame.fromRequest(req).encode()).toStreamRequest();
+        assertEquals(req.getSessionId(), back.getSessionId());
+        assertEquals(req.getReplyTo(), back.getReplyTo());
+        assertEquals(req.getPrompt(), back.getPrompt());
+        assertEquals(req.getModel(), back.getModel());
+        assertEquals(req.getConversationId(), back.getConversationId());
+    }
+
+    @Test
+    void streamChunkRoundTripMinimal() {
+        StreamChunk c = StreamChunk.builder().sessionId("s1").seq(7).chunk("Hello").done(false).build();
+        StreamChunk back = EventMeshFrame.decode(EventMeshFrame.fromChunk(c).encode()).toChunk();
+        assertEquals(c.getSessionId(), back.getSessionId());
+        assertEquals(c.getSeq(), back.getSeq());
+        assertEquals(c.getChunk(), back.getChunk());
+        assertEquals(c.isDone(), back.isDone());
+    }
+
+    @Test
+    void streamChunkTerminalWithError() {
+        StreamChunk c = StreamChunk.builder().sessionId("s1").seq(3).chunk("").done(true).error("LLM 500").build();
+        StreamChunk back = EventMeshFrame.decode(EventMeshFrame.fromChunk(c).encode()).toChunk();
+        assertEquals("LLM 500", back.getError());
+        assertTrue(back.isDone());
+    }
+
+    @Test
+    void streamChunkWithExtensions() {
+        Map<String, Object> meta = new LinkedHashMap<>();
+        meta.put("tool", "search");
+        meta.put("count", 3);
+        StreamChunk c = StreamChunk.builder().sessionId("s2").seq(1).chunk("delta")
+            .eventType("tool").meta(meta).build();
+        StreamChunk back = EventMeshFrame.decode(EventMeshFrame.fromChunk(c).encode()).toChunk();
+        assertEquals("tool", back.getEventType());
+        assertEquals("search", back.getMeta().get("tool"));
+        assertEquals(3, ((Number) back.getMeta().get("count")).intValue());
+    }
+
+    @Test
+    void eventRoundTripPreservesAttributesAndExtensions() {
+        CloudEvent ce = CloudEventBuilder.v1()
+            .withId("order-123")
+            .withSource(URI.create("/checkout"))
+            .withType("com.example.order.created")
+            .withSubject("orders")
+            .withDataContentType("application/json")
+            .withData("{\"amount\":99}".getBytes())
+            .withExtension("emttl", "60000")
+            .withExtension("emcorrelationid", "corr-1")
+            .build();
+        CloudEvent back = EventMeshFrame.decode(EventMeshFrame.fromCloudEvent(ce).encode()).toCloudEvent();
+        assertEquals("order-123", back.getId());
+        assertEquals("com.example.order.created", back.getType());
+        assertEquals("orders", back.getSubject());
+        assertEquals("/checkout", back.getSource().toString());
+        assertEquals("60000", back.getExtension("emttl"));
+        assertEquals("corr-1", back.getExtension("emcorrelationid"));
+        assertEquals("{\"amount\":99}", new String(back.getData().toBytes()));
+    }
+
+    @Test
+    void eventRoundTripNoData() {
+        CloudEvent ce = CloudEventBuilder.v1()
+            .withId("e1").withSource(URI.create("/x")).withType("t").build();
+        CloudEvent back = EventMeshFrame.decode(EventMeshFrame.fromCloudEvent(ce).encode()).toCloudEvent();
+        assertEquals("e1", back.getId());
+        assertEquals("t", back.getType());
+    }
+
+    @Test
+    void streamChunkSmallerThanCloudEventsJson() {
+        StreamChunk tiny = StreamChunk.builder().sessionId("agent-1:abc").seq(0).chunk("He").build();
+        int frameBytes = EventMeshFrame.fromChunk(tiny).encode().length;
+        CloudEvent ce = org.apache.eventmesh.common.stream.StreamEventCodec.toEvent(tiny);
+        int ceBytes = io.cloudevents.core.provider.EventFormatProvider.getInstance()
+            .resolveFormat(io.cloudevents.jackson.JsonFormat.CONTENT_TYPE).serialize(ce).length;
+        assertTrue(frameBytes * 3 < ceBytes,
+            "frame (" + frameBytes + "B) not smaller enough than CE (" + ceBytes + "B)");
+    }
+
+    @Test
+    void badMagicRejected() {
+        StreamChunk c = StreamChunk.builder().sessionId("s").seq(0).chunk("x").build();
+        byte[] wire = EventMeshFrame.fromChunk(c).encode();
+        wire[0] = 0x00;
+        try {
+            EventMeshFrame.decode(wire);
+        } catch (IllegalArgumentException expected) {
+            return;
+        }
+        throw new AssertionError("expected IllegalArgumentException for bad magic");
+    }
+
+    @Test
+    void unicodePayloadRoundTrips() {
+        // CJK payload built from char codes to keep this source file pure ASCII.
+        String cjk = new String(new char[] {0x4f60, 0x597d, 0x4e16, 0x754c});
+        String sessionId = "s-" + (char) 0x4e2d;
+        StreamChunk c = StreamChunk.builder().sessionId(sessionId).seq(2).chunk(cjk).build();
+        StreamChunk back = EventMeshFrame.decode(EventMeshFrame.fromChunk(c).encode()).toChunk();
+        assertEquals(cjk, back.getChunk());
+        assertEquals(sessionId, back.getSessionId());
+    }
+
+    @Test
+    void decodeWithOffsetHandlesSlice() {
+        StreamChunk c = StreamChunk.builder().sessionId("s").seq(1).chunk("hi").build();
+        byte[] wire = EventMeshFrame.fromChunk(c).encode();
+        byte[] padded = new byte[wire.length + 5];
+        System.arraycopy(wire, 0, padded, 5, wire.length);
+        StreamChunk back = EventMeshFrame.decode(padded, 5, wire.length).toChunk();
+        assertEquals("hi", back.getChunk());
+    }
+
+    @Test
+    void metaOrderAndTypesPreserved() {
+        Map<String, Object> meta = new LinkedHashMap<>();
+        meta.put("a", "1");
+        meta.put("b", 2L);
+        meta.put("c", true);
+        StreamChunk c = StreamChunk.builder().sessionId("s").seq(0).chunk("").meta(meta).build();
+        Map<String, Object> back = EventMeshFrame.decode(EventMeshFrame.fromChunk(c).encode()).toChunk().getMeta();
+        assertArrayEquals(meta.keySet().toArray(), back.keySet().toArray());
+        assertEquals("1", back.get("a"));
+        assertEquals(2L, back.get("b"));
+        assertEquals(true, back.get("c"));
+    }
+
+    @Test
+    void msgTypeDistinguishesFamilies() {
+        EventMeshFrame req = EventMeshFrame.fromRequest(StreamRequest.builder().sessionId("s").prompt("p").build());
+        EventMeshFrame chunk = EventMeshFrame.fromChunk(StreamChunk.builder().sessionId("s").seq(0).chunk("c").build());
+        EventMeshFrame evt = EventMeshFrame.fromCloudEvent(
+            CloudEventBuilder.v1().withId("i").withSource(URI.create("/x")).withType("t").build());
+        assertTrue(req.isStreamRequest());
+        assertTrue(chunk.isStreamChunk());
+        assertTrue(evt.isEvent());
+    }
+}
diff --git a/eventmesh-examples/src/main/java/org/apache/eventmesh/cloudevents/demo/stream/StreamingCallDemo.java b/eventmesh-examples/src/main/java/org/apache/eventmesh/cloudevents/demo/stream/StreamingCallDemo.java
new file mode 100644
index 000000000..fe35846c8
--- /dev/null
+++ b/eventmesh-examples/src/main/java/org/apache/eventmesh/cloudevents/demo/stream/StreamingCallDemo.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.cloudevents.demo.stream;
+
+import org.apache.eventmesh.client.cloudevents.CloudEventsClient;
+import org.apache.eventmesh.client.cloudevents.stream.OpenSession;
+import org.apache.eventmesh.client.cloudevents.stream.StreamRequest;
+import org.apache.eventmesh.client.cloudevents.stream.StreamingResponse;
+import org.apache.eventmesh.client.cloudevents.stream.StreamingSession;
+
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Streaming-call client demo for the uni-architecture (mode 1: client↔agent, runtime-mediated).
+ *
+ * <p>This is a pure HTTP client demo that connects to a running EventMesh Runtime + at least one
+ * registered streaming Agent. It does NOT depend on any runtime server-side classes.
+ *
+ * <p>Prerequisites:
+ * <ol>
+ *   <li>A running EventMesh Runtime on {@code runtimeUrl} (default {@code http://localhost:8080}),
+ *       wired with a {@code SessionRouter} (see docs/sdk-streaming-call-guide.md §9).</li>
+ *   <li>At least one streaming Agent registered + heartbeating against that runtime.</li>
+ * </ol>
+ *
+ * <p>Run:
+ * <pre>
+ *   java org.apache.eventmesh.cloudevents.demo.stream.StreamingCallDemo [runtimeUrl] [prompt]
+ *   # defaults: runtimeUrl=http://localhost:8080  prompt="Introduce Apache EventMesh in three sentences."
+ * </pre>
+ *
+ * <p>The demo exercises {@code forEach}-only consumption (the sole posture) on a single-turn call,
+ * then a multi-turn session that proves context is carried across turns.
+ * See docs/sdk-streaming-call-guide.md for the full API reference.
+ */
+@Slf4j
+public class StreamingCallDemo {
+
+    private static final String DEFAULT_RUNTIME_URL = "http://localhost:8080";
+    private static final String DEFAULT_PROMPT = "Introduce Apache EventMesh in three sentences.";
+
+    public static void main(String[] args) throws Exception {
+        String runtimeUrl = args.length > 0 ? args[0] : DEFAULT_RUNTIME_URL;
+        String prompt = args.length > 1 ? args[1] : DEFAULT_PROMPT;
+
+        log.info("=== Streaming Call Demo ===");
+        log.info("Runtime: {}", runtimeUrl);
+
+        // Each session uses a FRESH clientId. The matchmaker binds clientId→agent, so a fresh clientId
+        // per session lets it load-balance across all registered agents (random within the
+        // least-loaded tier). The multi-turn session keeps a single client on purpose: sticky binding
+        // is what carries context across turns, so it stays on whichever agent it first binds to.
+
+        // 1) Single-turn session: openSession → call → forEach → close
+        withClient(runtimeUrl, c -> singleTurnForEach(c, prompt));
+
+        // 2) Multi-turn session: context is carried across turns (single client → sticky to one agent)
+        withClient(runtimeUrl, StreamingCallDemo::multiTurnSession);
+
+        log.info("=== Demo Complete ===");
+    }
+
+    /** Build a client with a unique clientId, run an action, always shut it down. */
+    private static void withClient(String runtimeUrl, ThrowingClientAction action) throws Exception {
+        CloudEventsClient client = CloudEventsClient.builder()
+                .runtimeUrl(runtimeUrl)
+                .clientId("streaming-demo-" + System.nanoTime())
+                .build();
+        try {
+            action.run(client);
+        } finally {
+            client.shutdown();
+        }
+    }
+
+    @FunctionalInterface
+    private interface ThrowingClientAction {
+        void run(CloudEventsClient client) throws Exception;
+    }
+
+    // ------------------------------------------------------------------
+    // Single-turn (openSession → call → forEach)
+    // ------------------------------------------------------------------
+
+    /** Open a session, make one call, print tokens as they arrive, close. */
+    private static void singleTurnForEach(CloudEventsClient client, String prompt) throws Exception {
+        log.info("");
+        log.info("--- [1] single-turn / forEach ---  prompt=\"{}\"", prompt);
+        StreamingSession session = client.streaming()
+                .openSession(OpenSession.builder().clientId(client.clientId()).build());
+        log.info("sessionId={}, agentId={}", session.sessionId(), session.agentId());
+        try {
+            try (StreamingResponse r = session.call(
+                    StreamRequest.builder().prompt(prompt).timeout(Duration.ofMinutes(2)).build())) {
+                StringBuilder sb = new StringBuilder();
+                r.forEach(chunk -> {
+                    sb.append(chunk.getChunk());
+                    System.out.print(chunk.getChunk());   // stream tokens as they arrive
+                })
+                        .orTimeout(2, TimeUnit.MINUTES)
+                        .join();
+                log.info("");
+                log.info("full reply: {}", sb);
+            }
+        } finally {
+            session.close();
+        }
+    }
+
+    // ------------------------------------------------------------------
+    // Multi-turn session
+    // ------------------------------------------------------------------
+
+    /**
+     * Open one session and run two turns. Closing a turn's {@link StreamingResponse} cancels only
+     * that turn's read; the same {@code sessionId} is reused so the agent accumulates context.
+     */
+    private static void multiTurnSession(CloudEventsClient client) throws Exception {
+        log.info("");
+        log.info("--- [2] multi-turn session (context carried across turns) ---");
+        StreamingSession session = client.streaming()
+                .openSession(OpenSession.builder().clientId(client.clientId()).build());
+        log.info("sessionId={}", session.sessionId());
+        try {
+            // Turn 1
+            try (StreamingResponse r = session.call("My name is Zhang San and I am a Java engineer.")) {
+                log.info("[turn 1] prompt=\"My name is Zhang San and I am a Java engineer.\"");
+                r.forEach(chunk -> System.out.print(chunk.getChunk())).join();
+                log.info("");
+            }
+
+            // Turn 2 — same session: the agent remembers turn 1
+            try (StreamingResponse r = session.call("What is my name and what do I do?")) {
+                log.info("[turn 2] prompt=\"What is my name and what do I do?\"");
+                r.forEach(chunk -> System.out.print(chunk.getChunk())).join();
+                log.info("");
+            }
+        } finally {
+            session.close();   // POST /session/close/{sessionId} — idempotent
+        }
+    }
+}
\ No newline at end of file
diff --git a/eventmesh-protocol-plugin/eventmesh-protocol-a2a/src/main/java/org/apache/eventmesh/protocol/a2a/A2AFrameAdaptor.java b/eventmesh-protocol-plugin/eventmesh-protocol-a2a/src/main/java/org/apache/eventmesh/protocol/a2a/A2AFrameAdaptor.java
new file mode 100644
index 000000000..b62a60486
--- /dev/null
+++ b/eventmesh-protocol-plugin/eventmesh-protocol-a2a/src/main/java/org/apache/eventmesh/protocol/a2a/A2AFrameAdaptor.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.protocol.a2a;
+
+import org.apache.eventmesh.common.protocol.ByteTransport;
+import org.apache.eventmesh.common.protocol.ProtocolTransportObject;
+import org.apache.eventmesh.common.wire.EventMeshFrame;
+import org.apache.eventmesh.protocol.api.FrameAdaptor;
+import org.apache.eventmesh.protocol.api.exception.ProtocolHandleException;
+
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * {@link FrameAdaptor} for the A2A (Agent-to-Agent) JSON-RPC 2.0 protocol. Converts A2A JSON-RPC
+ * messages directly to/from {@link EventMeshFrame} — no CloudEvent intermediary.
+ *
+ * <p>Ingress: A2A JSON-RPC bytes → parse JSON-RPC → extract routing (method, params._topic,
+ * agent-card identity) into frame attributes + raw JSON as data.
+ * Egress: frame attributes + data → reconstruct A2A JSON-RPC bytes.</p>
+ *
+ * <p>Registered under the name {@code a2a}; load via
+ * {@code FrameAdaptors.get("a2a")}.</p>
+ */
+public class A2AFrameAdaptor implements FrameAdaptor {
+
+    public static final String PROTOCOL_TYPE = "a2a";
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    @Override
+    public EventMeshFrame toFrame(ProtocolTransportObject proto) throws ProtocolHandleException {
+        byte[] jsonBytes;
+        if (proto instanceof ByteTransport) {
+            jsonBytes = ((ByteTransport) proto).getBytes();
+        } else {
+            jsonBytes = proto.toString().getBytes(StandardCharsets.UTF_8);
+        }
+        if (jsonBytes == null || jsonBytes.length == 0) {
+            throw new ProtocolHandleException("empty A2A body");
+        }
+        try {
+            String content = new String(jsonBytes, StandardCharsets.UTF_8);
+            JsonNode node = MAPPER.readTree(content);
+
+            Map<String, String> attrs = new LinkedHashMap<>();
+            attrs.put("specversion", "1.0");
+            attrs.put("type", "a2a");
+            attrs.put("source", "eventmesh-a2a");
+            attrs.put("id", node.has("id") ? node.get("id").asText() : java.util.UUID.randomUUID().toString());
+            attrs.put(A2AProtocolConstants.CE_EXTENSION_PROTOCOL, PROTOCOL_TYPE);
+
+            if (node.has("jsonrpc")) {
+                attrs.put("ema2aversion", node.get("jsonrpc").asText());
+            }
+            if (node.has("method")) {
+                String method = node.get("method").asText();
+                attrs.put(A2AProtocolConstants.CE_EXTENSION_A2A_METHOD, method);
+                attrs.put("type", A2AProtocolConstants.CE_TYPE_PREFIX + method.replace("/", ".") + ".req");
+            } else if (node.has("result") || node.has("error")) {
+                attrs.put("type", A2AProtocolConstants.CE_TYPE_PREFIX + "common.response");
+            }
+
+            // Extract routing from params
+            if (node.has("params")) {
+                JsonNode params = node.get("params");
+                if (params.has("_topic")) {
+                    attrs.put("subject", params.get("_topic").asText());
+                } else if (params.has("org_id") && params.has("unit_id") && params.has("agent_id")) {
+                    AgentIdentity identity = new AgentIdentity(
+                        params.get("org_id").asText(),
+                        params.get("unit_id").asText(),
+                        params.get("agent_id").asText());
+                    attrs.put("subject", identity.discoveryTopic());
+                }
+                if (params.has("_seq")) {
+                    attrs.put(A2AProtocolConstants.CE_EXTENSION_SEQ, params.get("_seq").asText());
+                }
+                if (params.has("_agentId")) {
+                    attrs.put(A2AProtocolConstants.CE_EXTENSION_TARGET_AGENT, params.get("_agentId").asText());
+                }
+            }
+
+            return EventMeshFrame.event(attrs, jsonBytes);
+        } catch (Exception e) {
+            throw new ProtocolHandleException("A2A → EventMeshFrame conversion failed", e);
+        }
+    }
+
+    @Override
+    public ProtocolTransportObject fromFrame(EventMeshFrame frame) throws ProtocolHandleException {
+        // A2A egress: the frame's data IS the original A2A JSON-RPC bytes (preserved verbatim on
+        // ingress). Return as-is — the A2A client speaks JSON-RPC.
+        return new ByteTransport(frame.data());
+    }
+
+    @Override
+    public String getProtocolType() {
+        return PROTOCOL_TYPE;
+    }
+}
diff --git a/eventmesh-runtime/conf/a2a-protocol-config.yaml b/eventmesh-protocol-plugin/eventmesh-protocol-a2a/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.protocol.api.FrameAdaptor
similarity index 58%
rename from eventmesh-runtime/conf/a2a-protocol-config.yaml
rename to eventmesh-protocol-plugin/eventmesh-protocol-a2a/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.protocol.api.FrameAdaptor
index 4ce81f502..32de77f5f 100644
--- a/eventmesh-runtime/conf/a2a-protocol-config.yaml
+++ b/eventmesh-protocol-plugin/eventmesh-protocol-a2a/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.protocol.api.FrameAdaptor
@@ -13,23 +13,4 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-eventMesh:
-  protocol:
-    plugin:
-      a2a:
-        # Whether the A2A (Agent-to-Agent) protocol adaptor is enabled
-        enabled: true
-        
-        # Protocol version
-        version: "2.0"
-        
-        # Features configuration
-        features:
-          # Enable delegation to existing CloudEvents/HTTP protocols
-          delegation: true
-          
-          # Enable MCP (Model Context Protocol) JSON-RPC 2.0 support
-          mcp-support: true
-          
-          # Enable batch processing support
-          batch-processing: true
+a2a=org.apache.eventmesh.protocol.a2a.A2AFrameAdaptor
diff --git a/eventmesh-protocol-plugin/eventmesh-protocol-api/src/main/java/org/apache/eventmesh/protocol/api/FrameAdaptor.java b/eventmesh-protocol-plugin/eventmesh-protocol-api/src/main/java/org/apache/eventmesh/protocol/api/FrameAdaptor.java
new file mode 100644
index 000000000..d00f58835
--- /dev/null
+++ b/eventmesh-protocol-plugin/eventmesh-protocol-api/src/main/java/org/apache/eventmesh/protocol/api/FrameAdaptor.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.protocol.api;
+
+import org.apache.eventmesh.common.protocol.ProtocolTransportObject;
+import org.apache.eventmesh.common.wire.EventMeshFrame;
+import org.apache.eventmesh.protocol.api.exception.ProtocolHandleException;
+import org.apache.eventmesh.spi.EventMeshExtensionType;
+import org.apache.eventmesh.spi.EventMeshSPI;
+
+/**
+ * SPI for converting between an <b>external protocol</b> (CloudEvents JSON, MeshMessage TCP Package,
+ * gRPC, …) and EventMesh's internal {@link EventMeshFrame}. Each protocol plugin implements this so
+ * the runtime calls a single SPI boundary — it never hardcodes {@code fromCloudEvent} /
+ * {@code MeshMessageFrameCodec} directly.
+ *
+ * <p>This is the protocol-layer counterpart to the internal {@code WireCodec}: {@code WireCodec}
+ * shapes the <i>internal MQ bytes</i> (EventMeshFrame ↔ byte[]); {@code FrameAdaptor} shapes the
+ * <i>external protocol representation</i> (EventMeshFrame ↔ the client's wire format). The two
+ * never overlap: CloudEvent and MeshMessage are peer external protocols, neither converts to the
+ * other — both convert directly to/from EventMeshFrame.</p>
+ *
+ * <h3>Ingress (client → runtime)</h3>
+ * <pre>{@code
+ *   ProtocolTransportObject proto = ...; // what arrived on the wire (Package, byte[], HttpCommand)
+ *   EventMeshFrame frame = adaptor.toFrame(proto);
+ *   ingress.publish(topic, frame);
+ * }</pre>
+ *
+ * <h3>Egress (runtime → client)</h3>
+ * <pre>{@code
+ *   EventMeshFrame frame = ...; // what the dispatcher is delivering
+ *   ProtocolTransportObject proto = adaptor.fromFrame(frame);
+ *   // write proto to the client's transport
+ * }</pre>
+ *
+ * @since 1.11.0
+ */
+@EventMeshSPI(eventMeshExtensionType = EventMeshExtensionType.PROTOCOL)
+public interface FrameAdaptor {
+
+    /**
+     * Convert an external-protocol transport object to the internal EventMeshFrame (ingress).
+     *
+     * @param proto the wire-format object ({@link org.apache.eventmesh.common.protocol.tcp.Package}
+     *              for MeshMessage TCP, {@code byte[]} / {@link org.apache.eventmesh.common.protocol.http.HttpCommand}
+     *              for CloudEvents HTTP, etc.)
+     * @return the internal EventMeshFrame
+     */
+    EventMeshFrame toFrame(ProtocolTransportObject proto) throws ProtocolHandleException;
+
+    /**
+     * Convert an internal EventMeshFrame back to the external-protocol transport object (egress).
+     *
+     * @param frame the internal EventMeshFrame being delivered to a client
+     * @return the wire-format object to write to the client's transport
+     */
+    ProtocolTransportObject fromFrame(EventMeshFrame frame) throws ProtocolHandleException;
+
+    /**
+     * The protocol type this adaptor handles (e.g. {@code "cloudevents"}, {@code "meshmessage"}).
+     * Used by the runtime to select the right adaptor per client connection.
+     */
+    String getProtocolType();
+
+    /**
+     * Ingress convenience: convert without throwing — wraps {@link ProtocolHandleException} in a
+     * {@link RuntimeException} so call sites that can't propagate checked exceptions stay terse.
+     */
+    default EventMeshFrame toFrameSilent(ProtocolTransportObject proto) {
+        try {
+            return toFrame(proto);
+        } catch (ProtocolHandleException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Egress convenience: convert without throwing — wraps {@link ProtocolHandleException} in a
+     * {@link RuntimeException}.
+     */
+    default ProtocolTransportObject fromFrameSilent(EventMeshFrame frame) {
+        try {
+            return fromFrame(frame);
+        } catch (ProtocolHandleException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/eventmesh-protocol-plugin/eventmesh-protocol-api/src/main/java/org/apache/eventmesh/protocol/api/FrameAdaptors.java b/eventmesh-protocol-plugin/eventmesh-protocol-api/src/main/java/org/apache/eventmesh/protocol/api/FrameAdaptors.java
new file mode 100644
index 000000000..8d6b14515
--- /dev/null
+++ b/eventmesh-protocol-plugin/eventmesh-protocol-api/src/main/java/org/apache/eventmesh/protocol/api/FrameAdaptors.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.protocol.api;
+
+import org.apache.eventmesh.common.protocol.ByteTransport;
+import org.apache.eventmesh.common.protocol.ProtocolTransportObject;
+import org.apache.eventmesh.common.wire.EventMeshFrame;
+import org.apache.eventmesh.spi.EventMeshExtensionFactory;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Entry point for protocol-adaptor SPI loading. Caches {@link FrameAdaptor} instances by protocol
+ * name ({@code "cloudevents"}, {@code "meshmessage"}, …); falls back to the CloudEvents adaptor
+ * when a named adaptor can't be loaded. Callers use {@link #get(String)} instead of touching
+ * {@code EventMeshFrame.fromCloudEvent} / {@code MeshMessageFrameCodec} directly, so all protocol
+ * conversion logic lives in the adaptors.
+ */
+@Slf4j
+public final class FrameAdaptors {
+
+    /** Default protocol name when the caller doesn't specify (HTTP CloudEvents path). */
+    public static final String DEFAULT = "cloudevents";
+
+    private static final ConcurrentHashMap<String, FrameAdaptor> CACHE = new ConcurrentHashMap<>();
+
+    private FrameAdaptors() {
+    }
+
+    /**
+     * @return the {@link FrameAdaptor} for {@code protocolName} (cached; CloudEvents fallback).
+     */
+    public static FrameAdaptor get(String protocolName) {
+        String name = (protocolName == null || protocolName.isEmpty()) ? DEFAULT : protocolName;
+        return CACHE.computeIfAbsent(name, FrameAdaptors::load);
+    }
+
+    /** Convenience: the default (CloudEvents) adaptor. */
+    public static FrameAdaptor cloudevents() {
+        return get(DEFAULT);
+    }
+
+    private static FrameAdaptor load(String name) {
+        try {
+            FrameAdaptor adaptor = EventMeshExtensionFactory.getExtension(FrameAdaptor.class, name);
+            if (adaptor != null) {
+                return adaptor;
+            }
+        } catch (Throwable t) {
+            log.warn("FrameAdaptor '{}' load failed ({}); trying default", name, t.toString());
+        }
+        // If the named adaptor isn't found, try the default (cloudevents) — it's registered in the
+        // separate eventmesh-protocol-cloudevents plugin on the classpath.
+        if (!DEFAULT.equals(name)) {
+            try {
+                FrameAdaptor fallback = EventMeshExtensionFactory.getExtension(FrameAdaptor.class, DEFAULT);
+                if (fallback != null) {
+                    log.warn("FrameAdaptor '{}' not found; using default ({})", name, DEFAULT);
+                    return fallback;
+                }
+            } catch (Throwable ignored) {
+                // best-effort
+            }
+        }
+        throw new IllegalStateException("FrameAdaptor '" + name + "' not found (is the protocol plugin on the classpath?)");
+    }
+
+    // ---- convenience wrappers for the common CloudEvents path ----
+
+    /** Ingress: CloudEvents-JSON bytes → EventMeshFrame (via the default CloudEvents adaptor). */
+    public static EventMeshFrame toFrame(byte[] cloudEventsJson) {
+        return cloudevents().toFrameSilent(new ByteTransport(cloudEventsJson));
+    }
+
+    /** Egress: EventMeshFrame → CloudEvents-JSON bytes (via the default CloudEvents adaptor). */
+    public static byte[] toCloudEventsJson(EventMeshFrame frame) {
+        ProtocolTransportObject proto = cloudevents().fromFrameSilent(frame);
+        if (proto instanceof ByteTransport) {
+            return ((ByteTransport) proto).getBytes();
+        }
+        return new byte[0];
+    }
+}
diff --git a/eventmesh-protocol-plugin/eventmesh-protocol-cloudevents/build.gradle b/eventmesh-protocol-plugin/eventmesh-protocol-cloudevents/build.gradle
new file mode 100644
index 000000000..dc81e8f4c
--- /dev/null
+++ b/eventmesh-protocol-plugin/eventmesh-protocol-cloudevents/build.gradle
@@ -0,0 +1,7 @@
+// CloudEvents protocol plugin — CloudEvents-JSON ↔ EventMeshFrame FrameAdaptor.
+// The default external protocol for HTTP/SSE/WS clients speaking CloudEvents 1.0.
+dependencies {
+    api project(":eventmesh-protocol-plugin:eventmesh-protocol-api")
+    implementation "io.cloudevents:cloudevents-core"
+    implementation "io.cloudevents:cloudevents-json-jackson"
+}
diff --git a/eventmesh-protocol-plugin/eventmesh-protocol-cloudevents/src/main/java/org/apache/eventmesh/protocol/cloudevents/CloudEventsFrameAdaptor.java b/eventmesh-protocol-plugin/eventmesh-protocol-cloudevents/src/main/java/org/apache/eventmesh/protocol/cloudevents/CloudEventsFrameAdaptor.java
new file mode 100644
index 000000000..1414856f7
--- /dev/null
+++ b/eventmesh-protocol-plugin/eventmesh-protocol-cloudevents/src/main/java/org/apache/eventmesh/protocol/cloudevents/CloudEventsFrameAdaptor.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.protocol.cloudevents;
+
+import org.apache.eventmesh.common.protocol.ByteTransport;
+import org.apache.eventmesh.common.protocol.ProtocolTransportObject;
+import org.apache.eventmesh.common.wire.EventMeshFrame;
+import org.apache.eventmesh.protocol.api.FrameAdaptor;
+import org.apache.eventmesh.protocol.api.exception.ProtocolHandleException;
+import org.apache.eventmesh.spi.EventMeshExtensionType;
+import org.apache.eventmesh.spi.EventMeshSPI;
+
+import io.cloudevents.CloudEvent;
+import io.cloudevents.core.provider.EventFormatProvider;
+import io.cloudevents.jackson.JsonFormat;
+
+/**
+ * {@link FrameAdaptor} for the CloudEvents 1.0 protocol (structured-JSON on the wire). Converts
+ * directly between CloudEvents-JSON bytes and {@link EventMeshFrame}.
+ *
+ * <p>Registered under the name {@code cloudevents}; the default external protocol for HTTP/SSE/WS
+ * clients. Load via {@code FrameAdaptors.get("cloudevents")}.</p>
+ */
+@EventMeshSPI(eventMeshExtensionType = EventMeshExtensionType.PROTOCOL)
+public class CloudEventsFrameAdaptor implements FrameAdaptor {
+
+    public static final String PROTOCOL_TYPE = "cloudevents";
+
+    private static io.cloudevents.core.format.EventFormat jsonFormat() {
+        return EventFormatProvider.getInstance().resolveFormat(JsonFormat.CONTENT_TYPE);
+    }
+
+    @Override
+    public EventMeshFrame toFrame(ProtocolTransportObject proto) throws ProtocolHandleException {
+        if (!(proto instanceof ByteTransport)) {
+            throw new ProtocolHandleException("expected ByteTransport (CloudEvents JSON bytes), got "
+                + (proto == null ? "null" : proto.getClass()));
+        }
+        byte[] ceJsonBytes = ((ByteTransport) proto).getBytes();
+        if (ceJsonBytes == null || ceJsonBytes.length == 0) {
+            throw new ProtocolHandleException("empty CloudEvents body");
+        }
+        try {
+            CloudEvent ce = jsonFormat().deserialize(ceJsonBytes);
+            if (ce == null) {
+                throw new ProtocolHandleException("failed to deserialize CloudEvents JSON");
+            }
+            return EventMeshFrame.fromCloudEvent(ce);
+        } catch (ProtocolHandleException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ProtocolHandleException("CloudEvents → EventMeshFrame conversion failed", e);
+        }
+    }
+
+    @Override
+    public ProtocolTransportObject fromFrame(EventMeshFrame frame) throws ProtocolHandleException {
+        try {
+            CloudEvent ce = frame.toCloudEvent();
+            byte[] json = jsonFormat().serialize(ce);
+            return new ByteTransport(json);
+        } catch (Exception e) {
+            throw new ProtocolHandleException("EventMeshFrame → CloudEvents conversion failed", e);
+        }
+    }
+
+    @Override
+    public String getProtocolType() {
+        return PROTOCOL_TYPE;
+    }
+}
diff --git a/gradle.properties b/eventmesh-protocol-plugin/eventmesh-protocol-cloudevents/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.protocol.api.FrameAdaptor
similarity index 62%
copy from gradle.properties
copy to eventmesh-protocol-plugin/eventmesh-protocol-cloudevents/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.protocol.api.FrameAdaptor
index d752fa384..38c007f09 100644
--- a/gradle.properties
+++ b/eventmesh-protocol-plugin/eventmesh-protocol-cloudevents/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.protocol.api.FrameAdaptor
@@ -1,4 +1,3 @@
-#
 # Licensed to the Apache Software Foundation (ASF) under one or more
 # contributor license agreements.  See the NOTICE file distributed with
 # this work for additional information regarding copyright ownership.
@@ -13,24 +12,5 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
-
-jdk=1.8
-snapshot=false
-group=org.apache.eventmesh
-version=1.12.0-release
-#last eight bits of public key
-signing.keyId=
-#passphrase for key pairs
-signing.password=
-#path of exporting private key
-signing.secretKeyRingFile=
-#apache id
-apacheUserName=
-#apache password
-apachePassWord=
-signEnabled=false
 
-org.gradle.warning.mode=none
-org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
-org.gradle.caching=true
+cloudevents=org.apache.eventmesh.protocol.cloudevents.CloudEventsFrameAdaptor
diff --git a/eventmesh-protocol-plugin/eventmesh-protocol-meshmessage/src/main/java/org/apache/eventmesh/protocol/meshmessage/MeshMessageFrameAdaptor.java b/eventmesh-protocol-plugin/eventmesh-protocol-meshmessage/src/main/java/org/apache/eventmesh/protocol/meshmessage/MeshMessageFrameAdaptor.java
new file mode 100644
index 000000000..d529a37e4
--- /dev/null
+++ b/eventmesh-protocol-plugin/eventmesh-protocol-meshmessage/src/main/java/org/apache/eventmesh/protocol/meshmessage/MeshMessageFrameAdaptor.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.eventmesh.protocol.meshmessage;
+
+import org.apache.eventmesh.common.Constants;
+import org.apache.eventmesh.common.protocol.ProtocolTransportObject;
+import org.apache.eventmesh.common.protocol.tcp.EventMeshMessage;
+import org.apache.eventmesh.common.protocol.tcp.Header;
+import org.apache.eventmesh.common.protocol.tcp.Package;
+import org.apache.eventmesh.common.utils.JsonUtils;
+import org.apache.eventmesh.common.wire.EventMeshFrame;
+import org.apache.eventmesh.protocol.api.FrameAdaptor;
+import org.apache.eventmesh.protocol.api.exception.ProtocolHandleException;
+
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * {@link FrameAdaptor} for the legacy MeshMessage TCP protocol — converts a {@link Package}
+ * (carrying an {@link EventMeshMessage}) directly to/from an {@link EventMeshFrame}, without going
+ * through CloudEvent. This replaces the old {@code MeshMessageProtocolAdaptor.toCloudEvent} path
+ * that used CloudEvent as an intermediary.
+ *
+ * <p>Registered under the name {@code meshmessage}; load via
+ * {@code EventMeshExtensionFactory.getExtension(FrameAdaptor.class, "meshmessage")}.</p>
+ */
+public class MeshMessageFrameAdaptor implements FrameAdaptor {
+
+    public static final String PROTOCOL_TYPE = "meshmessage";
+    static final String ATTR_ORIGIN_PROTOCOL = "emorigin";
+
+    @Override
+    public EventMeshFrame toFrame(ProtocolTransportObject proto) throws ProtocolHandleException {
+        if (!(proto instanceof Package)) {
+            throw new ProtocolHandleException("expected Package, got " + (proto == null ? "null" : proto.getClass()));
+        }
+        Package pkg = (Package) proto;
+        Object body = pkg.getBody();
+        EventMeshMessage message;
+        if (body instanceof EventMeshMessage) {
+            message = (EventMeshMessage) body;
+        } else if (body instanceof String) {
+            message = JsonUtils.parseObject((String) body, EventMeshMessage.class);
+        } else {
+            throw new ProtocolHandleException("Package body is not an EventMeshMessage: " + (body == null ? "null" : body.getClass()));
+        }
+        if (message == null) {
+            throw new ProtocolHandleException("failed to parse EventMeshMessage from Package body");
+        }
+
+        Map<String, String> attrs = new LinkedHashMap<>();
+        attrs.put("specversion", "1.0");
+        attrs.put("type", "eventmeshmessage");
+        attrs.put("source", "/");
+        attrs.put(ATTR_ORIGIN_PROTOCOL, PROTOCOL_TYPE);
+        Header header = pkg.getHeader();
+        if (header != null && header.getSeq() != null) {
+            attrs.put("id", header.getSeq());
+        }
+        if (message.getTopic() != null) {
+            attrs.put("subject", message.getTopic());
+        }
+        if (message.getProperties() != null) {
+            attrs.putAll(message.getProperties());
+        }
+        if (message.getHeaders() != null) {
+            attrs.putAll(message.getHeaders());
+        }
+        byte[] data = message.getBody() == null ? new byte[0]
+            : message.getBody().getBytes(Constants.DEFAULT_CHARSET);
+        return EventMeshFrame.event(attrs, data);
+    }
+
+    @Override
+    public ProtocolTransportObject fromFrame(EventMeshFrame frame) throws ProtocolHandleException {
+        Map<String, String> attrs = frame.attributes();
+        EventMeshMessage message = new EventMeshMessage();
+        message.setTopic(attrs.get("subject"));
+        message.setBody(frame.data() == null ? "" : new String(frame.data(), StandardCharsets.UTF_8));
+
+        Map<String, String> props = new LinkedHashMap<>();
+        for (Map.Entry<String, String> e : attrs.entrySet()) {
+            String k = e.getKey();
+            if ("type".equals(k) || ATTR_ORIGIN_PROTOCOL.equals(k) || "id".equals(k) || "subject".equals(k)
+                || "source".equals(k) || "specversion".equals(k) || "time".equals(k) || "dataschema".equals(k)
+                || "datacontenttype".equals(k)) {
+                continue;
+            }
+            props.put(k, e.getValue());
+        }
+        message.setProperties(props);
+
+        Package pkg = new Package();
+        pkg.setBody(message);
+        return pkg;
+    }
+
+    @Override
+    public String getProtocolType() {
+        return PROTOCOL_TYPE;
+    }
+}
diff --git a/gradle.properties b/eventmesh-protocol-plugin/eventmesh-protocol-meshmessage/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.protocol.api.FrameAdaptor
similarity index 62%
copy from gradle.properties
copy to eventmesh-protocol-plugin/eventmesh-protocol-meshmessage/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.protocol.api.FrameAdaptor
index d752fa384..0a2191628 100644
--- a/gradle.properties
+++ b/eventmesh-protocol-plugin/eventmesh-protocol-meshmessage/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.protocol.api.FrameAdaptor
@@ -1,4 +1,3 @@
-#
 # Licensed to the Apache Software Foundation (ASF) under one or more
 # contributor license agreements.  See the NOTICE file distributed with
 # this work for additional information regarding copyright ownership.
@@ -13,24 +12,5 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
-
-jdk=1.8
-snapshot=false
-group=org.apache.eventmesh
-version=1.12.0-release
-#last eight bits of public key
-signing.keyId=
-#passphrase for key pairs
-signing.password=
-#path of exporting private key
-signing.secretKeyRingFile=
-#apache id
-apacheUserName=
-#apache password
-apachePassWord=
-signEnabled=false
 
-org.gradle.warning.mode=none
-org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
-org.gradle.caching=true
+meshmessage=org.apache.eventmesh.protocol.meshmessage.MeshMessageFrameAdaptor
diff --git a/eventmesh-runtime/build.gradle b/eventmesh-runtime/build.gradle
index ab9b525a9..4b8e0d49e 100644
--- a/eventmesh-runtime/build.gradle
+++ b/eventmesh-runtime/build.gradle
@@ -46,6 +46,7 @@ dependencies {
 
     // 协议适配 (TCP/HTTP 桥 + A2A Agent 通信)
     implementation project(':eventmesh-protocol-plugin:eventmesh-protocol-api')
+    implementation project(':eventmesh-protocol-plugin:eventmesh-protocol-cloudevents')
     implementation project(':eventmesh-protocol-plugin:eventmesh-protocol-meshmessage')
     implementation project(':eventmesh-protocol-plugin:eventmesh-protocol-a2a')
 
@@ -62,10 +63,12 @@ dependencies {
     testImplementation project(':eventmesh-storage-plugin:eventmesh-storage-kafka')
     // Topic creation in RealBrokerIntegrationTest (broker may have autoCreateTopicEnable=false).
     testImplementation 'org.apache.rocketmq:rocketmq-tools:4.9.5'
-    // A2AClientServerIntegrationTest drives the A2A Gateway over HTTP.
-    testImplementation 'org.apache.httpcomponents:httpclient:4.5.14'
     // ClientBrokerIntegrationTest drives the runtime via the real CloudEventsClient SDK over HTTP.
     testImplementation project(':eventmesh-sdks:eventmesh-sdk-java')
+    // LiteStreamCallIntegrationTest boots the agent (StreamingAgent + OpenAiLlmClient) in-process.
+    testImplementation project(':eventmesh-agent')
+    // StreamingCallDemoE2ETest drives the demo's main() against a real booted runtime + agent.
+    testImplementation project(':eventmesh-examples')
 }
 
 // Resolve the lz4-java capability conflict when kafka-clients and rocketmq are both on the test
@@ -77,16 +80,162 @@ configurations.all {
     exclude group: 'org.lz4', module: 'lz4-java'
 }
 
+// Dev runner: boots EventMeshApplication with the rocketmq5 storage plugin on the classpath (so the
+// SPI loads it without a dist/plugin dir). Forwards eventmesh*/eventMesh*/llm.*/it.* -D flags.
+task runRuntime(type: JavaExec) {
+    group = 'application'
+    mainClass = 'org.apache.eventmesh.runtime.boot.EventMeshApplication'
+    classpath = sourceSets.main.runtimeClasspath
+    classpath += project(':eventmesh-storage-plugin:eventmesh-storage-rocketmq5').sourceSets.main.output
+    classpath += project(':eventmesh-storage-plugin:eventmesh-storage-rocketmq5').configurations.runtimeClasspath
+    classpath += files('conf') // so conf/log4j2.xml is auto-discovered (INFO console)
+    systemProperties = System.properties.findAll { k, v ->
+        k.startsWith('eventmesh') || k.startsWith('eventMesh') || k.startsWith('llm.')
+            || k.startsWith('it.') || k.startsWith('log4j2')
+    }
+    jvmArgs('-XX:MaxDirectMemorySize=512m', '-Xmx1024m', '-Dio.netty.noPreferDirect=true')
+}
+
+// Broker integration tests need real brokers (rocketmq4/5/kafka/nacos) and are flaky from a dev
+// machine, so they run ON-DEMAND via `gradle :eventmesh-runtime:e2e-test` - NOT in the default
+// `test` task (which stays fast + green: unit tests + in-memory ITs only). They auto-detect infra
+// via E2EConfig (assumeTrue) - no -D needed.
+//
+// rocketmq 4.9.5 and 5.5.0 cannot coexist on one classpath (5.x moved common.protocol.* ->
+// remoting.protocol.*), so the 4.9 broker ITs (which reference 4.9.5 classes directly) run in a
+// separate task with storage-rocketmq5 excluded; the 5.x/kafka/nacos ITs run in another task with
+// the full classpath. `e2e-test` is a lifecycle task that runs both (two JVMs).
+final List<String> BROKER_IT_5X_CLASSES = [
+    'org.apache.eventmesh.runtime.it.LiteStreamCallIntegrationTest',
+    'org.apache.eventmesh.runtime.it.StreamingSdkE2ETest',
+    'org.apache.eventmesh.runtime.it.RocketMQ5BrokerIntegrationTest',
+    'org.apache.eventmesh.runtime.it.RocketMQ5LiteHttpIntegrationTest',
+    'org.apache.eventmesh.runtime.it.KafkaClientE2EIntegrationTest',
+    'org.apache.eventmesh.runtime.it.NacosClusterForwardIntegrationTest'
+]
+final List<String> BROKER_IT_4_CLASSES = [
+    'org.apache.eventmesh.runtime.it.RealBrokerIntegrationTest',
+    'org.apache.eventmesh.runtime.it.AckTimeoutRedeliveryIntegrationTest',
+    'org.apache.eventmesh.runtime.it.BrokerListTest',
+    'org.apache.eventmesh.runtime.it.ClientBrokerIntegrationTest',
+    'org.apache.eventmesh.runtime.it.LoadThroughputIntegrationTest',
+    'org.apache.eventmesh.runtime.it.LegacyTcpClusterBrokerIntegrationTest',
+    'org.apache.eventmesh.runtime.it.MultiInstanceRocketMqIntegrationTest'
+]
+
 test {
-    // Forward -Dit.* (set on the gradle CLI) to the test JVM so RealBrokerIntegrationTest can be
-    // enabled + pointed at a broker. Only forwarded when actually set, so the test's own defaults
-    // (it.topic=em-it-orders, it.namesrv=localhost:9092) still apply otherwise.
-    // Enable + run: -Dit.storage=rocketmq -Dit.namesrv=host:9876 [-Dit.topic=...]
     jvmArgs('-XX:MaxDirectMemorySize=512m', '-Xmx1024m', '-Dio.netty.noPreferDirect=true')
-    ['it.storage', 'it.topic', 'it.namesrv', 'it.nacos', 'it.storage5', 'it.namesrv5'].each { k ->
+    ['it.storage', 'it.topic', 'it.namesrv', 'it.nacos', 'it.storage5', 'it.namesrv5',
+     'llm.base.url', 'llm.api.key', 'llm.model'].each { k ->
         def v = System.getProperty(k)
         if (v != null) {
             systemProperty k, v
         }
     }
+    // Exclude all broker ITs - they run on-demand via `e2e-test`.
+    (BROKER_IT_5X_CLASSES + BROKER_IT_4_CLASSES).each { cls -> filter { excludeTestsMatching cls } }
+}
+
+// --- isolated classpath for the 4.9 broker ITs (excludes storage-rocketmq5 so 4.9.5 wins) ---
+configurations {
+    rocketmq4TestRuntimeClasspath {
+        canBeResolved = true
+        canBeConsumed = false
+        extendsFrom configurations.implementation, configurations.runtimeOnly,
+                     configurations.testImplementation, configurations.testRuntimeOnly
+    }
+}
+configurations.rocketmq4TestRuntimeClasspath.exclude(
+    group: 'org.apache.eventmesh', module: 'eventmesh-storage-rocketmq5')
+
+// 5.x/kafka/nacos broker ITs: full test classpath (storage-rocketmq5 present -> 5.5.0 wins).
+task e2eTest5x(type: Test) {
+    testClassesDirs = sourceSets.test.output.classesDirs
+    classpath = sourceSets.test.runtimeClasspath
+    useJUnitPlatform()
+    BROKER_IT_5X_CLASSES.each { cls -> filter { includeTestsMatching cls } }
+    jvmArgs('-XX:MaxDirectMemorySize=512m', '-Xmx1024m', '-Dio.netty.noPreferDirect=true')
+    ['it.storage', 'it.topic', 'it.namesrv', 'it.nacos', 'it.storage5', 'it.namesrv5',
+     'it.kafka.bootstrap', 'it.kafka.user', 'it.kafka.password',
+     'llm.base.url', 'llm.api.key', 'llm.model'].each { k ->
+        def v = System.getProperty(k)
+        if (v != null) { systemProperty k, v }
+    }
+}
+
+// 4.9 broker ITs: isolated classpath (storage-rocketmq5 excluded -> 4.9.5 wins).
+task e2eTest4(type: Test) {
+    testClassesDirs = sourceSets.test.output.classesDirs
+    classpath = configurations.rocketmq4TestRuntimeClasspath + sourceSets.main.output + sourceSets.test.output
+    useJUnitPlatform()
+    BROKER_IT_4_CLASSES.each { cls -> filter { includeTestsMatching cls } }
+    jvmArgs('-XX:MaxDirectMemorySize=512m', '-Xmx1024m', '-Dio.netty.noPreferDirect=true')
+    ['it.storage', 'it.topic', 'it.namesrv', 'it.nacos'].each { k ->
+        def v = System.getProperty(k)
+        if (v != null) { systemProperty k, v }
+    }
+}
+
+// On-demand: run ALL broker ITs (5.x + 4.9, in two separate JVMs).
+// `gradle :eventmesh-runtime:e2e-test`
+task('e2e-test') {
+    group = 'verification'
+    description = 'Runs all broker integration tests (auto-detect real brokers via E2EConfig).'
+    dependsOn 'e2eTest5x', 'e2eTest4'
+}
+
+// One-command demo runner: boots an in-process EventMeshApplication + StreamingAgent (mock LLM)
+// against a real RocketMQ 5.x broker, then drives the StreamingCallDemo client (eventmesh-examples).
+// Override the broker: -Dit.namesrv5=host:9876 (default = E2EConfig.ROCKETMQ5_NAMESRV).
+task runDemo(type: JavaExec) {
+    group = 'application'
+    mainClass = 'org.apache.eventmesh.runtime.it.StreamingDemoRunner'
+    classpath = sourceSets.test.runtimeClasspath
+    classpath += project(':eventmesh-storage-plugin:eventmesh-storage-rocketmq5').sourceSets.main.output
+    classpath += project(':eventmesh-examples').sourceSets.main.output
+    systemProperties = System.properties.findAll { k, v ->
+        k.startsWith('it.') || k.startsWith('eventmesh') || k.startsWith('eventMesh') || k.startsWith('llm.')
+    }
+    jvmArgs('-XX:MaxDirectMemorySize=512m', '-Xmx1024m', '-Dio.netty.noPreferDirect=true')
+}
+
+// --- standalone launchers for the streaming demo (real-LLM, multi-process deployment) ---
+// Start in 3 separate terminals: startRuntime → startAgent (×N) → demo client.
+
+// Terminal 1: the EventMesh Runtime (session layer, dual channel mode). Blocks.
+task startRuntime(type: JavaExec) {
+    group = 'application'
+    mainClass = 'org.apache.eventmesh.runtime.it.RuntimeLauncher'
+    classpath = sourceSets.test.runtimeClasspath
+    classpath += project(':eventmesh-storage-plugin:eventmesh-storage-rocketmq5').sourceSets.main.output
+    systemProperties = System.properties.findAll { k, v ->
+        k.startsWith('it.') || k.startsWith('eventmesh') || k.startsWith('eventMesh') || k.startsWith('em.')
+    }
+    jvmArgs('-XX:MaxDirectMemorySize=512m', '-Xmx1024m', '-Dio.netty.noPreferDirect=true')
+}
+
+// Terminal 2..N: one streaming Agent per invocation, connected to the real LLM gateway.
+//   gradle :eventmesh-runtime:startAgent --args="agent1" -Dllm.api.key=sk-...
+task startAgent(type: JavaExec) {
+    group = 'application'
+    mainClass = 'org.apache.eventmesh.runtime.it.AgentLauncher'
+    classpath = sourceSets.test.runtimeClasspath
+    classpath += project(':eventmesh-storage-plugin:eventmesh-storage-rocketmq5').sourceSets.main.output
+    systemProperties = System.properties.findAll { k, v ->
+        k.startsWith('it.') || k.startsWith('eventmesh') || k.startsWith('eventMesh') || k.startsWith('llm.') || k.startsWith('em.')
+    }
+    jvmArgs('-XX:MaxDirectMemorySize=512m', '-Xmx1024m', '-Dio.netty.noPreferDirect=true')
+}
+
+// Demo client (pure HTTP). Requires startRuntime + at least one startAgent already running.
+//   gradle :eventmesh-runtime:runDemoClient
+task runDemoClient(type: JavaExec) {
+    group = 'application'
+    mainClass = 'org.apache.eventmesh.cloudevents.demo.stream.StreamingCallDemo'
+    classpath = sourceSets.test.runtimeClasspath
+    classpath += project(':eventmesh-examples').sourceSets.main.output
+    systemProperties = System.properties.findAll { k, v ->
+        k.startsWith('it.') || k.startsWith('eventmesh') || k.startsWith('eventMesh') || k.startsWith('llm.') || k.startsWith('em.')
+    }
+    jvmArgs('-XX:MaxDirectMemorySize=512m', '-Xmx1024m', '-Dio.netty.noPreferDirect=true')
 }
diff --git a/eventmesh-runtime/conf/eventmesh.properties b/eventmesh-runtime/conf/eventmesh.properties
index cdcd34e45..19e3a8522 100644
--- a/eventmesh-runtime/conf/eventmesh.properties
+++ b/eventmesh-runtime/conf/eventmesh.properties
@@ -19,6 +19,10 @@
 # ===== RocketMQ 5.x (EVENTMESH_STORAGE_TYPE=rocketmq5) =====
 #eventMesh.server.rocketmq5.namesrvAddr=127.0.0.1:9876
 #eventMesh.server.rocketmq5.cluster=DefaultCluster
+# Lite-topic pull offset checkpoint interval (ms). Offsets are persisted to disk periodically so a
+# JVM crash replays at most this interval's worth of messages. Shutdown always persists regardless.
+# Set to 0 or negative to disable periodic checkpointing (only persist on shutdown).
+#eventmesh.rocketmq5.lite.checkpoint.interval.ms=5000
 
 # ===== Kafka (EVENTMESH_STORAGE_TYPE=kafka) =====
 eventMesh.server.kafka.namesrvAddr=localhost:9092
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2ACardHttpHandler.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2ACardHttpHandler.java
deleted file mode 100644
index 1e9f262c8..000000000
--- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2ACardHttpHandler.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.eventmesh.runtime.a2a;
-
-import org.apache.eventmesh.protocol.a2a.AgentIdentity;
-import org.apache.eventmesh.protocol.a2a.model.AgentCard;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.Unpooled;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.codec.http.DefaultFullHttpResponse;
-import io.netty.handler.codec.http.FullHttpResponse;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpMethod;
-import io.netty.handler.codec.http.HttpRequest;
-import io.netty.handler.codec.http.HttpResponseStatus;
-import io.netty.handler.codec.http.HttpVersion;
-import io.netty.handler.codec.http.QueryStringDecoder;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * HTTP handler for A2A Agent Card Registry API.
- * Matches EMQX's API pattern:
- * - GET  /a2a/cards/list                           - list agent cards
- * - GET  /a2a/cards/card/{org_id}/{unit_id}/{agent_id}  - get specific card
- * - POST /a2a/cards/card/{org_id}/{unit_id}/{agent_id}  - register card
- * - DELETE /a2a/cards/card/{org_id}/{unit_id}/{agent_id} - delete card
- */
-@Slf4j
-public class A2ACardHttpHandler {
-
-    private static final ObjectMapper objectMapper = new ObjectMapper();
-    private static final String PATH_LIST = "/a2a/cards/list";
-    private static final String PATH_CARD_PREFIX = "/a2a/cards/card/";
-
-    private final A2APublishSubscribeService a2aService;
-
-    public A2ACardHttpHandler(A2APublishSubscribeService a2aService) {
-        this.a2aService = a2aService;
-    }
-
-    public FullHttpResponse handle(HttpRequest httpRequest, ChannelHandlerContext ctx) throws Exception {
-        String uri = httpRequest.uri();
-        QueryStringDecoder decoder = new QueryStringDecoder(uri);
-        String path = decoder.path();
-        HttpMethod method = httpRequest.method();
-
-        try {
-            // Handle CORS preflight
-            if (method == HttpMethod.OPTIONS) {
-                return corsResponse();
-            }
-
-            if (path.equals(PATH_LIST)) {
-                return handleList(decoder);
-            }
-
-            if (path.startsWith(PATH_CARD_PREFIX)) {
-                String[] segments = path.substring(PATH_CARD_PREFIX.length()).split("/");
-                if (segments.length != 3) {
-                    return jsonResponse(HttpResponseStatus.BAD_REQUEST,
-                        errorBody("Invalid path. Expected /a2a/cards/card/{org_id}/{unit_id}/{agent_id}"));
-                }
-
-                String orgId = segments[0];
-                String unitId = segments[1];
-                String agentId = segments[2];
-
-                if (method == HttpMethod.GET) {
-                    return handleGet(orgId, unitId, agentId);
-                } else if (method == HttpMethod.POST) {
-                    return handleRegister(orgId, unitId, agentId, httpRequest);
-                } else if (method == HttpMethod.DELETE) {
-                    return handleDelete(orgId, unitId, agentId);
-                }
-            }
-
-            return jsonResponse(HttpResponseStatus.NOT_FOUND, errorBody("Not found: " + path));
-        } catch (Exception e) {
-            log.error("A2A card handler error: {}", e.getMessage(), e);
-            return jsonResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR, errorBody(e.getMessage()));
-        }
-    }
-
-    private FullHttpResponse handleList(QueryStringDecoder decoder) throws Exception {
-        String orgId = getQueryParam(decoder, "org_id");
-        String unitId = getQueryParam(decoder, "unit_id");
-        String agentId = getQueryParam(decoder, "agent_id");
-
-        List<A2APublishSubscribeService.CardEntry> cards = a2aService.listCards(orgId, unitId, agentId);
-        String body = objectMapper.writeValueAsString(cards);
-        return jsonResponse(HttpResponseStatus.OK, body);
-    }
-
-    private FullHttpResponse handleGet(String orgId, String unitId, String agentId) throws Exception {
-        AgentIdentity identity = AgentIdentity.builder()
-            .orgId(orgId).unitId(unitId).agentId(agentId).build();
-        A2APublishSubscribeService.CardEntry card = a2aService.getCard(identity);
-
-        if (card == null) {
-            return jsonResponse(HttpResponseStatus.NOT_FOUND, errorBody("Card not found"));
-        }
-
-        String body = objectMapper.writeValueAsString(formatCardOut(card));
-        return jsonResponse(HttpResponseStatus.OK, body);
-    }
-
-    private FullHttpResponse handleRegister(String orgId, String unitId, String agentId, HttpRequest request) throws Exception {
-        // Read body from request
-        String requestBody = "";
-        if (request instanceof io.netty.handler.codec.http.FullHttpRequest) {
-            ByteBuf content = ((io.netty.handler.codec.http.FullHttpRequest) request).content();
-            requestBody = content.toString(StandardCharsets.UTF_8);
-        }
-
-        AgentCard card;
-        try {
-            card = objectMapper.readValue(requestBody, AgentCard.class);
-        } catch (Exception e) {
-            return jsonResponse(HttpResponseStatus.BAD_REQUEST, errorBody("Invalid card JSON: " + e.getMessage()));
-        }
-
-        AgentIdentity identity = AgentIdentity.builder()
-            .orgId(orgId).unitId(unitId).agentId(agentId).build();
-        A2APublishSubscribeService.RegistrationResult result = a2aService.registerCard(identity, card);
-
-        if (result.isSuccess()) {
-            return jsonResponse(HttpResponseStatus.NO_CONTENT, "");
-        } else {
-            return jsonResponse(HttpResponseStatus.BAD_REQUEST, errorBody(result.getErrorMessage()));
-        }
-    }
-
-    private FullHttpResponse handleDelete(String orgId, String unitId, String agentId) throws Exception {
-        AgentIdentity identity = AgentIdentity.builder()
-            .orgId(orgId).unitId(unitId).agentId(agentId).build();
-        boolean deleted = a2aService.deleteCard(identity);
-
-        if (deleted) {
-            return jsonResponse(HttpResponseStatus.NO_CONTENT, "");
-        } else {
-            return jsonResponse(HttpResponseStatus.NOT_FOUND, errorBody("Card not found"));
-        }
-    }
-
-    private Map<String, Object> formatCardOut(A2APublishSubscribeService.CardEntry entry) {
-        Map<String, Object> out = new LinkedHashMap<>();
-        out.put("namespace", entry.getNamespace());
-        out.put("id", entry.getId());
-        out.put("name", entry.getName());
-        out.put("version", entry.getVersion());
-        out.put("description", entry.getDescription());
-        out.put("status", entry.getStatus());
-        return out;
-    }
-
-    private String getQueryParam(QueryStringDecoder decoder, String key) {
-        List<String> values = decoder.parameters().get(key);
-        return (values != null && !values.isEmpty()) ? values.get(0) : null;
-    }
-
-    private String errorBody(String message) {
-        try {
-            return objectMapper.writeValueAsString(Collections.singletonMap("error", message));
-        } catch (Exception e) {
-            return "{\"error\":\"" + message + "\"}";
-        }
-    }
-
-    private FullHttpResponse jsonResponse(HttpResponseStatus status, String body) {
-        ByteBuf content = Unpooled.copiedBuffer(body, StandardCharsets.UTF_8);
-        DefaultFullHttpResponse response = new DefaultFullHttpResponse(
-            HttpVersion.HTTP_1_1, status, content);
-        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8");
-        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
-        setCorsHeaders(response);
-        return response;
-    }
-
-    private FullHttpResponse corsResponse() {
-        DefaultFullHttpResponse response = new DefaultFullHttpResponse(
-            HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
-        setCorsHeaders(response);
-        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
-        return response;
-    }
-
-    private void setCorsHeaders(FullHttpResponse response) {
-        response.headers().set("Access-Control-Allow-Origin", "*");
-        response.headers().set("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
-        response.headers().set("Access-Control-Allow-Headers", "Content-Type, Authorization");
-        response.headers().set("Access-Control-Max-Age", "3600");
-    }
-}
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2AGatewayHttpHandler.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2AGatewayHttpHandler.java
deleted file mode 100644
index 1fdcef901..000000000
--- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2AGatewayHttpHandler.java
+++ /dev/null
@@ -1,648 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.eventmesh.runtime.a2a;
-
-import java.nio.charset.StandardCharsets;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.Unpooled;
-import io.netty.channel.ChannelFutureListener;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.codec.http.DefaultFullHttpResponse;
-import io.netty.handler.codec.http.DefaultHttpContent;
-import io.netty.handler.codec.http.DefaultHttpResponse;
-import io.netty.handler.codec.http.FullHttpResponse;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpHeaderValues;
-import io.netty.handler.codec.http.HttpMethod;
-import io.netty.handler.codec.http.HttpRequest;
-import io.netty.handler.codec.http.HttpResponse;
-import io.netty.handler.codec.http.HttpResponseStatus;
-import io.netty.handler.codec.http.HttpVersion;
-import io.netty.handler.codec.http.LastHttpContent;
-import io.netty.handler.codec.http.QueryStringDecoder;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * HTTP handler for A2A Gateway API.
- *
- * <p>Endpoints:
- * <ul>
- *   <li>POST   /a2a/tasks                      - submit a new task (sync or async)</li>
- *   <li>GET    /a2a/tasks                       - list tasks (optional ?state=COMPLETED&amp;limit=100&amp;offset=0)</li>
- *   <li>GET    /a2a/tasks/{taskId}             - get task status</li>
- *   <li>DELETE /a2a/tasks/{taskId}             - cancel a task</li>
- *   <li>GET    /a2a/tasks/{taskId}/wait        - wait for task result (long-poll)</li>
- *   <li>GET    /a2a/tasks/{taskId}/stream      - SSE stream of task status updates</li>
- *   <li>GET    /a2a/agents                     - list registered agents</li>
- *   <li>POST   /a2a/heartbeat                  - agent heartbeat</li>
- *   <li>GET    /a2a/health                     - health check (for liveness/readiness probes)</li>
- *   <li>OPTIONS *                               - CORS preflight</li>
- * </ul>
- */
-@Slf4j
-public class A2AGatewayHttpHandler {
-
-    private static final ObjectMapper objectMapper = new ObjectMapper();
-
-    private static final String PATH_TASKS = "/a2a/tasks";
-    private static final String PATH_AGENTS = "/a2a/agents";
-    private static final String PATH_HEARTBEAT = "/a2a/heartbeat";
-    private static final String PATH_HEALTH = "/a2a/health";
-
-    private static final long DEFAULT_WAIT_TIMEOUT_MS = 30_000L;
-
-    private final A2AGatewayService gatewayService;
-    private final A2APublishSubscribeService a2aService;
-
-    public A2AGatewayHttpHandler(A2AGatewayService gatewayService,
-        A2APublishSubscribeService a2aService) {
-        this.gatewayService = gatewayService;
-        this.a2aService = a2aService;
-    }
-
-    /**
-     * Handles an HTTP request. Returns a FullHttpResponse, or null if the response was written
-     * directly to the channel (e.g. SSE streaming).
-     */
-    public FullHttpResponse handle(HttpRequest httpRequest, ChannelHandlerContext ctx) throws Exception {
-        String uri = httpRequest.uri();
-        QueryStringDecoder decoder = new QueryStringDecoder(uri);
-        String path = decoder.path();
-        HttpMethod method = httpRequest.method();
-
-        try {
-            // Handle CORS preflight
-            if (method == HttpMethod.OPTIONS) {
-                return corsResponse(HttpResponseStatus.OK);
-            }
-
-            // POST /a2a/tasks
-            if (PATH_TASKS.equals(path) && method == HttpMethod.POST) {
-                return handleSubmitTask(httpRequest, decoder);
-            }
-
-            // GET /a2a/tasks (list tasks with optional pagination)
-            if (PATH_TASKS.equals(path) && method == HttpMethod.GET) {
-                return handleListTasks(decoder);
-            }
-
-            // /a2a/tasks/{taskId} or /a2a/tasks/{taskId}/wait
-            if (path.startsWith(PATH_TASKS + "/")) {
-                String suffix = path.substring(PATH_TASKS.length() + 1);
-                String[] parts = suffix.split("/", 2);
-                String taskId = parts[0];
-
-                if (taskId.isEmpty()) {
-                    return jsonResponse(HttpResponseStatus.BAD_REQUEST, errorBody("Missing taskId"));
-                }
-
-                // GET /a2a/tasks/{taskId}
-                if (method == HttpMethod.GET && parts.length == 1) {
-                    return handleGetTask(taskId);
-                }
-
-                // DELETE /a2a/tasks/{taskId}
-                if (method == HttpMethod.DELETE && parts.length == 1) {
-                    return handleCancelTask(taskId);
-                }
-
-                // GET /a2a/tasks/{taskId}/wait
-                if (method == HttpMethod.GET && parts.length == 2 && "wait".equals(parts[1])) {
-                    return handleWaitTask(taskId, decoder);
-                }
-
-                // GET /a2a/tasks/{taskId}/stream (SSE)
-                if (method == HttpMethod.GET && parts.length == 2 && "stream".equals(parts[1])) {
-                    return handleStreamTask(taskId, ctx);
-                }
-            }
-
-            // GET /a2a/health
-            if (PATH_HEALTH.equals(path) && method == HttpMethod.GET) {
-                return handleHealth();
-            }
-
-            // GET /a2a/agents
-            if (PATH_AGENTS.equals(path) && method == HttpMethod.GET) {
-                return handleListAgents();
-            }
-
-            // POST /a2a/heartbeat
-            if (PATH_HEARTBEAT.equals(path) && method == HttpMethod.POST) {
-                return handleHeartbeat(httpRequest);
-            }
-
-            return jsonResponse(HttpResponseStatus.NOT_FOUND, errorBody("Not found: " + path));
-        } catch (Exception e) {
-            log.error("A2A gateway handler error: {}", e.getMessage(), e);
-            return jsonResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR, errorBody(e.getMessage()));
-        }
-    }
-
-    // =========================================================================
-    // POST /a2a/tasks
-    // =========================================================================
-
-    private FullHttpResponse handleSubmitTask(HttpRequest request, QueryStringDecoder decoder) throws Exception {
-        String body = readBody(request);
-        TaskRequest taskRequest;
-        try {
-            taskRequest = objectMapper.readValue(body, TaskRequest.class);
-        } catch (Exception e) {
-            return jsonResponse(HttpResponseStatus.BAD_REQUEST, errorBody("Invalid JSON: " + e.getMessage()));
-        }
-
-        if (taskRequest.getTargetAgent() == null || taskRequest.getTargetAgent().isEmpty()) {
-            return jsonResponse(HttpResponseStatus.BAD_REQUEST, errorBody("targetAgent is required"));
-        }
-        if (taskRequest.getMessage() == null) {
-            taskRequest.setMessage("");
-        }
-
-        String mode = getQueryParam(decoder, "mode");
-        String parentTaskId = taskRequest.getParentTaskId();
-
-        // Generate taskId first so we can include it in the response
-        String taskId = "task-" + java.util.UUID.randomUUID().toString().substring(0, 8);
-
-        // Submit the task with the explicit taskId
-        java.util.concurrent.CompletableFuture<A2AGatewayService.TaskResult> future =
-            gatewayService.submitTask(taskId, taskRequest.getTargetAgent(), taskRequest.getMessage(), parentTaskId);
-
-        // For sync mode, wait for result
-        if ("sync".equalsIgnoreCase(mode) || mode == null) {
-            try {
-                A2AGatewayService.TaskResult result = future.get(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-                Map<String, Object> response = new LinkedHashMap<>();
-                response.put("taskId", taskId);
-                response.put("state", result.getState().name());
-                response.put("data", result.getData());
-                if (result.getErrorMessage() != null) {
-                    response.put("error", result.getErrorMessage());
-                }
-                return jsonResponse(HttpResponseStatus.OK, objectMapper.writeValueAsString(response));
-            } catch (java.util.concurrent.TimeoutException e) {
-                Map<String, Object> response = new LinkedHashMap<>();
-                response.put("taskId", taskId);
-                response.put("state", "TIMEOUT");
-                response.put("error", "Task accepted but timed out waiting for response");
-                return jsonResponse(HttpResponseStatus.ACCEPTED, objectMapper.writeValueAsString(response));
-            } catch (java.util.concurrent.ExecutionException e) {
-                // Unwrap cause to determine correct status code
-                Throwable cause = e.getCause() != null ? e.getCause() : e;
-                if (cause instanceof IllegalArgumentException || cause instanceof IllegalStateException) {
-                    return jsonResponse(HttpResponseStatus.BAD_REQUEST,
-                        errorBody(cause.getMessage()));
-                }
-                return jsonResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR,
-                    errorBody("Task failed: " + cause.getMessage()));
-            } catch (Exception e) {
-                return jsonResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR,
-                    errorBody("Task failed: " + e.getMessage()));
-            }
-        } else {
-            // Async mode: return task ID immediately
-            Map<String, Object> response = new LinkedHashMap<>();
-            response.put("taskId", taskId);
-            response.put("status", "accepted");
-            response.put("message", "Task submitted. Use GET /a2a/tasks/" + taskId + " to check status.");
-            return jsonResponse(HttpResponseStatus.ACCEPTED, objectMapper.writeValueAsString(response));
-        }
-    }
-
-    // =========================================================================
-    // GET /a2a/tasks (list with optional pagination)
-    // =========================================================================
-
-    private FullHttpResponse handleListTasks(QueryStringDecoder decoder) throws Exception {
-        String stateFilter = getQueryParam(decoder, "state");
-        int limit = parseIntQueryParam(decoder, "limit", 100, 1, 1_000);
-        int offset = parseIntQueryParam(decoder, "offset", 0, 0, Integer.MAX_VALUE);
-
-        List<TaskRegistry.TaskEntry> allTasks = gatewayService.getTaskRegistry().listTasks();
-        List<Map<String, Object>> filteredTasks = new java.util.ArrayList<>();
-        for (TaskRegistry.TaskEntry entry : allTasks) {
-            if (stateFilter != null && !stateFilter.equalsIgnoreCase(entry.getState().name())) {
-                continue;
-            }
-            Map<String, Object> task = new LinkedHashMap<>();
-            task.put("taskId", entry.getTaskId());
-            task.put("state", entry.getState().name());
-            task.put("targetAgent", entry.getTargetAgent());
-            task.put("parentTaskId", entry.getParentTaskId());
-            task.put("createdAt", entry.getCreatedAt());
-            task.put("updatedAt", entry.getUpdatedAt());
-            filteredTasks.add(task);
-        }
-
-        int total = filteredTasks.size();
-        int fromIndex = Math.min(offset, total);
-        int toIndex = Math.min(fromIndex + limit, total);
-        List<Map<String, Object>> page = filteredTasks.subList(fromIndex, toIndex);
-
-        Map<String, Object> response = new LinkedHashMap<>();
-        response.put("total", total);
-        response.put("count", page.size());
-        response.put("limit", limit);
-        response.put("offset", offset);
-        response.put("hasMore", toIndex < total);
-        response.put("tasks", page);
-        return jsonResponse(HttpResponseStatus.OK, objectMapper.writeValueAsString(response));
-    }
-
-    // =========================================================================
-    // GET /a2a/tasks/{taskId}
-    // =========================================================================
-
-    private FullHttpResponse handleGetTask(String taskId) throws Exception {
-        TaskRegistry.TaskEntry entry = gatewayService.getTaskStatus(taskId);
-        if (entry == null) {
-            return jsonResponse(HttpResponseStatus.NOT_FOUND, errorBody("Task not found: " + taskId));
-        }
-        Map<String, Object> response = new LinkedHashMap<>();
-        response.put("taskId", entry.getTaskId());
-        response.put("state", entry.getState().name());
-        response.put("targetAgent", entry.getTargetAgent());
-        response.put("gatewayId", entry.getGatewayId());
-        response.put("parentTaskId", entry.getParentTaskId());
-        response.put("createdAt", entry.getCreatedAt());
-        response.put("updatedAt", entry.getUpdatedAt());
-        if (entry.getResult() != null) {
-            response.put("result", entry.getResult());
-        }
-        if (entry.getErrorMessage() != null) {
-            response.put("error", entry.getErrorMessage());
-        }
-        return jsonResponse(HttpResponseStatus.OK, objectMapper.writeValueAsString(response));
-    }
-
-    // =========================================================================
-    // DELETE /a2a/tasks/{taskId}
-    // =========================================================================
-
-    private FullHttpResponse handleCancelTask(String taskId) throws Exception {
-        boolean cancelled = gatewayService.cancelTask(taskId);
-        if (cancelled) {
-            Map<String, Object> response = new LinkedHashMap<>();
-            response.put("taskId", taskId);
-            response.put("state", "CANCELLED");
-            return jsonResponse(HttpResponseStatus.OK, objectMapper.writeValueAsString(response));
-        }
-        return jsonResponse(HttpResponseStatus.NOT_FOUND, errorBody("Task not found or cannot be cancelled: " + taskId));
-    }
-
-    // =========================================================================
-    // GET /a2a/tasks/{taskId}/wait
-    // =========================================================================
-
-    private FullHttpResponse handleWaitTask(String taskId, QueryStringDecoder decoder) throws Exception {
-        String timeoutStr = getQueryParam(decoder, "timeout");
-        long timeout = timeoutStr != null ? Long.parseLong(timeoutStr) : DEFAULT_WAIT_TIMEOUT_MS;
-
-        TaskRegistry.TaskEntry entry = gatewayService.getTaskStatus(taskId);
-        if (entry == null) {
-            return jsonResponse(HttpResponseStatus.NOT_FOUND, errorBody("Task not found: " + taskId));
-        }
-
-        // If already in a terminal state, return immediately
-        if (isTerminalState(entry.getState())) {
-            return handleGetTask(taskId);
-        }
-
-        // Register a status subscriber and wait
-        java.util.concurrent.CompletableFuture<TaskRegistry.TaskEntry> waitFuture = new java.util.concurrent.CompletableFuture<>();
-        A2AGatewayService.StatusSubscriber subscriber = (tid, state, data) -> {
-            if ("completed".equals(state) || "failed".equals(state) || "cancelled".equals(state)) {
-                waitFuture.complete(gatewayService.getTaskStatus(tid));
-            }
-        };
-        gatewayService.registerStatusSubscriber(taskId, subscriber);
-
-        try {
-            TaskRegistry.TaskEntry result = waitFuture.get(timeout, TimeUnit.MILLISECONDS);
-            Map<String, Object> response = new LinkedHashMap<>();
-            response.put("taskId", result.getTaskId());
-            response.put("state", result.getState().name());
-            if (result.getResult() != null) {
-                response.put("result", result.getResult());
-            }
-            if (result.getErrorMessage() != null) {
-                response.put("error", result.getErrorMessage());
-            }
-            return jsonResponse(HttpResponseStatus.OK, objectMapper.writeValueAsString(response));
-        } catch (java.util.concurrent.TimeoutException e) {
-            return jsonResponse(HttpResponseStatus.OK,
-                errorBody("Task still in progress. State: " + entry.getState()));
-        } catch (Exception e) {
-            return jsonResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR,
-                errorBody("Wait failed: " + e.getMessage()));
-        } finally {
-            gatewayService.unregisterStatusSubscriber(taskId, subscriber);
-        }
-    }
-
-    // =========================================================================
-    // GET /a2a/tasks/{taskId}/stream (SSE)
-    // =========================================================================
-
-    /**
-     * Streams task status updates via Server-Sent Events.
-     * Writes directly to the channel and returns null (no FullHttpResponse).
-     */
-    private FullHttpResponse handleStreamTask(String taskId, ChannelHandlerContext ctx) throws Exception {
-        TaskRegistry.TaskEntry entry = gatewayService.getTaskStatus(taskId);
-        if (entry == null) {
-            return jsonResponse(HttpResponseStatus.NOT_FOUND, errorBody("Task not found: " + taskId));
-        }
-
-        // Write SSE response headers
-        HttpResponse headers = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
-        headers.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/event-stream; charset=utf-8");
-        headers.headers().set(HttpHeaderNames.CACHE_CONTROL, "no-cache");
-        headers.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
-        headers.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
-        headers.headers().set("Access-Control-Allow-Origin", "*");
-        ctx.writeAndFlush(headers);
-
-        // Send initial state
-        sendSseEvent(ctx, taskId, entry.getState().name(),
-            entry.getResult() != null ? entry.getResult() : entry.getErrorMessage());
-
-        // If already terminal, close stream
-        if (isTerminalState(entry.getState())) {
-            ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
-            ctx.close();
-            return null;
-        }
-
-        // Schedule periodic heartbeat to keep connection alive through proxies/load balancers
-        java.util.concurrent.ScheduledExecutorService heartbeatExecutor = java.util.concurrent.Executors.newSingleThreadScheduledExecutor(r -> {
-            Thread t = new Thread(r, "a2a-sse-heartbeat");
-            t.setDaemon(true);
-            return t;
-        });
-        heartbeatExecutor.scheduleAtFixedRate(() -> {
-            try {
-                ByteBuf hb = Unpooled.copiedBuffer(": heartbeat\n\n", StandardCharsets.UTF_8);
-                ctx.writeAndFlush(new DefaultHttpContent(hb));
-            } catch (Exception e) {
-                // Channel may have been closed; ignore
-            }
-        }, 15, 15, TimeUnit.SECONDS);
-
-        // Register subscriber for ongoing updates
-        A2AGatewayService.StatusSubscriber subscriber = (tid, state, data) -> {
-            sendSseEvent(ctx, tid, state, data);
-            if ("completed".equals(state) || "failed".equals(state) || "cancelled".equals(state)) {
-                heartbeatExecutor.shutdownNow();
-                ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT)
-                    .addListener(ChannelFutureListener.CLOSE);
-            }
-        };
-        gatewayService.registerStatusSubscriber(taskId, subscriber);
-
-        // Remove subscriber and stop heartbeat when channel closes
-        ctx.channel().closeFuture().addListener(future -> {
-            gatewayService.unregisterStatusSubscriber(taskId, subscriber);
-            heartbeatExecutor.shutdownNow();
-        });
-
-        return null; // Response already written to channel
-    }
-
-    private void sendSseEvent(ChannelHandlerContext ctx, String taskId, String state, String data) {
-        Map<String, Object> event = new LinkedHashMap<>();
-        event.put("taskId", taskId);
-        event.put("state", state);
-        if (data != null) {
-            event.put("data", data);
-        }
-        String json;
-        try {
-            json = objectMapper.writeValueAsString(event);
-        } catch (Exception e) {
-            json = "{\"error\":\"" + e.getMessage() + "\"}";
-        }
-        String sse = "data: " + json + "\n\n";
-        ByteBuf buf = Unpooled.copiedBuffer(sse, StandardCharsets.UTF_8);
-        ctx.writeAndFlush(new DefaultHttpContent(buf));
-    }
-
-    // =========================================================================
-    // GET /a2a/health
-    // =========================================================================
-
-    private FullHttpResponse handleHealth() throws Exception {
-        Map<String, Object> response = new LinkedHashMap<>();
-        response.put("status", "UP");
-        response.put("gatewayId", gatewayService.getGatewayId());
-        response.put("namespace", gatewayService.getNamespace());
-        response.put("taskCount", gatewayService.getTaskRegistry().size());
-        response.put("agentCount", a2aService.listAllCards().size());
-        response.put("timestamp", System.currentTimeMillis());
-        return jsonResponse(HttpResponseStatus.OK, objectMapper.writeValueAsString(response));
-    }
-
-    // =========================================================================
-    // GET /a2a/agents
-    // =========================================================================
-
-    private FullHttpResponse handleListAgents() throws Exception {
-        return jsonResponse(HttpResponseStatus.OK,
-            objectMapper.writeValueAsString(a2aService.listAllCards()));
-    }
-
-    // =========================================================================
-    // POST /a2a/heartbeat
-    // =========================================================================
-
-    private FullHttpResponse handleHeartbeat(HttpRequest request) throws Exception {
-        String body = readBody(request);
-        HeartbeatRequest hb;
-        try {
-            hb = objectMapper.readValue(body, HeartbeatRequest.class);
-        } catch (Exception e) {
-            return jsonResponse(HttpResponseStatus.BAD_REQUEST, errorBody("Invalid JSON: " + e.getMessage()));
-        }
-
-        org.apache.eventmesh.protocol.a2a.AgentIdentity identity =
-            org.apache.eventmesh.protocol.a2a.AgentIdentity.builder()
-                .orgId(hb.getOrgId())
-                .unitId(hb.getUnitId())
-                .agentId(hb.getAgentId())
-                .build();
-
-        boolean refreshed = a2aService.heartbeat(identity);
-        Map<String, Object> response = new LinkedHashMap<>();
-        response.put("ok", refreshed);
-        if (!refreshed) {
-            response.put("error", "Agent not registered. POST /a2a/cards/card/{org}/{unit}/{agent} first.");
-        }
-        return jsonResponse(HttpResponseStatus.OK, objectMapper.writeValueAsString(response));
-    }
-
-    // =========================================================================
-    // Helpers
-    // =========================================================================
-
-    private boolean isTerminalState(TaskRegistry.TaskState state) {
-        return state == TaskRegistry.TaskState.COMPLETED
-            || state == TaskRegistry.TaskState.FAILED
-            || state == TaskRegistry.TaskState.CANCELLED;
-    }
-
-    private String readBody(HttpRequest request) {
-        if (request instanceof io.netty.handler.codec.http.FullHttpRequest) {
-            ByteBuf content = ((io.netty.handler.codec.http.FullHttpRequest) request).content();
-            return content.toString(StandardCharsets.UTF_8);
-        }
-        return "";
-    }
-
-    private String getQueryParam(QueryStringDecoder decoder, String key) {
-        java.util.List<String> values = decoder.parameters().get(key);
-        return (values != null && !values.isEmpty()) ? values.get(0) : null;
-    }
-
-    private int parseIntQueryParam(QueryStringDecoder decoder, String key,
-        int defaultValue, int minValue, int maxValue) {
-        String value = getQueryParam(decoder, key);
-        if (value == null || value.isEmpty()) {
-            return defaultValue;
-        }
-        try {
-            int parsed = Integer.parseInt(value);
-            if (parsed < minValue) {
-                return minValue;
-            }
-            if (parsed > maxValue) {
-                return maxValue;
-            }
-            return parsed;
-        } catch (NumberFormatException e) {
-            return defaultValue;
-        }
-    }
-
-    private String errorBody(String message) {
-        try {
-            return objectMapper.writeValueAsString(java.util.Collections.singletonMap("error", message));
-        } catch (Exception e) {
-            return "{\"error\":\"" + message + "\"}";
-        }
-    }
-
-    private FullHttpResponse jsonResponse(HttpResponseStatus status, String body) {
-        ByteBuf content = Unpooled.copiedBuffer(body, StandardCharsets.UTF_8);
-        DefaultFullHttpResponse response = new DefaultFullHttpResponse(
-            HttpVersion.HTTP_1_1, status, content);
-        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8");
-        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
-        setCorsHeaders(response);
-        return response;
-    }
-
-    private FullHttpResponse corsResponse(HttpResponseStatus status) {
-        DefaultFullHttpResponse response = new DefaultFullHttpResponse(
-            HttpVersion.HTTP_1_1, status);
-        setCorsHeaders(response);
-        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
-        return response;
-    }
-
-    private void setCorsHeaders(HttpResponse response) {
-        response.headers().set("Access-Control-Allow-Origin", "*");
-        response.headers().set("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
-        response.headers().set("Access-Control-Allow-Headers", "Content-Type, Authorization");
-        response.headers().set("Access-Control-Max-Age", "3600");
-    }
-
-    // =========================================================================
-    // Request / Response DTOs
-    // =========================================================================
-
-    public static class TaskRequest {
-
-        private String targetAgent;
-        private String message;
-        private String parentTaskId;
-
-        public String getTargetAgent() {
-            return targetAgent;
-        }
-
-        public void setTargetAgent(String targetAgent) {
-            this.targetAgent = targetAgent;
-        }
-
-        public String getMessage() {
-            return message;
-        }
-
-        public void setMessage(String message) {
-            this.message = message;
-        }
-
-        public String getParentTaskId() {
-            return parentTaskId;
-        }
-
-        public void setParentTaskId(String parentTaskId) {
-            this.parentTaskId = parentTaskId;
-        }
-    }
-
-    public static class HeartbeatRequest {
-
-        private String orgId;
-        private String unitId;
-        private String agentId;
-
-        public String getOrgId() {
-            return orgId;
-        }
-
-        public void setOrgId(String orgId) {
-            this.orgId = orgId;
-        }
-
-        public String getUnitId() {
-            return unitId;
-        }
-
-        public void setUnitId(String unitId) {
-            this.unitId = unitId;
-        }
-
-        public String getAgentId() {
-            return agentId;
-        }
-
-        public void setAgentId(String agentId) {
-            this.agentId = agentId;
-        }
-    }
-}
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2AGatewayServer.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2AGatewayServer.java
deleted file mode 100644
index 9a2410614..000000000
--- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2AGatewayServer.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.eventmesh.runtime.a2a;
-
-import org.apache.eventmesh.protocol.a2a.A2AProtocolConstants;
-import org.apache.eventmesh.protocol.a2a.A2ATopicFactory;
-import org.apache.eventmesh.protocol.a2a.AgentIdentity;
-import org.apache.eventmesh.protocol.a2a.model.AgentCard;
-import org.apache.eventmesh.protocol.a2a.model.AgentSkill;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.UUID;
-
-import io.cloudevents.CloudEvent;
-import io.cloudevents.core.builder.CloudEventBuilder;
-import io.netty.bootstrap.ServerBootstrap;
-import io.netty.buffer.Unpooled;
-import io.netty.channel.ChannelFuture;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInitializer;
-import io.netty.channel.ChannelOption;
-import io.netty.channel.EventLoopGroup;
-import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.channel.socket.SocketChannel;
-import io.netty.channel.socket.nio.NioServerSocketChannel;
-import io.netty.handler.codec.http.DefaultFullHttpResponse;
-import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.FullHttpResponse;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpObjectAggregator;
-import io.netty.handler.codec.http.HttpRequestDecoder;
-import io.netty.handler.codec.http.HttpResponseEncoder;
-import io.netty.handler.codec.http.HttpResponseStatus;
-import io.netty.handler.codec.http.HttpVersion;
-
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * Standalone A2A Gateway Server.
- *
- * <p>Starts an embedded Netty HTTP server that exposes the A2A Gateway REST API.
- * Pre-registers a mock "weather-agent" that auto-responds to task requests.
- *
- * <p>Usage:
- * <pre>
- *   java org.apache.eventmesh.runtime.a2a.A2AGatewayServer [port]
- * </pre>
- *
- * <p>Then run the client demo (A2AGatewayDemo) to submit tasks via HTTP.
- */
-@Slf4j
-public class A2AGatewayServer {
-
-    private static final String NAMESPACE = "global";
-    private static final String GATEWAY_ID = "demo-gateway";
-    private static final int DEFAULT_PORT = 10105;
-
-    private final int port;
-    private EventLoopGroup bossGroup;
-    private EventLoopGroup workerGroup;
-    private ChannelFuture serverChannel;
-
-    private InMemoryA2AMessageTransport transport;
-    private TaskRegistry taskRegistry;
-    private A2APublishSubscribeService a2aService;
-    private A2AGatewayService gatewayService;
-    private A2AGatewayHttpHandler gatewayHandler;
-    private A2ACardHttpHandler cardHandler;
-
-    public A2AGatewayServer(int port) {
-        this.port = port;
-    }
-
-    public void start() throws Exception {
-        // 1. Initialize components
-        transport = new InMemoryA2AMessageTransport();
-        taskRegistry = new TaskRegistry();
-        a2aService = new A2APublishSubscribeService();
-        a2aService.init();
-        a2aService.start();
-
-        gatewayService = new A2AGatewayService(NAMESPACE, GATEWAY_ID, transport, taskRegistry, a2aService);
-        gatewayService.start();
-
-        gatewayHandler = new A2AGatewayHttpHandler(gatewayService, a2aService);
-        cardHandler = new A2ACardHttpHandler(a2aService);
-
-        // 2. Pre-register a mock weather agent
-        registerMockWeatherAgent();
-
-        // 3. Start Netty HTTP server
-        bossGroup = new NioEventLoopGroup(1);
-        workerGroup = new NioEventLoopGroup();
-
-        ServerBootstrap bootstrap = new ServerBootstrap();
-        bootstrap.group(bossGroup, workerGroup)
-            .channel(NioServerSocketChannel.class)
-            .childHandler(new ChannelInitializer<SocketChannel>() {
-
-                @Override
-                protected void initChannel(SocketChannel ch) {
-                    ch.pipeline()
-                        .addLast(new HttpRequestDecoder())
-                        .addLast(new HttpObjectAggregator(65536))
-                        .addLast(new HttpResponseEncoder())
-                        .addLast(new A2AGatewayRequestHandler(gatewayHandler, cardHandler));
-                }
-            })
-            .option(ChannelOption.SO_BACKLOG, 128)
-            .childOption(ChannelOption.SO_KEEPALIVE, true);
-
-        serverChannel = bootstrap.bind(port).sync();
-        log.info("=== A2A Gateway Server started on port {} ===", port);
-        log.info("Gateway ID: {}, Namespace: {}", GATEWAY_ID, NAMESPACE);
-        log.info("Endpoints:");
-        log.info("  POST   /a2a/tasks              - submit task (sync/async)");
-        log.info("  GET    /a2a/tasks               - list tasks (?state=COMPLETED&limit=100&offset=0)");
-        log.info("  GET    /a2a/tasks/{{taskId}}       - get task status");
-        log.info("  DELETE /a2a/tasks/{{taskId}}       - cancel task");
-        log.info("  GET    /a2a/tasks/{{taskId}}/wait  - wait for result (long-poll)");
-        log.info("  GET    /a2a/tasks/{{taskId}}/stream - SSE stream of task status updates");
-        log.info("  GET    /a2a/agents               - list registered agents");
-        log.info("  POST   /a2a/heartbeat            - agent heartbeat");
-        log.info("  GET    /a2a/health               - health check");
-        log.info("  GET    /a2a/cards/list           - list agent cards");
-        log.info("  POST   /a2a/cards/card/{{org}}/{{unit}}/{{agent}} - register card");
-    }
-
-    /**
-     * Registers a mock weather agent that auto-responds to task requests.
-     */
-    private void registerMockWeatherAgent() throws Exception {
-        AgentCard weatherCard = AgentCard.builder()
-            .name("weather-agent")
-            .description("A weather agent that can tell you the weather")
-            .version("1.0.0")
-            .supportedInterfaces(Arrays.asList(
-                org.apache.eventmesh.protocol.a2a.model.AgentInterface.builder()
-                    .url("http://localhost:" + port + "/a2a")
-                    .protocolBinding("JSONRPC")
-                    .protocolVersion("0.3")
-                    .build()))
-            .capabilities(org.apache.eventmesh.protocol.a2a.model.AgentCapabilities.builder()
-                .streaming(false)
-                .pushNotifications(false)
-                .build())
-            .skills(Arrays.asList(
-                AgentSkill.builder()
-                    .id("get-weather")
-                    .name("Get Weather")
-                    .description("Get the current weather for a city")
-                    .tags(Arrays.asList("weather", "test"))
-                    .build()))
-            .defaultInputModes(Arrays.asList("text/plain"))
-            .defaultOutputModes(Arrays.asList("text/plain"))
-            .build();
-
-        AgentIdentity identity = AgentIdentity.builder()
-            .orgId("default").unitId("default").agentId("weather-agent").build();
-        A2APublishSubscribeService.RegistrationResult regResult =
-            a2aService.registerCard(identity, weatherCard);
-        if (!regResult.isSuccess()) {
-            log.warn("Failed to register weather-agent card: {}", regResult.getErrorMessage());
-        }
-
-        // Subscribe to agent request topic and auto-respond
-        String requestTopic = A2ATopicFactory.agentRequestTopic(NAMESPACE, "weather-agent");
-        transport.subscribe(requestTopic, (topic, event) -> {
-            String taskId = event.getId();
-            String message = event.getData() != null
-                ? new String(event.getData().toBytes(), StandardCharsets.UTF_8)
-                : "";
-
-            log.info("[Weather Agent] Received request: taskId={}, message={}", taskId, message);
-
-            // Simulate processing delay
-            try {
-                Thread.sleep(300);
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-            }
-
-            // Publish response
-            String gwId = extractGatewayId(event);
-            String responseTopic = A2ATopicFactory.gatewayResponseTopic(NAMESPACE, gwId, taskId);
-            String result = "The weather in " + message + " is sunny, 25°C";
-            CloudEvent responseEvent = CloudEventBuilder.v1()
-                .withId(UUID.randomUUID().toString())
-                .withType(A2AProtocolConstants.CE_TYPE_PREFIX + "task.response")
-                .withSource(java.net.URI.create("agent/weather-agent"))
-                .withDataContentType("application/json")
-                .withData(result.getBytes(StandardCharsets.UTF_8))
-                .withExtension(A2AProtocolConstants.CE_EXTENSION_A2A_METHOD, A2AProtocolConstants.OP_SEND_MESSAGE)
-                .build();
-
-            try {
-                transport.publish(responseTopic, responseEvent);
-                log.info("[Weather Agent] Response published: taskId={}", taskId);
-            } catch (Exception e) {
-                log.error("[Weather Agent] Failed to publish response", e);
-            }
-        });
-
-        log.info("Mock weather-agent registered and listening for requests.");
-    }
-
-    private String extractGatewayId(CloudEvent event) {
-        if (event.getSource() == null) {
-            return "default-gateway";
-        }
-        String source = event.getSource().toString();
-        if (source.startsWith("gateway/")) {
-            return source.substring("gateway/".length());
-        }
-        return "default-gateway";
-    }
-
-    /**
-     * Returns the actual bound port. Useful when port 0 was specified to let the OS pick a port.
-     */
-    public int getBoundPort() {
-        if (serverChannel != null) {
-            return ((io.netty.channel.socket.ServerSocketChannel) serverChannel.channel()).localAddress().getPort();
-        }
-        return port;
-    }
-
-    public void shutdown() throws Exception {
-        if (serverChannel != null) {
-            serverChannel.channel().close().sync();
-        }
-        if (workerGroup != null) {
-            workerGroup.shutdownGracefully();
-        }
-        if (bossGroup != null) {
-            bossGroup.shutdownGracefully();
-        }
-        if (gatewayService != null) {
-            gatewayService.shutdown();
-        }
-        if (a2aService != null) {
-            a2aService.shutdown();
-        }
-        log.info("A2A Gateway Server shut down.");
-    }
-
-    public static void main(String[] args) throws Exception {
-        int port = args.length > 0 ? Integer.parseInt(args[0]) : DEFAULT_PORT;
-
-        A2AGatewayServer server = new A2AGatewayServer(port);
-
-        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
-            try {
-                server.shutdown();
-            } catch (Exception e) {
-                log.error("Error during shutdown", e);
-            }
-        }));
-
-        server.start();
-
-        // Keep running until interrupted
-        Thread.currentThread().join();
-    }
-
-    // =========================================================================
-    // Netty Channel Handler
-    // =========================================================================
-
-    private static class A2AGatewayRequestHandler extends io.netty.channel.SimpleChannelInboundHandler<FullHttpRequest> {
-
-        private final A2AGatewayHttpHandler gatewayHandler;
-        private final A2ACardHttpHandler cardHandler;
-
-        A2AGatewayRequestHandler(A2AGatewayHttpHandler gatewayHandler, A2ACardHttpHandler cardHandler) {
-            this.gatewayHandler = gatewayHandler;
-            this.cardHandler = cardHandler;
-        }
-
-        @Override
-        protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
-            String uri = request.uri();
-            try {
-                FullHttpResponse response;
-                if (uri.startsWith("/a2a/cards")) {
-                    response = cardHandler.handle(request, ctx);
-                } else if (uri.startsWith("/a2a/")) {
-                    response = gatewayHandler.handle(request, ctx);
-                } else {
-                    response = jsonError(HttpResponseStatus.NOT_FOUND, "Not found: " + uri);
-                }
-
-                // null means the handler wrote the response directly (e.g. SSE streaming)
-                if (response != null) {
-                    ctx.writeAndFlush(response);
-                }
-            } catch (Exception e) {
-                log.error("Error handling request: {} - {}", uri, e.getMessage(), e);
-                FullHttpResponse errorResponse = jsonError(HttpResponseStatus.INTERNAL_SERVER_ERROR, e.getMessage());
-                ctx.writeAndFlush(errorResponse);
-            }
-        }
-
-        @Override
-        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
-            log.error("Channel exception: {}", cause.getMessage(), cause);
-            ctx.close();
-        }
-
-        private FullHttpResponse jsonError(HttpResponseStatus status, String message) {
-            String body = "{\"error\":\"" + message + "\"}";
-            DefaultFullHttpResponse response = new DefaultFullHttpResponse(
-                HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(body, StandardCharsets.UTF_8));
-            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8");
-            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
-            response.headers().set("Access-Control-Allow-Origin", "*");
-            response.headers().set("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
-            response.headers().set("Access-Control-Allow-Headers", "Content-Type, Authorization");
-            return response;
-        }
-    }
-}
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2AGatewayService.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2AGatewayService.java
deleted file mode 100644
index e70db7107..000000000
--- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2AGatewayService.java
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.eventmesh.runtime.a2a;
-
-import org.apache.eventmesh.protocol.a2a.A2AMessageTransport;
-import org.apache.eventmesh.protocol.a2a.A2AProtocolConstants;
-import org.apache.eventmesh.protocol.a2a.A2ATopicFactory;
-
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import io.cloudevents.CloudEvent;
-import io.cloudevents.core.builder.CloudEventBuilder;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * A2A Gateway Service: orchestrates task submission, response handling, and SSE streaming.
- *
- * <p>This is the core component that ties together:
- * <ul>
- *   <li>{@link A2AMessageTransport} for pub/sub</li>
- *   <li>{@link TaskRegistry} for task lifecycle</li>
- *   <li>{@link A2APublishSubscribeService} for agent discovery</li>
- *   <li>{@link A2ATopicFactory} for topic routing</li>
- * </ul>
- */
-@Slf4j
-public class A2AGatewayService {
-
-    private static final ObjectMapper objectMapper = new ObjectMapper();
-
-    private final String namespace;
-    private final String gatewayId;
-    private final A2AMessageTransport transport;
-    private final TaskRegistry taskRegistry;
-    private final A2APublishSubscribeService a2aService;
-
-    // Pending tasks waiting for response
-    private final ConcurrentHashMap<String, CompletableFuture<TaskResult>> pendingTasks = new ConcurrentHashMap<>();
-    // SSE subscribers for status updates
-    private final ConcurrentHashMap<String, List<StatusSubscriber>> statusSubscribers = new ConcurrentHashMap<>();
-
-    private volatile boolean started = false;
-    private String responseSubscriptionId;
-    private String statusSubscriptionId;
-
-    // Task timeout: tasks that don't receive a response within this duration are auto-failed
-    private static final long DEFAULT_TASK_TIMEOUT_MS = 120_000L; // 2 minutes
-    private final long taskTimeoutMs;
-    private ScheduledExecutorService taskTimeoutScheduler;
-
-    public A2AGatewayService(String namespace, String gatewayId,
-        A2AMessageTransport transport,
-        TaskRegistry taskRegistry,
-        A2APublishSubscribeService a2aService) {
-        this(namespace, gatewayId, transport, taskRegistry, a2aService, DEFAULT_TASK_TIMEOUT_MS);
-    }
-
-    public A2AGatewayService(String namespace, String gatewayId,
-        A2AMessageTransport transport,
-        TaskRegistry taskRegistry,
-        A2APublishSubscribeService a2aService,
-        long taskTimeoutMs) {
-        this.namespace = namespace;
-        this.gatewayId = gatewayId;
-        this.transport = transport;
-        this.taskRegistry = taskRegistry;
-        this.a2aService = a2aService;
-        this.taskTimeoutMs = taskTimeoutMs;
-    }
-
-    public String getGatewayId() {
-        return gatewayId;
-    }
-
-    public String getNamespace() {
-        return namespace;
-    }
-
-    public A2APublishSubscribeService getA2aService() {
-        return a2aService;
-    }
-
-    public TaskRegistry getTaskRegistry() {
-        return taskRegistry;
-    }
-
-    /**
-     * Starts the gateway service: subscribes to gateway response/status topics.
-     */
-    public synchronized void start() throws Exception {
-        if (started) {
-            return;
-        }
-        // Start task registry TTL cleanup
-        taskRegistry.start();
-
-        // Start task timeout scheduler
-        taskTimeoutScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
-            Thread t = new Thread(r, "a2a-task-timeout");
-            t.setDaemon(true);
-            return t;
-        });
-
-        // Subscribe to all responses for this gateway
-        String responseTopic = A2ATopicFactory.gatewayResponseWildcardTopic(namespace, gatewayId);
-        responseSubscriptionId = transport.subscribe(responseTopic, this::handleResponse);
-
-        // Subscribe to all status updates for this gateway
-        String statusTopic = A2ATopicFactory.gatewayStatusWildcardTopic(namespace, gatewayId);
-        statusSubscriptionId = transport.subscribe(statusTopic, this::handleStatus);
-
-        started = true;
-        log.info("A2AGatewayService started: gatewayId={}, namespace={}", gatewayId, namespace);
-    }
-
-    public synchronized void shutdown() throws Exception {
-        if (!started) {
-            return;
-        }
-        if (responseSubscriptionId != null) {
-            transport.unsubscribe(responseSubscriptionId);
-        }
-        if (statusSubscriptionId != null) {
-            transport.unsubscribe(statusSubscriptionId);
-        }
-        if (taskTimeoutScheduler != null) {
-            taskTimeoutScheduler.shutdownNow();
-            taskTimeoutScheduler = null;
-        }
-        pendingTasks.clear();
-        statusSubscribers.clear();
-        taskRegistry.shutdown();
-        started = false;
-        log.info("A2AGatewayService shutdown.");
-    }
-
-    // =========================================================================
-    // Task Submission
-    // =========================================================================
-
-    /**
-     * Submits an A2A task to a target agent.
-     *
-     * @param targetAgent  target agent name
-     * @param message      the message content (JSON string)
-     * @param parentTaskId parent task ID (null for root tasks from external clients)
-     * @return CompletableFuture that will be completed with the task result
-     */
-    public CompletableFuture<TaskResult> submitTask(String targetAgent, String message, String parentTaskId) {
-        String taskId = generateTaskId();
-        return submitTask(taskId, targetAgent, message, parentTaskId);
-    }
-
-    /**
-     * Submits an A2A task with a specific task ID.
-     */
-    public CompletableFuture<TaskResult> submitTask(String taskId, String targetAgent,
-        String message, String parentTaskId) {
-        if (!started) {
-            CompletableFuture<TaskResult> future = new CompletableFuture<>();
-            future.completeExceptionally(new IllegalStateException("Gateway not started"));
-            return future;
-        }
-
-        // Validate that the target agent is registered
-        if (!a2aService.isAgentRegistered(targetAgent)) {
-            CompletableFuture<TaskResult> future = new CompletableFuture<>();
-            future.completeExceptionally(new IllegalArgumentException(
-                "Target agent not registered: " + targetAgent));
-            return future;
-        }
-
-        // Create task entry
-        TaskRegistry.TaskEntry taskEntry = taskRegistry.createTask(taskId, parentTaskId, targetAgent, gatewayId);
-
-        // Build A2A CloudEvent
-        CloudEvent event = buildTaskRequestEvent(taskId, targetAgent, message, parentTaskId);
-
-        // Register pending future BEFORE publishing to avoid race condition:
-        // InMemoryTransport delivers synchronously, so if we publish first,
-        // handleResponse() could run before put() and the future would never complete.
-        CompletableFuture<TaskResult> future = new CompletableFuture<>();
-        pendingTasks.put(taskId, future);
-
-        // Schedule timeout: if no response within taskTimeoutMs, auto-fail the task
-        if (taskTimeoutScheduler != null) {
-            taskTimeoutScheduler.schedule(() -> {
-                CompletableFuture<TaskResult> pending = pendingTasks.get(taskId);
-                if (pending != null && !pending.isDone()) {
-                    taskRegistry.markFailed(taskId, "Task timed out after " + taskTimeoutMs + "ms with no response");
-                    pendingTasks.remove(taskId);
-                    pending.completeExceptionally(new java.util.concurrent.TimeoutException(
-                        "Task " + taskId + " timed out after " + taskTimeoutMs + "ms"));
-                    notifyStatusSubscribers(taskId, "failed", "Task timed out");
-                    log.warn("Task timed out: taskId={}, targetAgent={}", taskId, targetAgent);
-                }
-            }, taskTimeoutMs, TimeUnit.MILLISECONDS);
-        }
-
-        // Publish to agent request topic
-        String requestTopic = A2ATopicFactory.agentRequestTopic(namespace, targetAgent);
-        try {
-            transport.publish(requestTopic, event);
-            log.info("Task submitted: taskId={}, targetAgent={}, topic={}", taskId, targetAgent, requestTopic);
-        } catch (Exception e) {
-            log.error("Failed to publish task: taskId={}", taskId, e);
-            taskRegistry.markFailed(taskId, "Publish failed: " + e.getMessage());
-            pendingTasks.remove(taskId);
-            future.completeExceptionally(e);
-        }
-
-        return future;
-    }
-
-    /**
-     * Cancels a task.
-     */
-    public boolean cancelTask(String taskId) {
-        boolean cancelled = taskRegistry.markCancelled(taskId);
-        if (cancelled) {
-            CompletableFuture<TaskResult> future = pendingTasks.remove(taskId);
-            if (future != null) {
-                future.complete(new TaskResult(TaskRegistry.TaskState.CANCELLED, null, "Task cancelled"));
-            }
-            notifyStatusSubscribers(taskId, "cancelled", "Task cancelled");
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Gets task status.
-     */
-    public TaskRegistry.TaskEntry getTaskStatus(String taskId) {
-        return taskRegistry.getTask(taskId);
-    }
-
-    // =========================================================================
-    // SSE Status Subscription
-    // =========================================================================
-
-    /**
-     * Registers a subscriber for status updates on a specific task.
-     */
-    public void registerStatusSubscriber(String taskId, StatusSubscriber subscriber) {
-        statusSubscribers.computeIfAbsent(taskId, k -> new CopyOnWriteArrayList<>()).add(subscriber);
-    }
-
-    /**
-     * Removes a status subscriber.
-     */
-    public void unregisterStatusSubscriber(String taskId, StatusSubscriber subscriber) {
-        List<StatusSubscriber> subs = statusSubscribers.get(taskId);
-        if (subs != null) {
-            subs.remove(subscriber);
-            if (subs.isEmpty()) {
-                statusSubscribers.remove(taskId);
-            }
-        }
-    }
-
-    // =========================================================================
-    // Response / Status Handling
-    // =========================================================================
-
-    private void handleResponse(String topic, CloudEvent event) {
-        A2ATopicFactory.ParsedTopic parsed = A2ATopicFactory.parse(topic);
-        if (parsed == null || !parsed.isResponse() || parsed.getTaskId() == null) {
-            return;
-        }
-        String taskId = parsed.getTaskId();
-        log.info("Received response for task: {}", taskId);
-
-        TaskRegistry.TaskEntry entry = taskRegistry.getTask(taskId);
-        if (entry == null) {
-            log.warn("Response received for unknown task: {}", taskId);
-            return;
-        }
-
-        String resultData = extractEventData(event);
-        taskRegistry.markCompleted(taskId, resultData);
-
-        CompletableFuture<TaskResult> future = pendingTasks.remove(taskId);
-        if (future != null) {
-            future.complete(new TaskResult(TaskRegistry.TaskState.COMPLETED, resultData, null));
-        }
-
-        // Notify SSE subscribers
-        notifyStatusSubscribers(taskId, "completed", resultData);
-    }
-
-    private void handleStatus(String topic, CloudEvent event) {
-        A2ATopicFactory.ParsedTopic parsed = A2ATopicFactory.parse(topic);
-        if (parsed == null || !parsed.isStatus() || parsed.getTaskId() == null) {
-            return;
-        }
-        String taskId = parsed.getTaskId();
-        String statusData = extractEventData(event);
-        log.debug("Received status for task: {} -> {}", taskId, statusData);
-
-        // Mark as working if currently submitted
-        TaskRegistry.TaskEntry entry = taskRegistry.getTask(taskId);
-        if (entry != null && entry.getState() == TaskRegistry.TaskState.SUBMITTED) {
-            taskRegistry.markWorking(taskId);
-        }
-
-        // Notify SSE subscribers
-        notifyStatusSubscribers(taskId, "working", statusData);
-    }
-
-    private void notifyStatusSubscribers(String taskId, String state, String data) {
-        List<StatusSubscriber> subs = statusSubscribers.get(taskId);
-        if (subs != null) {
-            for (StatusSubscriber sub : subs) {
-                try {
-                    sub.onStatus(taskId, state, data);
-                } catch (Exception e) {
-                    log.warn("Status subscriber error for task {}: {}", taskId, e.getMessage());
-                }
-            }
-        }
-    }
-
-    // =========================================================================
-    // CloudEvent Building
-    // =========================================================================
-
-    private CloudEvent buildTaskRequestEvent(String taskId, String targetAgent,
-        String message, String parentTaskId) {
-        CloudEventBuilder builder = CloudEventBuilder.v1()
-            .withId(taskId)
-            .withType(A2AProtocolConstants.CE_TYPE_PREFIX + "task.request")
-            .withSource(java.net.URI.create("gateway/" + gatewayId))
-            .withDataContentType("application/json")
-            .withData(message.getBytes(StandardCharsets.UTF_8))
-            .withExtension(A2AProtocolConstants.CE_EXTENSION_A2A_METHOD, A2AProtocolConstants.OP_SEND_MESSAGE)
-            .withExtension(A2AProtocolConstants.CE_EXTENSION_TARGET_AGENT, targetAgent)
-            .withExtension(A2AProtocolConstants.CE_EXTENSION_PROTOCOL, "A2A")
-            .withExtension(A2AProtocolConstants.CE_EXTENSION_PROTOCOL_VERSION, A2AProtocolConstants.PROTOCOL_VERSION);
-
-        if (parentTaskId != null) {
-            builder.withExtension(A2AProtocolConstants.CE_EXTENSION_COLLABORATION_ID, parentTaskId);
-        }
-
-        return builder.build();
-    }
-
-    private String extractEventData(CloudEvent event) {
-        if (event.getData() == null) {
-            return null;
-        }
-        return new String(event.getData().toBytes(), StandardCharsets.UTF_8);
-    }
-
-    private String generateTaskId() {
-        return "task-" + UUID.randomUUID().toString().substring(0, 8);
-    }
-
-    // =========================================================================
-    // Result Types
-    // =========================================================================
-
-    public static class TaskResult {
-
-        private final TaskRegistry.TaskState state;
-        private final String data;
-        private final String errorMessage;
-
-        public TaskResult(TaskRegistry.TaskState state, String data, String errorMessage) {
-            this.state = state;
-            this.data = data;
-            this.errorMessage = errorMessage;
-        }
-
-        public TaskRegistry.TaskState getState() {
-            return state;
-        }
-
-        public String getData() {
-            return data;
-        }
-
-        public String getErrorMessage() {
-            return errorMessage;
-        }
-    }
-
-    /**
-     * Callback interface for task status change notifications.
-     */
-    @FunctionalInterface
-    public interface StatusSubscriber {
-
-        void onStatus(String taskId, String state, String data);
-    }
-}
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2APublishSubscribeService.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2APublishSubscribeService.java
deleted file mode 100644
index 10734d2d3..000000000
--- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2APublishSubscribeService.java
+++ /dev/null
@@ -1,449 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.eventmesh.runtime.a2a;
-
-import org.apache.eventmesh.protocol.a2a.A2AProtocolConstants;
-import org.apache.eventmesh.protocol.a2a.AgentCardValidator;
-import org.apache.eventmesh.protocol.a2a.AgentIdentity;
-import org.apache.eventmesh.protocol.a2a.model.AgentCard;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import io.cloudevents.CloudEvent;
-import io.cloudevents.core.builder.CloudEventBuilder;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * A2APublishSubscribeService: Manages A2A Agent Card Registry and processes A2A events.
- *
- * <p>Features:
- * - Agent Card CRUD (register, delete, get, list)
- * - Agent status tracking (online/offline)
- * - Event processing with status metadata augmentation
- * - Hierarchical identity (org_id/unit_id/agent_id) with wildcard queries
- */
-@Slf4j
-public class A2APublishSubscribeService {
-
-    private static final ObjectMapper objectMapper = new ObjectMapper();
-
-    private volatile boolean isStarted = false;
-
-    private final ConcurrentHashMap<AgentIdentity, RegisteredCard> cardRegistry = new ConcurrentHashMap<>();
-    private final ConcurrentHashMap<String, AgentStatus> agentStatusMap = new ConcurrentHashMap<>();
-
-    private AgentCardValidator cardValidator;
-
-    // TTL heartbeat configuration
-    private static final long DEFAULT_CARD_TTL_MS = 60_000L; // 60 seconds
-    private static final long DEFAULT_CLEANUP_INTERVAL_MS = 15_000L; // 15 seconds
-    private final long cardTtlMs;
-    private final long cleanupIntervalMs;
-    private ScheduledExecutorService cleanupExecutor;
-
-    public A2APublishSubscribeService() {
-        this(DEFAULT_CARD_TTL_MS, DEFAULT_CLEANUP_INTERVAL_MS);
-    }
-
-    public A2APublishSubscribeService(long cardTtlMs, long cleanupIntervalMs) {
-        this.cardTtlMs = cardTtlMs;
-        this.cleanupIntervalMs = cleanupIntervalMs;
-    }
-
-    public void init() throws Exception {
-        this.cardValidator = new AgentCardValidator(true);
-        log.info("A2APublishSubscribeService initialized with Agent Card Registry (TTL={}ms, cleanup={}ms).",
-            cardTtlMs, cleanupIntervalMs);
-    }
-
-    public void start() throws Exception {
-        isStarted = true;
-        startCleanupScheduler();
-        log.info("A2APublishSubscribeService started.");
-    }
-
-    public void shutdown() throws Exception {
-        isStarted = false;
-        if (cleanupExecutor != null) {
-            cleanupExecutor.shutdownNow();
-            cleanupExecutor = null;
-        }
-        cardRegistry.clear();
-        agentStatusMap.clear();
-        log.info("A2APublishSubscribeService shutdown.");
-    }
-
-    // -------------------------------------------------------------------------
-    // TTL Heartbeat Cleanup
-    // -------------------------------------------------------------------------
-
-    private void startCleanupScheduler() {
-        cleanupExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
-            Thread t = new Thread(r, "a2a-card-ttl-cleanup");
-            t.setDaemon(true);
-            return t;
-        });
-        cleanupExecutor.scheduleAtFixedRate(this::cleanupExpiredCards,
-            cleanupIntervalMs, cleanupIntervalMs, TimeUnit.MILLISECONDS);
-    }
-
-    /**
-     * Removes AgentCards that have not been refreshed within the TTL window.
-     */
-    void cleanupExpiredCards() {
-        if (!isStarted) {
-            return;
-        }
-        long now = System.currentTimeMillis();
-        int removed = 0;
-        Iterator<Map.Entry<AgentIdentity, RegisteredCard>> it = cardRegistry.entrySet().iterator();
-        while (it.hasNext()) {
-            Map.Entry<AgentIdentity, RegisteredCard> entry = it.next();
-            RegisteredCard rc = entry.getValue();
-            if (now - rc.lastHeartbeat > cardTtlMs) {
-                it.remove();
-                agentStatusMap.remove(entry.getKey().clientId());
-                removed++;
-                log.info("Agent card expired and removed: {} (lastHeartbeat={}ms ago)",
-                    entry.getKey().clientId(), now - rc.lastHeartbeat);
-            }
-        }
-        if (removed > 0) {
-            log.info("TTL cleanup removed {} expired agent card(s).", removed);
-        }
-    }
-
-    /**
-     * Refreshes the heartbeat timestamp for an agent card (agent calls this periodically).
-     */
-    public boolean heartbeat(AgentIdentity identity) {
-        if (!isStarted) {
-            throw new IllegalStateException("A2APublishSubscribeService is not started");
-        }
-        RegisteredCard rc = cardRegistry.get(identity);
-        if (rc == null) {
-            return false;
-        }
-        rc.lastHeartbeat = System.currentTimeMillis();
-        agentStatusMap.put(identity.clientId(), AgentStatus.ONLINE);
-        return true;
-    }
-
-    // =========================================================================
-    // Agent Card Registry Operations
-    // =========================================================================
-
-    /**
-     * Registers an Agent Card. Validates the card and identity before storing.
-     *
-     * @param identity the agent identity (org_id/unit_id/agent_id)
-     * @param card     the agent card
-     * @return RegistrationResult with success/failure
-     */
-    public RegistrationResult registerCard(AgentIdentity identity, AgentCard card) {
-        if (!isStarted) {
-            throw new IllegalStateException("A2APublishSubscribeService is not started");
-        }
-
-        // Validate identity
-        if (!identity.isValid()) {
-            String msg = String.format("Invalid agent identity: orgId=%s, unitId=%s, agentId=%s",
-                identity.getOrgId(), identity.getUnitId(), identity.getAgentId());
-            log.warn(msg);
-            return RegistrationResult.failure(msg);
-        }
-
-        // Validate card
-        try {
-            String cardJson = objectMapper.writeValueAsString(card);
-            AgentCardValidator.ValidationResult result = cardValidator.validate(cardJson);
-            if (!result.isValid()) {
-                log.warn("Agent card schema validation failed for {}: {}", identity.clientId(), result.getErrorMessage());
-                return RegistrationResult.failure(result.getErrorMessage());
-            }
-        } catch (Exception e) {
-            log.warn("Failed to serialize/validate agent card for {}: {}", identity.clientId(), e.getMessage());
-            return RegistrationResult.failure("Card validation error: " + e.getMessage());
-        }
-
-        RegisteredCard existing = cardRegistry.put(identity, new RegisteredCard(card, System.currentTimeMillis()));
-        if (existing != null) {
-            log.info("Updated agent card for {}", identity.clientId());
-        } else {
-            log.info("Registered new agent card for {}", identity.clientId());
-        }
-
-        // Set agent online
-        agentStatusMap.put(identity.clientId(), AgentStatus.ONLINE);
-
-        return RegistrationResult.success();
-    }
-
-    /**
-     * Deletes an Agent Card from the registry.
-     */
-    public boolean deleteCard(AgentIdentity identity) {
-        if (!isStarted) {
-            throw new IllegalStateException("A2APublishSubscribeService is not started");
-        }
-        RegisteredCard removed = cardRegistry.remove(identity);
-        if (removed != null) {
-            agentStatusMap.remove(identity.clientId());
-            log.info("Deleted agent card for {}", identity.clientId());
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Gets a specific Agent Card.
-     */
-    public CardEntry getCard(AgentIdentity identity) {
-        if (!isStarted) {
-            throw new IllegalStateException("A2APublishSubscribeService is not started");
-        }
-        RegisteredCard rc = cardRegistry.get(identity);
-        if (rc == null) {
-            return null;
-        }
-        return new CardEntry(identity, rc.card, lookupAgentStatus(identity));
-    }
-
-    /**
-     * Lists Agent Cards matching the given filters. Use null or "+" for wildcard matching.
-     */
-    public List<CardEntry> listCards(String orgId, String unitId, String agentId) {
-        if (!isStarted) {
-            throw new IllegalStateException("A2APublishSubscribeService is not started");
-        }
-        List<CardEntry> results = new ArrayList<>();
-        for (Map.Entry<AgentIdentity, RegisteredCard> entry : cardRegistry.entrySet()) {
-            AgentIdentity id = entry.getKey();
-            if (id.matchesFilter(orgId, unitId, agentId)) {
-                results.add(new CardEntry(id, entry.getValue().card, lookupAgentStatus(id)));
-            }
-        }
-        return results;
-    }
-
-    /**
-     * Lists all registered Agent Cards.
-     */
-    public List<CardEntry> listAllCards() {
-        return listCards(null, null, null);
-    }
-
-    /**
-     * Checks whether an agent with the given name is registered.
-     *
-     * @param agentName the agent name (or agentId segment) to look up
-     * @return true if a registered card's name matches
-     */
-    public boolean isAgentRegistered(String agentName) {
-        if (!isStarted || agentName == null) {
-            return false;
-        }
-        for (RegisteredCard rc : cardRegistry.values()) {
-            if (agentName.equals(rc.card.getName())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    // =========================================================================
-    // Agent Status
-    // =========================================================================
-
-    /**
-     * Looks up the status of an agent (online/offline).
-     */
-    public String lookupAgentStatus(AgentIdentity identity) {
-        AgentStatus status = agentStatusMap.get(identity.clientId());
-        return status != null ? status.value : A2AProtocolConstants.STATUS_OFFLINE;
-    }
-
-    /**
-     * Sets the status of an agent.
-     */
-    public void setAgentStatus(AgentIdentity identity, String status) {
-        if (A2AProtocolConstants.STATUS_ONLINE.equals(status)) {
-            agentStatusMap.put(identity.clientId(), AgentStatus.ONLINE);
-        } else {
-            agentStatusMap.put(identity.clientId(), AgentStatus.OFFLINE);
-        }
-    }
-
-    // =========================================================================
-    // Event Processing
-    // =========================================================================
-
-    /**
-     * Processes an A2A CloudEvent. Augments events with agent status metadata.
-     *
-     * @param event The CloudEvent to process.
-     * @return The processed (potentially modified) CloudEvent.
-     */
-    public CloudEvent process(CloudEvent event) {
-        if (!isStarted) {
-            throw new IllegalStateException("A2APublishSubscribeService is not started");
-        }
-
-        log.debug("Processing A2A event: {}", event.getId());
-
-        // Check if this is an A2A discovery topic event
-        String subject = event.getSubject();
-        if (subject != null && subject.startsWith(A2AProtocolConstants.TOPIC_NAMESPACE + "/")) {
-            AgentIdentity identity = AgentIdentity.fromDiscoveryTopic(subject);
-            if (identity != null) {
-                return augmentWithStatusMetadata(event, identity);
-            }
-        }
-
-        return event;
-    }
-
-    /**
-     * Augments a CloudEvent with agent status metadata (a2astatus, a2astatussource extensions).
-     * Matches EMQX's on_message_delivered hook behavior.
-     */
-    private CloudEvent augmentWithStatusMetadata(CloudEvent event, AgentIdentity identity) {
-        String status = lookupAgentStatus(identity);
-        CloudEventBuilder builder = CloudEventBuilder.from(event);
-        builder.withExtension(A2AProtocolConstants.CE_EXTENSION_A2A_STATUS, status);
-        builder.withExtension(A2AProtocolConstants.CE_EXTENSION_A2A_STATUS_SOURCE, "eventmesh");
-        return builder.build();
-    }
-
-    // =========================================================================
-    // Inner Types
-    // =========================================================================
-
-    private enum AgentStatus {
-
-        ONLINE(A2AProtocolConstants.STATUS_ONLINE),
-        OFFLINE(A2AProtocolConstants.STATUS_OFFLINE);
-
-        final String value;
-
-        AgentStatus(String value) {
-            this.value = value;
-        }
-    }
-
-    private static class RegisteredCard {
-
-        final AgentCard card;
-        final long registeredAt;
-        volatile long lastHeartbeat;
-
-        RegisteredCard(AgentCard card, long registeredAt) {
-            this.card = card;
-            this.registeredAt = registeredAt;
-            this.lastHeartbeat = registeredAt;
-        }
-    }
-
-    /**
-     * Represents a registered agent card entry with identity, card data, and status.
-     */
-    public static class CardEntry {
-
-        private final AgentIdentity identity;
-        private final AgentCard card;
-        private final String status;
-
-        public CardEntry(AgentIdentity identity, AgentCard card, String status) {
-            this.identity = identity;
-            this.card = card;
-            this.status = status;
-        }
-
-        public AgentIdentity getIdentity() {
-            return identity;
-        }
-
-        public AgentCard getCard() {
-            return card;
-        }
-
-        public String getStatus() {
-            return status;
-        }
-
-        public String getNamespace() {
-            return identity.getNamespace();
-        }
-
-        public String getId() {
-            return identity.clientId();
-        }
-
-        public String getName() {
-            return card.getName();
-        }
-
-        public String getVersion() {
-            return card.getVersion();
-        }
-
-        public String getDescription() {
-            return card.getDescription();
-        }
-    }
-
-    /**
-     * Result of a card registration attempt.
-     */
-    public static class RegistrationResult {
-
-        private final boolean success;
-        private final String errorMessage;
-
-        private RegistrationResult(boolean success, String errorMessage) {
-            this.success = success;
-            this.errorMessage = errorMessage;
-        }
-
-        public static RegistrationResult success() {
-            return new RegistrationResult(true, null);
-        }
-
-        public static RegistrationResult failure(String errorMessage) {
-            return new RegistrationResult(false, errorMessage);
-        }
-
-        public boolean isSuccess() {
-            return success;
-        }
-
-        public String getErrorMessage() {
-            return errorMessage;
-        }
-    }
-}
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/EventMeshA2ATransport.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/EventMeshA2ATransport.java
index 23da28bb4..1353b7fc3 100644
--- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/EventMeshA2ATransport.java
+++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/EventMeshA2ATransport.java
@@ -101,7 +101,8 @@ public class EventMeshA2ATransport implements A2AMessageTransport {
             try {
                 List<BufferedEvent> batch = ingress.poll(clientId, 100, 500L);
                 for (BufferedEvent be : batch) {
-                    CloudEvent event = be.getEvent();
+                    // Egress boundary: the push buffer carries Frame; convert to CloudEvent for the A2A callback.
+                    CloudEvent event = be.getEvent().toCloudEvent();
                     String topic = event.getSubject() != null ? event.getSubject() : "default";
                     MessageCallback cb = callbacks.get(topic);
                     if (cb != null) {
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/InMemoryA2AMessageTransport.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/InMemoryA2AMessageTransport.java
deleted file mode 100644
index d7f2f2ed4..000000000
--- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/InMemoryA2AMessageTransport.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
... 18926 lines suppressed ...


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@eventmesh.apache.org
For additional commands, e-mail: commits-help@eventmesh.apache.org