# Claude Agent SDK for Elixir
The Elixir SDK for building AI agents with Claude Code.
- **โ
Full Feature Parity**: 100% feature parity with the official Python and TypeScript SDKs
- **๐ญ OTP Supervision**: Fault-tolerant sessions with automatic restarts and concurrent actors
- **๐ Elixir Streams**: Native streaming with backpressure, composable pipelines, and LiveView support
- **๐ In-Process Hooks**: Control tool permissions and lifecycle events inside your BEAM application
- **๐งช Built-in Test Stubs**: Mock Claude responses for fast, deterministic tests without API calls
[](https://hex.pm/packages/claude_code)
[](https://hexdocs.pm/claude_code)
[](https://github.com/guess/claude_code/blob/main/LICENSE)
[](https://elixir-lang.org)
<div align="center">
<img src="https://github.com/guess/claude_code/raw/main/docs/claudecode.png" alt="ClaudeCode" width="200">
</div>
## ๐ฏ 30-Second Demo
```elixir
# Simple one-off query (uses ANTHROPIC_API_KEY from env)
{:ok, result} = ClaudeCode.query("Explain Elixir GenServers in one sentence")
IO.puts(result.result)
# For multi-turn conversations, use sessions with streaming
{:ok, session} = ClaudeCode.start_link()
session
|> ClaudeCode.stream("My favorite language is Elixir")
|> Stream.run()
# Claude remembers context across queries
session
|> ClaudeCode.stream("What's my favorite language?")
|> ClaudeCode.Stream.text_content()
|> Enum.each(&IO.write/1)
# => "Your favorite language is Elixir!"
ClaudeCode.stop(session)
```
## ๐ฆ Installation
**Step 1:** Add to your `mix.exs`
```elixir
def deps do
[{:claude_code, "~> 0.19"}]
end
```
**Step 2:** Install dependencies
```bash
mix deps.get
# Install bundled CLI binary (optional)
mix claude_code.install
```
The Claude CLI is automatically installed to `priv/bin/` on first use. To pre-install (e.g., for CI or releases), run `mix claude_code.install`.
**Step 3:** Authenticate (choose one)
```bash
# Option A: Use your Claude subscription (no API key needed)
$(mix claude_code.path) /login # Authenticate using the SDK's bundled CLI
# Option B: Use an API key
# In runtime.exs:
config :claude_code, api_key: System.get_env("ANTHROPIC_API_KEY")
```
๐ **Ready to go!** Try the quick demo above.
## โก Quick Examples
```elixir
# Basic usage (uses ANTHROPIC_API_KEY from environment)
{:ok, session} = ClaudeCode.start_link()
# Stream response messages
session
|> ClaudeCode.stream("Hello, Claude!")
|> Enum.each(&IO.inspect/1)
# File operations
session
|> ClaudeCode.stream("Review my mix.exs file", allowed_tools: ["View", "Edit"])
|> ClaudeCode.Stream.final_text()
# Custom agents
alias ClaudeCode.Agent
agents = [
Agent.new(
name: "code-reviewer",
description: "Expert code reviewer. Use proactively after code changes.",
prompt: "You are a senior code reviewer. Focus on quality and best practices.",
tools: ["Read", "Grep", "Glob"],
model: "sonnet"
)
]
{:ok, session} = ClaudeCode.start_link(agents: agents)
# Production with supervision
{:ok, _} = ClaudeCode.Supervisor.start_link(name: :assistant)
:assistant
|> ClaudeCode.stream("Help with this task")
|> ClaudeCode.Stream.final_text()
```
## ๐ญ Production Usage
### Supervised Sessions
```elixir
# In your application.ex
def start(_type, _args) do
children = [
MyAppWeb.Endpoint,
{ClaudeCode.Supervisor, [
[name: :code_reviewer, system_prompt: "You review code"],
[name: :general_assistant]
]}
]
Supervisor.start_link(children, strategy: :one_for_one)
end
# Use from anywhere in your app
review =
:code_reviewer
|> ClaudeCode.stream("Review this code")
|> ClaudeCode.Stream.final_text()
```
**Benefits:**
- โ
**Fault tolerance** - Sessions restart automatically on crashes
- โ
**Zero downtime** - Hot code reloading preserves session state
- โ
**Global access** - Named sessions work from anywhere in your app
- โ
**Distributed support** - Sessions work across Elixir clusters
### Phoenix Integration
```elixir
# Simple controller usage
def ask(conn, %{"prompt" => prompt}) do
response =
:assistant
|> ClaudeCode.stream(prompt)
|> ClaudeCode.Stream.final_text()
json(conn, %{response: response})
end
```
For LiveView with real-time streaming, use `stream/3` with a Task:
```elixir
# LiveView with streaming responses
def handle_event("send", %{"message" => msg}, socket) do
parent = self()
Task.start(fn ->
:assistant
|> ClaudeCode.stream(msg, include_partial_messages: true)
|> ClaudeCode.Stream.text_deltas()
|> Enum.each(&send(parent, {:chunk, &1}))
send(parent, :complete)
end)
{:noreply, assign(socket, streaming: true)}
end
def handle_info({:chunk, chunk}, socket) do
{:noreply, assign(socket, response: socket.assigns.response <> chunk)}
end
def handle_info(:complete, socket) do
{:noreply, assign(socket, streaming: false)}
end
```
๐ **[Full Phoenix Integration Guide โ](docs/integration/phoenix.md)**
### Error Handling
```elixir
# Stream errors are thrown and can be caught
try do
session
|> ClaudeCode.stream("Hello")
|> Enum.to_list()
catch
{:stream_timeout, _ref} -> IO.puts("Request timed out")
{:stream_error, {:provisioning_failed, reason}} -> IO.puts("CLI error: #{inspect(reason)}")
{:stream_error, reason} -> IO.puts("Stream error: #{inspect(reason)}")
end
# Or use one-off query with result pattern matching
case ClaudeCode.query("Hello") do
{:ok, result} -> IO.puts(result.result)
{:error, %{is_error: true, result: msg}} -> IO.puts("Error: #{msg}")
{:error, reason} -> IO.puts("Error: #{inspect(reason)}")
end
```
### Multi-turn Conversations
Sessions automatically maintain context across queries:
```elixir
{:ok, session} = ClaudeCode.start_link()
# First turn
session
|> ClaudeCode.stream("What's the capital of France?")
|> ClaudeCode.Stream.text_content()
|> Enum.each(&IO.write/1) # Paris
# Second turn - context is preserved automatically
session
|> ClaudeCode.stream("What about Germany?")
|> ClaudeCode.Stream.text_content()
|> Enum.each(&IO.write/1) # Berlin
# Get session ID for later resume
session_id = ClaudeCode.get_session_id(session)
ClaudeCode.stop(session)
# Resume later with the same context
{:ok, new_session} = ClaudeCode.start_link(resume: session_id)
new_session
|> ClaudeCode.stream("What was the first capital I asked about?")
|> ClaudeCode.Stream.text_content()
|> Enum.each(&IO.write/1) # Paris
```
**Benefits:**
- โ
**Persistent connection** - Single CLI process handles all queries
- โ
**Auto-connect/disconnect** - No manual lifecycle management
- โ
**Resume support** - Continue previous conversations with `resume: session_id`
- โ
**Lower latency** - No startup overhead between turns
### Tool Callbacks
Monitor tool executions for logging, auditing, or analytics:
```elixir
callback = fn event ->
Logger.info("Tool #{event.name} executed",
input: event.input,
result: event.result,
is_error: event.is_error
)
end
{:ok, session} = ClaudeCode.start_link(tool_callback: callback)
```
### MCP Integration (Optional)
Expose Elixir tools to Claude using Hermes MCP:
```elixir
# Add {:hermes_mcp, "~> 0.14"} to your deps
# Start MCP server with your tools
{:ok, config_path} = ClaudeCode.MCP.Server.start_link(
server: MyApp.MCPServer,
port: 9001
)
# Connect ClaudeCode to MCP server
{:ok, session} = ClaudeCode.start_link(mcp_config: config_path)
# Claude can now use your custom tools!
```
## ๐งช Testing
ClaudeCode includes a test adapter for fast, deterministic tests without API calls:
```elixir
test "handles greeting" do
ClaudeCode.Test.stub(ClaudeCode, fn _query, _opts ->
[ClaudeCode.Test.text("Hello! How can I help?")]
end)
{:ok, session} = ClaudeCode.start_link()
result = session |> ClaudeCode.stream("Hi") |> ClaudeCode.Stream.final_text()
assert result == "Hello! How can I help?"
end
```
Includes message helpers (`text`, `tool_use`, `tool_result`, `thinking`), dynamic stubs, and concurrent test support.
๐ **[Full Testing Guide โ](docs/reference/testing.md)**
## ๐ Documentation
- ๐ **[Documentation Hub](docs/README.md)** - All guides and references
- ๐ญ **[Production Guide](docs/guides/hosting.md)** - Fault-tolerant production deployments
- ๐ป **[Examples](docs/reference/examples.md)** - Real-world usage patterns and code samples
- ๐ **[API Reference](https://hexdocs.pm/claude_code)** - Complete API documentation
- ๐ง **[Troubleshooting](docs/reference/troubleshooting.md)** - Common issues and solutions
## ๐ค Contributing
We โค๏ธ contributions! Whether it's:
- ๐ **Bug reports** - Found an issue? Let us know!
- ๐ก **Feature requests** - Have an idea? We'd love to hear it!
- ๐ **Documentation** - Help make our docs even better
- ๐ง **Code contributions** - PRs welcome!
See our [Contributing Guide](https://github.com/guess/claude_code/blob/main/CONTRIBUTING.md) to get started.
## ๐ ๏ธ Development
```bash
# Clone and setup
git clone https://github.com/guess/claude_code.git
cd claude_code
mix deps.get
# Run tests and quality checks
mix test
mix quality # format, credo, dialyzer
```
## ๐ License
MIT License - see [LICENSE](https://github.com/guess/claude_code/blob/main/LICENSE) for details.
---
**Built for Elixir developers on top of the [Claude Code CLI](https://github.com/anthropics/claude-code).**