Skip to Content
ProtocolsAgent-to-Agent (A2A)

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

A2A request sequence Agent A posts a tasks/send request to the AgentTrust ID Gateway. The Gateway verifies the decision integrity chain, checks the trust score, and fetches the Agent Card against Agent B. Agent B returns a task result and the Gateway returns a JSON-RPC response to Agent A. Agent A AgentTrust ID Gateway Agent B POST /a2a (tasks/send) Verify decision integrity chain Check trust score Fetch Agent Card GW: task result --> Task result A: json-rpc response --> JSON-RPC response

Task Lifecycle

Tasks follow a state machine with six states:

A2A task lifecycle A task moves from submitted to working. From working it can reach completed, input-required, canceled, or failed. completed, canceled, and failed are terminal states that cannot be modified. submitted working completed input-required canceled failed Terminal states (completed, canceled, failed) cannot be modified

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):

FieldTypeDescription
typestringPart type: "text", "data", or "file"
textstringText content (used when type is "text")
dataobjectStructured data as key-value pairs (used when type is "data")
mimeTypestringMIME type of the content (e.g., "application/json", "image/png")
uristringURI 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.json

Response (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):

FieldTypeDescription
agent_idstringUUID of the owning agent
cardAgentCardThe full Agent Card structure
card_hashstringSHA hash of the card JSON for change detection
jws_signaturestringOptional JWS compact serialization (Ed25519) for trust verification
versionintCard version number, incremented on each regeneration
publishedboolWhether the card is publicly accessible via the /agent.json endpoint
created_attimestampWhen the card was first generated
updated_attimestampWhen 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 agent
  • PUT /api/v1/agents/{agentID}/card/publish — Publish the card (makes it accessible at the public /agent.json URL)

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:

FieldTypeRequiredDescription
from_agent_idstring (UUID)YesParent agent granting the delegation
to_agent_idstring (UUID)YesChild agent receiving the delegation
scopestring[]YesActions the child is allowed to perform
restrictionsobjectNoAdditional constraints (key-value pairs)
parent_delegation_idstring (UUID)NoExtends an existing delegation chain (scope must be a subset of parent)
ttl_secondsintNoTime-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_id and to_agent_id are required and must differ (cannot delegate to self)
  • scope must not be empty
  • If parent_delegation_id is 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:

  1. Fetch Agent Card — HTTP GET to the remote agent’s card URL
  2. JWS Verification — Verify Ed25519 compact serialization signature
  3. 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 score

SDK 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

CodeMeaning
-32700Parse error (malformed JSON)
-32600Invalid JSON-RPC version or standard denial from the MCP-style proxy path
-32601Method not found
-32602Invalid params
-32000Server or authorization processing error
-32001Elevation required (approval_id returned in error data)
-32002Authorization 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):

EffectPatterns (substring match)Guardian Route
destructivedelete, drop, destroy, purge, terminate, remove, truncateFast Guard + Full Guardian (Spot + Deep)
adminadmin, transfer_ownership, revoke, escalate, grant, impersonateFast Guard + Full Guardian (Spot + Deep)
mutatingwrite, update, create, execute, invoke, modify, send, put, post, commit, push, deployFast Guard + Spot Guard
readget, list, read, describe, search, view, fetch, query, headFast 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:

  • read actions proceed through Fast Guard only
  • mutating actions go through Fast Guard as a prefilter, then Spot Guard
  • destructive and admin actions 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 as read effect
  • write_calls: Actions classified as mutating, destructive, or admin effect
  • denied_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:

  1. Delegation scope — session scope_ceiling (immutable)
  2. Allowed actions — subset the child can use
  3. Elevation scope — temporarily granted for specific actions

The A2A runtime now has two mediated paths:

  • Delegated-session path: POST /api/v1/delegations/{delegationID}/session creates the session, then A2A calls include X-Session-ID and are authorized through A2AAdapter.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

Agent Card lifecycle An Agent Card is generated via POST from agent data and reputation with optional JWS, stored in the database unpublished by default, published via PUT by setting published true, then discovered via GET on the public endpoint with no auth required. Generate (POST) Build from agent data + reputation Optional JWS Store (DB) Unpublished by default Publish (PUT) Set published=true Discover (GET) Public endpoint, no auth required

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:

FieldDescription
ati_trust_scoreReputation score from 0.0 to 1.0 (default 0.5)
ati_guardian_tierGuardian check tiers: fast+spot+deep
entity_typeAgent 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:

StatusCondition
400Invalid UUID format for agentID
404Agent not found for the authenticated org
409Agent 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:

StatusCondition
400Invalid UUID format for agentID
404Agent 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:

StatusCondition
400Invalid UUID format for agentID or missing org ID
404Agent 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.json

Example 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):

  1. Fetches the card via HTTP GET
  2. Attempts to parse the response as a StoredCard wrapper (which includes jws_signature)
  3. If a JWS signature is found and a signer is available, verifies the Ed25519 signature and returns the authenticated AgentCard
  4. If no JWS wrapper is present, falls back to parsing the response as a raw AgentCard directly

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_id match)
  • 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_cards table with version tracking (version increments by 1 on each call)

Authenticated vs Public Card Retrieval

EndpointAuth RequiredReturns
GET /api/v1/agents/{agentID}/cardYesAny card (published or unpublished), as StoredCard wrapper
GET /a2a/agents/{agentID}/agent.jsonNoOnly published cards, as raw AgentCard JSON
Last updated on