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:
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.
NimbleOptionscan still front the public ASM keyword API, butZoiis 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_managergives you the common ASM surface - adding
:claude_agent_sdkor:codex_sdkactivates the matching SDK lane and the matching provider-native namespace when it is loadable locally - adding
:gemini_cli_sdkor:amp_sdkactivates 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_capabilitieslane_info/2:preferred_lane,backendresolve/2: effectivelane,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
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:
These root modules are the namespace anchors for optional provider-native helpers.
Activation-aware discovery follows a separate rule:
extensions/0is the static Claude/Codex native-extension catalogprovider_extensions/1is the static native-extension catalog for one provideravailable_extensions/0reports which of those namespaces are active for the currently installed optional depsavailable_provider_extensions/1reports the active native-extension subset for one providerprovider_report/1andcapability_report/0always include all ASM providers, including Gemini and Amp, and show whether each provider SDK runtime is available plus any active native namespace inventoryregistered_namespacesandregistered_extensionskeep the static catalog visible even when a provider currently composes only through the common surface- Gemini and Amp may therefore report
sdk_available?: truewithnamespaces: [], 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/2ASM.Extensions.ProviderSDK.Claude.sdk_options_for_session/3ASM.Extensions.ProviderSDK.Claude.start_client/3ASM.Extensions.ProviderSDK.Claude.start_client_for_session/4
Those helpers do not redefine Claude control semantics. They only:
- derive
ClaudeAgentSDK.Optionsfrom ASM-style config or session defaults - preserve ASM execution-surface placement on the resulting SDK options
- keep Claude-native options in a separate
native_overridesbag - start
ClaudeAgentSDK.Clientwhen callers explicitly opt into the SDK-local control family
Example:
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/2ASM.Extensions.ProviderSDK.Codex.codex_options_for_session/3ASM.Extensions.ProviderSDK.Codex.thread_options/2ASM.Extensions.ProviderSDK.Codex.thread_options_for_session/3ASM.Extensions.ProviderSDK.Codex.connect_app_server/3ASM.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.Optionsfrom ASM-style config or session defaults - derive
Codex.Thread.Optionsfrom 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.AppServerwhen callers explicitly opt into the SDK-local app-server family
Example:
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 locallyprovider_capabilities/1reports 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
:coreand local:sdklanes preserve the same normalizedexecution_surfacecontract;execution_mode: :remote_noderemains a separate ASM-only rule - ASM-derived fields such as
:cwd,:execution_environment,:model,:max_turns, and:timeout_msmust stay in ASM config and are rejected fromnative_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.