# barrel_a2a

Agent2Agent (A2A) protocol v1.0 for Erlang/OTP: the protocol objects,
a server that exposes your agent over the JSON-RPC and HTTP+JSON
bindings, and a client that talks to any A2A agent. The server runs
on `h1`/`h2` (HTTP/1.1 and HTTP/2 on one port), the client on
`hackney`. Requires OTP 27 or later.

## What A2A is

A2A is a protocol for one autonomous agent to communicate and
collaborate with another autonomous agent: discover it through an
Agent Card, send it messages, follow the task it runs, receive the
artifacts it produces. It differs from MCP: MCP lets an agent invoke
tools and capabilities, A2A lets agents talk to each other as peers.
This library implements the A2A boundary only; see
[a2a-protocol.org](https://a2a-protocol.org) for the specification.

## What this library does

- Protocol objects as plain JSON maps (wire shape, binary camelCase
  keys) with accessor modules: `barrel_a2a_message`, `barrel_a2a_part`,
  `barrel_a2a_artifact`, `barrel_a2a_task`, `barrel_a2a_agent_card`,
  `barrel_a2a_event`, `barrel_a2a_error`.
- A server (`barrel_a2a_server`) with one handler per agent, task
  processes, streaming over SSE, push notifications, authentication
  and authorization hooks, extended cards, card signing, extensions,
  versioning and multi-tenancy, on both HTTP bindings.
- A client (`barrel_a2a_client`, `barrel_a2a_remote_task`) with card
  discovery and caching, signature verification, interface selection,
  blocking and streaming sends, follow-ups, task listing, cancel,
  push configs and a webhook receiver helper.
- Validation: structural checks plus JSON Schema 2020-12 validation
  against the official `a2a.json`.
- Pluggable task storage (`barrel_a2a_task_store`): in memory by
  default, DETS for tasks that survive a restart.

## What it deliberately does not do

- No agent runtime, planner, memory, MCP implementation, LLM
  abstraction or orchestration. The application owns agent execution;
  the library exposes it over A2A.
- No gRPC binding in this package. It is planned as `livery_grpc_a2a`,
  a separate package on `livery_grpc`, built on the binding-neutral
  contracts described in [guides/embedding.md](guides/embedding.md).

## Installation

```erlang
{deps, [
    {barrel_a2a, "0.1.0"}
]}.
```

Or from git:

```erlang
{deps, [
    {barrel_a2a,
        {git, "https://github.com/barrel-platform/barrel_a2a.git", {tag, "v0.1.0"}}}
]}.
```

## Server quick start

```erlang
Card = barrel_a2a_agent_card:new(#{
    name => <<"Echo Agent">>,
    description => <<"Repeats whatever you say">>,
    version => <<"1.0.0">>,
    skills => [#{id => <<"echo">>, name => <<"Echo">>, tags => [<<"demo">>]}]
}),
{ok, Server} = barrel_a2a_server:start(Card, #{
    handler => fun(_Ctx, Message) -> {ok, barrel_a2a_message:text(Message)} end,
    http => #{port => 8080}
}).
```

The card is served at `http://127.0.0.1:8080/.well-known/agent-card.json`,
JSON-RPC at `/a2a/jsonrpc`, REST under `/a2a/v1`. See
[guides/server.md](guides/server.md).

## Client quick start

```erlang
{ok, Agent} = barrel_a2a_client:connect(<<"http://127.0.0.1:8080">>),
Card = barrel_a2a_client:card(Agent),

%% Blocking send: the task (or a direct message) when it settles.
{ok, {task, Task}} = barrel_a2a_client:send(Agent, <<"hello">>),
completed = barrel_a2a_task:state(Task),

%% Streaming: a remote task handle that pushes events to a process.
{ok, RT} = barrel_a2a_client:start(Agent, <<"review this repository">>),
ok = barrel_a2a_remote_task:stream_to(RT, self()),
{ok, Final} = barrel_a2a_remote_task:result(RT, 60000),
Text = barrel_a2a_remote_task:text(RT).
```

See [guides/client.md](guides/client.md).

## Task lifecycle

Every transition goes through `barrel_a2a_task_state:transition/2`:

```
submitted      -> working | input_required | auth_required
                | completed | failed | canceled | rejected
working        -> working | input_required | auth_required
                | completed | failed | canceled
input_required -> working | canceled | failed
auth_required  -> working | canceled | failed
terminal       -> nothing
```

Handler results map to states:

| Result | State |
|---|---|
| `{ok, Result}` | `completed`, `Result` becomes the final artifact |
| `{message, Message}` | no task, a direct message reply |
| `{input_required, Message}` | `input_required`, next message re-enters the handler |
| `{auth_required, Message}` | `auth_required`, resumable with `barrel_a2a_ctx:resume/1` |
| `{reject, Message}` | `rejected` |
| `ok` | as left by the ctx calls; a task still `working` completes |
| `{error, Reason}` or a crash | `failed` |

See [guides/task-lifecycle.md](guides/task-lifecycle.md).

## Streaming

`SendStreamingMessage` and `SubscribeToTask` return SSE on both
bindings (JSON-RPC frames `{"jsonrpc":"2.0","id":..,"result":<StreamResponse>}`,
REST frames the bare `StreamResponse`). The stream opens with the
`Task` snapshot, then `statusUpdate` and `artifactUpdate` events, and
closes on a terminal state. Handlers publish with
`barrel_a2a_ctx:status/2,3`, `barrel_a2a_ctx:artifact/2,3` and
`barrel_a2a_ctx:message/2`. On the client `barrel_a2a_remote_task`
pushes events (`stream_to/2`) or lets you pull them (`next/2`), and
falls back to polling when the agent does not stream. See
[guides/streaming.md](guides/streaming.md).

## Push notifications

Enable with `push_notifications => #{...}` on the server; clients
register webhooks at send time or with
`barrel_a2a_client:create_push_config/3`, and decode deliveries with
`barrel_a2a_webhook:receive_notification/3`. Delivery is ordered per
config, retried with backoff, and guarded against SSRF. See
[guides/push-notifications.md](guides/push-notifications.md).

## Authentication hooks

Server side (`auth` option):

```erlang
none
| {bearer, fun((Token) -> {ok, Principal} | {error, unauthenticated | forbidden})}
| {api_key, HeaderName, fun((Key) -> ...)}
| {basic, fun((User, Password) -> ...)}
| {Module, State}                     %% Module:authenticate(Request, State)
| fun((Request) -> ...)               %% Request = #{headers, op, binding, peer}
```

Client side (`auth` option): `{bearer, Token | fun(() -> Token)}`,
`{api_key, HeaderName, Value}`, `{basic, User, Password}`,
`fun((Op) -> Headers)` or `none`; or `credentials`, a map keyed by
security scheme name resolved against the card. See
[guides/authentication.md](guides/authentication.md).

## Architecture

- Protocol objects: `barrel_a2a_message`, `_part`, `_artifact`,
  `_task`, `_task_state`, `_agent_card`, `_event`, `_error`.
- Codec and validation: `barrel_a2a_json`, `_jsonrpc`, `_rest`,
  `_sse`, `_validate`, `_schema`, `_jsonschema`, `_canonical`,
  `_card_sign`, `_version`, `_extensions`, `_tenant`.
- Server: `barrel_a2a_listener` (accept, TLS, ALPN, h1/h2) ->
  `barrel_a2a_http_engine` (routes, bindings, SSE loops) ->
  `barrel_a2a_server_core` (auth, tenant, version, extensions,
  capabilities, validation, scoping, operations) ->
  `barrel_a2a_task_proc` (one process per task) and `barrel_a2a_push`.
- Client: `barrel_a2a_client` (facade) -> `barrel_a2a_client_transport`
  behaviour (`barrel_a2a_client_http` for both HTTP bindings) ->
  `barrel_a2a_remote_task`.

The engine never touches sockets: it writes through a responder map
(`reply`, `stream_start`, `stream_chunk`, `stream_end`,
`disconnected`) that the listener builds over h1/h2 and that an
embedding application such as `livery_a2a` builds over its own
adapter. Embedding into livery uses `barrel_a2a_server:start/2` with
`listen => false`, `barrel_a2a_server:engine_config/2`,
`barrel_a2a_http_engine:routes/1` and
`barrel_a2a_http_engine:handle/6`. See
[docs/architecture.md](docs/architecture.md) and
[guides/embedding.md](guides/embedding.md).

## Spec coverage

Every operation, object and normative behaviour of A2A v1.0.1 is
handled here, except the gRPC binding which is planned as
`livery_grpc_a2a`. The section-by-section table is in
[docs/features.md](docs/features.md).

## Development

```bash
make compile       # rebar3 compile
make lint          # rebar3 lint
make xref          # rebar3 xref
make dialyzer      # rebar3 dialyzer
make eunit         # rebar3 eunit
make ct            # rebar3 ct
make examples-test # build and test examples/* against this checkout
make interop-python # a2a-sdk interop suite (needs python3)
make check         # fmt compile lint xref dialyzer eunit ct
```

Before changing the library itself, read
[docs/internals/tour.md](docs/internals/tour.md): it says where to
start, what you can skip, and which files a given kind of change
touches. [docs/internals/invariants.md](docs/internals/invariants.md)
lists the rules the runtime relies on that no single module shows, and
[docs/decisions](docs/decisions/README.md) records why the library is
shaped as it is.

## License

Apache-2.0
