Skip to main content

Getting Started with A2A

Prerequisites

  1. iac-code installed — See the Installation guide.

  2. LLM credentials configured — See the Authentication guide to configure your model provider credentials.

  3. A2A server dependencies — Install iac-code with the a2a extra:

uv sync --extra a2a

Starting the A2A Server

Start the server on the default local interface:

iac-code a2a --host 127.0.0.1 --port 41242

Use a YAML config file when you need local state, artifact storage, push notification delivery, or signed Agent Cards:

persistence-dir: ~/.iac-code/a2a
artifact-dir: ~/.iac-code/a2a/artifacts
signing-secret: local-card-signing-secret
push-notifications: true
thinking-exposure:
- tool-trace
- raw-thinking

Run it with:

iac-code a2a --config a2a-server.yml

push-notifications: true enables A2A task push notification config methods and terminal-state delivery. Use push-queue: redis-streams with push-redis-url when multiple workers need to coordinate push delivery.

thinking-exposure controls which non-answer runtime signals are exposed through metadata.iac_code during streaming. The default is tool-trace; add raw-thinking only for trusted clients that are allowed to see provider reasoning content.

The server exposes:

RoutePurpose
GET /healthHealth check
GET /.well-known/agent-card.jsonAgent Card discovery
POST /A2A JSON-RPC endpoint

The HTTP server also registers the A2A SDK REST routes and advertises both JSONRPC and HTTP+JSON interfaces in the Agent Card.

Verify Discovery

Fetch the Agent Card:

curl http://127.0.0.1:41242/.well-known/agent-card.json

You should see name: "iac-code", JSONRPC and HTTP+JSON interfaces, cache headers such as ETag, the optional urn:iac-code:a2a:artifact-metadata:v1 extension, supported input modes, and skills such as iac_generation, iac_review, aliyun_ros_operations, and terraform_ros_conversion.

Check the health endpoint:

curl http://127.0.0.1:41242/health

Expected response:

{"status":"healthy"}

Require Authentication

Authentication is optional. If no A2A authentication options or environment variables are set, requests do not need auth. When any auth scheme is configured, every request, including Agent Card discovery, must satisfy one configured scheme.

Bearer Token

export IACCODE_A2A_HTTP_TOKEN=your-secret-token
iac-code a2a

The equivalent YAML config key is token.

Authorization: Bearer <token>

Basic Auth

export IACCODE_A2A_BASIC_USERNAME=iac-code
export IACCODE_A2A_BASIC_PASSWORD=your-password

iac-code a2a

The username and password must both be present. The equivalent YAML config keys are basic-username and basic-password.

API Key

export IACCODE_A2A_API_KEY=your-api-key

iac-code a2a

The default API key header is:

X-API-Key: <api-key>

Override it with the api-key-header YAML config key or IACCODE_A2A_API_KEY_HEADER:

api-key: your-api-key
api-key-header: X-IAC-Code-Key

Call a Remote A2A Agent

Put stable client connection and auth settings in a YAML file:

url: http://127.0.0.1:41242/
token: your-secret-token
verify-card-secret: your-card-signing-secret
require-card-signature: true
cwd: /path/to/workspace

Use a2a-client call for a direct Phase 1 client call:

iac-code a2a-client --config a2a-client.yml call --prompt "Create a VPC with two vSwitches" --cwd "$PWD"

Use --stream when you want incremental events instead of one final response:

iac-code a2a-client --config a2a-client.yml call \
--prompt "Review this template" \
--cwd "$PWD" \
--stream

Command-line options override config values when you need a one-off target or token:

iac-code a2a-client --config a2a-client.yml call \
--url https://other-agent.example.com/ \
--prompt "Review this template"

For multi-agent routing, preview route selection before calling:

iac-code a2a-route-preview \
--route "template=http://127.0.0.1:41242/;skills=iac_generation;tags=ros,template" \
--skill iac_generation \
--route-state-dir ~/.iac-code/a2a

See Command Reference for every A2A command, including task management, push config CRUD, extended Agent Cards, and transport options.

Send a First Message with curl

Pass the workspace directory through message.metadata.iac_code.cwd; the path must be absolute, must already exist, and must be inside an allowed workspace root. By default, allowed roots are the server process directory and the system temp directory. Override them with IACCODE_A2A_ALLOWED_CWDS.

The server accepts text-like parts, JSON data parts, raw UTF-8 text, local workspace file:// text files, and bounded multimodal attachments. Remote URL ingestion is not supported; url parts must be local file:// URLs inside the allowed workspace.

curl -s -X POST http://127.0.0.1:41242/ \
-H "Content-Type: application/json" \
-H "A2A-Version: 1.0" \
-d '{
"jsonrpc": "2.0",
"id": "1",
"method": "SendMessage",
"params": {
"message": {
"messageId": "msg-1",
"role": "ROLE_USER",
"parts": [
{"text": "Generate a ROS VPC template with two vSwitches."}
],
"metadata": {
"iac_code": {
"cwd": "/path/to/project"
}
}
},
"configuration": {
"acceptedOutputModes": ["text/plain"]
}
}
}'

For streaming output, use SendStreamingMessage:

curl -N -X POST http://127.0.0.1:41242/ \
-H "Content-Type: application/json" \
-H "A2A-Version: 1.0" \
-d '{
"jsonrpc": "2.0",
"id": "2",
"method": "SendStreamingMessage",
"params": {
"message": {
"messageId": "msg-2",
"role": "ROLE_USER",
"parts": [
{"text": "Review my Terraform files and suggest ROS equivalents."}
],
"metadata": {
"iac_code": {
"cwd": "/path/to/project"
}
}
},
"configuration": {
"acceptedOutputModes": ["text/plain"]
}
}
}'

Minimal Python SDK Example

The example below uses a2a-sdk>=1.0.2,<2, which is the version range used by the a2a extra.

"""Minimal iac-code A2A client using a2a-sdk."""

import asyncio
import uuid
from pathlib import Path

import httpx
from a2a.client import ClientConfig, ClientFactory
from a2a.types import Message, Part, Role, SendMessageRequest


async def main() -> None:
async with httpx.AsyncClient(timeout=120.0) as httpx_client:
config = ClientConfig(httpx_client=httpx_client, streaming=True)
client = await ClientFactory(config).create_from_url("http://127.0.0.1:41242")

request = SendMessageRequest(
message=Message(
message_id=f"msg-{uuid.uuid4().hex}",
role=Role.ROLE_USER,
parts=[Part(text="Generate a ROS VPC template with two vSwitches.")],
metadata={"iac_code": {"cwd": str(Path.cwd())}},
)
)

async for event in client.send_message(request):
if event.HasField("status_update"):
status = event.status_update.status
if status.message:
for part in status.message.parts:
if part.text:
print(part.text, end="", flush=True)

await client.close()


if __name__ == "__main__":
asyncio.run(main())
tip

For authenticated servers, construct the httpx.AsyncClient with headers={"Authorization": "Bearer <token>"} so both Agent Card discovery and JSON-RPC calls include the token.

Next Steps

  • Command Reference — Complete CLI command and option reference.
  • Protocol Reference — Method, route, state, and metadata details.
  • HTTP Transport — JSON-RPC HTTP behavior, bearer auth, and curl workflows.
  • Examples — SDK, direct HTTP, follow-up, cancellation, and metadata handling examples.