---
title: "Orrery Studio Part 3: How the A2A Protocol Works, From Agent Cards to Tasks"
url: "https://tirraflow.com/posts/orrery-studio-part-3-how-the-a2a-protocol-works-from-agent-cards-to-tasks"
author: "taoufiq"
published: "2026-10-25T13:05:49Z"
updated: "2026-10-25T13:01:49Z"
category: "AI Agents"
difficulty: "advanced"
series: "Orrery Studio"
series_part: 3
series_url: "https://tirraflow.com/series/orrery-studio"
tags: ["Agents", "Security", "Python", "A2A"]
---

# Orrery Studio Part 3: How the A2A Protocol Works, From Agent Cards to Tasks

> **Status, October 2026:** calling remote A2A agents shipped in Orrery 0.10.0, inside the Studio preview. Only _read-only_ remotes can be registered for now; remotes that act on your behalf need a human confirmation step that isn't built yet.

The A2A protocol (Agent2Agent) lets one AI agent hand a task to another, even when the two are built in different frameworks by different teams. This part walks through a real A2A call in Orrery Studio: the Agent Card, the task it creates, and the checks that make calling someone else's agent safe.

---

## What Is an A2A Agent Card?

Every A2A agent publishes a JSON document at a well-known URL, `/.well-known/agent-card.json`, saying who it is, what it can do, and where to send requests. This is the live card of the Studio's demo writer, fetched from the lab (shortened):

```json
{
  "name": "demo-writer",
  "description": "Drafts release notes from research notes (deterministic, no LLM).",
  "supportedInterfaces": [
    { "url": "https://demo-writer.studio.internal:8443/",
      "protocolBinding": "JSONRPC", "protocolVersion": "1.0" }
  ],
  "capabilities": { "streaming": false, "pushNotifications": false },
  "skills": [
    { "id": "draft", "name": "Draft release notes",
      "description": "Turns research notes into a bulleted release-notes draft." }
  ]
}
```

Three things matter to a caller:

- **Skills**: what you may ask for. Here, `draft`.
- **Interfaces**: where requests go and how. Here, JSON-RPC over HTTPS.
- **Security schemes**: how to authenticate. The demo writer needs none.

The writer itself is one file of about 160 lines, on the plain `a2a-sdk`, with no LLM and no Orrery code. That is the point of A2A: Orrery doesn't need to know how it was built.

---

## A2A Tasks: Work With a Lifecycle

When you send an A2A agent a message, it usually answers with a **task**; a simple agent may reply with a plain message instead. A task moves through states such as `submitted` and `working`, may pause at `input-required`, and ends `completed`, `failed`, `canceled` or `rejected`. It carries **artifacts**, the results. As Orrery sees it, the demo writer answers `completed` straight away, with one text artifact: its draft.

Orrery turns that into a plain result for the model (values shortened):

```json
{ "status": "success", "task_state": "completed",
  "reply": "# Kafka consumer lag\n\n- Lag is the gap between …",
  "parts": [{ "kind": "text", "size": 412 }] }
```

Two states get special handling. On `input-required`, the model is told that _a person_ must answer and that it must not answer for them. On a timeout, it is told the remote may still finish, so it shouldn't blindly call again.

---

## One A2A Call, Step by Step

![Sequence of one A2A call in Orrery Studio: the writer node calls the A2A tool, Orrery runs its guards, fetches and checks the agent card against its pin, sends only the request with SendMessage, and screens the task's reply](https://tirraflow.com/uploads/cb8a837a-7c0d-4db5-9496-b21805603a09.png)

The node never talks to the remote directly. Orrery's `GuardedRemoteA2aTool` sits in the middle and does four jobs.

### 1. Pin the card

Orrery fetches the card itself and hashes the parts that decide trust: the interfaces, the security schemes and requirements, and the skills, their descriptions included. Only the card's own name and description, and skill examples, are left out, so those may change freely. The hash is SHA-256 over a canonical form of the JSON (RFC 8785), so reordering keys or adding spaces can't change it.

- If the registry sets `card_sha256`, the card must match it.
- If not, the first card seen is pinned and a warning is logged.
- If the card changes later, for example to point at a new URL or add a skill, calls are refused until an admin reviews it, puts the new digest in the registry as `card_sha256`, and restarts Orrery.

Orrery also checks that every JSON-RPC URL in the card is HTTPS and on the card's own origin (the same scheme, host and port), or on one the registry lists. Otherwise a card could quietly send your requests somewhere else.

![Orrery Studio A2A inspector for the demo-writer agent, showing its draft skill, the pinned card digest and a timeline of outbound and inbound messages with task states](https://tirraflow.com/uploads/f3828e55-7006-4fda-82c1-b68461bdaf6a.png)

_The A2A inspector with sample data: each call's states, and the card's pinned digest._

### 2. Send only the request

![Comparison: a remote wired in as an agent node receives the whole session including earlier tool results, while Orrery's remote-as-a-tool sends only the request string the model wrote and screens the reply](https://tirraflow.com/uploads/78b5a4dd-4cb4-4df5-a033-2531e65c638d.png)

Google ADK has a `RemoteA2aAgent`. Used as an agent, it forwards the **whole session**: the user's question, other agents' answers, and every earlier tool result. If one of those results held a secret, the remote now has it. Its reply also arrives as an agent event, which no tool-level guard ever sees.

So in Orrery a remote is always a **tool**. Behind the scenes, ADK's agent runs in a fresh, empty session, and the function that builds what it sends returns exactly one thing: the `request` string the model wrote. A test plants a marker string (a canary) in an earlier tool result and checks it never reaches the remote. Another test fails the build if any code uses `RemoteA2aAgent` another way.

### 3. Treat the reply as untrusted data

The reply is a tool result, so it passes the safety screen (looking for injected instructions), PII redaction (emails included), and a cap of 16,000 characters. Only text comes back. Files or other part types are reported by kind and size, never relayed.

### 4. Stay on a short leash

- The connection may only reach the entry's own origin. Redirects are never followed and proxy variables are ignored.
- A private address needs two settings: `private_network: true` on the entry, and the host in `ORRERY_A2A_PRIVATE_ALLOW`. Cloud metadata addresses are refused always.
- Cards are capped at 64 KB and replies at 512 KB, with connect, read and total timeouts.
- Every request carries `x-orrery-hop: 1`, so an Orrery receiving it refuses it and two Orrerys can't loop.
- The caller's own token is never forwarded. A bearer token for the remote can only come from an `ORRERY_STUDIO_SECRET_*` variable, sent only to that remote's origin.

---

## Limits Today

- **Read-only remotes only.** Registering one with `effect: acting` fails at startup. It needs L4 autonomy and a human confirmation, which come in phase P0c.
- **No remote cancel.** Cancelling a run doesn't send the remote a cancel message yet.
- **Pins live in memory.** Without `card_sha256` in the registry, a restart pins whatever card is served next. In production, copy the logged digest into the registry once you've reviewed the card.

The full design is [ADR-008](https://github.com/BAHALLA/orrery/blob/main/docs/adr/008-consuming-remote-a2a-agents.md), and the protocol itself is documented at [a2a-protocol.org](https://a2a-protocol.org). Next: [Part 4, MCP server security](https://tirraflow.com/posts/orrery-studio-part-4-mcp-server-security-connecting-servers-you-dont-trust).
