Chio/Docs
LOGIN · JOIN

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.

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 implicitEvery hop is a validated capability and a signed receipt, so the caller behind a call is a fact the operator holds

Current Scope

The adapter sends A2A-Version: 1.0 and selects any advertised interface whose protocolVersion begins 1., so a 1.x agent is in scope. It supports these 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, and it lives in a separate crate, chio-a2a-edge. 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, and it does not use an A2A client library. It opens a Chio session and calls the tool the adapter published, named after the A2A skill id. The kernel validates the capability behind the session, runs guards, calls the A2A server, and returns the result. Both SDKs below are the published clients: npm install @chio-protocol/sdk and pip install chio-sdk, which imports as chio.

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

const client = ChioClient.withStaticBearer(
  process.env.CHIO_BASE_URL!,
  process.env.CHIO_AUTH_TOKEN!,
);
const session = await client.initialize({
  clientInfo: { name: "partner-console", version: "0.1.0" },
});

try {
  const result = await session.callTool("research", {
    message: "Summarise the latest filings for tenant-alpha.",
    metadata: { source: "partner-console" },
  });
  console.log(JSON.stringify(result, null, 2));
} finally {
  await session.close();
}

Following Up on a Deferred Task

return_immediately makes the adapter hand back the A2A task instead of waiting for it. get_task is the follow-up mode on the same tool: it takes the task id and issues A2A GetTask rather than another SendMessage. It is mutually exclusive with every SendMessage field, so the follow-up call carries only get_task. The two calls below are the caller-side rendering of the pair the adapter's own test drives directly at crates/protocol/chio-a2a-adapter/src/tests/invoke_manifest.rs:58-80, which asserts the deferred call returns TASK_STATE_WORKING and the poll returns TASK_STATE_COMPLETED.

// The adapter answers with the A2A task rather than a terminal message.
const deferred = await session.callTool("research", {
  message: "Start a long-running research task",
  return_immediately: true,
});

// Poll it on the same tool. Only `get_task` may be present.
const followUp = await session.callTool("research", {
  get_task: { id: "task-1", history_length: 2 },
});

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 hop signs its own receipt:

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

The link between those receipts is GovernedTransactionIntent.call_chain, and it is not this adapter that writes it. The adapter never reads or sets the field: call_chain has no occurrence anywhere in crates/protocol/chio-a2a-adapter/src. A caller that wants cross-organization lineage supplies the context on its governed intent, and the kernel validates it.

The context is one link, not a growing list of hops. GovernedCallChainContext (crates/core/chio-core-types/src/capability/governance.rs:184-196) carries a stable chain_id, the parent_request_id, an optional parent_receipt_id, the origin_subject at the root of the chain, and the delegator_subject that handed control to the current subject. The full path is reconstructed by following those parent pointers, not read off any single receipt. The kernel refuses a link that does not hold together: an empty chain_id, a parent_request_id equal to the current request, or an origin_subject or delegator_subject that disagrees with the validated capability lineage (crates/kernel/chio-kernel/src/kernel/governed_validation.rs:738-812).

Chio preserves that 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-root the chain at the boundary

When relaying an A2A request from one org to another, carry the upstream chain_id and point the new link at the upstream request, rather than starting a fresh chain at the boundary. A chain that starts at the boundary makes prior hops unreachable to downstream auditors and loses the delegation history. Because this adapter does not populate the field, that is work the host process around it has to do.

What the Adapter Covers

The adapter governs the A2A surface and stops there:

  • The long-running task lifecycle is GetTask, SubscribeToTask, CancelTask, and push-notification config CRUD.
  • Authentication is bearer, HTTP Basic, API key, OAuth, OpenID, or mTLS. A custom scheme terminates in front of the adapter and presents one of these.
  • Admission is adapter-local: its own policy plus task correlation. Partner onboarding across a federation is the trust plane's job, not the adapter's.

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