Protocols (A2A, AG-UI, A2UI, MCP)
How the 4 interoperability protocols work together with governance
Protocol Implementation Guide
The TSI Agentic Hub uses four interoperability protocols to handle communication across every boundary in the platform. Each protocol serves a distinct purpose:
| Protocol | Boundary | Transport | Spec | Version |
|---|---|---|---|---|
| AG-UI | Agent ↔ Frontend | SSE (Server-Sent Events) | ag-ui-protocol/ag-ui | 1.0 |
| A2A | Agent ↔ Agent | JSON-RPC 2.0 over HTTP | Google A2A v1.0 | 1.0 (with v0.3 compat) |
| A2UI | Agent → User Input | JSONL inside AG-UI CUSTOM events or A2A DataParts | a2ui.org/specification/v0.8 | 0.8 |
| MCP | Agent → Tool | JSON-RPC 2.0 over stdio / SSE / Streamable HTTP | Model Context Protocol | Nov 2025 |
All four protocols are wrapped by a unified governance layer (Cedar policies, SPIFFE identity, OTel tracing, audit logging) so that every cross-boundary call is authorized, attributed, and observable.
Protocol Layering (No Cross-Contamination)
The protocols are cleanly layered — each operates at a specific boundary with no type leakage between layers:
| Layer | Protocol | Responsibility | Never Touches |
|---|---|---|---|
| Frontend ↔ Gateway | AG-UI | SSE event streaming, text deltas, tool call lifecycle | MCP wire format, A2A JSON-RPC |
| Agent ↔ Agent | A2A | Discovery (Agent Cards), task lifecycle (JSON-RPC 2.0) | AG-UI events, MCP transport |
| Agent → User (UI) | A2UI | Declarative UI surfaces for HITL | Direct DOM, framework-specific widgets |
| Agent → Tool | MCP | Tool discovery + execution, credential injection | AG-UI events, A2A task state |
A2UI transport bindings — A2UI surfaces travel via three transports depending on the protocol boundary:
- AG-UI path (Portal):
CUSTOMSSE events withname: "a2ui" - A2A path (agent-to-agent):
DataPartwithmediaType: "application/json+a2ui" - MCP path (embedded resources):
a2ui://URI scheme in MCP resource responses
This separation ensures that protocol-specific concerns (SSE framing, JSON-RPC envelopes, MCP tool schemas) never leak across boundaries.
1. AG-UI (Agent-to-UI Protocol)
AG-UI is the streaming protocol between the API Gateway and the Portal frontend. It uses Server-Sent Events to deliver a structured sequence of typed events as the agent executes.
Event Types
Defined in packages/types/src/ag-ui-events.ts as the AGUIEventType union:
Lifecycle events control the run boundary:
RUN_STARTED-- carriesthreadId(conversation) andrunIdRUN_FINISHED-- signals completion with optionalresultRUN_ERROR-- error withmessageand optionalcodeSTEP_STARTED/STEP_FINISHED-- named sub-steps within a run
Text streaming events deliver the assistant's response token by token:
TEXT_MESSAGE_START-- opens a message withmessageIdandroleTEXT_MESSAGE_CONTENT-- incrementaldeltastring fragmentTEXT_MESSAGE_END-- closes the message
Tool call events follow the "agent declares, platform executes" pattern:
TOOL_CALL_START-- agent declares intent withtoolCallIdandtoolCallNameTOOL_CALL_ARGS-- streamed JSON fragment (accumulate all deltas, then parse)TOOL_CALL_END-- agent finished declaring this tool callTOOL_CALL_RESULT-- platform injects the execution result back
State management events for shared state across segments:
STATE_SNAPSHOT/STATE_DELTA-- full snapshot or RFC 6902 JSON PatchMESSAGES_SNAPSHOT-- full conversation history sync
Activity events for in-progress operation tracking:
ACTIVITY_SNAPSHOT/ACTIVITY_DELTA-- research progress, plan building, etc.
Extension events:
CUSTOM-- namespaced extension point (A2UI messages travel here as{ name: "a2ui", value: <A2UIMessage> })RAW-- pass-through for unstructured data
SSE Emitters
The API Gateway emits AG-UI events via helper functions in services/api-gateway/src/routes/chat-helpers.ts:
// Emit any AG-UI event to the SSE stream
async function emitAGUI(stream: SSEStreamingApi, event: Record<string, unknown>)
// Emit the handshake (first event in every stream)
async function emitHandshake(stream: SSEStreamingApi)
// Sends: { type: "CUSTOM", name: "ag-ui:handshake", value: { version: "1.0", capabilities: ["streaming", "tool_calls", "hitl", "a2ui"] } }
// Emit a complete tool call sequence (START + ARGS + RESULT + END)
async function emitToolCallAGUI(stream: SSEStreamingApi, toolCall: {...}, messageId: string)
Main Chat Endpoint
POST /api/chat/:agentId/message in services/api-gateway/src/routes/chat.ts opens an SSE stream and emits AG-UI events throughout the agent execution lifecycle. The event sequence for a platform-managed agent is:
CUSTOM (ag-ui:handshake)
RUN_STARTED { threadId, runId }
STEP_STARTED { stepName: "llm-inference" }
TEXT_MESSAGE_CONTENT { delta: "..." } // repeated per token
STEP_FINISHED
TOOL_CALL_START { toolCallId, toolCallName }
TOOL_CALL_ARGS { delta: "..." }
TOOL_CALL_RESULT { content: "..." }
TOOL_CALL_END
TEXT_MESSAGE_CONTENT { delta: "..." } // continued response after tool result
RUN_FINISHED { threadId, runId }
External Agent AG-UI Integration
External agents (LangGraph, CrewAI, etc.) implement the AG-UI protocol by exposing a POST /ag-ui endpoint. The platform sends an AGUIRunInput:
interface AGUIRunInput {
threadId: string; // conversation ID
runId: string; // unique run identifier
messages: AGUIMessage[]; // full history including tool results
tools?: AGUIToolDef[]; // available tools the agent can declare
state?: Record<string, unknown>; // shared state from previous segments
clientCapabilities?: { supportedCatalogIds: string[] }; // A2UI support
}
The agent responds with an SSE stream of AGUIEvent[]. The platform intercepts TOOL_CALL_START/ARGS/END events, executes the declared tools through the MCP Gateway (with D2 access control and HITL gates), and injects TOOL_CALL_RESULT back into the stream. This is configured per-agent via externalConfig.agUiEnabled and externalConfig.agUiEndpointUrl in the agent config.
Multi-Segment AG-UI Flow (Temporal Durable Workflows)
The platform's Temporal durable workflow splits agent execution into segments. Each tool call boundary creates a new segment — this gives per-tool retry, HITL gates, and checkpoint/resume semantics.
Critical: STATE_SNAPSHOT must be emitted by the agent (or SDK) to preserve state between segments. Without it, the platform starts each segment with empty state, causing the agent to lose track of its execution phase.
Segment 0 (initial):
Platform → POST /ag-ui { messages: [user msg], state: {} }
Agent → TEXT_MESSAGE_CONTENT ("Searching...")
Agent → TOOL_CALL_START/ARGS/END (web_search)
Agent → STATE_SNAPSHOT { phase: "awaiting_search_results", query: "..." }
Agent → RUN_FINISHED
Platform → Executes web_search via MCP Gateway
Segment 1 (with tool results):
Platform → POST /ag-ui { messages: [user msg, assistant msg, tool result], state: { phase: "awaiting_search_results", query: "..." } }
Agent → TEXT_MESSAGE_CONTENT ("Here are the results...")
Agent → STATE_SNAPSHOT { phase: "awaiting_approval", ... }
Agent → CUSTOM { name: "a2ui", value: { surfaceUpdate: ... } } // A2UI approval surface
Agent → RUN_FINISHED
Message format between segments:
AG-UI messages use { role: string, content: string } format. Tool results arrive as messages with role: "tool" and toolCallId:
// AG-UI message format (what the SDK's /ag-ui endpoint receives)
interface AGUIMessage {
role: 'user' | 'assistant' | 'tool';
content: string; // plain text or JSON string for tool results
toolCallId?: string; // present only for role: 'tool'
}
The SDK converts these to the internal A2A v1.0 format (A2AMessage with content: A2APart[]) and extracts toolResults from role: 'tool' messages before passing them to the agent's onTurn() handler.
Sequence Diagram: AG-UI Chat Flow
Scroll to zoom · Drag to pan · 100%
2. A2A (Agent-to-Agent Protocol) — v1.0
A2A enables agents to invoke other agents. Based on Google's A2A v1.0 specification (JSON-RPC 2.0), extended with platform governance. The platform supports both A2A v1.0 and legacy v0.3 method names for backward compatibility.
A2A v1.0 Changes
| Concept | v0.3 (Legacy) | v1.0 (Current) |
|---|---|---|
| Method names | agent/sendMessage, agent/getTask | SendMessage, GetTask, CancelTask, ListTasks |
| Task state values | submitted, working, completed, failed | TASK_STATE_SUBMITTED, TASK_STATE_WORKING, TASK_STATE_COMPLETED, TASK_STATE_FAILED, TASK_STATE_CANCELED, TASK_STATE_INPUT_REQUIRED |
| Message format | { role: "user", parts: [...] } | { role: "ROLE_USER", content: [...] } |
| Task field | artifacts | messages (with content: A2APart[]) |
The platform's JSON-RPC dispatcher accepts both v0.3 and v1.0 method names. The TASK_STATE_INPUT_REQUIRED state is new in v1.0 and is used for HITL flows — when an agent needs user input, it returns this state along with A2UI surfaces.
Agent Cards
Every agent can publish a discovery card at GET /.well-known/agent.json containing metadata, capabilities, supported input/output modalities, and skill declarations. The platform generates these from the Agent model (defined in packages/types/src/agent.ts) including defaultInputModes, defaultOutputModes, and AgentCardPublishConfig.
Invocation Types
Defined in packages/types/src/a2a.ts:
interface A2AInvokeRequest {
sourceAgentId: string;
targetAgentId: string;
payload: {
message: string;
context?: Record<string, unknown>;
attachments?: Array<{ mimeType: string; name?: string; uri?: string }>;
};
delegationDepth?: number; // incremented per hop
delegationChain?: string[]; // full chain of agent IDs
conversationId?: string;
userContext?: A2AUserContext; // end-user identity propagated through chain
}
interface A2AInvokeResponse {
invocationId: string;
sourceAgentId: string;
targetAgentId: string;
status: 'completed' | 'error' | 'approval_required';
result?: { message: string; tokenUsage?: { input: number; output: number; cost: number } };
error?: string;
approvalId?: string;
delegationDepth: number;
delegationChain: string[];
durationMs: number;
}
User Context Propagation
The A2AUserContext carries the end user's identity through the entire delegation chain:
interface A2AUserContext {
userId: string;
tenantId: string;
token?: string; // JWT for OBO token exchange
}
This enables credential resolution (user-scoped BYOK keys), audit trail attribution, and HITL approval tracking at every hop.
External Agent Registration
External agents self-register via POST /api/a2a/register with an admin-issued registration token. The response includes an invocationSecret (shown once) and an a2aInvokeUrl for the agent to invoke other platform agents.
Governance Extension (Dimension 3)
The platform wraps every A2A invocation with Cedar policy enforcement. The full pipeline in services/api-gateway/src/lib/a2a-invoker.ts:
- Loop detection -- checks if the target agent already appears in the
delegationChain - Interaction policy check (D3) -- verifies source's
canInvokelist AND target'sinvocableBylist viacheckInteractionPolicy()inservices/api-gateway/src/middleware/interaction-policy-check.ts - Delegation depth enforcement -- configurable per-tenant (default: 5), also per-grant via
maxDelegationsPerExecution - HITL approval -- if
requireApprovalis set on the invoke grant, creates an approval request and returnsstatus: 'approval_required' - Capability attenuation -- effective tool permissions = intersection of source grants and target grants, preventing privilege escalation through delegation (MCP Nov 2025 section 7.1). Three modes:
full,readwrite,readonly - Cedar PDP overlay -- additional
checkAgentInteractionPolicy()call to the Cedar policy engine for fine-grained D3 rules - Target execution -- routes to the appropriate runtime (external pro-code, external n8n, legacy callback, or platform-managed LLM)
- Tracing and execution recording -- OTel trace span and execution record stored for observability
Interaction Policy Check
Defined in services/api-gateway/src/middleware/interaction-policy-check.ts. The check is bidirectional:
- Source must list target in
interactionPolicy.canInvoke[]with anAgentInvokeGrant - Target must accept source in
interactionPolicy.invocableBy[]with anAgentCallerGrant - Target agent must not be
suspendedordeprecated - Self-invocation is always blocked (loop prevention)
Sequence Diagram: A2A Invocation with Policy Checks
Scroll to zoom · Drag to pan · 100%
3. A2UI (Agent-to-User Input Protocol) — v0.8
A2UI is a JSONL-based streaming protocol that enables agents to generate platform-agnostic UI definitions during execution. The Portal renders these progressively using native widgets.
Transport Bindings
A2UI surfaces are transported via three protocol-specific bindings:
1. AG-UI binding (Portal ↔ Agent): A2UI messages travel inside AG-UI CUSTOM events:
{ "type": "CUSTOM", "name": "a2ui", "value": <A2UIMessage> }
2. A2A binding (Agent ↔ Agent): A2UI surfaces travel as DataPart entries in A2A task messages:
{
"role": "ROLE_AGENT",
"content": [{ "data": <A2UIMessage>, "mediaType": "application/json+a2ui" }]
}
When an A2A task returns TASK_STATE_INPUT_REQUIRED, the SDK automatically generates A2UI surfaces from the agent's statePatch HITL metadata and includes them as DataParts.
3. MCP binding (embedded resources): A2UI surfaces can be embedded in MCP resource responses via the a2ui:// URI scheme.
A2UI registers with A2A as an extension at URI https://a2ui.org/a2a-extension/a2ui/v0.8.
SDK Surface Builders
Both the Python and TypeScript SDKs provide surface builders that automatically generate A2UI v0.8 surfaces from HITL metadata in statePatch. The dispatcher function buildA2UIFromHITL(statePatch, runId) reads hitl_type from the state patch and calls the appropriate builder:
hitl_type | Builder | UI Surface |
|---|---|---|
"plan_selection" | buildPlanSelectorSurface() | MultipleChoice list with selectable options |
"parameter_edit" | buildParameterEditorSurface() | Form with TextField/Slider inputs per parameter |
"user_input" | buildUserInputSurface() | Single TextField (text, textarea, or number) |
"approval" | buildApprovalSurface() | Card with tool name, reasoning, Approve/Reject buttons |
Python:
from agentic_sdk import request_approval, message
return request_approval(hitl_type="approval", options={"tool": "create_ticket"}, message=message("assistant", "Approve?"))
TypeScript:
import { buildApprovalSurface, buildA2UIFromHITL } from '@agentic-platform/sdk';
// Low-level: build surfaces directly
const surfaces = buildApprovalSurface(requestId, 'create_ticket', args, 0.5, 'Creating ticket');
// High-level: dispatch from statePatch (used by SDK internals)
const surfaces = buildA2UIFromHITL(statePatch, runId);
The SDK's AG-UI endpoint (POST /ag-ui) and A2A endpoint (POST /) both automatically call buildA2UIFromHITL() when the agent returns TASK_STATE_INPUT_REQUIRED.
Message Types (Server to Client)
Defined in packages/types/src/a2ui-protocol.ts:
surfaceUpdate -- add or modify components in a surface. The first update with a new surfaceId implicitly creates the surface. Components use an adjacency list model with ID references.
interface A2UISurfaceUpdate {
surfaceId: string;
components: A2UIComponentEntry[]; // adjacency list of UI components
}
dataModelUpdate -- modify application state for a surface. Data is separate from UI structure for efficient partial updates.
interface A2UIDataModelUpdate {
surfaceId: string;
path?: string; // e.g., "/form/inputs"
contents: A2UIDataEntry[]; // adjacency list of data entries
}
beginRendering -- signal that a surface is ready to render. Prevents flash of incomplete content.
interface A2UIBeginRendering {
surfaceId: string;
root: string; // root component ID
catalogId?: string; // defaults to standard catalog
}
deleteSurface -- remove a surface when no longer needed (e.g., after HITL resolution).
Message Types (Client to Server)
userAction -- sent when a user interacts with a component (button click, form submit, selection change):
interface A2UIUserAction {
name: string; // action identifier from component's action.name
surfaceId: string;
sourceComponentId: string;
timestamp: string; // ISO 8601
context: Record<string, unknown>; // resolved key-value pairs from action definition
}
Component Catalog (v0.8)
The standard catalog defines these component types, all rendered by A2UIProtocolRenderer in packages/ui/src/components/a2ui-protocol/A2UIProtocolRenderer.tsx:
| Component | Purpose | Key Props |
|---|---|---|
Text | Display text | text (BoundValue), usageHint (h1-h5, caption, body) |
Button | Interactive action | label, action (ActionDef), primary |
Card | Container with border | child (component ID) |
Column | Vertical layout | children (explicit or template) |
Row | Horizontal layout | children |
TextField | Text input | value, label, placeholder, inputType |
CheckBox | Boolean toggle | value, label |
MultipleChoice | Selection (checkbox or chips) | value, options, variant |
Slider | Numeric range | value, min, max, label |
Image | Display image | url, fit, sizeHint |
Divider | Visual separator | -- |
List | Dynamic item rendering | children (template) |
Tabs | Tabbed container | children, labels |
Modal | Overlay dialog | child, title |
Data Binding
All component properties use A2UIBoundValue for dynamic data binding:
interface A2UIBoundValue {
literalString?: string; // static value
literalNumber?: number;
literalBoolean?: boolean;
literalArray?: unknown[];
path?: string; // data model path, e.g., "/user/name"
}
Three binding modes:
- Literal only --
{ literalString: "Hello" }is static - Path only --
{ path: "/user/name" }resolves dynamically from the surface data model - Both -- path is updated with the literal value, then bound for future updates
Rendering Flow
Scroll to zoom · Drag to pan · 100%
4. MCP (Model Context Protocol)
MCP is the universal tool interoperability protocol. The MCP Gateway (services/api-gateway/src/lib/mcp-gateway.ts) manages server connections, discovers tools, routes execution requests, and enforces access control.
Transport Modes
The gateway supports three MCP transports:
| Transport | URI Scheme | Use Case |
|---|---|---|
| stdio | mcp:// | Local development; gateway spawns child process |
| SSE | mcp+sse:// | Docker/K8s deployments; HTTP SSE endpoint |
| Streamable HTTP | mcp+http:// | Nov 2025 MCP spec; single HTTP endpoint with reconnection |
Built-in MCP servers can be overridden to SSE via environment variables:
MCP_BUILTIN_ENDPOINT-- platform tools (web search, KB search) on port 3210MCP_CODE_EXEC_ENDPOINT-- code execution tools on port 3201
Gateway Architecture
Scroll to zoom · Drag to pan · 100%
Tool Execution Pipeline
When executeTool(request: ToolExecutionRequest) is called:
Scroll to zoom · Drag to pan · 100%
Tool Registration and Discovery
Tools are registered in the mcp_tools database table. When the gateway connects to an MCP server:
- Calls
client.listTools()to discover available tools - Matches discovered tools against existing DB records using
sanitizeToolName()(e.g., "Knowledge Base Search" matchesknowledge_base_search) - Known tools get routing entries (
toolId → serverId) - New tools from external servers are auto-registered with
riskLevel: 'medium'andcategory: 'read-only'
D2 Access Control (Agent to Tool)
Defined in packages/types/src/agent.ts as AgentToolGrants:
interface AgentToolGrants {
tools: ToolGrant[];
dynamicDiscovery: boolean; // default: false
maxToolCallsPerExecution: number; // default: 20
}
interface ToolGrant {
toolId: string;
permissions: ('read' | 'write' | 'execute')[];
conditions?: {
maxCallsPerHour?: number;
requireApproval?: boolean;
timeWindow?: { start: string; end: string };
approvalPolicy?: ApprovalPolicy;
};
}
The checkToolGrant() middleware enforces that the agent has an explicit grant for the tool, with matching permissions and condition checks.
Tool Signing (Sigstore)
Before execution, checkSignatureEnforcement() verifies the tool's supply-chain signature via Sigstore. If enforcement is enabled and the signature check fails, the tool call is rejected with TOOL_SIGNATURE_FAILED.
MCP Sampling
When an MCP server requests LLM completion via sampling/createMessage, the gateway routes it through the platform's LLM Gateway (T-Systems AIFS). This enables multi-tenant credential resolution, token tracking, cost attribution, and D4 model access policy enforcement within MCP server execution.
Circuit Breaker
Per-server failure tracking prevents cascading failures:
| State | Behavior |
|---|---|
| CLOSED | Normal operation; failures counter increments |
| OPEN | After 5 consecutive failures; fast-rejects all calls for 30 seconds |
| HALF_OPEN | After recovery timeout; allows one probe call |
5. How They Work Together
A single user message can traverse all four protocols. Here is the full lifecycle of a request that involves LLM inference, an MCP tool call, an A2A agent delegation, and an A2UI user input surface.
Full Request Lifecycle
Portal (user types message)
│
├─ AG-UI ─► API Gateway opens SSE stream
│ │
│ ├─ Guardrails: input policy check
│ ├─ Memory: retrieve context from Redis/Qdrant/Neo4j
│ ├─ Knowledge: RAG retrieval from attached KBs
│ │
│ ├─ LLM inference (streaming via AIFS)
│ │ └─ AG-UI events: TEXT_MESSAGE_CONTENT tokens
│ │
│ ├─ LLM declares tool_call: "web_search"
│ │ ├─ AG-UI: TOOL_CALL_START + TOOL_CALL_ARGS
│ │ │
│ │ ├─ MCP ─► MCP Gateway
│ │ │ ├─ D2: checkToolGrant (agent → tool)
│ │ │ ├─ Sigstore signature verification
│ │ │ ├─ HITL gate (if requireApproval)
│ │ │ ├─ Credential injection (OAuth/API key)
│ │ │ ├─ Circuit breaker check
│ │ │ ├─ client.callTool() via MCP transport
│ │ │ └─ Audit + OTel trace
│ │ │
│ │ └─ AG-UI: TOOL_CALL_RESULT + TOOL_CALL_END
│ │
│ ├─ LLM declares tool_call: "invoke_agent" (target: agent-research)
│ │ ├─ AG-UI: TOOL_CALL_START + TOOL_CALL_ARGS
│ │ │
│ │ ├─ A2A ─► A2A Invoker
│ │ │ ├─ Loop detection (delegation chain)
│ │ │ ├─ D3: checkInteractionPolicy (agent → agent)
│ │ │ ├─ Cedar PDP overlay
│ │ │ ├─ Capability attenuation (least privilege)
│ │ │ ├─ HITL gate (if requireApproval)
│ │ │ ├─ Forward userContext (userId, tenantId, JWT)
│ │ │ ├─ Execute target agent
│ │ │ └─ Trace + execution recording
│ │ │
│ │ └─ AG-UI: TOOL_CALL_RESULT + TOOL_CALL_END
│ │
│ ├─ LLM needs user input (e.g., confirmation)
│ │ │
│ │ ├─ A2UI ─► via AG-UI CUSTOM events
│ │ │ ├─ surfaceUpdate (components)
│ │ │ ├─ dataModelUpdate (state)
│ │ │ ├─ beginRendering (root component)
│ │ │ │
│ │ │ ├─ Portal renders A2UIProtocolRenderer
│ │ │ ├─ User interacts (button click, form submit)
│ │ │ ├─ userAction sent back to gateway
│ │ │ │
│ │ │ └─ deleteSurface (cleanup)
│ │ │
│ │ └─ Agent continues with user's response
│ │
│ ├─ Guardrails: output policy check
│ ├─ Memory: extract and store conversation memories
│ │
│ └─ AG-UI: RUN_FINISHED
Sequence Diagram: All Four Protocols in One Flow
Scroll to zoom · Drag to pan · 100%
6. Governance Layer
All four protocols are wrapped by a unified governance layer that enforces security, authorization, and observability at every boundary.
Cedar Policy Enforcement
Cedar policies are evaluated at three protocol boundaries:
| Dimension | Boundary | Policy Type | Enforcement Point |
|---|---|---|---|
| D1 | User to Agent | Entitlement check | Auth middleware (checkUserAgentAccess) |
| D2 | Agent to Tool | Tool grant check | MCP Gateway (checkToolGrant) |
| D3 | Agent to Agent | Interaction policy | A2A Invoker (checkInteractionPolicy + checkAgentInteractionPolicy) |
| D4 | Agent to Model | Model access policy | LLM Gateway (checkModelAccess + checkAgentModelPolicy) |
Cedar PDP calls are non-fatal (fail-open with warning log) to prevent policy engine outages from blocking all agent execution.
SPIFFE Agent Identity
Each agent is assigned a SPIFFE identity (spiffe://platform/tenant/<tenantId>/agent/<agentId>) via the SPIRE infrastructure. This identity is used for:
- mTLS between agent containers and the API Gateway
- Cryptographic proof of agent identity in A2A invocations
- Audit trail attribution at every protocol boundary
OBO Token Exchange
When an agent needs to access downstream services on behalf of a user, the platform performs RFC 8693 token exchange (or SAP BTP Destination Service flow). Configured per-agent via oboTargets in AgentConfig:
interface OBOTarget {
audience: string; // e.g., "sap-s4hana", "salesforce"
scopes: string[]; // e.g., ["read:orders", "write:tickets"]
description?: string;
type?: 'rfc8693' | 'sap-destination';
sapDestinationName?: string;
}
The user's JWT is propagated through A2A delegation chains via A2AUserContext.token, enabling OBO exchange at every hop. External agent callbacks receive user identity via X-User-Id and X-Tenant-Id headers.
Audit Logging
Every protocol boundary records an audit event with:
- Agent ID and name
- Tenant ID
- Action performed (e.g.,
oauth:credential_resolved,a2a:invoke,tool:execute) - Outcome (
success/blocked/error) - Risk level
- Access control dimension (1-4)
- Principal ID (user or agent)
- Resource ID (tool, agent, or model)
Rate Limiting and Circuit Breakers
| Mechanism | Scope | Configuration |
|---|---|---|
| Tool rate limits | Per-agent per-tool | ToolGrant.conditions.maxCallsPerHour |
| Max tool calls | Per-execution | AgentToolGrants.maxToolCallsPerExecution (default: 20) |
| Delegation depth | Per-tenant / per-grant | Tenant config maxDelegationDepth (default: 5), grant maxDelegationsPerExecution |
| MCP circuit breaker | Per-server | 5 failures trips to OPEN, 30s recovery timeout |
| Execution quotas | Per-tenant | Concurrent execution limits via checkExecutionQuota |
| Budget limits | Per-agent / per-subagent | SubagentResourceConfig.tokenBudget, costBudget, timeoutMs |
| Model cost caps | Per-agent | AgentModelAccess.maxCostPerRequest, maxCostPerDay |
7. Key Source Files
| File | Purpose |
|---|---|
| Types | |
packages/types/src/ag-ui-events.ts | AG-UI event type definitions |
packages/types/src/google-a2a.ts | A2A v1.0 types (Task, Message, Part, AgentCard) |
packages/types/src/a2a-compat.ts | A2A v0.3 → v1.0 compatibility layer |
packages/types/src/a2a.ts | A2A invocation and registration types |
packages/types/src/a2ui-protocol.ts | A2UI protocol types (v0.8) |
packages/types/src/agent.ts | Agent config including OBO, interaction policy, tool grants, model access |
| Gateway | |
services/api-gateway/src/routes/chat.ts | Main chat route orchestrator (SSE streaming) |
services/api-gateway/src/routes/chat-helpers.ts | AG-UI emitters (emitAGUI, emitHandshake, emitToolCallAGUI) |
services/api-gateway/src/routes/chat-durable.ts | Temporal durable workflow chat handler (multi-segment AG-UI) |
services/api-gateway/src/routes/chat-external.ts | External agent handler (non-durable, debug fallback) |
services/api-gateway/src/routes/google-a2a.ts | A2A v1.0 JSON-RPC endpoint (POST /api/a2a) |
services/api-gateway/src/lib/a2a-invoker.ts | A2A invocation pipeline with policy enforcement |
services/api-gateway/src/lib/a2a-sse-bridge.ts | AG-UI → A2A bridge (exposes AG-UI agents to A2A callers) |
services/api-gateway/src/lib/a2a-task-manager.ts | A2A task lifecycle management |
services/api-gateway/src/lib/external-agent-executor.ts | External agent HTTP client (AG-UI POST /ag-ui) |
services/api-gateway/src/lib/mcp-gateway.ts | MCP Gateway (connections, routing, tool execution) |
services/api-gateway/src/lib/capability-attenuation.ts | Capability attenuation for A2A delegation |
services/api-gateway/src/lib/credential-resolver.ts | 4-tier credential resolution |
services/api-gateway/src/lib/tool-signer.ts | Sigstore tool signing verification |
services/api-gateway/src/lib/policy-engine.ts | Cedar PDP integration |
services/api-gateway/src/middleware/interaction-policy-check.ts | D3 interaction policy check |
services/api-gateway/src/middleware/tool-grant-check.ts | D2 tool grant check |
services/api-gateway/src/middleware/model-access-pep.ts | D4 model access policy enforcement |
| SDKs | |
sdks/python/agentic_sdk/server.py | Python SDK: FastAPI server with AG-UI + A2A v1.0 endpoints |
sdks/python/agentic_sdk/a2ui.py | Python SDK: A2UI surface builders (4 builders + dispatcher) |
sdks/typescript/src/server.ts | TypeScript SDK: Hono server with AG-UI + A2A v1.0 endpoints |
sdks/typescript/src/agent.ts | TypeScript SDK: AgenticAgent base class with multi-segment tool result extraction |
sdks/typescript/src/a2ui.ts | TypeScript SDK: A2UI surface builders (parity with Python) |
| Frontend | |
packages/ui/src/components/a2ui-protocol/A2UIProtocolRenderer.tsx | A2UI frontend renderer |