Packages
A Spark.Dsl.Extension that exposes Ash.Resource/Ash.Domain actions as A2A protocol agent skills, compiling a verified AgentCard and dispatching inbound A2A messages to Ash actions.
Current section
Files
Jump to
Current section
Files
README.md
# AshA2A
[](https://hex.pm/packages/ash_a2a)
[](https://hexdocs.pm/ash_a2a/)
[](https://github.com/seanchatmangpt/ash_a2a/actions/workflows/ci.yml)
[](LICENSE)
A `Spark.Dsl.Extension` that exposes `Ash.Resource`/`Ash.Domain` actions as
[A2A protocol](https://github.com/a2aproject/A2A) agent skills, built on the
[`:a2a` Elixir SDK](https://github.com/actioncard/a2a-elixir) (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:
- builds a real `A2A.AgentCard` (`AshA2A.Info.agent_card/2`) advertising each
exposed skill,
- fails closed at compile time (`AshA2A.Verify`) if a skill override names a
nonexistent action (`:REFUSED_ACTION_NOT_FOUND`) or duplicates a skill
name (`:REFUSED_DUPLICATE_SKILL_NAME`),
- and dispatches an inbound `A2A.Message` to the right Ash action
(`AshA2A.Dispatcher.dispatch/5`), either as a bare function call or
through a real supervised `A2A.Agent` process (`AshA2A.Agent`) — routing
consequence-bearing skills (`:change`/`:external_do`) through the
receipted `AshA2A.CommandBus` with authority admission and replay-safe
receipts.
## Requirements
- Elixir `~> 1.19` with OTP 27+ (CI runs OTP 28.0 / Elixir 1.19.0;
`.tool-versions` and the swarm Docker image pin OTP 27.2.4).
- Ash `~> 3.0`.
- No Rust toolchain is needed to **use** the published package. Rust is only
needed to build the native HDDL/FOND planner when developing this repo or
when you use the deterministic planning path — the binary is not shipped
on Hex, so point `config :ash_a2a, :hddl_cli_path` at your own build (see
[Configuration](docs/reference/configuration.md)).
## Installation
```elixir
def deps do
[
{:ash_a2a, "~> 26.9"}
]
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):
```elixir
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):
```elixir
message = A2A.Message.new_user([A2A.Part.Data.new(%{})])
{:reply, [%A2A.Part.Data{data: %{results: []}}]} =
AshA2A.Dispatcher.dispatch(:echo, message, MyApp.Echo)
```
`dispatch/5` takes further `history` and `auth_identity` arguments
(both default to `nil`). `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](docs/how-to/authenticate-agent-requests.md).
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](docs/tutorials/getting-started.md); for the exact
wire contract, see the
[A2A endpoint reference](docs/reference/a2a-endpoint-contract.md).
## 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:
```sh
cd native/hddl_cli && cargo build --release --locked && cd -
```
- `native/hddl_cli` is the sole integration surface for the real FOND/HTN
planner (`ferroplan`) — dozens of tests (deterministic HDDL synthesis,
the FreedomGym facilitator fixture, request-router phase dispatch,
Chicago qualification courts) invoke this binary as a subprocess and
raise a clear, actionable error (`hddl_cli_not_built`) if it is missing —
never a silent skip. Without it, `mix test` reports real test *failures*
(not skips), which is easy to mistake for a code regression.
- `native/graphlaw_host` (optional, NOT built by CI) backs the GraphLaw
WASM runtime B; if left unbuilt, the one test that needs it reports a
named, correctly-handled skip rather than a failure.
- Postgres 16 on `localhost:55432` (user/password `postgres`, db
`ash_a2a_test`, per `config/test.exs`) is needed by the real Oban
delivery qualification tests.
- The canonical full-suite invocation is `mix test --max-cases 6` (the
suite spawns `:peer` nodes and subprocesses; higher parallelism trips
port-bind races). 1–2 known-flaky tests are documented in the
[CHANGELOG](CHANGELOG.md) under `[26.9.17]`.
Full detail: [Testing ash_a2a (your app and this
repo)](docs/how-to/test-your-ash_a2a-app.md). Both `target/` directories are
gitignored build artifacts and are never committed.
## Documentation
This project follows the [Diataxis](https://diataxis.fr/) documentation
framework: tutorials for learning, how-to guides for specific tasks,
reference for lookup, and explanation for understanding. It is published on
[HexDocs](https://hexdocs.pm/ash_a2a/) and buildable locally with `mix docs`.
- **Tutorials** — [Getting Started](docs/tutorials/getting-started.md): a
complete, end-to-end walkthrough from resource declaration through direct
dispatch, a supervised agent process, and serving the agent over HTTP.
- **How-to guides**:
- [Authenticate inbound A2A requests](docs/how-to/authenticate-agent-requests.md)
— wire `A2A.Plug.Auth` so a verified credential becomes
`context.actor`/`context.tenant`, and grants — not authentication —
decide consequential authority.
- [Verify authority on async (Oban) paths](docs/how-to/verify-authority-on-async-paths.md)
— re-verify grants in your own Oban workers with
`ObanAuthority.verify_live!/3` so a revoked grant cannot actuate from a
stale queue payload.
- [Observe dispatch with OCEL](docs/how-to/observe-dispatch-with-ocel.md)
— forward every dispatch as an OCEL v2 event to a process-mining
ingest endpoint.
- [Use role-based LLM resolution](docs/how-to/use-role-based-llm-resolution.md)
— resolve LLM-backed actions through abstract roles instead of
hardcoded provider strings.
- [Enable semantic requests](docs/how-to/enable-semantic-requests.md) —
opt a resource and caller into the semantic-compilation pipeline
(`AshA2A.Semantic.Compiler`).
- [Test your ash_a2a app](docs/how-to/test-your-ash_a2a-app.md) — run
commands, native prerequisites, test taxonomy, known flakiness.
- **Reference**:
- [Module index](docs/reference/index.md)
- [DSL reference](docs/reference/dsl.md) — the `a2a` section, `skill`
entity, `hddl_operator`, and `semantic_requests` gate.
- [Configuration](docs/reference/configuration.md) — every application
config key and environment variable the library reads.
- [Telemetry events](docs/reference/telemetry.md) — the event catalog
with payloads.
- [Mix tasks](docs/reference/mix-tasks.md) — the 13 shipped tasks.
- [A2A endpoint contract](docs/reference/a2a-endpoint-contract.md) —
served HTTP surface: agent card, JSON-RPC methods, error codes,
streaming, auth.
- **Explanation**:
- [Architecture](docs/explanation/architecture.md) — the capability
projection, admission/receipt layers, adapters, and consequence
semantics.
- [Message lifecycle](docs/explanation/message-lifecycle.md) — one
request end to end, from wire to receipt.
- [Canonical graph identity](docs/explanation/canonical-graph-identity.md)
and [GraphLaw WASM integration](docs/explanation/graphlaw-wasm-integration.md).
### Internal evidence and reports (not user documentation)
These artifacts are deliberately published with the repository but are
point-in-time engineering records, not guides:
- `docs/explanation/chicago-benchmark-report.md`,
`v26.9.17-stress-report.md`, `v26.9.17-hardening-audit.md`,
`sa2a-v26-9-17-capability-coverage-sweep.md`,
`sa2a-v26-9-17-hddl-reachability-analysis.md` — measured evidence from
the v26.9.17 hardening/benchmark/stress pass (referenced from the
CHANGELOG).
- `docs/explanation/chicago-conformance-court.md` — what the RFC-SA2A-002
conformance court is.
- `docs/AIRGAP_READINESS_REPORT.md`, `docs/ENTERPRISE_READINESS_REPORT.md`,
`docs/SSP_CONTROL_APPENDIX.md` — security-posture evidence gathered
against the `k8s/` swarm manifests (kind-cluster scope, 2026-09-15);
explicitly not an ATO.
- `docs/rfc/` — RFC-SA2A-001/002 (Proposed Standard status).
- `MANUFACTURING_RECEIPT.md`, `docs/jira/`, `litho.docs/`, `research/` —
internal session history and manufacturing records.
## 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. See
[the authentication how-to](docs/how-to/authenticate-agent-requests.md) and
[SECURITY.md](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](CHANGELOG.md) — note that `main` routinely runs ahead of the
latest published Hex release.
## License
MIT — see [LICENSE](LICENSE).