Chio/Docs

Bridge Between Protocols

Your agent speaks one protocol. Your tool server speaks another. An A2A caller wants to reach an MCP tool; an ACP session needs to invoke an OpenAI function-call; a native worker needs to dispatch through an A2A skill. Without a governed bridge you either run two separate sessions with no shared lineage — receipts stop chaining at the boundary — or you write an ad-hoc translator that enforces nothing. Chio mediates the translation and unifies provenance: one kernel evaluation, one attenuated capability envelope, one receipt tree that spans the hop.

Prerequisites

The Chio CLI or Rust crate. At least one agent speaking the source protocol and one tool server speaking the target. You should already have read the relevant per-protocol guides: Wrap an MCP Server, Wrap an ACP Server, Govern OpenAI Tool Calls, and the A2A Adapter reference. Cross-protocol bridging is a composition over those edges, not a replacement for them.

Why Bridge Protocols

A single-protocol deployment is comfortable. Traffic enters over MCP, a tool call runs on an MCP server, a receipt comes out. The receipt ledger is linear, the capability chain is local, and the kernel's authority boundary matches the protocol boundary exactly.

Real agent systems are not single-protocol. Partner ecosystems expose A2A skills. Editor surfaces speak ACP. Frontier models hand you OpenAI-shaped tool_calls. Any serious tool catalog is already half MCP, half REST, and acquiring integrations that don't fit either. The moment a call crosses a protocol boundary, you face three bad options:

  • Two independent governed sessions. One kernel on the inbound edge, another on the outbound edge, no shared capability chain. Receipts are signed on both sides but they do not reference each other. You cannot prove, post-hoc, that the A2A request and the MCP call were the same logical act.
  • An ad-hoc translator. A shim that rewrites envelopes between protocols without any policy or receipts of its own. The kernel sees two unrelated hops. Attenuation is not enforced. A capability can silently widen when it crosses the shim.
  • Protocol lock-in. Pick one protocol for your whole estate, refuse to integrate anything else, and lose access to the tooling gravity of the other ecosystems.

Cross-protocol bridging is Chio's fourth option. A single signed route through the kernel, with a CrossProtocolCapabilityEnvelope that records the source protocol, the target protocol, and the attenuated scope, and a CrossProtocolTraceContext that threads receipts from every hop into one tree.


How the Cross-Protocol Substrate Works

The shared machinery lives in chio-cross-protocol. Its docstring is blunt about why it exists: it "centralizes the reusable types needed by outward protocol edges so A2A, ACP, and later MCP/OpenAI/HTTP bridge paths do not each redefine provenance, attenuation, and receipt-lineage behavior independently." Each per-protocol edge crate (A2A, ACP, MCP, OpenAI, HTTP) depends on this substrate rather than reinventing it.

Three moving parts carry almost all the weight:

  • TargetProtocolRegistry. Maps each known DiscoveryProtocol (Native, Http, Mcp, A2a, Acp, OpenAi) to a registered TargetProtocolExecutor. Resolution is driven by the x-chio-target-protocol schema extension on the tool definition, with a configured default for tools that don't declare one.
  • plan_authoritative_route. The control-plane route planner. Given a source protocol, a requested target protocol, an optional GovernedTransactionIntent, and a map of RouteAvailabilityStatus values, it returns a RoutePlanningOutcome with a RouteSelectionDecision of Select, Attenuate, or Deny. The outcome is captured as signed RouteSelectionEvidence that rides in the receipt.
  • CrossProtocolOrchestrator. The runtime. It extracts a CrossProtocolCapabilityRef from the inbound envelope, builds the attenuated CrossProtocolCapabilityEnvelope, calls plan_authoritative_route, hands a CrossProtocolTargetRequest to the selected executor, and returns an OrchestratedToolCall whose receipt carries authority_path = "cross_protocol_orchestrator".
rendering…
Caller speaks protocol A. The registry resolves the authoritative target protocol B. The route planner chooses a candidate or attenuates to native. The kernel evaluates the attenuated capability. The target executor runs the call. Every hop is written into one trace context and one signed receipt envelope.

The kernel never sees two unrelated requests. It sees one evaluation whose route_selection metadata records the source protocol, the requested target, the selected target, and every candidate that was considered and rejected. If the route planner denies (for example, because a governed intent disallowed projected protocols and no native fallback existed), the receipt is a denial receipt with the reason populated; the target executor is never invoked.


The Four Bridges Today

Four edge crates currently depend on the cross-protocol substrate. Their coverage is honest about what's authoritative and what remains future work.

BridgeCrateDirectionStatus
A2Achio-a2a-edge, chio-a2a-adapterInbound (A2A caller → native/MCP target)Shipped. Claim-eligible authoritative surface with message/send, message/stream, task/get, task/cancel.
ACPchio-acp-edge, chio-acp-proxyInbound (ACP session → native/MCP target)Shipped. Authoritative surface with tool/invoke, tool/stream, tool/resume, tool/cancel.
OpenAIchio-openaiInbound (OpenAI tool_calls → native target)Shipped. Projects allowed calls as function_call_output items with a receipt_ref for chain-of-custody.
MCPchio-mcp-edgeInbound (MCP tools/call → native target)Shipped for the MCP-to-native path. Projecting outward from MCP to A2A or ACP as a target protocol is on the roadmap; the substrate already exposes the necessary executor seam.

HTTP is not a fifth bridge in its own right — it rides as a transport under the MCP and OpenAI edges. If you want to govern a REST API for agent consumption instead, see Bridge OpenAPI to MCP or Protect an API.

One substrate, many edges

The reason all four bridges agree on provenance semantics is that none of them owns the cross-protocol types. They all consume the shared chio-cross-protocol crate. Adding a new protocol edge means implementing CapabilityBridge and optionally a TargetProtocolExecutor; you inherit the receipt envelope, the trace context, and the attenuation math for free.

Wire a Bridge

At the crate level, wiring a cross-protocol bridge has three moves: register the target executors your deployment should support, declare which routes are currently available, and invoke the authoritative route planner before executing the hop.

The following is a conceptual Rust sketch shaped after the substrate's public surface in chio-cross-protocol. Exact wiring in a given edge crate adds a per-protocol CapabilityBridge implementation — see chio-a2a-edge, chio-acp-edge, and chio-openai for the production shapes.

bridge_example.rs
use std::collections::BTreeMap;
use chio_cross_protocol::{
    CrossProtocolOrchestrator, DiscoveryProtocol, OpenAiTargetExecutor,
    RouteAvailabilityStatus, TargetProtocolRegistry,
    plan_authoritative_route,
};
use chio_kernel::ChioKernel;

fn build_orchestrator<'a>(kernel: &'a ChioKernel) -> CrossProtocolOrchestrator<'a> {
    // 1. Register target executors for every non-native protocol this
    //    deployment is willing to dispatch into.
    let openai = OpenAiTargetExecutor::default();
    let registry = TargetProtocolRegistry::new(DiscoveryProtocol::Native)
        .with_executor(&openai);
        // .with_executor(&mcp_executor)   // add MCP target executor
        // .with_executor(&a2a_executor)   // add A2A target executor

    // 2. Declare which protocol families are available right now. The
    //    route planner refuses to select an unavailable target and will
    //    attenuate to the native fallback where allowed.
    let orchestrator = CrossProtocolOrchestrator::new(kernel)
        .with_registry(registry)
        .with_protocol_availability(
            DiscoveryProtocol::Native,
            RouteAvailabilityStatus::available(),
        )
        .with_protocol_availability(
            DiscoveryProtocol::OpenAi,
            RouteAvailabilityStatus::available(),
        )
        .with_protocol_availability(
            DiscoveryProtocol::A2a,
            RouteAvailabilityStatus::unavailable("a2a executor not configured"),
        );

    orchestrator
}

The planner is a standalone function you can call before dispatch to materialise the route decision and its evidence. Edge crates use this both inside the orchestrator and in tests:

plan_route.rs
let mut availability = BTreeMap::new();
availability.insert(DiscoveryProtocol::Native, RouteAvailabilityStatus::available());
availability.insert(DiscoveryProtocol::OpenAi, RouteAvailabilityStatus::available());

let planning = plan_authoritative_route(
    "req-01HXYZ",                  // origin request id
    DiscoveryProtocol::A2a,        // source protocol
    DiscoveryProtocol::OpenAi,     // requested target
    governed_intent.as_ref(),      // optional GovernedTransactionIntent
    &registry,
    &availability,
)?;

match planning.evidence.decision {
    RouteSelectionDecision::Select     => { /* ship it */ },
    RouteSelectionDecision::Attenuate  => { /* fell back to native or alt target */ },
    RouteSelectionDecision::Deny       => { /* policy said no path is safe */ },
}

The evidence emitted by the planner is canonical JSON that gets embedded in the receipt via route_selection_metadata. That is the piece that makes a denied route just as auditable as an allowed one — every candidate considered, along with its availability status and reason, is signed.

The CapabilityBridge trait supplies the protocol-specific glue: extracting a CrossProtocolCapabilityRef from the inbound envelope, injecting it back out when building the outbound envelope, and optionally surfacing protocol context such as A2A task ids or ACP session ids. You do not implement route planning or attenuation there; those live in the substrate.


Receipt Lineage Across a Bridge

A cross-protocol receipt is the same ChioReceipt you already know, with two additions: the metadata carries the chio.cross-protocol-cap.v1 capability envelope, and the authority_path is the constant cross_protocol_orchestrator. That pair tells downstream verifiers that this decision flowed through the shared substrate and not a direct edge invocation.

bridged-receipt.json
{
  "version": "chio.receipt.v1",
  "receipt_id": "01HXYZ...7K4",
  "decision": "allow",
  "capability_id": "cap_01HXYZ...4J8",
  "authority_path": "cross_protocol_orchestrator",
  "authoritative": true,
  "metadata": {
    "chio": {
      "bridge": {
        "bridgeId": "bridge_a2a_to_native_01",
        "sourceProtocol": "a2a",
        "targetProtocol": "native",
        "terminalProtocol": "native",
        "capabilityEnvelope": {
          "schema": "chio.cross-protocol-cap.v1",
          "targetProtocol": "native",
          "attenuatedScope": {
            "grants": [
              { "serverId": "srv-files", "toolName": "read_file" }
            ],
            "resourceGrants": [],
            "promptGrants": []
          },
          "bridgedAt": 1744820523,
          "bridgeId": "bridge_a2a_to_native_01"
        },
        "trace": {
          "traceId": "trc_01HXYZ...W2R",
          "sessionFingerprint": "sha256:3f2c...",
          "hops": [
            { "protocol": "a2a",    "requestId": "a2a-msg-84",  "bridgeId": "bridge_a2a_to_native_01", "timestamp": 1744820522 },
            { "protocol": "native", "requestId": "krn-req-517", "bridgeId": "bridge_a2a_to_native_01", "timestamp": 1744820523 }
          ]
        }
      },
      "routeSelection": {
        "routeSelectionId": "rsel_01HXYZ...Q9",
        "decision": "select",
        "sourceProtocol": "a2a",
        "requestedTargetProtocol": "native",
        "selectedTargetProtocol": "native",
        "selectedProtocols": ["native"],
        "candidates": [
          { "routeId": "a2a->native", "targetProtocol": "native", "available": true,  "selectedProtocols": ["native"] },
          { "routeId": "a2a->openai", "targetProtocol": "open_ai", "available": false, "availabilityReason": "openai executor not configured", "selectedProtocols": ["open_ai"] }
        ]
      }
    }
  },
  "signature": "ed25519:..."
}

Two things to notice. First, the attenuatedScope inside the envelope is a strict subset of the inbound capability's scope — only the one grant the target tool actually needed survives the bridge. Second, the rejected candidate is still in the receipt, along with why it was rejected. A denial at the bridge is not a black hole; it is a first-class signed artefact with full candidate lineage.

The trace.traceId is how you stitch hops together across a multi-step workflow. Query the receipt log by traceId and you get the tree of every receipt that participated in that logical act, regardless of which protocol each hop happened to speak.

Attenuation only narrows

The attenuatedScope on the envelope must be a subset of the inbound capability's scope. The bridge does not manufacture authority; it only filters the parent scope down to the grant(s) the target tool requires. An envelope whose attenuated scope is not a subset of the parent is rejected by the kernel as Deny. Tokens can narrow across a bridge; they must never widen.

Policy Patterns

Because the bridged call lands on the kernel as a normal tool invocation (with extra metadata), everything in your HushSpec policy still applies. But a few patterns are specific to cross-protocol routing and worth calling out.

  • Narrow the allowed target protocols per inbound caller. A public A2A endpoint should probably only reach Native targets. An internal ACP session might reach Native or Mcp, but never OpenAi. Encode this as a policy rule on the chio.bridge.targetProtocol metadata field.
  • Require prior attenuation. Refuse to cross a bridge unless the inbound subject's capability has already been attenuated — in other words, is not the root delegation. This keeps the blast radius of a compromised token from crossing a protocol boundary untouched. Combine with capability delegation.
  • Disallow projected protocols. Set disallow_projected_protocols on the GovernedTransactionIntent when the caller explicitly wants a native-only execution. The planner will Attenuate to Native if available and Deny otherwise. This is the kill-switch for scenarios where bridging is a known-bad shape (regulated workflows, evidence-only calls).
  • Bind to a BridgeFidelity gate. Tools are published to a target protocol only if the substrate classifies their BridgeFidelity as Lossless or Adapted. Write policy that refuses Adapted bridges for workflows that cannot tolerate caveats (streamed output collapsing, cancellation semantics dropping, permission prompts disappearing). See Write a Policy for the rule shapes.

The mechanical rule is always the same: the kernel is the single decision point, and the cross-protocol envelope is evidence. A policy that doesn't want to permit a bridge simply denies the call as it would any other.


Fidelity and Truthful Publication

Not every tool maps cleanly into every protocol. A tool whose contract depends on streaming partial output cannot be faithfully projected onto an A2A compatibility surface that only returns collated terminal payloads. The substrate captures this explicitly through BridgeFidelity and BridgeSemanticHints:

  • Lossless — the tool can be projected with no loss of semantics; published by default.
  • Adapted { caveats } — published with the caveats surfaced in the outward discovery metadata so callers know what to expect (for example: "streaming output is collected into a final payload").
  • Unsupported { reason } — not published on that target protocol at all. The tool remains available on its authoritative surface and is omitted from the outward manifest for bridges that cannot represent it honestly.

The inputs to the fidelity decision come from the tool's x-chio-* schema extensions: x-chio-publish, x-chio-approval-required, x-chio-streaming, x-chio-cancellation, x-chio-partial-output, and x-chio-target-protocol. You don't have to set them; the substrate infers sensible defaults from the tool's latency hint and schema. You should set them for any tool with non-trivial lifecycle behaviour.

Provenance stays unified

The single most valuable invariant of cross-protocol bridging is that the receipt lineage does not fragment. Whether a call entered as A2A, ACP, OpenAI tool_calls, or MCP tools/call, every hop reaches the same kernel, writes into the same traceId, and produces a receipt you can verify with the same tooling. Audit, billing, and incident response all look the same on the other side of the bridge.

Next Steps

  • Architecture · where the cross-protocol substrate sits relative to the kernel and the per-protocol edges
  • Capabilities and Delegation · the attenuation math the bridge enforces before a call reaches a target executor
  • Wrap an MCP Server · the canonical inbound pattern that composes with cross-protocol routing
  • Wrap an ACP Server · how the ACP authoritative surface registers with the substrate
  • Govern OpenAI Tool Calls · the OpenAI inbound bridge, including the function_call_output projection and receipt_ref trail
Bridge Between Protocols · Chio Docs