> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coval.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Chat A2A (JSON-RPC)

> Connect to text chat agents that speak the A2A v2 JSON-RPC protocol

## Overview

Chat A2A agents communicate over HTTPS using the A2A (Agent-to-Agent) v2 JSON-RPC protocol. Unlike the standard [Chat (HTTP)](/concepts/agents/connections/chat) integration which uses a free-form request/response shape, A2A defines a structured `message/send` envelope with stateful conversation tracking via `contextId` and `taskId`, and an explicit end-of-conversation signal via `result.status.state`.

**When to use Chat A2A instead of Chat (HTTP) or [Chat WebSocket](/concepts/agents/connections/chat-websocket):**

* Your agent implements the A2A v2 JSON-RPC specification
* Your agent requires OAuth2 token exchange before accepting messages
* Your agent uses `contextId` / `taskId` to maintain conversation state across turns
* Your agent signals end-of-conversation through a structured status field rather than a chat reply

## Conversation Flow

A2A conversations follow a fixed three-phase shape:

1. **Initialization** — Coval calls your `initialization_endpoint` (typically OAuth2 `client_credentials`) and captures the returned access token for use as the `Authorization: Bearer …` header on subsequent requests.
2. **Message turns** — Coval sends each persona message to your `chat_endpoint` as a JSON-RPC `message/send` request. After the first turn, every request echoes back the `contextId` and `taskId` returned by the previous response so your agent can maintain conversation state.
3. **Termination** — When your agent's response sets `result.status.state` to `completed` or `failed` (configurable), Coval shuts down the simulation cleanly and finalizes the transcript.

## Configuration

| Field                       | Required | Description                                                                                                                                    |
| --------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Chat Endpoint               | Yes      | URL Coval `POST`s each JSON-RPC `message/send` request to                                                                                      |
| Initialization Endpoint     | No       | URL Coval calls once per simulation to obtain credentials (e.g., OAuth2 token endpoint)                                                        |
| Initialization Payload      | No       | Body sent to the initialization endpoint. Stored as JSON; encoded based on Initialization Content-Type                                         |
| Initialization Content-Type | No       | `application/x-www-form-urlencoded` (default) or `application/json`                                                                            |
| Authorization Header        | No       | Static auth value. Overridden automatically when the initialization response contains `access_token`                                           |
| Custom Headers              | No       | Additional headers sent on every chat request                                                                                                  |
| Response Message Path       | No       | Dot-notation path to the agent's reply text. Default: `result.artifacts.0.parts.0.text`                                                        |
| Response State Extraction   | No       | JSON paths to the `contextId` and `taskId` Coval echoes back on each turn. Default: `{"contextId": "result.contextId", "taskId": "result.id"}` |
| End State Path              | No       | Dot-notation path to the conversation status field. Default: `result.status.state`                                                             |
| End State Values            | No       | List of status values that terminate the conversation. Default: `["completed", "failed"]`                                                      |
| Custom Data                 | No       | Arbitrary JSON object merged into `params.metadata` on every chat request (e.g., user identifiers required by your agent)                      |

## Initialization

If your agent requires an access token, configure the initialization endpoint and payload. Coval calls it once at the start of each simulation, captures `access_token` from the response, and uses it as `Authorization: Bearer …` for every subsequent chat request.

<Accordion title="Example: OAuth2 client_credentials initialization">
  **Configuration:**

  ```
  Initialization Endpoint:     https://auth.example.com/oauth_token.do
  Initialization Content-Type: application/x-www-form-urlencoded
  Initialization Payload:      {"grant_type":"client_credentials","client_id":"...","client_secret":"..."}
  ```

  **Request Coval sends:**

  ```http theme={null}
  POST /oauth_token.do HTTP/1.1
  Host: auth.example.com
  Content-Type: application/x-www-form-urlencoded

  grant_type=client_credentials&client_id=...&client_secret=...
  ```

  **Response your auth endpoint should return:**

  ```json theme={null}
  {
    "access_token": "eyJhbGc...",
    "token_type": "Bearer",
    "expires_in": 1799
  }
  ```

  Coval extracts `access_token` and uses it as `Authorization: Bearer eyJhbGc...` for every chat request in the simulation. Other fields (`token_type`, `expires_in`, `scope`) are captured but not interpreted.
</Accordion>

If you don't need authentication, leave Initialization Endpoint blank — Coval will skip the init phase and call your chat endpoint directly.

## Message Format

### Sending Messages (Coval to Agent)

Each persona turn is sent as a JSON-RPC 2.0 `message/send` request. Coval auto-generates a fresh `messageId` (UUIDv4) per turn and echoes the previous turn's `contextId` and `taskId`.

<Accordion title="Example: Turn 1 request envelope">
  ```json theme={null}
  {
    "jsonrpc": "2.0",
    "id": "<simulation_output_id>",
    "method": "message/send",
    "params": {
      "message": {
        "role": "user",
        "kind": "message",
        "messageId": "b1a2f3c4-d5e6-4a7b-8c9d-0e1f2a3b4c5d",
        "contextId": "",
        "taskId": "",
        "parts": [
          {"kind": "text", "text": "Hi, I'd like to reschedule my appointment for next week."}
        ]
      },
      "contextId": "",
      "taskId": "",
      "metadata": {
        "user_reference": "u_4f8c1a2b"
      }
    }
  }
  ```

  On turn 1, `contextId` and `taskId` are empty strings — your agent should treat that as a new conversation and return its own values in the response. The `metadata` object is whatever you configured under Custom Data.
</Accordion>

<Accordion title="Example: Turn 2+ request envelope (with echoed state)">
  ```json theme={null}
  {
    "jsonrpc": "2.0",
    "id": "<simulation_output_id>",
    "method": "message/send",
    "params": {
      "message": {
        "role": "user",
        "kind": "message",
        "messageId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f",
        "contextId": "ctx_a1b2c3d4e5f6",
        "taskId": "task_x9y8z7w6v5u4",
        "parts": [
          {"kind": "text", "text": "Tuesday at 2pm if possible."}
        ]
      },
      "contextId": "ctx_a1b2c3d4e5f6",
      "taskId": "task_x9y8z7w6v5u4",
      "metadata": {
        "user_reference": "u_4f8c1a2b"
      }
    }
  }
  ```

  `contextId` and `taskId` are present at both `params.*` and `params.message.*` for compatibility with strict A2A v2 implementations.
</Accordion>

### Receiving Messages (Agent to Coval)

Coval extracts the assistant text from the path configured under Response Message Path (default: `result.artifacts.0.parts.0.text`) and the conversation state from Response State Extraction.

<Accordion title="Example: Expected response shape">
  ```json theme={null}
  {
    "jsonrpc": "2.0",
    "id": "<simulation_output_id>",
    "result": {
      "id": "task_x9y8z7w6v5u4",
      "kind": "task",
      "contextId": "ctx_a1b2c3d4e5f6",
      "status": {
        "state": "working",
        "timestamp": "2026-01-15T14:30:00.000Z"
      },
      "artifacts": [
        {
          "artifactId": "art_p1q2r3s4t5u6",
          "parts": [
            {"kind": "text", "text": "Sure — I can help with that. What date and time work best for you?"}
          ]
        }
      ]
    }
  }
  ```

  From this response Coval extracts:

  * **Reply text** from `result.artifacts.0.parts.0.text` → played back to the persona
  * **`contextId`** from `result.contextId` → echoed on the next request
  * **`taskId`** from `result.id` → echoed on the next request
  * **End-state** from `result.status.state` → if `"completed"` or `"failed"`, simulation terminates
</Accordion>

<Warning>
  **A2A is text-only.** All content the persona needs to act on must be serialized into `parts[*].text`. If your agent embeds rich content (lists, tables, attachments) as a structured non-text part or relies on a UI widget rendered outside the JSON-RPC payload, the simulator will not see it and the persona may loop or stall waiting for context that never arrives.
</Warning>

If your agent returns multiple parts on the same artifact, Coval automatically picks the longest text part (some A2A implementations return a short header in `parts[0]` and the full content in `parts[1]`).

## State Extraction

Response State Extraction tells Coval where to find the `contextId` and `taskId` your agent returns on each turn so they can be echoed back on the next request. The defaults match the A2A v2 specification:

```json theme={null}
{
  "contextId": "result.contextId",
  "taskId": "result.id"
}
```

If your agent returns these values at different paths, override the map. The keys must be `contextId` and `taskId` (those are the slots Coval echoes back); the values are the dot-notation paths into your agent's response. Once captured, both values are sent on the next request at `params.contextId`, `params.taskId`, `params.message.contextId`, and `params.message.taskId` for compatibility with strict A2A v2 implementations.

## End-State Detection

Coval checks every response against End State Path / End State Values. When the value at End State Path matches one of End State Values, the simulation finalizes cleanly after the current turn.

The defaults (`result.status.state` ∈ `["completed", "failed"]`) match the A2A v2 specification. Override them if your agent uses different terminal states:

```
End State Path:   result.status.lifecycle
End State Values: ["resolved", "abandoned"]
```

## Setup Instructions

<Steps>
  <Step title="Create the agent">
    Create a new agent from the **Agents** tab in the [Coval platform](https://app.coval.dev/agents/create), or via the [Coval API](/api-reference/v1/agents/agents/connect-an-agent) with `model_type: "MODEL_TYPE_CHAT_A2A"` and the metadata fields described under Configuration above.
  </Step>

  <Step title="Configure initialization">
    If your agent requires authentication, set the initialization endpoint, payload, and content type. Otherwise leave them blank.
  </Step>

  <Step title="Pass user context via Custom Data">
    Any per-conversation identifiers your agent expects (user IDs, channel IDs, locale) go into Custom Data — they're merged into `params.metadata` on every chat request.
  </Step>

  <Step title="Verify response shape">
    If your agent's response paths differ from the defaults, set Response Message Path, Response State Extraction, End State Path, and End State Values to match what your agent actually returns.
  </Step>

  <Step title="Test with a single test case">
    Create a test set with one test case and launch a simulation to verify connectivity, state echoing, and end-state detection before scaling up.
  </Step>
</Steps>

## Common Patterns

<Accordion title="Pattern 1: OAuth2 client_credentials with default A2A v2 paths">
  ```
  Chat Endpoint:               https://api.example.com/a2a/v2/agent/id/<agent_id>
  Initialization Endpoint:     https://auth.example.com/oauth_token.do
  Initialization Content-Type: application/x-www-form-urlencoded
  Initialization Payload:      {"grant_type":"client_credentials","client_id":"...","client_secret":"..."}
  Custom Data:                 {"user_id": "<user_record_id>"}
  ```

  All response paths use defaults — no further configuration needed.
</Accordion>

<Accordion title="Pattern 2: Static bearer token, no init endpoint">
  ```
  Chat Endpoint:        https://api.example.com/a2a/v2/agent/id/<agent_id>
  Authorization Header: Bearer <long-lived-token>
  ```

  No initialization request — Coval sends the static `Authorization` header on every chat request. Use this when your agent accepts a long-lived API token instead of requiring an OAuth2 exchange per simulation.
</Accordion>

## Troubleshooting

### Initialization Failures

**"Failed A2A initialization request"**

* Verify the initialization endpoint URL is correct and reachable from Coval servers
* Check that the content type matches what the endpoint expects (form vs JSON)
* Confirm the credentials in the initialization payload are valid

**Auth works on init but chat requests return 401**

* Confirm the init response includes a top-level `access_token` field — that's what Coval extracts
* If your token field has a different name, set it via Authorization Header directly and skip the init endpoint

### State Not Persisting Across Turns

* Check that Response State Extraction paths actually resolve in your agent's response (test with a direct request)
* Confirm your agent reads `contextId` / `taskId` from the request and uses them to look up conversation history
* A2A v2 expects state at both `params.contextId` and `params.message.contextId` — Coval sends both

### Conversation Never Ends

* Verify End State Path resolves to a non-null value in your agent's responses
* Confirm at least one End State Value matches what your agent actually returns when the conversation completes
* If your agent never sends a terminal state, configure a reasonable test-case length so the simulation isn't open-ended

### Persona Loops or Stalls Mid-Conversation

* Check that all content the persona needs is serialized as plain text in `parts[*].text`
* If your agent produces a short header in `parts[0]` and the full content in `parts[1]`, Coval picks the longest — but neither should reference content that lives outside the JSON-RPC payload (e.g., "see the attached list")

## Technical Requirements

| Requirement         | Details                                                                                  |
| ------------------- | ---------------------------------------------------------------------------------------- |
| Protocol            | A2A v2 JSON-RPC over HTTPS                                                               |
| Method              | `message/send`                                                                           |
| Content type (chat) | `application/json`                                                                       |
| Content type (init) | Configurable (`application/x-www-form-urlencoded` default, `application/json` supported) |
| Accessibility       | Endpoints must be publicly accessible from Coval servers                                 |
| Payload size        | Standard HTTP limits apply                                                               |
