A2A Analytics

This document explains A2A Analytics and how it works in Moesif’s API Analytics suite.

A2A Analytics lets you observe Agent-to-Agent (A2A) protocol traffic in the same event pipeline as your API calls. The A2A protocol lets AI agents discover each other, exchange messages, and delegate work as tasks. Related messages and tasks share a context. A2A Analytics turns these protocol details into fields that you can filter, group, and chart.

A2A Analytics supports the following:

  • A2A protocol version 1.0
  • Every remote procedure call (RPC) operation that the A2A specification defines
  • All three A2A transports: JSON-RPC, HTTP+JSON, and gRPC

For a hands-on walkthrough, see Analyze Agent-to-Agent (A2A) Protocol Traffic in Moesif.

How A2A Analytics Works

To mark an event as an A2A call, add an a2a object to the event, alongside the request and response objects. Moesif reads the a2a object and makes its fields available under the A2A Analytics filter.

The a2a object is optional. Add it only to events where you observe an A2A protocol call. Leave it out of all other events, such as REST API calls or large language model (LLM) calls. Moesif ingests those events as regular API events.

Fields in the a2a Object

The a2a object contains two kinds of fields:

  • Protocol fields: These fields use the A2A specification’s own vocabulary. Examples include operation, task_id, context_id, and task_state.
  • Analytics fields: Moesif adds these fields to turn raw protocol data into metrics that the A2A specification doesn’t provide. Examples include outcome, terminal, failure_origin, is_error, and time_to_first_event_ms.

Only the operation and transport fields are required. For the full list of fields, their allowed values, and validation rules, see A2A Analytics Filters.

Example: JSON-RPC Event

The following example shows the a2a object for a SendMessage call over JSON-RPC. A user asks a weather agent about the weather. The agent replies with a completed task:

{
  "request": {},
  "response": {},
  "a2a": {
    "operation": "SendMessage",
    "transport": "JSONRPC",
    "protocol_version": "1.0",
    "agentId": "weatheragent2",
    "agentName": "Weather Agent v2",
    "request_type": "operation",
    "request": {
      "message_id": "m-001",
      "input_part_count": 1,
      "return_immediately": false
    },
    "response": {
      "is_error": false,
      "is_streaming": false,
      "payload_type": "task",
      "task_id": "t-a1b2c3",
      "context_id": "c-xyz-1",
      "task_state": "TASK_STATE_COMPLETED"
    },
    "terminal": true,
    "outcome": "SUCCESS"
  }
}

The request and response objects at the top level hold the raw HTTP exchange, like any other Moesif event. For a complete event with request and response data, see the tutorial.

Transports

The a2a object has the same shape for every transport. You can analyze all A2A traffic together or split it by transport. The following parts of an event differ between transports:

  • The transport field: The value is JSONRPC, HTTP+JSON, or GRPC.
  • The raw request: The URL, headers, and body shape depend on the transport.
  • Error codes: The response.error_code field uses the error codes of the transport. These are JSON-RPC codes in the -32xxx range, gRPC status codes from 0 to 16, or HTTP status codes of 400 and above.

The following examples show the top-level request object for the same SendMessage call over each transport.

JSON-RPC

JSON-RPC calls post to the agent’s base URL. The method field of the body holds the operation name. The params field holds the operation’s parameters:

"request": {
  "uri": "http://agent.example.com/",
  "verb": "POST",
  "headers": {"content-type": "application/json"},
  "body": {
    "jsonrpc": "2.0",
    "id": "req-001",
    "method": "SendMessage",
    "params": {
      "message": {
        "kind": "message",
        "messageId": "m-001",
        "role": "user",
        "parts": [{"kind": "text", "text": "What is the weather in Colombo?"}]
      },
      "configuration": {"blocking": true}
    }
  }
}

In the a2a object, transport is JSONRPC.

HTTP+JSON

HTTP+JSON calls use REST-style paths, such as /v1/messages:send or /v1/tasks/{id}. The path identifies the operation. The body holds the operation’s parameters directly:

"request": {
  "uri": "http://agent.example.com/v1/messages:send",
  "verb": "POST",
  "headers": {"content-type": "application/json"},
  "body": {
    "message": {
      "kind": "message",
      "messageId": "m-001",
      "role": "user",
      "parts": [{"kind": "text", "text": "What is the weather in Colombo?"}]
    },
    "configuration": {"blocking": true}
  }
}

In the a2a object, transport is HTTP+JSON.

gRPC

gRPC calls use paths that name the service and method, such as /lf.a2a.v1.A2AService/SendMessage. The headers carry gRPC metadata. Message roles use the ROLE_ prefix:

"request": {
  "uri": "https://agent.example.com:443/lf.a2a.v1.A2AService/SendMessage",
  "verb": "POST",
  "headers": {
    "content-type": "application/grpc",
    "te": "trailers"
  },
  "body": {
    "message": {
      "kind": "message",
      "messageId": "m-001",
      "role": "ROLE_USER",
      "parts": [{"kind": "text", "text": "What is the weather in Colombo?"}]
    },
    "configuration": {"blocking": true}
  }
}

The response headers carry the gRPC status in the grpc-status header. In the a2a object, transport is GRPC. The response.error_code field holds the same status code. For a successful call, both are 0.

Errors and Outcomes

A2A Analytics answers two separate questions about each call:

  • Did the response carry an error? The response.is_error field answers this question. The value is true for a JSON-RPC error body, a gRPC status other than OK, or an HTTP status of 400 or higher.
  • Did the call achieve its goal? The outcome field answers this question. If the outcome is FAILURE, the failure_origin field identifies the responsible party: the client, a policy, the gateway, or the upstream agent.

The two fields can disagree. For example, an agent can return a task with HTTP status 200, but end the task in the TASK_STATE_FAILED state. In that case, is_error is false and outcome is FAILURE. A chart that counts only HTTP errors misses this failure.

The terminal field shows whether the reported task state is final. A task in a terminal state doesn’t change any further.

Streaming Metrics

For a streaming call, the overall response time mostly reflects how long the stream stays open. The following fields measure streaming performance more precisely:

  • response.time_to_first_event_ms: The time from the start of the request to the first complete data event in the server-sent events (SSE) stream. This is how long the caller waits before receiving any data.
  • response.stream_duration_ms: The time from the first frame to the end of the stream.

Send A2A Events to Moesif

You can send A2A events to Moesif in the following ways:

  • Moesif SDKs: Include the a2a object in the event payload. For a list of SDKs, see Server Integrations.
  • Collector API: Send events to the Collector API directly, for example from an API gateway. Send single events to https://api.moesif.net/v1/events, or arrays of events to https://api.moesif.net/v1/events/batch.

If your Moesif organization uses the EU region, use eu.api.moesif.net instead of api.moesif.net. For more information, see Data Residency.

Use Cases

Use A2A Analytics to answer questions such as the following:

  • Agent traffic: Which agents and operations receive the most traffic? Group A2A events by agentName and operation. To see which agents use which transports, group by transport.
  • Task outcomes: How many tasks complete, fail, get canceled, or get rejected? Group terminal events by response.task_state. Among non-terminal events, growth in TASK_STATE_INPUT_REQUIRED and TASK_STATE_AUTH_REQUIRED can show that agents can’t complete work without help from the user.
  • Failure attribution: When a call fails, which party is responsible? Filter events where outcome is FAILURE. Then group them by failure_origin.
  • Streaming responsiveness: How quickly do streaming agents respond? Chart the average response.time_to_first_event_ms of streaming events by agent.
  • Conversation tracing: Which calls belong to one conversation? Filter events by response.context_id. For GetTask and CancelTask calls, the client sends the task ID in the request. To find every call for a task, filter on request.task_id as well as response.task_id.
  • Customer dependency: Which customers depend on which agents? Group A2A events by user or company. For more information, see Identify Customers.

You can save these charts to a dashboard and set up alerts on them.

You can also use the prebuilt Dashboard template for A2A Analytics to quickly get started. The template creates charts that give insights into latency trend by agent, error rate, breakdown of agents by operations and traffic volume, and more. Follow these steps from Moesif Portal:

  1. Select + Create New in the navigation menu.
  2. Select Dashboard Templates.
  3. Select the A2A Analytics template.

Updated: