Multi-Provider Setup

Copy Markdown View Source

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.

ProviderAtomAdapterDescription
Claude:claudeClaudeAdapterAnthropic Claude models
Codex:codexCodexAdapterOpenAI Codex models
Amp:ampAmpAdapterAmp 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"
  ]
end

If 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/ use Mix.install with 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:

AliasFull 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.

OptionClaudeCodexAmp
permission_mode--permission-mode CLI flagfull_auto / dangerously_bypassdangerously_allow_all
max_turns--max-turns N (nil=unlimited)RunConfig max_turns (nil=SDK default 10)ignored (CLI-enforced)
system_promptsystem_prompt on Optionsbase_instructions on Thread.Optionsstored in state
sdk_optsmerged into ClaudeAgentSDK.Optionsmerged into Codex.Thread.Optionsmerged 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 EventDescription
:run_startedSession started (model, session_id)
:message_streamedText content delta
:tool_call_startedTool invocation started (tool_name, tool_input)
:tool_call_completedTool invocation finished (tool_name, tool_output)
:token_usage_updatedToken counts (input_tokens, output_tokens)
:message_receivedComplete message received
:run_completedSession completed (stop_reason)
:run_failedSession failed (error_code, error_message, optional provider_error)
:run_cancelledSession cancelled
:error_occurredError 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:

  1. Resolves the provider and builds an adapter spec ({Module, opts} tuple)
  2. Delegates to AgentSessionManager.StreamSession.start/1 which:
    • Starts an InMemorySessionStore automatically
    • Starts the adapter from the spec
    • Launches a task that calls SessionManager.run_once/4
    • Returns a lazy event stream with 120s idle timeout
  3. 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.