# Synchronous Messaging (/aiway/a2a/sync-messaging)



Synchronous A2A messaging is a request/reply call: a caller POSTs a JSON-RPC 2.0
request to `POST /a2a/<agent_id>`, the gateway routes it to the target agent through
its virtual subscriber, and the agent's reply comes back on the same HTTP response —
no SSE, no polling.

## Overview [#overview]

`message/send` is the workhorse of the A2A connector. You address an agent by ID in
the URL, put a standard JSON-RPC 2.0 envelope in the body, and read the reply
synchronously. The gateway is **method-agnostic**: it forwards whatever `method` you
send — `message/send`, `tasks/get`, `tasks/cancel`, or your own `custom/action` — and
returns the agent's response as-is. Use synchronous messaging whenever you want a
single answer back in one round-trip; switch to
[streaming](/aiway/a2a/streaming) when the agent produces incremental
output over time.

## How it works [#how-it-works]

A synchronous call travels from the caller through the gateway and the agent's
virtual subscriber to the plain HTTP agent, then the reply retraces the path.

<Mermaid
  chart="`
sequenceDiagram
participant C as Caller
participant G as A2A Gateway<br/>:9090
participant V as Virtual Subscriber
participant A as Agent (HTTP)
C->>G: POST /a2a/agent-1<br/>{&#x22;method&#x22;:&#x22;message/send&#x22;}
G->>V: Query<br/>_AGENTS_.agents/agent-1
V->>A: HTTP POST (JSON-RPC 2.0)<br/>+ X-KubeMQ-Caller-ID
A-->>V: JSON-RPC response
V-->>G: pb.Response (Executed:true)
G-->>C: agent reply (as-is)
`"
/>

*The gateway proxies one request to one agent and relays the reply on the same HTTP response.*

The gateway validates the agent ID and `Content-Type`, reads the timeout from
`params.configuration.timeout` (falling back to `DefaultTimeoutSeconds`, capped at
`MaxTimeoutSeconds`), packs the caller's `X-*` headers, and issues a Query on
`_AGENTS_.agents/<agent_id>`. The agent's virtual subscriber unpacks the request,
POSTs it to the agent's registered URL, and relays the response.

## Basic message/send [#basic-messagesend]

Send a text message and read the synchronous reply. The agent receives the full
JSON-RPC envelope and returns its result.

<Tabs groupId="language" items="['curl','C#','Go','Java','Python','TypeScript']">
  <Tab value="curl">
    ```bash
    curl -X POST http://localhost:9090/a2a/echo-agent-01 \
      -H "Content-Type: application/json" \
      -d '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "message/send",
        "params": {
          "message": {
            "parts": [{"text": "Hello, agent!"}]
          }
        }
      }'
    ```
  </Tab>

  <Tab value="C#">
    ```csharp
    const string KubeMqUrl = "http://localhost:9090";
    const string AgentId = "echo-agent-01";

    var payload = new JsonObject
    {
        ["jsonrpc"] = "2.0",
        ["id"] = 1,
        ["method"] = "message/send",
        ["params"] = new JsonObject
        {
            ["message"] = new JsonObject
            {
                ["parts"] = new JsonArray(new JsonObject { ["text"] = "Hello, agent!" })
            }
        }
    };

    using var client = new HttpClient();
    var resp = await client.PostAsync(
        $"{KubeMqUrl}/a2a/{AgentId}",
        new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"));

    Console.WriteLine($"Status: {(int)resp.StatusCode}");
    var body = await resp.Content.ReadAsStringAsync();
    var data = JsonNode.Parse(body)!;
    Console.WriteLine(JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true }));

    if (data["result"] != null)
        Console.WriteLine("\nBasic send completed successfully!");
    ```
  </Tab>

  <Tab value="Go">
    ```go
    const (
        kubemqURL = "http://localhost:9090"
        agentID   = "echo-agent-01"
    )

    payload := map[string]interface{}{
        "jsonrpc": "2.0",
        "id":      1,
        "method":  "message/send",
        "params": map[string]interface{}{
            "message": map[string]interface{}{
                "parts": []map[string]interface{}{{"text": "Hello, agent!"}},
            },
        },
    }

    data, _ := json.Marshal(payload)
    resp, err := http.Post(kubemqURL+"/a2a/"+agentID, "application/json", bytes.NewReader(data))
    if err != nil {
        fmt.Fprintf(os.Stderr, "Request failed: %v\n", err)
        os.Exit(1)
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Printf("Status: %d\n", resp.StatusCode)
    fmt.Println(string(body))
    ```
  </Tab>

  <Tab value="Java">
    ```java
    static final String KUBEMQ_URL = "http://localhost:9090";
    static final String AGENT_ID = "echo-agent-01";
    static final ObjectMapper MAPPER = new ObjectMapper();

    var payload = Map.of(
        "jsonrpc", "2.0",
        "id", 1,
        "method", "message/send",
        "params", Map.of(
            "message", Map.of(
                "parts", List.of(Map.of("text", "Hello, agent!"))
            )
        )
    );

    var client = HttpClient.newHttpClient();
    var req = HttpRequest.newBuilder()
        .uri(URI.create(KUBEMQ_URL + "/a2a/" + AGENT_ID))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(MAPPER.writeValueAsString(payload)))
        .build();
    var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
    System.out.println("Status: " + resp.statusCode());

    var data = MAPPER.readTree(resp.body());
    System.out.println(MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(data));
    ```
  </Tab>

  <Tab value="Python">
    ```python
    import httpx

    KUBEMQ_URL = "http://localhost:9090"
    AGENT_ID = "echo-agent-01"

    payload = {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "message/send",
        "params": {
            "message": {
                "parts": [{"text": "Hello, agent!"}],
            },
        },
    }

    async with httpx.AsyncClient() as client:
        resp = await client.post(f"{KUBEMQ_URL}/a2a/{AGENT_ID}", json=payload)
        print(f"Status: {resp.status_code}")
        data = resp.json()
        print(json.dumps(data, indent=2))
        assert "result" in data
    ```
  </Tab>

  <Tab value="TypeScript">
    ```typescript
    const KUBEMQ_URL = "http://localhost:9090";
    const AGENT_ID = "echo-agent-01";

    const request = {
      jsonrpc: "2.0",
      id: 1,
      method: "message/send",
      params: {
        message: {
          parts: [{ text: "Hello, agent!" }],
        },
      },
    };

    const resp = await fetch(`${KUBEMQ_URL}/a2a/${AGENT_ID}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(request),
    });

    const data = await resp.json();
    console.log("Response:", JSON.stringify(data, null, 2));
    ```
  </Tab>
</Tabs>

## Context IDs [#context-ids]

Set `params.contextId` to correlate a sequence of messages with the same agent — for
conversation threading or session management. The gateway forwards it to the agent,
which echoes it back so callers can confirm the correlation.

<Tabs groupId="language" items="['curl','C#','Go','Java','Python','TypeScript']">
  <Tab value="curl">
    ```bash
    curl -X POST http://localhost:9090/a2a/context-agent-01 \
      -H "Content-Type: application/json" \
      -d '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "message/send",
        "params": {
          "message": {"parts": [{"text": "Track this request"}]},
          "contextId": "ctx-001"
        }
      }'
    ```
  </Tab>

  <Tab value="C#">
    ```csharp
    var payload = new JsonObject
    {
        ["jsonrpc"] = "2.0",
        ["id"] = 1,
        ["method"] = "message/send",
        ["params"] = new JsonObject
        {
            ["message"] = new JsonObject
            {
                ["parts"] = new JsonArray(new JsonObject { ["text"] = "Track this request" })
            },
            ["contextId"] = "ctx-001"
        }
    };

    using var client = new HttpClient();
    var resp = await client.PostAsync(
        $"{KubeMqUrl}/a2a/context-agent-01",
        new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"));

    var body = await resp.Content.ReadAsStringAsync();
    var data = JsonNode.Parse(body)!;
    var returnedCtx = data["result"]?["contextId"]?.GetValue<string>();
    Console.WriteLine($"Received contextId: {returnedCtx}");
    ```
  </Tab>

  <Tab value="Go">
    ```go
    payload := map[string]interface{}{
        "jsonrpc": "2.0",
        "id":      1,
        "method":  "message/send",
        "params": map[string]interface{}{
            "message":   map[string]interface{}{"parts": []map[string]interface{}{{"text": "Track this request"}}},
            "contextId": "ctx-001",
        },
    }

    data, _ := json.Marshal(payload)
    resp, _ := http.Post("http://localhost:9090/a2a/context-agent-01", "application/json", bytes.NewReader(data))
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    var result map[string]interface{}
    json.Unmarshal(body, &result)
    if r, ok := result["result"].(map[string]interface{}); ok {
        fmt.Printf("Received contextId: %v\n", r["contextId"])
    }
    ```
  </Tab>

  <Tab value="Java">
    ```java
    var payload = Map.of(
        "jsonrpc", "2.0",
        "id", 1,
        "method", "message/send",
        "params", Map.of(
            "message", Map.of("parts", List.of(Map.of("text", "Track this request"))),
            "contextId", "ctx-001"
        )
    );

    var client = HttpClient.newHttpClient();
    var req = HttpRequest.newBuilder()
        .uri(URI.create(KUBEMQ_URL + "/a2a/context-agent-01"))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(MAPPER.writeValueAsString(payload)))
        .build();
    var resp = client.send(req, HttpResponse.BodyHandlers.ofString());

    var data = MAPPER.readTree(resp.body());
    var returnedCtx = data.path("result").path("contextId").asText(null);
    System.out.println("Received contextId: " + returnedCtx);
    ```
  </Tab>

  <Tab value="Python">
    ```python
    payload = {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "message/send",
        "params": {
            "message": {"parts": [{"text": "Track this request"}]},
            "contextId": "ctx-001",
        },
    }

    async with httpx.AsyncClient() as client:
        resp = await client.post("http://localhost:9090/a2a/context-agent-01", json=payload)
        data = resp.json()
        returned_ctx = data.get("result", {}).get("contextId")
        print(f"Received contextId: {returned_ctx}")
    ```
  </Tab>

  <Tab value="TypeScript">
    ```typescript
    const contextId = "ctx-001";

    const request = {
      jsonrpc: "2.0",
      id: 1,
      method: "message/send",
      params: {
        message: { parts: [{ text: "Track this request" }] },
        contextId,
      },
    };

    const resp = await fetch(`${KUBEMQ_URL}/a2a/context-agent-01`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(request),
    });

    const data = await resp.json();
    const echoedCtx = data.result?.contextId;
    console.log(`Echoed contextId: ${echoedCtx}`);
    ```
  </Tab>
</Tabs>

## Custom methods [#custom-methods]

The gateway forwards any JSON-RPC `method` name to the agent unchanged — standard A2A
methods like `tasks/get` and `tasks/cancel`, or your own `custom/action`. KubeMQ does
not interpret the method; the agent decides how to handle it.

<Tabs groupId="language" items="['curl','C#','Go','Java','Python','TypeScript']">
  <Tab value="curl">
    ```bash
    curl -X POST http://localhost:9090/a2a/custom-method-agent-01 \
      -H "Content-Type: application/json" \
      -d '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "custom/action",
        "params": {"data": "custom-payload"}
      }'
    ```
  </Tab>

  <Tab value="C#">
    ```csharp
    var payload = new JsonObject
    {
        ["jsonrpc"] = "2.0",
        ["id"] = 1,
        ["method"] = "custom/action",
        ["params"] = new JsonObject { ["data"] = "custom-payload" }
    };

    using var client = new HttpClient();
    var resp = await client.PostAsync(
        $"{KubeMqUrl}/a2a/custom-method-agent-01",
        new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"));

    var body = await resp.Content.ReadAsStringAsync();
    var data = JsonNode.Parse(body)!;
    var handledMethod = data["result"]?["handled_method"]?.GetValue<string>();
    Console.WriteLine($"Handled method: {handledMethod}");
    ```
  </Tab>

  <Tab value="Go">
    ```go
    payload := map[string]interface{}{
        "jsonrpc": "2.0",
        "id":      1,
        "method":  "custom/action",
        "params":  map[string]interface{}{"data": "custom-payload"},
    }

    data, _ := json.Marshal(payload)
    resp, _ := http.Post("http://localhost:9090/a2a/custom-method-agent-01", "application/json", bytes.NewReader(data))
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    var result map[string]interface{}
    json.Unmarshal(body, &result)
    r, _ := result["result"].(map[string]interface{})
    fmt.Printf("Handled method: %v\n", r["handled_method"])
    ```
  </Tab>

  <Tab value="Java">
    ```java
    var payload = Map.of(
        "jsonrpc", "2.0",
        "id", 1,
        "method", "custom/action",
        "params", Map.of("data", "custom-payload")
    );

    var client = HttpClient.newHttpClient();
    var req = HttpRequest.newBuilder()
        .uri(URI.create(KUBEMQ_URL + "/a2a/custom-method-agent-01"))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(MAPPER.writeValueAsString(payload)))
        .build();
    var resp = client.send(req, HttpResponse.BodyHandlers.ofString());

    var data = MAPPER.readTree(resp.body());
    System.out.println("Handled method: " + data.path("result").path("handled_method").asText());
    ```
  </Tab>

  <Tab value="Python">
    ```python
    payload = {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "custom/action",
        "params": {"data": "custom-payload"},
    }

    async with httpx.AsyncClient() as client:
        resp = await client.post("http://localhost:9090/a2a/custom-method-agent-01", json=payload)
        data = resp.json()
        result = data.get("result", {})
        print(f"Handled method: {result.get('handled_method')}")
    ```
  </Tab>

  <Tab value="TypeScript">
    ```typescript
    async function sendRpc(method: string, params: unknown) {
      const request = { jsonrpc: "2.0", id: Date.now(), method, params };
      const resp = await fetch(`${KUBEMQ_URL}/a2a/custom-method-agent-01`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(request),
      });
      return resp.json();
    }

    const custom = await sendRpc("custom/action", { data: "custom-payload" });
    console.log("Response:", JSON.stringify(custom, null, 2));
    ```
  </Tab>
</Tabs>

## Header forwarding [#header-forwarding]

Caller request headers prefixed with `X-` are forwarded to the agent; hop-by-hop and
sensitive headers (`Authorization`, `Cookie`, `X-Forwarded-For`, and others) are
stripped. The gateway always injects `X-KubeMQ-Caller-ID` so the agent knows which
client originated the call.

<Tabs groupId="language" items="['curl','C#','Go','Java','Python','TypeScript']">
  <Tab value="curl">
    ```bash
    curl -X POST http://localhost:9090/a2a/header-agent-01 \
      -H "Content-Type: application/json" \
      -H "X-Custom-Header: my-custom-value" \
      -d '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "message/send",
        "params": {
          "message": {"parts": [{"text": "Check my headers"}]}
        }
      }'
    ```
  </Tab>

  <Tab value="C#">
    ```csharp
    var payload = new JsonObject
    {
        ["jsonrpc"] = "2.0",
        ["id"] = 1,
        ["method"] = "message/send",
        ["params"] = new JsonObject
        {
            ["message"] = new JsonObject
            {
                ["parts"] = new JsonArray(new JsonObject { ["text"] = "Check my headers" })
            }
        }
    };

    using var client = new HttpClient();
    var request = new HttpRequestMessage(HttpMethod.Post, $"{KubeMqUrl}/a2a/header-agent-01")
    {
        Content = new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json")
    };
    request.Headers.Add("X-Custom-Header", "my-custom-value");

    var resp = await client.SendAsync(request);
    var body = await resp.Content.ReadAsStringAsync();
    var data = JsonNode.Parse(body)!;
    var received = data["result"]?["received_headers"];
    Console.WriteLine($"Forwarded headers: {received?.ToJsonString()}");
    ```
  </Tab>

  <Tab value="Go">
    ```go
    payload := map[string]interface{}{
        "jsonrpc": "2.0",
        "id":      1,
        "method":  "message/send",
        "params": map[string]interface{}{
            "message": map[string]interface{}{
                "parts": []map[string]interface{}{{"text": "Check my headers"}},
            },
        },
    }

    data, _ := json.Marshal(payload)
    req, _ := http.NewRequest("POST", "http://localhost:9090/a2a/header-agent-01", bytes.NewReader(data))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-Custom-Header", "my-custom-value")

    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    var result map[string]interface{}
    json.Unmarshal(body, &result)
    received, _ := result["result"].(map[string]interface{})["received_headers"].(map[string]interface{})
    fmt.Printf("Forwarded headers: %v\n", received)
    ```
  </Tab>

  <Tab value="Java">
    ```java
    var payload = Map.of(
        "jsonrpc", "2.0",
        "id", 1,
        "method", "message/send",
        "params", Map.of(
            "message", Map.of("parts", List.of(Map.of("text", "Check my headers")))
        )
    );

    var client = HttpClient.newHttpClient();
    var req = HttpRequest.newBuilder()
        .uri(URI.create(KUBEMQ_URL + "/a2a/header-agent-01"))
        .header("Content-Type", "application/json")
        .header("X-Custom-Header", "my-custom-value")
        .POST(HttpRequest.BodyPublishers.ofString(MAPPER.writeValueAsString(payload)))
        .build();
    var resp = client.send(req, HttpResponse.BodyHandlers.ofString());

    var data = MAPPER.readTree(resp.body());
    System.out.println("Forwarded headers: " + data.path("result").path("received_headers"));
    ```
  </Tab>

  <Tab value="Python">
    ```python
    payload = {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "message/send",
        "params": {
            "message": {"parts": [{"text": "Check my headers"}]},
        },
    }

    async with httpx.AsyncClient() as client:
        resp = await client.post(
            "http://localhost:9090/a2a/header-agent-01",
            json=payload,
            headers={"X-Custom-Header": "my-custom-value"},
        )
        data = resp.json()
        received = data.get("result", {}).get("received_headers", {})
        print(f"Forwarded headers: {received}")
    ```
  </Tab>

  <Tab value="TypeScript">
    ```typescript
    const request = {
      jsonrpc: "2.0",
      id: 1,
      method: "message/send",
      params: {
        message: { parts: [{ text: "Check my headers" }] },
      },
    };

    const resp = await fetch(`${KUBEMQ_URL}/a2a/header-agent-01`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Custom-Header": "test-value-123",
      },
      body: JSON.stringify(request),
    });

    const data = await resp.json();
    const headers = data.result?.received_headers || {};
    console.log(`X-Custom-Header:    ${headers["x-custom-header"] || "(not forwarded)"}`);
    console.log(`X-KubeMQ-Caller-ID: ${headers["x-kubemq-caller-id"] || "(not present)"}`);
    ```
  </Tab>
</Tabs>

## Concurrent requests [#concurrent-requests]

Multiple simultaneous calls to the same agent are processed in parallel up to the
per-agent concurrency cap (`AgentMaxConcurrency`, default 100). Requests beyond the
cap are rejected immediately with a "server busy" error instead of being queued — see
[Concurrency & limits](/aiway/a2a/guides/concurrency).

<Tabs groupId="language" items="['curl','C#','Go','Java','Python','TypeScript']">
  <Tab value="curl">
    ```bash
    # Fire 20 requests in parallel against the same agent
    for i in $(seq 1 20); do
      curl -s -X POST http://localhost:9090/a2a/concurrent-agent-01 \
        -H "Content-Type: application/json" \
        -d "{\"jsonrpc\":\"2.0\",\"id\":$i,\"method\":\"message/send\",\"params\":{\"message\":{\"parts\":[{\"text\":\"Request #$i\"}]}}}" &
    done
    wait
    ```
  </Tab>

  <Tab value="C#">
    ```csharp
    const int NumRequests = 20;

    async Task<JsonNode?> SendRequest(HttpClient httpClient, int requestId)
    {
        var payload = new JsonObject
        {
            ["jsonrpc"] = "2.0",
            ["id"] = requestId,
            ["method"] = "message/send",
            ["params"] = new JsonObject
            {
                ["message"] = new JsonObject
                {
                    ["parts"] = new JsonArray(new JsonObject { ["text"] = $"Request #{requestId}" })
                }
            }
        };
        var resp = await httpClient.PostAsync(
            $"{KubeMqUrl}/a2a/concurrent-agent-01",
            new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"));
        var body = await resp.Content.ReadAsStringAsync();
        return JsonNode.Parse(body);
    }

    using var client = new HttpClient();
    var tasks = Enumerable.Range(1, NumRequests).Select(i => SendRequest(client, i)).ToArray();
    var results = await Task.WhenAll(tasks);

    var successes = results.Count(r => r?["result"] != null);
    Console.WriteLine($"Successes: {successes} / {NumRequests}");
    ```
  </Tab>

  <Tab value="Go">
    ```go
    const numRequests = 20

    results := make(chan map[string]interface{}, numRequests)
    var wg sync.WaitGroup

    for i := 1; i <= numRequests; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            payload := map[string]interface{}{
                "jsonrpc": "2.0", "id": id, "method": "message/send",
                "params": map[string]interface{}{
                    "message": map[string]interface{}{
                        "parts": []map[string]interface{}{{"text": fmt.Sprintf("Request #%d", id)}},
                    },
                },
            }
            data, _ := json.Marshal(payload)
            resp, err := http.Post("http://localhost:9090/a2a/concurrent-agent-01", "application/json", bytes.NewReader(data))
            if err != nil {
                results <- nil
                return
            }
            defer resp.Body.Close()
            body, _ := io.ReadAll(resp.Body)
            var r map[string]interface{}
            json.Unmarshal(body, &r)
            results <- r
        }(i)
    }
    wg.Wait()
    close(results)

    successes := 0
    for r := range results {
        if r != nil {
            if _, ok := r["result"]; ok {
                successes++
            }
        }
    }
    fmt.Printf("Successes: %d / %d\n", successes, numRequests)
    ```
  </Tab>

  <Tab value="Java">
    ```java
    static final int NUM_REQUESTS = 20;

    var client = HttpClient.newHttpClient();
    List<CompletableFuture<HttpResponse<String>>> futures = new ArrayList<>();
    for (int i = 1; i <= NUM_REQUESTS; i++) {
        var payload = Map.of(
            "jsonrpc", "2.0", "id", i, "method", "message/send",
            "params", Map.of(
                "message", Map.of("parts", List.of(Map.of("text", "Request #" + i)))
            )
        );
        var req = HttpRequest.newBuilder()
            .uri(URI.create(KUBEMQ_URL + "/a2a/concurrent-agent-01"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(MAPPER.writeValueAsString(payload)))
            .build();
        futures.add(client.sendAsync(req, HttpResponse.BodyHandlers.ofString()));
    }

    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

    int successes = 0;
    for (var future : futures) {
        var data = MAPPER.readTree(future.get().body());
        if (data.has("result")) successes++;
    }
    System.out.println("Successes: " + successes + " / " + NUM_REQUESTS);
    ```
  </Tab>

  <Tab value="Python">
    ```python
    NUM_REQUESTS = 20

    async def send_request(client: httpx.AsyncClient, request_id: int) -> dict:
        payload = {
            "jsonrpc": "2.0",
            "id": request_id,
            "method": "message/send",
            "params": {
                "message": {"parts": [{"text": f"Request #{request_id}"}]},
            },
        }
        resp = await client.post("http://localhost:9090/a2a/concurrent-agent-01", json=payload)
        return resp.json()

    async with httpx.AsyncClient() as client:
        tasks = [send_request(client, i) for i in range(1, NUM_REQUESTS + 1)]
        results = await asyncio.gather(*tasks, return_exceptions=True)

    successes = sum(1 for r in results if isinstance(r, dict) and "result" in r)
    print(f"Successes: {successes} / {NUM_REQUESTS}")
    ```
  </Tab>

  <Tab value="TypeScript">
    ```typescript
    const NUM_REQUESTS = 20;

    async function sendRequest(id: number): Promise<boolean> {
      const request = {
        jsonrpc: "2.0",
        id,
        method: "message/send",
        params: { message: { parts: [{ text: `Request ${id}` }] } },
      };
      try {
        const resp = await fetch(`${KUBEMQ_URL}/a2a/concurrent-agent-01`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(request),
        });
        const data = await resp.json();
        return !!data.result;
      } catch {
        return false;
      }
    }

    const promises = Array.from({ length: NUM_REQUESTS }, (_, i) => sendRequest(i + 1));
    const results = await Promise.all(promises);
    const succeeded = results.filter((r) => r).length;
    console.log(`Succeeded: ${succeeded} / ${NUM_REQUESTS}`);
    ```
  </Tab>
</Tabs>

## Parameters [#parameters]

The body is a standard JSON-RPC 2.0 request. Method-specific fields live under
`params`.

| Field                          | Type             | Required           | Default                       | Description                                                                                                          |
| ------------------------------ | ---------------- | ------------------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `jsonrpc`                      | string           | yes                | —                             | Must be `"2.0"`.                                                                                                     |
| `id`                           | string \| number | yes                | —                             | Request identifier; echoed back on the response.                                                                     |
| `method`                       | string           | yes                | —                             | Any JSON-RPC method name, e.g. `message/send`, `tasks/get`, or a custom one. Empty method is rejected with `-32600`. |
| `params.message.parts`         | array            | for `message/send` | —                             | Message content parts, each with a `text` (or other typed) field.                                                    |
| `params.contextId`             | string           | no                 | —                             | Correlation ID forwarded to the agent for conversation threading.                                                    |
| `params.configuration.timeout` | number           | no                 | `DefaultTimeoutSeconds` (300) | Per-request timeout in seconds, capped at `MaxTimeoutSeconds` (3600).                                                |

The URL path segment `<agent_id>` selects the target agent and is validated against
the registry before routing.

## Response [#response]

On success the gateway returns the agent's JSON-RPC `result` verbatim (the echo agent
mirrors the request under `result.echo`):

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "echo": {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "message/send",
      "params": {
        "message": { "parts": [{ "text": "Hello, agent!" }] }
      }
    }
  }
}
```

A response carrying an `error` object signals a problem. KubeMQ distinguishes two
failure classes:

* **Transport errors** — the agent never processed the request (unreachable, timeout,
  `502`/`503`/`504`, or an oversized response). Internally these surface as
  `Executed: false`; safe to retry.
* **Application errors** — the agent processed the request and returned a JSON-RPC
  error in its response body (`Executed: true`). Retrying without changing the request
  usually will not help.

A timeout, for example, returns:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32001,
    "message": "agent timeout: echo-agent-01"
  }
}
```

See [Error handling](/aiway/a2a/error-handling) for the full code list and
retry guidance.

## Related [#related]

<Cards>
  <Card title="Streaming (SSE)" href="/aiway/a2a/streaming" description="Use message/stream when the agent emits incremental task events over time." />

  <Card title="Error handling" href="/aiway/a2a/error-handling" description="A2A error codes and how to tell transport failures from application errors." />

  <Card title="Concurrency & limits" href="/aiway/a2a/guides/concurrency" description="Per-agent concurrency cap, response-size limit, and the timeout chain." />

  <Card title="Reference" href="/aiway/a2a/reference" description="Endpoint, method, and error-code tables plus the AgentCard schema." />
</Cards>
