I’ve been building AI agents in Elixir for a while and kept running into the same frustration, every framework either locks you into one provider, ships fifteen dependencies you don’t need, or treats Elixir as a second-class citizen behind Python.
So I built Alloy. It’s a minimal agent engine — the completion → tool-call loop and nothing else. You give it a prompt, a provider, and some tools, and it runs until the job is done.
{:ok, result} = Alloy.run(
"Read mix.exs and tell me the version",
provider: {Alloy.Provider.Anthropic,
api_key: System.get_env("ANTHROPIC_API_KEY"),
model: "claude-sonnet-4-6"},
tools: [Alloy.Tool.Core.Read]
)
What’s in the box:
- 8 providers — Anthropic, OpenAI, Google Gemini, Ollama, OpenRouter, xAI, DeepSeek, Mistral. Swap in one line, nothing else changes.
- 3 dependencies — jason, req, telemetry. That’s it.
- GenServer agents — Alloy.Agent.Server wraps the loop in a supervised process. Stateful, long-running, message-passing.
- Multi-agent teams — Alloy.Team gives you delegate, broadcast, and handoff between named agents. Each is its own supervised process — one crashes, the others keep
going. - Streaming — token-by-token from any provider, same interface.
- Async dispatch — send_message/2 fires non-blocking and broadcasts the result via PubSub. Designed for Phoenix LiveView.
- Middleware — plug in telemetry, logging, or custom hooks before/after completions and tool calls.
- Context discovery — drop .md files in .alloy/context/ and they’re auto-injected into the system prompt.
The philosophy is close to pi-agent: what you leave out matters more than what you put in. If an agent needs a capability Alloy doesn’t ship, the agent writes the code itself.
The OTP angle is the part I’m most interested in feedback on. Agents as supervised GenServers isn’t a pattern you find in Python-based frameworks — it’s architecturally native to the BEAM, not bolted on. Fault isolation, message passing, and real concurrency (not coroutines) come for free.
{:ok, team} = Alloy.Team.start_link(
agents: [
researcher: [
provider: {Alloy.Provider.Google,
api_key: "...", model: "gemini-2.5-flash"},
system_prompt: "You are a research assistant."
],
coder: [
provider: {Alloy.Provider.Anthropic,
api_key: "...", model: "claude-sonnet-4-6"},
tools: [Alloy.Tool.Core.Read, Alloy.Tool.Core.Write],
system_prompt: "You are a senior developer."
]
]
)
{:ok, research} = Alloy.Team.delegate(team, :researcher,
"Find the latest Elixir 1.18 changes")
{:ok, _} = Alloy.Team.delegate(team, :coder,
"Update our code based on: #{research.text}")
Available now on Hex as {:alloy, “~> 0.4”}.
- Docs: alloy v0.4.2 — Documentation
- Site: https://alloylabs.dev
- GitHub:
Happy to answer questions or take PRs. Particularly interested in feedback on the Team API and whether the provider behaviour interface makes contributing new providers straightforward.
Personally, I think Elixir and OTP is the ideal architecture for agentic systems.






















