Elixir implementation of the Agent-to-Agent (A2A) protocol. Exposes ADK agents as A2A-compatible HTTP endpoints and enables inter-agent communication over JSON-RPC.
- Full A2A protocol support — All 10 JSON-RPC methods (message/send, message/stream, tasks/get, tasks/cancel, push config CRUD, etc.)
- Server — Plug-based HTTP endpoint with JSON-RPC dispatch and SSE streaming
- Client — HTTP client for consuming remote A2A agents (sync + streaming via SSE)
- ADK Integration — Bridge ADK agents into the A2A protocol via
ADKExecutor+Converter - RemoteAgent — Wrap any remote A2A agent as a local ADK agent for orchestration
- Agent Card — Serve agent metadata at
/.well-known/agent.json - Push Notifications — Webhook-based task update delivery
- Pluggable Storage — Behaviour-based
TaskStoreandPushConfigStorewith in-memory implementations
# mix.exs
def deps do
[
{:a2a_elixir_sdk, "~> 1.0"}
]
endThis package ships a usage-rules.md file describing conventions and gotchas for
agent-assisted development. If you use usage_rules
in your project, add "a2a_elixir_sdk" (or "a2a_elixir_sdk:all" for sub-rules like
a2a_elixir_sdk:server, a2a_elixir_sdk:client, a2a_elixir_sdk:adk_bridge,
a2a_elixir_sdk:types) to your sync list and run mix usage_rules.sync.
# Build your ADK agent and runner
agent = ADK.Agent.CustomAgent.new(%ADK.Agent.Config{
name: "my-agent",
run: fn _ctx -> [ADK.Event.new(content: ADK.Types.Content.new_from_text("model", "Hello!"))] end
})
{:ok, session_svc} = ADK.Session.InMemory.start_link()
{:ok, runner} = ADK.Runner.new(app_name: "my-app", root_agent: agent, session_service: session_svc)
# Configure the A2A handler
{:ok, store} = A2AEx.TaskStore.InMemory.start_link()
config = %A2AEx.ADKExecutor.Config{runner: runner, app_name: "my-app"}
handler = %A2AEx.RequestHandler{
executor: {A2AEx.ADKExecutor, config},
task_store: {A2AEx.TaskStore.InMemory, store},
agent_card: %A2AEx.AgentCard{name: "My Agent", url: "http://localhost:4000"}
}
# Start the server with Bandit (pure-Elixir HTTP server)
Bandit.start_link(plug: {A2AEx.Server, handler: handler}, port: 4000)# As a standalone client (default receive_timeout: 120s for LLM-backed agents)
client = A2AEx.Client.new("http://remote-host:4000")
{:ok, card} = A2AEx.Client.get_agent_card(client)
params = %{"message" => %{"role" => "user", "parts" => [%{"kind" => "text", "text" => "Hello!"}]}}
{:ok, task} = A2AEx.Client.send_message(client, params)
# Or stream events
{:ok, stream} = A2AEx.Client.stream_message(client, params)
Enum.each(stream, &IO.inspect/1)# Wrap as an ADK agent for use in Sequential/Parallel/etc.
remote = A2AEx.RemoteAgent.new(%A2AEx.RemoteAgent.Config{
name: "remote-helper",
url: "http://remote-host:4000",
description: "Remote A2A agent"
})
# Use as sub-agent in a SequentialAgent, ParallelAgent, or directly via Runnerdefmodule MyExecutor do
@behaviour A2AEx.AgentExecutor
@impl true
def execute(ctx, task_id) do
A2AEx.EventQueue.enqueue(task_id,
A2AEx.TaskStatusUpdateEvent.new(task_id, ctx.context_id, :working))
reply = A2AEx.Message.new(:agent, [%A2AEx.TextPart{text: "Hello from my agent!"}])
final = A2AEx.TaskStatusUpdateEvent.new(task_id, ctx.context_id, :completed, reply)
A2AEx.EventQueue.enqueue(task_id, %{final | final: true})
:ok
end
@impl true
def cancel(_ctx, _task_id), do: :ok
endServer: Client:
A2AEx.Server (Plug) A2AEx.Client (Req HTTP)
-> JSONRPC (parse/encode) -> Client.SSE (SSE parser)
-> RequestHandler -> RemoteAgent (ADK agent wrapper)
-> AgentExecutor -> Converter (A2A -> ADK)
-> {ADKExecutor, config}
-> Converter (ADK <-> A2A)
-> TaskStore, EventQueue
-> PushConfigStore, PushSender
All 6 phases complete — 231 tests, credo clean, dialyzer clean.
| Phase | Status | Tests |
|---|---|---|
| 1. Core Types + JSON-RPC | Done | 83 |
| 2. TaskStore + EventQueue + AgentExecutor | Done | 31 |
| 3. RequestHandler + Server | Done | 44 |
| 4. ADK Integration (Converter + ADKExecutor) | Done | 33 |
| 5. Client + RemoteAgent | Done | 26 |
| 6. Integration Testing | Done | 14 |
mix deps.get # Fetch dependencies
mix test # Run tests (231 passing)
mix credo # Static analysis (0 issues)
mix dialyzer # Type checking (0 errors)- adk_ex — Agent Development Kit for Elixir
- Plug — HTTP server interface
- Jason — JSON encoding/decoding
- Req — HTTP client (for push delivery, A2A client, and RemoteAgent)
- Bandit — HTTP server (test-only, for client/RemoteAgent integration tests)
MIT