Skip to content

JohnSmall/a2a-elixir-sdk

Repository files navigation

A2AEx

Hex.pm Documentation

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.

Features

  • 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 TaskStore and PushConfigStore with in-memory implementations

Quick Start

# mix.exs
def deps do
  [
    {:a2a_elixir_sdk, "~> 1.0"}
  ]
end

LLM Usage Rules

This 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.

Expose an ADK Agent as an A2A Endpoint

# 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)

Consume a Remote A2A Agent

# 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)

Use a Remote Agent in ADK Orchestration

# 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 Runner

Custom Executor (without ADK)

defmodule 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
end

Architecture

Server:                                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

Status

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

Development

mix deps.get       # Fetch dependencies
mix test           # Run tests (231 passing)
mix credo          # Static analysis (0 issues)
mix dialyzer       # Type checking (0 errors)

Dependencies

  • 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)

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages