ASM runs providers through a small backend contract instead of provider-specific driver/parser stacks.
Backend Roles
ASM.ProviderBackend.Core is the required baseline backend:
- starts
CliSubprocessCore.Session - works in
execution_mode: :local - works in
execution_mode: :remote_node - uses the built-in provider profiles from
cli_subprocess_core
ASM.ProviderBackend.SDK is optional and additive:
- starts provider runtime kits when they are installed locally
- works only in
execution_mode: :local - preserves the same session/run/event model as the core backend
- preserves the same normalized execution-surface contract as the core backend for local subprocess and SSH lanes
- never becomes a required dependency for ASM itself
- may exist without any separate ASM provider-native namespace, as with Antigravity today
- promotes the Codex app-server host-tool lane when Codex provider options
request
app_server: true,host_tools: [...], ordynamic_tools: [...]
Common Contract
Both backends satisfy the same ASM.ProviderBackend behaviour:
@callback start_run(map()) :: {:ok, pid(), ASM.ProviderBackend.Info.t()} | {:error, term()}
@callback send_input(pid(), iodata(), keyword()) :: :ok | {:error, term()}
@callback end_input(pid()) :: :ok | {:error, term()}
@callback interrupt(pid()) :: :ok | {:error, term()}
@callback close(pid()) :: :ok
@callback subscribe(pid(), pid(), reference()) :: :ok | {:error, term()}
@callback info(pid()) :: ASM.ProviderBackend.Info.t()That keeps ASM.Run.Server lane-agnostic after resolution.
After subscribe/3, the kernel receives %ASM.ProviderBackend.Event{} messages
instead of matching provider or transport mailbox tags directly. The envelope
can carry either a normalized CliSubprocessCore.Event or an ASM-native event
such as :host_tool_requested from the Codex app-server lane.
ASM.ProviderBackend.Info is the ASM-owned metadata contract consumed by the
kernel:
providerlanebackendruntimecapabilitiessessionobservability
The session field may still contain backend/runtime details, but backend
adapters must strip raw delivery tags such as session_event_tag before that
data crosses into ASM kernel state.
Backend Selection
ASM.ProviderRegistry.resolve/2 chooses which backend module to use.
lane: :coreresolves toASM.ProviderBackend.Corewithout probing or loading optional provider SDK moduleslane: :sdkresolves toASM.ProviderBackend.SDKonly when the runtime kit is available locallylane: :autoprefers the SDK lane when available and otherwise falls back to the core lane
An explicit lane: :sdk must fail clearly when the SDK runtime kit is
unavailable. It must not silently fall back to the core lane. Fallback is only
valid for documented lane: :auto cases.
The public error remains %ASM.Error{kind: :config_invalid, domain: :config},
with %ASM.ProviderBackend.SdkUnavailableError{} in error.cause for the
SDK-unavailable category. Tests and callers should assert that category instead
of matching human-readable error text.
execution_mode is applied after lane discovery. In the landed Phase 3
boundary, remote execution always uses the core backend even if :auto
preferred :sdk.
That split is intentional:
- local
:coreand local:sdkboth preserve the same normalizedexecution_surfacecontract and itsExecutionSurfacemetadata :remote_noderemains a separate ASM execution mode, not another execution surface
Observability
Backend choice is visible in run/event metadata:
requested_lanepreferred_lanelanebackendexecution_modelane_reasonlane_fallback_reason
That metadata is merged into both streamed %ASM.Event{} values and the final
%ASM.Result.metadata projection.
Provider-Native Extensions Stay Above The Backend Boundary
ASM.ProviderRegistry and the backend modules stop at normalized lane/runtime
selection.
Provider-native capability reporting now lives under
ASM.Extensions.ProviderSDK:
ASM.Extensions.ProviderSDK.extension/1ASM.Extensions.ProviderSDK.provider_extensions/1ASM.Extensions.ProviderSDK.available_provider_extensions/1ASM.Extensions.ProviderSDK.provider_capabilities/1ASM.Extensions.ProviderSDK.capability_report/0
That keeps backend discovery focused on :core versus :sdk, while
provider-native surfaces such as Claude control semantics, Codex app-server,
Amp permissions/MCP, Cursor mode/worktree controls,
and Antigravity runtime options remain explicit optional seams above the
kernel.
Four providers currently have explicit extension namespaces: Claude, Codex,
Amp, and Cursor. Amp and Cursor start with strict
derive_options/2 helpers; those helpers derive only common placement/session
data and require provider-native settings in native_overrides.
Antigravity is one of five first-party providers and is the current Google
coding-agent SDK route. Gemini CLI remains retired; gemini_ex is a distinct
model API SDK outside ASM's CLI provider registry. Antigravity has a core
profile and optional antigravity_cli_sdk runtime lane, but no separate
ASM.Extensions.ProviderSDK.Antigravity namespace yet. Provider SDK reports
therefore include :antigravity with an empty registered_namespaces list.
For Claude specifically, ASM.Extensions.ProviderSDK.Claude can bridge ASM
config into ClaudeAgentSDK.Client, but the resulting control calls still live
on ClaudeAgentSDK.Client.* rather than the backend contract.
Codex App-Server Host Tools
Codex remains on the normal SDK exec runtime unless app-server is requested. The app-server path:
- starts
Codex.AppServerwithexperimental_api: true - converts
ASM.HostTool.Specvalues into CodexdynamicTools - starts or resumes a Codex thread using the app-server transport
- maps
DynamicToolCallRequestedto:host_tool_requested - executes registered run
toolsautomatically when present - responds with
Codex.AppServer.respond/3 - emits
:host_tool_completed,:host_tool_failed, or:host_tool_denied
The app-server backend still reports through the same ASM.ProviderBackend
contract and emits normal core assistant/result events for stream consumers.
Broader Codex app-server APIs such as MCP, realtime, voice, plugin, and
filesystem helpers remain in codex_sdk or the provider SDK extension seam.
This is Codex-native behavior, not proof of all-provider ASM host-tool support.
Generic ASM tools: must remain rejected or provider-native until the
all-provider host-tool admission checklist is complete.
Host-tool declaration, request, and response metadata reject secret-shaped
fields such as API-key, token, auth, credential, password, and bearer keys.
Claude Backend-Specific Model Inputs
Claude is the first backend where ASM now forwards backend-specific model inputs into the shared core model registry.
The relevant Claude provider fields are:
:provider_backend:external_model_overrides:anthropic_base_url:anthropic_auth_token:model:allow_unknown_model(defaultfalse) — pass a Claude model newer than the shared registry straight through to the CLI--modelinstead of erroring. Common across every provider (:codex,:amp,:antigravity,:cursoraccept the same option with the same behavior), not Claude-specific.
Those values are still treated as value carriers only.
ASM does not validate Ollama models itself and does not build Ollama CLI env
itself. It forwards those values to
CliSubprocessCore.ModelRegistry.build_arg_payload/3, then passes the resolved
payload to either the core Claude profile or ClaudeAgentSDK.Options.
In governed ASM mode, Claude env/model defaults, native login state,
anthropic_auth_token, command paths, cwd, session refs, and target refs cannot
be reused as governed authority. Governed Claude starts fail closed until a
verified provider-auth materializer supplies the launch envelope.
Codex Backend-Specific Model Inputs
Codex now follows the same pattern for backend-aware model resolution.
Relevant Codex provider fields:
:provider_backend:model_provider:oss_provider:ollama_base_url:ollama_http:ollama_timeout_ms:model:reasoning_effort
The current shared catalog exposes gpt-5.6-sol as the live default plus
gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.4, gpt-5.4-mini, and
the text-only ChatGPT Pro preview gpt-5.3-codex-spark. ASM does not add a
gpt-5.6 alias. Sol and Terra admit :max and :ultra; Luna admits :max
but rejects :ultra, while Spark supports :low through :xhigh and defaults
to :high through the shared registry contract.
For the current local Ollama path, ASM callers should use:
provider_backend: :ossoss_provider: "ollama"model: "<local model id>"
ASM still does not invent Codex backend flags locally. It forwards those inputs to
CliSubprocessCore.ModelInput.normalize/3, which in turn resolves through
CliSubprocessCore.ModelRegistry.build_arg_payload/3 when the caller supplied
raw knobs instead of a payload. ASM then passes the finalized payload into
either the core Codex profile or Codex.Options / Codex.Thread.Options on
the SDK lane.
For Codex/Ollama, the shared core keeps gpt-oss:20b as the default validated
example model, but it also accepts other installed local model ids such as
llama3.2. The degraded-mode distinction for those non-default models is
metadata-driven rather than a hard rejection in ASM.
If a custom ollama_base_url is supplied, the finalized payload carries it in
payload-owned runtime data (CODEX_OSS_BASE_URL). Raw Ollama roots are
normalized to the OpenAI-compatible /v1 base for Codex, so downstream core
and SDK transports can consume the payload alone after normalization.
Amp Backend-Specific Model Inputs
Amp is intentionally payload-only for model input in the current stack.
Relevant Amp provider fields:
:model_payloadonly
amp_sdk does not expose a second raw model/backend surface. ASM finalizes any
shared-core model selection before the Amp SDK boundary, and AmpSdk.Types.Options.validate!/1
only canonicalizes a supplied payload rather than inventing another resolution
path inside the Amp repo. ASM forwards :transport_headless_timeout_ms
separately from Amp's rearming :stream_timeout_ms, so transport orphan
reaping remains finite even when the stream timeout is configured differently.
In governed ASM mode, Amp env/model defaults and native CLI auth remain standalone-only. Governed Amp starts fail closed until a verified provider-auth materializer supplies explicit launch authority.
Antigravity Backend-Specific Inputs
Antigravity runs on both ASM lanes:
:core->CliSubprocessCore.ProviderProfiles.Antigravity:sdk->AntigravityCliSdk.Runtime.CLIwhenantigravity_cli_sdkis installed
Relevant Antigravity provider fields:
:model:sandbox:dangerously_skip_permissions:conversation:continue:add_dirs:print_timeout:log_file
ASM common :cwd remains the workspace placement key. The Antigravity core
profile and SDK lane render the prompt with agy --print <prompt>. The CLI's
plain-text stdout is normalized into assistant deltas and final result text by
the core/SDK runtime layers. ASM also forwards
:transport_headless_timeout_ms into AntigravityCliSdk.Options, preserving
the SDK's finite transport orphan-reap bound independently of its stream idle
timeout.
Permission mapping:
- ASM
:default-> Antigravity:default-> no extra permission flag - ASM
:bypass-> Antigravity:bypass->--dangerously-skip-permissions
Auth and command path ownership:
ANTIGRAVITY_CLI_PATHASM_ANTIGRAVITY_MODEL
In governed ASM mode, Antigravity follows the same fail-closed family as Claude/Amp/Cursor. Provider auth, command, cwd, env, execution surface, and target authority must be materialized by the owner boundary; ASM does not promote local Antigravity login state into governed authority.
Cursor Backend-Specific Inputs
Cursor runs on both ASM lanes:
:core->CliSubprocessCore.ProviderProfiles.Cursor:sdk->CursorCliSdk.Runtime.CLIwhencursor_cli_sdkis installed
Relevant Cursor provider fields:
:model:mode:sandbox:approve_mcps:worktree:worktree_base:skip_worktree_setup:plugin_dirs:headers
ASM common :cwd is the only workspace placement key. The Cursor core profile
and SDK both render it as Cursor --workspace <cwd> while also using it as the
process cwd. There is no ASM :workspace option.
Permission mapping:
- ASM
:default-> Cursor:default-> no extra permission flag - ASM
:bypass-> Cursor:bypass->--force - ASM
:plan-> Cursor:plan->--mode plan
Cursor :ask is ASM.Options.Cursor mode: :ask, not an ASM permission mode.
Auth and command path ownership:
CURSOR_API_KEYCURSOR_CLI_PATHASM_CURSOR_MODEL
In governed ASM mode, Cursor follows the same fail-closed family as Claude/Amp. Provider auth, command, cwd, env, execution surface, and target authority must be materialized by the owner boundary; ASM does not promote local Cursor login state into governed authority.