Agent DocsDeveloper documentation
Builder Guide

A2A Protocol & Client SDK

Implement the A2A protocol with governance extension

Agent Communication Protocol Guide

How users, frontends, agents, and tools communicate on the platform — using AG-UI, A2A, A2UI, and MCP.


Protocol Architecture

The platform uses four protocols. They are not layers on top of each other — they serve different communication directions:

Scroll to zoom · Drag to pan · 100%

Protocol Roles

ProtocolSpecDirectionPurpose
AG-UIAG-UI ProtocolApp ↔ AgentHow the frontend and agent talk to each other (transport, events, streaming, sessions)
A2UIA2UI v0.8Agent → AppWhat UI to render — declarative, structured surfaces (forms, selectors, cards)
A2AGoogle A2A v0.3+Agent ↔ AgentHow agents talk to each other (discovery, task delegation, JSON-RPC)
MCPModel Context ProtocolAgent ↔ ToolsHow agents access tools, APIs, and data sources

Key distinctions:

  • AG-UI = how app and agent communicate (event-based streaming protocol)
  • A2UI = what gets rendered (declarative generative-UI language, travels inside AG-UI CUSTOM events)
  • A2A = how agents talk to other agents (JSON-RPC task management)
  • MCP = how agents use tools and data (tool registry, credential resolution)

How it maps to our platform

Scroll to zoom · Drag to pan · 100%
EndpointProtocolClientPurpose
POST /api/chat/:agentId/messageAG-UIPortal, UI clientsUser ↔ Agent interaction with streaming
POST /api/a2a/v1A2AOther agents, programmatic clientsAgent ↔ Agent task delegation
POST {external-agent}/ag-uiAG-UIPlatform (as client)Platform ↔ External agent execution
GET /.well-known/agents/:id.jsonA2ADiscoveryAgent Card for capabilities & auth

A2A Extension Declarations

Both A2UI and Governed Execution register as A2A extensions in the Agent Card, so other agents (and clients) know what capabilities are available:

{
  "extensions": [
    { "uri": "urn:agentic-platform:governed-execution:v1", "required": true },
    { "uri": "https://a2ui.org/a2a-extension/a2ui/v0.8" }
  ]
}

1. Authentication & Access Control

Every request to the platform — A2A, AG-UI, or AG-UI/A2A turn-based — passes through the same auth middleware pipeline before reaching any route handler.

Auth Middleware Pipeline

Scroll to zoom · Drag to pan · 100%

Authentication Methods

The platform supports four authentication methods, evaluated in priority order:

MethodToken FormatUse CaseValidation
API KeyBearer ak_*Programmatic clients, CI/CD, external systemsSHA-256 hash lookup in api_keys table
A2A Invocation SecretBearer a2a_inv_*Agent-to-agent callsSHA-256 hash lookup in agent config.a2aConfig.invocationSecretHash
JWT (Keycloak/OIDC)Bearer eyJ... (RS256)Production — SSO via corporate IdPJWKS public key from KEYCLOAK_JWKS_URL
JWT (Platform)Bearer eyJ... (HS256)Portal/Studio/Admin frontendsSigned with NEXTAUTH_SECRET

In development mode, mock headers are also accepted (X-User-Id, X-User-Role, X-Tenant-Id, X-User-Groups).

How to Authenticate an A2A Request

Option A: API Key (recommended for programmatic clients)

curl -X POST https://platform.example.com/api/a2a/v1 \
  -H "Authorization: Bearer ak_your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "req-1",
    "method": "agent/sendMessage",
    "params": {
      "agentId": "agent-research-001",
      "message": { "role": "user", "parts": [{ "type": "text", "text": "Hello" }] }
    }
  }'

The platform resolves the API key to a user context:

  • Tenant-wide keys (ownerType='tenant'): grants admin role within the tenant
  • User-bound keys (ownerType='user'): inherits the user's role and groups
  • Service account keys (ownerType='service-account'): inherits the SA's role and groups

Option B: A2A Invocation Secret (agent-to-agent)

When one agent invokes another via A2A, it authenticates with the target agent's invocation secret:

curl -X POST https://platform.example.com/api/a2a/v1 \
  -H "Authorization: Bearer a2a_inv_target-agent-secret" \
  -H "Content-Type: application/json" \
  -d '{ "jsonrpc": "2.0", "id": "1", "method": "agent/sendMessage", ... }'

This resolves to a synthetic identity: userId = "agent:{agentId}", role = "consumer", idpSource = "a2a-secret".

Option C: JWT (Portal frontend / SSO)

The Portal frontend authenticates via NextAuth.js, which sets an access-token httpOnly cookie or provides a Bearer token:

curl -X POST https://platform.example.com/api/a2a/v1 \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{ "jsonrpc": "2.0", "id": "1", "method": "agent/sendMessage", ... }'

JWT claims used: sub (userId), email, name, role, tenant_id, groups, idp_source.

Access Control (4 Dimensions)

After authentication, every agent interaction is checked against four access control dimensions:

Scroll to zoom · Drag to pan · 100%

D1 (User → Agent) is checked at request time in the route handler. D2-D4 are checked during workflow execution by the MCP Gateway and LLM Gateway.

Agent Card Security Schemes

Agents declare their authentication requirements in the A2A Agent Card:

{
  "name": "Research Agent",
  "url": "https://platform.example.com/api/a2a/v1",
  "securitySchemes": {
    "spiffe": {
      "type": "spiffe",
      "description": "SPIFFE workload identity for agent-to-agent authentication"
    },
    "bearer": {
      "type": "http",
      "scheme": "bearer",
      "description": "Bearer token (API key, JWT, or A2A invocation secret)"
    }
  },
  "security": [{ "bearer": [] }],
  "extensions": [
    {
      "uri": "urn:agentic-platform:governed-execution:v1",
      "required": true
    }
  ]
}

Token Revocation

JWT tokens can be revoked via JTI-based blocklist stored in Redis. Revoked tokens are rejected by the auth middleware even if the signature is valid.

Governance Metadata in A2A Requests

The metadata field in SendMessageParams carries identity context for governance checks:

{
  "params": {
    "agentId": "agent-research-001",
    "message": { ... },
    "metadata": {
      "userId": "user-456",        // D1 checks
      "tenantId": "tenant-1",      // Multi-tenancy scoping
      "userGroups": ["analysts"],   // Entitlement filtering
      "userRole": "consumer",      // Role-based checks
      "delegationChain": ["agent-coordinator-001"],  // D3 delegation tracking
      "conversationId": "conv-abc" // Context grouping
    }
  }
}

Note: When authenticating via API key or JWT, the platform resolves userId, tenantId, userRole, and userGroups from the token — the client does not need to provide them in metadata. The metadata fields are primarily for agent-to-agent delegation context (D3) and explicit conversation binding.


2. A2A Protocol — JSON-RPC Methods (Agent-to-Agent)

A2A is used for programmatic and agent-to-agent communication. UI clients typically use the AG-UI endpoint instead (see Section 4).

agent/sendMessage (Non-Streaming)

// Request
{
  "jsonrpc": "2.0",
  "id": "req-1",
  "method": "agent/sendMessage",
  "params": {
    "agentId": "agent-research-001",
    "message": {
      "role": "user",
      "parts": [{ "type": "text", "text": "Research quantum computing" }]
    },
    "contextId": "conv-abc",
    "metadata": {
      "userId": "user-456",
      "tenantId": "tenant-1"
    }
  }
}

// Response — A2A Task object
{
  "jsonrpc": "2.0",
  "id": "req-1",
  "result": {
    "id": "task-0001",
    "contextId": "conv-abc",
    "status": { "state": "working" },
    "history": [
      { "role": "user", "parts": [{ "type": "text", "text": "Research quantum computing" }] }
    ],
    "metadata": {
      "agentId": "agent-research-001",
      "tenantId": "tenant-1",
      "protocol": "a2a"
    }
  }
}

agent/sendStreamingMessage (Streaming)

Same request format. Returns an SSE stream with A2A task status events:

data: {"type":"status","taskId":"task-0001","status":{"state":"working"},"final":false}

data: {"type":"status","taskId":"task-0001","status":{"state":"working","message":{"role":"agent","parts":[{"type":"text","text":"Searching..."}]}},"final":false}

data: {"type":"artifact","taskId":"task-0001","artifact":{"id":"art-1","name":"summary.md","parts":[{"type":"text","text":"# Summary\n..."}]}}

data: {"type":"status","taskId":"task-0001","status":{"state":"completed","message":{"role":"agent","parts":[{"type":"text","text":"Done."}]}},"final":true}

Note: A2A streaming uses coarse-grained task status events. For token-level streaming, tool call lifecycle, and interactive UI surfaces, use the AG-UI endpoint (Section 4).

agent/getTask

Poll for task status (when streaming is not used):

{ "jsonrpc": "2.0", "id": "2", "method": "agent/getTask", "params": { "taskId": "task-0001" } }

agent/cancelTask

Cancel a running task. Sends a cancelWorkflowSignal to Temporal:

{ "jsonrpc": "2.0", "id": "3", "method": "agent/cancelTask", "params": { "taskId": "task-0001" } }

agent/listTasks

Query tasks with filtering:

{
  "jsonrpc": "2.0", "id": "4",
  "method": "agent/listTasks",
  "params": { "contextId": "conv-abc", "states": ["completed"], "limit": 20 }
}

3. Task State Machine

The A2A task state machine applies to all agent executions. AG-UI clients see the same state transitions reflected as RUN_STARTEDRUN_FINISHED / RUN_ERROR lifecycle events.

Scroll to zoom · Drag to pan · 100%

4. AG-UI — User-Facing Interaction Protocol

AG-UI is the primary protocol for user-facing clients (Portal, mobile apps, custom frontends). It is a separate protocol from A2A — not a layer on top of it.

  • A2A = agent-to-agent (JSON-RPC, task-oriented, coarse-grained status updates)
  • AG-UI = app-to-agent (HTTP POST + SSE, event-based, token-level streaming)

AG-UI Endpoint

POST /api/chat/:agentId/message
Content-Type: application/json
Accept: text/event-stream

Body: RunAgentInput
Response: SSE stream of AG-UI events
// RunAgentInput — what the client sends
interface RunAgentInput {
  threadId: string;           // Conversation ID
  runId: string;              // Unique run identifier
  messages: AGUIMessage[];    // Full message history
  tools?: AGUIToolDef[];      // Available tools (optional)
  state?: Record<string, unknown>;  // Shared state from previous runs
}

How it works internally

Scroll to zoom · Drag to pan · 100%

The Temporal workflow emits internal platform events to Redis. The SSE bridge translates them to AG-UI events:

Platform EventAG-UI Event
workflow_startedRUN_STARTED
tokenTEXT_MESSAGE_CONTENT (per-token delta)
tool_callTOOL_CALL_START + TOOL_CALL_ARGS
tool_resultTOOL_CALL_END
workflow_stepSTEP_STARTED / STEP_FINISHED
workflow_pausedCUSTOM hitl_paused + CUSTOM a2ui
hitl_response_receivedCUSTOM hitl_resumed + CUSTOM a2ui (deleteSurface)
doneCUSTOM token_usage + RUN_FINISHED
workflow_failedRUN_ERROR

Comparison: AG-UI vs. A2A for the same execution

AG-UI (Portal)A2A (Agent-to-Agent)
EndpointPOST /api/chat/:agentId/messagePOST /api/a2a/v1
RequestRunAgentInput (threadId, messages, tools, state)JSON-RPC agent/sendStreamingMessage (agentId, message, contextId)
Text streamingTEXT_MESSAGE_CONTENT per tokenAccumulated in status.message
Tool callsTOOL_CALL_STARTTOOL_CALL_ARGSTOOL_CALL_ENDEmbedded in status.message
HITLCUSTOM hitl_paused + A2UI surfacesstatus.state = "input-required" (no UI)
StepsSTEP_STARTED / STEP_FINISHEDNot visible
CompletionRUN_FINISHED with token usagestatus.state = "completed" (final: true)

AG-UI Event Flow Example

SSE Stream (protocol=ag-ui):
───────────────────────────────────────────────────

1.  RUN_STARTED          { runId, threadId }

2.  TEXT_MESSAGE_CONTENT  { delta: "Let me " }
3.  TEXT_MESSAGE_CONTENT  { delta: "search for that..." }

4.  TOOL_CALL_START       { toolCallId: "tc-1", toolCallName: "web_search" }
5.  TOOL_CALL_ARGS        { toolCallId: "tc-1", delta: '{"query":"quantum"}' }
6.  TOOL_CALL_END         { toolCallId: "tc-1" }

    ← Platform executes tool via MCP Gateway →

7.  TEXT_MESSAGE_CONTENT  { delta: "I found 5 results. " }
8.  TEXT_MESSAGE_CONTENT  { delta: "Here's a summary..." }

9.  CUSTOM                { name: "urn:...governance:v1:token_usage", value: { input: 800, output: 350 } }
10. RUN_FINISHED          { result: { tokenUsage: {...} } }

CUSTOM Event Namespaces

AG-UI's CUSTOM event is the extension point for all platform-specific behavior:

NameSourcePurpose
a2uiA2UI extensionInteractive UI surfaces (see Section 6)
urn:agentic-platform:governance:v1:hitl_pausedGovernance extensionWorkflow paused for human input
urn:agentic-platform:governance:v1:hitl_resumedGovernance extensionWorkflow resumed after response
urn:agentic-platform:governance:v1:token_usageGovernance extensionToken usage and cost attribution
thinking_start / thinking_completePlatformLLM reasoning (collapsible in UI)
memory_retrieved / memory_storedPlatformAgent memory context
knowledge_retrievedPlatformRAG/knowledge base retrieval
guardrailPlatformGuardrail check result
skills_loadedPlatformSkill bundles loaded for agent

5. Governed Execution Extension

A2A Extension URI: urn:agentic-platform:governed-execution:v1

This is the platform's core A2A extension. It extends A2A's Part system with two new part types that express the "agent declares, platform executes" model. Agents declare these in their Agent Card:

{
  "extensions": [
    {
      "uri": "urn:agentic-platform:governed-execution:v1",
      "name": "Governed Execution",
      "description": "Agent declares tool calls; platform executes through governance pipeline",
      "required": true
    }
  ]
}

Extended A2A Part Types

// Standard A2A parts:
type A2AStandardPart = TextPart | DataPart | FilePart;

// Platform extension parts (via governed-execution extension):
type A2APart = A2AStandardPart | GovernedToolCallPart | GovernedToolResultPart;

GovernedToolCallPart (Agent → Platform)

Agent declares a tool call inside an A2A message:

{
  "type": "governed-tool-call",
  "toolCallId": "tc-12345",
  "toolName": "web_search",
  "arguments": { "query": "quantum computing basics", "limit": 5 }
}

GovernedToolResultPart (Platform → Agent)

Platform returns the result after governance pipeline execution:

{
  "type": "governed-tool-result",
  "toolCallId": "tc-12345",
  "status": "success",
  "output": { "results": [...], "count": 5 },
  "durationMs": 1250,
  "approvalId": "appr-999"
}

Tool Call Flow

Scroll to zoom · Drag to pan · 100%

6. HITL (Human-in-the-Loop) — Complete Flow

HITL maps to A2A's input-required task state. When the workflow needs human input, the A2A task transitions to input-required. AG-UI clients additionally receive CUSTOM events with HITL details and A2UI surfaces for interactive input.

HITL Types

TypeA2UI ComponentUser Action
plan_selectionMulti-option selector (Checkboxes in Cards)Select one or more options
parameter_editEditable form (TextFields, NumberInputs)Modify tool parameters, approve/reject
user_inputText input (TextField)Provide free-form text
approvalAccept/reject card (Buttons)Binary approve or reject

End-to-End Sequence Diagram

Scroll to zoom · Drag to pan · 100%

A2A Client — Same Flow, Different Granularity

An A2A client (another agent, a programmatic client) sees the same execution as coarser-grained task status updates — no token deltas, no A2UI surfaces:

Scroll to zoom · Drag to pan · 100%

HITL API — Responding to Input Requests

AG-UI Client (Portal)

AG-UI clients POST the canonical v0.9 action envelope to the surfaces resolve route. The envelope carries the surface's own submit context verbatim — the platform does not restructure or rename fields, and agents read their own submit context via hitl_response.action.context.<field>. See docs/SDK_GUIDE.md §"HITL Response Contract".

POST /api/surfaces/{surfaceRequestId}/resolve
Content-Type: application/json

# picker surface submit (ChoicePicker):
{
  "action": {
    "kind": "event",
    "protocolVersion": "v0.9",
    "name": "urn:a2ui:submit",
    "surfaceId": "hitl-hitl-wf-001-1",
    "context": { "requestId": "hitl-wf-001-1", "selected": ["plan_b"] }
  },
  "dataModel": null
}

# form surface submit (TextField/NumberField):
{
  "action": {
    "kind": "event",
    "name": "urn:a2ui:submit",
    "surfaceId": "hitl-hitl-wf-001-2",
    "context": { "requestId": "hitl-wf-001-2", "parameters": { "query": "adjusted query", "limit": 10 } }
  }
}

# input surface submit:
{
  "action": {
    "kind": "event",
    "name": "urn:a2ui:submit",
    "surfaceId": "hitl-hitl-wf-001-3",
    "context": { "requestId": "hitl-wf-001-3", "value": "Focus on renewable energy" }
  }
}

# approval response:
{
  "type": "hitl_approval_response",
  "requestId": "hitl-wf-001-4",
  "approved": true,
  "respondedBy": "user-456"
}

Raw A2A Client

A raw A2A client responds by sending a follow-up agent/sendMessage on the same contextId/taskId. The platform routes it as the HITL response:

{
  "jsonrpc": "2.0",
  "id": "req-2",
  "method": "agent/sendMessage",
  "params": {
    "agentId": "agent-research-001",
    "message": {
      "role": "user",
      "parts": [{ "type": "text", "text": "Focus on topics 1 and 3" }]
    },
    "contextId": "conv-abc"
  }
}

7. A2UI — Interactive Surfaces (A2A Extension)

A2A Extension URI: https://a2ui.org/a2a-extension/a2ui/v0.8

A2UI is a separate protocol that registers as an A2A extension and travels inside AG-UI CUSTOM events:

// A2UI messages are wrapped in AG-UI CUSTOM events:
{
  type: "CUSTOM",
  name: "a2ui",           // ← AG-UI custom event name
  value: <A2UIMessage>    // ← A2UI protocol message
}

A2UI provides platform-agnostic UI definitions that agents can generate. Clients render them using native widgets. The protocol uses an adjacency-list model where components reference children by ID.

Surface Lifecycle

Scroll to zoom · Drag to pan · 100%

A2UI Message Types (Server → Client)

MessagePurpose
surfaceUpdateCreate or update components in a surface (adjacency list)
dataModelUpdateUpdate data model values (bindings for dynamic content)
beginRenderingSignal that surface is ready to render from root component
deleteSurfaceRemove a surface (after HITL resolved)

A2UI Message Types (Client → Server)

MessagePurpose
userActionUser interacted with a component (button click, form submit)
clientErrorClient-side rendering error

Component Types (Standard Catalog v0.8)

Layout:       Column · Row · Card · Tabs · Modal · List
Display:      Text · Image · Icon · Divider · ProgressBar
Input:        TextField · CheckBox · MultipleChoice · Slider
Interaction:  Button (with ActionDef → triggers userAction)

Example: Plan Selector Surface

{
  "surfaceUpdate": {
    "surfaceId": "plan-sel-1",
    "components": [
      {
        "id": "root",
        "component": {
          "Column": {
            "children": { "explicitList": ["header", "option_1", "option_2", "submit"] }
          }
        }
      },
      {
        "id": "header",
        "component": {
          "Text": {
            "text": { "literalString": "Select a research direction:" },
            "usageHint": "heading"
          }
        }
      },
      {
        "id": "option_1",
        "component": {
          "Card": { "child": "option_1_content" }
        }
      },
      {
        "id": "option_1_content",
        "component": {
          "CheckBox": {
            "label": { "literalString": "Quantum Computing Basics" },
            "value": { "path": "/selections/0" }
          }
        }
      },
      {
        "id": "option_2",
        "component": {
          "Card": { "child": "option_2_content" }
        }
      },
      {
        "id": "option_2_content",
        "component": {
          "CheckBox": {
            "label": { "literalString": "Quantum Error Correction" },
            "value": { "path": "/selections/1" }
          }
        }
      },
      {
        "id": "submit",
        "component": {
          "Button": {
            "label": { "literalString": "Confirm Selection" },
            "action": { "name": "submit_selection", "context": [
              { "key": "selections", "value": { "path": "/selections" } }
            ] }
          }
        }
      }
    ]
  }
}

8. Multi-Turn HITL Flow (Research Agent Example)

A real-world example showing all protocol layers working together. The agent finds 5 research topics, asks the user to select via A2UI, executes tools via Governed Execution, then asks a follow-up question.

Scroll to zoom · Drag to pan · 100%

9. Agent State Management Across Turns

External agents are stateless. The platform manages all state and sends the full context on every turn.

What the agent receives on each turn

interface AgentTurnRequest {
  threadId: string;                    // Conversation ID
  turnNumber: number;                  // 0-indexed turn counter
  messages: AgentMessage[];            // FULL message history (grows each turn)
  toolResults?: GovernedToolResultPart[]; // Results from previous turn's tool calls
  context: Record<string, unknown>;    // Agent's own statePatch from previous turn
  availableTools?: AvailableTool[];    // Only on turn 0 (tools don't change)
}

How state flows

Scroll to zoom · Drag to pan · 100%

The agent never needs a database. The context field acts as the agent's "memory" — the platform persists it and sends it back every turn. The full messages history is also included so the agent can always reconstruct what happened.


10. Error Handling

JSON-RPC Errors (A2A Layer)

CodeNameDescription
-32700Parse ErrorInvalid JSON
-32600Invalid RequestMissing jsonrpc/id/method
-32601Method Not FoundUnknown method
-32602Invalid ParamsMissing required params
-32603Internal ErrorServer error
-32001Task Not FoundUnknown taskId
-32002Task Not CancelableTask in terminal state
-32003Agent Not FoundUnknown agentId
-32004UnauthorizedAuthentication failed
-32005Policy ViolationD2 access denied

AG-UI Error Events

// Workflow failure
{ "type": "RUN_ERROR", "runId": "run-abc", "message": "Tool execution failed", "code": "TOOL_ERROR" }

Governed Execution Rejection

When a tool call is rejected by the governance pipeline:

{
  "type": "governed-tool-result",
  "toolCallId": "tc-1",
  "status": "rejected",
  "rejectionReason": "Agent does not have D2 grant for tool 'database_query'"
}

11. Client Implementation Checklist

Minimal A2A Client (Agent-to-Agent)

  • Send messages via agent/sendMessage or agent/sendStreamingMessage
  • Parse SSE stream — handle data: lines, match type: "status" and type: "artifact"
  • Handle input-required — detect when task needs input, send follow-up sendMessage
  • Handle errors — JSON-RPC error responses and status.state = "failed"
  • Support cancellationagent/cancelTask for abort
  • Poll if neededagent/getTask when SSE is unavailable

Full AG-UI Client (Portal / UI)

Everything above, plus:

  • Negotiate AG-UI — append ?protocol=ag-ui to SSE endpoint
  • Accumulate text deltas — build full message from TEXT_MESSAGE_CONTENT events
  • Track tool calls — match TOOL_CALL_STARTTOOL_CALL_ARGSTOOL_CALL_END by toolCallId
  • Detect HITL pauses — listen for CUSTOM events with hitl_paused name
  • Render A2UI surfaces — parse surfaceUpdate, beginRendering, deleteSurface from CUSTOM "a2ui" events
  • Submit HITL responsesPOST /api/surfaces/:surfaceRequestId/resolve with the canonical v0.9 action envelope ({action: {kind, name, surfaceId, context}, dataModel?})
  • Handle HITL resume — remove A2UI surface, create new message placeholder
  • Display thinking blocks — collapsible reasoning from thinking_start/thinking_complete
  • Show token usage — from token_usage CUSTOM events or RUN_FINISHED result
  • Handle governance events — guardrail results, memory retrieval, skill loading