Prompt Runner SDK supports three LLM providers through AgentSessionManager. The PromptRunner.Session module starts the appropriate adapter, runs a single prompt, normalizes the event stream, and cleans up.
| Provider | Atom | Adapter | Description |
|---|---|---|---|
| Claude | :claude | ClaudeAdapter | Anthropic Claude models |
| Codex | :codex | CodexAdapter | OpenAI Codex models |
| Amp | :amp | AmpAdapter | Amp models |
Required Dependencies
Prompt Runner SDK declares the three provider SDKs as optional dependencies.
You must add the SDK(s) you intend to use to your own mix.exs:
# mix.exs
defp deps do
[
{:prompt_runner_sdk, "~> 0.4"},
# Add the provider(s) you use:
{:claude_agent_sdk, "~> 0.13.0"}, # for provider: "claude"
{:codex_sdk, "~> 0.9.0"}, # for provider: "codex"
{:amp_sdk, "~> 0.3"}, # for provider: "amp"
]
endIf a required SDK is missing at runtime, the runner prints an actionable error with the exact dependency to add.
Standalone examples: The example scripts in
examples/useMix.installwith a path dependency, which pulls all transitive deps (including the optional SDKs) automatically. No extra deps are needed when running examples from the source tree.
Choosing a Provider
Set the provider key in the llm section:
%{
model: "haiku",
llm: %{provider: "claude"}
}If provider is omitted, it defaults to "claude".
Per-Prompt Overrides
Switch providers for individual prompts using prompt_overrides:
%{
llm: %{
provider: "claude",
model: "haiku",
prompt_overrides: %{
"02" => %{provider: "codex", model: "gpt-5.3-codex"},
"04" => %{provider: "amp"}
}
}
}Prompts 01 and 03 use Claude (the default). Prompt 02 uses Codex. Prompt 04 uses Amp.
Overrides are deep-merged with the base config, so you only specify what changes.
Provider Details
Claude
Claude model aliases are resolved by PromptRunner.Session:
| Alias | Full Model ID |
|---|---|
"haiku" | claude-haiku-4-5-20251001 |
"sonnet" | claude-sonnet-4-5-20250929 |
"opus" | claude-opus-4-6 |
Any other string is passed through as-is (e.g., "claude-sonnet-4-5-20250929").
Claude supports allowed_tools and permission_mode:
llm: %{
provider: "claude",
model: "haiku",
permission_mode: :accept_edits,
allowed_tools: ["Read", "Write", "Edit", "Bash"],
claude_opts: %{
# Additional options passed to ClaudeAdapter
}
}Claude uses project_dir as its cwd — the Claude CLI runs in that directory.
Codex
Codex uses the project_dir as its working directory automatically. No extra cwd configuration needed.
llm: %{
provider: "codex",
model: "gpt-5.3-codex",
codex_opts: %{
# Options passed to CodexAdapter
},
codex_thread_opts: %{
reasoning_effort: :xhigh # Reasoning effort level
# Plus sandbox, approval settings, etc.
}
}Both codex_opts and codex_thread_opts are merged into the adapter options, with adapter_opts applied last.
CLI Confirmation
When reasoning_effort is configured, the runner can verify that the Codex CLI is actually using it. Set cli_confirmation to control the response:
llm: %{
provider: "codex",
model: "gpt-5.3-codex",
cli_confirmation: :warn, # :off | :warn (default) | :require
codex_thread_opts: %{reasoning_effort: :xhigh}
}With :require, the run fails if the CLI does not confirm the configured model and reasoning effort. Machine-readable audit lines are written to session logs for traceability. See Configuration Reference for details.
Amp
Amp also uses project_dir as its working directory:
llm: %{
provider: "amp",
adapter_opts: %{
# Options passed to AmpAdapter
}
}Normalized Options
These options work across all providers. The Session module passes them to the appropriate adapter, which maps them to provider-specific SDK fields.
| Option | Claude | Codex | Amp |
|---|---|---|---|
permission_mode | --permission-mode CLI flag | full_auto / dangerously_bypass | dangerously_allow_all |
max_turns | --max-turns N (nil=unlimited) | RunConfig max_turns (nil=SDK default 10) | ignored (CLI-enforced) |
system_prompt | system_prompt on Options | base_instructions on Thread.Options | stored in state |
sdk_opts | merged into ClaudeAgentSDK.Options | merged into Codex.Thread.Options | merged into AmpSdk.Types.Options |
llm: %{
provider: "claude",
model: "haiku",
permission_mode: :dangerously_skip_permissions,
max_turns: 10,
system_prompt: "You are a code assistant. Be concise.",
sdk_opts: [verbose: true]
}Normalized options always take precedence over sdk_opts.
adapter_opts
The adapter_opts map provides a provider-agnostic way to pass options to any adapter. It is merged after provider-specific options (claude_opts, codex_opts, codex_thread_opts):
llm: %{
provider: "claude",
model: "sonnet",
adapter_opts: %{max_tokens: 16384}
}adapter_opts can be set at both the root config level and within the llm section. The llm value takes precedence.
Event Format
PromptRunner.Session passes canonical AgentSessionManager events directly to the rendering pipeline. The rendering system (AgentSessionManager.Rendering) handles all event types uniformly regardless of which provider is in use:
| Canonical Event | Description |
|---|---|
:run_started | Session started (model, session_id) |
:message_streamed | Text content delta |
:tool_call_started | Tool invocation started (tool_name, tool_input) |
:tool_call_completed | Tool invocation finished (tool_name, tool_output) |
:token_usage_updated | Token counts (input_tokens, output_tokens) |
:message_received | Complete message received |
:run_completed | Session completed (stop_reason) |
:run_failed | Session failed (error_code, error_message, optional provider_error) |
:run_cancelled | Session cancelled |
:error_occurred | Error during session (error_code, error_message, optional provider_error) |
For backward compatibility, older events may only include error_message.
When available, provider_error follows this contract:
%{
provider: :codex | :amp | :claude | :gemini | :unknown,
kind: atom(),
message: String.t(),
exit_code: integer() | nil,
stderr: String.t() | nil,
truncated?: boolean() | nil
}Session Lifecycle
For each prompt execution, Session.start_stream/2:
- Resolves the provider and builds an adapter spec (
{Module, opts}tuple) - Delegates to
AgentSessionManager.StreamSession.start/1which:- Starts an
InMemorySessionStoreautomatically - Starts the adapter from the spec
- Launches a task that calls
SessionManager.run_once/4 - Returns a lazy event stream with 120s idle timeout
- Starts an
- Returns
{:ok, stream, close_fun, meta}
The returned close_fun terminates the task, adapter, and store. There is no session persistence across prompts — each prompt gets a fresh session.
Backward Compatibility
The legacy sdk key still works everywhere provider is accepted:
# Legacy (still supported)
llm: %{sdk: "claude_agent_sdk"}
# Current
llm: %{provider: "claude"}See the Configuration Reference for the full alias table.