Module a2a

Module a2a 

Source
Expand description

A2A (agent-to-agent) protocol support (DW-114, AI-12 #252).

This module implements the A2A protocol’s task lifecycle state machine and the v0.3 JSON-RPC wire format against the A2A Protocol Specification (https://github.com/a2aproject/A2A) as of 2025-Q4. The task state vocabulary (submitted, working, completed, failed, canceled) and the legal transitions are stable; the wire format (JSON-RPC method names, envelope shapes) is the v0.3 shape (message/send, message/stream, tasks/get, etc.).

§What is implemented

  • The TaskStateMachine struct: a validating state machine for A2A task lifecycle transitions. Legal transitions:
    • Submitted -> Working (the agent starts processing)
    • Submitted -> Failed (the agent rejects the task)
    • Submitted -> Canceled (the caller cancels before processing)
    • Working -> Completed (the agent finishes successfully)
    • Working -> Failed (the agent fails mid-processing)
    • Working -> Canceled (the caller cancels during processing)
    • Completed, Failed, Canceled are terminal (no transitions out). Illegal transitions return an A2AError naming the attempted and target states.
  • The A2AAdapter struct implementing ProviderAdapter: it translates a canonical ChatRequest into an A2A v0.3 JSON-RPC message/send (or message/stream for streaming) body (A2AAdapter::build_request) and parses an A2A response back into the canonical ChatResponse (A2AAdapter::parse_response). The adapter handles both direct Message results (with parts[] using the kind discriminator) and Task results (extracting text from status.message or artifacts). Error and stream-event parsing are wired too (the SSE framing reuses crate::ai::sse); stream events cover TaskStatusUpdateEvent, TaskArtifactUpdateEvent, and direct Message frames.
  • The AgentCard struct and AgentCardParser: parse the JSON-LD-ish Agent Card discovery doc (name, description, url, version, capabilities, authentication) from an inline JSON value or a file path.
  • The TaskLifecycle enum: the task state vocabulary (Submitted, Working, Completed, Failed, Canceled).
  • The A2ASession struct: reuses the MCP session-management patterns (session id, TTL, max-concurrent) for agent-to-agent task sessions. Each session owns a TaskStateMachine.
  • The handle_a2a_request function: routes an A2A call through the existing dataplane::ai_proxy path (the transport is the agent’s named upstream, exactly like a regular provider).

§v0.3 wire format (AI-12, #252)

The adapter speaks the A2A v0.3 JSON-RPC binding:

  • Request: message/send (non-streaming) or message/stream (streaming). The canonical ChatRequest is folded into params.message.parts[] using the kind-discriminated part shape (text, file, data). System messages are folded as a text preamble (A2A has no system role). The message carries a generated messageId and role: "user".
  • Response: the adapter detects whether the JSON-RPC result is a Message (has parts or kind: "message") or a Task (has status or kind: "task"). For Tasks, text is extracted from status.message.parts or artifacts[].parts. The task state maps to FinishReason (completed -> Stop, failed -> Other("failed"), etc.).
  • Streaming: SSE frames carry JSON-RPC responses whose result is one of TaskStatusUpdateEvent (has status), TaskArtifactUpdateEvent (has artifact), or a direct Message (has parts). Terminal states emit a finish delta.
  • Backward compat: the adapter still accepts the older content/type message shape and the result.message wrapper for agents that have not migrated to v0.3.

§Dependency direction

ai depends on config only. The A2A adapter is a pure translator (no HTTP client of its own), mirroring the OpenAI/Anthropic/Gemini adapters and the MCP gateway. No new dependencies are introduced (the scaffold is hand-rolled, the same locked M4 decision as MCP).

§Feature gate

The a2a cargo feature is flag-only (no new deps). When it is OFF, the ai.a2a config block is accepted but inert: validation warns, and CompiledA2a::compile returns None (no A2A providers are wired into the alias table). When it is ON, each configured agent appears as a provider of kind a2a in the model alias table.

Structs§

A2AAdapter
The A2A provider adapter (DW-114, DP-12 #252). A pure translator, like the OpenAI/Anthropic/Gemini adapters: it holds no state and opens no connections. The transport is the agent’s named upstream, driven from dataplane::ai_proxy.
A2ASession
One A2A task session (DW-114), mirroring MCP’s session model: a session id, a TTL, and a max-concurrent cap. The session is a correlation handle for an agent-to-agent task exchange and owns a TaskStateMachine tracking the task’s lifecycle state.
A2AStub
The former stub error type, kept as a compatibility alias for the handle_a2a_request function (which is about the network call, not the state machine). New code should use A2AError directly.
AgentCard
An Agent Card (DW-114): the JSON-LD-ish discovery doc an A2A agent publishes to declare its identity, capabilities, and authentication. Parsed from an inline JSON value or a file path by AgentCardParser.
AgentCardParser
Parses Agent Card JSON (DW-114) from an inline JSON value or a file path. The card shape is JSON-LD-ish: a top-level object with name, description, url, version, capabilities, and authentication fields. Unknown fields are tolerated (the spec is not frozen). Required fields are name and url; missing either is a parse error.
CompiledA2a
The compiled A2A surface (DW-114): the agent table and session policy. Built at AiRuntime compile time from the ai.a2a config block; immutable once built. None when the block is absent or the a2a feature is off (the block is inert).
CompiledA2aAgent
One compiled A2A agent (DW-114): the resolved provider entry an alias can route to. Built at AiRuntime compile time from the ai.a2a.agents[] config; immutable once built.
TaskStateMachine
A validating state machine for A2A task lifecycle transitions (DW-114). Holds the current state; transition methods validate the transition and return the new state, or an A2AError when the transition is illegal.

Enums§

A2AError
Error returned by A2A task lifecycle operations. Replaces the former A2AStub (the spec is now sufficiently stable for the task state machine; the wire format may still evolve). The A2AStub type is kept as an alias for backward compatibility with the handle_a2a_request function (which is about the network call, not the state machine).
TaskLifecycle
The A2A task lifecycle state vocabulary (DW-114). The states follow the A2A Protocol Specification draft: submitted, working, completed, failed, canceled. The legal transitions between them are enforced by TaskStateMachine.

Functions§

a2a_providers
The A2A agent entries that should appear as providers in the alias table (DW-114). Each entry is (name, upstream) — the provider name and the upstream that carries its transport. The AiRuntime compile path inserts these into the provider pool with kind: a2a. Returns an empty vec when the block is absent or inert.
compile_a2a
Build the CompiledA2a from the gateway’s ai.a2a block, for the AiRuntime compile path. Returns None when the block is absent or inert (feature off / disabled).
handle_a2a_request
Route an A2A call through the existing dataplane::ai_proxy path (DW-114). The transport is the agent’s named upstream, exactly like a regular provider: the A2AAdapter builds the task-submit request, ai_proxy places the call through the upstream, and the response is parsed back to the canonical shape. This function is the seam the dataplane calls for an A2A-routed alias; today it returns an A2AStub error because the adapter intentionally does not own an HTTP client (the transport is the agent’s upstream, driven from dataplane::ai_proxy). The task lifecycle state machine itself is implemented (see TaskStateMachine); this function is the network-call seam, not the state machine.