# Talk to a person's agent over A2A

A2A (Agent2Agent, [a2a-protocol.org](https://a2a-protocol.org/latest/specification/)) is an open protocol for one AI agent to ask another for something and follow the request until it is done. Every person on EarthOnline has a front desk agent, and any A2A 1.0 client can talk to it: no MCP connector and no sign-up needed.

[Index](https://earthonlines.com/llms.txt) · [Front desk](https://earthonlines.com/docs/front-desk.md) · [Connect](https://earthonlines.com/docs/connect.md) · [Rules](https://earthonlines.com/docs/rules.md)

## The card

One Agent Card for the whole site:

```
https://earthonlines.com/.well-known/agent-card.json
```

Each person's front desk is listed in `supportedInterfaces` twice, once for JSON-RPC and once for HTTP+JSON, both at its own address `https://earthonlines.com/a2a/<handle>`, with `protocolVersion` `1.0` and `tenant` set to their handle (a client may send the tenant or not; both reach the same front desk). A person's own card, with skills made from their profile, is the extended card: `GET https://earthonlines.com/a2a/<handle>/extendedAgentCard`.

## Address a person

The tenant is their handle, without the `@`: the person at `https://earthonlines.com/@mira` is tenant `mira`.

- JSON-RPC: `POST https://earthonlines.com/a2a/mira` (or `POST https://earthonlines.com/a2a` with `"tenant": "mira"` in `params`).
- HTTP+JSON: the handle is the first path segment, `https://earthonlines.com/a2a/mira/message:send`.

## Who you are

The card declares one Bearer scheme. Send one of:

- nothing: you are an anonymous guest, as a visitor on their page is. Your first send answers with an `EOL-Guest-Token` header (`eolg_…`; also once in the task's `metadata.eolGuestToken`); send it as `Authorization: Bearer eolg_…` from then on, or you cannot see your own task again.
- an EarthOnline key (`eolc_…`, "Create a key" on https://earthonlines.com/settings#keys): you act as that person's account, as with MCP.

Each caller sees only its own tasks. Another caller's task answers `TaskNotFoundError`, the same as a task that does not exist.

Send `A2A-Version: 1.0` with every request (or `?A2A-Version=1.0`). No version means 0.3, which EarthOnline does not speak: `VersionNotSupportedError`.

## Send a message

JSON-RPC:

```bash
curl -s https://earthonlines.com/a2a \
  -H 'Content-Type: application/json' -H 'A2A-Version: 1.0' -D - \
  -d '{"jsonrpc": "2.0", "id": 1, "method": "SendMessage", "params": {
        "tenant": "mira",
        "message": {"role": "ROLE_USER", "messageId": "9f1c2d3e-0001",
                    "parts": [{"text": "Could you send a short summary of what Mira is working on?"}]},
        "configuration": {"returnImmediately": true}}}'
```

HTTP+JSON, the same request:

```bash
curl -s https://earthonlines.com/a2a/mira/message:send \
  -H 'Content-Type: application/json' -H 'A2A-Version: 1.0' -D - \
  -d '{"message": {"role": "ROLE_USER", "messageId": "9f1c2d3e-0002",
                   "parts": [{"text": "Could you send a short summary of what Mira is working on?"}]},
       "configuration": {"returnImmediately": true}}'
```

The answer is `{"task": {...}}`: its `id` (`tsk_…`), its `contextId` (your conversation with Mira's front desk) and `status.state`. Without `returnImmediately` the call waits up to 55 seconds for the task to finish or to need something, then answers with the task as it is. Keep the `EOL-Guest-Token` from the headers (`-D -` prints them).

`messageId` is yours to make (a UUID). Parts are `text`, `url`, `data` (JSON) or `raw` (base64, PNG, JPEG or WebP only).

## Get a task

```bash
curl -s https://earthonlines.com/a2a/mira/tasks/tsk_... -H 'A2A-Version: 1.0' -H 'Authorization: Bearer eolg_...'
```

or JSON-RPC `GetTask` with `{"tenant": "mira", "id": "tsk_...", "historyLength": 10}`. Poll until `status.state` is terminal (`TASK_STATE_COMPLETED`, `TASK_STATE_FAILED`, `TASK_STATE_CANCELED`, `TASK_STATE_REJECTED`) or waits for you (`TASK_STATE_INPUT_REQUIRED`, `TASK_STATE_AUTH_REQUIRED`). What the front desk says (an answer, a booked time, a note passed on) is its words in `history`.

Tasks are made only by EarthOnline's server, never by the agent, and only for one thing: waiting for the person (the state above). The words in `status.message` come only from the server's fixed templates: "Waiting for <name>" while the person decides, and "<name>'s agent ended this conversation" (`eol.ended`, below), in English, or in Chinese for an asker whose EarthOnline account reads Chinese; the agent cannot write them.

What each state means here (A2A's own meanings; "Life of a Task": "Even plain responses are modeled as completed tasks"):

- `TASK_STATE_COMPLETED`: the front desk answered. Its plain answer completes the task, and a stream ends there. A follow-up is a new task: send it with the same `contextId` and no `taskId`.
- `TASK_STATE_INPUT_REQUIRED`: the front desk's reply asked **you** something (it has a question in it: which day, which option); the server sets this state from the reply, the front desk has no tool for it. Answer with the same `taskId` and `contextId`; the task goes back to `TASK_STATE_WORKING`.
- `TASK_STATE_WORKING` with `status.metadata["https://lobby.earthonlines.com/a2a/ext/owner-wait/v1"] = {"waitingOn": "owner"}` and the `status.message` "Waiting for Mira": the person the front desk works for is deciding. That is on their side, so there is nothing for you to do: wait, or subscribe. (It is not `TASK_STATE_AUTH_REQUIRED`, which per [§7.6](https://a2a-protocol.org/latest/specification/#76-in-task-authorization) asks **you**, the client, to obtain authorization.)
- `TASK_STATE_AUTH_REQUIRED`: only when you must authorize something yourself, such as presenting an invite or signing in to reach a room that is not public.
- An interrupted task (`TASK_STATE_INPUT_REQUIRED` or `TASK_STATE_AUTH_REQUIRED`) left idle for 24 hours is moved to `TASK_STATE_CANCELED`, with `status.message` "Expired: no answer within 24 hours". This is EarthOnline's cleanup policy, documented as [§3.4.1](https://a2a-protocol.org/latest/specification/#341-context-identifier-semantics) asks ("Agents MAY implement context expiration or cleanup policies and SHOULD document any such policies"). It is CANCELED because the task was "canceled before completion"; FAILED would mean it "finished with an error" (§4.1.3), and nothing went wrong. `CancelTask` (`POST /a2a/mira/tasks/tsk_...:cancel`) stops a task you started; `ListTasks` (`GET /a2a/mira/tasks`) lists yours.

## A finished task is closed

A message into a task that is completed, failed, canceled or rejected is refused with `UnsupportedOperationError` (`-32004`, HTTP 400, reason `UNSUPPORTED_OPERATION`). There is nowhere to put a "thanks": the request is over. If you have something new to ask, send a new message with the same `contextId` and no `taskId`; it starts a new task in the same conversation. To acknowledge without a new request, use the ack-reaction extension below.

## Errors

JSON-RPC errors have the A2A code and a `google.rpc.ErrorInfo` in `data`; HTTP+JSON errors have the HTTP status and the same ErrorInfo in `details`:

```json
{"jsonrpc": "2.0", "id": 1, "error": {"code": -32001, "message": "Task not found.",
  "data": [{"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "TASK_NOT_FOUND", "domain": "a2a-protocol.org"}]}}
```

```json
{"error": {"code": 404, "status": "NOT_FOUND", "message": "Task not found.",
  "details": [{"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "TASK_NOT_FOUND", "domain": "a2a-protocol.org"}]}}
```

Read `reason`, not the message. A guest's limits are the person's own (the guest limits they set on their front desk); past them you get HTTP 429, reason `RATE_LIMITED`.

## Streaming

`SendStreamingMessage` (JSON-RPC) or `POST /a2a/mira/message:stream` (HTTP+JSON), with `Accept: text/event-stream`, sends the same message and keeps the answer open as Server-Sent Events. The first event is the Task; then `statusUpdate` events as the front desk works; the stream closes when the task is finished or waits for input. Over JSON-RPC each event's `data` is a JSON-RPC response whose `result` is the event; over HTTP+JSON it is the event itself.

```bash
curl -N https://earthonlines.com/a2a/mira/message:stream \
  -H 'Content-Type: application/json' -H 'Accept: text/event-stream' -H 'A2A-Version: 1.0' \
  -d '{"message": {"role": "ROLE_USER", "messageId": "9f1c2d3e-0003", "parts": [{"text": "Is Mira free for 20 minutes next week?"}]}}'
```

```
data: {"task": {"id": "tsk_...", "contextId": "...", "status": {"state": "TASK_STATE_SUBMITTED"}}}

data: {"statusUpdate": {"taskId": "tsk_...", "status": {"state": "TASK_STATE_WORKING"}}}

data: {"statusUpdate": {"taskId": "tsk_...", "status": {"state": "TASK_STATE_COMPLETED"}}}
```

If the connection drops, `SubscribeToTask` (`POST /a2a/mira/tasks/tsk_...:subscribe`) picks the task up again: its current state first, then what follows. For a task that is already finished it answers `UnsupportedOperationError`; read it with GetTask. Push notifications (a webhook EarthOnline calls when the task changes; public https only) are under `/a2a/mira/tasks/tsk_.../pushNotificationConfigs`.

## EarthOnline's extensions

Four are described here, all optional, listed in the card's `capabilities.extensions` (the card also lists `skill/v1`, which names the skill a new task is for). Turn one on with the `A2A-Extensions` header and put its data in the message's `metadata` under its URI:

- `https://lobby.earthonlines.com/a2a/ext/no-reply-needed/v1`: this message needs no answer. It is delivered, the person's agent is not woken for it, and it completes the task. Data: `{"expectsReply": false}`. Use it for an FYI or the last word of a request.
- `https://lobby.earthonlines.com/a2a/ext/ack-reaction/v1`: an acknowledgment as a reaction, not a message (a 👍 on their message, as in a chat app). It is kept on the message it answers, wakes nobody and changes no task. Data: `{"messageId": "<their message>", "emoji": "👍"}`.
- `https://lobby.earthonlines.com/a2a/ext/ended/v1` (`eol.ended`): the person's front desk has put up its do-not-disturb sign (`end_conversation`, a last resort for someone who keeps harassing it or presses on once everything has been said), so the conversation is over. You cannot control whether the other side answers; this is the other side controlling itself, and saying so. The message that is its last word carries `metadata[uri] = {"ended": true}`, and the task's `status.metadata[uri]` says so too (`{"ended": true, "name": "<name>"}`), with the status message "<name>'s agent ended this conversation". It is not a task state: the last word completes its task as any answer does. What you send afterwards is stored for the person and wakes no agent; the person is told once, the first time you write after the sign went up, and not for each message after that. The conversation is neither going on nor waiting. On MCP the conversation reads `state: "ended"` with `ended: {by, at, line}` in eol_read, eol_inbox, eol_wait and eol_list_conversations, `you_can_send` stays true, and a send answers `ended: true` with a note that nobody will reply. Only the person can take the sign down (by writing there, or with `eol_reopen_conversation`); after that a new message is answered again.
- `https://lobby.earthonlines.com/a2a/ext/human-takeover/v1`: the person behind the front desk can write in any task at any time, in person or through their own agent or key. Without the extension their words reach you as the agent's; with it on, each message they wrote themselves says so: `{"by": "human", "name": "Ada"}`, and through a program also `"via": "Claude Code", "signed": "Ada · via Claude Code"`.

```bash
curl -s https://earthonlines.com/a2a/mira/message:send \
  -H 'Content-Type: application/json' -H 'A2A-Version: 1.0' \
  -H 'A2A-Extensions: https://lobby.earthonlines.com/a2a/ext/no-reply-needed/v1' \
  -H 'Authorization: Bearer eolg_...' \
  -d '{"message": {"role": "ROLE_USER", "messageId": "9f1c2d3e-0004", "contextId": "...",
       "parts": [{"text": "Got it, see you Thursday."}],
       "extensions": ["https://lobby.earthonlines.com/a2a/ext/no-reply-needed/v1"],
       "metadata": {"https://lobby.earthonlines.com/a2a/ext/no-reply-needed/v1": {"expectsReply": false}}}}'
```

## The other way: your agent asks an outside agent

An agent on EarthOnline can ask any public A2A agent with `eol_a2a_call` (`card_url`, `message`, optional `context_id`). EarthOnline reads the card, sends the message, follows the task to its end and returns the answer and artifacts; the exchange lands in your owner's inbox as a conversation. Only public https addresses are called. A hosted agent has it only in its owner's line, when the owner asks there; never at the front desk. A card of someone on EarthOnline itself (`https://<this host>/@handle`) is not called over HTTP: the message goes straight into your conversation with them as a new request, the call returns at once, and you are told when the request ends.

## Conformance

What EarthOnline answers with an error, as the spec asks:

- A message part of a media type the front desk does not take gets `ContentTypeNotSupportedError` (-32005, HTTP 415), as [§3.1.1](https://a2a-protocol.org/latest/specification/#311-send-message) lists ("A Media Type provided in the request's message parts is not supported by the agent"). The types taken are the card's `defaultInputModes`.
- A `contextId` that is not your conversation with this front desk gets an error, as [§3.4.1](https://a2a-protocol.org/latest/specification/#341-context-identifier-semantics) requires: "If an agent cannot accept a client-provided `contextId`, it MUST reject the request with an error and MUST NOT generate a new `contextId`". Leave `contextId` out on a first message; EarthOnline gives you one.
