AshA2A

Hex Version HexDocs CI License: MIT

A Spark.Dsl.Extension that exposes Ash.Resource/Ash.Domain actions as A2A protocol agent skills, built on the :a2a Elixir SDK (a published Hex package, {:a2a, "~> 0.2"}, implementing A2A protocol v0.3 over JSON-RPC 2.0/HTTP with SSE streaming).

Naming: the library and module prefix is AshA2A, the Hex package is ash_a2a, and SA2A is the internal project codename used by the RFCs (docs/rfc/) and the conformance courts (docs/explanation/).

Every public Ash action on an extended resource or domain is projected into a verified capability index — no declaration required. An optional a2a do skill ... end block adds A2A-only metadata overrides, and the compiled index:

Requirements

Installation

def deps do
[
{:ash_a2a, "~> 26.9.31"}
]
end

:a2a arrives transitively; pin {:a2a, "~> 0.2"} explicitly only if your own code calls A2A.* modules directly.

mix ash_a2a.install (an Igniter task) wires the extension into an existing project; see Mix.Tasks.AshA2a.Install.

Usage

Declare the DSL on a resource (or domain):

defmodule MyApp.Echo do
use Ash.Resource,
domain: MyApp.Domain,
data_layer: Ash.DataLayer.Ets,
extensions: [AshA2A]
attributes do
uuid_primary_key(:id)
attribute(:message, :string, public?: true)
end
actions do
defaults([:read])
end
end
defmodule MyApp.Domain do
use Ash.Domain, extensions: [AshA2A]
resources do
resource(MyApp.Echo)
end
end

Public actions are exposed with no a2a block at all; a2a do skill(:echo, :read) end is an optional override (rename, describe, tag, exclude, or classify). AshA2A.Transformers.BuildCapabilityIndex compiles the residual overrides and AshA2A.Verify checks the projection fail-closed after compilation.

Dispatch a message directly (no process):

message = A2A.Message.new_user([A2A.Part.Data.new(%{})])
{:reply, [%A2A.Part.Data{data: %{results: []}}]} =
AshA2A.Dispatcher.dispatch(:echo, message, MyApp.Echo)

dispatch/6 takes further history, auth_identity, and opts arguments (history/auth_identity default to nil-able values, opts to [] — e.g. :resolved_skill carries the exact resolved skill through a CommandBus re-dispatch, fixing multi-resource namesake :capability_mismatch refusals). auth_identity is the trust boundary: with the default nil, context.actor/context.tenant resolve to nil and dispatch fails closed for anything your Ash policies gate on identity — identity only ever arrives from transport-verified auth (A2A.Plug.Auth), never from message metadata. See Authenticate inbound A2A requests.

To boot supervised agent processes, serve them over HTTP (agent card + JSON-RPC + SSE), and drive them with A2A.Client, continue with the Getting Started tutorial; for the exact wire contract, see the A2A endpoint reference. Beyond the vendored SDK plug, the library ships its own AshA2A.A2ATransport.Plug — a drop-in wrapper implementing the methods the vendored plug refuses: supervised message/stream fan-out with tasks/resubscribe replay, push-notification config RPCs with signed webhook delivery, and the authenticated extended card (same reference).

Local development setup

Running this repo's own mix test requires one native Rust CLI binary to be built first. CI (.github/workflows/ci.yml) builds it automatically (pinned Rust 1.97.1); a local clone does not, so this is a real, one-time manual step:

cd native/hddl_cli && cargo build --release --locked && cd -

Full detail: Testing ash_a2a (your app and this repo). Both target/ directories are gitignored build artifacts and are never committed.

Documentation

This project follows the Diataxis documentation framework: tutorials for learning, how-to guides for specific tasks, reference for lookup, and explanation for understanding. It is published on HexDocs and buildable locally with mix docs.

Internal evidence and reports (not user documentation)

These artifacts are deliberately published with the repository but are point-in-time engineering records, not guides. As of v26.9.31 the audit-era records live under docs/archive/, grouped by kind:

Security

Identity is a trust boundary: actor/tenant only ever come from transport-verified A2A.Plug.Auth output, and consequential (:change/:external_do) skills additionally require a standing grant from AshA2A.Authority.Grant — authentication alone never confers authority (RFC-SA2A-001 S29). Neither shipped broker (InMemory, Ekv) is Sybil-resistant; bring your own identity system for production. Optional absorbed boundaries extend that identity system: SPIFFE workload-attested identities (AshA2A.SPIFFE.*) and OpenID AuthZEN PDP policy evidence (AshA2A.AuthZEN.*) bind external decisions in as evidence — never as authority. See the authentication how-to and SECURITY.md.

Status

Versioning is calendar-based (26.9.x); ~> 26.9 pins within the 26.9 series. Semantic Versioning is intended after 1.0. See the CHANGELOG — note that main routinely runs ahead of the latest published Hex release.

License

MIT — see LICENSE.