OTP-native AI agent framework inspired by Google ADK, built for the BEAM.
Python's ADK is great, but Elixir's runtime was built for this:
| Challenge | Python ADK | ADK Elixir |
|---|---|---|
| Concurrent agents | asyncio, threading | Lightweight processes — millions of agents per node |
| Crash isolation | try/except per agent | Process boundaries — one agent crash can't take down others |
| Recovery | Manual retry logic | OTP supervisors — automatic restart with backoff strategies |
| Streaming | Async generators | Native Stream + Phoenix PubSub |
| State | Shared dicts, locks | Process isolation — no locks, no mutexes, no race conditions |
| Distribution | Custom networking | Built-in clustering — agents span nodes transparently |
- Getting Started
- Core Concepts
- mix adk.new — Project Generator
- Phoenix Integration
- Supervision & OTP
- Dev Server
- Evaluations
- Oban Integration
- ADK Web Compatibility
- Deployment
- Benchmarks — Elixir vs Python
Add adk to your dependencies in mix.exs:
def deps do
[
{:adk, "~> 0.1.0"}
]
end# Create an agent
agent = ADK.new("assistant",
model: "gemini-flash-latest",
instruction: "You are a helpful assistant."
)
# Chat (blocking — returns text)
ADK.chat(agent, "What is Elixir?")
#=> "Elixir is a dynamic, functional language designed for building scalable..."
# Run (returns events for full control)
events = ADK.run(agent, "Tell me about OTP")By default, ADK uses a mock LLM for testing. Configure a real backend:
# config/config.exs
config :adk, :llm_backend, ADK.LLM.Gemini
config :adk, :gemini_api_key, System.get_env("GEMINI_API_KEY")Supported backends: ADK.LLM.Gemini, ADK.LLM.OpenAI, ADK.LLM.Anthropic.
The core agent — sends messages to an LLM, handles tool calls, returns events:
agent = ADK.new("researcher",
model: "gemini-flash-latest",
instruction: "You research topics thoroughly.",
tools: [&MyTools.search/1, &MyTools.summarize/1]
)Chain agents — output of one feeds into the next:
pipeline = ADK.sequential([
ADK.new("researcher", instruction: "Find relevant information."),
ADK.new("writer", instruction: "Write a clear summary from the research.")
])
ADK.chat(pipeline, "Explain BEAM concurrency")Run agents concurrently or iteratively:
# Run multiple agents at once, merge results
parallel = %ADK.Agent.ParallelAgent{
name: "multi",
agents: [researcher, fact_checker, editor]
}
# Loop until a condition is met
loop = %ADK.Agent.LoopAgent{
name: "refiner",
agent: editor,
max_iterations: 3
}Any function becomes a tool:
def get_weather(%{"city" => city}) do
%{temp: 72, condition: "sunny", city: city}
end
agent = ADK.new("assistant", tools: [&get_weather/1])For richer metadata, use the declarative macro:
defmodule MyTools.Calculator do
use ADK.Tool.Declarative
@tool name: "calculate",
description: "Evaluate a math expression",
parameters: %{
"expression" => %{type: "string", description: "Math expression"}
}
def run(%{"expression" => expr}, _ctx) do
{result, _} = Code.eval_string(expr)
%{result: result}
end
endSessions are GenServers with pluggable storage:
# In-memory (ETS), JSON files, or Ecto (any database)
{:ok, pid} = ADK.Session.start_link(
app_name: "my_app",
user_id: "user1",
session_id: "sess1",
store: {ADK.Session.Store.InMemory, []}
)| Store | Backend | Best for |
|---|---|---|
InMemory |
ETS | Testing, single-node |
JsonFile |
JSON files | Development |
Ecto |
Any database | Production |
Optional Phoenix helpers — no Phoenix dependency required:
# REST API with SSE streaming
plug ADK.Phoenix.Controller
# WebSocket real-time communication
socket "/agent", ADK.Phoenix.Channel
# Drop-in LiveView chat component
live "/chat", ADK.Phoenix.ChatLive📖 See the Phoenix Integration Guide.
Full A2A protocol support for inter-agent communication:
# Expose as A2A server
plug ADK.A2A.Server, agent: my_agent, runner: runner
# Call remote agents
{:ok, result} = ADK.A2A.Client.send_task("http://remote:4000", "Research OTP")
# Use remote agents as tools
researcher = ADK.A2A.RemoteAgentTool.new(name: "researcher", url: "http://remote:4000")Extend agent behavior with plugins and safety policies:
# Plugin: automatic retry on LLM reflection
ADK.Plugin.Registry.register(ADK.Plugin.ReflectRetry)
# Policy: control what agents can do
config :adk, :policy, MyApp.SafetyPolicyBuilt-in :telemetry events + optional OpenTelemetry:
# All agent/tool/LLM calls emit telemetry events
:telemetry.attach("my-handler", [:adk, :agent, :run, :stop], &MyHandler.handle/4, nil)# Generate a new ADK project
mix adk.new my_agent
# Generate Ecto migrations for session persistence
mix adk.gen.migrationADK.Runner
├── ADK.Session (GenServer per session)
├── ADK.Context (immutable invocation context)
└── ADK.Agent (behaviour)
├── LlmAgent (LLM ↔ tool loop)
├── SequentialAgent (pipeline)
├── ParallelAgent (concurrent)
└── LoopAgent (iterative)
Apache-2.0 — see LICENSE.