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 any provider SDK activates the matching SDK lane and, when one is registered, the matching provider-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)
report = ProviderSDK.capability_report()Current built-in namespaces:
ASM.Extensions.ProviderSDK.AmpASM.Extensions.ProviderSDK.ClaudeASM.Extensions.ProviderSDK.CodexASM.Extensions.ProviderSDK.Cursor
These root modules are the namespace anchors for optional provider-native helpers.
Antigravity is a first-party provider with core and optional SDK runtime lanes,
but it does not currently register an
ASM.Extensions.ProviderSDK.Antigravity namespace. Direct SDK-lane execution
uses AntigravityCliSdk.Runtime.CLI, and direct SDK ownership remains in the
antigravity_cli_sdk package.
ASM.Extensions.ProviderSDK.Cursor is backed by cursor_cli_sdk when that
dependency is present. Cursor also keeps the common core lane through
cli_subprocess_core.
Activation-aware discovery follows a separate rule:
extensions/0is the static registered 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 Cursor, Amp, and Antigravity, 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- Cursor and Amp start with strict derivation helpers. They still have
explicit namespaces so Cursor mode/MCP/plugin settings and Amp
permissions/MCP/skills have one provider-native home.
Amp derivation forwards both
:transport_timeout_msas:stream_timeout_msand the independent:transport_headless_timeout_msorphan-reap bound. Antigravity has no derivation helper in this namespace layer yet; its SDK lane is available through the runtime kit when installed.
The first argument to each derive_options/2 helper is checked with strict common ASM preflight. Provider-native
keys in that map are rejected. Native provider data must be passed explicitly in
native_overrides or through the provider SDK directly.
Claude now exposes an explicit bridge into the SDK-local control family:
ASM.Extensions.ProviderSDK.Claude.derive_options/2ASM.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",
reasoning_effort: :high,
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.derive_options/2ASM.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
The normalized ASM backend has one promoted Codex app-server path: host dynamic
tools. When a Codex run requests app_server: true, host_tools: [...], or
dynamic_tools: [...], ASM.ProviderBackend.SDK starts app-server, renders the
tool specs, handles DynamicToolCallRequested, and emits ASM host-tool events.
Other Codex app-server APIs still belong to codex_sdk or this explicit
extension seam.
Example:
alias ASM.Extensions.ProviderSDK.Codex
alias Codex, as: CodexSDK
{:ok, conn} =
Codex.connect_app_server(
[
provider: :codex,
model: "gpt-5.6-sol",
reasoning_effort: :max,
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.6-sol"],
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.
Cursor exposes the same strict derivation shape for cursor_cli_sdk:
alias ASM.Extensions.ProviderSDK.Cursor
asm_common = [
provider: :cursor,
model: "composer-2.5-fast",
cwd: File.cwd!(),
execution_surface: [surface_kind: :local_subprocess]
]
native_overrides = [
mode: :ask,
approve_mcps: true,
plugin_dirs: ["priv/cursor/plugins"]
]
{:ok, cursor_options} =
Cursor.derive_options(asm_common, native_overrides: native_overrides)The resulting struct is %CursorCliSdk.Options{}. The SDK lane uses
CursorCliSdk.Runtime.CLI; the core lane remains
CliSubprocessCore.ProviderProfiles.Cursor.
Cursor-native settings such as :mode, :sandbox, :approve_mcps, worktree
controls, plugin dirs, and headers belong in native_overrides. ASM-derived
fields such as :model, :cwd, :execution_surface, :timeout_ms, and
launch auth placement stay in ASM config.
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
- Antigravity reports as an ASM provider in
capability_report/0withregistered_namespaces: []until a real native-extension namespace lands - 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,:effort,:max_turns, and:timeout_msmust stay in ASM config and are rejected fromnative_overrides; callers select effort with ASM's:reasoning_effort - 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 at the app-server entry seam; ASM
only promotes host dynamic tools into the backend, while the broader
app-server, MCP, realtime, and voice APIs remain in
codex_sdk - the Cursor bridge derives
CursorCliSdk.Options; Cursor-native settings stay innative_overrides
Current Native Capability Inventory
Claude namespace:
:control_client:control_protocol:hooks:permission_callbacks
Codex namespace:
:app_server:mcp:realtime:voice
Amp namespace:
:mcp:permissions:skills:threads
Cursor namespace:
:mcp:plugins:worktrees
Antigravity currently has no ProviderSDK namespace capability inventory. Its
core and SDK runtime capability surfaces are reported through
ASM.ProviderFeatures and ASM.ProviderRegistry, not through this native
extension catalog.
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.