Chio/Docs

BuildGateways

A2A Adapter

Map a Google Agent-to-Agent server's advertised skills to Chio tools, then evaluate each call and sign a receipt.


Why A2A Through Chio

Google's A2A protocol standardises how agents expose capabilities to other agents over HTTP. Agents publish an Agent Card describing their skills, bindings, and security schemes; callers select an interface and send structured messages. A2A defines transport and discovery; the operator supplies enforcement, attribution, and audit.

When an A2A call is proxied through Chio, the caller's capability token is validated, guards fire at the adapter boundary, and a signed ChioReceipt is emitted for every invocation. For cross-organization delegation, the receipt DAG records each call-chain hop.

A2A aloneA2A + Chio
Transport and Agent Card discoveryCapability validation and guard evaluation at the adapter boundary
Skill advertisement via Agent CardOne Chio tool per skill, with stable tool names and scoping
Auth negotiation is per-integration glueDeclared schemes matched to configured credentials, fail closed otherwise
Cross-agent trust is implicitDelegated provenance is carried in call_chain and signed into receipts

Current Scope

The adapter supports these A2A v1.0.0 features for partner integrations:

  • Agent Card discovery via /.well-known/agent-card.json on a base URL, or a full Agent Card URL consumed directly
  • Both JSONRPC and HTTP+JSON bindings
  • Blocking SendMessage, streaming SendStreamingMessage, and follow-up GetTask, SubscribeToTask, and CancelTask
  • Push-notification config create, get, list, and delete over both bindings
  • OAuth2 client-credentials, OpenID Connect discovery, HTTP Basic, API keys in header, query, or cookie position, and mutual TLS
  • Adapter-level request headers, query parameters, and cookies for partner-specific requirements
  • Fail-closed partner admission policy for expected tenant, required skills, required security schemes, and allowed interface origins
  • Optional durable task registry so follow-up correlation survives adapter recreation and process restarts

HTTPS by default

The adapter requires https for remote A2A targets. Plain http is allowed only for localhost. Production configurations must use HTTPS.

One direction: this is the consuming side

chio-a2a-adapter is the outbound direction — chio calls a remote A2A agent and governs the call. Hosting chio's own tools as an A2A server (building an Agent Card and serving message/send, message/stream, task/get, and task/cancel) is the inbound counterpart. The separate chio-a2a-edge crate. If you are building an A2A server for chio, use the edge crate.

Protocol Boundary

A2A v1.0.0 does not define a native skillId selector inside SendMessage. The adapter adds this local convention to top-level request metadata:

json
{
  "chio": {
    "targetSkillId": "research",
    "targetSkillName": "Research"
  }
}

The convention gives Chio stable per-skill tool names and per-skill capability scopes without changing the A2A protocol.

The adapter sends only credentials that satisfy a declared A2A requirement set. If the Agent Card demands a scheme the adapter does not implement yet, the invocation is denied locally before any call goes upstream. The following Agent Card declarations are recognised:

Agent Card declarationChio behavior
bearer, OAuth2-bearer, OpenID-bearerBearer-style Authorization header
httpAuthSecurityScheme basicHTTP Basic authentication
oauth2SecuritySchemeClient-credentials token acquisition against the declared endpoint
openIdConnectSecuritySchemeOIDC discovery followed by client-credentials
mtlsSecuritySchemeMutual TLS using the configured client identity
apiKeySecuritySchemeNamed API key placed in header, query, or cookie

Lifecycle payload validation is fail-closed

SendMessage task responses, GetTask results, and streamed task, statusUpdate, and artifactUpdate events must contain the required fields (id, status.state, taskId, and artifact where applicable). Malformed payloads are rejected before reaching the kernel.

Serving an A2A Bridge

There is no dedicated chio a2a CLI subcommand. The documented way to expose the adapter is to embed it in a Rust host process backed by a chio kernel and register it with kernel.register_tool_server(...). On registration, the adapter discovers the Agent Card and every advertised skill becomes a Chio tool, invocable through the kernel like any other tool server. The complete builder is shown in Configuring the Adapter from Rust below.


Configuring the Adapter from Rust

Operators run the adapter as a long-lived process backed by a chio kernel. A2aAdapterConfig is a builder: set the target Agent Card URL and manifest public key, add transport credentials and partner policy, then register the discovered adapter with the kernel.

rust
use chio_a2a_adapter::{A2aAdapter, A2aAdapterConfig, A2aPartnerPolicy};
use chio_core::crypto::Keypair;
use chio_kernel::ChioKernel;

let manifest_key = Keypair::generate();
let adapter = A2aAdapter::discover(
    A2aAdapterConfig::new(
        "https://agent.example.com",
        manifest_key.public_key().to_hex(),
    )
    .with_tls_root_ca_pem(include_str!("agent-root-ca.pem"))
    .with_mtls_client_auth_pem(
        include_str!("agent-client-cert-chain.pem"),
        include_str!("agent-client-key.pem"),
    )
    .with_request_header("X-Partner", "design-partner-a")
    .with_oauth_client_credentials("client-id", "client-secret")
    .with_oauth_scope("a2a.invoke")
    .with_partner_policy(
        A2aPartnerPolicy::new("design-partner-a")
            .with_required_tenant("tenant-alpha")
            .require_skill("research")
            .require_security_scheme("oauthAuth")
            .allow_interface_origin("https://agent.example.com"),
    )
    .with_task_registry_file(".chio/a2a-task-registry.json")
)?;

let mut kernel = ChioKernel::new(/* ... */);
kernel.register_tool_server(Box::new(adapter));

Tool Contract

Each generated chio tool accepts a superset of SendMessage fields plus adapter-local follow-up modes. The blocking fields are:

  • message: plain text sent as an A2A text part
  • data: structured JSON sent as an A2A data part
  • context_id, task_id, reference_task_ids
  • metadata for SendMessageRequest.metadata
  • message_metadata for Message.metadata
  • history_length, requires the Agent Card to advertise capabilities.stateTransitionHistory
  • return_immediately
  • stream: adapter-local opt-in for SendStreamingMessage

Follow-up and task-management modes are mutually exclusive with the SendMessage fields above. They include get_task, subscribe_task, cancel_task, and the push-notification config family (create, get, list, delete).


Caller Examples

A caller agent does not talk to A2A directly. It invokes the Chio tool that the adapter published, presenting its capability token. The Chio kernel validates the token, runs guards, calls the A2A server, and returns the result along with a receipt id.

TypeScript Caller

ts
import { ChioClient } from "@chio-protocol/sdk";

const chio = new ChioClient({
  sidecarUrl: "http://127.0.0.1:9090",
  capabilityToken: process.env.CHIO_CAP_TOKEN!,
});

const result = await chio.invoke({
  server: "a2a:agent.example.com",
  tool: "research",
  arguments: {
    message: "Summarise the latest filings for tenant-alpha.",
    metadata: { source: "partner-console" },
  },
});

console.log("receipt:", result.receiptId);
console.log("payload:", result.payload);

Python Caller

python
from chio import ChioClient

chio = ChioClient(
    sidecar_url="http://127.0.0.1:9090",
    capability_token=os.environ["CHIO_CAP_TOKEN"],
)

result = chio.invoke(
    server="a2a:agent.example.com",
    tool="research",
    arguments={
        "message": "Summarise the latest filings for tenant-alpha.",
        "return_immediately": True,
    },
)

# A long-running task was returned. Poll for completion.
while result.payload.get("task", {}).get("status", {}).get("state") != "TASK_STATE_COMPLETED":
    result = chio.invoke(
        server="a2a:agent.example.com",
        tool="research",
        arguments={"get_task": {"id": result.payload["task"]["id"]}},
    )

Streaming and Follow-Up

When stream: true is set, the adapter issues SendStreamingMessage and the kernel surfaces each upstream chunk as one stream event. The chunk payload is the raw A2A object, for example a status update:

json
{
  "statusUpdate": {
    "taskId": "task-1",
    "status": {
      "state": "TASK_STATE_COMPLETED"
    }
  }
}

If a call returns a task instead of a terminal message, follow-up modes let you poll, subscribe, or cancel it through the same tool:

json
{ "get_task": { "id": "task-1", "history_length": 2 } }

{ "subscribe_task": { "id": "task-1" } }

{ "cancel_task": { "id": "task-1", "metadata": { "reason": "user-request" } } }

Push notification callbacks

If the upstream agent advertises pushNotifications, the adapter also exposes config create, get, list, and delete through the same tool API. Callback URLs are validated the same way as the target: remote callbacks must be HTTPS, plain HTTP only for localhost.

Partner Admission

When a design partner has a narrow, expected contract, configure A2aPartnerPolicy so discovery rejects an Agent Card that does not match the configured contract. The policy enforces four checks:

Policy methodRejects when
with_required_tenantThe selected interface advertises a different tenant id
require_skillThe Agent Card does not expose the required skill id
require_security_schemeA required scheme name is missing from the card or its security requirements
allow_interface_originNo supported interface is advertised from an allowed origin

Discovery errors identify the failing tenant, interface, or scheme contract.


Durable Task Correlation

Long-running A2A tasks frequently outlive the adapter process. Setting with_task_registry_file persists one fail-closed binding per observed task id. The binding records:

  • Chio tool name
  • Selected interface URL
  • Protocol binding
  • Tenant and partner label
  • Last observed task state and its source

Follow-up calls are rejected unless the task_id was previously recorded for the same tool, server, binding, interface, and tenant. This prevents a caller from polling a task that is not associated with that caller context.


Cross-Organization Delegation

A2A supports cross-organization delegation: an agent at org A calls an agent at org B, which in turn calls a tool at org C. Without Chio, only the final hop is visible to any single operator. With Chio on both sides, each receipt records the chain:

text
org-a caller
  -> a2a adapter (chio, org-a)   signed receipt R1, call_chain = [a]
     -> a2a server (org-b agent)
        -> chio kernel (org-b)    signed receipt R2, call_chain = [a, b]
           -> tool at org-c       signed receipt R3, call_chain = [a, b, c]

The upstream task lineage is carried through governed_intent.call_chain, not attached as freeform operator notes. chio preserves that delegated provenance in the signed receipt and later projects it through /v1/reports/authorization-context or chio trust authorization-context list, alongside derived authorization-detail scope for commerce and metered-billing context.

Do not re-sign at the boundary

When relaying an A2A request from one org to another, do not strip and re-sign the call chain. Boundary adapters must extend the chain, not replace it. A chain that starts at the boundary makes prior hops invisible to downstream auditors and removes the delegation history.

What Is Not Shipped Yet

The following features remain on the roadmap:

  • Deeper long-running task lifecycle surfaces beyond GetTask, SubscribeToTask, CancelTask, and push-notification config CRUD
  • Custom or non-standard auth schemes beyond bearer, HTTP Basic, API key, OAuth, OpenID, and mTLS
  • Broader federation and partner onboarding flows beyond adapter-local admission policy and task correlation

Next Steps

  • LangGraph · orchestrate multi-agent graphs with per-node capability scoping
  • Temporal · enforce capabilities across durable workflow activities
  • Wrap an MCP Server · the same pattern applied to the Model Context Protocol