# A2A Agents

Publish a deployed workflow as an A2A agent that other AI agents discover and call

A2A (Agent-to-Agent) is an open protocol that lets one AI agent discover and call another. SteelEngine publishes a deployed workflow as an A2A agent. It serves an Agent Card that describes the agent, and a JSON-RPC endpoint that runs the workflow as a task. SteelEngine implements protocol version 0.3.

A2A and MCP are different surfaces. [Workflow MCP](/mcp/deploy-workflows) exposes workflows as tools inside an assistant such as Claude or Cursor. An A2A agent exposes one workflow to other agents with tasks, streaming, and push notifications. A workflow can be published on both.

## Publishing an Agent

1. Open the workflow and click **Deploy**.
2. Open the **A2A** tab.
3. Enter an **Agent name** and a **Description**. Both are required and appear in the Agent Card.
4. Choose **Access**: **API Key** or **Public**.
5. Under **Capabilities**, check &#x2A;*Push notifications (webhooks)** if callers may register a webhook per task.
6. Add **Tags** to describe the agent's skill.
7. Click **Publish Agent**.

If the workflow is not deployed, SteelEngine deploys it first. The tab then shows the agent's **URL** and a **Live** badge. You need write access to the workflow to publish. Each workflow can have one A2A agent.

The endpoint is:

```txt
https://steelengine.com/api/a2a/serve/{agentId}
```

## Access

* **API Key**: Every call must send an `X-API-Key` header with a Workspace, Personal, or Organization API key from **Settings → API Keys**. A Workspace key must belong to the agent's workspace, and the key holder must be able to read that workspace. A Personal key runs the workflow as that user. Other keys run it as the workspace's billing account.
* **Public**: Calls need no credentials. Every run is billed to the workspace that owns the workflow.

A public agent runs your workflow for anyone who finds the URL. Prefer **API Key** unless the agent must be open.

## Workflow Inputs

An A2A message is a list of parts. SteelEngine maps them to the Start block:

| Part      | Start block field |
| --------- | ----------------- |
| Text part | `<start.input>`   |
| Data part | `<start.data>`    |
| File part | `<start.files>`   |

Several text parts are joined with line breaks. Several data parts are merged into one object. A message with no content in any part is rejected.

Once the agent exists, the tab shows an **Add inputs** button if the Start block lacks `input`, `data`, or `files`. Click it to add the missing fields, then deploy again. Other Start block fields can be supplied inside the data part.

## Agent Card

A `GET` request to the endpoint URL returns the Agent Card as JSON. SteelEngine serves the card at the endpoint URL itself, not at a `/.well-known/agent.json` path. Point clients that expect the well-known path at the endpoint URL. The card contains:

* `name` and `description` from the tab
* `url`: the endpoint URL
* `protocolVersion`: `0.3.0`
* `capabilities`: `streaming: true`, `stateTransitionHistory: true`, and `pushNotifications` as you set it
* `skills`: one skill named "Execute" followed by the agent name, with your **Tags**
* `defaultInputModes` and `defaultOutputModes`: `["text"]`

The card is cached for 60 seconds. An unpublished agent returns `404` with "Agent not published".

## Calling the Agent

Send a JSON-RPC 2.0 request with `POST` to the endpoint URL. The **Send message** panel in the tab generates a ready-made call in cURL, Python, JavaScript, or TypeScript:

```bash
curl -X POST \
  -H "X-API-Key: $STEELENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"1","method":"message/send","params":{"message":{"role":"user","parts":[{"kind":"text","text":"Hello, agent!"}]}}}' \
  https://steelengine.com/api/a2a/serve/YOUR_AGENT_ID
```

Supported methods:

| Method                                | What it does                                                            |
| ------------------------------------- | ----------------------------------------------------------------------- |
| `message/send`                        | Runs the workflow and returns the finished task                         |
| `message/stream`                      | Runs the workflow and streams task status updates as Server-Sent Events |
| `tasks/get`                           | Returns a task, with `historyLength` to trim the message history        |
| `tasks/cancel`                        | Cancels a task that is still running                                    |
| `tasks/resubscribe`                   | Reattaches a stream to a running task                                   |
| `tasks/pushNotificationConfig/set`    | Registers a webhook for a task                                          |
| `tasks/pushNotificationConfig/get`    | Returns a task's webhook                                                |
| `tasks/pushNotificationConfig/delete` | Removes a task's webhook                                                |

`message/send` and `message/stream` wait for the run to finish, up to 5 minutes. A longer run returns an error that names the timeout, whatever the workspace plan. For long workflows, register a push notification webhook and poll `tasks/get`.

The result is a task with `id`, `contextId`, `status.state` (`completed` or `failed`), `history`, and `artifacts`. The agent's reply in `history` is the workflow's `content` output when it has one. Otherwise it is the whole output as JSON. Artifacts come from the workflow's `artifacts` output.

Each message creates a new task, and every task ends completed or failed. To group messages into one conversation, send the same `contextId` on each message and omit `taskId`. A task that has completed, failed, or been cancelled rejects new messages. The `contextId` is not passed to the workflow. Each task keeps the last 100 messages.

## Push Notifications

Enable &#x2A;*Push notifications (webhooks)** on the agent. A caller then registers a webhook per task:

```json
{
  "jsonrpc": "2.0",
  "id": "2",
  "method": "tasks/pushNotificationConfig/set",
  "params": {
    "id": "TASK_ID",
    "pushNotificationConfig": { "url": "https://example.com/a2a-hook", "token": "optional-secret" }
  }
}
```

The URL must be reachable and use HTTPS. SteelEngine posts the task state to the URL when it changes and sends `token`, if given, as a Bearer token. Delivery is best effort.

## Managing the Agent

Open the **A2A** tab again to change the agent:

* The badge reads **Live** while the published agent matches the form and the deployed workflow. It changes to **Update deployment** when you edit a field or change the workflow. Click **Update** to redeploy and republish.
* **Unpublish** keeps the agent but stops serving it. The endpoint returns `404` until you click **Publish**.
* **Delete** removes the agent. It is available while the agent is unpublished.

Undeploying the workflow also stops the agent. Calls return `400` with "Workflow is not deployed" until you deploy again.

## Errors

Errors follow JSON-RPC:

| Code     | Meaning                                                         |
| -------- | --------------------------------------------------------------- |
| `-32001` | Task not found, or the task belongs to another caller           |
| `-32002` | Task already in a terminal state                                |
| `-32003` | Agent not found, not published, or its workflow is not deployed |
| `-32004` | Authentication required or access denied                        |
| `-32601` | Unknown method                                                  |
| `-32602` | Invalid parameters, for example a message with no content       |

## Common Questions

**How is an A2A agent different from a Workflow MCP server?**

An MCP server lists several deployed workflows as tools that an assistant calls in its own conversation. An A2A agent publishes one workflow as a peer agent with its own Agent Card, task lifecycle, streaming, and push notifications. Use MCP to give an assistant tools, and A2A when another agent should delegate work to your workflow.

**Does the workflow need to be deployed?**

Yes. Publish Agent deploys the workflow if needed, and the endpoint answers 'Workflow is not deployed' if you undeploy it later. When you change the workflow, the tab shows Update deployment; click Update to redeploy and republish.

**Which credentials does the endpoint accept?**

With Access set to API Key, send a Workspace, Personal, or Organization API key in the X-API-Key header. A Workspace key must belong to the agent's workspace. With Access set to Public, no credentials are needed and any sent are ignored.

**How do I pass structured input to the workflow?**

Add a data part to the message. Its object arrives as <start.data>, and text parts arrive joined as <start.input>. File parts arrive as <start.files>. Use Add inputs in the A2A tab if the Start block does not yet define these fields.

**What happens when the workflow takes longer than the timeout?**

message/send and message/stream wait up to 5 minutes, then return an error that names the timeout. The limit is the same on every plan. For long runs, register a push notification webhook for the task and poll tasks/get until the state is completed or failed.

**Where do I see A2A runs?**

Each task runs the workflow once. Filter the Logs page by the a2a trigger to review the runs, their inputs, and their cost.
