Note: This example is available in the source repository and is not included in the Hex package. Clone the repo to run it locally.
A complete working example demonstrating how to use the Claude Agent SDK in Mix tasks with streaming responses.
What This Example Shows
- How to create Mix tasks that use the Claude Agent SDK
- Real-time streaming of Claude's responses (typewriter effect)
- Multi-turn interactive conversations
- Simple query-response patterns for scripting
- Command-line argument parsing with options
Prerequisites
- Elixir 1.15+ installed
- Claude Code CLI installed and authenticated:
# Install Claude Code CLI npm install -g @anthropic-ai/claude-code # Authenticate (opens browser) claude
Quick Start
# Navigate to this example
cd examples/mix_task_chat
# Install dependencies
mix deps.get
# Run a streaming chat
mix chat "What is the capital of France?"
# Start an interactive session
mix chat --interactive
The Two Mix Tasks
mix chat - Streaming Responses
This task streams Claude's response in real-time, character by character:
# Basic usage - watch the response appear in real-time
mix chat "Explain how photosynthesis works"
# Interactive multi-turn conversation
mix chat --interactive
# or
mix chat -i
# Use a more capable model
mix chat --model sonnet "Write a haiku about Elixir"
mix chat -m opus "Solve this complex problem..."
# Allow Claude to use tools (read files, run commands, etc.)
mix chat --tools "What files are in this directory?"
mix chat -t "Read the mix.exs and explain the dependencies"
# Combine options
mix chat -i -m sonnet -t
Interactive Mode Commands
When in interactive mode (-i), you can use these commands:
/quitor/exit- End the session/clear- Start a fresh conversation (clears context)
mix ask - Complete Responses
This task waits for the full response before displaying it. Useful for scripting:
# Basic usage
mix ask "What is 2 + 2?"
# Quiet mode - only outputs the response (great for scripts)
mix ask -q "What is the square root of 144?"
# JSON output for programmatic parsing
mix ask -j "List 3 programming languages"
# With tools
mix ask -t "Summarize the README.md file"
Scripting Example
#!/bin/bash
# save_summary.sh
SUMMARY=$(cd /path/to/mix_task_chat && mix ask -q -t "Summarize the mix.exs file")
echo "Project Summary: $SUMMARY" > summary.txt
How It Works
Streaming Architecture
The mix chat task uses ClaudeAgentSDK.Streaming which:
- Starts a persistent Claude CLI subprocess
- Sends prompts via stdin as JSON
- Receives streaming events via stdout
- Parses events and displays text deltas in real-time
┌─────────────┐ stdin (JSON) ┌──────────────┐
│ mix chat │ ───────────────────► │ Claude CLI │
│ (Elixir) │ │ subprocess │
│ │ ◄─────────────────── │ │
└─────────────┘ stdout (SSE/JSON) └──────────────┘
↓
Parse events
↓
Display text_delta
events in real-timeKey Code Walkthrough
Starting a Session
# In lib/mix/tasks/chat.ex
options = %Options{
model: "haiku", # Model to use
max_turns: 10, # Max conversation turns
allowed_tools: [], # Tools Claude can use
include_partial_messages: true # Enable streaming
}
{:ok, session} = Streaming.start_session(options)Streaming a Response
session
|> Streaming.send_message(prompt)
|> Stream.each(fn
# Text chunk received - print immediately
%{type: :text_delta, text: text} ->
IO.write(text)
# Tool being used
%{type: :tool_use_start, name: tool_name} ->
IO.write("\n[Using #{tool_name}...] ")
# Tool finished
%{type: :tool_complete} ->
IO.write("[Done]\n")
# Response complete
%{type: :message_stop} ->
IO.puts("")
# Handle errors
%{type: :error, error: reason} ->
Mix.shell().error("\nError: #{inspect(reason)}")
# Ignore other events
_event ->
:ok
end)
|> Stream.run()Cleanup
# Always close the session when done
try do
stream_response(session, prompt)
after
Streaming.close_session(session)
endCreating Your Own Mix Task
Here's a minimal template for your own streaming Mix task:
defmodule Mix.Tasks.MyTask do
use Mix.Task
alias ClaudeAgentSDK.{Options, Streaming}
@impl Mix.Task
def run(args) do
# Start the SDK application
Application.ensure_all_started(:claude_agent_sdk)
prompt = Enum.join(args, " ")
options = %Options{
model: "haiku",
include_partial_messages: true
}
{:ok, session} = Streaming.start_session(options)
try do
session
|> Streaming.send_message(prompt)
|> Stream.each(fn
%{type: :text_delta, text: text} -> IO.write(text)
%{type: :message_stop} -> IO.puts("")
_ -> :ok
end)
|> Stream.run()
after
Streaming.close_session(session)
end
end
endEvent Types Reference
When streaming, you'll receive these event types:
| Event | Description | Fields |
|---|---|---|
:text_delta | A chunk of text | text, accumulated |
:message_start | Response beginning | model, role |
:message_stop | Response complete | final_text |
:tool_use_start | Tool invocation started | name, id |
:tool_input_delta | Tool input being built | json |
:tool_complete | Tool finished | tool_name, result |
:thinking_delta | Extended thinking (Opus) | thinking |
:error | An error occurred | error |
Options Reference
%Options{
# Model selection
model: "haiku", # "haiku", "sonnet", or "opus"
# Conversation limits
max_turns: 10,
# Tool permissions
allowed_tools: ["Read", "Glob", "Grep", "Bash", "Write", "Edit"],
# Streaming control
include_partial_messages: true, # Enable character-level streaming
# Timeouts
timeout_ms: 300_000, # 5 minutes
# Working directory for tools
cwd: "/path/to/project"
}Troubleshooting
"Claude CLI not found"
Make sure Claude Code CLI is installed and in your PATH:
npm install -g @anthropic-ai/claude-code
which claude # Should show the path
"Authentication required"
Run claude once to authenticate:
claude
# Follow the browser authentication flow
"No response received"
Check that:
- You have API credits available
- The model name is correct (
haiku,sonnet, oropus) - Your network can reach the Anthropic API
Streaming not working
Ensure include_partial_messages: true is set in your options:
options = %Options{
include_partial_messages: true, # Required for streaming!
# ...
}Files in This Example
mix_task_chat/
├── README.md # This file
├── mix.exs # Project configuration
└── lib/
├── mix_task_chat.ex # Application module
└── mix/
└── tasks/
├── chat.ex # Streaming chat task
└── ask.ex # Simple query taskNext Steps
- Streaming Guide - Advanced streaming patterns
- Hooks Guide - Tool interception and customization
- More Examples - Additional example scripts