# Connect an agent with A2A

> Let another AI agent delegate an investigation to Anectico over the Agent2Agent protocol.

Canonical page: https://anectico.com/docs/agents/connect-a2a/


Anectico speaks [Agent2Agent](https://a2a-protocol.org) (A2A). Another agent — yours, or a
framework that speaks the protocol — can hand Anectico a question about a customer, an Issue, an
agent run, or an incident, and receive a cited, evidence-grounded conclusion back as a task
artifact.

This is the delegation surface. Use [MCP](/docs/agents/connect-mcp) when you want an agent to hold
a set of tools and drive the investigation itself; use A2A when you want to hand the whole
investigation over and get an answer.

## Discover the agent

The agent card is public and needs no credential — an A2A client fetches it first to learn what the
agent can do and how to authenticate:

```bash
curl https://app.anectico.com/.well-known/agent-card.json
```

It names one interface: a JSON-RPC endpoint at `https://app.anectico.com/a2a`. The card declares
`streaming: true` and `pushNotifications: true`; the authenticated extended card is not offered, and
`GetExtendedAgentCard` is refused rather than silently accepted. Read the card for what is offered
rather than trusting this page: the card and the methods behind it move together.

## Authenticate

The protocol endpoint requires a credential on every call. Either form works:

```http
X-Anectico-API-Key: an_...
```

```http
Authorization: Bearer an_...
```

Create a key with the `investigate` scope bundle — the same credential an MCP agent uses. See
[Scope recipes](/docs/agents/scope-recipes) for the bundles and
[Revoke agent access](/docs/agents/revoke-agent-access) for rotation.

A project-scoped key is pinned to its project. An organization-scoped credential selects the
project per request through the A2A `tenant` field, which Anectico reads as the project id.

Every protocol request must also declare the representation and the protocol version advertised by
the card. The current JSON-RPC interface requires both headers:

```http
Content-Type: application/json
A2A-Version: 1.0
```

An absent or empty `A2A-Version` means version 0.3 under the A2A specification, which this interface
does not advertise, so Anectico answers with `VersionNotSupportedError`. JSON field names use the
protocol's lower-camel-case spelling; for example, push configuration requests use `taskId`, not
`task_id`.

## Ask a question

Send one message. The text of the message is the question; every message starts a new task.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "SendMessage",
  "params": {
    "tenant": "<project-id>",
    "message": {
      "role": "ROLE_USER",
      "messageId": "5f1c...",
      "parts": [{ "content": { "text": "Why did checkout keep failing for this user yesterday?" } }],
      "metadata": {
        "anectico.skill": "investigate-person",
        "anectico.anchor.ref": "person_01H..."
      }
    }
  }
}
```

Use `SendStreamingMessage` for the same call with progress delivered as it happens; it answers with
Server-Sent Events.

### Skills

`anectico.skill` chooses what the investigation is about, and `anectico.anchor.ref` supplies the
identifier it needs. Omit both to ask an open-ended question scoped to the project.

| `anectico.skill` | `anectico.anchor.ref` | Answers |
| --- | --- | --- |
| `investigate-person` | person id or raw distinct ID | What happened to one customer: profile, activity, a recent cross-signal timeline, their errors, their session recordings |
| `triage-issue` | error group id | What is causing one Issue and who it hits: a representative occurrence, affected people, releases, the trace, replay and logs around it |
| `explain-agent-run` | agent run id (its trace ID) | What one of your own AI agent runs did: the run, its step transcript, and the customer it was serving |
| `assess-incident-impact` | incident id | An incident's blast radius: the case file, its timeline, every attached signal, and the people those signals reached |
| `verify-recovery` | incident id | Whether an incident really recovered: the same evidence, asked whether the signals stopped and the affected customers succeeded afterwards |

A skill this agent does not advertise is rejected, as is a skill whose identifier is missing. Ask
the card for the current list rather than hard-coding one.

## Read the answer

The task's answer is an artifact with the ID `conclusion`: one `text/markdown` part holding the
full, cited write-up. The task then moves to `COMPLETED`. Progress arrives as status updates before
it; the conclusion artifact is the converged content, so a client that only reads artifacts loses
nothing.

The conclusion artifact and terminal status both carry `anectico.evidence.omissions` metadata. Each
entry has closed `source` and `reason` enum strings identifying evidence the model did not examine
and whether the cause was scope, policy, availability, or the evidence limit. A present empty list
means disclosure was evaluated and nothing was omitted. An absent metadata key means the conclusion
producer did not populate the contract; do not treat that as complete. This is the agent contract —
the matching Markdown sections remain for human readers, but clients do not need to parse them.

An answer is built from what **your credential** could read, and both that permission and the
project's current content policy are re-checked every time you read it back. If the credential's
scopes are narrowed after the task completes — a role change, or a key replaced by a narrower one —
`GetTask`, `ListTasks` and `SubscribeToTask` return the task with its conclusion replaced by a short
notice saying content of a kind your role no longer includes went into it. A later project-policy
denial withholds the same conclusion for every reader, and an unavailable policy check fails closed
with a distinct “could not be confirmed” notice. Everything else about the task stays: its state,
its timing, and the question you asked. Re-run the investigation with the current credential to get
an answer built from what you can see.

Follow up in the same conversation by sending another message with the same `contextId`. Each
follow-up is a new task in that context and continues the same investigation.

The Investigator does not pause a task in `INPUT_REQUIRED`. It completes the current turn with the
evidence available; when you want to clarify or add input, send a follow-up as a new task in the same
context. A completed task cannot accept another message.

## The rest of the surface

| Method | Use |
| --- | --- |
| `GetTask` | One task's current state, with optional history |
| `ListTasks` | Your own tasks, filtered by context or state |
| `CancelTask` | Stop a running investigation |
| `SubscribeToTask` | Receive the task's current snapshot, then follow newly committed updates |
| `CreateTaskPushNotificationConfig`, `GetTaskPushNotificationConfig`, `ListTaskPushNotificationConfigs`, `DeleteTaskPushNotificationConfig` | Register a webhook to be told when a task changes, instead of staying attached |

`ListTasks` and `GetTask` return only tasks created by the calling credential.

A push configuration may also ride in on `SendMessage`, in which case it is stored against the task
that call creates. The callback URL is stored encrypted, and it is re-resolved before every delivery
attempt — an address that resolves somewhere Anectico will not call is refused at delivery time, not
only at registration time. Configuration IDs are opaque strings: supply one to preserve it, or omit
it and let Anectico assign one. Deleting a configuration is idempotent and suppresses attempts that
have not been sent yet, not only future ones.

The callback is also governed by the project's current **Sent outside Anectico** content-policy
decision. Anectico checks it immediately before each attempt, including retries. If the conclusion's
content is denied or the decision cannot be confirmed, the callback still receives task status and
metadata but not the conclusion artifact or terminal answer text. A later policy change does not
rewrite the durable task history inside Anectico.

## Not offered

There is no authenticated extended card: everything this agent can do is on the public card, and
`GetExtendedAgentCard` is refused with `UnsupportedOperation`.

## When a stream ends early

A streaming call is authorized when you open it, and **re-authorized while it runs**. Anectico
re-checks the credential against its authoritative record before handing over an update, so revoking
a key, freezing it, or narrowing its scopes stops a `SendStreamingMessage` or a `SubscribeToTask`
that is already in flight — within 30 seconds of the next update it would have delivered. The update
that would have crossed that line is withheld, not delivered and then apologized for.

You learn about it as a final JSON-RPC error in place of the next update, and the code says what to
do next:

| Error | What happened | What to do |
| --- | --- | --- |
| `UNAUTHENTICATED` | The credential no longer authenticates | Read the message. A credential that merely expired says to re-authenticate and call again; one that was taken away says to obtain a new one, and retrying with it will not work |
| `UNAUTHORIZED` | The credential still authenticates but no longer holds what the stream was admitted with — a narrowed key, or a frozen one | Do not retry with it; the answer will not change |
| `SERVER_ERROR` | The credential could not be checked at all | Transient. Back off and call again |

The stream always ends on any of the three. Anectico will not carry on delivering while it is unsure
the caller is still entitled to what it is delivering.

`SubscribeToTask` deserves a note, because it is the call with no natural end: it opens with the
task's current snapshot and then follows newly committed updates for as long as you stay attached.
It is bounded by exactly the same re-check. If your stream ends and your credential is still good,
simply re-subscribe: the opening snapshot gives you the converged current state before new updates
resume.

## Verify it works

```bash
curl -s https://app.anectico.com/.well-known/agent-card.json | jq '.skills[].id'
```

Then send one `SendMessage` with an identifier you can check by hand, and confirm the returned
conclusion cites evidence you recognize. A `401` means the credential is missing or malformed; a
`403` means the key lacks the scope to run investigations.

## Next steps

- [Scope recipes](/docs/agents/scope-recipes) — pick the narrowest credential that works
- [Agent security model](/docs/agents/security-model) — what an agent credential can and cannot do
- [Connect an AI agent with MCP](/docs/agents/connect-mcp) — the tool-driven alternative
