A2A Protocol Bridge
The A2A (Agent-to-Agent) Protocol Bridge is a standalone, self-managed service that bridges the Scion Hub with external systems using the open A2A Protocol specification.
By running the bridge, you expose Scion agents as standard A2A-compliant JSON-RPC endpoints. This allows programmatic multi-agent orchestration, third-party platform integrations, and federation with desktop clients such as Claude Desktop or Codex Desktop.
1. Overview
Section titled “1. Overview”The A2A protocol is an open standard developed by Google for secure, structured communication between independent artificial agents. It defines a lightweight JSON-RPC 2.0 dialect over HTTP, alongside standard discovery mechanics (“Agent Cards”) for advertising capabilities.
The Scion A2A Protocol Bridge (scion-a2a-bridge) translates these standard JSON-RPC payloads into Scion Hub API calls and vice versa. It enables:
- Universal Client Access: Any software that speaks the A2A protocol can discover, query, and message Scion agents without needing a dedicated Scion SDK.
- Desktop App Federation: Users can run agents locally inside desktop wrappers (like Claude Desktop or Codex Desktop) while leveraging the orchestration, resources, and hardware runtimes managed by a hosted Scion Hub.
- Programmatic Integration: Connect Scion agents to external multi-agent systems or automated workflow engines.
2. Architecture
Section titled “2. Architecture”The A2A bridge runs as an independent, standalone Go process alongside the Scion Hub. It acts as both a client of the Hub (for calling Hub APIs) and a broker plugin (for receiving real-time stream messages).
┌─────────────────┐ ┌──────────────┐ ┌───────────┐ │ A2A Client │───JSON-RPC─>│ A2A Bridge │───REST API─>│ Scion Hub │ │ (e.g., Desktop) │<───Cards────│ (Port 8443)│ │ │ └─────────────────┘ └──────────────┘ └───────────┘ ▲ │ │ Broker Plugin │ └──────(go-plugin RPC)─────┘ (Port 9090)Dual-Server Design
Section titled “Dual-Server Design”The scion-a2a-bridge process runs two concurrent RPC servers:
- A2A HTTP Server (Default: Port 8443): Serves external A2A clients. Handles agent discovery requests (
/.well-known/agent-card.json), JSON-RPC message exchanges, operational health checks (/healthz,/readyz), and Prometheus metrics (/metrics). - Broker Plugin RPC Server (Default: Port 9090): A
go-pluginRPC interface. The Scion Hub connects to this port to push real-time agent-emitted messages, events, and status changes back to the bridge.
High Availability (HA) & Cloud Run Deployment
Section titled “High Availability (HA) & Cloud Run Deployment”To support robust, load-balanced hosted environments, the A2A Bridge includes advanced production capabilities:
Leaderless HA Architecture
Section titled “Leaderless HA Architecture”The A2A Bridge supports High Availability (HA) natively through a leaderless architecture. Every replica is identical and interchangeable — there is no leader election, coordination overhead, or instance-identity requirement. Behind a load balancer, any replica can handle any incoming A2A request or receive broker plugin events, enabling seamless horizontal scaling.
HA mode requires standalone mode (--standalone flag or A2A_STANDALONE=true) with a shared PostgreSQL backend (DATABASE_URL). This replaces the default local SQLite store so that all replicas share webhook subscriptions, task state, and admin configuration. Without a shared database, per-replica local state (SQLite) would drift across replicas and be lost on restart — making SQLite unsuitable for multi-replica or ephemeral deployments like Cloud Run.
Durable Task State
Section titled “Durable Task State”In standalone/HA mode, the bridge stores A2A SDK tasks in PostgreSQL with authoritative ownership, cross-replica correlation, and execution leases. Key properties:
- Execution Leases: Each task’s Hub-bound send is serialized via an
exec_owner/exec_heartbeatlease — only one replica processes a task at a time, preventing duplicate messages to agents. - Atomic Crash Recovery: On startup and periodically, stale leases (where the owning replica stopped heartbeating) are automatically reaped and the tasks transitioned to a failed state.
- Event Deduplication: Task events and artifacts are deduplicated by a
dedup_key, ensuring idempotent delivery across replicas. - LISTEN/NOTIFY Acceleration: PostgreSQL’s
LISTEN/NOTIFYmechanism accelerates event delivery to waiting subscribers, supplementing the polling-based correctness floor.
Authenticated gRPC Transport
Section titled “Authenticated gRPC Transport”In standalone mode, the Hub communicates with the bridge via gRPC instead of the in-process go-plugin RPC. This transport supports fail-closed authentication:
| Environment Variable | Description |
|---|---|
GRPC_AUTH_MODE |
Auth mode for incoming Hub gRPC RPCs: google_id_token or local_dev. Required for non-local listen addresses. |
GRPC_AUTH_AUDIENCE |
Expected audience claim in Google ID tokens (required when mode is google_id_token). |
GRPC_AUTH_SUBJECTS |
Comma-separated allowlist of authorized service account emails (required when mode is google_id_token). |
GRPC_TLS_CERT / GRPC_TLS_KEY |
Server TLS certificate and key (for Kubernetes deployments; ignored in Cloud Run mux mode). |
GRPC_TLS_CLIENT_CA |
Client CA for mTLS verification (optional). |
On the Hub side, the plugin configuration in settings.yaml specifies mode: "grpc" with address, auth_type, and optional TLS fields for the outbound connection to the bridge.
Single-Port h2c Multiplexing (Cloud Run)
Section titled “Single-Port h2c Multiplexing (Cloud Run)”Google Cloud Run enforces a strict single-port limitation for incoming traffic. To run both the A2A HTTP server and the Broker Plugin RPC gRPC server on a single container port, the bridge implements h2c port multiplexing:
- Auto-Detection: The bridge automatically detects when it is running on Cloud Run by checking for the presence of the
K_SERVICEenvironment variable. - Traffic Routing: When detected, the bridge binds to the designated
$PORTand multiplexes incoming HTTP/1.1 (standard A2A protocol JSON-RPC, health checks, and metrics) and HTTP/2 (gRPC broker traffic) over that single port. This eliminates the need for separate external ports or complex sidecar routing proxies.
Interaction Modes
Section titled “Interaction Modes”The bridge supports three distinct A2A communication mechanics:
- Synchronous Blocking (SendMessage): The client POSTs a message and holds the HTTP connection open (up to
timeouts.send_message, default 120s) until the task completes; the response is the agent’s explicit reply (see the note below). - Server-Sent Events (SSE) Streaming (SendStreamingMessage): The client initiates streaming to receive real-time, token-by-token streaming updates as the agent executes.
- Asynchronous Webhooks (Push Notifications): Clients register a callback URL (via the
CreateTaskPushNotificationConfigmethod). The bridge stores this subscription in its state database (SQLite in default mode, PostgreSQL in standalone/HA mode) and POSTs state-change alerts (running, completed, input-required, error) to the webhook as they occur.
3. Setup & Installation
Section titled “3. Setup & Installation”The A2A bridge can be built from source and is typically installed and managed via the Hub Admin UI.
Step 1: Install via Admin UI (Recommended)
Section titled “Step 1: Install via Admin UI (Recommended)”- Log in to the Scion Web Dashboard as an administrator.
- Navigate to Admin > Integrations in the sidebar.
- Locate A2A Bridge under the Available Integrations list. Its description reads: “External service — installed separately, managed via admin UI”.
- Click Install.
- The Hub registers the bridge as a
self_managedplugin undersettings.yamland displays detailed setup instructions, including the location of the generated operator bootstrap config template (e.g.,~/.scion/scion-a2a-bridge.yaml) and the startup command.
Step 2: Build the Binary
Section titled “Step 2: Build the Binary”Clone the Scion repository and navigate to the root:
git clone https://github.com/GoogleCloudPlatform/scion.gitcd scionThen compile the Go binary:
# Using the project Makefilemake build-a2a-bridge
# Or compiling manuallymkdir -p bingo build -o bin/scion-a2a-bridge ./extras/scion-a2a-bridge/cmd/scion-a2a-bridge/Verify the binary is available:
./bin/scion-a2a-bridge --helpStep 3: Run the Bridge
Section titled “Step 3: Run the Bridge”Run the bridge binary using the bootstrap configuration file created during the Admin UI installation:
# If using a static API key, export it as an environment variableexport A2A_API_KEY="your-secure-static-api-key"
# Run the process./bin/scion-a2a-bridge --config ~/.scion/scion-a2a-bridge.yamlStep 4: Verify Connection
Section titled “Step 4: Verify Connection”Return to the Hub Admin > Integrations page. Click Reconnect on the A2A Bridge integration card. Once the Hub successfully dials the bridge’s RPC server on port 9090, the status badge will update to Connected.
Dev-Mode Rebuild
Section titled “Dev-Mode Rebuild”When running the Scion Hub in development mode (MaintenanceConfig.RepoPath is configured in settings.yaml), clicking Update inside the A2A integration detail page triggers an automatic compilation of the bridge binary directly from your local repository. The Hub will output instructions for you to restart the process manually to apply the new binary.
4. Configuration
Section titled “4. Configuration”The A2A bridge uses a two-tier configuration model:
- Operator-Managed Bootstrap Config (
scion-a2a-bridge.yaml): Low-level infrastructure settings (listen ports, DB paths, and signing keys) that require a process restart. - Admin-Managed Overlay Config (Hot-Reloaded): Dynamic operational settings configured directly from the Scion Admin UI that take effect instantly.
Operator Bootstrap YAML Reference
Section titled “Operator Bootstrap YAML Reference”Below is a complete, annotated example of scion-a2a-bridge.yaml:
# A2A protocol server settingsbridge: # Port and address for the A2A HTTP server (JSON-RPC, agent cards, health check). listen_address: ":8443"
# Public-facing base URL where A2A clients reach this bridge. # Used to construct agent card URLs and self-referencing links. external_url: "https://a2a.example.com"
# Identity included in generated Agent Cards. provider: organization: "My Organization" url: "https://example.com"
# Scion Hub credentialshub: # The HTTP API endpoint of your Scion Hub. endpoint: "https://hub.example.com"
# Hub administrator email the bridge authenticates as for background operations. user: "a2a-bridge-admin@example.com"
# Base64-encoded Hub HS256 signing key file. Used to sign JWTs for Hub API calls. # Exactly one of signing_key or signing_key_secret must be set. signing_key: "/etc/scion-a2a-bridge/signing-key.b64"
# Alternatively, retrieve the signing key from GCP Secret Manager: # signing_key_secret: "projects/my-gcp-project/secrets/hub-signing-key/versions/latest"
# Broker plugin RPC server (Hub dials this to send agent stream events)plugin: listen_address: "localhost:9090" # Set to true to listen on external networks. Requires strict firewall rules! allow_remote: false
# Client Authenticationauth: # Schemes: "apiKey", "bearer", "hubUAT", "hubJWT", "geGoogle", "oidcFederation", or "none" (not recommended) scheme: "apiKey"
# Static key (required only for "apiKey" and "bearer" schemes). # Supports environment variable expansion. api_key: "${A2A_API_KEY}"
# UAT validation cache duration for "hubUAT" scheme (default: 60s, max: 300s). uat_cache_ttl: 60s
# Google credential exchange settings (required only for "geGoogle" scheme). # ge_exchange: # credential_type: "id_token" # "id_token" or "access_token" # cache_ttl: 60s # max 300s; capped by token expiry
# SQLite State Databasestate: database: "/var/lib/scion-a2a-bridge/state.db"
# Timeout Configurationtimeouts: send_message: 120s # Maximum wait time for synchronous blocking calls sse_keepalive: 30s # Interval between SSE ping packets push_retry_max: 3 # Max delivery retries for webhook push notifications
# Client Request Rate Limitingrate_limit: enabled: true requests_per_sec: 10 # Sustained token bucket rate burst: 20 # Maximum transient spike capacity trust_proxy: false # Set to true only behind a trusted reverse proxy
# Logging Configurationlogging: level: "info" # debug, info, warn, error format: "json" # "json" for structured logs, "text" for human-readable console5. The Admin Overlay & Hot-Reloading
Section titled “5. The Admin Overlay & Hot-Reloading”When the A2A bridge connects to the Hub via port 9090, it registers itself for live configuration updates.
- How it works: Saved changes in the Admin > Integrations web UI are serialized by the Hub and pushed instantly to the running bridge via an active RPC stream (the
Configure()method). - Atomic Snapshots: The bridge takes the updated values, parses and validates them, and builds a new, immutable
ConfigSnapshotcontaining the pre-compiled rate limiters, timeouts, and auth validators. This snapshot is swapped atomically into a lock-free memory pointer (SnapshotHolder), allowing active connections to read settings without locking overhead. - No-Restart Apply: Settings like rate-limit thresholds, timeouts, and active auth schemes take effect instantly without restarting the bridge process or dropping ongoing client connections.
- Config Persistence: To survive process restarts, the bridge automatically persists its current admin settings in a local JSON file named
admin-overlay.jsonin its configuredstatedatabase directory. At boot, the bridge loads this file first, applying all user-customized settings before joining the network.
6. Project & Agent Exposure Controls
Section titled “6. Project & Agent Exposure Controls”By default, the A2A bridge does not expose any Scion projects. Operators must explicitly allow projects and configure which agents within those projects are accessible to A2A clients.
This is managed using the A2A Projects Editor on the Integration Detail page in the Admin UI:
┌─────────────────────────────────────────────────────────────┐ │ A2A Projects Editor │ ├─────────────────────────────────────────────────────────────┤ │ [+] Add Project │ │ │ │ ▼ my-awesome-project [Delete]│ │ Exposed Agents: [ research-agent ⓧ ] [ coding-agent ⓧ ] │ │ (Leave empty to expose all agents) │ │ │ │ [✔] Auto-Provision New Agents │ │ Default Template: [ standard-dev ▼ ] │ └─────────────────────────────────────────────────────────────┘Controls & Behaviors
Section titled “Controls & Behaviors”- Add Project: Select a project from the dropdown. Only agents belonging to selected projects can be reached via the bridge.
- Granular Agent Allowlist (
exposed_agents): Enter specific agent slugs. If left empty, all agents in the selected project are exposed to discovery. If populated, only listed agents appear in the project’s agent card. - Auto-Provisioning (
auto_provision):- If Disabled (default): Clients can only connect to pre-existing, running agents in the project.
- If Enabled: When a client sends a request with an unrecognized
contextId(representing a new conversation), the bridge calls the Hub to automatically provision and start a new agent for that user on demand.
- Default Template: Select which template is used when auto-provisioning new agents.
Asynchronous agent create (bridge.async_create)
Section titled “Asynchronous agent create (bridge.async_create)”When the bridge auto-provisions an agent, it asks the Hub for an asynchronous create (YAML bridge.async_create, default true). A Hub with asynchronous launch enabled answers as soon as a Runtime Broker accepts the create, while the agent is still starting. A Hub without it answers once the agent is running, as before.
While the agent starts, the bridge holds the first message and reports the task as working. Streaming and A2A SDK clients also see the status message provisioning agent (<step>) for each launch step (the step is shown as its code, for example pod_create); other clients see the working state only. Once the agent is running, the bridge sends the message, and the usual response wait starts. If the launch fails, the task ends failed and the message is not sent; streaming and SDK clients see Agent failed to start (<code>): <message>. A task can therefore stay working until the launch times out on the Hub, plus about 30 seconds. A user caller from another hub sees only the code, or (phase: <phase>) when the outcome has no code; see Users from another hub (project visitors) in section 8 below.
The bridge also waits when it adopts an agent that another client is still starting, and before a follow-up message on an existing task whose agent is still starting; a follow-up sent while the task’s first message is still waiting is delivered after that message when both reach the same bridge instance (a follow-up handled by another replica does not wait for it). It waits in those cases even with async_create: false, because that setting controls only the bridge’s own request. Set async_create: false to always use the synchronous create.
7. Desktop App Federation
Section titled “7. Desktop App Federation”A primary use case for the A2A bridge is connecting local desktop chat applications (such as Claude Desktop or Codex Desktop) to robust Scion deep agents.
To secure and isolate desktop client sessions, Scion uses User Access Token (UAT) Authentication.
┌────────────────┐ ┌────────────┐ ┌───────────┐ │ Claude Desktop │─Bearer Token─>│ A2A Bridge │─Introspect───>│ Scion Hub │ │ (A2A Client) │ │ (hubUAT) │ │(/auth/me) │ └────────────────┘ └────────────┘ └───────────┘Setup Walkthrough
Section titled “Setup Walkthrough”Step 1: Create a Personal User Access Token
Section titled “Step 1: Create a Personal User Access Token”To authorize your desktop application, generate a Scion Personal Access Token scoped to the project you wish to target:
scion token create \ --name "claude-desktop-integration" \ --project "my-coding-project" \ --scope agent:message,agent:read \ --expires 365dThis command returns a secure token starting with scion_pat_.... Copy it immediately; it will not be shown again.
Step 2: Configure Your Desktop App
Section titled “Step 2: Configure Your Desktop App”In your desktop app (e.g., Claude Desktop, Codex Desktop) provider or integration settings, configure the connection:
- Endpoint:
https://<a2a-bridge-domain>/projects/<project-slug>/agents/<agent-slug> - Authentication Method: Bearer Token
- Token: Paste your generated
scion_pat_...token.
Step 3: Test with curl
Section titled “Step 3: Test with curl”Verify that your token and endpoint are correct by querying the agent card:
curl -s -H "Authorization: Bearer scion_pat_..." \ https://a2a.example.com/projects/my-coding-project/agents/code-helper/.well-known/agent-card.jsonSend your first programmatic test message:
curl -X POST https://a2a.example.com/projects/my-coding-project/agents/code-helper/jsonrpc \ -H "Authorization: Bearer scion_pat_..." \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "desktop-test-1", "method": "SendMessage", "params": { "message": { "role": "user", "parts": [{"kind": "text", "text": "Are you connected?"}] } } }'8. Per-User Authentication & Isolation
Section titled “8. Per-User Authentication & Isolation”When the A2A bridge is configured with per-user authentication, callers present their own individual credentials instead of a shared static API key. This activates CallerIdentity context propagation and granular task isolation.
Per-User & Federation Schemes
Section titled “Per-User & Federation Schemes”The A2A bridge supports five authentication schemes for granular access control, specified via auth.scheme in the bridge configuration. Four are described below; the fifth, hubBearer (Google credential pass-through — recommended over geGoogle, see below), is documented in the Hub’s External Bearer Tokens section and scion-a2a-bridge.yaml.sample, since its verification logic lives on the Hub side.
1. hubUAT (Recommended for Desktop App Federation)
Section titled “1. hubUAT (Recommended for Desktop App Federation)”- How it works: Callers present a Scion User Access Token (
Authorization: Bearer scion_pat_...) created via the CLI. - Validation (
UATValidator): The bridge dynamically introspects each incoming token by calling the Hub’s/api/v1/auth/meendpoint. - SHA-256 Keyed Cache: To avoid overwhelming the Hub with authentication requests, validated tokens are cached in memory using a SHA-256 hash of the token as the key.
- Configurable TTL: The cache TTL is configurable via
auth.uat_cache_ttl(default:60s, maximum:300s). If a user revokes their UAT, access is completely cut off once the cache TTL expires (within 60 seconds by default).
2. hubJWT (Recommended for CLI & Scripted Access)
Section titled “2. hubJWT (Recommended for CLI & Scripted Access)”- How it works: Callers present a Scion-signed User JWT.
- Local Validation: The bridge validates the JWT signature locally using the HS256
hub.signing_keysecret shared with the Hub. Since this happens entirely locally, it requires no active API calls to the Hub, making it extremely fast.
3. geGoogle (For Google Cloud Environments) — Deprecated, superseded by hubBearer
Section titled “3. geGoogle (For Google Cloud Environments) — Deprecated, superseded by hubBearer”- How it works: Callers present a Google credential (ID token or access token) in the
Authorization: Bearerheader. The bridge exchanges it with the Hub for a short-lived Hub user token via the/api/v1/auth/integrations/google/exchangeendpoint. - Credential Types: Supports both
id_tokenandaccess_tokenviaauth.ge_exchange.credential_type. - LRU Cache: Validated tokens are cached per-replica with a configurable TTL (
auth.ge_exchange.cache_ttl, default60s, max300s). The effective TTL is capped at the minimum of the configured value, the Hub token expiry, and the upstream Google credential expiry. Expired tokens fail closed (never cached). - No Refresh Tokens: This scheme is designed for short-lived credentials only. Clients must rotate credentials upstream.
Configure in scion-a2a-bridge.yaml:
auth: scheme: "geGoogle" ge_exchange: credential_type: "id_token" # or "access_token" cache_ttl: 60s # max 300s4. oidcFederation (For Federated Access)
Section titled “4. oidcFederation (For Federated Access)”- How it works: Callers present an OIDC ID token issued by a trusted federation provider.
- Token Verification & Bookkeeping: The bridge decodes the OIDC token and performs local bookkeeping. It fully supports RFC 7519
aud(audience) claim validation, accepting the claim in either string or array-of-strings format. - Transport Auth Wiring: Integrated with Google Cloud Identity-Aware Proxy (IAP) transport auth wiring to automatically resolve and bypass platform-level guards when accessing protected backends.
- OIDC & JWKS Discovery Proxying: Behind IAP, third-party federation callers cannot access the Scion Hub’s OIDC discovery and JWKS public keys because they lack IAP credentials. The bridge resolves this by proxying
/.well-known/openid-configurationand/.well-known/jwks.json:- It uses its own internal bridge transport auth (IAP credentials) to fetch these documents from the Hub.
- It rewrites the returned
jwks_uriinside the OIDC config to point back to the bridge’s own/.well-known/jwks.jsonendpoint. - It serves these endpoints publicly and caches them for 5 minutes to ensure high performance and reduce Hub load.
- For air-gapped or manual distribution, administrators can also use the Download JWKS button on the federation admin page in the Web Dashboard to download public keys out-of-band.
- Users from another hub (project visitors): The bridge reads the
scion_token_kindclaim (useroragent). A credential without the claim counts as an agent when it carries an agent claim (project_id,agent_name,ancestry,root_user), and as a user when it carries only an email. A credential that claims both kinds, or another kind, is refused. Agent credentials work as before.- The Hub decides everything for a user caller, asked with the caller’s own user access credential.
GET /api/v1/auth/menames the caller’s project visitor account on this Hub, and the bridge sends asuser:<account ID>. Tasks are kept separate per account. - A user with no project visitor account on this Hub (for example, the user of an identity issuer who is not linked to an account) is refused: the Hub answers 403, and the bridge answers 403 with “You don’t have access to this resource” before any task starts. A user access credential from another hub is refused by the Hub with 401 while the Hub’s project visitor experiment is off, or while the issuer does not accept user access credentials; the bridge passes the 401 on. When the Hub cannot be reached, the caller gets 502 and can retry.
- Messages from a user caller are text only. The agent’s reply comes back on the user’s own topic, and the bridge forwards to the user only replies that arrive on that user’s own topic. The bridge listens on that topic alone, and a reply on a user’s topic never reaches another caller: an agent caller’s task takes replies only on the bridge admin’s topic. Before the bridge forwards a reply, it asks the Hub again whether the caller may still read that conversation. If the Hub refuses (for example, the grant was revoked), the task ends with “You don’t have access to this resource”. The caller gets the same answer when the project or agent is unknown, not exposed by the bridge, or not open to them. For an agent that is still starting, the bridge first asks the Hub, with the same conversation read, whether the caller may message it: a user caller sees launch progress only after the Hub allows the message, and if the launch fails it sees only the launch outcome code (
Agent failed to start (<code>)), or the phase when the outcome has no code (Agent failed to start (phase: <phase>)). Any other Hub answer while the bridge routes or sends the message ends the task with “Failed to route message to agent” instead, and any other Hub answer to the check before a reply ends it with “Unable to verify access to agent”. That includes a 400, which the Hub gives for a request it does not accept from a user caller (such as a message with an attachment); sending the same message again gets the same answer. GET /projects/{project}/agents(authenticated) returns the cards of the project’s exposed agents that the caller may message. This comes from the Hub’s?messageable=trueagent list.- The bridge never creates an agent for a user caller, and never sends for it as the bridge admin.
- Requirements: the bridge needs a signing key (
hub.signing_keyorhub.signing_key_secret), because it sends its own minted credential next to the caller’s; without one a user caller gets 502. The Hub needs native chat enabled, because the access check before each reply reads the conversation through the chat API; with chat off, every reply to a user caller is refused, and so is every message from a user caller to an agent that is still starting.
- The Hub decides everything for a user caller, asked with the caller’s own user access credential.
Per-User Isolation Benefits
Section titled “Per-User Isolation Benefits”- Task Ownership & Visibility: Users can only see, query, and cancel/interrupt tasks they created. One user cannot view or modify the active tasks of another user. This is enforced at the SQLite level using a
ScopedTaskStore. - Audit Trails & Attribution: All downstream Hub API calls made by the bridge (such as sending messages or interrupting containers) propagate the user’s actual
CallerIdentity. The Hub’s audit logs will show the real user’s identity as the initiator rather than the bridge admin’s service account. - Extended Identity Context: The propagated
CallerIdentitycarries critical security metadata, including:CallerKey: Resolves the exact key/token credentials used to authenticate the request.IsAgent: Identifies whether the calling entity is a federated agent or a human user.SenderLabel: A human-readable attribution tag attached to transaction and delivery logs.
9. API Reference
Section titled “9. API Reference”HTTP Endpoints
Section titled “HTTP Endpoints”The bridge exposes the following HTTP endpoints:
| Endpoint | Method | Authentication | Description |
|---|---|---|---|
/.well-known/agent-card.json |
GET | None | Base bridge registry agent card. |
/.well-known/openid-configuration |
GET | None | OIDC discovery proxy document for deployments behind IAP. Uses bridge transport auth to fetch from the Hub and rewrites the jwks_uri to point back to the bridge. Cached for 5 minutes. |
/.well-known/jwks.json |
GET | None | JSON Web Key Set (JWKS) proxy endpoint. Serves Hub public keys with a 5-minute cache. |
/projects/{projectSlug}/agents/{agentSlug}/.well-known/agent-card.json |
GET | Configured Scheme | Per-agent capabilities card. |
/projects/{projectSlug}/agents/{agentSlug}/jsonrpc |
POST | Configured Scheme | Primary A2A JSON-RPC 2.0 communication endpoint. |
/healthz |
GET | None | Liveness check (returns HTTP 200). |
/readyz |
GET | None | Readiness check (checks DB and active broker plugin connection). |
/metrics |
GET | Configured Scheme | Prometheus metrics. |
Supported JSON-RPC Methods
Section titled “Supported JSON-RPC Methods”Standard A2A JSON-RPC methods supported at the /jsonrpc endpoint:
| JSON-RPC Method | Description |
|---|---|
SendMessage |
Send a message to the agent. Supports standard and blocking modes. Returns the agent’s completed response. |
SendStreamingMessage |
Send a message and establish an SSE streaming connection. Real-time token updates are pushed over the stream. |
GetTask |
Retrieve detailed status and execution state of a specific task by its unique task ID. |
ListTasks |
List all tasks associated with a particular contextId (conversation). |
CancelTask |
Cancel an in-progress agent execution task. |
SubscribeToTask |
Re-attach an active SSE streaming connection to an ongoing task (useful on connection drops). |
CreateTaskPushNotificationConfig |
Register a webhook callback URL to receive real-time POST alerts on task state changes. |
GetTaskPushNotificationConfig |
Retrieve registered webhooks for a specific task. |
DeleteTaskPushNotificationConfig |
Remove a webhook callback subscription. |
Input-required replies. When the agent asks for input (an input-needed reply, for example after sciontool status ask_user), the bridge returns the reply as a task artifact with the task in the input-required state, on every path: blocking SendMessage, SSE streams, push notifications, and SubscribeToTask replays. Plain replies keep the working and completed states. The bridge derives task state from the agent’s structured status, and falls back to the message text only when no status is set.
10. Troubleshooting & Operations
Section titled “10. Troubleshooting & Operations”Health Checks (/healthz & /readyz)
Section titled “Health Checks (/healthz & /readyz)”- Liveness (
/healthz): ReturnsHTTP 200 OKas long as the HTTP server is listening. Used by container runtimes to detect deadlocks. - Readiness (
/readyz): ReturnsHTTP 200 OKonly when:- The local SQLite database is reachable and writable.
- The Hub’s broker plugin has successfully dialed and established its active RPC connection.
- If either component fails,
/readyzreturnsHTTP 503 Service Unavailable, preventing traffic from hitting the node.
SQLite State Database Permissions
Section titled “SQLite State Database Permissions”The SQLite database stores task metadata and webhook credentials (auth_credentials) in plain text.
- File Permissions: Ensure the database file (default:
state.db) is readable and writable only by the bridge process user (chmod 0600). - Docker Container Security: The official
scion-a2a-bridgeDocker image runs as a non-root userbridge(UID1000). It expects its state directory at/var/lib/scion-a2a-bridge/to be mounted as a persistent volume writable by UID1000.
Security of Broker Plugin RPC (plugin.allow_remote)
Section titled “Security of Broker Plugin RPC (plugin.allow_remote)”The broker plugin RPC channel on port 9090 is the pathway through which the Hub pushes agent-emitted logs.
- No Transport Auth: The
go-pluginRPC protocol has no built-in transport authentication. By default, it binds only tolocalhost(127.0.0.1:9090). - Remote Warning: If you must separate the Hub and A2A bridge across distinct machines/containers and set
plugin.allow_remote: true, you must restrict access to port 9090 at the network layer (e.g., using a security group, firewall rule, or private VPC) so that only the Hub’s IP can dial the bridge. Anyone who can dial port 9090 can forge agent execution output.
SSRF Protection in Webhooks
Section titled “SSRF Protection in Webhooks”To prevent Server-Side Request Forgery (SSRF) when making push notification callback requests, the bridge:
- Validates all registered webhook URLs against a private IP blacklist (rejecting loopback, local network, link-local, and multicast ranges).
- Uses an active connection dialer callback (
ssrfSafeDialer) that checks the resolved IP after DNS resolution to prevent DNS-rebinding attacks. - Completely blocks HTTP redirection requests on webhook callbacks to prevent credential leaks.