# Provider SDK Extensions Guide
Phase 3 keeps an explicit optional layer for provider-native surfaces above
ASM's normalized kernel.
## Kernel Versus Extension Split
Normalized kernel surfaces stay where they already belong:
- `ASM`
- `ASM.Stream`
- `ASM.Result`
- `ASM.ProviderRegistry`
Those APIs continue to own:
- provider selection
- lane selection
- normalized event/result projection
- session/run orchestration
Provider-native extension discovery now lives under
`ASM.Extensions.ProviderSDK`.
Schema ownership follows the same split:
- ASM owns orchestration, provider-option, event, and remote-node envelopes.
- provider-native runtime and protocol schemas stay local to the owning SDK
repo.
- `NimbleOptions` can still front the public ASM keyword API, but `Zoi` is the
canonical schema layer underneath new dynamic boundaries.
## Dependency Model
ASM keeps `cli_subprocess_core` required and all provider SDK packages
optional.
- depending only on `:agent_session_manager` gives you the common ASM surface
- adding `:claude_agent_sdk` or `:codex_sdk` activates the matching SDK lane
and the matching provider-native namespace when it is loadable locally
- adding `:gemini_cli_sdk` or `:amp_sdk` activates only SDK lane/runtime-kit
availability today; Gemini and Amp still have no separate ASM native
namespace
- declaring the optional dependency is the only client-app activation step;
ASM performs discovery and activation automatically
ASM handles those optional dependencies through runtime-checked module loading
and explicit extension/backend boundaries. The kernel never relies on
compile-time warning suppression to reference provider SDK packages.
## Why This Is Separate
`ASM.ProviderRegistry` keeps normalized discovery on its existing surfaces:
- `provider_info/1`: `available_lanes`, `core_capabilities`, `sdk_capabilities`
- `lane_info/2`: `preferred_lane`, `backend`
- `resolve/2`: effective `lane`, `backend`
`ASM.Extensions.ProviderSDK` reports provider-native extension facts:
- explicit optional namespace modules
- provider-native capability inventory
- whether the backing SDK package is loadable locally
That keeps lane discovery and provider-native surface discovery from collapsing
into one API.
## Discovery API
```elixir
alias ASM.Extensions.ProviderSDK
ProviderSDK.extensions()
ProviderSDK.available_extensions()
{:ok, claude_extension} = ProviderSDK.extension(:claude)
{:ok, active_claude_extensions} = ProviderSDK.available_provider_extensions(:claude)
{:ok, codex_extensions} = ProviderSDK.provider_extensions(:codex)
{:ok, codex_native_caps} = ProviderSDK.provider_capabilities(:codex)
{:ok, gemini_report} = ProviderSDK.provider_report(:gemini)
report = ProviderSDK.capability_report()
```
Current built-in namespaces:
- `ASM.Extensions.ProviderSDK.Claude`
- `ASM.Extensions.ProviderSDK.Codex`
These root modules are the namespace anchors for optional provider-native
helpers.
Activation-aware discovery follows a separate rule:
- `extensions/0` is the static Claude/Codex native-extension catalog
- `provider_extensions/1` is the static native-extension catalog for one
provider
- `available_extensions/0` reports which of those namespaces are active for the
currently installed optional deps
- `available_provider_extensions/1` reports the active native-extension subset
for one provider
- `provider_report/1` and `capability_report/0` always include all ASM
providers, including Gemini and Amp, and show whether each provider SDK
runtime is available plus any active native namespace inventory
- `registered_namespaces` and `registered_extensions` keep the static catalog
visible even when a provider currently composes only through the common
surface
- Gemini and Amp may therefore report `sdk_available?: true` with
`namespaces: []`, because they currently compose only through the common ASM
surface
Claude now exposes an explicit bridge into the SDK-local control family:
- `ASM.Extensions.ProviderSDK.Claude.sdk_options/2`
- `ASM.Extensions.ProviderSDK.Claude.sdk_options_for_session/3`
- `ASM.Extensions.ProviderSDK.Claude.start_client/3`
- `ASM.Extensions.ProviderSDK.Claude.start_client_for_session/4`
Those helpers do not redefine Claude control semantics. They only:
- derive `ClaudeAgentSDK.Options` from ASM-style config or session defaults
- preserve ASM execution-surface placement on the resulting SDK options
- keep Claude-native options in a separate `native_overrides` bag
- start `ClaudeAgentSDK.Client` when callers explicitly opt into the SDK-local
control family
Example:
```elixir
alias ASM.Extensions.ProviderSDK.Claude
asm_opts = [
provider: :claude,
cwd: File.cwd!(),
execution_environment: [permission_mode: :plan],
model: "sonnet",
execution_surface: [
surface_kind: :ssh_exec,
transport_options: [destination: "buildbox-a", port: 2222]
]
]
native_overrides = [
enable_file_checkpointing: true,
thinking: %{type: :adaptive}
]
{:ok, client} =
Claude.start_client(
asm_opts,
native_overrides,
control_request_timeout_ms: 5_000
)
:ok = ClaudeAgentSDK.Client.set_permission_mode(client, :plan)
```
The normalized ASM APIs stay unchanged. Only the optional extension crosses
into the Claude-native client surface.
Codex now exposes a similarly narrow bridge into the SDK-local app-server
entry path:
- `ASM.Extensions.ProviderSDK.Codex.codex_options/2`
- `ASM.Extensions.ProviderSDK.Codex.codex_options_for_session/3`
- `ASM.Extensions.ProviderSDK.Codex.thread_options/2`
- `ASM.Extensions.ProviderSDK.Codex.thread_options_for_session/3`
- `ASM.Extensions.ProviderSDK.Codex.connect_app_server/3`
- `ASM.Extensions.ProviderSDK.Codex.connect_app_server_for_session/4`
Those helpers do not re-model app-server, MCP, realtime, or voice as ASM
kernel APIs. They only:
- derive `Codex.Options` from ASM-style config or session defaults
- derive `Codex.Thread.Options` from ASM-style config or session defaults
- preserve ASM execution-surface placement on `Codex.Options`
- keep Codex-native global or thread-only fields in explicit override bags
- start `Codex.AppServer` when callers explicitly opt into the SDK-local
app-server family
Example:
```elixir
alias ASM.Extensions.ProviderSDK.Codex
alias Codex, as: CodexSDK
{:ok, conn} =
Codex.connect_app_server(
[
provider: :codex,
model: "gpt-5.4",
reasoning_effort: :high,
execution_environment: [permission_mode: :default],
execution_surface: [
surface_kind: :ssh_exec,
transport_options: [destination: "codex-host-1"],
lease_ref: "lease-42"
]
],
[model_personality: :pragmatic],
experimental_api: true
)
{:ok, thread_opts} =
Codex.thread_options(
[
provider: :codex,
cwd: "/repo",
execution_environment: [permission_mode: :default],
approval_timeout_ms: 45_000,
output_schema: %{"type" => "object"}
],
transport: {:app_server, conn},
personality: :pragmatic
)
{:ok, codex_opts} =
Codex.codex_options(
[provider: :codex, model: "gpt-5.4"],
model_personality: :pragmatic
)
{:ok, thread} = CodexSDK.start_thread(codex_opts, thread_opts)
```
ASM intentionally does not normalize Codex `:auto` onto `Codex.Thread.Options`
because the current Codex workspace-write auto-edit path dirties repo roots
with a `.codex` artifact. Keep ASM's Codex bridge on `:default` or `:bypass`,
or use `codex_sdk` directly when you explicitly need provider-native
workspace-write behavior.
## Optional-Loading Rules
- discovery calls are always available from ASM
- each extension reports `sdk_available?`
- `sdk_available?` only means the backing SDK package is loadable locally
- `provider_capabilities/1` reports only active native capabilities for the
current dependency set
- richer provider-native APIs still live in the provider SDK repos
- ASM does not re-model those richer APIs in the kernel
- the Claude bridge keeps ASM config and Claude-native config in separate
arguments on purpose
- local `:core` and local `:sdk` lanes preserve the same normalized
`execution_surface` contract; `execution_mode: :remote_node`
remains a separate ASM-only rule
- ASM-derived fields such as `:cwd`, `:execution_environment`, `:model`,
`:max_turns`, and `:timeout_ms` must stay in ASM config and are rejected
from `native_overrides`
- the Codex bridge follows the same rule for ASM-derived fields such as
`:model`, `:reasoning_effort`, `:cwd`, `:approval_timeout_ms`, and
`:output_schema`
- the Codex bridge is intentionally useful only at the app-server entry seam;
the actual app-server, MCP, realtime, and voice APIs remain in `codex_sdk`
## Current Native Capability Inventory
Claude namespace:
- `:control_client`
- `:control_protocol`
- `:hooks`
- `:permission_callbacks`
Codex namespace:
- `:app_server`
- `:mcp`
- `:realtime`
- `:voice`
These are capability labels for discovery and documentation only in this
foundation slice. They are not new normalized kernel APIs.
## Session-Control Extensions
Provider SDK extensions may now publish optional callbacks and capability markers for:
- session history listing
- exact or latest-session resume
- pause
- operator intervention
Those extensions are only valid when the provider runtime can back them with a real native surface.
ASM now keeps the provider-native recovery handles in session/run state so a caller can choose exact
resume before it falls back to replaying prompts.