Claude Agent SDK for Elixir
An Elixir SDK aiming for high parity with the official claude-agent-sdk-python while treating the Claude Code CLI as the authoritative runtime contract. Build AI-powered applications with Claude using a production-ready interface for the Claude Code CLI, featuring streaming responses, lifecycle hooks, permission controls, and in-process tool execution via MCP.
Note: This SDK does not bundle the Claude Code CLI. You must install it separately (see Prerequisites).
Documentation Menu
README.md- installation, quick start, and ownership boundariesguides/getting-started.md- first query and streaming flowsguides/configuration.md- runtime config and defaultsguides/model-configuration.md- Claude model selection and effort controlsguides/agents.md- agent and orchestration patternsguides/testing.md- local validation workflow
What You Can Build
- AI coding assistants with real-time streaming output
- Automated code review pipelines with custom permission policies
- Multi-agent workflows with specialized personas
- Tool-augmented applications using the Model Context Protocol (MCP)
- Interactive chat interfaces with typewriter-style output
Runtime Architecture
-
Common CLI query/streaming flows run on the shared
cli_subprocess_coreruntime:CliSubprocessCore.Session,ExternalRuntimeTransport.Transport, andCliSubprocessCore.Command. ClaudeAgentSDK.Clientremains SDK-local only for the advanced Claude control family: hooks, permission callbacks, SDK MCP routing, and control request/response state.ClaudeAgentSDK.Clientnow runs its control lane throughCliSubprocessCore.ProtocolSession; custom transport injection has been removed. UseOptions.execution_surfacefor SSH/local routing.
ASM Boundary
If you enter Claude through agent_session_manager, the normalized ASM kernel
still stops at provider selection, lane selection, event projection, and
session/run orchestration.
The optional ASM seam for Claude lives under
ASM.Extensions.ProviderSDK.Claude. That extension may:
-
derive
ClaudeAgentSDK.Optionsfrom ASM-style config -
start
ClaudeAgentSDK.Clientfrom ASM session defaults or ASM-style config
If claude_agent_sdk is present in the dependency graph, ASM activates that
namespace automatically in ASM.Extensions.ProviderSDK.available_extensions/0
and ASM.Extensions.ProviderSDK.capability_report/0. Client apps do not need
to register it manually.
ASM-owned fields such as cwd, permission_mode, model, max_turns, and
the transport timeout still stay in ASM config. The extension's native override
bag is for Claude-native fields only.
It does not move the control family into ASM. Once you cross that seam, the real control APIs remain here:
ClaudeAgentSDK.Client.set_permission_mode/2ClaudeAgentSDK.Client.set_model/2ClaudeAgentSDK.Client.interrupt/1ClaudeAgentSDK.Client.rewind_files/2ClaudeAgentSDK.ControlProtocol.Protocol
Packaging Boundary
Phase 4 finalizes the Claude release boundary:
cli_subprocess_coreremains the required lower dependency for the common Claude query and streaming laneclaude_agent_sdkremains the source of truth for Claude-native control features such as hooks, permission callbacks, SDK MCP routing, and control protocol state-
ASM may bridge into that richer family only through
ASM.Extensions.ProviderSDK.Claude; that seam does not move the control family into ASM or the shared core -
the operator publication order remains
cli_subprocess_core, thenclaude_agent_sdk, thenagent_session_manager
Schema Boundary
Zoi is now the canonical boundary-schema layer for new dynamic boundary work
inside claude_agent_sdk.
cli_subprocess_coreowns the shared common-lane event and payload shapes.claude_agent_sdkowns Claude-local control-protocol envelopes, permission update payloads, and Claude-native message-frame adaptation.-
public structs such as
%ClaudeAgentSDK.Message{},%ClaudeAgentSDK.Permission.Update{}, and%ClaudeAgentSDK.Permission.RuleValue{}remain the ergonomic caller-facing layer. -
forward-compatible wire surfaces use
Zoi.map(..., unrecognized_keys: :preserve)plus projection instead of directZoi.struct/3. - closed local payloads still use schema validation, but unknown-field preservation is only enabled where the wire surface can legitimately expand.
Centralized Model Selection
claude_agent_sdk no longer owns active model defaulting or fallback policy.
That authority lives in cli_subprocess_core.
Authoritative core surface:
CliSubprocessCore.ModelRegistry.resolve/3CliSubprocessCore.ModelRegistry.validate/2CliSubprocessCore.ModelRegistry.default_model/2CliSubprocessCore.ModelRegistry.build_arg_payload/3CliSubprocessCore.ModelInput.normalize/3
Claude-side behavior:
ClaudeAgentSDK.Options.new/1routes mixed raw-versus-payload input throughCliSubprocessCore.ModelInput.normalize/3, then ensuresmodel_payloadandmodelare aligned from the shared core contractClaudeAgentSDK.Model.default_model/0reads the shared core default instead of carrying a local fallback- effort gating runs against the resolved model, not mutable app config
- provider CLI rendering only formats arguments from resolved values
-
repo-local env defaults remain fallback inputs only when
model_payloadwas not supplied explicitly
Do not treat repo-local config snapshots as authoritative model policy. The shared core registry is the source of truth.
Claude Ollama Backend
claude_agent_sdk now supports an explicit Claude :ollama backend through
the same core-owned payload path.
Example:
options =
ClaudeAgentSDK.Options.new(
provider_backend: :ollama,
anthropic_base_url: "http://localhost:11434",
external_model_overrides: %{"haiku" => "llama3.2"},
model: "haiku"
)
The SDK still runs the normal claude binary. The core payload resolves the
actual transport model and injects the Anthropic-compatible Ollama env.
Installation
Add to your mix.exs:
def deps do
[
{:claude_agent_sdk, "~> 0.17.0"}
]
endThen fetch dependencies:
mix deps.getPrerequisites
Install the Claude Code CLI (requires Node.js):
npm install -g @anthropic-ai/claude-codeVerify installation:
claude --versionCLI Compatibility
-
Minimum supported Claude CLI version:
2.1.0 -
Recommended Claude CLI version:
2.1.84 -
Compatibility policy: this SDK follows the Python SDK where practical, but the Claude CLI wire protocol is authoritative. CLI-native frames such as
:rate_limit_eventare surfaced here even if the current Python SDK skips unknown message types for forward compatibility.
Quick Start
1. Authenticate
Choose one method:
# Option A: Environment variable (recommended for CI/CD)
export ANTHROPIC_API_KEY="sk-ant-api03-..."
# Option B: OAuth token
export CLAUDE_AGENT_OAUTH_TOKEN="sk-ant-oat01-..."
# Option C: Interactive login
claude login2. Run Your First Query
alias ClaudeAgentSDK.{ContentExtractor, Options}
# Simple query with streaming collection
ClaudeAgentSDK.query("Write a function that calculates factorial in Elixir")
|> Enum.each(fn msg ->
case msg.type do
:assistant -> IO.puts(ContentExtractor.extract_text(msg) || "")
:result -> IO.puts("Done! Cost: $#{msg.data.total_cost_usd}")
_ -> :ok
end
end)3. Real-Time Streaming
alias ClaudeAgentSDK.Streaming
{:ok, session} = Streaming.start_session()
Streaming.send_message(session, "Explain GenServers in one paragraph")
|> Stream.each(fn
%{type: :text_delta, text: chunk} -> IO.write(chunk)
%{type: :message_stop} -> IO.puts("")
_ -> :ok
end)
|> Stream.run()
Streaming.close_session(session)
If session initialization or message send fails, the stream now emits an immediate
%{type: :error, error: reason} event instead of waiting for the 5-minute stream timeout.
Authentication
The SDK supports three authentication methods, checked in this order:
| Method | Environment Variable | Best For |
|---|---|---|
| OAuth Token | CLAUDE_AGENT_OAUTH_TOKEN | Production / CI |
| API Key | ANTHROPIC_API_KEY | Development |
| CLI Login |
(uses claude login session) | Local development |
Cloud Providers
AWS Bedrock:
export CLAUDE_AGENT_USE_BEDROCK=1
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_REGION=us-west-2Google Vertex AI:
export CLAUDE_AGENT_USE_VERTEX=1
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json
export GOOGLE_CLOUD_PROJECT=your-project-idToken Setup (Local Development)
For persistent authentication without re-login:
mix claude.setup_tokenAuthManager keeps running if token storage save/clear fails and returns {:error, reason}.
Handle clear_auth/0 accordingly in your app code:
case ClaudeAgentSDK.AuthManager.clear_auth() do
:ok -> :ok
{:error, reason} -> IO.puts("Failed to clear auth: #{inspect(reason)}")
endCheck authentication status:
alias ClaudeAgentSDK.AuthChecker
diagnosis = AuthChecker.diagnose()
# => %{authenticated: true, auth_method: "Anthropic API", ...}Core Concepts
Choosing the Right API
| API | Use Case | When to Use |
|---|---|---|
query/2 | Simple queries | Batch processing, scripts |
Streaming | Typewriter UX | Chat interfaces, real-time output |
Client | Full control | Multi-turn agents, tools, hooks |
Query API
The simplest way to interact with Claude:
# Basic query
messages = ClaudeAgentSDK.query("What is recursion?") |> Enum.to_list()
# With options
opts = %ClaudeAgentSDK.Options{
model: "sonnet",
max_turns: 5,
output_format: :stream_json
}
messages = ClaudeAgentSDK.query("Explain OTP", opts) |> Enum.to_list()
# Streamed input prompts (unidirectional)
prompts = [
%{"type" => "user", "message" => %{"role" => "user", "content" => "Hello"}},
%{"type" => "user", "message" => %{"role" => "user", "content" => "How are you?"}}
]
ClaudeAgentSDK.query(prompts, opts) |> Enum.to_list()
# Execution-surface routing
opts = %ClaudeAgentSDK.Options{
execution_surface: [
surface_kind: :ssh_exec,
transport_options: [
destination: "claude.example",
ssh_user: "sdk",
port: 22
]
]
}
ClaudeAgentSDK.query("Hello", opts) |> Enum.to_list()
# Continue a conversation
ClaudeAgentSDK.continue("Can you give an example?") |> Enum.to_list()
# Resume a specific session
ClaudeAgentSDK.resume("session-id", "What about supervision trees?") |> Enum.to_list()Streaming API
For real-time, character-by-character output:
alias ClaudeAgentSDK.{Options, Streaming}
{:ok, session} = Streaming.start_session(%Options{model: "haiku"})
# Send messages and stream responses
Streaming.send_message(session, "Write a haiku about Elixir")
|> Enum.each(fn
%{type: :text_delta, text: t} -> IO.write(t)
%{type: :tool_use_start, name: n} -> IO.puts("\nUsing tool: #{n}")
%{type: :message_stop} -> IO.puts("\n---")
_ -> :ok
end)
# Multi-turn conversation (context preserved)
Streaming.send_message(session, "Now write one about Phoenix")
|> Enum.to_list()
Streaming.close_session(session)Subagent Streaming: When Claude spawns subagents via the Agent tool, events include a parent_tool_use_id field to identify the source. Main agent events have nil, subagent events have the Agent tool call ID. Streaming events also include uuid, session_id, and raw_event metadata for parity with the Python SDK. Stream event wrappers require uuid and session_id (missing keys raise). See the Streaming Guide for details.
Hooks System
Intercept and control agent behavior at key lifecycle points:
alias ClaudeAgentSDK.{Client, Options}
alias ClaudeAgentSDK.Hooks.{Matcher, Output}
# Block dangerous commands
check_bash = fn input, _id, _ctx ->
case input do
%{"tool_name" => "Bash", "tool_input" => %{"command" => cmd}} ->
if String.contains?(cmd, "rm -rf") do
Output.deny("Dangerous command blocked")
else
Output.allow()
end
_ -> %{}
end
end
opts = %Options{
hooks: %{
pre_tool_use: [Matcher.new("Bash", [check_bash])]
}
}
{:ok, client} = Client.start_link(opts)Available Hook Events (all 12 Python SDK events supported):
pre_tool_use/post_tool_use/post_tool_use_failure- Tool execution lifecycleuser_prompt_submit- Before sending user messagesstop/subagent_stop/subagent_start- Agent lifecyclenotification- CLI notificationspermission_request- Permission dialog interceptionsession_start/session_end- Session lifecyclepre_compact- Before context compaction
See the Hooks Guide for comprehensive documentation.
Supervision
Hook and permission callbacks run in async tasks. For production, add the SDK task supervisor so callback processes are supervised:
children = [
ClaudeAgentSDK.TaskSupervisor,
{ClaudeAgentSDK.Client, options}
]If you use a custom supervisor name, configure the SDK to match:
children = [
{ClaudeAgentSDK.TaskSupervisor, name: MyApp.ClaudeTaskSupervisor}
]
config :claude_agent_sdk, task_supervisor: MyApp.ClaudeTaskSupervisor
If an explicitly configured supervisor is missing at runtime, the SDK logs a warning and
falls back to Task.start/1. With default settings, missing
ClaudeAgentSDK.TaskSupervisor falls back silently for backward compatibility.
For stricter behavior in dev/test:
config :claude_agent_sdk, task_supervisor_strict: true
In strict mode, ClaudeAgentSDK.TaskSupervisor.start_child/2 returns
{:error, {:task_supervisor_unavailable, supervisor}} instead of spawning
an unsupervised fallback task.
Permission System
Fine-grained control over tool execution:
alias ClaudeAgentSDK.{Options, Permission.Result}
permission_callback = fn ctx ->
case ctx.tool_name do
"Write" ->
# Redirect system file writes to safe location
if String.starts_with?(ctx.tool_input["file_path"], "/etc/") do
safe_path = "/tmp/sandbox/" <> Path.basename(ctx.tool_input["file_path"])
Result.allow(updated_input: %{ctx.tool_input | "file_path" => safe_path})
else
Result.allow()
end
_ ->
Result.allow()
end
end
opts = %Options{
can_use_tool: permission_callback,
permission_mode: :default # :default | :accept_edits | :plan | :bypass_permissions | :auto | :dont_ask
}
Note: can_use_tool is mutually exclusive with permission_prompt_tool. The SDK routes can_use_tool through the control client (including string prompts), auto-enables include_partial_messages, and sets permission_prompt_tool to \"stdio\" internally so the CLI can emit permission callbacks. Use :default, :plan, or the CLI's :auto mode for built-in tool permissions. Hook-based fallback only applies when the CLI does not emit can_use_tool, and that fallback ignores updated_permissions. :delegate is no longer forwarded because current Claude CLI builds reject it.
CLI transcript history now matches the official SDK surface:
# Claude CLI transcript history
sessions = ClaudeAgentSDK.list_sessions(directory: "/path/to/project")
messages = ClaudeAgentSDK.get_session_messages("550e8400-e29b-41d4-a716-446655440000",
directory: "/path/to/project"
)
# SDK-managed SessionStore history
{:ok, saved_sessions} = ClaudeAgentSDK.list_saved_sessions(storage_dir: "/custom/path")Stream a single client response until the final result:
Client.receive_response_stream(client)
|> Enum.to_list()MCP Tools (In-Process)
Define custom tools that Claude can call directly in your application:
defmodule MyTools do
use ClaudeAgentSDK.Tool
deftool :calculate, "Perform a calculation", %{
type: "object",
properties: %{
expression: %{type: "string", description: "Math expression to evaluate"}
},
required: ["expression"]
} do
def execute(%{"expression" => expr}) do
# Your logic here
result = eval_expression(expr)
{:ok, %{"content" => [%{"type" => "text", "text" => "Result: #{result}"}]}}
end
end
end
# Create an MCP server with your tools
server = ClaudeAgentSDK.create_sdk_mcp_server(
name: "calculator",
version: "1.0.0",
tools: [MyTools.Calculate]
)
# Without :supervisor, the SDK keeps the registry under its internal
# SDK MCP supervisor so the server survives creator process exits.
# Optional: start tool registry under your DynamicSupervisor
{:ok, sup} = DynamicSupervisor.start_link(strategy: :one_for_one)
server = ClaudeAgentSDK.create_sdk_mcp_server(
name: "calculator",
version: "1.0.0",
tools: [MyTools.Calculate],
supervisor: sup
)
opts = %ClaudeAgentSDK.Options{
mcp_servers: %{"calc" => server},
allowed_tools: ["mcp__calc__calculate"]
}
Note: MCP server routing only supports initialize, tools/list, tools/call, and notifications/initialized. Calls to resources/list or prompts/list return JSON-RPC method-not-found errors to match the Python SDK.
If version is omitted, it defaults to "1.0.0".
Configuration Options
Key options for ClaudeAgentSDK.Options:
| Option | Type | Description |
|---|---|---|
model | string | "sonnet", "opus", "haiku" |
effort | atom | :low, :medium, :high, :max — controls reasoning effort; :max is Opus-only; invalid values raise ArgumentError (not supported for Haiku) |
thinking | map | %{type: :adaptive}, %{type: :enabled, budget_tokens: N}, %{type: :disabled} |
max_turns | integer | Maximum conversation turns |
system_prompt | string | Custom system instructions |
output_format | atom/map | :text, :json, :stream_json, or JSON schema (SDK enforces stream-json for transport; JSON schema still passed) |
allowed_tools | list | Tools Claude can use |
permission_mode | atom | :default, :accept_edits, :plan, :bypass_permissions, :auto, :dont_ask |
hooks | map | Lifecycle hook callbacks |
mcp_servers | map or string |
MCP server configurations (or JSON/path alias for mcp_config) |
cwd | string | Working directory for file operations |
timeout_ms | integer | Command timeout (default: 75 minutes) |
transport_error_mode | atom | :result (default) or :raise for strict transport/decode failures |
max_buffer_size | integer |
Maximum JSON buffer size (default: 1MB, overflow yields CLIJSONDecodeError) |
CLI path override: set path_to_claude_code_executable or executable in Options (Python cli_path equivalent).
On SSH-backed execution_surface values, Claude is resolved on the remote host.
If the binary is installed outside the remote non-login PATH, pass an
explicit executable or path_to_claude_code_executable for that target.
Runtime Application Config
All tunable constants (timeouts, buffer sizes, auth paths, CLI flags, env var
names, concurrency limits) are centralized in Config.* sub-modules and can
be overridden per-environment:
# config/config.exs
config :claude_agent_sdk, ClaudeAgentSDK.Config.Timeouts,
query_total_ms: 5_400_000, # total query timeout (default: 75 min)
tool_execution_ms: 60_000 # per-tool timeout (default: 30 s)
config :claude_agent_sdk, ClaudeAgentSDK.Config.Buffers,
max_stdout_buffer_bytes: 2_097_152 # stdout buffer (default: 1 MB)
config :claude_agent_sdk, ClaudeAgentSDK.Config.Orchestration,
max_concurrent: 10, # parallel query limit (default: 5)
max_retries: 5 # retry attempts (default: 3)
# Legacy flat keys still work:
config :claude_agent_sdk,
cli_stream_module: ClaudeAgentSDK.Query.CLIStream,
task_supervisor_strict: falseSee the Configuration Internals guide for the complete reference of every tunable, its default, and override examples.
config :claude_agent_sdk, :process_module is still read as a fallback for query streaming,
but it is deprecated and logs a warning once per legacy module.
SessionStore now hydrates on-disk cache in a handle_continue/2 step. Startup is faster,
but list/search can be briefly incomplete immediately after boot while warmup finishes.
ExternalRuntimeTransport.Transport and Streaming.Session support startup_mode: :lazy
to defer subprocess startup to handle_continue/2. Deterministic startup
validation still happens before start_link returns, so missing cwd/command
style failures surface immediately. Once preflight passes, lazy mode can still
surface subprocess launch failures as a process exit after init.
Query-side transport errors normalize equivalent reasons to stable atoms where possible:
{:command_not_found, "claude"} is treated as :cli_not_found.
SDK Logging
The SDK uses its own log level filter (default: :warning) to keep output quiet in dev. Configure via application env:
config :claude_agent_sdk, log_level: :warning # :debug | :info | :warning | :error | :offOption Presets
alias ClaudeAgentSDK.OptionBuilder
# Environment-based presets
OptionBuilder.build_development_options() # Permissive, verbose
OptionBuilder.build_production_options() # Restrictive, safe
OptionBuilder.for_environment() # Auto-detect from Mix.env()
# Use-case presets
OptionBuilder.build_analysis_options() # Read-only code analysis
OptionBuilder.build_chat_options() # Simple chat, no tools
OptionBuilder.quick() # Fast one-off queries
# Effort and thinking helpers
OptionBuilder.with_opus()
|> OptionBuilder.with_effort(:max)
|> OptionBuilder.with_thinking(%{type: :adaptive})Examples
The examples/ directory contains runnable demonstrations.
Mix Task Example (Start Here)
If you want to integrate Claude into your own Mix project, see the mix_task_chat example — a complete working app with Mix tasks:
cd examples/mix_task_chat
mix deps.get
mix chat "Hello, Claude!" # Streaming response
mix chat --interactive # Multi-turn conversation
mix ask -q "What is 2+2?" # Script-friendly outputScript Examples
# Run all examples
bash examples/run_all.sh
# Run a specific example
mix run examples/basic_example.exs
mix run examples/streaming_tools/quick_demo.exs
mix run examples/hooks/basic_bash_blocking.exsKey Examples:
mix_task_chat/- Full Mix task integration (streaming + interactive chat)basic_example.exs- Minimal SDK usagestreaming_tools/quick_demo.exs- Real-time streaminghooks/complete_workflow.exs- Full hooks integrationsdk_mcp_tools_live.exs- Custom MCP toolsmax_effort_opus_live.exs- Opus:maxeffort (not inrun_all.sh)advanced_features/agents_live.exs- Multi-agent workflowsadvanced_features/subagent_spawning_live.exs- Parallel subagent coordinationadvanced_features/web_tools_live.exs- WebSearch and WebFetch
Full Application Examples
Complete Mix applications demonstrating production-ready SDK integration patterns:
| Example | Description | Key Features |
|---|---|---|
phoenix_chat/ | Real-time chat with Phoenix LiveView | LiveView, Channels, streaming responses, session management |
document_generation/ | AI-powered Excel document generation | elixlsx, natural language parsing, Mix tasks |
research_agent/ | Multi-agent research coordination | Agent tool, subagent tracking via hooks, parallel execution |
skill_invocation/ | Skill tool usage and tracking | Skill definitions, hook-based tracking, GenServer state |
email_agent/ | AI-powered email assistant | SQLite storage, rule-based processing, natural language queries |
# Run Phoenix Chat
cd examples/phoenix_chat && mix deps.get && mix phx.server
# Visit http://localhost:4000
# Run Document Generation
cd examples/document_generation && mix deps.get && mix generate.demo
# Run Research Agent
cd examples/research_agent && mix deps.get && mix research "quantum computing"
# Run Skill Invocation demo
cd examples/skill_invocation && mix deps.get && mix run -e "SkillInvocation.demo()"
# Run Email Agent
cd examples/email_agent && mix deps.get && mix email.assistant "find emails from last week"Guides
| Guide | Description |
|---|---|
| Getting Started | Installation, authentication, and first query |
| Streaming | Real-time streaming and typewriter effects |
| Hooks | Lifecycle hooks for tool control |
| MCP Tools | In-process tool definitions with MCP |
| Permissions | Fine-grained permission controls |
| Configuration | Complete options reference |
| Agents | Custom agent personas |
| Sessions | Session management and persistence |
| Testing | Mock system and testing patterns |
| Error Handling | Error types and recovery |
Upgrading
For breaking changes and migration notes, see CHANGELOG.md.
0.12.0 breaking changes:
Transport.Portremoved. The built-in common transport lane now runs throughExternalRuntimeTransport.Transport.-
Custom transport injection removed from
Client,Query, and common CLI lanes. UseOptions.execution_surfacefor SSH/local routing instead. -
Transport error tuple shape updated: low-level failures now use
{:error, {:transport, reason}}instead of bare{:error, reason}. -
String prompts now delivered via stdin (
--input-format stream-json) instead of CLI arg (-- prompt).
0.11.0 breaking changes:
--printflag removed from all modules. All queries now use--output-format stream-jsonexclusively.--agentsCLI flag removed. Agents are now sent via theinitializecontrol request. UseOptions.agents_for_initialize/1.AgentsFilemodule deleted. Remove anyagents_temp_file_max_age_secondsconfig.Clientstate is now adefstruct. Four deprecated fields removed:current_model,pending_model_change,current_permission_mode,pending_inbound_count.-
All 12 hook events are now supported (6 new:
post_tool_use_failure,notification,subagent_start,permission_request,session_start,session_end).
0.10.0 fix (resume turn persistence):
resume/3no longer uses--print --resume(one-shot mode that dropped intermediate turns). It now uses--resumewith--input-format stream-json, preserving the full conversation history across resume calls.-
Updated default Opus model to
claude-opus-4-6.
0.9.0 breaking change (streaming):
-
Stream event wrappers now require
uuidandsession_id. Missing keys raise and terminate the streaming client. -
If you emit or mock
stream_eventwrappers, include both fields (custom transports, fixtures, tests).
That strict requirement applies to the common CLI streaming parser. The
SDK-local control lane still accepts incomplete stream_event wrappers and
surfaces missing metadata as nil, which keeps control-client transports
forward-compatible without weakening the core streaming contract.
Additional Resources:
- CHANGELOG.md - Version history and release notes
- HexDocs - API documentation
- Claude Code SDK - Upstream documentation
License
MIT License - see LICENSE for details.
Model Selection Contract
/home/home/p/g/n/claude_agent_sdk no longer owns active model-selection policy. Model catalog visibility, defaulting, validation, and failure semantics are owned by /home/home/p/g/n/cli_subprocess_core through CliSubprocessCore.ModelRegistry.resolve/3, CliSubprocessCore.ModelRegistry.validate/2, and CliSubprocessCore.ModelRegistry.default_model/2.
The Claude SDK now treats model values as resolved payload data and limits its responsibility to option shaping and command rendering.
Session History And Intervention
The Claude runtime lane now publishes an honest session-control contract for orchestration layers.
ClaudeAgentSDK.Runtime.CLI.capabilities/0includes:session_history,:session_resume,:session_pause, and:session_interveneClaudeAgentSDK.Runtime.CLI.list_provider_sessions/1standardizes transcript-history entries for upper layers without flattening the richer native Claude session model
This is meant for recovery and operator-control flows, not blind retries. The caller should resume the same session when possible and only replay work when a real continuation handle is unavailable.