A2A (Agent-to-Agent Protocol)
AgentTrust ID’s implementation of Google’s A2A protocol, enabling inter-agent task dispatch with AgentTrust ID authorization checks.
Overview
A2A is an open protocol for standardized agent-to-agent communication. AgentTrust ID implements a JSON-RPC 2.0 server that manages task lifecycles, supports Agent Card discovery, and mediates live A2A task operations through AgentTrust ID authorization before task state changes are applied. JWS verification is available when a card response includes a signed StoredCard wrapper; the public discovery endpoint returns raw Agent Card JSON.
Architecture
Task Lifecycle
Tasks follow a state machine with six states:
Terminal states (completed, canceled, failed) cannot be modified.
API Reference
POST /a2a — JSON-RPC Dispatch
Org-level endpoint. Routes JSON-RPC requests to the appropriate handler. Before dispatch, the runtime resolves the acting agent and sends the request through AgentTrust ID.
curl -X POST http://localhost:8080/a2a \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tasks/send",
"params": {
"message": {
"role": "user",
"parts": [{"type": "text", "text": "Analyze this dataset"}]
},
"target_agent_id": "agent-uuid-here"
}
}'Supported methods: message/send, tasks/get, tasks/cancel, tasks/resubscribe (A2A v1.0), plus tasks/send retained for v0.3 back-compat.
message/send takes a { message: { role, parts[], messageId } } param and returns a v1.0 Task.
Optional AgentTrust ID header: X-Session-ID
- When present, the runtime treats the call as a delegated-session request and authorizes it against the referenced AgentTrust ID session.
- When absent, the runtime still performs AgentTrust ID authorization by resolving the acting agent from the request or persisted task.
POST /a2a/agents/{agentID} — Per-Agent Endpoint
Routes to a specific target agent. The agentID is automatically injected into tasks/send params and is also used as an actor hint for AgentTrust ID when the request itself does not provide one.
curl -X POST http://localhost:8080/a2a/agents/fb18b174-xxxx \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tasks/send",
"params": {
"message": {
"role": "user",
"parts": [{"type": "text", "text": "Run security scan"}]
}
}
}'Message Part Schema
A Message contains a role ("user" or "agent") and an array of parts. Each Part represents a content segment and supports the following types (defined in internal/a2a/types.go):
| Field | Type | Description |
|---|---|---|
type | string | Part type: "text", "data", or "file" |
text | string | Text content (used when type is "text") |
data | object | Structured data as key-value pairs (used when type is "data") |
mimeType | string | MIME type of the content (e.g., "application/json", "image/png") |
uri | string | URI reference to external content (used when type is "file") |
Examples:
// Text part
{"type": "text", "text": "Analyze this dataset"}
// Data part with structured payload
{"type": "data", "data": {"key": "value", "count": 42}, "mimeType": "application/json"}
// File part with URI reference
{"type": "file", "uri": "https://storage.example.com/report.pdf", "mimeType": "application/pdf"}GET /a2a/agents/{agentID}/agent.json — Public Agent Card
Returns the published Agent Card for A2A discovery (no authentication required). Source: internal/agentcard/handler.go.
The response is the raw AgentCard JSON (not wrapped in a StoredCard envelope). The server sets Cache-Control: public, max-age=300 (5-minute cache) to allow CDN/proxy caching of public cards.
curl http://localhost:8080/a2a/agents/fb18b174-xxxx/agent.jsonResponse (200 OK):
{
"name": "Security Scanner Agent",
"description": "Scans for security vulnerabilities",
"version": "1.0.0",
"supportedInterfaces": [
{
"url": "https://example.com/a2a/agents/fb18b174-xxxx",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
}
],
"provider": {
"organization": "Celeste",
"url": "https://celeste.example.com"
},
"capabilities": {
"streaming": false,
"pushNotifications": false,
"extensions": [
{
"uri": "https://agenttrust.id/ext/trust/v1",
"params": {
"ati_trust_score": 0.85,
"ati_guardian_tier": "spot",
"entity_type": "ai_agent"
}
}
]
},
"securitySchemes": {
"bearer": {"type": "http", "scheme": "bearer"}
},
"securityRequirements": [{"bearer": []}],
"defaultInputModes": ["text/plain", "application/json"],
"defaultOutputModes": ["text/plain", "application/json"],
"skills": [
{
"id": "security_scan",
"name": "Security Scan",
"description": "Run a security vulnerability scan",
"tags": ["security", "scan"]
}
],
"signatures": [
{
"protected": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpPU0UifQ",
"signature": "<base64url-ed25519-signature>"
}
]
}Agent Card Storage Model
Internally, agent cards are persisted as StoredCard records (defined in internal/agentcard/types.go):
| Field | Type | Description |
|---|---|---|
agent_id | string | UUID of the owning agent |
card | AgentCard | The full Agent Card structure |
card_hash | string | SHA hash of the card JSON for change detection |
jws_signature | string | Optional JWS compact serialization (Ed25519) for trust verification |
version | int | Card version number, incremented on each regeneration |
published | bool | Whether the card is publicly accessible via the /agent.json endpoint |
created_at | timestamp | When the card was first generated |
updated_at | timestamp | When the card was last modified |
The jws_signature field is optional — cards can be published without JWS for internal-only deployments. When JWS is present, remote agents can verify the card’s authenticity via the Ed25519 signature.
v1.0 cards carry in-card signatures[] (RFC 8785 JCS-canonicalized detached EdDSA JWS); the legacy jws_signature sidecar remains for back-compat.
Agent Card Management Endpoints (Auth Required)
These endpoints manage the card lifecycle and require authentication:
POST /api/v1/agents/{agentID}/card— Generate a new Agent Card (returns 201)GET /api/v1/agents/{agentID}/card— Get the stored card for an agentPUT /api/v1/agents/{agentID}/card/publish— Publish the card (makes it accessible at the public/agent.jsonURL)
Delegation REST API
AgentTrust ID provides HTTP endpoints for managing delegations between agents. Delegations define what actions a parent agent authorizes a child agent to perform on its behalf. These routes are registered in internal/a2a/delegation_handler.go.
POST /api/v1/delegations — Create Delegation
Creates a new delegation from one agent to another. The delegation’s scope becomes the session scope ceiling for the child agent, meaning the child can never exceed the granted actions.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
from_agent_id | string (UUID) | Yes | Parent agent granting the delegation |
to_agent_id | string (UUID) | Yes | Child agent receiving the delegation |
scope | string[] | Yes | Actions the child is allowed to perform |
restrictions | object | No | Additional constraints (key-value pairs) |
parent_delegation_id | string (UUID) | No | Extends an existing delegation chain (scope must be a subset of parent) |
ttl_seconds | int | No | Time-to-live in seconds (default: 3600 = 1 hour) |
curl -X POST http://localhost:8080/api/v1/delegations \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"from_agent_id": "parent-agent-uuid",
"to_agent_id": "child-agent-uuid",
"scope": ["web_search", "read_file"],
"restrictions": {"max_results": 10},
"ttl_seconds": 1800
}'Response (201 Created):
{
"delegation": {
"id": "delegation-uuid",
"from_agent_id": "parent-agent-uuid",
"to_agent_id": "child-agent-uuid",
"org_id": "org-uuid",
"scope": ["web_search", "read_file"],
"restrictions": {"max_results": 10},
"delegation_chain": [],
"expires_at": "2026-03-05T13:30:00Z",
"created_at": "2026-03-05T13:00:00Z"
}
}Validation rules:
from_agent_idandto_agent_idare required and must differ (cannot delegate to self)scopemust not be empty- If
parent_delegation_idis set, the requested scope must be a subset of the parent delegation’s scope (scope narrowing enforcement)
GET /api/v1/delegations — List Delegations
Returns all delegations for the authenticated organization.
curl http://localhost:8080/api/v1/delegations \
-H "Authorization: Bearer sk_live_..."Response (200 OK):
{
"delegations": [
{
"id": "delegation-uuid",
"from_agent_id": "parent-agent-uuid",
"to_agent_id": "child-agent-uuid",
"org_id": "org-uuid",
"scope": ["web_search", "read_file"],
"restrictions": {},
"delegation_chain": [],
"expires_at": "2026-03-05T13:30:00Z",
"created_at": "2026-03-05T13:00:00Z"
}
]
}Returns an empty array if no delegations exist.
DELETE /api/v1/delegations/{delegationID} — Revoke Delegation
Revokes an existing delegation. The delegation is marked with a revoked_at timestamp and any associated sessions lose their authorization.
curl -X DELETE http://localhost:8080/api/v1/delegations/delegation-uuid \
-H "Authorization: Bearer sk_live_..."Response (200 OK):
{
"status": "revoked"
}Returns 404 if the delegation is not found or does not belong to the authenticated organization.
POST /api/v1/delegations/{delegationID}/session — Initialize Delegated Session
Creates an AgentTrust ID session from an existing delegation. This is the runtime entry point for delegated A2A work that should remain bound to the delegation ceiling.
curl -X POST http://localhost:8080/api/v1/delegations/delegation-uuid/session \
-H "Authorization: Bearer sk_live_..." \
-H "X-Org-ID: org-uuid"Response (201 Created):
{
"session_id": "session-uuid",
"mode": "read_only",
"scope_ceiling": ["web_search", "read_file"],
"source": "a2a",
"agent_id": "child-agent-uuid",
"delegation_id": "delegation-uuid",
"created_at": "2026-03-07T13:00:00Z"
}Validation rules:
- Delegation must exist in the authenticated org.
- Delegation must not be expired or revoked.
- Self-delegation is rejected.
- Any parent chain must still validate under continuity and scope-narrowing rules.
Trust Verification
AgentTrust ID verifies remote agents before allowing A2A communication:
- Fetch Agent Card — HTTP GET to the remote agent’s card URL
- JWS Verification — Verify Ed25519 compact serialization signature
- Trust Score Check — Ensure agent meets minimum trust threshold
verifier := a2a.NewTrustVerifier(signer)
card, err := verifier.VerifyRemoteAgent(ctx, "https://remote.example.com/agent.json")
err = verifier.VerifyTrustLevel(card, 0.7) // minimum trust scoreSDK Examples
Python
from agenttrustid import AgentTrustClient
client = AgentTrustClient(api_key="sk_live_...")
# Send a task to another agent
result = client.a2a.send_task(
target_agent_id="target-agent-uuid",
message="Analyze this dataset",
)
# Check task status
task = client.a2a.get_task(task_id=result["id"])TypeScript
import { AgentTrustClient } from "@agenttrustid/sdk";
const client = new AgentTrustClient({ apiKey: "sk_live_..." });
const task = await client.a2a.sendTask({
targetAgentId: "target-agent-uuid",
message: "Analyze this dataset",
});
const status = await client.a2a.getTask(task.id);JSON-RPC Error Codes
| Code | Meaning |
|---|---|
| -32700 | Parse error (malformed JSON) |
| -32600 | Invalid JSON-RPC version or standard denial from the MCP-style proxy path |
| -32601 | Method not found |
| -32602 | Invalid params |
| -32000 | Server or authorization processing error |
| -32001 | Elevation required (approval_id returned in error data) |
| -32002 | Authorization denied |
AgentTrust ID Session Context
AgentTrust ID introduces session-based authorization for delegated agent actions.
Delegation — Session Mapping
When an agent creates a delegation, the delegation scope defines the session’s scope ceiling:
Delegation {
Scope: ["web_search", "read_file"] --> Session.ScopeCeiling: ["web_search", "read_file"]
ParentAgentID: "parent-agent" --> Session.ParentAgentID: "parent-agent"
}The child agent’s session cannot exceed the delegated scope. If the parent grants ["web_search", "read_file"], the child cannot call write_file even if separately authorized.
Effect Classification in Delegated Actions
Effect classification does not happen in the A2A adapter. The A2A adapter (internal/a2a/adapter.go) translates the delegation context into an AgentTrustRequest and calls UnifiedChecker.Check(). The UnifiedChecker (internal/agenttrust/check.go) then uses EffectClassifier (internal/agenttrust/effect.go) to classify the action if ActionEffect is not already set by the adapter.
Classification is based on pattern matching against the action name (case-insensitive):
| Effect | Patterns (substring match) | Guardian Route |
|---|---|---|
destructive | delete, drop, destroy, purge, terminate, remove, truncate | Fast Guard + Full Guardian (Spot + Deep) |
admin | admin, transfer_ownership, revoke, escalate, grant, impersonate | Fast Guard + Full Guardian (Spot + Deep) |
mutating | write, update, create, execute, invoke, modify, send, put, post, commit, push, deploy | Fast Guard + Spot Guard |
read | get, list, read, describe, search, view, fetch, query, head | Fast Guard only |
Unknown action names default to mutating (safe default — requires higher guard tier). Patterns are checked in priority order: destructive, admin, mutating, read.
Routing through the Guardian pipeline:
readactions proceed through Fast Guard onlymutatingactions go through Fast Guard as a prefilter, then Spot Guarddestructiveandadminactions go through Fast Guard as a prefilter, then the full Guardian pipeline (Spot + Deep). If Guardian is unavailable, these actions fail closed (denied by default)
If the delegation session is read_only and the child attempts a mutating or destructive action, an elevation request is created via the ApprovalGate — same flow as MCP sessions.
Session Metrics
A2A sessions track the same metrics as MCP sessions. Metrics are not collected in the A2A adapter itself — they are recorded by the AgentTrust ID session layer (internal/agenttrust/session.go) via SessionManager.RecordCall(). After each authorization check, UnifiedChecker.Check() calls RecordCall() with the effect classification and the allow/deny result. The A2A adapter only initiates the session and delegates action checks to UnifiedChecker.
Tracked metrics per session:
total_calls: All authorization checks (incremented on every call)read_calls: Actions classified asreadeffectwrite_calls: Actions classified asmutating,destructive, oradmineffectdenied_calls: Authorization checks that were denied
RecordCall() also handles automatic elevation expiry — if a session’s elevated_until timestamp has passed, the session reverts to read_only mode and the elevation scope is cleared.
These metrics feed into anomaly detection and audit logging.
Scope Enforcement
Scope enforcement follows three layers:
- Delegation scope — session scope_ceiling (immutable)
- Allowed actions — subset the child can use
- Elevation scope — temporarily granted for specific actions
The A2A runtime now has two mediated paths:
- Delegated-session path:
POST /api/v1/delegations/{delegationID}/sessioncreates the session, then A2A calls includeX-Session-IDand are authorized throughA2AAdapter.CheckDelegatedAction(). - Direct live path: A2A JSON-RPC requests without a delegated session still resolve the acting agent and call
UnifiedChecker.Check()before task mutation or retrieval.
Agent Cards
A2A Agent Cards for agent discovery, with optional Ed25519 JWS signing support.
Overview
An Agent Card is a JSON document that describes an agent’s identity, capabilities, and trust posture. AgentTrust ID generates cards from agent data, stores a content hash, and can publish them for A2A discovery. If the Agent Card service is configured with a signer, the stored card also includes an Ed25519 JWS signature.
Lifecycle
Card Structure
{
"name": "security-scanner",
"description": "Automated security scanning agent",
"version": "1.0.0",
"supportedInterfaces": [
{
"url": "https://api.agenttrust.id/a2a/agents/agent-uuid",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
}
],
"provider": {
"organization": "AgentTrust ID",
"url": "https://agenttrust.id"
},
"capabilities": {
"streaming": false,
"pushNotifications": false,
"extensions": [
{
"uri": "https://agenttrust.id/ext/trust/v1",
"params": {
"ati_trust_score": 0.85,
"ati_guardian_tier": "fast+spot+deep",
"entity_type": "ai_agent"
}
}
]
},
"securitySchemes": {
"bearer": {"type": "http", "scheme": "bearer"}
},
"securityRequirements": [{"bearer": []}],
"defaultInputModes": ["text/plain", "application/json"],
"defaultOutputModes": ["text/plain", "application/json"],
"skills": [
{"id": "web_search", "name": "web_search", "description": "Search the web", "tags": ["search", "web"]},
{"id": "code_analysis", "name": "code_analysis", "tags": ["code"]}
],
"signatures": [
{
"protected": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpPU0UifQ",
"signature": "<base64url-ed25519-signature>"
}
]
}Optional fields: description (on AgentCard), provider.url, and skill.description are all optional and omitted from JSON when empty. Every skill carries a tags array.
URL format: The supportedInterfaces[0].url field is constructed as {baseURL}/a2a/agents/{agentID} by the card generator.
Optional Ed25519 JWS Signing
When a signer is configured, cards are signed using JWS compact serialization with Ed25519 (EdDSA algorithm):
base64url(header) . base64url(payload) . base64url(signature)Header:
{"alg": "EdDSA", "typ": "JWT"}The signature covers base64url(header) + "." + base64url(payload), signed with the server’s Ed25519 private key. If no signer is configured, the jws_signature field is omitted.
Security Policy Fields
AgentTrust ID extends the standard Agent Card with security metadata:
| Field | Description |
|---|---|
ati_trust_score | Reputation score from 0.0 to 1.0 (default 0.5) |
ati_guardian_tier | Guardian check tiers: fast+spot+deep |
entity_type | Agent type: ai_agent, human, service |
API Reference
POST /api/v1/agents/{agentID}/card — Generate Card
Builds an Agent Card from the agent’s current data and stores it. If the service has a signer configured, the stored card includes a JWS signature. The agent must have status = "active" or the request returns 409 Conflict.
curl -X POST http://localhost:8080/api/v1/agents/fb18b174-xxxx/card \
-H "Authorization: Bearer sk_live_..."Response (201):
{
"agent_id": "fb18b174-xxxx",
"card": { "...card fields..." },
"card_hash": "sha256-hex",
"jws_signature": "eyJhbGciOiJFZERTQSJ9.eyJuYW1l...",
"version": 1,
"published": false,
"created_at": "2026-03-01T12:00:00Z",
"updated_at": "2026-03-01T12:00:00Z"
}Each call increments the version number by 1 (via ON CONFLICT ... version = agent_cards.version + 1). The card hash changes when agent data changes.
Error responses:
| Status | Condition |
|---|---|
400 | Invalid UUID format for agentID |
404 | Agent not found for the authenticated org |
409 | Agent exists but is not active |
GET /api/v1/agents/{agentID}/card — Retrieve Card
Returns the stored card regardless of published status (published or unpublished). Requires authentication.
curl http://localhost:8080/api/v1/agents/fb18b174-xxxx/card \
-H "Authorization: Bearer sk_live_..."Error responses:
| Status | Condition |
|---|---|
400 | Invalid UUID format for agentID |
404 | Agent card not found |
PUT /api/v1/agents/{agentID}/card/publish — Publish Card
Makes the card discoverable at the public A2A endpoint. The agent must belong to the authenticated organization — the SQL validates agent_id IN (SELECT id FROM agents WHERE org_id = $2).
curl -X PUT http://localhost:8080/api/v1/agents/fb18b174-xxxx/card/publish \
-H "Authorization: Bearer sk_live_..."Error responses:
| Status | Condition |
|---|---|
400 | Invalid UUID format for agentID or missing org ID |
404 | Agent card not found or agent does not belong to org |
GET /a2a/agents/{agentID}/agent.json — Public Discovery
No authentication required. Only returns published cards. Returns 404 for unpublished or nonexistent cards.
Important: This endpoint returns the raw AgentCard JSON directly (the card_json column contents), NOT the StoredCard wrapper. The response does not include agent_id, card_hash, jws_signature, version, or published fields — only the AgentCard fields like name, url, provider, skills, etc.
Cache headers: Cache-Control: public, max-age=300 (5-minute cache).
curl http://localhost:8080/a2a/agents/fb18b174-xxxx/agent.jsonExample response:
{
"name": "security-scanner",
"version": "1.0.0",
"supportedInterfaces": [
{"url": "https://api.agenttrust.id/a2a/agents/fb18b174-xxxx", "protocolBinding": "JSONRPC", "protocolVersion": "1.0"}
],
"provider": {"organization": "AgentTrust ID"},
"capabilities": {
"streaming": false,
"pushNotifications": false,
"extensions": [
{"uri": "https://agenttrust.id/ext/trust/v1", "params": {"ati_trust_score": 0.85, "ati_guardian_tier": "fast+spot+deep", "entity_type": "ai_agent"}}
]
},
"securitySchemes": {"bearer": {"type": "http", "scheme": "bearer"}},
"securityRequirements": [{"bearer": []}],
"defaultInputModes": ["text/plain", "application/json"],
"defaultOutputModes": ["text/plain", "application/json"],
"skills": [{"id": "web_search", "name": "web_search", "tags": ["search", "web"]}],
"signatures": [
{"protected": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpPU0UifQ", "signature": "<base64url-ed25519-signature>"}
]
}Source: handler.go:GetPublicCard writes card.CardJSON directly — see w.Write(card.CardJSON) at line 151.
Trust Verification
Remote agents can verify a card’s authenticity:
verifier := a2a.NewTrustVerifier(signer)
// Fetch a card and verify JWS when the response includes a signature wrapper
card, err := verifier.VerifyRemoteAgent(ctx,
"https://remote.example.com/a2a/agents/uuid/agent.json")
// Check minimum trust score
err = verifier.VerifyTrustLevel(card, 0.7)The verifier handles both response formats (since the public endpoint returns raw AgentCard JSON, but other sources may return the full StoredCard wrapper):
- Fetches the card via HTTP GET
- Attempts to parse the response as a
StoredCardwrapper (which includesjws_signature) - If a JWS signature is found and a signer is available, verifies the Ed25519 signature and returns the authenticated
AgentCard - If no JWS wrapper is present, falls back to parsing the response as a raw
AgentCarddirectly
Note: Because the public discovery endpoint (/a2a/agents/{agentID}/agent.json) returns raw AgentCard JSON without the JWS wrapper, the verifier will use the fallback path (step 4) when fetching from public endpoints. JWS verification only occurs when the response includes the StoredCard wrapper with a jws_signature field.
Card Generation Details
When generating a card, AgentTrust ID:
- Validates the agent belongs to the authenticated organization (
org_idmatch) - Requires
agent.status = "active"— returns an error if the agent is inactive, suspended, or revoked - Queries the agent’s name, framework, entity type, and capabilities from the DB
- Fetches the reputation score from
agent_reputation(defaults to 0.5) - Maps capabilities to A2A skills
- Constructs the URL as
{baseURL}/a2a/agents/{agentID} - Signs the serialized card JSON with the server’s Ed25519 key when a signer is configured
- Computes a SHA-256 content hash for change detection
- Upserts into
agent_cardstable with version tracking (version increments by 1 on each call)
Authenticated vs Public Card Retrieval
| Endpoint | Auth Required | Returns |
|---|---|---|
GET /api/v1/agents/{agentID}/card | Yes | Any card (published or unpublished), as StoredCard wrapper |
GET /a2a/agents/{agentID}/agent.json | No | Only published cards, as raw AgentCard JSON |