Skip to main content

Overview

Chat A2A agents communicate over HTTPS using the A2A (Agent-to-Agent) v2 JSON-RPC protocol. Unlike the standard Chat (HTTP) 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:
  • 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

FieldRequiredDescription
Chat EndpointYesURL Coval POSTs each JSON-RPC message/send request to
Initialization EndpointNoURL Coval calls once per simulation to obtain credentials (e.g., OAuth2 token endpoint)
Initialization PayloadNoBody sent to the initialization endpoint. Stored as JSON; encoded based on Initialization Content-Type
Initialization Content-TypeNoapplication/x-www-form-urlencoded (default) or application/json
Authorization HeaderNoStatic auth value. Overridden automatically when the initialization response contains access_token
Custom HeadersNoAdditional headers sent on every chat request
Response Message PathNoDot-notation path to the agent’s reply text. Default: result.artifacts.0.parts.0.text
Response State ExtractionNoJSON paths to the contextId and taskId Coval echoes back on each turn. Default: {"contextId": "result.contextId", "taskId": "result.id"}
End State PathNoDot-notation path to the conversation status field. Default: result.status.state
End State ValuesNoList of status values that terminate the conversation. Default: ["completed", "failed"]
Custom DataNoArbitrary 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.
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:
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:
{
  "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.
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.
{
  "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.
{
  "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.

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.
{
  "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
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.
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:
{
  "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

1

Create the agent

Create a new agent from the Agents tab in the Coval platform, or via the Coval API with model_type: "MODEL_TYPE_CHAT_A2A" and the metadata fields described under Configuration above.
2

Configure initialization

If your agent requires authentication, set the initialization endpoint, payload, and content type. Otherwise leave them blank.
3

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.
4

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.
5

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.

Common Patterns

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.
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.

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

RequirementDetails
ProtocolA2A v2 JSON-RPC over HTTPS
Methodmessage/send
Content type (chat)application/json
Content type (init)Configurable (application/x-www-form-urlencoded default, application/json supported)
AccessibilityEndpoints must be publicly accessible from Coval servers
Payload sizeStandard HTTP limits apply