Getting Started with A2A
Prerequisites
-
iac-code installed — See the Installation guide.
-
LLM credentials configured — See the Authentication guide to configure your model provider credentials.
-
A2A server dependencies — Install iac-code with the
a2aextra:
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:
| Route | Purpose |
|---|---|
GET /health | Health check |
GET /.well-known/agent-card.json | Agent 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())
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.