Provider Backends Guide

Copy Markdown View Source

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: [...], or dynamic_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:

  • provider
  • lane
  • backend
  • runtime
  • capabilities
  • session
  • observability

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: :core resolves to ASM.ProviderBackend.Core without probing or loading optional provider SDK modules
  • lane: :sdk resolves to ASM.ProviderBackend.SDK only when the runtime kit is available locally
  • lane: :auto prefers 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 :core and local :sdk both preserve the same normalized execution_surface contract and its ExecutionSurface metadata
  • :remote_node remains a separate ASM execution mode, not another execution surface

Observability

Backend choice is visible in run/event metadata:

  • requested_lane
  • preferred_lane
  • lane
  • backend
  • execution_mode
  • lane_reason
  • lane_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:

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.AppServer with experimental_api: true
  • converts ASM.HostTool.Spec values into Codex dynamicTools
  • starts or resumes a Codex thread using the app-server transport
  • maps DynamicToolCallRequested to :host_tool_requested
  • executes registered run tools automatically 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 (default false) — pass a Claude model newer than the shared registry straight through to the CLI --model instead of erroring. Common across every provider (:codex, :amp, :antigravity, :cursor accept 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: :oss
  • oss_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_payload only

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:

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_PATH
  • ASM_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:

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_KEY
  • CURSOR_CLI_PATH
  • ASM_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.