Erlang ADK v0.2.1
An Erlang-native Agent Development Kit (ADK) designed to bring the capabilities of Google ADK 2.0 to the Erlang/OTP ecosystem. It leverages Erlang's robust OTP framework (processes, gen_server, and supervisors) to provide a scalable, observable, and highly concurrent multi-agent system.
It features native integration with Google Gemini, allowing your agents to interact with real LLMs.
Features (ADK 2.0 Feature Parity)
- Graph-Based Workflows: Decouple execution from LLMs using
adk_graph. Build workflows with nodes, deterministic edges, and conditional branching. - Multi-Agent Systems: Create teams of specialized agents that collaborate. An agent can act as a tool for another agent using
adk_agent_tool. - Model Context Protocol (MCP): Connect agents to external data sources. Includes both an MCP Client (
adk_mcp_client) and Server (adk_mcp_server). - Human-in-the-Loop (HITL): Built-in primitives to pause workflows mid-run (
adk_long_running_tool), ask for human approval, and resume later. - Session Management: Full state scoping (
user:,app:,temp:) and session persistence via ETS or Mnesia (adk_session_service). - Long-term Semantic Memory: Dedicated
adk_memory_servicefor storing and searching past interactions across sessions. - Callbacks: Hook into the execution lifecycle with
before_agent,after_tool, etc., usingadk_callbacks. - Streaming: Stream Gemini responses directly via
gunintegration for low latency. - Evaluation Framework: Evaluate agent performance against datasets using
adk_eval. - Resiliency: Built-in exponential backoff via
adk_retry. - Agent Orchestrators: Backward-compatible orchestrators (
sequential,parallel,loop). - Agent-to-Agent (A2A): HTTP-based communication between agents.
- Observability: Built-in
telemetryintegration.
Quickstart
Add erlang_adk as a dependency in your rebar.config:
{deps, [
erlang_adk,
{gun, "2.1.0"} %% Required for streaming capabilities
]}.
Ensure the application is started:
application:ensure_all_started(erlang_adk).
Before spawning an agent, make sure your API key is available:
export GEMINI_API_KEY="your_api_key_here"
Basic LLM Agent
Spawn an agent and prompt it:
%% Define agent configuration
Config = #{
name => <<"WeatherAgent">>,
description => <<"An agent that checks the weather.">>,
model => <<"gemini-1.5-flash">>
},
%% Spawn the agent
{ok, Pid} = erlang_adk:spawn_agent(Config, [], [my_weather_tool]),
%% Prompt the agent
{ok, Response} = erlang_adk:prompt(Pid, <<"What is the weather in Tokyo?">>).
io:format("Response: ~s~n", [Response]).
Graph-Based Workflows
Agents and functions can be orchestrated using Graph Workflows (adk_graph), which decouple logic from LLM reasoning. This allows complex control flow (loops, branches, retries).
%% 1. Create a new graph
G0 = adk_graph:new(<<"MyWorkflow">>, []),
%% 2. Define a simple function node
NodeFn = fun(State) -> #{<<"count">> => maps:get(<<"count">>, State, 0) + 1} end,
G1 = adk_graph:add_node(G0, adk_graph_node:function_node(<<"increment">>, NodeFn)),
%% 3. Add a conditional edge
CondFn = fun(State) ->
if maps:get(<<"count">>, State) < 3 -> <<"increment">>;
true -> <<"end_node">>
end
end,
G2 = adk_graph:add_edge(G1, <<"increment">>, cond_target, CondFn),
%% 4. Execute the workflow
{ok, Compiled} = adk_graph:compile(G2),
{ok, FinalState} = adk_graph:execute(Compiled, #{<<"count">> => 0}).
Multi-Agent Systems & Agent-as-Tool
An entire agent can act as a tool for another agent using adk_agent_tool. This allows a master agent to delegate complex tasks to sub-agents.
%% 1. Spawn a sub-agent
SubConfig = #{name => <<"ResearchAgent">>, description => <<"Finds info">>},
{ok, SubPid} = erlang_adk:spawn_agent(SubConfig, [], [search_tool]),
%% 2. Wrap it as a tool
AgentTool = adk_agent_tool:new(SubPid, #{skip_summarization => false}),
%% 3. Spawn a master agent equipped with the sub-agent
MasterConfig = #{name => <<"Master">>, description => <<"Coordinates research">>},
{ok, MasterPid} = erlang_adk:spawn_agent(MasterConfig, [], [AgentTool]),
%% 4. Prompt the master agent
{ok, Result} = erlang_adk:prompt(MasterPid, <<"Research quantum computing.">>).
Human-in-the-Loop (HITL)
Suspend execution and wait for human input using the built-in pause mechanism (adk_long_running_tool).
%% 1. Wrap a dangerous tool to require human approval
SafeTool = adk_long_running_tool:new(format_hard_drive_tool),
%% 2. The agent executes it and pauses
Result = adk_tool:execute(SafeTool, #{<<"disk">> => <<"C:">>}),
%% -> returns {pending, PauseToken} and hibernates the agent
%% 3. The UI or a human reviews the action and resumes the agent
adk_runner:resume(Runner, UserId, SessionId, PauseToken, <<"Approved">>).
Session Management
ADK 2.0 introduces Scoped State prefixes for advanced state management across turns:
- Keys without a prefix are session-scoped (cleared when the session ends).
- Keys starting with
<<"user:">>are user-scoped (shared across all sessions for a user). - Keys starting with
<<"app:">>are app-scoped (shared globally across all users). - Keys starting with
<<"temp:">>are temporary (discarded after a single interaction turn).
StateDelta = #{
<<"user:preferences">> => <<"dark_mode">>,
<<"temp:cache">> => <<"transient_data">>
},
adk_session_service:update_state(<<"MyApp">>, <<"User1">>, <<"SessionX">>, StateDelta).
Long-term Memory
Automatically extract facts from sessions and index them into long-term memory for future retrieval.
%% Add a completed session to long-term memory
adk_memory_service:add_session_to_memory(Session),
%% Later, search memory for context
{ok, Memories} = adk_memory_service:search(Pid, <<"user prefers dark mode">>, #{}, 5).
MCP Integration
MCP Client (Connecting to External Servers)
Connect to any external Model Context Protocol (MCP) server (e.g. running on Node.js or Python) and use its tools in Erlang.
%% Connect via stdio
{ok, Client} = adk_mcp_client:connect(<<"stdio">>, <<"node server.js">>),
%% Wrap all external tools as Erlang ADK tools
McpTools = adk_mcp_client:as_adk_tools(Client),
%% Pass them to your agent
erlang_adk:spawn_agent(Config, [], McpTools).
MCP Server (Exposing Agents as Tools)
Start an MCP server in Erlang to expose your agents and functions to MCP clients like Claude Desktop.
%% Start an SSE MCP server exposing specific tools/agents
{ok, Server} = adk_mcp_server:start(<<"sse">>, [my_erlang_tool, AgentTool]),
Callbacks
Hook into the lifecycle of agents and tools. Callbacks can even override tool results or skip execution entirely.
-module(my_audit_callback).
-behaviour(adk_callbacks).
before_tool(ToolName, Args, _Context) ->
io:format("Executing ~s with ~p~n", [ToolName, Args]),
ok. %% Return {skip, Reason} to prevent execution
Attach callbacks via agent config:
Config = #{callbacks => [my_audit_callback]},
Evaluation Framework
Test your agents against deterministic JSON datasets.
%% Define test configuration
EvalConfig = #{
criteria => #{tool_trajectory_avg_score => 0.8},
match_type => <<"in_order">>
},
%% Run the evaluation suite
{ok, Results} = adk_eval:evaluate(AgentConfig, "test_data.json", EvalConfig),
io:format("Score: ~p~n", [Results]).
Streaming
Stream responses directly from the LLM (useful for chat UIs).
Callback = fun(Chunk) -> io:format("~s", [Chunk]) end,
{ok, StreamPid} = adk_llm_gemini:stream(LLMConfig, History, Tools, Callback).
Retry Configuration
Wrap tools or API calls with robust exponential backoff.
%% Configure max 5 attempts, 1000ms base delay, 2.0x multiplier
RetryConf = adk_retry:new(#{max_attempts => 5, backoff_ms => 1000, backoff_mult => 2.0}),
%% Execute a flaky function
adk_retry:with_retry(fun() -> flaky_network_call() end, RetryConf).
Observability (Telemetry)
The Erlang ADK uses the telemetry library. You can monitor latency and attach handlers to these events:
[erlang_adk, agent, prompt, start][erlang_adk, agent, prompt, stop](includes duration)
telemetry:attach(
<<"my-agent-handler">>,
[erlang_adk, agent, prompt, stop],
fun(EventName, Measurements, Metadata, _Config) ->
io:format("Agent took ~p microseconds~n", [maps:get(duration, Measurements)])
end,
[]
).
Testing
Run the full test suite using rebar3:
rebar3 do clean, compile, ct, dialyzer, ex_doc, edoc